【问题标题】:iText - add content to existing PDF fileiText - 将内容添加到现有的 PDF 文件
【发布时间】:2011-03-21 02:12:30
【问题描述】:

我想用 iText 做以下事情:

(1) 解析现有的 PDF 文件

(2) 在文档的现有单页上添加一些数据(例如时间戳)

(3) 写出文件

我似乎无法弄清楚如何使用 iText 执行此操作。在伪代码中我会这样做:

Document document = reader.read(input);
document.add(new Paragraph("my timestamp"));
writer.write(document, output);

但由于某种原因,iText 的 API 非常复杂,以至于我无法理解它。 PdfReader 实际上保存了文档模型或其他东西(而不是吐出文档),您需要一个 PdfWriter 来从中读取页面......嗯?

【问题讨论】:

    标签: java pdf itext


    【解决方案1】:

    how-to-update-a-pdf-without-creating-a-new-pdf

    iText 7,请注意版本

    PdfReader reader = new PdfReader(src);
    PdfWriter writer = new PdfWriter(dest);
    PdfDocument pdfDoc = new PdfDocument(reader, writer);
    //manipulate pdf…
    pdfDoc.close();
    

    【讨论】:

    • 这对于 itext 7.x 是正确的。由于问题和接受的答案来自 2010 年,但是,当还没有 itext 7 时,您应该在答案中提及这个细节。
    • 很高兴知道 iText 7 有更好的 API,以防我需要再次在 Java 中进行 PDF 工作......
    • @WouterLievens 许可也发生了变化,所以在使用之前检查一下。
    【解决方案2】:
    Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, 
            new FileOutputStream("E:/TextFieldForm.pdf"));
        document.open();
    
        PdfPTable table = new PdfPTable(2);
        table.getDefaultCell().setPadding(5f); // Code 1
        table.setHorizontalAlignment(Element.ALIGN_LEFT);
        PdfPCell cell;      
    
        // Code 2, add name TextField       
        table.addCell("Name"); 
        TextField nameField = new TextField(writer, 
            new Rectangle(0,0,200,10), "nameField");
        nameField.setBackgroundColor(Color.WHITE);
        nameField.setBorderColor(Color.BLACK);
        nameField.setBorderWidth(1);
        nameField.setBorderStyle(PdfBorderDictionary.STYLE_SOLID);
        nameField.setText("");
        nameField.setAlignment(Element.ALIGN_LEFT);
        nameField.setOptions(TextField.REQUIRED);               
        cell = new PdfPCell();
        cell.setMinimumHeight(10);
        cell.setCellEvent(new FieldCell(nameField.getTextField(), 
            200, writer));
        table.addCell(cell);
    
        // force upper case javascript
        writer.addJavaScript(
            "var nameField = this.getField('nameField');" +
            "nameField.setAction('Keystroke'," +
            "'forceUpperCase()');" +
            "" +
            "function forceUpperCase(){" +
            "if(!event.willCommit)event.change = " +
            "event.change.toUpperCase();" +
            "}");
    
    
        // Code 3, add empty row
        table.addCell("");
        table.addCell("");
    
    
        // Code 4, add age TextField
        table.addCell("Age");
        TextField ageComb = new TextField(writer, new Rectangle(0,
             0, 30, 10), "ageField");
        ageComb.setBorderColor(Color.BLACK);
        ageComb.setBorderWidth(1);
        ageComb.setBorderStyle(PdfBorderDictionary.STYLE_SOLID);
        ageComb.setText("12");
        ageComb.setAlignment(Element.ALIGN_RIGHT);
        ageComb.setMaxCharacterLength(2);
        ageComb.setOptions(TextField.COMB | 
            TextField.DO_NOT_SCROLL);
        cell = new PdfPCell();
        cell.setMinimumHeight(10);
        cell.setCellEvent(new FieldCell(ageComb.getTextField(), 
            30, writer));
        table.addCell(cell);
    
        // validate age javascript
        writer.addJavaScript(
            "var ageField = this.getField('ageField');" +
            "ageField.setAction('Validate','checkAge()');" +
            "function checkAge(){" +
            "if(event.value < 12){" +
            "app.alert('Warning! Applicant\\'s age can not" +
            " be younger than 12.');" +
            "event.value = 12;" +
            "}}");      
    
    
    
        // add empty row
        table.addCell("");
        table.addCell("");
    
    
        // Code 5, add age TextField
        table.addCell("Comment");
        TextField comment = new TextField(writer, 
            new Rectangle(0, 0,200, 100), "commentField");
        comment.setBorderColor(Color.BLACK);
        comment.setBorderWidth(1);
        comment.setBorderStyle(PdfBorderDictionary.STYLE_SOLID);
        comment.setText("");
        comment.setOptions(TextField.MULTILINE | 
            TextField.DO_NOT_SCROLL);
        cell = new PdfPCell();
        cell.setMinimumHeight(100);
        cell.setCellEvent(new FieldCell(comment.getTextField(), 
            200, writer));
        table.addCell(cell);
    
    
        // check comment characters length javascript
        writer.addJavaScript(
            "var commentField = " +
            "this.getField('commentField');" +
            "commentField" +
            ".setAction('Keystroke','checkLength()');" +
            "function checkLength(){" +
            "if(!event.willCommit && " +
            "event.value.length > 100){" +
            "app.alert('Warning! Comment can not " +
            "be more than 100 characters.');" +
            "event.change = '';" +
            "}}");          
    
        // add empty row
        table.addCell("");
        table.addCell("");
    
    
        // Code 6, add submit button    
        PushbuttonField submitBtn = new PushbuttonField(writer,
                new Rectangle(0, 0, 35, 15),"submitPOST");
        submitBtn.setBackgroundColor(Color.GRAY);
        submitBtn.
            setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
        submitBtn.setText("POST");
        submitBtn.setOptions(PushbuttonField.
            VISIBLE_BUT_DOES_NOT_PRINT);
        PdfFormField submitField = submitBtn.getField();
        submitField.setAction(PdfAction
        .createSubmitForm("",null, PdfAction.SUBMIT_HTML_FORMAT));
    
        cell = new PdfPCell();
        cell.setMinimumHeight(15);
        cell.setCellEvent(new FieldCell(submitField, 35, writer));
        table.addCell(cell);
    
    
    
        // Code 7, add reset button
        PushbuttonField resetBtn = new PushbuttonField(writer,
                new Rectangle(0, 0, 35, 15), "reset");
        resetBtn.setBackgroundColor(Color.GRAY);
        resetBtn.setBorderStyle(
            PdfBorderDictionary.STYLE_BEVELED);
        resetBtn.setText("RESET");
        resetBtn
        .setOptions(
            PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT);
        PdfFormField resetField = resetBtn.getField();
        resetField.setAction(PdfAction.createResetForm(null, 0));
        cell = new PdfPCell();
        cell.setMinimumHeight(15);
        cell.setCellEvent(new FieldCell(resetField, 35, writer));
        table.addCell(cell);        
    
        document.add(table);
        document.close();
    }
    
    
    class FieldCell implements PdfPCellEvent{
    
        PdfFormField formField;
        PdfWriter writer;
        int width;
    
        public FieldCell(PdfFormField formField, int width, 
            PdfWriter writer){
            this.formField = formField;
            this.width = width;
            this.writer = writer;
        }
    
        public void cellLayout(PdfPCell cell, Rectangle rect, 
            PdfContentByte[] canvas){
            try{
                // delete cell border
                PdfContentByte cb = canvas[PdfPTable
                    .LINECANVAS];
                cb.reset();
    
                formField.setWidget(
                    new Rectangle(rect.left(), 
                        rect.bottom(), 
                        rect.left()+width, 
                        rect.top()), 
                        PdfAnnotation
                        .HIGHLIGHT_NONE);
    
                writer.addAnnotation(formField);
            }catch(Exception e){
                System.out.println(e);
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      这是我能想象到的最复杂的场景:我有一个使用 Ilustrator 创建并使用 Acrobat 修改为具有 AcroFields (AcroForm) 的 PDF 文件,我将使用这个 Java 代码填充数据,该 PDF 的结果字段中包含数据的文件被修改添加一个文档。

      实际上,在这种情况下,我正在动态生成添加到 PDF 的背景,该 PDF 也是动态生成的,其中包含未知数量的数据或页面。

      我正在使用 JBoss,此代码位于 JSP 文件中(应该可以在任何 JSP 网络服务器中使用)。

      注意:如果您使用 IExplorer,您必须使用 POST 方法提交 HTTP 表单才能下载文件。如果不是,您将在屏幕上看到 PDF 代码。这在 Chrome 或 Firefox 中不会发生。

      <%@ page import="java.io.*, com.lowagie.text.*, com.lowagie.text.pdf.*" %><%
      
      response.setContentType("application/download");
      response.setHeader("Content-disposition","attachment;filename=listaPrecios.pdf" );  
      
      // -------- FIRST THE PDF WITH THE INFO ----------
      String str = "";
      // lots of words
      for(int i = 0; i < 800; i++) str += "Hello" + i + " ";
      // the document
      Document doc = new Document( PageSize.A4, 25, 25, 200, 70 );
      ByteArrayOutputStream streamDoc = new ByteArrayOutputStream();
      PdfWriter.getInstance( doc, streamDoc );
      // lets start filling with info
      doc.open();
      doc.add(new Paragraph(str));
      doc.close();
      // the beauty of this is the PDF will have all the pages it needs
      PdfReader frente = new PdfReader(streamDoc.toByteArray());
      PdfStamper stamperDoc = new PdfStamper( frente, response.getOutputStream());
      
      // -------- THE BACKGROUND PDF FILE -------
      // in JBoss the file has to be in webinf/classes to be readed this way
      PdfReader fondo = new PdfReader("listaPrecios.pdf");
      ByteArrayOutputStream streamFondo = new ByteArrayOutputStream();
      PdfStamper stamperFondo = new PdfStamper( fondo, streamFondo);
      // the acroform
      AcroFields form = stamperFondo.getAcroFields();
      // the fields 
      form.setField("nombre","Avicultura");
      form.setField("descripcion","Esto describe para que sirve la lista ");
      stamperFondo.setFormFlattening(true);
      stamperFondo.close();
      // our background is ready
      PdfReader fondoEstampado = new PdfReader( streamFondo.toByteArray() );
      
      // ---- ADDING THE BACKGROUND TO EACH DATA PAGE ---------
      PdfImportedPage pagina = stamperDoc.getImportedPage(fondoEstampado,1);
      int n = frente.getNumberOfPages();
      PdfContentByte background;
      for (int i = 1; i <= n; i++) {
          background = stamperDoc.getUnderContent(i);
          background.addTemplate(pagina, 0, 0);
      }
      // after this everithing will be written in response.getOutputStream()
      stamperDoc.close(); 
      %>
      

      还有另一种更简单的解决方案,可以解决您的问题。这取决于您要添加的文本数量。

      // read the file
      PdfReader fondo = new PdfReader("listaPrecios.pdf");
      PdfStamper stamper = new PdfStamper( fondo, response.getOutputStream());
      PdfContentByte content = stamper.getOverContent(1);
      // add text
      ColumnText ct = new ColumnText( content );
      // this are the coordinates where you want to add text
      // if the text does not fit inside it will be cropped
      ct.setSimpleColumn(50,500,500,50);
      ct.setText(new Phrase(str, titulo1));
      ct.go();
      

      【讨论】:

        【解决方案4】:

        Gutch 的代码是 close,但只有在以下情况下才能正常工作:

        • 没有注释(链接、字段等)、没有文档结构/标记的内容、没有书签、没有文档级脚本等等……
        • 页面大小恰好为 A.4(几率不错,但它不适用于您碰巧遇到的任何 ol' PDF)
        • 您不介意丢失所有原始文档元数据(制作者、创建日期,可能还有作者/标题/关键字),也许还有文档 ID。您无法复制创建日期和文档 ID,除非您对 iText 本身进行了一些非常深入的黑客攻击)。

        认可的方法是反其道而行之。使用 PdfStamper 打开现有文档,并使用从 getOverContent() 返回的 PdfContentByte 将文本(以及您可能需要的任何其他内容)直接写入页面。不需要第二份文件。

        您可以使用 ColumnText 来处理布局等...无需使用 beginText()、setFontAndSize()、drawText()、drawText()...、endText()。

        【讨论】:

        • 所有优点...这是确定PdfStamperaddTemplate 方法是否更适合您的方案的好方法。在我的情况下,addTemplate 显然更好,因为您的观点:我得到了一个图形设计师提供的源模板,该模板是在 Adob​​e Illustrator 中生成的,有很多垃圾和元数据,重量为 1MB。如果我使用PdfStamper,生成的文档将超过 1MB,并且其中包含合同图形设计师的姓名;通过使用addDocument,他们的文档大小为 50kB,并且没有嵌入任何个人信息。
        • 哇。这是一个巨大的尺寸变化。元数据并没有那么大......剩下的空间是什么?!
        • 我认为那些大的 PDF 会选中“保留 Illustrator 编辑功能”框,这会将所有 Adob​​e Illustrator 信息保存在文件中以允许进一步编辑。这有点像从文档创建 PDF 并将源 DOC 文件嵌入其中。
        • 在现有 Stamper 上使用 CT 的示例:over = PdfStamper.getOverContent(1); ct = new ColumnText(over); ct.setSimpleColumn(120,48,200,500); p = new Paragraph(24, new Chunk("some multi line") ); ct.addElement(p); ct.go();
        【解决方案5】:

        iText 有不止一种方法可以做到这一点。 PdfStamper 类是一种选择。但我发现最简单的方法是创建一个新的 PDF 文档,然后将现有文档中的各个页面导入到新的 PDF 中。

        // Create output PDF
        Document document = new Document(PageSize.A4);
        PdfWriter writer = PdfWriter.getInstance(document, outputStream);
        document.open();
        PdfContentByte cb = writer.getDirectContent();
        
        // Load existing PDF
        PdfReader reader = new PdfReader(templateInputStream);
        PdfImportedPage page = writer.getImportedPage(reader, 1); 
        
        // Copy first page of existing PDF into output PDF
        document.newPage();
        cb.addTemplate(page, 0, 0);
        
        // Add your new data / text here
        // for example...
        document.add(new Paragraph("my timestamp")); 
        
        document.close();
        

        这将从templateInputStream 读取PDF 并将其写入outputStream。这些可能是文件流或内存流或适合您的应用程序的任何内容。

        【讨论】:

        • 谢谢。如果您不想将其限制为仅 A4,您可以添加 document.setPageSize(reader.getPageSize(1));
        • 当我使用这种方法时,PDF 完全错位了。因此,我选择了 Mark Storer 的答案并使用了 PdfStamper。
        • 这工作正常,但带有 acrofields 的页面并没有在 cb.addTemplate(page,0,0) 中与它们一起复制。 Acrofields 在输出 pdf 中不可用
        • 我不得不将 newPage 放在 cb.addTemplate() 之后,但它可以工作!
        猜你喜欢
        • 2012-11-08
        • 2011-12-31
        • 1970-01-01
        • 2017-12-19
        • 2015-05-06
        • 1970-01-01
        • 1970-01-01
        • 2016-08-04
        • 1970-01-01
        相关资源
        最近更新 更多