前言

    在之前的第1到第3章,我们总是从头开始用iText创建一个新的PDF文档。在第4章的最后几个例子中,我们使用了一个现有的PDF文档,利用现有的PDF来读取表单并填写了自己的表单或者预填充定义的表单。在本章,我们会使用PdfReader读取一个存在的PDF文件,或者使用PdfWriter对象来创建一个新的PdfDocument

添加注释和内容

    在前面的章节中,我们读取了一个带有表单的PDF文档,然后填充了里面的内容。在这个章节中,我们会进行深入的操作。我们首先添加一个文档注释,一些文本和一个新的复选框,如下图:

itext5-1

    接下来就是我们在第四章里面添加注释和内容里面的代码一样了:

  1. PdfDocument pdfDoc =
  2. new PdfDocument(new PdfReader(src), new PdfWriter(dest));
  3. // add content
  4. pdfDoc.close();

    我们在//add content部分添加相应的内容,首先,我们通过PdfDocument实例来向一个页面里面添加注释:

  1. PdfAnnotation ann = new PdfTextAnnotation(new Rectangle(400, 795, 0, 0))
  2. .setTitle(new PdfString("iText"))
  3. .setContents("Please, fill out the form.")
  4. .setOpen(true);
  5. pdfDoc.getFirstPage().addAnnotation(ann);

    如果我们想添加内容到内容流里面,我们需要创建一个PdfCancas对象。我们可以传入一个PdfPage对象传入PdfCanvas的构造函数:

  1. PdfCanvas canvas = new PdfCanvas(pdfDoc.getFirstPage());
  2. canvas.beginText().setFontAndSize(
  3. PdfFontFactory.createFont(FontConstants.HELVETICA), 12)
  4. .moveText(265, 597)
  5. .showText("I agree to the terms and conditions.")
  6. .endText();

    添加文本的代码和我们在第2章写的代码差不多。无论您是从头开始创建文档还是将内容添加到现有文档,都可以使用我们写的代码。当然,向PdfAcroForm实例添加字段也是跟之前的例子一样的:

  1. PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
  2. PdfButtonFormField checkField = PdfFormField.createCheckBox(
  3. pdfDoc, new Rectangle(245, 594, 15, 15),
  4. "agreement", "Off", PdfFormField.TYPE_CHECK);
  5. checkField.setRequired(true);
  6. form.addField(checkField);

    最后,既然我们已经添加了一个多余的字段,我们想要改变reset的动作:

  1. form.getField("reset").setAction(PdfAction.createResetForm(
  2. new String[]{"name", "language", "experience1", "experience2",
  3. "experience3", "shift", "info", "agreement"}, 0))

改变表单字段的属性

    之前的第四章的例子中,我们是填充了表单的内容。在这里,我们可以尝试改变字段的属性,添加或者删除。

  1. PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
  2. Map<String, PdfFormField> fields = form.getFormFields();
  3. fields.get("name").setValue("James Bond").setBackgroundColor(Color.ORANGE);
  4. fields.get("language").setValue("English");
  5. fields.get("experience1").setValue("Yes");
  6. fields.get("experience2").setValue("Yes");
  7. fields.get("experience3").setValue("Yes");
  8. List<PdfString> options = new ArrayList<PdfString>();
  9. options.add(new PdfString("Any"));
  10. options.add(new PdfString("8.30 am - 12.30 pm"));
  11. options.add(new PdfString("12.30 pm - 4.30 pm"));
  12. options.add(new PdfString("4.30 pm - 8.30 pm"));
  13. options.add(new PdfString("8.30 pm - 12.30 am"));
  14. options.add(new PdfString("12.30 am - 4.30 am"));
  15. options.add(new PdfString("4.30 am - 8.30 am"));
  16. PdfArray arr = new PdfArray(options);
  17. fields.get("shift").setOptions(arr);
  18. fields.get("shift").setValue("Any");
  19. PdfFont courier = PdfFontFactory.createFont(FontConstants.COURIER);
  20. fields.get("info")
  21. .setValue("I was 38 years old when I became a 007 agent.", courier, 7);

    在这里我们关注下面的几行:

  • 行3:我们设置name字段的值为James Bond,同时我们设置这个字段的背景色为Color.ORANGE
  • 行9-17:创建了一个Java List来添加更多的选项(行8-15),然后把这个List转换成PdfArray(行16),最后使用这个array来更新shift字段
  • 行19-21:我们创建了一个新的PdfFont,然后使用这个新的字体和新的字体大小作为info字段的设置值方法的参数。

    让我们看看加入这些代码以后是否有所改变,如图:

