前言

在我们将一些基本构建块(Chunk,Paragraph,Chapter等)添加到Document对象的实例中是,基本的构建块是由PdfWriter对象转换为pdf语法。在这个过程中,有一个我们很少直接使用但很重要的类:PdfDocument。这个类负责检测high-level对象,同时也负责调用IPdfPageEvent接口的页面事件(page event)。IPdfPageEvent接口包含11个方法,这11个方法分为以下两组:

  • 基本构建块相关的方法---这些方法和上一节中介绍的TableLayout方法以及CellLayout方法类似,但这些方法是由Chunk,Paragraphs,Chapters和Sections对象使用,这些方法会在这一节中详细说明。
  • 文档和页面相关的方法---这些方法是在文档打开关闭或者页面开始或者接受时调用,我们会在其他节中详细介绍。

在第一组方法中OnGenerictag方法毫无疑问是最有作用。

Generic Chunk functionality

在第二节学习Chunk对象时我们将国家代码(country code)用白色字体黑色背景呈现出来。在下图中我们用竖条将年份装饰,为IMDB画一个蓝色的椭圆背景。

Chunk对象没有标准的方法来画一个特殊的背景,但我们可以通过实现IPdfPageEvent接口中的OnGenericTag方法定义自己的功能,具体代码如下:

listing 5.8 MovieYears.cs

public void OnGenericTag(PdfWriter writer, Document document, Rectangle rect, string text)
{if ("strip".Equals(text)){Strip(writer.DirectContent, rect);}else if ("ellipse".Equals(text)){Ellipse(writer.DirectContentUnder, rect);}else{CountYear(text);}
}
public void Strip(PdfContentByte content, Rectangle rect)
{content.Rectangle(rect.Left - 1, rect.Bottom - 5f, rect.Width, rect.Height + 8);content.Rectangle(rect.Left, rect.Bottom - 2, rect.Width - 2, rect.Height + 2);float y1 = rect.Top + 0.5f;float y2 = rect.Bottom - 4;for (float f = rect.Left; f < rect.Right - 4; f += 5){content.Rectangle(f, y1, 4f, 1.5f);content.Rectangle(f, y2, 4f, 1.5f);}content.EoFill();
}public void Ellipse(PdfContentByte content, Rectangle rect)
{content.SaveState();content.SetRGBColorFill(0x00, 0x00, 0xFF);content.Ellipse(rect.Left - 3f, rect.Bottom - 5f, rect.Right + 3f, rect.Top + 3f);content.Fill();content.RestoreState();
}public void CountYear(string text)
{int count = 0;if (years.TryGetValue(text, out count)){years[text]++;}else{years.Add(text, 1);}
}

我们首先先看下OnGenericTag方法的参数:

  • writer----事件添加的PdfWriter对象
  • document----这不是Paragraph对象要添加到的Document对象实例,而是内部创建的PdfDocument类的实例,也只能当作只读使用。
  • rect----对应Chunk对象的矩形边界。
  • text----Chunk对象通过SetGenericTag方法设置的字符串。

IPdfPageEvent接口有11个方法,我们不需要全部实现。为了让OnGenericTag方法生效,要调用Chunk对象的SetGenericTag方法。具体代码如下:

listing 5.9 MovieYears.cs(continued)

PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create));
GenericTags gEvents = new GenericTags();
writer.PageEvent = gEvents;
foreach (Movie movie in sortMovies)
{p = new Paragraph(22);c = new Chunk(string.Format("{0} ", movie.Year), bold);c.SetGenericTag("strip");p.Add(c);c = new Chunk(movie.Title);c.SetGenericTag(movie.Year.ToString());p.Add(c);c = new Chunk(string.Format("( {0} minuts)  ", movie.Duration), italic);p.Add(c);c = new Chunk("IMDB", white);c.SetAnchor("http://www.imdb.com/title/tt" + movie.IMDB);c.SetGenericTag("ellipse");p.Add(c);document.Add(p);
}

