使用 Python 生成平面 PDF
Posted
技术标签:
【中文标题】使用 Python 生成平面 PDF【英文标题】:Generate flattened PDF with Python 【发布时间】:2015-01-17 08:05:27 【问题描述】:当我从任何源 PDF 打印 PDF 时,文件大小会减小并删除表单中显示的文本框。简而言之,它会使文件变平。 这是我想要实现的行为。
以下代码使用另一个 PDF 作为源(我想要展平的那个)创建 PDF,它也写入文本框表单。
我可以获得没有文本框的 PDF,将其展平吗?就像我将 PDF 打印为 PDF 时 Adobe 所做的一样。
我的其他代码看起来像这样减去一些东西:
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,可以安装为 pdftk
或 pdftk-java
。最后签到 21.04。【参考方案2】:
根据 Adobe 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的主要内容,如果未能解决你的问题,请参考以下文章