Как я могу редактировать текст в формате pdf, который закодирован в шестнадцатеричном формате?

#python #pdf #text #pdf-generation #text-processing

#python #PDF #текст #pdf-генерация #обработка текста

Вопрос:

Я пытаюсь найти и заменить определенный текст определенным значением в PDF. Я использую библиотеку python pdfrw, поскольку моя предпочтительная среда — python. Ниже приведен пример содержимого на первой странице документа.

 BTn/F8 40 Tfn1 0 0 -1 569 376 Tmn<0034> Tjn26 0 Td <0028> Tjn22 0 Td <0032> Tjn25 0 Td <0031> Tjn32 0 Td <0034> Tjn26 0 Td <0036> TjnETn0 .8863 1 RG
  

что соответствует слову «ОТЧЕТ» в документе. До сих пор я понимал значение всех специальных тегов и чисел в этом формате и успешно манипулировал позицией и некоторыми символами в ней. Но я не понимаю, в каком формате или кодировке кодируются каждый символ (<0034>, <0028> и т.д.).

Я пробовал перебирать каждую отдельную комбинацию <00xx>, но нашел только допустимое совпадение для букв R, E, P, O, T, которые являются буквами, используемыми в word. Я попробовал то же самое для F11 и F10, которые включены в страницу, и нашел тот же результат, где я сопоставил буквы, которые используются только. Если кто-нибудь может объяснить, как работает эта кодировка и как я могу отредактировать ее так, чтобы я мог вставить любой символ utf-8, это было бы очень полезно.

Спасибо.

примечание-1: ниже приведен объект F8:

