【问题标题】:PDFbox: sporadic exception while creating PDF DocumentsPDFbox:创建 PDF 文档时偶尔出现异常
【发布时间】:2019-01-29 16:36:28
【问题描述】:

我正在使用 PDFBox 循环生成一堆发票。 这通常是有效的,但不幸的是,我在循环中不时遇到以下异常。为失败的发票重新开始生成一次或两次,迟早会创建所有发票。

java.io.IOException: COSStream has been closed and cannot be read. Perhaps its enclosing PDDocument has been closed?
at org.apache.pdfbox.cos.COSStream.checkClosed(COSStream.java:83)
at org.apache.pdfbox.cos.COSStream.createRawInputStream(COSStream.java:133)
at org.apache.pdfbox.pdfwriter.COSWriter.visitFromStream(COSWriter.java:1202)
at org.apache.pdfbox.cos.COSStream.accept(COSStream.java:400)
at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObject(COSWriter.java:521)
at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObjects(COSWriter.java:459)
at org.apache.pdfbox.pdfwriter.COSWriter.doWriteBody(COSWriter.java:443)
at org.apache.pdfbox.pdfwriter.COSWriter.visitFromDocument(COSWriter.java:1096)
at org.apache.pdfbox.cos.COSDocument.accept(COSDocument.java:417)
at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1369)
at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1256)
at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1279)
at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1250)
at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1238)
at de.xx.xxx.CreateLandscapePDF.createPdf(CreateLandscapePDF.java:37)
at de.xx.xxx.CreateInvoiceAsPDF.createPdf(CreateInvoiceAsPDF.java:172)
...

我已经研究了一些类似的问题,例如PDFbox saying PDDocument closed when its not,我只是认为它与垃圾收集器释放的对象有关,但我没有在我的代码中看到错误。

对于 PDF 本身的创建,我通常使用https://pdfbox.apache.org/1.8/cookbook/documentcreation.html 上对 Apache PDFBox Cookbook 的描述。 我或多或少只是添加更多的内容,一张图片,一些文本块,一个表格等等。

public class CreateLandscapePDF {

private ArrayList<ContentBlock> content;
private PDRectangle pageDIN;
private PDDocument doc;

public CreateLandscapePDF(ArrayList<ContentBlock> content, PDRectangle pageDIN) {
    this.content = content;
    this.pageDIN = pageDIN;
}

public void createPdf(String pdfFileName) throws IOException
{
    doc = new PDDocument();

    PDPage page = new PDPage(pageDIN);
    doc.addPage(page);
    PDPageContentStream contentStream = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.OVERWRITE, false);

    for (ContentBlock contentBlock : content) {
        contentBlock.getContentHelper().writeContentToPDF(contentStream);
        contentStream.moveTo(0, 0);
    }
    contentStream.close();
    doc.save( pdfFileName );
    doc.close();
}

}

在我的创建过程中,我在 CreateInvoiceAsPDF.createPdf 方法中有循环。在这个循环中,我总是创建 CreateLandscapePDF 的新对象。

CreateLandscapePDF pdf = new CreateLandscapePDF(contentList, PDRectangle.A4);
pdf.createPdf(TEMP_FILEPATH_NAME + pdfFileName);

