Python zlib解码PDF的TJ/Tj算子得乱码,pdftotext为何正常?
问题原因与解决方案
为什么TJ/Tj算子解码乱码,但pdftotext能正常提取?
- 字体编码映射缺失:PDF中TJ/Tj输出的是字体内部编码的字节,并非直接的Unicode文本。之前处理的PDF用了标准编码(如WinAnsiEncoding),编码与Unicode的映射简单,直接转码就能得到正确内容;但当前PDF可能使用了自定义编码、CID字体,或是字体带有私有ToUnicode映射表,你只解码了流数据,没做编码到Unicode的映射,因此显示乱码。pdftotext会自动读取PDF中的字体映射规则完成转码,所以能提取正确文本。
- 文本流解析不完整:TJ算子的参数是数组(如
[(Hello) -5 (World)]TJ),其中包含字符字符串和间距调整数字。如果只提取所有字节而忽略数字、未拆分字符串,会把间距数值当成字符编码处理,导致乱码。另外,部分PDF的字体资源本身是压缩的,你只解码了页面内容流,没解码字体的CMap数据,无法完成映射。 - 转义字符处理错误:PDF字符串中的八进制(如
\141)、十六进制(如\x61)转义字符,需要先转成对应字节,再结合字体映射转Unicode。如果直接把转义序列当成普通字符处理,会出现乱码。
如何用Python/zlib得到正确结果?
不建议手动用zlib硬解析PDF文本流(逻辑复杂,容易遗漏细节),优先使用成熟的PDF处理库。如果必须手动实现,步骤如下:
1. 正确解码FlateDecode流
PDF的FlateDecode流通常是原始DEFLATE数据(无zlib头),解码时需要指定wbits=-15:
import zlib # 假设compressed_data是从PDF中提取的FlateDecode压缩字节流 decoded_stream = zlib.decompress(compressed_data, wbits=-15)
2. 解析文本操作符(TJ/Tj)
需要按照PDF语法解析解码后的流,区分字符串、数字和操作符:
- 对于Tj:提取其后的字符串对象(注意处理括号嵌套和转义)
- 对于TJ:遍历数组,只提取其中的字符串元素,忽略数字(间距调整值)
3. 完成字体编码到Unicode的映射
从PDF的字体字典中获取映射规则:
- 如果字体包含
/ToUnicode条目:解析该CMap文件,将字符编码字节转成Unicode - 如果没有
/ToUnicode:使用字体/Encoding指定的标准编码(如/WinAnsiEncoding对应cp1252),或/Differences数组定义的自定义映射
简化示例(依赖pdfminer辅助映射)
import zlib from pdfminer.pdffont import PDFUnicodeMapper # 假设已获取页面的字体对象font和压缩的文本流compressed_data decoded_stream = zlib.decompress(compressed_data, wbits=-15) # 假设已从解码流中提取到字符编码字节char_bytes if '/ToUnicode' in font: # 解析ToUnicode CMap cmap_stream = font['/ToUnicode'].get_data() cmap = PDFUnicodeMapper(cmap_stream) text = cmap.to_unicode(char_bytes) else: # 处理标准编码,如WinAnsiEncoding encoding = font.get('/Encoding', '/WinAnsiEncoding') if encoding == '/WinAnsiEncoding': text = char_bytes.decode('cp1252') # 其他标准编码需对应处理,如MacRomanEncoding对应mac_roman
更高效的方案:使用现成库
直接用pdfplumber或PyPDF2,这些库已封装所有解码、映射逻辑:
import pdfplumber with pdfplumber.open("your_file.pdf") as pdf: page = pdf.pages[0] text = page.extract_text() print(text)
内容的提问来源于stack exchange,提问作者Pawan Sharma
相关产品推荐
相关产品推荐

