如何使用 iText 从书签在 PDF 文件中创建 table 个内容页面?
How create a table of contents page in the PDF file from the bookmarks with iText?
我需要在 PDF 中为 table 的内容创建一个页面。我将创建 PDF 阅读书签。
我使用 iText:
tmp = SimpleBookmark.getBookmark (reader);
使用此 PDF 进行测试:
Returns这张地图:
[{Action = GoTo, Named = __ WKANCHOR_2, Title = Secretariat Teste0}, {Action = GoTo, Named = __ WKANCHOR_4, Title = Secretariat TestBook1}, {Action = GoTo, Named = __ WKANCHOR_6, Title = Secretariat Test2}, {Action = GoTo , Named = __ WKANCHOR_8 ...
没有页码。
如何显示 table 的一篇内容,其中包含标题和页码?
我想展示这个:
请阅读此问题的答案:Java: Reading PDF bookmark names with itext
它解释了如何使用 SimpleBookmark
方法获取大纲树的标题(这就是 "bookmarks" 在 PDF 规范中的调用方式)。
public void inspectPdf(String filename) throws IOException, DocumentException {
PdfReader reader = new PdfReader(filename);
List<HashMap<String,Object>> bookmarks = SimpleBookmark.getBookmark(reader);
for (int i = 0; i < bookmarks.size(); i++){
showTitle(bookmarks.get(i));
}
reader.close();
}
public void showTitle(HashMap<String, Object> bm) {
System.out.println((String)bm.get("Title"));
List<HashMap<String,Object>> kids = (List<HashMap<String,Object>>)bm.get("Kids");
if (kids != null) {
for (int i = 0; i < kids.size(); i++) {
showTitle(kids.get(i));
}
}
}
然后阅读这个问题的答案:Set inherit Zoom(action property) to bookmark in the pdf file
您会看到 HashMap<String, Object>
不仅包含一个键为 "Title"
的条目,它还可以包含一个键为 "Page"
的条目。当书签指向一个页面时就是这种情况。该值将是一个 显式目标 。它将由页码、Fit
、FitH
、FitB
、XYZ
等值组成,后跟一些标记位置的参数。
如果您查看 CreateOutlineTree 示例,您会发现您还可以将书签提取为 XML 文件:
public void createXml(String src, String dest) throws IOException {
PdfReader reader = new PdfReader(src);
List<HashMap<String, Object>> list = SimpleBookmark.getBookmark(reader);
SimpleBookmark.exportToXML(list,
new FileOutputStream(dest), "ISO8859-1", true);
reader.close();
}
这是我写的一本关于 iText 的书的屏幕截图,其中显示了书签条目中可以使用的键:
从这个table可以看出,link也可以表示为命名目的地。在这种情况下,您不会得到页码,而是名称。要获取页码,您需要提取命名目标列表。此列表将为您提供与指定目的地相对应的明确目的地。
书中也有解释,official documentation。
获得标题和页码(使用基于上述指针编写的代码检索)后,您可以使用 PdfStamper
和 insertPage()
方法将页面插入 PDF 文件。您可以使用 ColumnText
将目录放在这些页面上,或者您可以为目录创建一个单独的 PDF 并将其与原始文件合并。请参阅 How to add a cover/PDF in a existing iText document 以了解有关这两种技术的更多信息。
您还将从此示例中受益:Create Index File(TOC) for merged pdf using itext library in java
至于标题和页码之间的虚线,这是使用 分隔符 完成的,更具体地说是虚线分隔符。你应该先阅读这个问题:iTextSharp - Is it possible to set a different alignment in the same cell for text
然后看这道题:How to Generate Table-of-Figures Dot Leaders in a PdfPCell for the Last Line of Wrapped Text (or this question It is possible with itext 5 which at the end of a paragraph justified the remaining space is filled with scripts?)
请注意,您的问题实际上是 off-topic。它被表述为 "home work" 问题。它邀请人们在你的地方做你的工作。现在您已经拥有了所需的所有元素,您应该能够自己完成这项工作。如果你没有成功,你应该写一个关于主题的 Stack Overflow 问题。在这个问题中,您可以展示您的尝试并解释您遇到的技术问题。
更新:
您使用以下大纲树共享文档:
如您所见,书签是使用命名目标定义的,例如 /__WKANCHOR_2
、/__WKANCHOR_4
等。从 /
字符可以看出,名称存储为 PDF 名称 objects (PDF 1.1),而不是 PDF 字符串 objects(自 1.2 起)。最新的 PDF 标准建议使用 PDF 字符串 objects 而不是 PDF 名称 objects,您可能需要请求 PDF 生成软件的供应商更新软件以满足最新 PDF 的建议标准。
不过,我们可以很容易地得到与那些命名目的地相对应的显式目的地。它们存储在根字典的 /Dests
条目中:
当您查看目的地的方式时,您会看到另一个应该报告给 wkhtmltopdf 的问题。让我们看一下 ISO 标准告诉我们有关用于目标的语法的内容:
PDF 中不存在页码的概念。页面是使用页面字典来描述的,页码是从页面在页面树中的位置得出的。在页面树中遇到的第一个页面是第1页,遇到的第二个页面是第2页,依此类推。
在您的示例中,解释目标定义如下:[9/XYZ 30.2400000 524.179999 0]
、[9/XYZ 30.2400000 231.379999 0]
,依此类推。
这是错误的。 ISO 标准规定数组中的第一个值需要是间接引用。间接引用的格式为 9 0 R
,而不是 9
。我查看了文档的结构,发现 wkhtmltopdf 使用页码 - 1 而不是间接引用。如果我查看 /__WKANCHOR_2
,它指的是 [0/XYZ 30.240000 781.459999 0]
,而 0
应该指向第 1 页。由于 Adobe Reader 容忍蹩脚的软件,这在 Adobe Reader,但由于该文件违反了 ISO-32000,iText 不知道如何处理那些误导性的目的地,至少,convience class SimpleNamedDEstination
不知道如何处理用它来做。
幸运的是,iText 是一个非常通用的库,可让您深入 PDF 的底层。在这种情况下,我们只需要更深入一层。我们可以使用以下方法代替 SimpleNamedDestination.getNamedDestination(reader, true)
:
HashMap<String, PdfObject> names = reader.getNamedDestinationFromNames();
for (Map.Entry<String, PdfObject> entry: names.entrySet()) {
System.out.print(entry.getKey());
System.out.print(": p");
PdfArray arr = (PdfArray)entry.getValue();
System.out.println(arr.getAsNumber(0).intValue() + 1);
}
reader.close();
这个方法的输出是:
__WKANCHOR_w: p7
__WKANCHOR_y: p7
__WKANCHOR_2: p1
__WKANCHOR_4: p1
__WKANCHOR_16: p9
__WKANCHOR_14: p8
__WKANCHOR_18: p9
__WKANCHOR_1s: p13
__WKANCHOR_a: p2
__WKANCHOR_1q: p13
__WKANCHOR_1o: p12
__WKANCHOR_12: p8
__WKANCHOR_1m: p12
__WKANCHOR_e: p3
__WKANCHOR_10: p7
__WKANCHOR_1k: p12
__WKANCHOR_c: p3
__WKANCHOR_1i: p11
__WKANCHOR_i: p4
__WKANCHOR_8: p2
__WKANCHOR_g: p3
__WKANCHOR_1g: p11
__WKANCHOR_6: p1
__WKANCHOR_1e: p10
__WKANCHOR_m: p5
__WKANCHOR_1c: p10
__WKANCHOR_k: p4
__WKANCHOR_q: p5
__WKANCHOR_1a: p9
__WKANCHOR_o: p5
__WKANCHOR_u: p6
__WKANCHOR_s: p6
如果我们检查 __WKANCHOR_2
,我们看到它正确指向第 1 页。我检查了大纲中最后的 link,它指向名为 de名称 __WKANCHOR_1s
的 tination 确实:应该 link 到第 13 页。
您的问题是 "garbage in-garbage out" 问题的一个明显例子。您的工具生成的 PDF 违反了 PDF 的 ISO 标准,结果您浪费了大量时间来找出问题所在。但更糟糕的是:你让我因为别人的错误而浪费了时间。
我需要在 PDF 中为 table 的内容创建一个页面。我将创建 PDF 阅读书签。
我使用 iText:
tmp = SimpleBookmark.getBookmark (reader);
使用此 PDF 进行测试:
Returns这张地图:
[{Action = GoTo, Named = __ WKANCHOR_2, Title = Secretariat Teste0}, {Action = GoTo, Named = __ WKANCHOR_4, Title = Secretariat TestBook1}, {Action = GoTo, Named = __ WKANCHOR_6, Title = Secretariat Test2}, {Action = GoTo , Named = __ WKANCHOR_8 ...
没有页码。
如何显示 table 的一篇内容,其中包含标题和页码?
我想展示这个:
请阅读此问题的答案:Java: Reading PDF bookmark names with itext
它解释了如何使用 SimpleBookmark
方法获取大纲树的标题(这就是 "bookmarks" 在 PDF 规范中的调用方式)。
public void inspectPdf(String filename) throws IOException, DocumentException {
PdfReader reader = new PdfReader(filename);
List<HashMap<String,Object>> bookmarks = SimpleBookmark.getBookmark(reader);
for (int i = 0; i < bookmarks.size(); i++){
showTitle(bookmarks.get(i));
}
reader.close();
}
public void showTitle(HashMap<String, Object> bm) {
System.out.println((String)bm.get("Title"));
List<HashMap<String,Object>> kids = (List<HashMap<String,Object>>)bm.get("Kids");
if (kids != null) {
for (int i = 0; i < kids.size(); i++) {
showTitle(kids.get(i));
}
}
}
然后阅读这个问题的答案:Set inherit Zoom(action property) to bookmark in the pdf file
您会看到 HashMap<String, Object>
不仅包含一个键为 "Title"
的条目,它还可以包含一个键为 "Page"
的条目。当书签指向一个页面时就是这种情况。该值将是一个 显式目标 。它将由页码、Fit
、FitH
、FitB
、XYZ
等值组成,后跟一些标记位置的参数。
如果您查看 CreateOutlineTree 示例,您会发现您还可以将书签提取为 XML 文件:
public void createXml(String src, String dest) throws IOException {
PdfReader reader = new PdfReader(src);
List<HashMap<String, Object>> list = SimpleBookmark.getBookmark(reader);
SimpleBookmark.exportToXML(list,
new FileOutputStream(dest), "ISO8859-1", true);
reader.close();
}
这是我写的一本关于 iText 的书的屏幕截图,其中显示了书签条目中可以使用的键:
从这个table可以看出,link也可以表示为命名目的地。在这种情况下,您不会得到页码,而是名称。要获取页码,您需要提取命名目标列表。此列表将为您提供与指定目的地相对应的明确目的地。
书中也有解释,official documentation。
获得标题和页码(使用基于上述指针编写的代码检索)后,您可以使用 PdfStamper
和 insertPage()
方法将页面插入 PDF 文件。您可以使用 ColumnText
将目录放在这些页面上,或者您可以为目录创建一个单独的 PDF 并将其与原始文件合并。请参阅 How to add a cover/PDF in a existing iText document 以了解有关这两种技术的更多信息。
您还将从此示例中受益:Create Index File(TOC) for merged pdf using itext library in java
至于标题和页码之间的虚线,这是使用 分隔符 完成的,更具体地说是虚线分隔符。你应该先阅读这个问题:iTextSharp - Is it possible to set a different alignment in the same cell for text
然后看这道题:How to Generate Table-of-Figures Dot Leaders in a PdfPCell for the Last Line of Wrapped Text (or this question It is possible with itext 5 which at the end of a paragraph justified the remaining space is filled with scripts?)
请注意,您的问题实际上是 off-topic。它被表述为 "home work" 问题。它邀请人们在你的地方做你的工作。现在您已经拥有了所需的所有元素,您应该能够自己完成这项工作。如果你没有成功,你应该写一个关于主题的 Stack Overflow 问题。在这个问题中,您可以展示您的尝试并解释您遇到的技术问题。
更新:
您使用以下大纲树共享文档:
如您所见,书签是使用命名目标定义的,例如 /__WKANCHOR_2
、/__WKANCHOR_4
等。从 /
字符可以看出,名称存储为 PDF 名称 objects (PDF 1.1),而不是 PDF 字符串 objects(自 1.2 起)。最新的 PDF 标准建议使用 PDF 字符串 objects 而不是 PDF 名称 objects,您可能需要请求 PDF 生成软件的供应商更新软件以满足最新 PDF 的建议标准。
不过,我们可以很容易地得到与那些命名目的地相对应的显式目的地。它们存储在根字典的 /Dests
条目中:
当您查看目的地的方式时,您会看到另一个应该报告给 wkhtmltopdf 的问题。让我们看一下 ISO 标准告诉我们有关用于目标的语法的内容:
PDF 中不存在页码的概念。页面是使用页面字典来描述的,页码是从页面在页面树中的位置得出的。在页面树中遇到的第一个页面是第1页,遇到的第二个页面是第2页,依此类推。
在您的示例中,解释目标定义如下:[9/XYZ 30.2400000 524.179999 0]
、[9/XYZ 30.2400000 231.379999 0]
,依此类推。
这是错误的。 ISO 标准规定数组中的第一个值需要是间接引用。间接引用的格式为 9 0 R
,而不是 9
。我查看了文档的结构,发现 wkhtmltopdf 使用页码 - 1 而不是间接引用。如果我查看 /__WKANCHOR_2
,它指的是 [0/XYZ 30.240000 781.459999 0]
,而 0
应该指向第 1 页。由于 Adobe Reader 容忍蹩脚的软件,这在 Adobe Reader,但由于该文件违反了 ISO-32000,iText 不知道如何处理那些误导性的目的地,至少,convience class SimpleNamedDEstination
不知道如何处理用它来做。
幸运的是,iText 是一个非常通用的库,可让您深入 PDF 的底层。在这种情况下,我们只需要更深入一层。我们可以使用以下方法代替 SimpleNamedDestination.getNamedDestination(reader, true)
:
HashMap<String, PdfObject> names = reader.getNamedDestinationFromNames();
for (Map.Entry<String, PdfObject> entry: names.entrySet()) {
System.out.print(entry.getKey());
System.out.print(": p");
PdfArray arr = (PdfArray)entry.getValue();
System.out.println(arr.getAsNumber(0).intValue() + 1);
}
reader.close();
这个方法的输出是:
__WKANCHOR_w: p7
__WKANCHOR_y: p7
__WKANCHOR_2: p1
__WKANCHOR_4: p1
__WKANCHOR_16: p9
__WKANCHOR_14: p8
__WKANCHOR_18: p9
__WKANCHOR_1s: p13
__WKANCHOR_a: p2
__WKANCHOR_1q: p13
__WKANCHOR_1o: p12
__WKANCHOR_12: p8
__WKANCHOR_1m: p12
__WKANCHOR_e: p3
__WKANCHOR_10: p7
__WKANCHOR_1k: p12
__WKANCHOR_c: p3
__WKANCHOR_1i: p11
__WKANCHOR_i: p4
__WKANCHOR_8: p2
__WKANCHOR_g: p3
__WKANCHOR_1g: p11
__WKANCHOR_6: p1
__WKANCHOR_1e: p10
__WKANCHOR_m: p5
__WKANCHOR_1c: p10
__WKANCHOR_k: p4
__WKANCHOR_q: p5
__WKANCHOR_1a: p9
__WKANCHOR_o: p5
__WKANCHOR_u: p6
__WKANCHOR_s: p6
如果我们检查 __WKANCHOR_2
,我们看到它正确指向第 1 页。我检查了大纲中最后的 link,它指向名为 de名称 __WKANCHOR_1s
的 tination 确实:应该 link 到第 13 页。
您的问题是 "garbage in-garbage out" 问题的一个明显例子。您的工具生成的 PDF 违反了 PDF 的 ISO 标准,结果您浪费了大量时间来找出问题所在。但更糟糕的是:你让我因为别人的错误而浪费了时间。