使用 PDFBOX 生成的 PDF 中未正确显示已保存的文本字段值

Posted

技术标签:

【中文标题】使用 PDFBOX 生成的 PDF 中未正确显示已保存的文本字段值【英文标题】:Saved Text Field value is not displayed properly in PDF generated using PDFBOX 【发布时间】:2015-11-30 15:02:28 【问题描述】:
import java.io.IOException;

import javax.swing.text.BadLocationException;

import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSFloat;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.action.PDAnnotationAdditionalActions;
import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionjavascript;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextbox;
import org.junit.Test;

public class TestPDTextbox 
    @Test
    public void Sample1 () throws IOException, COSVisitorException, BadLocationException 


        PDDocument doc = new PDDocument();
        PDPage page = new PDPage();
        doc.addPage(page);   

        COSDictionary acroFormDict = new COSDictionary(); 
//        acroFormDict.setBoolean(COSName.getPDFName("NeedAppearances"), true);
        acroFormDict.setItem(COSName.getPDFName("Fields"), new COSArray());

        PDAcroForm acroForm = new PDAcroForm(doc, acroFormDict);
        doc.getDocumentCatalog().setAcroForm(acroForm);

        COSDictionary cosDict1 = new COSDictionary();
        COSArray rect1 = new COSArray();
        rect1.add(new COSFloat(100));
        rect1.add(new COSFloat(700));
        rect1.add(new COSFloat(200));
        rect1.add(new COSFloat(750));

        cosDict1.setItem(COSName.RECT, rect1);
        cosDict1.setItem(COSName.FT, COSName.getPDFName("Tx")); // Field Type
        cosDict1.setItem(COSName.TYPE, COSName.ANNOT);
        cosDict1.setItem(COSName.SUBTYPE, COSName.getPDFName("Widget"));
        cosDict1.setItem(COSName.T, new COSString("tx1"));
        cosDict1.setItem(COSName.DA, new COSString("/Helv 7 Tf 0 g"));
        cosDict1.setItem(COSName.V, new COSString("Test Value1"));

        PDTextbox textbox = new PDTextbox(doc.getDocumentCatalog().getAcroForm(), cosDict1);

//      textbox.setValue("Test Value");

        page.getAnnotations().add(textbox.getWidget());
        acroForm.getFields().add(textbox);

         doc.save("C:\\PDF\\SampleTextbox.pdf");
         doc.close();
    

问题#1 我创建了一个文本字段,如上面的代码所示,并尝试使用 textbox.setValue("Test Value"); 设置值。方法,但它给出了如下所示的错误:

java.io.IOException: Error: Don't know how to calculate the position for non-simple fonts
    at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.getTextPosition(PDAppearance.java:1037)
    at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.insertGeneratedAppearance(PDAppearance.java:558)
    at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.setAppearanceValue(PDAppearance.java:338)
    at org.apache.pdfbox.pdmodel.interactive.form.PDVariableText.setValue(PDVariableText.java:131)
    at sample.pdfbox.example.TestPDTextbox.Sample1(TestPDTextbox.java:54)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

问题#2

为了解决问题#1,如果我使用 cosDictionary 属性设置 textBox 的值 即 cosDict1.setItem(COSName.V, new COSString("Test Value1"));

然后在 Adob​​e Reader 中,文本框的值没有正确填充。 我必须单击文本框,然后只出现值,一旦我从该字段中移出,值再次变得不可见。

问题#3

为了解决问题#2,我需要将 needAppearances 标志设置为 true,如下所示,然后该字段值在 PDF 中正确显示。但是在这个解决方案之后,一旦用户更改了字段值,我就无法提取/解析 PDF 字段,我们再次解析这个 PDF。

注意:-此问题存在于 Adob​​e Reader 中,此处在打开 PDF 时也会显示一些消息,例如修复表单字段。一旦我保存 PDF 并尝试解析 acroform 字段,就会发现所有字段都被重置或为空。无法提取任何字段名称或字段值。

所以使用 acroFormDict.setBoolean(COSName.getPDFName("NeedAppearances"), true); in code 似乎有风险,它会在 PDF 解析中产生其他问题,因此无法使用。

COSDictionary acroFormDict = new COSDictionary(); 
        acroFormDict.setBoolean(COSName.getPDFName("NeedAppearances"), true);
        acroFormDict.setItem(COSName.getPDFName("Fields"), new COSArray());

        PDAcroForm acroForm = new PDAcroForm(doc, acroFormDict);
        doc.getDocumentCatalog().setAcroForm(acroForm);

