合并PDF,同时保留自定义页码(也称为页面标签)和书签

2024-10-03 11:20:38 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试自动合并几个PDF文件,并且有两个要求:a)现有书签和b)需要保留页面标签(自定义页面编号)

默认情况下,与PyPDF2和pdftk合并时保留书签,但与pdfrw合并时不保留书签。 页面标签始终不会保留在PyPDF2、pdftk或pdfrw中

我猜,在搜索了很多之后,没有直接的方法来做我想做的事情。如果我错了,那么我希望有人能指出这个简单的解决方案。但是,如果没有简单的解决方案,任何关于如何在python中实现这一点的提示都将不胜感激

一些示例代码:

1)使用PyPDF2

from PyPDF2 import PdfFileWriter, PdfFileMerger, PdfFileReader 
tmp1 = PdfFileReader('file1.pdf', 'rb')
tmp2 = PdfFileReader('file2.pdf', 'rb')
#extracting pagelabels is easy
pl1 = tmp1.trailer['/Root']['/PageLabels']
pl2 = tmp2.trailer['/Root']['/PageLabels']
#but PdfFileWriter or PdfFileMerger does not support writing from what I understand

所以我不知道如何从这里开始

2)有pdfrw(更有希望)

from pdfrw import PdfReader, PdfWriter
writer = PdfWriter()
#read 1st file
tmp1 = PdfReader('file1')
#add the pages
writer.addpages(tmp1.pages)
#copy bookmarks to writer
writer.trailer.Root.Outlines = tmp1.Root.Outlines
#copy pagelabels to writer
writer.trailer.Root.PageLabels = tmp1.Root.PageLabels
#read second file
tmp2 = PdfReader('file2')
#append pages
writer.addpages(tmp2.pages)
# so far so good

第二个文件中书签的页码需要在添加它们之前进行偏移,但在阅读大纲时,我几乎总是得到(IndirectObject,XXX)而不是页码。目前还不清楚如何使用pdfrw获取每个标签和书签的页码。所以,我又被卡住了

zp


Tags: fromroot页面标签pageswriter书签pdfrw
2条回答

正如我在评论中提到的,我发布了一个通用的解决方案来合并在PyPDF2中工作的几个PDF。除了将pls初始化为ArrayObject()之外,不知道在PyPDF2中实现此功能有什么不同

from PyPDF2 import PdfFileWriter, PdfFileMerger, PdfFileReader
import PyPDF2.pdf as PDF

# pls holds all the pagelabels as we iterate through multiple pdfs
pls = PDF.ArrayObject()
# used to offset bookmarks 
pageCount = 0
cpdf = PdfFileMerger()
# pdffiles is a list of all files to be merged
for i in range(len(pdffiles)):
    tmppdf = PdfFileReader(pdffiles[i], 'rb')
    cpdf.append(tmppdf)
    # copy all the pagelabels which I assume is present in all files
    # you could use 'try' in case no pagelabels are present
    plstmp = tmppdf.trailer['/Root']['/PageLabels']['/Nums']
    # sometimes keys are indirect objects
    # so, iterate through each pagelabel and...
    for j in range(len(plstmp)):
        # ... get the actual values 
        plstmp[j] = plstmp[j].getObject()
        # offset pagenumbers by current count of pages
        if isinstance(plstmp[j], int): 
            plstmp[j] = PDF.NumberObject(plstmp[j] + pageCount) 
    # once all the pagelabels are processed I append to pls
    pls += plstmp
    #increment pageCount
    pageCount += tmppdf.getNumPages()

# rest follows KevinM's answer
pagenums = PDF.DictionaryObject()
pagenums.update({PDF.NameObject('/Nums') : pls})
pagelabels = PDF.DictionaryObject()
pagelabels.update({PDF.NameObject('/PageLabels') : pagenums})
cpdf.output._root_object.update(pagelabels)
cpdf.write("filename.pdf")

您需要遍历现有的PageLabels,并将它们添加到合并的输出中,注意根据已添加的页面数向页面索引项添加偏移量

这个解决方案还需要PyPDF4,因为PyPDF2会产生一个奇怪的错误(见下图)

from PyPDF4 import PdfFileWriter, PdfFileMerger, PdfFileReader 

# To manipulate the PDF dictionary
import PyPDF4.pdf as PDF

import logging

def add_nums(num_entry, page_offset, nums_array):
    for num in num_entry['/Nums']:
        if isinstance(num, (int)):
            logging.debug("Found page number %s, offset %s: ", num, page_offset)

            # Add the physical page information
            nums_array.append(PDF.NumberObject(num+page_offset))
        else:
            # {'/S': '/r'}, or {'/S': '/D', '/St': 489}
            keys = num.keys()
            logging.debug("Found page label, keys: %s", keys)
            number_type = PDF.DictionaryObject()
            # Always copy the /S entry
            s_entry = num['/S']
            number_type.update({PDF.NameObject("/S"): PDF.NameObject(s_entry)})
            logging.debug("Adding /S entry: %s", s_entry)

            if '/St' in keys:
                # If there is an /St entry, fetch it
                pdf_label_offset = num['/St']
                # and add the new offset to it
                logging.debug("Found /St %s", pdf_label_offset)
                number_type.update({PDF.NameObject("/St"): PDF.NumberObject(pdf_label_offset)})

            # Add the label information
            nums_array.append(number_type)

    return nums_array

def write_merged(pdf_readers):
    # Output
    merger = PdfFileMerger()

    # For PageLabels information
    page_labels = []
    page_offset = 0
    nums_array = PDF.ArrayObject()

    # Iterate through all the inputs
    for pdf_reader in pdf_readers:
        try:
            # Merge the content
            merger.append(pdf_reader)

            # Handle the PageLabels
            # Fetch page information
            old_page_labels = pdf_reader.trailer['/Root']['/PageLabels']
            page_count = pdf_reader.getNumPages()

            # Add PageLabel information
            add_nums(old_page_labels, page_offset, nums_array)
            page_offset = page_offset + page_count

        except Exception as err:
            print("ERROR: %s" % err)

    # Add PageLabels
    page_numbers = PDF.DictionaryObject()
    page_numbers.update({PDF.NameObject("/Nums"): nums_array})

    page_labels = PDF.DictionaryObject()
    page_labels.update({PDF.NameObject("/PageLabels"): page_numbers})

    root_obj = merger.output._root_object
    root_obj.update(page_labels)

    # Write output
    merger.write('merged.pdf')


pdf_readers = []
tmp1 = PdfFileReader('file1.pdf', 'rb')
tmp2 = PdfFileReader('file2.pdf', 'rb')
pdf_readers.append(tmp1)
pdf_readers.append(tmp2)

write_merged(pdf_readers)

注意:PyPDF2会产生这种奇怪的错误:

  ...
  ...
  File "/usr/lib/python3/dist-packages/PyPDF2/pdf.py", line 552, in _sweepIndirectReferences
    data[key] = value
  File "/usr/lib/python3/dist-packages/PyPDF2/generic.py", line 507, in __setitem__
    raise ValueError("key must be PdfObject")
ValueError: key must be PdfObject

相关问题 更多 >