itext5-2

    我们可以看到shift字段有了更多的选项,但是我们没有看见name字段的背景,我们是不能清楚info字段是否改变。是否发生错误了呢?其实是没有错误的,所有字段当前是被突出显示的而且蓝色的突出色覆盖了背景颜色,我们可以点击Highlight Existing Fields来看看还会发生什么,如图:

itext5-3

    一切跟我们期望的一样。但是我们没有遇到这样一种情况:在关闭PdfDocument之前,我们加入了form.flattenFields();语句,这时候表单就锁定,我们就没有了表单,针对这种情况,我们会在下一章探讨。现在,我们来看看我们是不是还能对没有表单的pdf文档进行一些其他的操作。

添加页眉、页脚和水印

    我们回忆在一下第三章里面的UFO信息的PDF,我们会创建一个相似的ufo.pdf文件:

itext5-4

    可以看出我们创建的ufo.pdf并没有第三章创建的那样炫酷,如果我们往里面添加一个页眉和一个页脚(显示第几页/总页数)还有水印,该如何做呢,如下图:

itext5-5

    在上图中,我们不像第三章那样在添加页脚的时候不知道总的页数,现在我们知道了总的页数,可以添加”1” of “4”,”2” of “4”等

从头创建文档时,可以为总页数创建一个占位符(placeholder)。一旦创建了所有的页面,我们就可以将该页面的总数添加到该占位符,但这不在本介绍性教程的范围之内。

    如下代码展示了如何在已知的文档的每一页中添加内容:

  1. PdfDocument pdfDoc =
  2. new PdfDocument(new PdfReader(src), new PdfWriter(dest));
  3. Document document = new Document(pdfDoc);
  4. Rectangle pageSize;
  5. PdfCanvas canvas;
  6. int n = pdfDoc.getNumberOfPages();
  7. for (int i = 1; i <= n; i++) {
  8. PdfPage page = pdfDoc.getPage(i);
  9. pageSize = page.getPageSize();
  10. canvas = new PdfCanvas(page);
  11. // add new content
  12. }
  13. pdfDoc.close();

    我们使用pdfDoc对象来创建一个Document实例。我们将要使用这个document对象来添加内容。我们同时也会使用pdfDoc对象来找到原始PDF的总页数。我们遍历所有所有的页数,获得每一页的PdfPage对象。以下代码是//add new content所做得东西:

  1. //Draw header text
  2. canvas.beginText().setFontAndSize(
  3. PdfFontFactory.createFont(FontConstants.HELVETICA), 7)
  4. .moveText(pageSize.getWidth() / 2 - 24, pageSize.getHeight() - 10)
  5. .showText("I want to believe")
  6. .endText();
  7. //Draw footer line
  8. canvas.setStrokeColor(Color.BLACK)
  9. .setLineWidth(.2f)
  10. .moveTo(pageSize.getWidth() / 2 - 30, 20)
  11. .lineTo(pageSize.getWidth() / 2 + 30, 20).stroke();
  12. //Draw page number
  13. canvas.beginText().setFontAndSize(
  14. PdfFontFactory.createFont(FontConstants.HELVETICA), 7)
  15. .moveText(pageSize.getWidth() / 2 - 7, 10)
  16. .showText(String.valueOf(i))
  17. .showText(" of ")
  18. .showText(String.valueOf(n))
  19. .endText();
  20. //Draw watermark
  21. Paragraph p = new Paragraph("CONFIDENTIAL").setFontSize(60);
  22. canvas.saveState();
  23. PdfExtGState gs1 = new PdfExtGState().setFillOpacity(0.2f);
  24. canvas.setExtGState(gs1);
  25. document.showTextAligned(p,
  26. pageSize.getWidth() / 2, pageSize.getHeight() / 2,
  27. pdfDoc.getPageNumber(page),
  28. TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45);
  29. canvas.restoreState();

    总共的步骤分为四部:

  • 页眉(行2-6):我们使用第二章谈及的低等级的文本api来在每一页的最上方添加"I want to believe"
  • 页脚线(行8-11):我们使用低等级的画图api来在每一页的底部画线
  • 包含页码的页脚(行13-19):我们使用低等级的文本api在每一页底部添加当前页号,"of",以及总的页数。
  • 水印(行21-28):我们创建一个带有文本的Paragrah来当做水印。然后我们改变画布的透明性。最后我们添加这个Paragrah到文档中,使用showTextAligned()方法使水印定位在每一页的中间,并45度倾斜。

    在这里,我们添加水印的时候做了一些特殊的工作。我们改变了canvas对象的图像状态,然后我们在document的每一页中添加了文本,在内部,iText会发现我们已经使用了当前页的PafCanvas实例,同时showTextAligned会想同一个canvas写入内容,这种方式,我们可以使用低级api和一些简单方法(如高级api)的结合(可以和第三章的添加水印相比较,确实是方便了不少)。在本章最后的例子中,我们会改变页面的大小和upf.pdf的每一页的方向。

