You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 15:40:09