2012年7月28日星期六

iText in Action 2nd 6.4(Copying pages with PdfCopy)读书笔记

iText in Action 2nd 6.4(Copying pages with PdfCopy)读书笔记

前言

在前面的章节中我们知道一个PdfStamper对象只能对应一个PdfReader对象。因此当我们将多个文档组装或者编辑时就需要用到另一个类:PdfCopy。PdfCopy继承PdfWriter,因此在五步创建文档过程中可以用PdfCopy代替PdfWriter,就如以下代码所示:

listing 6.20 SelectPages.cs

int n = reader.NumberOfPages;Document document = new Document();PdfCopy copy = new PdfCopy(document, new FileStream(result2, FileMode.Create));document.Open();for (int i = 1; i <= n; i++){    PdfImportedPage page = copy.GetImportedPage(reader, i);    copy.AddPage(page);}document.Close();

PdfCopy和PdfWriter有很大的区别:使用PdfCopy类添加内容只能通过AddPage方法。以上代码中我们只操作了一个文档,接下来我们在此基础上扩展并将两个文档连接起来。

Concatenating and splitting PDF documents

在第二节时我们创建一个包含了链接的电影列表文档,然后还自动创建了这些电影的历史信息,现在我们将这两个文档连接起来:

listing 6.21 Concatenate.cs

string[] files = { movieLink1, movieHistory };// step 1Document document = new Document();using (document){    // step 2    PdfCopy copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create));    // step 3    document.Open();    // step 4    PdfReader reader;    int n;    for (int i = 0; i < files.Length; i++)    {        reader = new PdfReader(files[i]);        n = reader.NumberOfPages;        for (int page = 0; page < n; )        {            copy.AddPage(copy.GetImportedPage(reader, ++page));        }    }}

movieLink1有34页,movieHistory有26页,最后连接的文档一共有60页。PdfCopy的AddPage还有一个重载的方法:

public void AddPage(Rectangle rect, int rotation);

通过以上方法会添加一个空的页面,或者添加一个PdfImportedPage对象。

PRESERVATION OF INTERACTIVE FEATURES

在前面的列子我们使用已经使用PdfWriter和PdfStamper类获取PdfImportedPage对象,然后将其旋转,缩放等一系列操作。但通过PdfCopy获取的PdfImportedPage类有很大的区别:我们只能按照其以前的大小添加新的文档中。这是一个很大的限制但也伴随一个很大的优点:页面大部分交互的特性也被保存了。如果使用PdfWriter或者PdfStamper那么movieLink1中链接就会全部lost,但使用PdfCopy则完好无损。链接是一种特殊的注释(annotation),我们在第7节会有详细说明,这里大家知道通过PdfCopy链接还可以保存即可。但movieHistory中的书签全部不见了,我们会在下一节中解决这个问题。

ADDING CONTENT WITH PDFCOPY

在前面的章节中我们知道PdfImportedPage是PdfTemplate的一个只读子类,我们不能在其中添加内容,但我们也学习了如何通过PdfWriter和PdfStamper在导出页面的上面或者下面添加内容。使用PdfCopy也有类似的功能:

listing 6.22 ConcatenateStamp.cs

// step 1Document document = new Document();using (document){// step 2PdfCopy copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create));// step 3document.Open();// step 4// reader for document 1PdfReader reader1 = new PdfReader(movieLink1);int n1 = reader1.NumberOfPages;// reader for document 2PdfReader reader2 = new PdfReader(movieHistory);int n2 = reader2.NumberOfPages;// initializationsPdfImportedPage page;PdfCopy.PageStamp stamp;// Loop over the pages of document 1for (int i = 0; i < n1; ){    page = copy.GetImportedPage(reader1, ++i);    stamp = copy.CreatePageStamp(page);    ColumnText.ShowTextAligned(stamp.GetUnderContent(), Element.ALIGN_CENTER,        new Phrase(string.Format("page {0} of {1}", i, n1 + n2)), 297.5f, 28, 0);    stamp.AlterContents();    copy.AddPage(page);}// Loop over the pages of document 2for (int i = 0; i < n2; ){    page = copy.GetImportedPage(reader2, ++i);    stamp = copy.CreatePageStamp(page);    ColumnText.ShowTextAligned(stamp.GetUnderContent(), Element.ALIGN_CENTER,        new Phrase(string.Format("page {0} of {1}", i + n1, n1 + n2)), 297.5f, 28, 0);    stamp.AlterContents();    copy.AddPage(page);}

