如何编辑以十六进制格式编码的pdf文本?

问题描述

我正在尝试查找某些文本并将其替换为PDF中的特定值。我使用的是Python库pdfrw,因为我的首选环境是python。以下是文档首页中的示例内容

BT\n/F8 40 Tf\n1 0 0 -1 569 376 Tm\n<0034> Tj\n26 0 Td <0028> Tj\n22 0 Td <0032> Tj\n25 0 Td <0031> Tj\n32 0 Td <0034> Tj\n26 0 Td <0036> Tj\nET\n0 .8863 1 RG

对应于文档中的单词“ REPORT”。到目前为止,我已经理解了这种格式的所有特殊标记和数字的含义,并成功地操纵了位置和其中的某些字符。但是我不明白每个字符的编码格式(,等)。

我尝试强行强制的每个单个组合,但只发现单词R,E,P,O,T的有效匹配项。我对页面中包含的F11和F10进行了相同的尝试,并找到了与仅使用的字母相匹配的结果。如果有人能解释这种编码的工作方式以及如何对其进行编辑,以便能够插入任何utf-8字符,那将非常有帮助。

谢谢。

注释1: 以下是F8对象:

{'/ Subtype':'/ Type0', '/ Type':'/字体', '/ BaseFont':'/ OpenSans-Bold', '/编码':'/ Identity-H', '/ DescendantFonts':[{'/ DW':'0', '/ Subtype':'/ CIDFontType2', '/ CIDSystemInfo':{'/ Supplement':'0', '/ Registry':'(Adobe)', '/ Ordering':'(身份)'}, '/ Type':'/字体', '/ FontDescriptor':{'/ Descent':'-292.96875', '/ CapHeight':'713.86719', '/ stemV':'83 .984375', '/ Type':'/ FontDescriptor', '/ FontFile2':{'/ Length1':'5540','/ Length':'5540'}, '/标记':'4', '/ FontName':'/ OpenSans-Bold', '/ ItalicAngle':'0', '/FontBBox':['-619.14063'、'-292.96875'、'1318.84766'、'1068.84766'], '/ Ascent':'1068.84766'}, '/ BaseFont':'/ OpenSans-Bold', '/ W':['0', ['600.09766'], '40', ['560.05859'], '49', ['795.89844','627.92969','0','660.15625','0','579.10156']], '/ CIDToGIDMap':'/ Identity'}], '/ ToUnicode':{'/ Length':'413'}}

note2: 同样,以(漂亮的文本)Tj \ n或( )方式替换文本在这里也不起作用。

解决方法

通常,我认为pdf文本可以通过不同的算法进行压缩/编码,因此pdfrw本身不会解码文本。因此,您通常不知道正确的方法是什么,因为每种情况都不同。我尝试了简单的pdf from here,并且其中仅包含纯文本。

您可能没有弄清楚字符和十六进制代码之间的正确对应关系是由于它可能是压缩流,这意味着每个代码都取决于字符在整个流中的位置以及值所有以前的字符。例如。文本可以zlib压缩。

PDF文本也是用于定位/格式化/输出文本的命令序列,因此,通常,您必须能够对所有这些命令进行解码/编码才能真正处理任何文本。您的格式可能包含符号表,其中所有使用的符号都映射到十六进制值。为了弄清楚正确的映射,示例文字中应包含所有符号。

对于您来说,您可能使用下一张表进行转换,我使用了字母R具有十六进制值0x34的事实:

Try it online!

import sys
for i,n in enumerate(range(32,128)):
    sys.stdout.write(f"{hex(n - ord('R') + 0x34).ljust(4)}: '{chr(n)}' ")
    if (i + 1) % 8 == 0:
        sys.stdout.write('\n')

输出:

