【问题标题】:PyPDF2 PdfFileMerger loosing PDF module in merged filePyPDF2 PdfFileMerger在合并文件中丢失PDF模块
【发布时间】:2019-07-12 14:14:15
【问题描述】:

我正在将 PDF 文件与 PyPDF2 合并,但是当其中一个文件包含一个填充了数据的 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 pypdf2 pdfa


    【解决方案1】:

    终于自己解决了,在这里分享一下,希望对大家有用。

    这是一项艰巨的任务。

    最后我坚持使用 pdfrw 库(https://pypi.org/project/pdfrw/https://github.com/pmaupin/pdfrw),它提供了很好的 PDF-DOM 表示,非常接近 Adob​​e 官方公开记录的 PDF-Structure参考(https://www.adobe.com/devnet/pdf/pdf_reference.html)。

    使用这个库、PyCharm 的对象检查器和 Adob​​e 的文档,我可以试验输出文件的结构并发现简单的 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)
    

    希望这会有所帮助。

    【讨论】:

    • 干得好,我一直在寻找一种方法来做到这一点
    • 非常感谢你……你帮了我很多!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-07
    • 1970-01-01
    • 2018-07-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多