使用 Python 生成平面 PDF

Posted

技术标签:

【中文标题】使用 Python 生成平面 PDF【英文标题】:Generate flattened PDF with Python 【发布时间】:2015-01-17 08:05:27 【问题描述】:

当我从任何源 PDF 打印 PDF 时,文件大小会减小并删除表单中显示的文本框。简而言之,它会使文件变平。 这是我想要实现的行为。

以下代码使用另一个 PDF 作为源(我想要展平的那个)创建 PDF,它也写入文本框表单。

我可以获得没有文本框的 PDF,将其展平吗?就像我将 PDF 打印为 PDF 时 Adob​​e 所做的一样。

我的其他代码看起来像这样减去一些东西:

import os
import StringIO
from pyPdf import PdfFileWriter, PdfFileReader
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter

directory = os.path.join(os.getcwd(), "source")  # dir we are interested in
fif = [f for f in os.listdir(directory) if f[-3:] == 'pdf'] # get the PDFs
for i in fif:
    packet = StringIO.StringIO()
    can = canvas.Canvas(packet, pagesize=letter)
    can.rotate(-90)
    can.save()

    packet.seek(0)
    new_pdf = PdfFileReader(packet)
    fname = os.path.join('source', i)
    existing_pdf = PdfFileReader(file(fname, "rb"))
    output = PdfFileWriter()
    nump = existing_pdf.getNumPages()
    page = existing_pdf.getPage(0)
    for l in range(nump):
        output.addPage(existing_pdf.getPage(l))
    page.mergePage(new_pdf.getPage(0))
    outputStream = file("out-"+i, "wb")
    output.write(outputStream)
    outputStream.close()
    print fName + " written as", i

总结:我有一个 pdf,我在其中添加一个文本框,隐藏信息并添加新信息,然后我从该 pdf 打印一个 pdf。文本框不再可编辑或移动。我想自动化这个过程,但我尝试的一切仍然允许该文本框是可编辑的。

【问题讨论】:

也在寻找解决方案。我有一个水印 Python 脚本,但是当尝试选择或突出显示文档中的文本时,水印会妨碍我。如果我可以生成一个扁平的水印 PDF,然后将它与源 PDF 合并,那就可以解决它。 文件名是否遵循某些特定约定?如果是这样,语义是什么?用空格分隔文件名,然后用逗号分隔文件名的目的是什么? (否则,脚本会失败,但我不确定是否与您面临的问题相关) +MakeCents 我无法重现该问题。我没有盒子。您可以粘贴一张带有您获得的结果和预期结果的图像吗? @gpoo 我认为这些盒子存在于原件中,但我也不知道它是什么类型的盒子,我有一个 pdf,第一页上有一个盒子,但我无法删除它打印(也许 Acrobat Pro 会这样做) @gpoo 我当时的目标是:我有一个 pdf,我在其中添加一个文本框,覆盖信息并添加新信息,然后我从该 pdf 打印一个 pdf .文本框不再可编辑或移动。我想自动化该过程,但我尝试的一切仍然允许该文本框是可编辑的。我希望这能清除它。我正在使用 Acrobat 9.5 【参考方案1】:

如果安装操作系统包是一个选项,那么您可以使用pdftk 及其python 包装器pypdftk,如下所示:

import pypdftk
pypdftk.fill_form('filled.pdf', out_file='flattened.pdf', flatten=True)

您还需要安装 pdftk 软件包,在 Ubuntu 上可以这样完成:

sudo apt-get install pdftk

pypdftk 库可以从 PyPI 下载:

pip install pypdftk

更新:pdftk 在 version 18.04 中被从 Ubuntu 中短暂删除,但它似乎又回来了 since 20.04。

【讨论】:

有没有办法在没有 pdftk 的情况下做到这一点?我问是因为我正在尝试编写 pdftk 克隆,因为 pdftk 在 centos7 上不起作用。任何帮助将不胜感激。 这在 ubuntu 18.04 上不起作用,因为 pdftk 不再在 repo 中 @FabrizioMiano 我看到人们在这里讨论解决方法:askubuntu.com/a/1029451/70751 qpdf 也可能是一个替代方案。 pdftk 用作 pdf 打印机,因此 cups 可能会工作。 看来 pdftk 又回到了 Ubuntu 20.04 及更高版本。它已移植到 Java,可以安装为 pdftkpdftk-java。最后签到 21.04。【参考方案2】:

根据 Adob​​e Docs,您可以将可编辑表单字段的位位置更改为 1,以使字段只读。我在这里提供了一个完整的解决方案,但它使用了 Django:

https://***.com/a/55301804/8382028

Adobe Docs(第 552 页):

https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf

使用 PyPDF2 填充字段,然后循环注释以更改位位置:

from io import BytesIO
import PyPDF2
from PyPDF2.generic import BooleanObject, NameObject, IndirectObject, NumberObject

# open the pdf
input_stream = open("YourPDF.pdf", "rb")
pdf_reader = PyPDF2.PdfFileReader(input_stream, strict=False)
if "/AcroForm" in pdf_reader.trailer["/Root"]:
    pdf_reader.trailer["/Root"]["/AcroForm"].update(
        NameObject("/NeedAppearances"): BooleanObject(True))

pdf_writer = PyPDF2.PdfFileWriter()
set_need_appearances_writer(pdf_writer)
if "/AcroForm" in pdf_writer._root_object:
    # Acro form is form field, set needs appearances to fix printing issues
    pdf_writer._root_object["/AcroForm"].update(
        NameObject("/NeedAppearances"): BooleanObject(True))

data_dict = dict() # this is a dict of your form values

pdf_writer.addPage(pdf_reader.getPage(0))
page = pdf_writer.getPage(0)
# update form fields
pdf_writer.updatePageFormFieldValues(page, data_dict)
for j in range(0, len(page['/Annots'])):
    writer_annot = page['/Annots'][j].getObject()
    for field in data_dict:
        if writer_annot.get('/T') == field:
            writer_annot.update(
                NameObject("/Ff"): NumberObject(1)    # make ReadOnly
            )
output_stream = BytesIO()
pdf_writer.write(output_stream)

# output_stream is your flattened PDF


def set_need_appearances_writer(writer):
    # basically used to ensured there are not 
    # overlapping form fields, which makes printing hard
    try:
        catalog = writer._root_object
        # get the AcroForm tree and add "/NeedAppearances attribute
        if "/AcroForm" not in catalog:
            writer._root_object.update(
                NameObject("/AcroForm"): IndirectObject(len(writer._objects), 0, writer))

        need_appearances = NameObject("/NeedAppearances")
        writer._root_object["/AcroForm"][need_appearances] = BooleanObject(True)


    except Exception as e:
        print('set_need_appearances_writer() catch : ', repr(e))

    return writer  

【讨论】:

【参考方案3】:

将 pdf 转换为图像而不是将这些图像放入 pdf 的一种简单但更多的方法。

你需要 pdf2image 和 PIL

像这样

from pdf2image import convert_from_path 
from PIL import Image

images = convert_from_path('temp.pdf') 
im1 = images[0]
images.pop(0)

pdf1_filename = "flattened.pdf"

im1.save(pdf1_filename, "PDF" ,resolution=100.0, save_all=True, append_images=images)

编辑:

我创建了一个名为 fillpdf 的库

pip install fillpdf

from fillpdf import fillpdfs
fillpdfs.flatten_pdf('input.pdf', 'newflat.pdf')

【讨论】:

我认为 OP 仍在寻求保留页面内容的矢量属性,而此建议会将文档转换为(低分辨率)图像? @AllanLRH 更正页面的矢量属性将丢失,这将导致图像分辨率较低,但图像仍然完全可读,并且可以在许多不同的情况下使用,其中可读性是唯一要求.【参考方案4】:

一个同样适用于 Windows 的解决方案,可以转换许多 pdf 页面,并将 chackbox 值展平。出于某种原因,@ViaTech 代码在我的电脑中不起作用(Windows7 python 3.8)

遵循@ViaTech 指示并广泛使用来自this post 的@hchillon 代码

from PyPDF2 import PdfFileReader, PdfFileWriter
from PyPDF2.generic import BooleanObject, NameObject, IndirectObject, TextStringObject, NumberObject