0x2 : ' ' 0x3 : '!' 0x4 : '"' 0x5 : '#' 0x6 : '$' 0x7 : '%' 0x8 : '&' 0x9 : ''' 
0xa : '(' 0xb : ')' 0xc : '*' 0xd : '+' 0xe : ',' 0xf : '-' 0x10: '.' 0x11: '/' 
0x12: '0' 0x13: '1' 0x14: '2' 0x15: '3' 0x16: '4' 0x17: '5' 0x18: '6' 0x19: '7' 
0x1a: '8' 0x1b: '9' 0x1c: ':' 0x1d: ';' 0x1e: '<' 0x1f: '=' 0x20: '>' 0x21: '?' 
0x22: '@' 0x23: 'A' 0x24: 'B' 0x25: 'C' 0x26: 'D' 0x27: 'E' 0x28: 'F' 0x29: 'G' 
0x2a: 'H' 0x2b: 'I' 0x2c: 'J' 0x2d: 'K' 0x2e: 'L' 0x2f: 'M' 0x30: 'N' 0x31: 'O' 
0x32: 'P' 0x33: 'Q' 0x34: 'R' 0x35: 'S' 0x36: 'T' 0x37: 'U' 0x38: 'V' 0x39: 'W' 
0x3a: 'X' 0x3b: 'Y' 0x3c: 'Z' 0x3d: '[' 0x3e: '\' 0x3f: ']' 0x40: '^' 0x41: '_' 
0x42: '`' 0x43: 'a' 0x44: 'b' 0x45: 'c' 0x46: 'd' 0x47: 'e' 0x48: 'f' 0x49: 'g' 
0x4a: 'h' 0x4b: 'i' 0x4c: 'j' 0x4d: 'k' 0x4e: 'l' 0x4f: 'm' 0x50: 'n' 0x51: 'o' 
0x52: 'p' 0x53: 'q' 0x54: 'r' 0x55: 's' 0x56: 't' 0x57: 'u' 0x58: 'v' 0x59: 'w' 
0x5a: 'x' 0x5b: 'y' 0x5c: 'z' 0x5d: '{' 0x5e: '|' 0x5f: '}' 0x60: '~' 0x61: '' 

从十六进制转换为char的代码很简单:

hex_val = '0030'
print(chr(int(hex_val,16) - 0x34 + ord('R')))

如果在chars和hex值之间有更多花哨的映射,则只需创建一个包含所有可能chars的文本,然后使用转换器将其转换,看看每个字母里面的hex是什么。

我还试图弄清楚PDF中的文本是如何编码的,使用了哪些命令,并且看起来像Tj命令末尾的字符串包含文本本身。因此,我在下面的代码中编写了pdf文本修饰符,它接受文件名或URL作为第一个arg,然后输出文件名作为第二个arg,或者只运行它以使用默认示例,所需的替换在脚本开头以changes的形式列出。变量。

但是next修饰符不会解码您的十六进制格式。替换任何以纯文本编码的文本都很方便。

Try it online!

import sys,os,io
# Needs: python -m pip install pdfrw
from pdfrw import PdfReader,PdfWriter

changes = {'And': 'Or','text': 'string'}

def ReplaceText(text,reps = {}):
    res,in_block = '',False
    for line in text.splitlines():
        line = line.strip()
        nline = line
        if line == 'BT':
            in_block = True
        elif line == 'ET':
            in_block = False
        elif in_block:
            cmd = line.rpartition(' ')[2]
            if cmd.lower() == 'tj':
                for k,v in reps.items():
                    nline = nline.replace(k,v)
        res += nline + '\n'
    return res

ifn = sys.argv[1] if len(sys.argv) > 1 else 'http://www.africau.edu/images/default/sample.pdf'
ofn = (ifn[:ifn.rfind('.')] + '.processed.pdf') if len(sys.argv) <= 2 else sys.argv[2]

if ifn.lower().startswith('http'):
    # Needs: python -m pip install requests
    import requests
    ofn = (ifn[ifn.rfind('/') + 1:] + '.processed.pdf') if len(sys.argv) <= 2 else sys.argv[2]
    ifn = io.BytesIO(requests.get(ifn).content)
    
r = PdfReader(ifn)
for page in r.pages:
    page.Contents.stream = ReplaceText(page.Contents.stream,changes)

PdfWriter(ofn,trailer = r).write()
,

'/Encoding': '/Identity-H''/CIDToGIDMap': '/Identity'表示字符代码对应于字形id。因此<0034>显示所选字体的字形数字0x34。

如果字体已被子集化,则您只能访问子集中包含的字形。

'/Length': '5540'表示字体大小为5540字节,这显然意味着它是子集。

,

因此,正如先前的答案所指出的那样,文档中嵌入的Font只是一个子集,而编码所引用的字符对我来说是未知的。我首先创建了一个临时pdf文件来解决这个问题,该文件包含字母表中的每个字母(包含我需要的字体信息),然后用新文件替换原始文件的资源字体。然后,我可以像临时文件一样轻松地操作文本

target.pages[0].Resources.Font=font_pdf.pages[0].Resources.Font
target.pages[0].Contents.stream.replace(
    "BT\n/F8 40 Tf\n1 0 0 -1 569 376 Tm\n<0034> Tj\n26 0 Td <0028> Tj\nET",f"BT\n/F0 11 Tf\n1 0 0 -1 500 500 Tm\n(\x02Y\x02Q) Tj\nET"
)

谢谢大家:)

注意:对于使用自己的字体解码十六进制,我仍然没有好的解决方案。因此,我决定使用模式匹配,因为我知道应该期待什么文本。更好的解决方案将非常有帮助