iText如何从可填写的模板创建多页文档

时间:2016-02-03 19:21:56

标签: java templates pdf itext

我正在尝试使用填写的表单在iText中创建一个多页PDF文档,每个人一个。我已经查找了如何在互联网上执行此操作的示例,并在我的解决方案中使用了这些示例。

PDF模板是使用Adobe Acrobat Pro创建的模板。

我已经能够使用iText从我的模板中成功填写并返回单页PDF文档,但多文档流程似乎无法正常工作。

我的程序演示了我要做的事情:

import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfSmartCopy;

import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.NumberFormat;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;

public class ITextTest
{
    public static final String TEMPLATE =
    "C:\\RAD7_5\\iTextTest\\iTextTest\\input\\LS213_1.pdf";

    public static void main(String[] args)
    {
        ITextTest iTextTest = new ITextTest();
        iTextTest.doItextTest();
    }

    public void doItextTest()
    {
        try
        {
            PdfReader pdfReader;
            PdfStamper pdfStamper;
            ByteArrayOutputStream baos;

            Document document = new Document();
            PdfSmartCopy pdfSmartCopy = new PdfSmartCopy(document,
                    new FileOutputStream("C:\\RAD7_5\\iTextTest\\iTextTest\\output\\LS213_1MultiTest.pdf"));

            DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
            Date currDate = new Date();
            NumberFormat numberFormat = NumberFormat.getCurrencyInstance();
            double amount = 4127.29d;

            document.open();

            for(int i = 1; i <= 5; i++)
            {
                pdfReader = new PdfReader(TEMPLATE);
                baos = new ByteArrayOutputStream();
                pdfStamper = new PdfStamper(pdfReader, baos);

                AcroFields acroFields = pdfStamper.getAcroFields();

                //key statement 1
                acroFields.setGenerateAppearances(true);

                //acroFields.setExtraMargin(5, 5);
                acroFields.setField("Name and Address", "John Doe\n123 Anywhere St.\nAnytown, USA 12345");
                acroFields.setField("Case Number", "123456789");
                acroFields.setField("Employer", "Employer Co., Inc.\n456 Anyhow ln.\nAnyville, USA 67890");
                acroFields.setField("Date", dateFormat.format(currDate));
                acroFields.setField("Name", "John Doe");
                acroFields.setField("restitution check No", "65432" + i);
                acroFields.setField("in the sum of", numberFormat.format(amount));

                //key statement 2
                pdfStamper.setFormFlattening(false);

                pdfStamper.close();
                pdfReader.close();

                pdfReader = new PdfReader(baos.toByteArray());
                pdfSmartCopy.addPage(pdfSmartCopy.getImportedPage(pdfReader, 1));
                pdfSmartCopy.freeReader(pdfReader);
                pdfReader.close();
            }

            document.close();
        }
        catch(DocumentException dex)
        {
            dex.printStackTrace();
            System.exit(1);
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
            System.exit(1);
        }
    }
}

在上面的代码中,您可以看到两个影响填充模板结果的关键语句:

acroFields.setGenerateAppearances(true);
pdfStamper.setFormFlattening(false);

使用上述两个语句,如果我将第一个设置为true而第二个设置为false,则它会填充字段,但它们与标签不对齐。此外,在第一个模板复制之后,由于某种原因,之后的每个副本都有一些未填充的字段。

如果我将它们都设置为true:

acroFields.setGenerateAppearances(true);
pdfStamper.setFormFlattening(true);

它设置所有模板副本中的所有字段。到目前为止,这对我来说是最成功的结果,但是填充的字段仍然与标签不对齐,设置表格变平为真,不再允许用户在应用程序中的数据错误后手动更正字段。

如果我将第一个设置为false而第二个设置为true:

acroFields.setGenerateAppearances(false);
pdfStamper.setFormFlattening(true);

所有字段都是空白的(最差结果)。

如果我将它们都设为假:

acroFields.setGenerateAppearances(false);
pdfStamper.setFormFlattening(false);

然后填充字段并显示与标签的右对齐。但是这些字段由于某种原因显示为空白,直到您单击它们为止。并且在后续页面中消除了某些字段的问题,就像真正的错误情景(提到的第一个场景)一样。

我想知道是否可以在没有错位字段值的情况下使其工作,而不会使字段变平,并且不会在后续页面上丢失字段。

我知道您可以使用

调整边距
acroFields.setExtraMargin(extraMarginLeft, extraMarginTop)

但使用

acroFields.setGenerateAppearances(false)

完美适用于单个表单而无需调整页边距,我希望它也能用于多页文档。

另外,使用

acroFields.setGenerateAppearances(true)
当您单击文本框时,