我认为,我需要为文本字段设置 PDAppearanceDictionary,但我不知道该怎么做以及是否需要为每个字段或在 acroform 级别设置。

请帮我解决这个问题,我应该如何解决。 我正在使用 PDFBOX 1.8.10 版。

【问题讨论】:

我能够使用 AR 打开文件,编辑字段,保存文件,然后像这样重新打开:doc = PDDocument.load("SampleTextbox2.pdf"); PDField 字段 = doc.getDocumentCatalog().getAcroForm().getField("tx1"); System.out.println(field.getValue()); doc.close();输出为Test Value2,即修改后的值。 【参考方案1】:

在上述问题中,我通过将页面资源添加到 acroform 并为文本使用正确的默认外观字符串来修复问题 #1。现在我不需要将 needsAppearance 标志设置为 true。

        PDFont font = PDType1Font.HELVETICA;
        PDResources res = new PDResources();
        String fontName = res.addFont(font);
        String defaultAppearance = "/"+fontName+" 7 Tf 0 g";

        COSDictionary acroFormDict = new COSDictionary(); 
        acroFormDict.setBoolean(COSName.getPDFName("NeedAppearances"), false);
        acroFormDict.setItem(COSName.getPDFName("Fields"), new COSArray());
        acroFormDict.setItem(COSName.DA, new COSString(defaultAppearance));

        PDAcroForm acroForm = new PDAcroForm(doc, acroFormDict);
        acroForm.setDefaultResources(res);

检查下面的整个更正代码:

import java.io.IOException;

import javax.swing.text.BadLocationException;

import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSFloat;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextbox;
import org.junit.Test;

public class TestPDTextbox 
    @Test
    public void Sample1 () throws IOException, COSVisitorException, BadLocationException 


        PDDocument doc = new PDDocument();
        PDPage page = new PDPage();
        doc.addPage(page);   

        PDFont font = PDType1Font.HELVETICA;
        PDResources res = new PDResources();
        String fontName = res.addFont(font);
        String defaultAppearance = "/"+fontName+" 7 Tf 0 g";

        COSDictionary acroFormDict = new COSDictionary(); 
        acroFormDict.setBoolean(COSName.getPDFName("NeedAppearances"), false);
        acroFormDict.setItem(COSName.getPDFName("Fields"), new COSArray());
        acroFormDict.setItem(COSName.DA, new COSString(defaultAppearance));

        PDAcroForm acroForm = new PDAcroForm(doc, acroFormDict);
        acroForm.setDefaultResources(res);

        doc.getDocumentCatalog().setAcroForm(acroForm);

        COSDictionary cosDict1 = new COSDictionary();
        COSArray rect1 = new COSArray();
        rect1.add(new COSFloat(100));
        rect1.add(new COSFloat(700));
        rect1.add(new COSFloat(200));
        rect1.add(new COSFloat(750));

        cosDict1.setItem(COSName.RECT, rect1);
        cosDict1.setItem(COSName.FT, COSName.getPDFName("Tx")); // Field Type
        cosDict1.setItem(COSName.TYPE, COSName.ANNOT);
        cosDict1.setItem(COSName.SUBTYPE, COSName.getPDFName("Widget"));
        cosDict1.setItem(COSName.T, new COSString("tx1"));
        cosDict1.setItem(COSName.DA, new COSString(defaultAppearance));
//        cosDict1.setItem(COSName.V, new COSString("Test Value1"));

        PDTextbox textbox = new PDTextbox(doc.getDocumentCatalog().getAcroForm(), cosDict1);

      textbox.setValue("Test Value");

        page.getAnnotations().add(textbox.getWidget());
        acroForm.getFields().add(textbox);

         doc.save("C:\\PDF\\SampleTextbox.pdf");
         doc.close();
    

【讨论】:

以上是关于使用 PDFBOX 生成的 PDF 中未正确显示已保存的文本字段值的主要内容,如果未能解决你的问题,请参考以下文章

使用 PDFBox 生成的 PDF 为空白

使用 PDFBox 为扁平化 PDF 表单嵌入字体

PDFBox 生成的 PDF 尺寸(高度和宽度)被交换

在 Laravel 刀片中生成的 PDF 中未显示字体真棒图标

非常强大的生成pdf的框架!

PDFBOX Flatten Acro-form 导致文本从原始位置移位