writeContentToPDF 方法仅将文本、图像和线条等几个内容以定义的像素单元放置到页面中。作为一个例子,我把我的 TextContentHelper 中的代码:

    public void writeContentToPDF(PDPageContentStream contentStream) throws IOException {
    float maxTextWidth = 1;
    contentStream.beginText();
    float fontSize = content.getFontSize();
    PDFont font = content.getFont();
    contentStream.setFont(font, fontSize);
    contentStream.setLeading(content.getLineSpace() * fontSize);
    float xPos =0;
    for (Object text : content.getContent()) {
        if (text instanceof String) {
            float textWidth = UnitTranslator.getPixUnitFromTextLength(font, fontSize, (String) text);
            switch (content.getAlignment()) {
            case CENTER:
                xPos = 0.5f*(content.getXEndPosition()+content.getXPosition()-textWidth);
                contentStream.newLineAtOffset(xPos, content.getYPosition());
                break;
            case RIGHT:
                xPos = content.getXEndPosition()-textWidth;
                contentStream.newLineAtOffset(xPos, content.getYPosition());
                break;
            default:
                xPos = content.getXPosition();
                contentStream.newLineAtOffset(xPos, content.getYPosition());
                break;
            }
            contentStream.showText((String) text);
            contentStream.newLine();
            contentStream.newLineAtOffset(-xPos, -content.getYPosition());
            if (textWidth > maxTextWidth) {
                maxTextWidth = textWidth;
            }
        }
    }
    contentStream.endText();
    if (content.isBorder()) {
        createTextBlockBorder(contentStream, maxTextWidth, fontSize);

    }
}

感谢任何解决这个烦人问题的提示!

【问题讨论】:

  • 如果您之前关闭了 COSStream,通常会出现异常,例如因为它是另一个 PDDocument 的一部分。所以我想知道writeContentToPDF 还做了什么。请确保您使用的是最新的 PDFBox 版本 (2.0.13) 和最新的 java.util.即 1.8.202(或 201)或 11.0.2。
  • 蒂尔曼,感谢您的回复!目前我正在使用 pdfbox 2.0.9,我已经尝试了 2.0.13 版本,但没有发现任何区别。使用最新的 Java 我没有机会,因为我的代码在运行 Java 1.6.0 的 Lotus Notes 环境中运行,我无法更新:-( writeContentToPDF 方法没有魔法。它只需要文本、图像等内容, 行并将其放置在特定的像素单元。我将从上面的 TextContentHelper 中添加一个示例。
  • content.getFont() 来自哪里?是否为那个 PDDocument 生成了字体对象?还是对所有 PDF 或一组 PDF 都是全局的? (这不起作用)
  • 调试提示:使用 NOTEPAD++ 之类的编辑器查看目标文件,您会在底部看到不完整的流。发布最后几行,从具有“数字 0 obj”的最后一行开始。这将表明哪种 COSStream 有问题。
  • 还要查看日志输出...如果我的理论是正确的,您会发现一些警告,表明您有未关闭的文档。如果您将new PDDocument 传递给字体创建方法,则可能会发生这种情况,并且该 PDDocument 当然是未引用的,因此它将在稍后自动关闭。

标签: pdfbox


【解决方案1】:

1) 最好通过查看部分保存文件的末尾来分析保存时的COSStream has been closed and cannot be read 异常。用 NOTEPAD++ 打开它,你会在底部看到一个不完整的流。发布最后几行,从具有“数字 0 obj”的最后一行开始。这将表明哪种 COSStream 有问题。

2) 您的文件显示了一个图像 XObject ("/Type /XObject /Subtype /Image")。

3) 进一步的研究表明您使用

创建图像
PDImageXObject pdImage = PDImageXObject.createFromByteArray(new PDDocument(), ...);

你偶尔也会收到警告Warning: You did not close a PDF Document

这是因为您的 new PDDocument() 对象被传递给 createFromByteArray 方法但未被保留,PDFBox 只需要它来获取该 PDDocument(“临时文件”)的内存管理内容。所以稍后(垃圾收集)这个未引用的 PDDocument 最终确定,并关闭所有相关流,其中包括您创建的图像流。

所以解决方法是传递你自己文档的PDDocument,而不是一些临时对象。

4) 请注意,这也适用于字体,因此不要将new PDDocument() 传递给字体创建方法。 (不适用于您,但可能适用于未来的人)。

【讨论】:

    猜你喜欢
    • 2019-01-30
    • 1970-01-01
    • 2013-02-22
    • 1970-01-01
    • 1970-01-01
    • 2014-01-10
    • 2016-10-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多