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

PyPDF2 PdfFileMerger loosing PDF module in merged file

我正在用 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 美元......:( 太糟糕了,我找不到开源的、独立于平台的解决方案。

终于自己搞定了,分享出来希望对大家有用

这是一项艰巨的任务。

最后我坚持使用 pdfrw 库 (https://pypi.org/project/pdfrw/ and https://github.com/pmaupin/pdfrw), which gives a good PDF-DOM representation, very close to the PDF-Structure publicly documented in Adobe's official reference (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 节点添加到 output PDF 文件,因此会丢失所有表单字段。

所以我不得不编写自己的代码来合并,尽我所能,各种输入文件的 AcroForm 节点.

我强调"as best ad I can"这句话,因为我最终得到的合并功能远非完美,但至少它对我有用 并可以帮助其他人在需要时从这一点开始建立。

一件重要的事情是重命名表单字段以避免冲突,所以我将它们重命名为 {file_num}_{field_num}_{original_name}.

然后,由于不知道如何合并 CO、DA、DR 和 NeedAppearances 节点,我简单地添加了第一个包含它们的源文件的节点。如果后续文件中存在相同的节点,我将跳过它。

我跳过它除了字体,我合并字体子节点的内容DR[=55] =]节点.

最后请注意,在我第一次尝试时,上述所有操作都是在输出的 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)

希望对您有所帮助。