前言
在上一节我们讨论了几种不同页边界的类型后这一节我们继续回到IPdfPageEvent接口中,现在这个接口还剩下以下4个关于文档和页面的方法没有说明:
- OnOpenDocument----当文档被带打开的时候调用,一般在这个方法中初始化一些需要在整个文档中使用的资源。
- OnStartPage----当一个新的页面开启时调用,一般使用这个方法初始化一些页面需要的参数,最后要注意不要在这个方法中往文档中添加内容。
- OnEndPage----在开启新的一页之前或者文档关闭之前调用,这是为文档添加页眉页脚和水印的最佳地方。
- OnCloseDocument----在文档关闭之前调用,一般清理一些资源。
现在我们将使用这些方法解决一些经常提到的需求,比如在创建文档的过程中为每一页添加页眉。
Adding a header and a footer
现在我们回到第二节中Chapter和Section对象的列子中,我们会进行两个小的修改:定义一个art box并为PdfWriter添加页面事件,具体到代码中就是HeaderFooter实例,我们可以通过此实例为文档添加页眉和页脚,就如下图所示:
在上图中,我们在每个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"的一个具体实例:
获取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
接下来的列子是对前一个列子的扩展,主要的区别就是新加了一个功能:水印。具体的效果图见下:
由于是对前面的扩展,代码基本上差不多,只是添加了一个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文档中的页面导入到另一个文档中,如何为已经存在的文档添加一些内容,如何将不同的文档组合成一个更大的文档等等。