天道酬勤

<<iText in Action 2nd>>5.4节(Adding page events to PdfWriter)读书笔记

前言

在上一节我们讨论了几种不同页边界的类型后这一节我们继续回到IPdfPageEvent接口中,现在这个接口还剩下以下4个关于文档和页面的方法没有说明:

  • OnOpenDocument----当文档被带打开的时候调用,一般在这个方法中初始化一些需要在整个文档中使用的资源。
  • OnStartPage----当一个新的页面开启时调用,一般使用这个方法初始化一些页面需要的参数,最后要注意不要在这个方法中往文档中添加内容。
  • OnEndPage----在开启新的一页之前或者文档关闭之前调用,这是为文档添加页眉页脚和水印的最佳地方。
  • OnCloseDocument----在文档关闭之前调用,一般清理一些资源。

现在我们将使用这些方法解决一些经常提到的需求,比如在创建文档的过程中为每一页添加页眉。

Adding a header and a footer

现在我们回到第二节中Chapter和Section对象的列子中,我们会进行两个小的修改:定义一个art box并为PdfWriter添加页面事件,具体到代码中就是HeaderFooter实例,我们可以通过此实例为文档添加页眉和页脚,就如下图所示:

HeaderAndFooter

在上图中,我们在每个Chapter开始的时候将此Chapter的页码顺序加入到页脚中,而且是居中格式显示。对于页眉则交替显示文本"Movie history"(居右显示)和Chapter的标题(居左显示),下面就是具体的代码:

listing 5.19 MovieHistory2.cs

class HeaderFooter : IPdfPageEvent
{
/** Alternating phrase for the header. */
Phrase[] header = new Phrase[2];
/** Current page number (will be reset for every chapter). */
int pagenumber;

/// <summary>
/// Initialize one of the headers, based on the chapter title;
/// reset the page number.
/// </summary>
/// <param name="writer"></param>
/// <param name="document"></param>
/// <param name="paragraphPosition"></param>
/// <param name="title"></param>
public void OnChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title)
{
    header[1] = new Phrase(title.Content);
    pagenumber = 1;
}


/// <summary>
/// Adds the header and the footer.
/// </summary>
/// <param name="writer"></param>
/// <param name="document"></param>
public void OnEndPage(PdfWriter writer, Document document)
{
    Rectangle rect = writer.GetBoxSize("art");
    switch (writer.PageNumber % 2)
    {
        case 0:
            ColumnText.ShowTextAligned(writer.DirectContent, Element.ALIGN_RIGHT, header[0], rect.Right, rect.Top, 0);
            break;
        case 1:
            ColumnText.ShowTextAligned(writer.DirectContent, Element.ALIGN_LEFT, header[1], rect.Left, rect.Top, 0);
            break;

    }

    ColumnText.ShowTextAligned(writer.DirectContent, Element.ALIGN_CENTER, new Phrase(string.Format("page {0}", pagenumber)),
        (rect.Left + rect.Right) / 2, rect.Bottom - 18, 0);
}

/// <summary>
///  Initialize one of the headers.
/// </summary>
/// <param name="writer"></param>
/// <param name="document"></param>
public void OnOpenDocument(PdfWriter writer, Document document)
{
    header[0] = new Phrase("Movie history");
}


/// <summary>
/// Increase the page number.
/// </summary>
/// <param name="writer"></param>
/// <param name="document"></param>
public void OnStartPage(PdfWriter writer, Document document)
{
    pagenumber++;
}
}       

以上的代码比较好懂,我们在类中定义了两个变量:

  • header----一个包含了两个Pharse对象的数组,一个在OnOpenDocument方法中初始化,这样就可以在整个文档操作过程中使用。一个定义在OnChapter方法中
  • pagenumber----一个定义的页码数,每次创建一个Chapter对象时重置为1。

