PDFbox内容流是按页面完成的,但是字段来自于来自目录的表单,该目录来自pdf doc本身。所以我不确定哪些字段在哪些页面上
其原因是PDF包含定义表单的全局对象结构。此结构中的表单字段在0、1或更多实际PDF页面上可能具有0、1或更多可视化效果。此外,在仅一个可视化的情况下,允许将现场对象和可视化对象合并。
PDFBox 1.8.x
不幸的是,PDFBox中的
PDAcroForm和
PDField对象仅表示此对象结构,无法轻松访问相关页面。但是,通过访问基础结构,您可以建立连接。
以下代码应明确说明如何执行此操作:
@SuppressWarnings("unchecked")public void printFormFields(PDdocument pdfDoc) throws IOException { PDdocumentCatalog docCatalog = pdfDoc.getdocumentCatalog(); List<PDPage> pages = docCatalog.getAllPages(); Map<COSDictionary, Integer> pageNrByAnnotDict = new HashMap<COSDictionary, Integer>(); for (int i = 0; i < pages.size(); i++) { PDPage page = pages.get(i); for (PDAnnotation annotation : page.getAnnotations()) pageNrByAnnotDict.put(annotation.getDictionary(), i + 1); } PDAcroForm acroForm = docCatalog.getAcroForm(); for (PDField field : (List<PDField>)acroForm.getFields()) { COSDictionary fieldDict = field.getDictionary(); List<Integer> annotationPages = new ArrayList<Integer>(); List<COSObjectable> kids = field.getKids(); if (kids != null) { for (COSObjectable kid : kids) { COSbase kidObject = kid.getCOSObject(); if (kidObject instanceof COSDictionary) annotationPages.add(pageNrByAnnotDict.get(kidObject)); } } Integer mergedPage = pageNrByAnnotDict.get(fieldDict); if (mergedPage == null) if (annotationPages.isEmpty()) System.out.printf("i Field '%s' not referenced (invisible).n", field.getFullyQualifiedName()); else System.out.printf("a Field '%s' referenced by separate annotation on %s.n", field.getFullyQualifiedName(), annotationPages); else if (annotationPages.isEmpty()) System.out.printf("m Field '%s' referenced as merged on %s.n", field.getFullyQualifiedName(), mergedPage); else System.out.printf("x Field '%s' referenced as merged on %s and by separate annotation on %s. (Not allowed!)n", field.getFullyQualifiedName(), mergedPage, annotationPages); }}当心 ,PDFBox
PDAcroForm表单字段处理中有两个缺点:
PDF规范允许定义表单的全局对象结构为一棵深树,即,实际字段不必是根的直接子代,而可以通过内部树节点来组织。PDFBox会忽略这一点,并希望这些字段是根的直接子代。
某些原始的PDF(最早的PDF)不包含域树,而仅通过可视化的窗口小部件注释引用页面中的域对象。PDFBox在其
PDAcroForm.getFields
列表中看不到这些字段。
PS:
@mikhailvs在他的答案中正确显示,您可以使用来从字段窗口小部件检索页面对象,
PDField.getWidget().getPage()并使用确定其页码
catalog.getAllPages().indexOf。这种
getPage()方法虽然速度很快,但有一个缺点:它从小部件注释字典的
可选
条目中检索页面引用。因此,如果您处理的PDF是由填充该条目的软件创建的,那么一切都很好,但是,如果PDF创建者尚未填充该条目,那么您得到的只是一个
null页面。
PDFBox 2.0.x
在2.0.x中,用于访问有问题的元素的某些方法已更改,但整体情况没有改变,要安全地检索小部件的页面,您仍然必须遍历页面并找到引用注释的页面。
安全方法:
int determineSafe(PDdocument document, PDAnnotationWidget widget) throws IOException{ COSDictionary widgetObject = widget.getCOSObject(); PDPageTree pages = document.getPages(); for (int i = 0; i < pages.getCount(); i++) { for (PDAnnotation annotation : pages.get(i).getAnnotations()) { COSDictionary annotationObject = annotation.getCOSObject(); if (annotationObject.equals(widgetObject)) return i; } } return -1;}快速方法
int determineFast(PDdocument document, PDAnnotationWidget widget){ PDPage page = widget.getPage(); return page != null ? document.getPages().indexOf(page) : -1;}用法:
PDAcroForm acroForm = document.getdocumentCatalog().getAcroForm();if (acroForm != null){ for (PDField field : acroForm.getFieldTree()) { System.out.println(field.getFullyQualifiedName()); for (PDAnnotationWidget widget : field.getWidgets()) { System.out.print(widget.getAnnotationName() != null ? widget.getAnnotationName() : "(NN)"); System.out.printf(" - fast: %s", determineFast(document, widget)); System.out.printf(" - safe: %sn", determineSafe(document, widget)); } }}( 确定小部件页面.java)
(与1.8.x代码相反,这里的安全方法只是搜索单个字段的页面。如果在代码中必须确定许多小部件的页面,则应
Map像在1.8.x情况下那样创建查找。 )
示例文件
快速方法失败的文档:aFieldTwice.pdf
快速方法适用的文档:test_duplicate_field2.pdf