会导致文本移动并在文本框中稍微移位。对于单页文档和多页文档都会发生这种情况。使用setGenerateAppearances(true)设置字段时,使用Adobe Pro创建的iText或PDF模板中似乎存在错误。

我目前正在使用iText 5.5.8。

对此问题的任何帮助将不胜感激。感谢您花时间阅读本文。

1 个答案:

答案 0 :(得分:1)

这是一个很长的问题,我想这让人们很难回答这个问题。我也没有确定的答案,因为我无法重现这个问题。但是,我可以澄清一些事情。

<强> 1。在PDF中,一个字段可以与多个窗口小部件注释相对应。一个字段只能有一个值。

假设您有一个PDF表单,其中包含名为&#34; name&#34;的字段。该字段可能出现在文档中的不同位置。例如:如果表单有多个页面,则字段&#34; name&#34;可以与每个页面上的小部件注释相对应(例如,在标题中)。

字段&#34;名称&#34;只能有一个值,例如:&#34; Charles Carrington。&#34;如果该字段与不同的窗口小部件注释相对应,则这些可视化中的每一个都应显示相同的名称。

字段&#34;名称&#34;是不可能的。有一个名字&#34; Charles Carrington&#34;在一页上,&#34; Bruno Lowagie&#34;在另一页上。

为什么这对您很重要?

您尝试了setFormFlattening()

如果您将此方法与值false一起使用,那么您会犯一些错误:

  1. 您不能告诉PdfSmartCopy您正在合并表单:How to merge forms from different files into one PDF?
  2. 您违反了规则&#34; 1 field = 1 value&#34;因为您首先使用不同的值填写不同表单中的字段(例如表单1:name = Charles Carrington;表单2:name = Bruno Lowagie),然后合并这些表单进入一种形式,你突然希望一个字段具有不同的值(例如,第1页的mergedform:name = Charles Carrington;第2页的名称= Bruno Lowagie)。这违反了ISO-32000-1。
  3. 您可以通过以下方式避免此问题:

    • 重命名字段,如果对您保持交互性很重要。
    • 展平字段:在这种情况下,将删除所有字段。而是添加值的外观。所有互动都失去了。

    <强> 2。在PDF中,字段具有值,但也可以具有一个或多个外观。

    假设您有一个PDF表单,其中包含名为&#34; birthdate&#34;该字段的值是&#34; 1970-06-10&#34; (以及它是如何存储在数据库中的方式)。但是,当您在PDF文档中填写该字段时,您希望它显示为&#34; 1970年6月10日和#34;。

    这是可能的。字典字典的/V键的值将是PDF字符串1970-06-10。但是,/DA键将定义外观,显示&#34; 1970年6月10日&#34;。

    甚至可以使一个具有单个值(1970-06-10)的字段与具有不同外观的不同窗口小部件注释相对应:&#34; 1970年6月10日&#34;,&#34; 10 juin 1970&#34;,&#34; 10 juni 1970&#34;等等。

    为什么这对您很重要?

    您一直在试验setGenerateAppearances()

    当您使用值false的此方法时,您指示iText省略/DA:不创建外观。当您展平表单时,字段为空。当您不压平表单时,PDF查看器将创建外观。由于Adobe和其他供应商在渲染PDF的方式上始终保持一致,因此很难预测外观会是什么样子。一个查看器将在为窗口小部件注释定义的矩形内的一个位置显示值;另一个观众将以不同的偏移显示它。

    当您使用值为true的此方法时,您会指示iText以一致的方式创建外观。

    但是:如果你没有弄平表格,你可以在问题Why does iText enter a cross symbol when CheckType style is check mark?的答案中描述效果。 在此示例中,您会看到复选框的外观取决于字段是否高亮显示。文本字段也是如此:无论您是否选择它,外观都可能不同。另外:当您更改值时,您将获得查看器创建的外观。例如。当你点击&#34; 1970年6月10日&#34;因为你想改变它,你会突然看到&#34; 1970-06-10&#34;因为那是为该字段存储的值,该值是由查看者生成的。

    如果您展平表单,那么iText会创建外观并删除所有交互性。在这种情况下,观看者不会创建任何外观:表单中没有更多字段。

    第3。 iText始终以相同的方式创建扁平化外观。

    这是阅读你的问题后仍然存在的谜。您声称填充和压平单个表单时的外观与填充和压平多个表单然后连接它们时的外观不同。我无法重现这个问题。我可以回答这个问题的唯一答案是:它对我有用。(如果你不相信我,那么请watch this tutorial。)

    请根据 1。 2。中提供的信息调整您的示例,如果问题仍然存在,请发布一个新的更短的问题。