天道酬勤

<<iText in Action 2nd>>6.1节(Accessing an existing PDF with PdfReader)读书笔记

前言

从这一节开始内容集中到操作现有的pdf文档,如何创建pdf文档已经在前五节中有了很详细的说明。这一大章的英文名为Manipulating existing PDF documents,在定下这个名字之前出版社的建议是Editing PDF。但是PDF不是一个适合编辑(edit)的文档格式,PDF是一个呈现的格式,和我们平常用到的word不一样。在word中内容是分布在不同的页上,所以如果用不同的应用程序打开内容就不太一致,比如对同一个文本片段用office打开会出现在页面X上,但如果用Open Office打开就可能会出现在页面Y上,这也是大家选择PDF文档的理由之一。

在PDF文档中,不管用什么应用程序打开,文档中莫页的字符或者符号都有其固定位置。这是一个优势但相应也是一个缺点,假设我们希望将一个句子中的单词"edit"修改为"manipulate",那么我们不得不reflow文本,而且其后单词的位置都要重新计算。所以如果我们要编辑一个pdf文档最好的方法是从源头开始,比如你是用word转换工具将word转换为pdf,那么就先修改word中的内容然后再进行转换。这里为了对应说法我就将manipulate说成操作,现在在我们对文档操作之前先要学会如何读取一个pdf文档并获取一些信息:如文档有多少页啊,用的那种页面大小等等,这些都可以通过PdfReader类来实现。

Retrieving information about the document and its pages

在我们的第一个列子中我们会检测第一节生成的Pdf文档,具体代码如下:

listing 6.1 PageInformation.cs

public void Inspect(StreamWriter writer, string fileName)
{
    PdfReader reader = new PdfReader(fileName);
    writer.WriteLine(fileName);
    writer.Write("Number of pages: ");
    writer.WriteLine(reader.NumberOfPages);
    Rectangle mediabox = reader.GetPageSize(1);
    writer.Write("Size of page 1: [");
    writer.Write(mediabox.Left);
    writer.Write(',');
    writer.Write(mediabox.Bottom);
    writer.Write(',');
    writer.Write(mediabox.Right);
    writer.Write(',');
    writer.Write(mediabox.Top);
    writer.WriteLine("]");

    writer.Write("Rotation of page 1:");
    writer.WriteLine(reader.GetPageRotation(1));
    writer.Write("Page size with rotation of page 1: ");
    writer.WriteLine(reader.GetPageSizeWithRotation(1));
    writer.Write("Is rebuilt? ");
    writer.WriteLine(reader.IsRebuilt());
    writer.Write("Is encrypted? ");
    writer.WriteLine(reader.IsEncrypted());

    writer.WriteLine();
    writer.Flush();
}

以下为生产的文本文件内容:

results/part1/chapter01/hello_landscape1.pdf
Number of pages: 1
Size of page 1: [0.0,0.0,612.0,792.0]
Rotation of page 1: 90
Page size with rotation of page 1:
Rectangle: 792.0x612.0 (rot: 90 degrees)
Is rebuilt? false
Is encrypted? false


results/part1/chapter01/hello_landscape2.pdf
Number of pages: 1
Size of page 1: [0.0,0.0,792.0,612.0]
Rotation of page 1: 0
Page size with rotation of page 1:
Rectangle: 792.0x612.0 (rot: 0 degrees)
Is rebuilt? false
Is encrypted? false


results/part1/chapter03/movie_templates.pdf
Number of pages: 8
Size of page 1: [0.0,0.0,595.0,842.0]
Rotation of page 1: 90
Page size with rotation of page 1:
Rectangle: 842.0x595.0 (rot: 90 degrees)
Is rebuilt? false
Is encrypted? false


results/part1/chapter05/hero1.pdf
Number of pages: 1

Size of page 1: [-1192.0,-1685.0,1192.0,1685.0]
Rotation of page 1: 0
Page size with rotation of page 1:
Rectangle: 2384.0x3370.0 (rot: 0 degrees)
Is rebuilt? false
Is encrypted? false

在这一节中PdfReader中最重要是NumberOfPages属性和GetPageSizeWithRotation方法,通过NumberOfPages属性我们可以对现有文档进行循环操作,而后一个方法是GetPageSize方法和GetPageRotation方法的组合。

PAGE SIZE

以上代码的前两个列子说明是一下两行代码的区别:

Document document = new Document(new Rectangle(792, 612));
Document document = new Document(PageSize.LETTER.Rotate());

这个区别在我们导出文档以及和存在文档添加格外内容的时候有很大的影响。大家还要注意的是最后一个列子中大家可以看到左下角的坐标不为(0,0)。