在以上代码中我们通过PdfCopy.PageStamp对象将内容添加到PdfImportedPage中,而PdfCopy.PageStamp对象则通过PdfCopy的CreatePageStamp方法获取,其他的就和PdfWriter和PdfStamper的操作一致,只是最好要格外添加AlterContent方法。

SPLITTING A PDF

PdfCopy和PdfStamper不一样,PdfCopy可以重复使用同一个PdfReader类。在第三节中我们构建了一个Timeable的pdf文档,现在我们会将每一页分成单独的一个文档,在Pdf的技术中这个又叫做PDF bursting,具体的如下代码:

listing 6.23 Burst.cs

// Create a readerPdfReader reader = new PdfReader(movieTemplates);// We'll create as many new PDFs as there are pagesDocument document;PdfCopy copy;int n = reader.NumberOfPages;// loop over all the pages in the original PDFfor (int i = 0; i < n; ){    // step 1    document = new Document();    using (document)    {        // step 2        string fileName = string.Format(result, ++i);        copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create));        // step 3        document.Open();        // step 4        copy.AddPage(copy.GetImportedPage(reader, i));    }}

原始的timeable文档一共有8页,大小大概15kb,分成单个文档之后每个文档大小大概4kb,4*8=32kb比原始的文档要大很多,主要原因是:在原始文档中我们可以共用一些资源,但分开之后这些共用的资源被单独的copy的每一份中。这是大家可以会想如果我们将有大量重复内容的文档连接起来会有什么样的结果?

PdfCopy versus PdfSmartCopy

在前一节中我们填充了一些pdf表单,但生产了大量的文档,现在我们会将其连接成一个文档:

listing 6.24 DataSheets1.cs