在以上代码中我们首先要设置PdfWriter的PageEvent属性,然后为年份的Chunk对象调用SetGenericTag方法并传入字符串"strip",这样在Chunk对象的内容写入到文档后OnGenericTag方法就会被调用,效果就是为其画一些竖条;同理包含IMDB的Chunk类调用SetGenericTag方法并传入字符串"ellipse",这样IMDB就有一个蓝色椭圆的背景。如果Chunk被分割在不同的行那么OnGenericTag方法就会被调用多次,每一行都有自己的矩形边界。以上的代码还有一个CountYear方法,其负责统计一年中有多少电影,后续将这些信息也打印出来,详细见一下代码:

listing 5.10 MovieYears.cs(continued)

document.NewPage();
writer.PageEvent = null;foreach (var item in gEvents.years)
{p = new Paragraph(string.Format("{0} : {1} movies", item.Key, item.Value));document.Add(p);
}

在以上代码中我们重起一页并移除了所有的页面事件,然后将每一年包含的电影信息打印出来。以上都是讨论Chunk对象使用的方法,但在途中还有一个其他的页面事件,它是通过以下代码定义的:

writer.PageEvent = new ParagraphPositions();

ParagraphPositions是如何为Paragraph对象创建事件的一个列子。

Paragraph events

ParagraphPositions类代码如下:

listing 5.11 MovieYears.cs(continued)

public void OnParagraph(PdfWriter writer, Document document, float paragraphPosition)
{DrawLine(writer.DirectContent, document.Left, document.Right, paragraphPosition - 8);
}public void OnParagraphEnd(PdfWriter writer, Document document, float paragraphPosition)
{DrawLine(writer.DirectContent, document.Left, document.Right, paragraphPosition - 5);
}public void DrawLine(PdfContentByte cb, float x1, float x2, float y)
{cb.MoveTo(x1, y);cb.LineTo(x2, y);cb.Stroke();
}

关于Paragraph对象的方法有两个,但其参数是一样的,参数的前两个writer和document和OnGenericTag方法的参数一致,最后一个参数paragraphPosition根据方法的不同有不同的含义

  • OnParagraph----在Paragraph呈现之前被调用,paragraphPosition参数是第一行基准线y坐标加上leading的值。
  • OnParagraphEnd----在Paragraph呈现之后被调用,paragraphPosition参数是最后一行基准线的y坐标

Chapter and Section events

使用Chapter和Section的事件和Paragraph一样:获取y坐标,然后根据坐标画线或者画图形,具体的效果图如下:

我们知道在创建Chapter和Section对象时会自动创建书签,这些书签会在Adobe Reader的书签面板中呈现。在下个列子中我们会使用页面事件创建一个可以打印的目录(table of content,简称TOC)。现在我们重用在第二节中的列子,但加上和Chapter和Section相关的页面事件,具体如下代码:

listing 5.12 MovieHistory1.cs

class ChapterSectionTOC : IPdfPageEvent
{public   List<Paragraph> titles = new List<Paragraph>();public void OnChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title){titles.Add(new Paragraph(title.Content, FONT[4]));}public void OnChapterEnd(PdfWriter writer, Document document, float paragraphPosition){DrawLine(writer.DirectContent, document.Left, document.Right, paragraphPosition - 5);}public void OnSection(PdfWriter writer, Document document, float paragraphPosition, int depth, Paragraph title){title = new Paragraph(title.Content, FONT[4]);title.IndentationLeft = 18 * depth;titles.Add(title);}public void OnSectionEnd(PdfWriter writer, Document document, float paragraphPosition){DrawLine(writer.DirectContent, document.Left, document.Right, paragraphPosition - 3);} public void DrawLine(PdfContentByte cb, float x1, float x2, float y){cb.MoveTo(x1, y);cb.LineTo(x2, y);cb.Stroke();}
}

以上代码中的OnSectionEnd和OnChapterEnd方法和OnParagraphEnd方法很类似,OnSection和OnChapter方法也和OnParagraph方法类似,但有一些格外的参数:title参数是我们构建Chapter和Section对象是传入的对应参数,depth可以告诉我们Section节点在书签中的深度。

在这里列子中我们将Chapter和Section的title(Paragraph类型)都添加到集合中,而且对于Section的不同深度定义了不同的左对齐大小。这样我们就有了目录的所有信息,但由于目录是在Chapter和Section被添加之后才获取的,因此目录会呈现在最后一页。一般来说目录都在内容的前面,因此我们希望在文档关闭之前重新排页面顺序。