改变页面大小和方向

    新的pdf文档效果如下:

itext5-6

    我们可以发现每一页面变得更大了,而且第二页的颠倒了,向下了。让我们来看看代码:

  1. PdfDocument pdfDoc =
  2. new PdfDocument(new PdfReader(src), new PdfWriter(dest));
  3. float margin = 72;
  4. for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) {
  5. PdfPage page = pdfDoc.getPage(i);
  6. // change page size
  7. Rectangle mediaBox = page.getMediaBox();
  8. Rectangle newMediaBox = new Rectangle(
  9. mediaBox.getLeft() - margin, mediaBox.getBottom() - margin,
  10. mediaBox.getWidth() + margin * 2, mediaBox.getHeight() + margin * 2);
  11. page.setMediaBox(newMediaBox);
  12. // add border
  13. PdfCanvas over = new PdfCanvas(page);
  14. over.setStrokeColor(Color.GRAY);
  15. over.rectangle(mediaBox.getLeft(), mediaBox.getBottom(),
  16. mediaBox.getWidth(), mediaBox.getHeight());
  17. over.stroke();
  18. // change rotation of the even pages
  19. if (i % 2 == 0) {
  20. page.setRotation(180);
  21. }
  22. }
  23. pdfDoc.close();

    可以发现,这里我们不需要Document实例,我们只需要PdfDocument实例就够啦。我们遍历所有的页面(行4)和获取每一页的PdfPage实例(行5):

  • 一个页面可以有不同的边界,其中/MediaBox基本上就是页面大小了(详情了可以百度PDF MediaBox),然后我们获取一个Rectangle作为原页面的边界(行7),根据这个边界来创建一英尺更大的边Rectangle来当做新的页面边界大小,最后我们使用setMediaBox()来改变页面大小
  • 我们创了页面的PdfCanvas对象,然后使用灰色画笔画出了mediaBox的边界(行14-17)
  • 偶数页,我们把页面旋转180度(行19)

    改变已知存在的PDF文档需要关于PDF的很多知识,比如/MediaBox的概念。我们为了使举的例子看起来简单,我们会规避一些其他的细节,例如在上面一个例子中,我们没有检查文档中有CropBox对象,如果有这个对象的话,使/MediaBox变大不会带来任何视觉上的变换。所以将会花上更多篇章来深入讨论这些问题。

总结

    在之前的篇章中,我们学习了交互式的PDF表单。在本章中,我们继续操作这些表单:添加注释,一些文本和额外的字段,我们在填充表单的同时还改变了字段的一些属性。
    然后我们讨论了那些没有交互的PDF文档,首先,我们添加了页眉、页脚和水印,然后我们改变了已知文档的页面大小和方向。
    在下一个章节中,我们会缩放和平铺已知文档,然后我们会学会如何将多个文档组合成一个PDF。