def set_need_appearances_writer(writer):

    try:
        catalog = writer._root_object
        # get the AcroForm tree and add "/NeedAppearances attribute
        if "/AcroForm" not in catalog:
            writer._root_object.update(
                NameObject("/AcroForm"): IndirectObject(len(writer._objects), 0, writer))

        need_appearances = NameObject("/NeedAppearances")
        writer._root_object["/AcroForm"][need_appearances] = BooleanObject(True)
        return writer

    except Exception as e:
        print('set_need_appearances_writer() catch : ', repr(e))
        return writer



class PdfFileFiller(object):

    def __init__(self, infile):

        self.pdf = PdfFileReader(open(infile, "rb"), strict=False)
        if "/AcroForm" in self.pdf.trailer["/Root"]:
            self.pdf.trailer["/Root"]["/AcroForm"].update(
            NameObject("/NeedAppearances"): BooleanObject(True))

    # newvals and newchecks have keys have to be filled. '' is not accepted
    def update_form_values(self, outfile, newvals=None, newchecks=None):

        self.pdf2 = MyPdfFileWriter()


        trailer = self.pdf.trailer['/Root'].get('/AcroForm', None)
        if trailer:
            self.pdf2._root_object.update(
                NameObject('/AcroForm'): trailer)

        set_need_appearances_writer(self.pdf2)
        if "/AcroForm" in self.pdf2._root_object:
            self.pdf2._root_object["/AcroForm"].update(
            NameObject("/NeedAppearances"): BooleanObject(True))

        for i in range(self.pdf.getNumPages()):
            self.pdf2.addPage(self.pdf.getPage(i))

            self.pdf2.updatePageFormFieldValues(self.pdf2.getPage(i), newvals)
            for j in range(0, len(self.pdf.getPage(i)['/Annots'])):
                writer_annot = self.pdf.getPage(i)['/Annots'][j].getObject()
                for field in newvals:
                    writer_annot.update(NameObject("/Ff"): NumberObject(1))

            self.pdf2.updatePageFormCheckboxValues(self.pdf2.getPage(i), newchecks)

        with open(outfile, 'wb') as out:
            self.pdf2.write(out)


class MyPdfFileWriter(PdfFileWriter):

    def __init__(self):
        super().__init__()

    def updatePageFormCheckboxValues(self, page, fields):

        for j in range(0, len(page['/Annots'])):
            writer_annot = page['/Annots'][j].getObject()
            for field in fields:
                writer_annot.update(NameObject("/Ff"): NumberObject(1))




origin = ## Put input pdf path here
destination = ## Put output pdf path here, even if the file does not exist yet

newchecks =  # A dict with all checkbox values that need to be changed
newvals = '':'' # A dict with all entry values that need to be changed
# newvals dict has to be equal to '':'' in case that no changes are needed

c = PdfFileFiller(origin)
c.update_form_values(outfile=destination, newvals=newvals, newchecks=newchecks)
print('PDF has been created\n')

【讨论】:

【参考方案5】:

我在拼合使用 pdfrw (How to Populate Fillable PDF's with Python) 输入内容的表单时遇到了问题,发现我必须使用 generate_fdf (pdftk flatten loses fillable field data) 添加额外的步骤。

os.system('pdftk '+outtemp+' generate_fdf output '+outfdf)
os.system('pdftk '+outtemp+' fill_form '+outfdf+' output '+outpdf)

我之所以选择这个解决方案,是因为我能够在我的 Mac 上使用 ghostscript 的 pdf2ps 和 ps2pdf 很好地展平文件,但是当我在 Amazon Linux 实例上运行它时质量分辨率较低。我不明白为什么会这样,所以转向了 pdftk 解决方案。

【讨论】:

以上是关于使用 Python 生成平面 PDF的主要内容,如果未能解决你的问题,请参考以下文章

python使用fpdf生成结账发票pdf文件

Django/Python:使用正确的语言生成 pdf

python使用fpdf生成pdf文件并添加页眉和页脚

python使用fpdf生成各种样式pdf表格数据

python使用fpdf将生成的长字符串手动换行写入pdf

python使用fpdf生成pdf文件:配置多种语言字体写入多种文字