Page order and blank pages

在写代码之前大家要知道的是:pdf文档的页面一般都是以拥有不同分支和节点的页面树组成。

LINEAR PAGE MODE

在默认情况下,iText会创建一个平衡树,因为平衡树的效率比较高,而最简单的平衡树就是一个根节点然后根节点直接引用文档中所有的页面。而要将文档页面重新排序我们就需要这样一个结构,具体的代码如下:

writer.SetLinearPageMode();

然后打开文档往其添加内容,在我们这个列子中,内容由一系列的Chapter对象组合。

REORDERING PAGES

在内容添加完毕之后我们就可以重新排页面顺序。

listing 5.13  MovieHistory1.cs (continued)

// add the TOC starting on the next page
document.NewPage();
int toc = writer.PageNumber;
foreach (Paragraph p in cEvent.titles)
{document.Add(p);
}
document.NewPage();
// get the total number of pages that needs to be reordered
int total = writer.ReorderPages(null);
// change the order
int[] order = new int[total];
for (int i = 0; i < total; i++)
{order[i] = i + toc;if (order[i] > total){order[i] -= total;}
}// apply the new order
writer.ReorderPages(order);

在以上代码中我们先从新的一页开始,然后将页码存储起来,这个页码就是目录在页面重新排序之前的号码,在我们的这个列子中就是第27页。然后我们将目录也添加到文档中。在计算页面总数之前我们需要开启新的一页,然后通过ReorderPages方法并传入null参数获取页面总数。在这个列子中页面总数为30。

接下来我们创建一个int数组来存储新的页面索引和旧的页面号码之间的映射。就我们这个列子来说目录一共4页,因此映射为:新的第一页对于旧的第27页,新的第五页索引为4对于旧的第一页。这些需要一些简单的数学计算。在映射完毕之后我们再次调用ReorderPages方法并将构建好的映射数组传入。

其实在获取页面总数的时候我们可以通过代码writer.PageNumber来获取,但如果当前页为空的话则可能会出现异常。大家可能觉得调用Page.NewPage方法时会在文档的末尾产生一些多余的空白页,但实际上如果当前页为空白页时iText会忽视Page.NewPage方法。

ADDING A BLANK PAGE

但如果我们一定要添加空白页时,我们必须显示的说明:

listing 5.14 NewPage.cs

document.Add(new Paragraph("This page will NOT be following by a blank page!"));
document.NewPage();
// we don't add anything to this page: newPage() will be ignored
document.NewPage();
document.Add(new Paragraph("This page will be following by a blank page!"));
document.NewPage();
writer.PageEmpty = false;
document.NewPage();
document.Add(new Paragraph("The prevoius page was a blank page!"));

在以上代码中我们首先在第一页加上一些文本,然后来到第二页,但马上我们有需要新的一页,但由于第二页没有任何内容所以新的一页不会创建,第二个文本会呈现在第二页上。这时第二页就不再为空了,因此调用NewPage方法后我们就来到了第三页,但这个时候我们调用PageEmpty属性并设置为false,这个时候iText就认为第三页不为空,因此就来到了第四页,最后在第四页上添加第三个文本。

总结

在这一节中我们介绍了Chunk,Paragraph,Chapter和Section对象的一些事件,并利用Chapter和Section事件构建一个目录还重新排了一下页面顺序,最后介绍了空白页的一些注意事项。但IPdfPageEvent中还有一些方法没有介绍,这些会在后续章节中介绍到,然后就是这一节的代码下载。

转载于:https://www.cnblogs.com/julyluo/archive/2012/07/13/2590936.html