在一页完成之前没有内容通过页面事件添加到文档中,我们添加的页眉和页脚都是在OnEndPage方法实现的。这里我们要注意的是通过GetBoxSize方法获取art box,然后使用此crop box来定位页眉和页脚,但我们首先要定义crop box,要不然就会返回null值。在接下来的列子中我们会将页码加到页眉中并显示总的页码数。

Solving the “page X of Y” problem

下图就是"page X of Y"的一个具体实例:

PageXOfY

获取X的值比较容易,我们可以在OnEndPage方法中获取PdfWriter对象,然后调用其PageNumber即可,但是我们如何获取Y的值呢?在文档还没有构建好的情况下我们是不知道总的页码数,只有在写完最好一页的情况下才可以计算出来。这个问题有两个解决方案,其中一个就是通过两次构建pdf的过程来完成,这个方法在后续章节中会说明,还有一个就是通过PdfTemplate对象和Page Event完成。

在第三节学习XObject的时候我们知道除非显示的调用ReleaseTemplate方法,否则iText会将此对象一直保存在内存中直到文档关闭。利用这个特性我们可以在每一个页中添加PdfTemplate,然后等待文档的关闭,之后再将页面的总数添加到PdfTemplate中,这样即使第一页的内容已经写入到输出流PdfTemplate中的数据还是会显示在第一页中。

listing 5.20 MovieCountries1.cs

public  class TableHeader : IPdfPageEvent
{
    /** The header text. */
    string header;

    public string Header
    {
        get { return header; }
        set { header = value; }
    }

    /** The template with the total number of pages. */
    PdfTemplate total;

    public TableHeader()
    {
    }

    public TableHeader(string header)
    {
        this.header = header;
    }

    /// <summary>
    /// Fills out the total number of pages before the document is closed.
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="document"></param>
    public void OnCloseDocument(PdfWriter writer, Document document)
    {
        ColumnText.ShowTextAligned(total, Element.ALIGN_LEFT, new Phrase((writer.PageNumber - 1).ToString()), 2, 2, 0);
    }

    /// <summary>
    /// Adds a header to every page
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="document"></param>
    public void OnEndPage(PdfWriter writer, Document document)
    {
        PdfPTable table = new PdfPTable(3);
        try
        {
            table.SetWidths(new int[] { 24, 24, 2 });
            table.TotalWidth = 527;
            table.LockedWidth = true;
            table.DefaultCell.FixedHeight = 20;
            table.DefaultCell.Border = Rectangle.BOTTOM_BORDER;
            table.AddCell(header);
            table.DefaultCell.HorizontalAlignment = Element.ALIGN_RIGHT;
            table.AddCell(string.Format("Page {0} of", writer.PageNumber));
            PdfPCell cell = new PdfPCell(Image.GetInstance(total));
            cell.Border = Rectangle.BOTTOM_BORDER;
            table.AddCell(cell);
            table.WriteSelectedRows(0, -1, 34, 803, writer.DirectContent);
        }
        catch (DocumentException)
        {

            throw;
        }
    }

    /// <summary>
    /// Creates the PdfTemplate that will hold the total number of pages.
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="document"></param>
    public void OnOpenDocument(PdfWriter writer, Document document)
    {
        total = writer.DirectContent.CreateTemplate(30, 16);
    }           
}

在以上代码中:我们先在OnOpenDocument方法中定义一个PdfTemplate并设置大小为30pt*16pt,然后在OnEndPage方法中我们构建一个表格来画页眉,此表格为1行三列。第一个单元格添加的内容为country,第二个单元格添加的内容为"page X of",第三个单元格就比较特殊:我们将PdfTemplate包含在Image中,但这个时候还没有数据添加到PdfTemplate中。最后在OnCloseDocument中往PdfTemplate写入中的页码数,这样所有的页眉都引用了PdfTemplate,总的页码数也随之显示出来。这样还要注意的是当文档被关闭之前,当前页会调用NewPage方法进行一些资源释放的操作,但NewPage方法会将页码增加,所以我们需要减去1获取真正的页码数。在前面的列子我们一般通过ShowTextAligned方法往页眉和页脚写数据,但在这里我们可以通过表格的WriteSelectedRows方法在页眉中添加内容,这是比较好的一个方法,因此通过表格的架构我们可以对线,图和文本进行一些设置。在创建文档的过程中还有一个通常的需求就是添加水印。

