我正在使用iText v5.4.2。我正在尝试从PDF文件解析图像。对于某些PDF文件中的某些图像,我得到了NullPointerException。带有一个“故障”图像的PDF文件可以在这里下载:https://dl.dropboxusercontent.com/u/3585277/LZW_Error.pdf
这是一个简单的演示:
public class LZWDecodeDemo {
public static void main(String[] args) throws Exception {
LZWDecodeDemo demo = new LZWDecodeDemo();
demo.parseImages();
}
private void parseImages() throws Exception {
String pathToPdf = "C:\\temp\\LZW_Error.pdf";
PdfReader reader = new PdfReader(pathToPdf);
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
ImageRenderListener imageRenderListener = new ImageRenderListener();
parser.processContent(1, imageRenderListener);
}
private class ImageRenderListener implements RenderListener {
public ImageRenderListener() {
//
}
public void beginTextBlock() {
// nothing
}
public void endTextBlock() {
// nothing
}
public void renderImage(ImageRenderInfo imageRenderInfo) {
try {
PdfImageObject image = imageRenderInfo.getImage();
System.out.println("Rendered image :" + image);
} catch (IOException e) {
e.printStackTrace();
}
}
public void renderText(TextRenderInfo arg0) {
// nothing
}
}
}
最佳答案
当LZW输出位长度增加时,恰好发生图像数据结尾时,可以在示例文件中观察到该问题:
在图像/ Im3的情况下,最后一个携带图像数据的代码导致了第511个LZW表条目的创建,这意味着以下代码应使用10位进行编码。不幸的是,后面的EOD(数据结束)标记仅使用9位进行编码。
iText正确解码了下一个代码(即使用10位,流中的下一个位为0位),因此看到514而不是257(这是EOD标记值),并尝试使用表条目号514 NPE发生;毕竟第511条只是刚刚添加的...
可能是因为编码器(知道它位于图像数据的末尾)根本没有在最后一个代码之后创建表项,所以可能会发生这种情况。因此,它没有看到达到表长度触发器,只是忘记使用10位。
对此,规范非常清楚。 ISO 32000-1中的第7.4.4.2节“ LZW编码的详细信息”:
使用LZW压缩方法编码的数据应由9到12位长的代码序列组成。每个代码应代表输入数据的单个字符(0–255),清除表标记(256),EOD标记(257)或代表在输入中先前遇到的多字符序列的表条目(258或更高)。
最初,代码长度应为9位,并且LZW表应仅包含258个固定代码的条目。随着编码的进行,条目应追加到表中,使新代码与越来越长的输入字符序列相关联。编码器和解码器应维护该表的相同副本。
每当编码器和解码器都独立(但同步)意识到当前代码长度不再足以表示表中的条目数时,它们应将每个代码的位数增加1。创建表条目511之后,应为10位长,同样对于11(1023)和12(2047)位也应如此。代码不得超过12位;因此,条目4095是LZW表的最后一个条目。
编码器应执行以下步骤序列以生成每个输出代码:
a)累积一个或多个输入字符的序列,该序列与表中已经存在的序列匹配。为了获得最大压缩,编码器会寻找最长的序列。
b)发出与该序列相对应的代码。
c)为第一个未使用的代码创建一个新的表条目。它的值是在步骤(a)中找到的顺序,后跟下一个输入字符。
因此,即使在发出最后一个输入字符的代码之后,也必须创建一个表项。并且,如果该表条目是数字511,则随后的第一个输出代码(即EOD标记)必须为10位长。
话虽这么说,iText的LZWDecoder方法decode
可以通过null
测试得到加强,至少在else
的if (code < tableIndex)
分支中,并且行为更优雅,要么抛出更具描述性的异常,要么默默地忽略如果没有太多输入位的问题。