iText in Action 2nd5.2节(Events for basic building blocks)读书笔记相关推荐

  1. iText in Action 2nd5.4节(Adding page events to PdfWriter)读书笔记

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

  2. iText in Action 2nd4.2节(Changing the properties of a cell)读书笔记

    前言 PdfPCell类继承于Rectangle类,因此也继承了很多修改边框和背景色的属性和方法,后续我们会讨论到,但现在我们先要说明PdfPCell的内容模式.在iText的内部PdfPCell的内 ...

  3. iText in Action 2nd3.1节(Introducing the concept of direct content)读书笔记

    前言 在第一节中我们学会了如何创建一个pdf文档,在2.2和2.3节时介绍了iText中的high-level对象的使用.接下来中我们会学习一种完全不同的添加内容模式:这通常也叫做low-level ...

  4. Machine Learning in Action 读书笔记---第3章 决策树

    Machine Learning in Action 读书笔记 第3章 决策树 文章目录 Machine Learning in Action 读书笔记 一.决策树算法简介 1 决策树的构造 2 决策 ...

  5. 《ASP.NET Core In Action》读书笔记系列五 ASP.NET Core 解决方案结构解析1

    <ASP.NET Core In Action>读书笔记系列五 ASP.NET Core 解决方案结构解析1 参考文章: (1)<ASP.NET Core In Action> ...

  6. Spring in Action 4 读书笔记之使用标签创建 AOP

    目录 1. 定义一个 aspect 2. 创建一个 around advice 在之前的读书笔记 Spring in Acton 4 读书笔记之 AOP 原理及 Spring 对 AOP 的支持 中, ...

  7. LINQ IN ACTION读书笔记:LINQ 使用连接 1、组连接 2、内连接 3、左外连接 4、交叉连接 使用和区别...

    使用的数据源类型: static public class SampleData{static public Publisher[] Publishers ={new Publisher {Name= ...

  8. Machine Learning in Action 读书笔记---第5章 Logistic回归

    Machine Learning in Action 读书笔记 第5章 Logistic回归 文章目录 Machine Learning in Action 读书笔记 一.Logistic回归 1.L ...

  9. 软考-架构师-第三章-数据库系统 第七节 数据库设计(读书笔记)

    版权声明 主要针对希赛出版的架构师考试教程<系统架构设计师教程(第4版)>,作者"希赛教育软考学院".完成相关的读书笔记以便后期自查,仅供个人学习使用,不得用于任何商业 ...

最新文章

  1. 安卓案例:利用SQLiteOpenHelper操作数据库及表
  2. sqlilab--writeup (5~6) 布尔盲注
  3. (116)System Verilog类合成(类包含关系)详解
  4. 一加Ace外观设计理念揭晓:主推硬朗直线条力量感/速度感十足
  5. oracle out参数查询,Oracle的out参数实例详解
  6. iOS开发使用TouchID验证登录踩过的一些坑(同时更新FaceID使用方法)
  7. 微软模拟飞行2020服务器多少内存,《微软模拟飞行2020》:一款游戏大到7000万GB,这才是真正的模拟地球?!...
  8. 可视化大屏|2022年最值得推荐的10款可视化软件
  9. CrystalReports水晶报表开发中遇到的问题
  10. Android App的破解技术有哪些?如何防止反编译?
  11. 动态数组是怎么创建的?BQe
  12. Selenium学习 - 简介
  13. C++病毒——鼠标乱飞
  14. Matlab/simulink、Saber及PSpice学习比较
  15. win32 api简易实现ftp文件上传
  16. [USACO13NOV]挤奶牛Crowded Cows(洛谷 P3088)
  17. eclipse连接不上mysql数据库,而且是在javaWeb开发环境中
  18. 2022/10/16 指针习题 各种练习题
  19. Qt实现半透明、无边框、可自由移动、不规则的窗体
  20. 概率论—贝叶斯定理 解析

热门文章

  1. 【kafka】关于Kafka Fetch Session的讨论
  2. 【算法】剑指 Offer 04. 二维数组中的查找 【重刷】
  3. 【算法】逆波兰式(后缀表达式)的计算 中缀表达式转后缀表达式(逆波兰式)
  4. 【Elasticsearch】将数据预加载到文件系统缓存中
  5. 【Elasticsearch】 es 搜索 返回信息 字段 解释
  6. 【ElasticSearch】Es 源码之 NetworkModule 源码解读
  7. 【Kafka】Kafka ERROR [ConsumerFetcherThread-console-consumer], Error for partition [xx,5] to broker 10
  8. 【hortonworks/registry】registry 如何添加新的类型 支持 json
  9. 自定义 Git - Git 钩子
  10. 95-140-122-源码-transform-算子reduce