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

iText 2.1.7 PdfCopy.addPage(page) can't find page reference?

我正在维护一个使用 iText 2.1.7 创建 PDF 的网络应用程序。我想获取现有 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() 为空。

我怎么用错了?如何从现有文档中获取页面并将其放入当前文档中?请注意,我不在一个可以方便地将文件写入临时存储或其他任何地方的地方。

我不再积极使用旧的 iText 版本,但从那时起有些事情没有改变。因此,这里有您的代码中的一些问题和有助于解决这些问题的指针:

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

  • Document 实例(您已经用于 PdfWriter 并且已经打开)重新用于 PdfCopy;虽然 Document 可以支持多个侦听器,但它们都需要在调用 open 之前注册;此构造的用例是以两种不同的格式并行创建相同的文档;还有你

  • 为您的 PdfWriterPdfCopy 使用相同的输出流;结果不是一个有效的 PDF,而是来自两个不同 PDF 的字节范围疯狂地混合在一起,即肯定不是有效 PDF 的东西。

正确使用PdfCopy

您可以重组您的代码,方法是首先创建一个新的 PDF,其中包含 ByteArrayOutputStream 中的新段落(关闭涉及的 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 规范术语中称为 Form XObjects。)

This answer 明确处理 PdfCopyPdfWriter 在合并 PDF 的上下文中的使用。

这是另一个版本,结合了 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 的修正有效;在我想使用它的环境中使用它,有几个问题:

我有一个程序,我想在其中使用此功能,它是一个网络应用程序,因此没有准备好访问写入文件的位置。我假设我可以像此处那样使用 ByteArrayOutputStream 代替输出文件。

是否绝对有必要创建一个新的输出流来插入内容?我希望有一种方法可以告诉某些 iText 组件“这是一个文件;阅读它的第一页并将其插入我已经打开的 document/outputStream/writer 中。内存中的 Pdf 文档可能会变得非常大;我宁愿不必复制所有现有的 PDF 结构,这样我就可以向它们添加另一个。如果我最终从多个其他文档中插入页面,我可能不得不多次这样做,我猜...