iText7 学习笔记3--目录

994 阅读3分钟

为PDF文档添加目录也是经常会用到的功能,itext做目录的思路一般有2种。

第一种:先将正文文档生成出来,同时保存正文文件的每一个模块以及对应的页码,然后生成一个新的文档,同时读取正文文件,复制正文文件的每一页,并通过正文模块与对应的页码数生成目录

第二种:先将正文文档生成出来,同时保存正文文件的每一个模块以及对应的页码,然后通过移动页面的方式生成目录,此方式在实际过程中当目录超过2页时会有问题。

我的案例以第一种方式生成目录,简单起见,正文文件先生成好,总共有5个模块,6个页面,其中一个模块占了2页,模块与页面的对应关系如下代码。

public List<Map<String, Object>> getCatalogList() {
		final List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
		final Map<String, Object> catalogMap = new HashMap<String, Object>();
		catalogMap.put("title", "关于公司");
		catalogMap.put("number", 1);
		list.add(catalogMap);
		final Map<String, Object> catalogMap2 = new HashMap<String, Object>();
		catalogMap2.put("title", "阅读指南");
		catalogMap2.put("number", 2);
		list.add(catalogMap2);
		final Map<String, Object> catalogMap4 = new HashMap<String, Object>();
		catalogMap4.put("title", "测评目的");
		catalogMap4.put("number", 4);
		list.add(catalogMap4);
		final Map<String, Object> catalogMap5 = new HashMap<String, Object>();
		catalogMap5.put("title", "综合评价");
		catalogMap5.put("number", 5);
		list.add(catalogMap5);
		final Map<String, Object> catalogMap6 = new HashMap<String, Object>();
		catalogMap6.put("title", "关于报告");
		catalogMap6.put("number", 6);
		list.add(catalogMap6);
		return list;
	}

正文源文件页面如下图 (source.pdf)

生成目录代码

    @Test
	public void test() {
		final List<Map<String, Object>> list = this.getCatalogList();
		PdfDocument sourcePdf = null;
		Document document = null;
		try {
			final PdfFont heiTi = this.getHeiTiFont();
			// ***源文件:也即需要添加目录的文件
			final String sourcePdfPath = "D:/itext-pdf/source.pdf";
			sourcePdf = new PdfDocument(new PdfReader(sourcePdfPath));
			final int totalPageNumber1 = sourcePdf.getNumberOfPages();
			System.out.println("totalPageNumber****" + totalPageNumber1);

			// ***添加目录后重新生成的文件
			final String dest = "D:/itext-pdf/004.pdf";
			final PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
			document = new Document(pdfDoc);
			document.add(new Paragraph(new Text("目录").setFont(heiTi)).setTextAlignment(TextAlignment.CENTER));
			// 用于做书签使用
			final PdfOutline root = pdfDoc.getOutlines(true);
			// 生成的目录需要几页,在实际过程中是需要根据目录的条数计算得出
			final int catalogNumber = 1;
			if (catalogNumber > 1) {
				for (int i = 1; i < catalogNumber; i++) {
					pdfDoc.addNewPage();
				}
			}
			// 上一页的页数,这个主要是因为在实际的文档中,会存在一个条目占多页的情况,例如本例中“阅读指南”就占2页
			int upPageNumber = 0;
			for (final Map<String, Object> map : list) {
				final String title = map.get("title").toString();
				final int curTotalPageNumber = pdfDoc.getNumberOfPages();
				final Integer pageNumber = Integer.valueOf(map.get("number").toString());
				// Copy page
				System.out.println(title + "**" + pageNumber + "**" + upPageNumber + "********" + curTotalPageNumber);
				// 当本页的页码与上一页页码直接多余1的时候,说明上一页后面还存在其它页,例如本例中的复制"测评目的"时要把 “阅读指南”的第2页先复制到新文档中。
				if (pageNumber - upPageNumber > 1) {
					for (int startNum = upPageNumber + 1; startNum < pageNumber; startNum++) {
						final PdfPage page = sourcePdf.getPage(startNum).copyTo(pdfDoc);
						pdfDoc.addPage(page);
					}
				}
				upPageNumber = pageNumber;
				final PdfPage page = sourcePdf.getPage(pageNumber).copyTo(pdfDoc);
				pdfDoc.addPage(page);
				System.out.println("page *****" + page + " ** " + page.getPdfObject());
				// 用于点击目录条目做跳转的处理,类始于锚点定位的功能
				final String destinationKey = "p" + pdfDoc.getNumberOfPages();
				final PdfArray destinationArray = new PdfArray();
				destinationArray.add(page.getPdfObject());
				destinationArray.add(PdfName.XYZ);
				destinationArray.add(new PdfNumber(0));
				destinationArray.add(new PdfNumber(page.getMediaBox().getHeight()));
				destinationArray.add(new PdfNumber(1));
				pdfDoc.addNamedDestination(destinationKey, destinationArray);
				// 书签
				final Text text = new Text(title);
				text.setFont(this.getSongTiFont());
				final TOCTextRenderer renderer = new TOCTextRenderer(root, text);
				text.setNextRenderer(renderer);
				// 生成目录
				final Paragraph p = new Paragraph();
				p.setFont(heiTi);
				p.addTabStops(new TabStop(540, TabAlignment.RIGHT, new DottedLine()));
				p.add(text);
				p.add(new Tab());
				p.add(String.valueOf(pdfDoc.getNumberOfPages() - catalogNumber));
				p.setProperty(Property.ACTION, PdfAction.createGoTo(destinationKey));
				document.add(p);
			}			
		} catch (final Exception e) {
			e.printStackTrace();
		} finally {
			sourcePdf.close();
			document.close();
		}
	}

TOCTextRenderer.java

public class TOCTextRenderer extends TextRenderer {

	protected PdfOutline parentOutline;

	public TOCTextRenderer(final PdfOutline parentOutline, final Text modelElement) {
		super(modelElement);
		this.parentOutline = parentOutline;
	}

	@Override
	public void draw(final DrawContext drawContext) {

		super.draw(drawContext);

		Text t = (Text) this.modelElement;

		Rectangle rect = this.getOccupiedAreaBBox();
		PdfDestination dest = PdfExplicitDestination.createXYZ(drawContext.getDocument().getLastPage(), rect.getLeft(),
				rect.getTop(), 0);
		PdfOutline curOutline = this.parentOutline.addOutline(t.getText());
		curOutline.addDestination(dest);

		// PdfOutline secOutline = curOutline.addOutline(t.getText() + " - 2");
		// secOutline.addDestination(dest);
		// System.out.println(curOutline.getTitle());
	}

}

最终生成的文档如下图

在实际生成带目录的PDF文档时,则要比上面的例子复杂一些,例如目录是有层级的,目录可能超过一页,目录可能也需要有页眉页脚等等,那就要做特殊的一些处理了,这些在接下来的搭建PDF脚手架里说明了。

本文已参与「新人创作礼」活动,一起开启掘金创作之路。