Add a watermark

接下来的列子是对前一个列子的扩展,主要的区别就是新加了一个功能:水印。具体的效果图见下:

WaterPrint

由于是对前面的扩展,代码基本上差不多,只是添加了一个Watermark类来添加水印。

listing 5.21 MovieCountries2.cs

class Watermark : IPdfPageEvent
{
    Font FONT = new Font(Font.FontFamily.HELVETICA, 52, Font.BOLD, new GrayColor(0.75f));
     
    public void OnEndPage(PdfWriter writer, Document document)
    {
        ColumnText.ShowTextAligned(writer.DirectContentUnder, Element.ALIGN_CENTER, new Phrase("FOOBAR FILM FESTIVAL", FONT),
            297.5f, 421, writer.PageNumber % 2 == 1 ? 45 : -45);
    }    
}

以上代码中水印是以文本的形式添加的,如果我们的水印是图片则可以有多种选择:通过PdfContentByte.AddImage方法或者将其包裹在ColumnText对象抑或将其放入到表格的单元格中。当我们将页面事件中处理图片时要确认图片只创建了一次,比如在OnOpenDocument方法中创建,如果我们在OnStartPage或者OnEndPage方法中创建则可能将同样的图片添加多次,这不仅会损坏性能而且增加了文档的大小。

接下来我们会介绍一个功能:文档的每一页自动呈现出来,就如同PPT一样。

Creating a slideshow

在我们读文档的时候我们一般按某个按钮,点击鼠标或者直接滚动到下一页,不过我们可以让PDF阅览器在几秒钟之后自动过度到下一页。下面这个列子中我们设置了”Full Screen"(全屏)模式,因为我们将PDF文档当作PPT来使用。

listing 5.22 MovieSlideShow.cs

class TransitionDuration:IPdfPageEvent 
{
    public void OnStartPage(PdfWriter writer, Document document)
    {
        writer.Transition = new PdfTransition(PdfTransition.DISSOLVE, 3);
        writer.Duration = 5;
    }           
}
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create));
writer.PdfVersion = PdfWriter.VERSION_1_5;
writer.ViewerPreferences = PdfWriter.PageModeFullScreen;
writer.PageEvent = new TransitionDuration();
在OnStartPage方法中我们设置了Duration和Transition属性,Duration每一个页面呈现的时间,单位以秒计算。Transition接受一个Transition对象,其构造器有两个参数:一个为类型,一个transition的持续时间,这个时间是页面过度效果的时间和页面呈现的时间不同。iText中有很多Transition类型,具体的大家可以看源代码,每个类型的具体介绍大家就直接看书吧,我这里就不详述了。

总结

在这一节中我们通过IPdfPageEvent的4个方法学习如何添加页面页脚和水印,并介绍解决"Page X of Y"问题的方法,到这里整个的第一章就结束了,还有就是这一节的代码下载

这里我们对整个的第一章总结一下:在第一节我们介绍了基本构建块的使用其中包括:Chunk,Phrase,Paragraphs,List,ListItem,Anchors,Image,Chapter和Section对象,整个第四节中我们介绍了PdfPTable和PdfPCell类。然后在第三节中我们学会了如何使用low-level的方法添加内容(线,图形,图和文本),并学习此节中两个很重要的类:ColumnText和XObject。最后在第五节中我们通过表格事件,单元格事件和页面事件来处理一些比较常见的需求。通过以上五节的学习大家可以从头开始用iText构建pdf文档,后续的章节中我们会学习如何操作已经存在的文档:如何将一个pdf文档中的页面导入到另一个文档中,如何为已经存在的文档添加一些内容,如何将不同的文档组合成一个更大的文档等等。

posted @ 2012-07-20 19:56  JulyLuo  阅读(3315)  评论(0编辑  收藏  举报