You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python填充PDF表单字段值多余垂直空白问题解决方案

问题背景

我们当前使用如下工作流生成可填写PDF并通过Python完成内容填充,该流程依赖pdftk外部工具,现在需要移除该依赖:

  • 在LibreOffice Writer(7.3.4.2版本)中生成带表单字段(controls控件)的.odt文件
  • 将文件导出为PDF格式(LibreOffice可直接导出可填写PDF;如果使用Word则需要通过Acrobat后处理才能生成可填写PDF,我们没有配备Acrobat工具)
  • 在Python中借助fdfgen库生成包含填充值的.fdf文件
  • 在Python中调用外部pdftk工具将.fdf文件与可填写PDF合并,生成flattened(不可编辑)的最终PDF文件

我们的目标是完全不依赖pdftk或其他任何外部工具,移除上述步骤4(如果不再需要.fdf格式,也可以一并移除步骤3)。

已尝试方案及存在的问题

我们已经测试了pypdf2、pdfrw(实际使用pdfrw2)、fillpdf三个Python模块,均存在无法满足需求的问题:

  • 使用pypdf2和pdfrw填充时,字段值的上方和/或下方会出现多余垂直空白。由于字段背景为不透明样式,该空白会挤占表单可用垂直空间、遮挡字段标签(为了展示问题我们将字段设置为灰色背景加边框,正常使用场景下字段为白色无边框样式)
  • fillpdf无法识别多行文本字段,仅能生成被裁剪的单行结果
  • 使用pdftk方案填充时不会出现上述多余垂直空白的问题
诉求

我们需要以下三类可行方案:
a)在LibreOffice导出PDF环节消除该多余垂直空白
b)通过纯Python方案消除填充后PDF中的多余垂直空白
c)将字段背景设置为透明,使得可以安全将整个字段上移,即使重叠也不会遮挡字段标签

效果对比
  • LibreOffice中编辑的文档效果:默认值上方无垂直空白
    LibreOffice编辑效果
  • LibreOffice导出的可填写PDF效果:此时已出现多余垂直空白,初步猜测问题出在LibreOffice PDF导出环节,但使用pdftk方案填充时该空白并不会出现
    LibreOffice导出PDF效果
  • 使用pypdf2生成的填充后PDF效果(pdfrw生成效果与之一致):
    pypdf2填充效果
  • 使用现有工作流(fdfgen+外部调用pdftk)生成的填充后PDF效果:
    pdftk填充效果
参考代码

以下为生成各版本填充PDF所用的代码:

import json
import os

cluePdfName='filled.pdf'
clueFdfName='filled.fdf'
fillableClueReportPdfFileName='clueReportFillable.pdf'

fields={
    'titleField':'SEARCH AND RESCUE\nSEARCH AND RESCUE\nSEARCH AND RESCUE SEARCH AND RESCUE SEARCH AND RESCUE ',
    'incidentNameField':'incident',
    'instructionsCollectField':True,
    'instructionsOtherField':True,
    'descriptionField':'long description long description long description long description long description long description long description long description long description long description long description long description long description',
    'locationField':'long description long description long description long description long description long description long description long description long description long description long description long description long description',
    'locationRadioGPSField':'(Radio GPS: 12345  67890)',
    'instructionsOtherTextField':'do some stuff'}

################################################
#  部分1: pypdf2实现
################################################

from PyPDF2 import PdfReader,PdfWriter
from PyPDF2.generic import NameObject,TextStringObject,NumberObject,BooleanObject

reader=PdfReader(fillableClueReportPdfFileName)
writer=PdfWriter()
page=reader.pages[0]
pdfFields=reader.get_fields()
writer.add_page(page)