{‘/ Subtype’: ‘/ Type0’, ‘/Type’: ‘/Font’, ‘/BaseFont’: ‘/OpenSans-Bold’, ‘/Encoding’: ‘/Identity-H’, ‘/ DescendantFonts’: [{‘/ DW’: ‘0’, ‘/Subtype’: ‘/CIDFontType2’, ‘/CIDSystemInfo’: {‘/ Дополнение’: ‘0’, ‘/ Registry’: ‘(Adobe)’, ‘/ Ordering’: ‘(Identity)’}, ‘/Type’: ‘/Font’, ‘/FontDescriptor’: {‘/ Descent’: ‘-292.96875’, ‘/capHeight’: ‘713.86719’, ‘/StemV’: ‘83.984375’, ‘/Type’: ‘/FontDescriptor’, ‘/FontFile2’: {‘/Length1’: ‘5540’, ‘/Length’: ‘5540’}, ‘/Flags’: ‘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’: {‘/Длина’: ‘413’}}

примечание 2: Здесь также не работает замена текста на (хороший текст) Tj n или (<0032><0032>).

Комментарии:

1. Не могли бы вы предоставить для загрузки этот образец pdf, который вы пытаетесь взломать? если не секрет

Ответ №1:

Итак, как указывалось в предыдущих ответах, встроенный шрифт в документе был всего лишь подмножеством, а кодировки ссылались на символы, которые мне неизвестны. Я решил проблему, сначала создав временный pdf-файл, содержащий все буквы алфавита (который содержит нужную мне информацию о шрифте) и заменив шрифт исходного файла шрифтом моего нового файла. И тогда я могу легко манипулировать текстом так же, как и моим временным файлом, например

 target.pages[0].Resources.Font=font_pdf.pages[0].Resources.Font
target.pages[0].Contents.stream.replace(
    "BTn/F8 40 Tfn1 0 0 -1 569 376 Tmn<0034> Tjn26 0 Td <0028> TjnET", 
    f"BTn/F0 11 Tfn1 0 0 -1 500 500 Tmn(x02Yx02Q) TjnET"
)
  

Спасибо вам всем 🙂

примечание: у меня все еще нет хорошего решения для декодирования шестнадцатеричных чисел с использованием собственного шрифта. Поэтому я решил использовать сопоставление с образцом, поскольку я знаю, какой текст следует ожидать. Лучшие решения были бы очень полезны

Комментарии:

1. Во всех случаях, а не только для текста в шестнадцатеричном формате, вам необходимо учитывать кодировку и карту ToUnicode.

Ответ №2:

В общем, я думаю, что текст pdf может быть сжат / закодирован различными алгоритмами, следовательно pdfrw , не декодирует текст сам по себе. Таким образом, вы не можете знать, какой правильный способ в целом, потому что он отличается для каждого случая. Я пробовал простой pdf отсюда, и он содержит только обычный текст внутри.

Вероятно, вы не выяснили, какое правильное соответствие между символами и шестнадцатеричными кодами связано с тем, что это может быть сжатый поток — это означает, что каждый код зависит от позиции символа во всем потоке плюс от значения всех предыдущих символов. Например. текст может быть zlib сжат.

Кроме того, текст pdf представляет собой последовательность команд для позиционирования / форматирования / вывода текста, поэтому в целом вы должны иметь возможность декодировать / кодировать все эти команды, чтобы иметь возможность обрабатывать действительно любой текст. Ваш формат может содержать таблицу символов, в которой все используемые символы отображаются в шестнадцатеричное значение. Чтобы определить правильное отображение, все символы должны присутствовать в тексте примера.

В вашем случае вы, вероятно, можете использовать следующую таблицу, для преобразования я использую тот факт, что буква R имеет шестнадцатеричное значение 0x34 :

Попробуйте онлайн!

 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 : 'amp;' 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: '' 
  

код для преобразования вашего шестнадцатеричного кода в символ прост:

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

Если у вас есть более причудливое сопоставление между символами и шестнадцатеричным значением, вам просто нужно создать текст со всеми возможными символами, затем преобразовать его с помощью вашего конвертера, посмотреть, какой шестнадцатеричный код находится внутри для каждой буквы.

Также я только что попытался выяснить, как кодируется текст внутри PDF, какие команды используются, и похоже, что строка с Tj командой в конце содержит сам текст. Поэтому я написал текстовый модификатор pdf в приведенном ниже коде, он принимает имя файла или URL в качестве первого аргумента, а имя выходного файла — как второе, или просто запустите его, чтобы использовать пример по умолчанию, необходимые замены перечислены в начале скрипта как changes переменная.

Но модификатор next не декодирует ваш шестнадцатеричный формат. Это просто удобно для замены любого текста, закодированного в обычном формате.

Попробуйте онлайн!

 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()
  

Комментарии:

1. спасибо за ответ. как вы думаете, моя проблема каким-то образом связана с функцией встраивания шрифтов в файл PDF? Я подозреваю, что в моем PDF-файле может использоваться встроенный шрифт, который содержит только буквы, которые используются в документе.

2. @BekuCh я обновил свой ответ, добавил только что написанный мной скрипт для изменения текста. Но он изменяет только обычный текст. Для шестнадцатеричного вам нужно выяснить отображение. И да, вы правы, где-то в заголовке pdf может быть сопоставление символа с шестнадцатеричным, может присутствовать только небольшая часть всех символов Юникода, поэтому некоторые символы пропускаются в шестнадцатеричных значениях.

3. спасибо, я попробую ваше решение и, возможно, найду это сопоставление. Я буду держать вас в курсе моих успехов

4. @BekuCh Как вы должны знать, файлы PDF также шифруются, а не только иногда сжимаются, поэтому там может быть какой-то причудливый код шифрования.

5. @BekuCh Я думаю, что может быть так F8 , что используемый шрифт почти наверняка имеет таблицу сопоставления внутри /ToUnicode поля. Попробуйте сбросить эту таблицу, возможно, в ней перечислены все используемые символы и их коды unicode в шестнадцатеричном формате.

Ответ №3:

'/Encoding': '/Identity-H' и '/CIDToGIDMap': '/Identity' означает, что код символа соответствует идентификатору глифа. Так <0034> отображается номер глифа 0x34 из выбранного шрифта.

Если шрифт был подмножеством, у вас есть доступ только к символам, которые были включены в подмножество.

'/Length': '5540' означает, что размер шрифта составляет 5540 байт, что явно означает, что он является подмножеством.