Портирование на Python3: PyPDF2 mergePage() выдает TypeError

Я использую Python 3.4.2 и PyPDF2 1.24 (также использую reportlab 3.1.44 на случай, если это поможет) в Windows 7.

Недавно я обновился с Python 2.7 до 3.4 и сейчас занимаюсь портированием своего кода. Этот код используется для создания пустой страницы PDF со встроенными в нее ссылками (с помощью reportlab) и ее объединения (с помощью PyPDF2) с существующей страницей PDF. У меня была проблема с reportlab в том, что при сохранении холста использовался StringIO, который нужно было изменить на BytesIO, но после этого я столкнулся с этой ошибкой:

Traceback (most recent call last):
File "C:\cms_software\pdf_replica\builder.py", line 401, in merge_pdf_files
    input_page.mergePage(link_page)
File "C:\Python34\lib\site-packages\PyPDF2\pdf.py", line 2013, in mergePage
    self.mergePage(page2)
File "C:\Python34\lib\site-packages\PyPDF2\pdf.py", line 2059, in mergePage
    page2Content = PageObject._pushPopGS(page2Content, self.pdf)
File "C:\Python34\lib\site-packages\PyPDF2\pdf.py", line 1973, in _pushPopGS
    stream = ContentStream(contents, pdf)
File "C:\Python34\lib\site-packages\PyPDF2\pdf.py", line 2446, in __init
    stream = BytesIO(b_(stream.getData()))
File "C:\Python34\lib\site-packages\PyPDF2\generic.py", line 826, in getData
    decoded._data = filters.decodeStreamData(self)
File "C:\Python34\lib\site-packages\PyPDF2\filters.py", line 326, in decodeStreamData
    data = ASCII85Decode.decode(data)
File "C:\Python34\lib\site-packages\PyPDF2\filters.py", line 264, in decode
    data = [y for y in data if not (y in ' \n\r\t')]
File "C:\Python34\lib\site-packages\PyPDF2\filters.py", line 264, in 
    data = [y for y in data if not (y in ' \n\r\t')]
TypeError: 'in <string>' requires string as left operand, not int

Вот строка и строка выше, где упоминается трассировка:

link_page = self.make_pdf_link_page(pdf, size, margin, scale_factor, debug_article_links)
if link_page != None:
input_page.mergePage(link_page)

Вот соответствующие части этой функции make_pdf_link_page:

packet = io.BytesIO()
can = canvas.Canvas(packet, pagesize=(size['width'], size['height']))
....# left out code here is just reportlab specifics for size and url stuff
can.linkURL(url, r1, thickness=1, color=colors.green)
can.rect(x1, y1, width, height, stroke=1, fill=0)
# create a new PDF with Reportlab that has the url link embedded
can.save()
packet.seek(0)
try:
    new_pdf = PdfFileReader(packet)
except Exception as e:
    logger.exception('e')
    return None
return new_pdf.getPage(0)

Я предполагаю, что это проблема с использованием BytesIO, но я не могу создать страницу с reportlab с помощью StringIO. Это важная функция, которая отлично работала с Python 2.7, поэтому я был бы признателен за любые отзывы об этом. Спасибо!

ОБНОВЛЕНИЕ: я также пытался перейти от использования BytesIO к простой записи во временный файл, а затем к слиянию. К сожалению, я получил ту же ошибку. Вот версия временного файла:

import tempfile
temp_dir = tempfile.gettempdir()
temp_path = os.path.join(temp_dir, "tmp.pdf")
can = canvas.Canvas(temp_path, pagesize=(size['width'], size['height']))
....
can.showPage()
can.save()
try:
    new_pdf = PdfFileReader(temp_path)
except Exception as e:
    logger.exception('e')
    return None
return new_pdf.getPage(0)

ОБНОВЛЕНИЕ: я нашел интересную информацию по этому поводу. Кажется, если я закомментирую вызовы can.rect и can.linkURL, они сольются. Таким образом, рисование чего-либо на странице, а затем попытка объединить его с моим существующим PDF-файлом вызывает ошибку.


person H0L0GH05t    schedule 15.01.2015    source источник


Ответы (1)


Покопавшись в коде библиотеки PyPDF2, я смог найти свой собственный ответ. Для пользователей Python 3 старые библиотеки могут быть сложными. Даже если они говорят, что поддерживают Python 3, они не обязательно все тестируют. В данном случае проблема была с классом ASCII85Decode в filter.py в PyPDF2. Для Python 3 этот класс должен возвращать байты. Я позаимствовал код для такого же типа функции из pdfminer3k, который является портом для python 3 pdfminer. Если вы замените класс ASCII85Decode() на этот код, он будет работать:

import struct
class ASCII85Decode(object):
    def decode(data, decodeParms=None):
        if isinstance(data, str):
            data = data.encode('ascii')
        n = b = 0
        out = bytearray()
        for c in data:
            if ord('!') <= c and c <= ord('u'):
                n += 1
                b = b*85+(c-33)
                if n == 5:
                    out += struct.pack(b'>L',b)
                    n = b = 0
            elif c == ord('z'):
                assert n == 0
                out += b'\0\0\0\0'
            elif c == ord('~'):
                if n:
                    for _ in range(5-n):
                        b = b*85+84
                    out += struct.pack(b'>L',b)[:n-1]
                break
        return bytes(out)
person H0L0GH05t    schedule 16.01.2015