# 重写PdfWriter.update_page_form_field_values逻辑
# 参考实现来源:https://stackoverflow.com/a/48412434/3577105
# - 填充文本字段和布尔值(复选框'/Btn'字段)
# - 将/AS设为相同值,解决字段值点击后才可见的问题
# - 最后为所有字段设置只读标记
for j in range(0, len(page['/Annots'])):
    writer_annot = page['/Annots'][j].getObject()
    for field in fields:
        if writer_annot.get('/T') == field:
            val=fields[field]
            valObj=TextStringObject('---')
            className=val.__class__.__name__
            if className=='str':
                valObj=TextStringObject(val)
            elif className=='bool':
                # 复选框需要NameObject类型,值为/Yes或/Off
                if val:
                    valObj=NameObject('/Yes')
                else:
                    valObj=NameObject('/Off')
            elif className in ['int','float']:
                valObj=TextStringObject(str(val))
            print('updating '+str(field)+' --> '+str(valObj))
            writer_annot.update({
                NameObject("/V"): valObj,
            })
    ff=writer_annot.get('/Ff')
    if ff:
        newff=ff|1 # 为字段设置只读标记,不修改其他位配置
        print('Ff: '+str(ff)+' --> '+str(newff))
        writer_annot.update({NameObject('/Ff'): NumberObject(newff)})
    else:
        writer_annot.update({NameObject('/Ff'): NumberObject(1)})

with open(cluePdfName,'wb') as out:
    writer.write(out)


################################################
#  部分2: fdfgen+pdftk实现
################################################

from fdfgen import forge_fdf

fdf=forge_fdf("",fields.items(),[],[],[])
fdf_file=open(clueFdfName,"wb")
fdf_file.write(fdf)
fdf_file.close()

cluePdfTkName=cluePdfName.replace('.pdf','_pdftk.pdf')
pdftk_cmd='pdftk "'+fillableClueReportPdfFileName+'" fill_form "'+clueFdfName+'" output "'+cluePdfTkName+'" flatten'
print("Calling pdftk with the following command:")
print(pdftk_cmd)
os.system(pdftk_cmd)


################################################
#  部分3: pdfrw实现
# 参考实现来源:https://akdux.com/python/2020/10/31/python-fill-pdf-files.html
# 执行pip install pdfrw2后,import pdfrw实际导入的是pdfrw2(v0.5版本)
################################################

import pdfrw

ANNOT_KEY = '/Annots'
ANNOT_FIELD_KEY = '/T'
ANNOT_VAL_KEY = '/V'
ANNOT_RECT_KEY = '/Rect'
SUBTYPE_KEY = '/Subtype'
WIDGET_SUBTYPE_KEY = '/Widget'

def fill_pdf(input_pdf_path, output_pdf_path, data_dict):
    template_pdf = pdfrw.PdfReader(input_pdf_path)
    for page in template_pdf.pages:
        annotations = page[ANNOT_KEY]
        for annotation in annotations:
            if annotation[SUBTYPE_KEY] == WIDGET_SUBTYPE_KEY:
                if annotation[ANNOT_FIELD_KEY]:
                    key = annotation[ANNOT_FIELD_KEY][1:-1]
                    if key in data_dict.keys():
                        if type(data_dict[key]) == bool:
                            if data_dict[key] == True:
                                annotation.update(pdfrw.PdfDict(
                                    AS=pdfrw.PdfName('Yes')))
                        else:
                            annotation.update(
                                pdfrw.PdfDict(V='{}'.format(data_dict[key]))
                            )
                            annotation.update(pdfrw.PdfDict(AP=''))
    template_pdf.Root.AcroForm.update(pdfrw.PdfDict(NeedAppearances=pdfrw.PdfObject('true')))
    pdfrw.PdfWriter().write(output_pdf_path, template_pdf)

fill_pdf(fillableClueReportPdfFileName,cluePdfName.replace('.pdf','_pdfrw.pdf'),fields)


################################################
#  部分4: fillpdf实现
################################################
import fillpdf
from fillpdf import fillpdfs

fillpdfs.write_fillable_pdf(fillableClueReportPdfFileName,cluePdfName.replace('.pdf','_fillpdf.pdf'),fields,flatten=True)

内容的提问来源于stack exchange,提问作者Tom Grundy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 22:36:20