如何知道字段是否在特定页面上?

Mar*_*ski 4 java pdfbox

PDFbox内容流是按页面完成的,但这些字段来自目录中的表单,该表格来自pdf文档本身.所以我不确定哪些字段在哪些页面上,并且导致将文本写入错误的位置/页面.

即.我正在处理每页的字段,但不确定哪些字段在哪些页面上.

有没有办法告诉哪个字段在哪个页面上?或者,有没有办法获得当前页面上的字段?

谢谢!

标记

代码段:

PDDocument pdfDoc = PDDocument.load(file);
PDDocumentCatalog docCatalog = pdfDoc.getDocumentCatalog();
PDAcroForm acroForm = docCatalog.getAcroForm();

// Get field names
List<PDField> fieldList = acroForm.getFields();
List<PDPage> pages = pdfDoc.getDocumentCatalog().getAllPages();
for (PDPage page : pages) {
  PDPageContentStream contentStream = new PDPageContentStream(pdfDoc, page, true, true, true);
  processFields(acroForm, fieldList, contentStream, page);
  contentStream.close();
}
Run Code Online (Sandbox Code Playgroud)

mkl*_*mkl 6

PDFbox内容流是按页面完成的,但这些字段来自目录中的表单,该表格来自pdf文档本身.所以我不确定哪些字段在哪个页面上

原因是PDF包含定义表单的全局对象结构.此结构中的表单字段可以在0,1或更多实际PDF页面上具有0,1或更多可视化.此外,在仅1个可视化的情况下,允许合并场对象和可视化对象.

PDFBox 1.8.x

在不幸的是PDFBox的PDAcroFormPDField对象仅代表此对象的结构,并且不提供方便地访问到相关的网页.但是,通过访问底层结构,您可以构建连接.

以下代码应该明确如何做到这一点:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,PDFBox PDAcroForm表单字段处理有两个缺点:

  1. PDF规范允许定义表单的全局对象结构是深树,即实际字段不必是根的直接子节点,而是可以通过内部树节点来组织.PDFBox忽略了这一点,并希望字段是根的直接子节点.

  2. 野外的一些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;
}
Run Code Online (Sandbox Code Playgroud)

快速的方法

int determineFast(PDDocument document, PDAnnotationWidget widget)
{
    PDPage page = widget.getPage();
    return page != null ? document.getPages().indexOf(page) : -1;
}
Run Code Online (Sandbox Code Playgroud)

用法:

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: %s\n", determineSafe(document, widget));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(DetermineWidgetPage.java)

(与1.8.x代码相比,这里的安全方法只是搜索单个字段的页面.如果在代码中你必须确定许多小部件的页面,你应该创建一个Map类似于1.8.x 的查找. )

示例文档

快速方法失败的文档:aFieldTwice.pdf

快速方法适用的文档:test_duplicate_field2.pdf