如何解析Length为后置间接对象的未知长度二进制PDF流
解析PDF中Length为后置间接对象的流:踩坑与解决方法
经常跟PDF解析打交道的朋友,肯定碰到过这个离谱的设计:PDF规范里明明说流的长度由字典里的Length指定,但偏偏允许Length是一个在流之后才定义的间接对象,这给阅读器的解析工作添了好大的麻烦。
先给大家明确下PDF流的基础规则,来自PDF规范:
流字典后的
stream关键字后必须跟一个行结束标记,该标记可以是回车加换行(\r\n),也可以仅为换行(\n),但不能仅为回车(\r)。构成流的字节序列位于stream关键字后的行结束标记与endstream关键字之间;流字典指定确切的字节数。
但这里有个关键问题:流内容可能是二进制数据,所以哪怕流中间出现了endstream字符串,也不代表流真的结束了。比如下面这个实际例子:
%PDF-1.4 %307쏢 5 0 obj << /Length 6 0 R /Filter /FlateDecode >> stream x234+T03203T0^@A(235234˥^_d256220^314^U310^E^@[364^F!endstream endobj 6 0 obj 30 endobj
你看,这里的Length是6号间接对象,而这个对象是在5号流对象的endobj之后才定义的——也就是说,你要解析5号流的话,得先跳过流内容找到6号对象,拿到Length值(这里是30),再回头去读流的实际内容。
我一直觉得这是PDF设计上的一个明显缺陷:虽然这样方便了PDF写入器按顺序输出内容(不用提前计算流长度),但阅读器的解析逻辑复杂度直接飙升。毕竟PDF文件是读取的频率远高于写入,实在搞不懂为啥要做这种取舍。
那遇到这种情况,正确的解析步骤应该是怎样的呢?给大家梳理下:
- 第一步:预扫描获取Length值:先从
stream关键字后的位置开始,跳过流内容(这里可以先按临时的方式定位到endstream和endobj,注意二进制内容里的endstream是假的),找到定义Length的间接对象(比如例子里的6号对象),读取并记录这个Length值。 - 第二步:读取真实流数据:回到
stream关键字后的行结束标记位置,严格按照刚拿到的Length值读取对应字节数的内容——这部分就是真正的流数据,哪怕中间出现endstream也不用管,只认字节数。 - 第三步:定位到真正的endstream:读完流数据后,再向后扫描找到真正的
endstream关键字,继续后续的PDF对象解析。
核心原则就是:流的结束永远以Length指定的字节数为准,而不是endstream关键字;当Length是后置间接对象时,必须先做一次预解析拿到Length值,再回头处理流内容。
内容的提问来源于stack exchange,提问作者U. Windl
相关产品推荐
相关产品推荐

