iText 2.1.7 PdfCopy.addPage(page)找不到页面引用?

3

我正在维护一个使用 iText 2.1.7 创建 PDF 的 Web 应用程序。我想将现有 PDF 的内容放入代码正在创建的 PDF 文档中。以下是我的代码(编辑:更完整的代码):

package itexttest;

import com.lowagie.text.Document;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

public class ITextTest 
{
    public static void main(String[] args) 
    {
        try
        {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Document bigDoc = new Document(PageSize.LETTER, 50, 50, 110, 60);
            PdfWriter writer = PdfWriter.getInstance(bigDoc, os);
            bigDoc.open();

            Paragraph par = new Paragraph("one");
            bigDoc.add(par);
            bigDoc.add(new Paragraph("three"));

            addPdfPage(bigDoc, os, "c:/insertable.pdf");

            bigDoc.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    private static void addPdfPage(Document document, OutputStream outputStream, String location) {
        try {

            PdfReader pdfReader = new PdfReader(location);
            int pages = pdfReader.getNumberOfPages();

            PdfCopy pdfCopy = new PdfCopy(document, outputStream);
            PdfImportedPage page = pdfCopy.getImportedPage(pdfReader, 1);
            pdfCopy.addPage(page);
        }
        catch (Exception e) {
            System.out.println("Cannot add PDF from PSC: <" + location + ">: " + e.getMessage());
            e.printStackTrace();
        }
    }

}

这会抛出一个错误,从 PdfWriter.getPageReference() 返回 null。

我使用方法有误吗?如何将现有文档中的页面放入当前文档中?请注意,我不方便将其写入文件作为临时存储或其他方式。


这会抛出一个错误,来自于 PdfWriter.getPageReference() 的 null 值。我没有看到你在代码中使用这个方法。你的代码在哪里出现了异常? - mkl
抱歉——来自pdfCopy.getImportedPage(pdfReader,1) - arcy
好的,基本上你的代码不能按照你想要的方式工作。你不能首先为PdfWriter然后为PdfCopy重用文档和输出流。错误本身有些令人惊讶,但某些错误是可以预料的。你到底希望实现什么? - mkl
我有一段生成PDF的代码。我想添加一些代码,将现有PDF的内容插入到我正在生成的PDF中。我有一个要读取的文件,但我不是在生成文件,而是生成一个字节数组。 - arcy
2个回答

5

我不再积极使用旧版iText,但有些事情自那时以来并没有改变。因此,在您的代码中存在一些问题,以下是一些指针帮助您解决这些问题:

您当前代码的主要问题是:

  • 重复使用Document实例(您已经用于PdfWriter并已打开)进行PdfCopy;虽然Document可以支持多个监听器,但所有这些监听器都需要在调用open之前注册;该构造的用例是在两种不同格式中并行创建相同的文档;并且

  • 对于您的PdfWriterPdfCopy都使用相同的输出流;结果不是一个有效的PDF,而是来自两个不同PDF的字节范围混合在一起,即绝对不会成为有效的PDF。

正确使用PdfCopy

您可以通过首先在ByteArrayOutputStream中创建一个包含新段落的PDF文件(关闭相关的Document),然后将此PDF文件和其他要添加的页面复制到新的PDF文件中来重新构造代码。

例如:

ByteArrayOutputStream os = new ByteArrayOutputStream();
Document bigDoc = new Document(PageSize.LETTER, 50, 50, 110, 60);
PdfWriter writer = PdfWriter.getInstance(bigDoc, os);
bigDoc.open();
Paragraph par = new Paragraph("one");
bigDoc.add(par);
bigDoc.add(new Paragraph("three"));
bigDoc.close();

ByteArrayOutputStream os2 = new ByteArrayOutputStream();
Document finalDoc = new Document();
PdfCopy copy = new PdfCopy(finalDoc, new FileOutputStream(RESULT2));
finalDoc.open();
PdfReader reader = new PdfReader(os.toByteArray());
for (int i = 0; i < reader.getNumberOfPages();) {
    copy.addPage(copy.getImportedPage(reader, ++i));
}
PdfReader pdfReader = new PdfReader("c:/insertable.pdf");
copy.addPage(copy.getImportedPage(pdfReader, 1));
finalDoc.close();
reader.close();
pdfReader.close();

// result PDF
byte[] result = os2.toByteArray();           

仅使用PdfWriter

您还可以通过直接将页面导入PdfWriter来更改代码,例如:

ByteArrayOutputStream os = new ByteArrayOutputStream();
Document bigDoc = new Document(PageSize.LETTER, 50, 50, 110, 60);
PdfWriter writer = PdfWriter.getInstance(bigDoc, os);
bigDoc.open();
Paragraph par = new Paragraph("one");
bigDoc.add(par);
bigDoc.add(new Paragraph("three"));

PdfReader pdfReader = new PdfReader("c:/insertable.pdf");
PdfImportedPage page = writer.getImportedPage(pdfReader, 1);
bigDoc.newPage();
PdfContentByte canvas = writer.getDirectContent();
canvas.addTemplate(page, 1, 0, 0, 1, 0, 0);

bigDoc.close();
pdfReader.close();

// result PDF
byte[] result = os.toByteArray();           

这种方法似乎更好,因为不需要中间的PDF。不幸的是,这种表象是具有误导性的,这种方法存在一些缺点。
这里不是将整个原始页面复制并添加到文档中,而是仅使用其内容流作为模板的内容,并从实际的新文档页面引用它。这特别意味着:
如果导入的页面与您的新目标文档具有不同的尺寸,则一些部分可能会被裁剪,而新页面的一些部分仍然为空。因此,您经常会发现上面的代码变体,通过缩放和旋转尝试使导入的页面和目标页面匹配。

  • 原始页面内容现在位于从新页面引用的模板中。如果您使用相同的机制将此新页面导入到另一个文档中,则会得到一个引用一个模板的页面,该模板再次仅引用具有原始内容的模板。如果您将此页面导入另一个文档,则会获得另一个间接性级别。等等等等。

    不幸的是,符合PDF查看器只需要有限度地支持这种间接性。如果您继续此过程,则您的页面内容突然可能不可见。如果原始页面已经带有自己的引用模板层次结构,则这种情况可能会很快发生。

  • 由于仅复制内容,因此原始页面中不在内容流中的属性将丢失。这特别关注注释,例如表单字段或某些类型的高亮显示标记,甚至某些类型的自由文本。

  • 顺便提一下,在通用PDF规范术语中,这些“模板”被称为“表单XObject”。 这个答案明确讨论了在合并PDF的上下文中使用PdfCopy和PdfWriter。

    非常感谢你纠正我的代码;我已经复制了您所写的代码并将其放入我的小示例程序中,它更或多或少地做到了我想要的。这个小例子还说明了关于iText的几件事情,其中有一些我还不太懂:例如,我们开始创建输出流和文档,向文档添加内容,然后关闭文档,但输出流仍然可以在以后使用(如上述os)。 - arcy
    我最不理解的事情,并一直试图避免的是,为了从一个文档复制到另一个文档,似乎必须创建第三个文档。我正在开发的程序(为其创建了上述示例)有一个文档,它会添加许多元素:图片、文本、表格等。但显然,为了从另一个文档添加页面,真的不可能只读取该页面并将其添加吗?我必须要创建这个额外的文档,将第一个文档中的所有页面复制到其中,然后再添加我的其他页面吗? - arcy
    IText没有一站式解决方案的架构,而是为不同任务提供了许多类。特别是有从头开始创建PDFDocument加上PdfWriter)、合并PDFDocument加上一个Pdf*Copy*变体)和操作PDFPdfStamper)。这意味着对于混合使用情况,您需要使用中间文档进行多次处理。 - mkl
    你能否在Java聊天室里见我?我想知道你是否有建议的最佳实践来从头开始构建PDF的一部分,然后将另一页合并到其中,然后对该文档进行一些更多的从头开始的操作等。在iText定义为不连续的操作之间如何存储文档? - arcy
    如上所述,您还可以导入到 PdfWriter。然后,您只需要将您的 Document/OutputStream pair 扩展为 Document/OutputStream/PdfWriter triple。但是结果不会像使用 PdfCopy 一样好。 - mkl
    显示剩余7条评论

    0
    这是另一个版本,包含了mkl的修正,希望这些名称能够适用于其他问题。
    import java.io.ByteArrayOutputStream;
    import java.io.FileOutputStream;
    
    import com.lowagie.text.Document;
    import com.lowagie.text.PageSize;
    import com.lowagie.text.Paragraph;
    import com.lowagie.text.pdf.PdfCopy;
    import com.lowagie.text.pdf.PdfReader;
    import com.lowagie.text.pdf.PdfWriter;
    
    public class PdfPlay
    {
          public static void main(String[] args) 
          {
              try
              {
                  ByteArrayOutputStream outputStream1 = new ByteArrayOutputStream();
    
                  Document document1 = new Document(PageSize.LETTER, 50, 50, 110, 60);
                  PdfWriter writer1 = PdfWriter.getInstance(document1, outputStream1);
    
                  document1.open();
                  document1.add(new Paragraph("one"));
                  document1.add(new Paragraph("two"));
                  document1.add(new Paragraph("three"));
                  document1.close();
    
                  byte[] withInsert = addPdfPage(outputStream1, "insertable.pdf");
    
              }
              catch (Exception e)
              {
                  e.printStackTrace();
              }
          }
    
          private static byte[] addPdfPage(ByteArrayOutputStream outputStream1, String insertFilename) 
          {
              try 
              {
                ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
                Document document2 = new Document();
                PdfCopy copy = new PdfCopy(document2, new FileOutputStream("inserted.pdf"));
                document2.open();
                PdfReader outputStream1Reader = new PdfReader(outputStream1.toByteArray());
                for (int i=1; i<=outputStream1Reader.getNumberOfPages(); i++)
                {
                  copy.addPage(copy.getImportedPage(outputStream1Reader, i));
                }
                PdfReader insertReader = new PdfReader(insertFilename);
                copy.addPage(copy.getImportedPage(insertReader, 1));
    
                document2.close();
                outputStream1Reader.close();
                insertReader.close();
    
                byte[] result = outputStream2.toByteArray();
                return result;
              }
              catch (Exception e) 
              {
                  System.out.println("Cannot add PDF from PSC: <" + insertFilename + ">: " + e.getMessage());
                  e.printStackTrace();
                  return null;
              }
          }
    
    }
    

    如果在程序的默认目录中使用文件'insertable.pdf'运行此程序,则该程序将在相同目录中生成文件'inserted.pdf',其中第一页有文本“one”,“two”和“three”,第二页为“insertable.pdf”的第一页。

    mkl的更正有效;在我想要使用它的环境中使用它时,有几个问题:

    我有一个想要使用此功能的Web应用程序,因此无法轻松地访问写入文件的位置。我假设我可以像这里一样在输出文件的位置使用ByteArrayOutputStream。

    为了插入内容,是否绝对需要创建新的输出流?我希望能够告诉某些iText组件“这是一个文件;读取其第一页并将其插入到我已经打开的文档/输出流/编写器中”。内存中的Pdf文档可能会变得非常大;我不想复制所有现有的PDF结构,以便我可以添加另一个结构。如果我最终从多个其他文档中插入页面,则可能需要多次执行此操作,我猜想...


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接