终于自己解决了,在这里分享一下,希望对大家有用。
这是一项艰巨的任务。
最后我坚持使用 pdfrw 库(https://pypi.org/project/pdfrw/ 和 https://github.com/pmaupin/pdfrw),它提供了很好的 PDF-DOM 表示,非常接近 Adobe 官方公开记录的 PDF-Structure参考(https://www.adobe.com/devnet/pdf/pdf_reference.html)。
使用这个库、PyCharm 的对象检查器和 Adobe 的文档,我可以试验输出文件的结构并发现简单的 1 行合并:
from pdfrw import PdfReader, PdfWriter
output = PdfWriter()
input = PdfReader(pdf_filename)
output.addpages(input.pages)
不会将 AcroForm 节点添加到 输出 PDF 文件,因此会丢失所有表单字段。
所以我必须编写自己的代码来尽我所能合并各种输入文件的 AcroForm 节点。
我强调“尽我所能”这句话,因为我最终得到的合并功能远非完美,但至少它对我有用并且可以如果需要,帮助其他人从这一点上建立起来。
要做的一件重要事情是重命名表单字段以避免冲突,因此我将它们重命名为 {file_num}_{field_num}_{original_name}。
然后,由于不知道如何合并 CO、DA、DR 和 NeedAppearances 节点,我只是添加了第一个具有它们的源文件的节点。如果后续文件中存在相同的节点,我会跳过它。
我跳过它除了字体,我合并了DR节点的Font子节点的内容。
最后一点,在我第一次尝试时,上述所有操作都是在输出的 trailer 属性上完成的。然后我发现每次我从一个新的输入文件添加页面时,pdfrw 似乎会删除预告片中已经存在的任何 AcroForm。
我不知道原因,但我必须构建一个 ouptut_acroform 变量并将其分配给输出文件的行,然后再写出最终的 pdf。
最后,这是我的代码。
如果不是pythonic,请见谅,我只是希望它能澄清以上几点。
from pdfrw import PdfReader, PdfWriter, PdfName
def merge_pdf_files_pdfrw(pdf_files, output_filename):
output = PdfWriter()
num = 0
output_acroform = None
for pdf in pdf_files:
input = PdfReader(pdf,verbose=False)
output.addpages(input.pages)
if PdfName('AcroForm') in input[PdfName('Root')].keys(): # Not all PDFs have an AcroForm node
source_acroform = input[PdfName('Root')][PdfName('AcroForm')]
if PdfName('Fields') in source_acroform:
output_formfields = source_acroform[PdfName('Fields')]
else:
output_formfields = []
num2 = 0
for form_field in output_formfields:
key = PdfName('T')
old_name = form_field[key].replace('(','').replace(')','') # Field names are in the "(name)" format
form_field[key] = 'FILE_{n}_FIELD_{m}_{on}'.format(n=num, m=num2, on=old_name)
num2 += 1
if output_acroform == None:
# copy the first AcroForm node
output_acroform = source_acroform
else:
for key in source_acroform.keys():
# Add new AcroForms keys if output_acroform already existing
if key not in output_acroform:
output_acroform[key] = source_acroform[key]
# Add missing font entries in /DR node of source file
if (PdfName('DR') in source_acroform.keys()) and (PdfName('Font') in source_acroform[PdfName('DR')].keys()):
if PdfName('Font') not in output_acroform[PdfName('DR')].keys():
# if output_acroform is missing entirely the /Font node under an existing /DR, simply add it
output_acroform[PdfName('DR')][PdfName('Font')] = source_acroform[PdfName('DR')][PdfName('Font')]
else:
# else add new fonts only
for font_key in source_acroform[PdfName('DR')][PdfName('Font')].keys():
if font_key not in output_acroform[PdfName('DR')][PdfName('Font')]:
output_acroform[PdfName('DR')][PdfName('Font')][font_key] = source_acroform[PdfName('DR')][PdfName('Font')][font_key]
if PdfName('Fields') not in output_acroform:
output_acroform[PdfName('Fields')] = output_formfields
else:
# Add new fields
output_acroform[PdfName('Fields')] += output_formfields
num +=1
output.trailer[PdfName('Root')][PdfName('AcroForm')] = output_acroform
output.write(output_filename)
希望这会有所帮助。