PyPDF2 PdfFileMerger 在合并文件中丢失 PDF 模块

问题描述 投票:0回答:3

我正在使用 PyPDF2 合并 PDF 文件,但是,当其中一个文件包含填充有数据的 PDF 模块(典型的应用程序填充的 PDF)时,在合并的文件中,该模块为空,不显示任何数据。

这是我用来合并PDF的两种方法:

def merge_pdf_files(pdf_files, i):
    pdf_merger = PdfFileMerger(strict=False)
    for pdf in pdf_files:
        pdf_merger.append(pdf)
    output_filename = '{out_root}{prog}.{cf}.pdf'.format(out_root=out_root_path, prog=i+1, cf=cf)
    pdf_merger.write(output_filename)

def merge_pdf_files2(pdf_files, i):
    output = PdfFileWriter()
    for pdf in pdf_files:
        input = PdfFileReader(pdf)
        for page in input.pages:
            output.addPage(page)
    output_filename = '{out_root}{prog}.{cf}.pdf'.format(out_root=out_root_path, prog=i+1, cf=cf)
    with open(output_filename,'wb') as output_stream:
        output.write(output_stream)

我希望最终的合并 PDF 显示 PDF 模块中填写的所有数据。 或者,另一种方法是,有人可以向我指出另一个没有遭受此(表面上)错误的Python库。 谢谢

更新 我也尝试了 PyMuPDF,得到了相同的结果。

def merge_pdf_files4(pdf_files, i):
    output = fitz.open()
    for pdf in pdf_files:
        input = fitz.open(pdf)
        output.insertPDF(input)
    output_filename = '{out_root}{prog}.{cf}.pdf'.format(out_root=out_root_path, prog=i+1, cf=cf)
    output.save(output_filename)

也尝试过 PyPDF4。与 PyPDF2 相同的结果

还尝试使用通过命令行从脚本启动的外部工具:

subprocess.call(cmd, shell=True)

我一开始尝试过pdftk,但也失败了。 唯一有效的是 PDFill,商业版本,在这项任务上花费了 19 美元......:( 太糟糕了,我找不到开源的、独立于平台的解决方案。

python pdf pypdf pdfa
3个回答
11
投票

最后我自己解决了,我在这里分享,希望对其他人有用。

这是一项艰巨的任务。

最后我坚持使用 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 节点添加到 output 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)

希望这有帮助。


1
投票
@A_E,无法告诉您这节省了多少时间。谢谢你!来自

https://github.com/pmaupin/pdfrw/issues/192

对于管理员来说,我认识到这是一个老问题,但它在 Google 搜索中对此信息的排名很高,并在该库的 Github 存储库中引用。

我有一个非常相似的要求,其中一个表单有空间容纳 3 件事,如果更多,我会构建一个单独的时间表并将其附加为新页面,但我在表单中得到了看起来像空白字段值的内容在 Kofax PDF、Acrobat Reader 或 Evince (Linux) 中查看时生成的 pdf。这些字段将在 Gmail 的 pdf 查看器中显示其值,或者在单独的浏览器窗口中查看(Edge 和 Chrome 有效)。当单击并更改字体或属性中的对齐方式等时,它们也会显示。导出数据并重新导入也有效,但这在我的应用程序中是不可行的。

我添加这个不是作为答案,而是为了提供根据我当前的设置进行一些更改后最终得到的代码,其中我已经有了一些“内存中”读者,而不是传递文件(原始的表格,以及额外项目的附加时间表)。

在这里回复是为了表示感谢,并对来到这里的其他人表示感谢,这种方法似乎确实有效(我无法想象通过挖掘调试器和文档来找出答案的过程)。

我传入一个 PdfReaders 列表,第一个已设置

NeedAppearances

,如下所示;否则,这些字段将继续显示为空白,直到单击为止。随后在另一种方法中使用
pdf_writer
保存到适当的位置。我用来将表单与非表单 pdf 结合起来的所有其他方法都会导致相同的行为。

self.template_pdf.Root.AcroForm.update(pdfrw.PdfDict(NeedAppearances=pdfrw.PdfObject('true')))


其中 self.template_pdf 是主表单的 PdfReader 实例。

import pdfrw def merge_pdf_files_pdfrw(pdf_readers, pdf_writer): # output = pdfrw.PdfWriter() output_acroform = None for reader_idx, pdf_reader in enumerate(pdf_readers): # input = PdfReader(pdf,verbose=False) pdf_writer.addpages(pdf_reader.pages) if pdfrw.PdfName.AcroForm in pdf_reader[pdfrw.PdfName.Root].keys(): # Not all PDFs have an AcroForm node source_acroform = pdf_reader[pdfrw.PdfName.Root][pdfrw.PdfName.AcroForm] if pdfrw.PdfName.Fields in source_acroform: output_formfields = source_acroform[pdfrw.PdfName.Fields] else: output_formfields = [] for ff_idx, form_field in enumerate(output_formfields): key = pdfrw.PdfName.T old_name = form_field[key].replace('(', '').replace(')', '') # Field names are in the "(name)" format form_field[key] = f'FILE_{reader_idx}_FIELD_{ff_idx}_{old_name}' if output_acroform is 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 (pdfrw.PdfName.DR in source_acroform.keys()) and ( pdfrw.PdfName.Font in source_acroform[pdfrw.PdfName.DR].keys()): if pdfrw.PdfName.Font not in output_acroform[pdfrw.PdfName.DR].keys(): # if output_acroform is missing entirely the /Font node under an existing /DR, simply add it output_acroform[pdfrw.PdfName.DR][pdfrw.PdfName.Font] = \ source_acroform[pdfrw.PdfName.DR][pdfrw.PdfName.Font] else: # else add new fonts only for font_key in source_acroform[pdfrw.PdfName.DR][pdfrw.PdfName.Font].keys(): if font_key not in output_acroform[pdfrw.PdfName.DR][pdfrw.PdfName.Font]: output_acroform[pdfrw.PdfName.DR][pdfrw.PdfName.Font][font_key] = \ source_acroform[pdfrw.PdfName.DR][pdfrw.PdfName.Font][font_key] if pdfrw.PdfName.Fields not in output_acroform: output_acroform[pdfrw.PdfName.Fields] = output_formfields else: # Add new fields output_acroform[pdfrw.PdfName.Fields] += output_formfields pdf_writer.trailer[pdfrw.PdfName.Root][pdfrw.PdfName.AcroForm] = output_acroform
    

0
投票
这篇文章已经一年多了,但我想说这确实对我有帮助......我花了几天时间尝试做到这一点,甚至在 chatgpt 和 claude 的帮助下。

© www.soinside.com 2019 - 2024. All rights reserved.