itext7 合并 HtmlConverter.convertToDocument 创建的文档并保留大纲
itext7 merging documents created by HtmlConverter.convertToDocument and perserving outlines
我的两个文档是使用 HtmlConverter.convertToDocument 创建的,然后合并为一个 PDF:
PdfDocument pdf = new PdfDocument(new PdfWriter(pdfDest));
PdfMerger merger = new PdfMerger(pdf, false, true).setCloseSourceDocuments(true);
// Convert
ConverterProperties converterProperties = new ConverterProperties().setBaseUri(resourceFolder);
OutlineHandler outlineHandler = OutlineHandler.createStandardHandler();
converterProperties.setBaseUri(".");
converterProperties.setOutlineHandler(outlineHandler);
第一个文档包含 "HTML Ipsum Presents" 书签,第二个文档包含 "Plastic_parts_Basic" 和 "Amo"(带有 children)。
注意大纲处理程序的使用。合并后,书签好像混在一起了。考虑到每个文档的 OutlineHandler 按照相同的模式创建目的地是有道理的:
OutlineHandler addOutline(ITagWorker tagWorker, IElementNode element, ProcessorContext context) {
String tagName = element.name();
if (null != tagWorker && hasTagPriorityMapping(tagName) && context.getPdfDocument() != null) {
int level = (int) getTagPriorityMapping(tagName);
if (null == currentOutline) {
currentOutline = context.getPdfDocument().getOutlines(false);
}
PdfOutline parent = currentOutline;
while (!levelsInProcess.isEmpty() && level <= levelsInProcess.getFirst()) {
parent = parent.getParent();
levelsInProcess.pop();
}
String content = ((JsoupElementNode) element).text();
if (content.isEmpty()) {
content = getUniqueID(tagName);
}
PdfOutline outline = parent.addOutline(content);
String destination = DESTINATION_PREFIX + getUniqueID(DESTINATION_PREFIX);
outline.addDestination(PdfDestination.makeDestination(new PdfString(destination)));
destinationsInProcess.push(destination);
levelsInProcess.push(level);
currentOutline = outline;
}
return this;
}
点击书签中的 "Header level 2" 将指向最后合并文档中的第二个 header ("Amo"):
我试图扩展 class OutlineHandler,但我需要更改的方法 (getUniqueID) 是私有的,因此在 superclass 中是不可见的。
有没有办法在 html 创建的多个文档中获得唯一的目的地?
源文件(java 和 html)和生成的 PDF(查看 RFQMerge.pdf)位于此处:
the source code, files and result
已接受的答案对我不起作用,我一直在这段代码的第二行收到 NullPointerException:
PdfDictionary names = targetPdf.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
names.put(PdfName.Dests, replaceDict);
这是源代码和 input/source 代码文件:https://www.dropbox.com/s/kg7vsb0j3hbkfca/WhosebugClarification.zip?dl=0
您的问题如下:iText 生成具有相同大纲名称的 pdf,并且在合并期间不解析它们(iText 记录警告并将旧目标替换为新目标)。
有两种方法可以处理所描述的情况:
1) 创建具有唯一大纲名称的 pdf。
不幸的是,现在 OutlineHandler 的实现过于私密,无法正确覆盖它。但是,您可以根据需要构建自定义版本的 pdfHTML。 repo 位于 https://github.com/itext/i7j-pdfhtml 并且您对 OutlineHandler 的 reset 方法感兴趣:
/**
* Resets the current state so that this {@link OutlineHandler} is ready to process new document
*/
public void reset() {
currentOutline = null;
destinationsInProcess.clear();
levelsInProcess.clear();
uniqueIDs.clear();
}
只需注释它的最后一行并构建 jar。
2) 如果您知道文档的目的地会引起一些麻烦,请重命名它们。
即使 PdfMerger
只是用新目的地替换旧目的地,它也会记录有关它的警告。您可以获得已被覆盖的目标名称,并在合并前手动重命名它们。
要遵循这种方式,应该:
a) 更新目的地名称:
PdfNameTree destsTree = updateDestNamesDocument.getCatalog().getNameTree(PdfName.Dests);
PdfNameTree newNameTree = new PdfNameTree(updateDestNamesDocument.getCatalog(), PdfName.Dests);
for (Map.Entry<String, PdfObject> entry : destsTree.getNames().entrySet()) {
newNameTree.addEntry(prefix + entry.getKey(), entry.getValue());
}
PdfDictionary replaceDict = newNameTree.buildTree();
replaceDict.makeIndirect(updateDestNamesDocument);
PdfDictionary names = updateDestNamesDocument.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
names.put(PdfName.Dests, replaceDict);
b) 更新大纲:
PdfOutline rootOutline = updateDestNamesDocument.getOutlines(false);
updateOutlines(rootOutline, prefix);
private void updateOutlines(PdfOutline parentOutline, String prefix) {
for (PdfOutline outline : parentOutline.getAllChildren()) {
updateOutlines(outline, prefix);
}
if (parentOutline.getDestination() instanceof PdfStringDestination) {
parentOutline.addDestination(new PdfStringDestination(prefix + ((PdfString)parentOutline.getDestination().getPdfObject()).getValue()));
}
}
然后就可以成功合并pdf了。
谢谢 Uladzimir Asipchuk,您的回答对我很有用。
我根据我的要求做了些微改动,我的要求是合并两个或多个可以从 html 或已经创建的 pdf 或两者创建的 pdf。
我在调用 rebuild() 方法时遇到了一些问题
PdfDictionary replaceDict = newNameTree.buildTree();
replaceDict.makeIndirect(updateDestNamesDocument);
com.itextpdf.kernel.PdfException: 没有关联的 PdfWriter 用于制作 indire
cts.
所以,我刚刚将其添加为具有自定义名称的新条目,并在大纲上也进行了更新。正在运行
try {
String prefix = "cus-" + (index) + "-";
PdfNameTree destsTree = pdf.getCatalog().getNameTree(PdfName.Dests);
PdfNameTree newNameTree = new PdfNameTree(pdf.getCatalog(), PdfName.Dests);
for (Map.Entry<String, PdfObject> entry : destsTree.getNames().entrySet()) {
newNameTree.addEntry(prefix + entry.getKey(), entry.getValue());
}
for (Map.Entry<String, PdfObject> entry : newNameTree.getNames().entrySet()) {
destsTree.addEntry(prefix + entry.getKey(), entry.getValue());
System.out.println(entry.getKey() +"==>>"+ entry.getValue());
}
PdfOutline rootOutline = pdf.getOutlines(false);
updateOutlines(rootOutline, prefix);
} catch (Exception e) {
e.printStackTrace();
}
将新添加的目的地映射到轮廓
public static void updateOutlines(PdfOutline parentOutline, String prefix) {
for (PdfOutline outline : parentOutline.getAllChildren()) {
updateOutlines(outline, prefix);
}
if (parentOutline.getDestination() instanceof PdfStringDestination) {
parentOutline.addDestination(new PdfStringDestination(prefix + ((PdfString) parentOutline.getDestination().getPdfObject()).getValue()));
}
}
这对我来说一直正常工作在此更改后进行合并,因此您的所有目的地看起来都是独一无二的,并且在合并时不会被覆盖。
我的两个文档是使用 HtmlConverter.convertToDocument 创建的,然后合并为一个 PDF:
PdfDocument pdf = new PdfDocument(new PdfWriter(pdfDest));
PdfMerger merger = new PdfMerger(pdf, false, true).setCloseSourceDocuments(true);
// Convert
ConverterProperties converterProperties = new ConverterProperties().setBaseUri(resourceFolder);
OutlineHandler outlineHandler = OutlineHandler.createStandardHandler();
converterProperties.setBaseUri(".");
converterProperties.setOutlineHandler(outlineHandler);
第一个文档包含 "HTML Ipsum Presents" 书签,第二个文档包含 "Plastic_parts_Basic" 和 "Amo"(带有 children)。
注意大纲处理程序的使用。合并后,书签好像混在一起了。考虑到每个文档的 OutlineHandler 按照相同的模式创建目的地是有道理的:
OutlineHandler addOutline(ITagWorker tagWorker, IElementNode element, ProcessorContext context) {
String tagName = element.name();
if (null != tagWorker && hasTagPriorityMapping(tagName) && context.getPdfDocument() != null) {
int level = (int) getTagPriorityMapping(tagName);
if (null == currentOutline) {
currentOutline = context.getPdfDocument().getOutlines(false);
}
PdfOutline parent = currentOutline;
while (!levelsInProcess.isEmpty() && level <= levelsInProcess.getFirst()) {
parent = parent.getParent();
levelsInProcess.pop();
}
String content = ((JsoupElementNode) element).text();
if (content.isEmpty()) {
content = getUniqueID(tagName);
}
PdfOutline outline = parent.addOutline(content);
String destination = DESTINATION_PREFIX + getUniqueID(DESTINATION_PREFIX);
outline.addDestination(PdfDestination.makeDestination(new PdfString(destination)));
destinationsInProcess.push(destination);
levelsInProcess.push(level);
currentOutline = outline;
}
return this;
}
点击书签中的 "Header level 2" 将指向最后合并文档中的第二个 header ("Amo"):
我试图扩展 class OutlineHandler,但我需要更改的方法 (getUniqueID) 是私有的,因此在 superclass 中是不可见的。
有没有办法在 html 创建的多个文档中获得唯一的目的地?
源文件(java 和 html)和生成的 PDF(查看 RFQMerge.pdf)位于此处: the source code, files and result
已接受的答案对我不起作用,我一直在这段代码的第二行收到 NullPointerException:
PdfDictionary names = targetPdf.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
names.put(PdfName.Dests, replaceDict);
这是源代码和 input/source 代码文件:https://www.dropbox.com/s/kg7vsb0j3hbkfca/WhosebugClarification.zip?dl=0
您的问题如下:iText 生成具有相同大纲名称的 pdf,并且在合并期间不解析它们(iText 记录警告并将旧目标替换为新目标)。
有两种方法可以处理所描述的情况:
1) 创建具有唯一大纲名称的 pdf。 不幸的是,现在 OutlineHandler 的实现过于私密,无法正确覆盖它。但是,您可以根据需要构建自定义版本的 pdfHTML。 repo 位于 https://github.com/itext/i7j-pdfhtml 并且您对 OutlineHandler 的 reset 方法感兴趣:
/**
* Resets the current state so that this {@link OutlineHandler} is ready to process new document
*/
public void reset() {
currentOutline = null;
destinationsInProcess.clear();
levelsInProcess.clear();
uniqueIDs.clear();
}
只需注释它的最后一行并构建 jar。
2) 如果您知道文档的目的地会引起一些麻烦,请重命名它们。
即使 PdfMerger
只是用新目的地替换旧目的地,它也会记录有关它的警告。您可以获得已被覆盖的目标名称,并在合并前手动重命名它们。
要遵循这种方式,应该: a) 更新目的地名称:
PdfNameTree destsTree = updateDestNamesDocument.getCatalog().getNameTree(PdfName.Dests);
PdfNameTree newNameTree = new PdfNameTree(updateDestNamesDocument.getCatalog(), PdfName.Dests);
for (Map.Entry<String, PdfObject> entry : destsTree.getNames().entrySet()) {
newNameTree.addEntry(prefix + entry.getKey(), entry.getValue());
}
PdfDictionary replaceDict = newNameTree.buildTree();
replaceDict.makeIndirect(updateDestNamesDocument);
PdfDictionary names = updateDestNamesDocument.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
names.put(PdfName.Dests, replaceDict);
b) 更新大纲:
PdfOutline rootOutline = updateDestNamesDocument.getOutlines(false);
updateOutlines(rootOutline, prefix);
private void updateOutlines(PdfOutline parentOutline, String prefix) {
for (PdfOutline outline : parentOutline.getAllChildren()) {
updateOutlines(outline, prefix);
}
if (parentOutline.getDestination() instanceof PdfStringDestination) {
parentOutline.addDestination(new PdfStringDestination(prefix + ((PdfString)parentOutline.getDestination().getPdfObject()).getValue()));
}
}
然后就可以成功合并pdf了。
谢谢 Uladzimir Asipchuk,您的回答对我很有用。
我根据我的要求做了些微改动,我的要求是合并两个或多个可以从 html 或已经创建的 pdf 或两者创建的 pdf。
我在调用 rebuild() 方法时遇到了一些问题
PdfDictionary replaceDict = newNameTree.buildTree();
replaceDict.makeIndirect(updateDestNamesDocument);
com.itextpdf.kernel.PdfException: 没有关联的 PdfWriter 用于制作 indire cts.
所以,我刚刚将其添加为具有自定义名称的新条目,并在大纲上也进行了更新。正在运行
try {
String prefix = "cus-" + (index) + "-";
PdfNameTree destsTree = pdf.getCatalog().getNameTree(PdfName.Dests);
PdfNameTree newNameTree = new PdfNameTree(pdf.getCatalog(), PdfName.Dests);
for (Map.Entry<String, PdfObject> entry : destsTree.getNames().entrySet()) {
newNameTree.addEntry(prefix + entry.getKey(), entry.getValue());
}
for (Map.Entry<String, PdfObject> entry : newNameTree.getNames().entrySet()) {
destsTree.addEntry(prefix + entry.getKey(), entry.getValue());
System.out.println(entry.getKey() +"==>>"+ entry.getValue());
}
PdfOutline rootOutline = pdf.getOutlines(false);
updateOutlines(rootOutline, prefix);
} catch (Exception e) {
e.printStackTrace();
}
将新添加的目的地映射到轮廓
public static void updateOutlines(PdfOutline parentOutline, String prefix) {
for (PdfOutline outline : parentOutline.getAllChildren()) {
updateOutlines(outline, prefix);
}
if (parentOutline.getDestination() instanceof PdfStringDestination) {
parentOutline.addDestination(new PdfStringDestination(prefix + ((PdfString) parentOutline.getDestination().getPdfObject()).getValue()));
}
}
这对我来说一直正常工作在此更改后进行合并,因此您的所有目的地看起来都是独一无二的,并且在合并时不会被覆盖。