public void CreateFile(string fileName){    // step 1    Document document = new Document();              using (document)    {        // step 2        PdfCopy copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create));        // step 3        document.Open();        // step 4        AddDataSheets(copy);    }}
public void AddDataSheets(PdfCopy copy){    string connStr = ConfigurationManager.AppSettings["SQLiteConnStr"];    SQLiteConnection conn = new SQLiteConnection(connStr);    using (conn)    {        conn.Open();        List<Movie> movies = PojoFactory.GetMovies(conn);        PdfReader reader;        PdfStamper stamper;        MemoryStream ms;        // Loop over all the movies and fill out the data sheet        foreach (Movie movie in movies)        {            reader = new PdfReader(datasheet);            ms = new MemoryStream();            stamper = new PdfStamper(reader, ms);            Fill(stamper.AcroFields, movie);            stamper.FormFlattening = true;            stamper.Close();            reader = new PdfReader(ms.ToArray());            copy.AddPage(copy.GetImportedPage(reader, 1));        }    }}

以上代码中我们使用PdfStamper来填充表单,然后在通过PdfCopy将页面导出并最终构建一个文档。

最后生成的文档如果打开看的话好像挺完美,但如果你仔细观察文档的大小:原始的datasheet.pdf只有60kb,但我们最终的文档大概有5MB。最终的文档有120页,每一页基本上是相同的,只不过文档上有不同的信息,但使用PdfCopy时其会将每一页所必需的资源全部copy过去。这显然大大增加了文档的大小,要解决这个问题,可以通过PdfSmartCopy类来代替PdfCopy类:

listing 6.25 DataSheets2.cs

Document document = new Document();using (document){    PdfSmartCopy copy = new PdfSmartCopy(document, new FileStream(fileName, FileMode.Create));    document.Open();    AddDataSheets(copy);}

通过以上代码生成的文档大小只有300KB,对比之下还有有很大的提升。PdfSmartCopy类继承PdfCopy,但其会仔细检查每一页看是否有相同的内容,这会减少最终文档的大小但由于要格外的检查因此要耗费更多的cpu和内存,所以大家可以权衡这两个类自己选择:要文档的大小还是更多考虑cpu和内存消耗。如果文档之间没什么太多的相同点那使用PdfCopy是个好选择,但如果文档都有相同的logo或者水印那么使用PdfSmartCopy去检查logo就应该是我们的选择。在这个列子我们连接已经flatter的表单,但如果我们希望连接原始的表单呢?我们不需要尝试因此那是不work的,虽然PdfCopy和PdfSmartCopy将一些表单交互的特性保留了,但如果连接多个表单那么表单的功能就会失效,所以我们最后的选择是使用PdfCopyFields类。

Concatenating forms

假设我们希望创建一个有两页或者多页的film data form,这很容易实现,只要一下四行代码即可:

listing 6.26 ConcatenateForms1.cs

PdfCopyFields copy = new PdfCopyFields(new FileStream(fileName, FileMode.Create));copy.AddDocument(new PdfReader(datasheet));copy.AddDocument(new PdfReader(datasheet));copy.Close();

通过以上代码现在的表单有两页,但估计不是我们想要的效果:我们希望在第一页的表单中输入信息然后在第二页表单中输入其他的信息,这在以上代码生成的文档是无法实现,如果你在第一页输入一些信息你会发现输入的信息在第二页相同的字段中也出现了。因为在表单中每一个字段都应该有单独的一个名称,因为我们直接连接了两个文档,因此其字段都有两个,输入的信息也会影响两个字段。所以我们在连接表单时要对字段重命名一下:

listing 6.27 ConcatenateForms2.cs

private byte[] RenameFiledsIn(string datasheet, int i){    MemoryStream ms = new MemoryStream();    // Create the stamper    PdfStamper stamper = new PdfStamper(new PdfReader(datasheet), ms);    // Get the fields    AcroFields form = stamper.AcroFields;    // Loop over the fields    List<string> fields = new List<string>(form.Fields.Keys);    foreach (string key in fields)    {        // rename the fields        form.RenameField(key, string.Format("{0}{1}", key, i));    }    stamper.Close();    return ms.ToArray();}

通过以上代码的重命名,每一个字段就有自己单独的名称,效果也就OK了。

总结

到这里第六节也就全部结束了,对应pdf文档的操作介绍了很多不同的类,每个类有自己不同的方法和缺点,以下的table是一个总结:

PdfReader 读取pdf文档的类,我们可以将其作为参数传给其他操作pdf的类。
PdfImportedPage PdfTemplate的只读子类,其代表一个导出页面,一般通过GetImportedPage方法获取。
PdfWriter 从头创建文档的一个类,也可以从其他文档导出页面,最大的缺点:导出页面所有的交互特性(annotation,bookmark,field etct)全部lost。
PdfStamper 只能操作一个文档,而且可以为文档添加内容或者格外的页面还可以填充表单。所有交互特性被保留除非我们显示将其移除。
PdfCopy 能够从现有的多个文档导出页面,最大的缺点:不能检测到多余的内容,而且不能连接表单。
PdfSmartCopy 也能够从现有多个文档中导出页面但可以检查多余的内容,但相应会消耗更多的cpu和内存。
PdfCopyFields 能够消除连接表单而产生的一些问题,但同样也会消耗更多的内存。
在接下来的章节中我们主要使用PdfStamper对象,然后会介绍annotation的概念并知道表单字段是一种特殊的annotation,最后就是这一节的代码下载。
TAG: