使用 PDFBox 合并大型 PDF 文件时出错 - 缺少文件结尾标记“%%EOF”
Error Merging Large PDF Files with PDFBox - Missing end of file marker '%%EOF'
我使用 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
class上有一个setEOFLookupRange()
方法。我想知道查找范围是否太小,这就是它找不到 '%%EOF'
标记的原因。问题是……我根本没有在我的代码中使用 COSParser
对象;我只使用 PDFMergerUtility
class。 PDFMergerUtility
似乎在后台使用 COSParser
。
所以我的问题是
- 我关于
EOFLookupRange
的假设是否正确?
- 如果是这样,我如何才能在我的代码中设置只有
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;
}
我看了一下代码,发现COSParser
中默认的EOFLookupRange
是2048
bytes。
我认为你的假设是正确的。
查看扩展 COSParser
并且是 PDFMergerUtility
内部使用的解析器的 PDFParser
我看到可以设置另一个 EOFLookupRange
by using a system property. The system property name is org.apache.pdfbox.pdfparser.nonSequentialPDFParser.eofLookupRange
并且它应该是一个有效的整数。
Here 是一个演示如何设置系统属性的问题。
我还没有测试过上面的内容,但我希望它会起作用:)
指向 PDFBox 代码的链接使用 2.0.11 版本您正在使用的一个。
正如我所怀疑的,这是 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 的所有帮助!
我使用 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
class上有一个setEOFLookupRange()
方法。我想知道查找范围是否太小,这就是它找不到 '%%EOF'
标记的原因。问题是……我根本没有在我的代码中使用 COSParser
对象;我只使用 PDFMergerUtility
class。 PDFMergerUtility
似乎在后台使用 COSParser
。
所以我的问题是
- 我关于
EOFLookupRange
的假设是否正确? - 如果是这样,我如何才能在我的代码中设置只有
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;
}
我看了一下代码,发现COSParser
中默认的EOFLookupRange
是2048
bytes。
我认为你的假设是正确的。
查看扩展 COSParser
并且是 PDFMergerUtility
内部使用的解析器的 PDFParser
我看到可以设置另一个 EOFLookupRange
by using a system property. The system property name is org.apache.pdfbox.pdfparser.nonSequentialPDFParser.eofLookupRange
并且它应该是一个有效的整数。
Here 是一个演示如何设置系统属性的问题。
我还没有测试过上面的内容,但我希望它会起作用:)
指向 PDFBox 代码的链接使用 2.0.11 版本您正在使用的一个。
正如我所怀疑的,这是 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 的所有帮助!