使用PyMuPDF替换PDF文本遇编码错误,求解决方案及逆向编码方法
PDF文本替换中的十六进制编码转换错误及逆向编码方案
问题背景
想要替换PDF文件中的文本,提取到PDF内容流后,尝试用一段Python代码转换其中的十六进制文本,却触发字节范围错误。
提取到的PDF内容流
BT 0 0 0 0 scn /C0_0 1 Tf 13.72 0 0 14 14.0156 76.1611 Tm <0D1804E10632>Tj /C0_1 1 Tf -0.01 Tc 0.01 Tw 7.84 0 0 8 25.5322 57.7412 Tm <001100120011000F0011001100110011000F0011001100110011>Tj 0.287 0.176 0.138 0 scn /C0_0 1 Tf -1.469 0.001 Td <002E001B>Tj 0 -1.456 TD <0026001B>Tj 0 -1.506 TD <0038001B>Tj 0 0 0 0 scn /C0_1 1 Tf 1.469 -0.001 Td [<004500460054004A0048004F00430042>41 <00540046000F0044>17 <0050000F004C0053>]TJ -1.469 -2.183 Td <048304DD0628000108BF056909260001081905FC04B300010A7F0A57063200010011001100110001001100110011>Tj 1.214 3.689 Td [<004500460054004A0048004F00430042>41 <00540046004C>19 <00500053>30.1 <0046004200210048004E0042>13.1 <004A004D000F0044>17 <0050004E>]TJ 0.287 0.176 0.138 0 scn /C0_0 1 Tf 8.82 0 0 9 55.3369 76.1611 Tm <06630A570A490581>Tj 0 0 0 0 scn /C0_1 1 Tf 6.86 0 0 7 165.2456 107.1807 Tm <05BE04B30561000108DA09140471000106630A570A4B07EC0A49091D>Tj ET
运行的Python代码
def chunks(lst, n): """Yield successive n-sized chunks from lst.""" for i in range(0, len(lst), n): yield lst[i:i + n] s = '048304DD0628000108BF056909260001081905FC04B300010A7F0A57063200010011001100110001001100110011' ns = [29 + int(c, 16) for c in chunks(s, 4)] print(bytes(ns))
触发的错误信息
Traceback (most recent call last): File "convert.py", line 10, in <module> print(bytes(ns)) ValueError: bytes must be in range(0, 256)
错误原因
这段代码把4位十六进制字符串转成整数后加29,部分结果远超255(比如04DD转成整数是1245,加29后为1274),而bytes()要求每个元素必须是0-255之间的整数,因此报错。更关键的是,不同PDF的字体编码规则不同,不能直接套用固定偏移值(比如加29),必须使用对应字体的ToUnicode映射表来转换编码。
解决方法与逆向编码步骤
1. 获取字体的ToUnicode映射表
每个PDF字体都有对应的ToUnicode CMap,用于将字体内部编码映射为Unicode字符。用PyMuPDF可以直接获取:
import fitz doc = fitz.open("你的PDF文件路径.pdf") page = doc[0] # 目标页码 # 遍历页面字体,获取指定字体的ToUnicode映射 for font in page.get_fonts(): font_name = font[3] if font_name in ["/C0_0", "/C0_1"]: # 目标字体名称 cmap = doc.get_font_cmap(font[0]) # font[0]是字体的xref编号 print(f"字体 {font_name} 的ToUnicode映射:") print(cmap)
输出的cmap是字典结构,键为字体内部的十六进制编码(如'0011'),值为对应的Unicode字符。
2. 基于映射表转换十六进制文本
拿到映射表后,按编码规则拆分十六进制字符串并转换:
def hex_to_text(hex_str, cmap): text = [] i = 0 hex_len = len(hex_str) while i < hex_len: # 先尝试双字节编码(4位十六进制) if i + 4 <= hex_len and hex_str[i:i+4] in cmap: text.append(cmap[hex_str[i:i+4]]) i += 4 # 再尝试单字节编码(2位十六进制) elif i + 2 <= hex_len and hex_str[i:i+2] in cmap: text.append(cmap[hex_str[i:i+2]]) i += 2 # 未知编码保留原格式 else: text.append(f"[{hex_str[i:i+2]}]") i += 2 return "".join(text) # 示例转换目标十六进制字符串 target_hex = '048304DD0628000108BF056909260001081905FC04B300010A7F0A57063200010011001100110001001100110011' # 假设cmap_c0_1是/C0_1字体的ToUnicode映射 converted_text = hex_to_text(target_hex, cmap_c0_1) print(converted_text)
3. 更高效的PDF文本替换方式
无需手动解析内容流,PyMuPDF提供了内置的文本替换功能:
import fitz doc = fitz.open("你的PDF文件路径.pdf") page = doc[0] # 查找并替换文本 for match in page.search_for("需要替换的原文本"): page.add_redact_annot(match, "替换后的文本") page.apply_redactions() # 保存修改后的PDF doc.save("修改后的PDF文件名.pdf")
如果原文本无法直接搜索到,再结合ToUnicode映射解析内容流定位替换位置。
总结
- 字节范围错误是因为固定偏移值导致整数超出0-255范围,核心问题是未使用对应字体的ToUnicode映射。
- 逆向编码必须依赖目标字体的ToUnicode CMap,不能套用通用偏移规则。
- 优先使用PyMuPDF内置的文本替换方法,避免手动解析内容流的复杂度。
内容的提问来源于stack exchange,提问作者KurtKim
相关产品推荐
相关产品推荐

