java - 使用 PDFBox 合并大型 PDF 文件时出错 - 缺少文件结尾标记 '%%EOF'

标签 java pdfbox

我已经使用InputStreams使用PDFBox成功实现了pdf合并解决方案。但是,当我尝试合并一个非常大的文档时,我收到以下错误:

Caused by: java.io.IOException: Missing root object specification in trailer.
at org.apache.pdfbox.pdfparser.COSParser.parseTrailerValuesDynamically(COSParser.java:2832) ~[pdfbox-2.0.11.jar:2.0.11]
at org.apache.pdfbox.pdfparser.PDFParser.initialParse(PDFParser.java:173) ~[pdfbox-2.0.11.jar:2.0.11]
at org.apache.pdfbox.pdfparser.PDFParser.parse(PDFParser.java:220) ~[pdfbox-2.0.11.jar:2.0.11]
at org.apache.pdfbox.pdmodel.PDDocument.load(PDDocument.java:1144) ~[pdfbox-2.0.11.jar:2.0.11]
at org.apache.pdfbox.pdmodel.PDDocument.load(PDDocument.java:1060) ~[pdfbox-2.0.11.jar:2.0.11]
at org.apache.pdfbox.multipdf.PDFMergerUtility.legacyMergeDocuments(PDFMergerUtility.java:379) ~[pdfbox-2.0.11.jar:2.0.11]
at org.apache.pdfbox.multipdf.PDFMergerUtility.mergeDocuments(PDFMergerUtility.java:280) ~[pdfbox-2.0.11.jar:2.0.11]

(我认为)更重要的是在错误之前发生的这些语句:

FINE (pdfparser.COSParser) [] - Missing end of file marker '%%EOF'
FINE (pdfparser.COSParser) [] - Set missing offset 388 for object 2 0 R

在我看来,它无法在非常大的文件中找到 '%%EOF' 标记。现在我知道它确实在那里,因为我可以查看源代码(不幸的是我无法提供文件本身)。

在网上进行一些搜索,我发现 COSParser 类中有一个 setEOFLookupRange() 方法。我想知道查找范围是否太小,这就是为什么它找不到 '%%EOF' 标记。问题是......我在代码中根本没有使用 COSParser 对象;我只使用 PDFMergerUtility 类。 PDFMergerUtility 似乎在底层使用 COSParser

所以我的问题是

  1. 我对 EOFLookupRange 的假设正确吗?
  2. 如果是这样,如何设置代码中仅包含 PDFMergerUtility 而不是 COSParser 对象的范围?

非常感谢您抽出时间!

更新了以下代码

 private boolean getCoolDocuments(final String slateId, final String filePathAndName)
            throws IOException {

        boolean status = false;
        InputStream pdfStream = null;
        HttpURLConnection connection = null;
        final PDFMergerUtility merger = new PDFMergerUtility();
        final ByteArrayOutputStream mergedPdfOutputStream = new ByteArrayOutputStream();

        try {

            final List<SlateDocument> parsedSlateDocuments = this.getSpecificDocumentsFromSlate(slateId);

            if (!parsedSlateDocuments.isEmpty()) {

                // iterate through each document, adding each pdf stream to the merger utility
                int numberOfDocuments = 0;
                for (final SlateDocument slateDocument : parsedSlateDocuments) {

                    final String url = this.getBaseURL() + "/slate/" + slateId + "/documents/"
                            + slateDocument.getDocumentId();

                     /* code for RequestResponseUtil.initializeRequest(...) below */
                    connection = RequestResponseUtil.initializeRequest(url, "GET", this.getAuthenticationHeader(),
                            true, MediaType.APPLICATION_PDF_VALUE);

                    if (RequestResponseUtil.isSuccessful(connection.getResponseCode())) {
                        pdfStream = connection.getInputStream();

                    }
                    else {
                        /* do various things */
                    }

                    merger.addSource(pdfStream);
                    numberOfDocuments++;
                }

                merger.setDestinationStream(mergedPdfOutputStream);

                // merge the all the pdf streams together
               merger.mergeDocuments(MemoryUsageSetting.setupTempFileOnly());

               status = true;
            }
            else {
                LOG.severe("An error occurred while parsing the slated documents; no documents remain after parsing!");
            }
        }
        finally {
            RequestResponseUtil.close(pdfStream);

            this.disconnect(connection);
        }

        return status;
    }

   public static HttpURLConnection initializeRequest(final String url, final String method,
            final String httpAuthHeader, final boolean multiPartFormData, final String reponseType) {

    HttpURLConnection conn = null;

    try {
        conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod(method);
        conn.setRequestProperty("X-Slater-Authentication", httpAuthHeader);
        conn.setRequestProperty("Accept", reponseType);
        if (multiPartFormData) {
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=BOUNDARY");
            conn.setDoOutput(true);
        }
        else {
            conn.setRequestProperty("Content-Type", "application/xml");
        }
    }
    catch (final MalformedURLException e) {
        throw new CustomException(e);
    }
    catch (final IOException e) {
        throw new CustomException(e);
    }
    return conn;

}

最佳答案

正如我所怀疑的,这是 InputStream 的问题。这并不完全是我的想法,但基本上我做了一个(非常错误的)假设:我可以这样做:

           pdfStream = connection.getInputStream();
                /* ... */
           merger.addSource(pdfStream);

当然,这是行不通的,因为整个InputStream可能会被读取,也可能不会被读取。需要显式地读入它,直到到达最后一个 -1 字节。我很确定,在较小的文件上,这工作正常,并且实际上在整个流中读取,但在较大的文件上,它根本没有完成......因此找不到 %%EOF 标记。

解决方案是使用中介ByteArrayOutputStream,然后通过ByteArrayInputStream将其转换回InputStream

所以如果你替换这行代码:

pdfStream = connection.getInputStream();

上面这段代码:

                final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

                int c;
                while ((c = connection.getInputStream().read()) != -1) {
                    byteArrayOutputStream.write(c);
                }

                pdfStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

您最终将得到一个可行的示例。

我最终可能会将其更改为使用 Pipes or Circular Buffers instead 的实现,但至少现在是有效的。

虽然这不一定是 Java 101 错误,但它更像是 Java 102 错误,而且仍然是可耻的。 :/希望它对其他人有帮助。

感谢@Tilman Hausherr 和@Master_ex 提供的所有帮助!

关于java - 使用 PDFBox 合并大型 PDF 文件时出错 - 缺少文件结尾标记 '%%EOF',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51564781/

相关文章:

java - Spring Integration 服务激活器引用 Hibernate JPA 存储库方法

java - 使用 Java 提取 HTML 标签

java - 在Java中递归搜索树

java - 在Java中分割以 "."结尾的段落并在点后换行

java - 使用 Apache PDFBox 阅读 pdf

java - jar 文件中的媒体文件夹

java - 构建使用 JAVA 11 的 Docker 镜像

java - 修改Itext中现有PDF的字体

java - 如何使用pdf框中的书签选择pdf页面?

java - 用另一个 PDFBOX 2.0.3 替换 PDImageXObject