BROKEN PDFS

但我们用Adobe Reader打开一个损坏的文档时,Adobe会有一个提示:"There was an error opening this document. The file is damaged could not be repaire."。PdfReader类对于这些文档的处理方式也是一样会抛出InvalidException异常还有一些格外的信息"Rebuild failed:trailer not found;original message:PDF starxref not found"。如果这种情况发生了,那么iTex也就无法处理这个文档。

在其他情况下如果文档损坏的不是很严重,Adobe Reader会自动修复并有以下警告提示:"The file is damaged but is being repaired"。PdfReader也是同样的处理方式,具体到代码中可以用IsRebulid方法去检测PDF是否需要修复。这是损坏文档的处理方式,但我们还需要知道加密文档的处理方式。

ENCRYPTED PDFS

PDF文件由两种密码保护:user password和owner password。如果是被user password保护我们在打开pdf文档之前就需要输入密码。如果文档有owner password则需要将password也传入到PdfReader的构造器中,否则会BadPasswordException异常抛出,更加详细的加密信息大家可以看书的12节。

Reducing the memory use of PdfReader

在操作pdf文档的代码中我们一般会创建PdfReader的一个实例,然后为其传入一个string类型参数(代表已存在文档的路径),使用这个构造器会导致PdfReader将大量的PDF对象转换为对应的java(iText)或者Net(iTextSharp)对象。这样一个较大的文档就有消耗很大的内容,而你可能只有其中某几页有兴趣,在这种情况下我们可以选择部分读取文档。

PARTIAL READS

使用PdfReader将其全部读取时和部分读取的代码如下:

listing 6.2 MemoryInfo.cs

public override void CreateFile(string fileName)
{
    FileStream fs = new FileStream(fileName, FileMode.Create);
    StreamWriter writer = new StreamWriter(fs);
    string movieFileName = new MovieTemplates().ContructFile();

    using (writer)
    {
        FullRead(writer, movieFileName);
        PartialRead(writer, movieFileName);
    }
}

public void  FullRead(StreamWriter writer, string fileName)
{
    long  beforeCreate = GC.GetTotalMemory(false);
    PdfReader reader = new PdfReader(fileName);
    long afterCreate = GC.GetTotalMemory(false);
    writer.WriteLine(string.Format("Full Read :Before Create: {0}, After Create: {1}, Consum :{2}", beforeCreate, afterCreate, afterCreate - beforeCreate));
    writer.Flush();
}

public void PartialRead(StreamWriter writer, string fileName)
{
    long beforeCreate = GC.GetTotalMemory(false);
    PdfReader reader = new PdfReader(new RandomAccessFileOrArray(fileName), null);
    long afterCreate = GC.GetTotalMemory(false);
    writer.WriteLine(string.Format("Part Read :Before Create: {0}, After Create: {1}, Consum :{2}", beforeCreate, afterCreate, afterCreate - beforeCreate));
    writer.Flush();
}

在以上代码中我们使用PdfReader的另一个构造器进行部分读取,从生成的文本文件大家可以看到内存还是减少了很多。当我们部分读取文档时,在开始使用PdfReader对象之前会消耗一些更多的内存,但PdfReader不会缓存那些不必要的对象,这个也是一个很大的区别。所以如果我们处理大的文档,可以优先选择使用RandomAccessFileOrArray对象的PdfReader构造器,使用PdfReader还有另一个减少内存损耗的方法。

SELECTING PAGES

通过指定页面的读取也可以减少内存的消耗,具体见以下代码:

listing 6.3 SelectPages.cs

reader.SelectPages("4-8");

SelectPages方法的语法看起来如下:

[!][o][odd][e][even]start[-end]

我们可以使用逗号(,)来组合多个选择范围,!符号的意思是移除已经选择了的页面。选择的范围是递增;以上中的start和end可以不进行设置,但如果两个都没有设置的话则至少有一个o(odd,选择所有的奇数页)或者设置一个e(even,选择所有的偶数页)。在以上代码中如果在SelectPages方法之前去获取页面的总数我们会得知文档有8页,然后在SelectPages方法之后再去获取页数时只会返回5:page 4,5,6,7和8。这个时候如果调用一下代码:

那么就会有NullPointerException异常,因为在PdfReader对象中没有第六页。

总结

这一节只是简单的介绍了PdfReader类的一些常用属性和方法,并对其内存损耗也进行一些讨论,后续我们就会开始对现有文档进行操作,最后就是代码下载

同步

此文章已同步到目录索引:iText in Action 2nd 读书笔记。

posted @ 2012-07-21 23:49  JulyLuo  阅读(4065)  评论(0编辑  收藏  举报