11 KiB
可填写字段
如果 PDF 含有可填写的表单字段:
- 在此文件所在目录下运行以下脚本:
python scripts/extract_form_field_info.py <input.pdf> <field_info.json>。该脚本会生成一个 JSON 文件,其中包含字段列表,格式如下:
[
{
"field_id": (字段的唯一 ID),
"page": (页码,从 1 开始),
"rect": ([左, 下, 右, 上] 边界框,使用 PDF 坐标,y=0 为页面底部),
"type": ("text"(文本)、"checkbox"(复选框)、"radio_group"(单选组)或 "choice"(下拉选择)),
},
// 复选框包含 "checked_value" 和 "unchecked_value" 属性:
{
"field_id": (字段的唯一 ID),
"page": (页码,从 1 开始),
"type": "checkbox",
"checked_value": (将该字段设为此值即可勾选复选框),
"unchecked_value": (将该字段设为此值即可取消勾选复选框),
},
// 单选组包含一个 "radio_options" 列表,列出所有可选选项:
{
"field_id": (字段的唯一 ID),
"page": (页码,从 1 开始),
"type": "radio_group",
"radio_options": [
{
"value": (将该字段设为此值即可选择该单选选项),
"rect": (该单选按钮的边界框)
},
// 其他单选选项
]
},
// 下拉选择字段包含一个 "choice_options" 列表,列出所有可选选项:
{
"field_id": (字段的唯一 ID),
"page": (页码,从 1 开始),
"type": "choice",
"choice_options": [
{
"value": (将该字段设为此值即可选择该选项),
"text": (选项的显示文本)
},
// 其他下拉选项
],
}
]
- 使用以下脚本将 PDF 转换为 PNG 图片(每页一张)(在此文件所在目录下运行):
python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>然后分析图片,确定每个表单字段的用途(注意将边界框的 PDF 坐标转换为图片坐标)。 - 创建一个
field_values.json文件,格式如下,包含每个字段要填写的值:
[
{
"field_id": "last_name", // 必须与 `extract_form_field_info.py` 中的 field_id 一致
"description": "用户的姓氏",
"page": 1, // 必须与 field_info.json 中的 "page" 值一致
"value": "Simpson"
},
{
"field_id": "Checkbox12",
"description": "若用户年满 18 岁则勾选的复选框",
"page": 1,
"value": "/On" // 如果是复选框,使用其 "checked_value" 进行勾选;如果是单选按钮组,使用 "radio_options" 中的某个 "value" 值
},
// 更多字段
]
- 在此文件所在目录下运行
fill_fillable_fields.py脚本,生成已填写的 PDF:python scripts/fill_fillable_fields.py <input pdf> <field_values.json> <output pdf>该脚本会验证你提供的字段 ID 和值是否有效;如果打印了错误信息,请修正相应字段后重试。
非可填写字段
如果 PDF 没有可填写的表单字段,你需要添加文本注释。首先尝试从 PDF 结构中提取坐标(更精确),如有需要再退回到视觉估算。
第一步:优先尝试结构提取
运行以下脚本,提取文本标签、线条和复选框及其精确的 PDF 坐标:
python scripts/extract_form_structure.py <input.pdf> form_structure.json
这会生成一个 JSON 文件,包含:
- labels(标签):每个文本元素及其精确坐标(以 PDF 点为单位:x0, top, x1, bottom)
- lines(线条):定义行边界的水平线
- checkboxes(复选框):作为复选框的小正方形矩形(包含中心坐标)
- row_boundaries(行边界):根据水平线计算出的行顶部/底部位置
检查结果:如果 form_structure.json 包含有意义的标签(与表单字段对应的文本元素),则使用 方法 A:基于结构的坐标。如果 PDF 是扫描件或基于图片,几乎没有标签,则使用 方法 B:视觉估算。
方法 A:基于结构的坐标(推荐)
当 extract_form_structure.py 在 PDF 中找到了文本标签时使用此方法。
A.1:分析结构
读取 form_structure.json 并识别:
- 标签组:相邻的文本元素构成一个完整的标签(例如,"Last" + "Name")
- 行结构:具有相近
top值的标签位于同一行 - 字段列:填写区域从标签结束之后开始(x0 = 标签.x1 + 间距)
- 复选框:直接从结构中获取复选框坐标
坐标系统:PDF 坐标中 y=0 位于页面顶部,y 值向下递增。
A.2:检查缺失元素
结构提取可能无法检测到所有表单元素。常见情况:
- 圆形复选框:只有正方形矩形会被检测为复选框
- 复杂图形:装饰性元素或非标准表单控件
- 褪色或浅色元素:可能无法被提取
如果你在 PDF 图片中看到了 form_structure.json 中未包含的表单字段,你需要对这些特定字段使用 视觉分析(见下方的「混合方法」)。
A.3:使用 PDF 坐标创建 fields.json
对于每个字段,根据提取的结构计算填写坐标:
文本字段:
- 填写区域 x0 = 标签 x1 + 5(标签后留出小间距)
- 填写区域 x1 = 下一个标签的 x0,或行边界
- 填写区域 top = 与标签 top 相同
- 填写区域 bottom = 下方行边界线,或标签 bottom + 行高
复选框:
- 直接使用 form_structure.json 中的复选框矩形坐标
- entry_bounding_box = [checkbox.x0, checkbox.top, checkbox.x1, checkbox.bottom]
使用 pdf_width 和 pdf_height(表示 PDF 坐标)创建 fields.json:
{
"pages": [
{"page_number": 1, "pdf_width": 612, "pdf_height": 792}
],
"form_fields": [
{
"page_number": 1,
"description": "姓氏填写字段",
"field_label": "Last Name",
"label_bounding_box": [43, 63, 87, 73],
"entry_bounding_box": [92, 63, 260, 79],
"entry_text": {"text": "Smith", "font_size": 10}
},
{
"page_number": 1,
"description": "美国公民「是」复选框",
"field_label": "Yes",
"label_bounding_box": [260, 200, 280, 210],
"entry_bounding_box": [285, 197, 292, 205],
"entry_text": {"text": "X"}
}
]
}
重要:使用 form_structure.json 中的 pdf_width/pdf_height 及坐标。
A.4:验证边界框
在填写之前,检查你的边界框是否有错误:
python scripts/check_bounding_boxes.py fields.json
该脚本会检查边界框是否存在相交,以及填写区域是否因字号过大而空间不足。修复所有报告的错误后再进行填写。
方法 B:视觉估算(备用方法)
当 PDF 是扫描件或基于图片,结构提取未能找到可用的文本标签(例如所有文本都显示为 "(cid:X)" 模式)时使用此方法。
B.1:将 PDF 转换为图片
python scripts/convert_pdf_to_images.py <input.pdf> <images_dir/>
B.2:初步字段识别
检查每页图片,识别表单区域并获取字段位置的大致估算:
- 表单字段标签及其大致位置
- 填写区域(用于文本输入的线条、方框或空白区域)
- 复选框及其大致位置
对于每个字段,记下近似的像素坐标(暂不需要精确)。
B.3:缩放精确定位(对精度至关重要)
对于每个字段,围绕估算位置裁剪出一个区域,以精确确定坐标。
使用 ImageMagick 创建缩放裁剪图:
magick <page_image> -crop <width>x<height>+<x>+<y> +repage <crop_output.png>
其中:
<x>, <y>= 裁剪区域左上角(使用你的大致估算值减去边距)<width>, <height>= 裁剪区域的大小(字段区域加上每边约 50px 的边距)
示例: 精确定位大约在 (100, 150) 位置的「姓名」字段:
magick images_dir/page_1.png -crop 300x80+50+120 +repage crops/name_field.png
(注意:如果 magick 命令不可用,可尝试使用相同参数的 convert。)
检查裁剪后的图片,确定精确坐标:
- 确定填写区域开始的精确像素位置(标签之后)
- 确定填写区域结束的位置(下一个字段或边缘之前)
- 确定填写线条/方框的顶部和底部位置
将裁剪坐标转换回完整图片坐标:
- full_x = crop_x + crop_offset_x
- full_y = crop_y + crop_offset_y
示例:如果裁剪从 (50, 120) 开始,填写方框在裁剪图内的坐标为 (52, 18):
- entry_x0 = 52 + 50 = 102
- entry_top = 18 + 120 = 138
对每个字段重复上述步骤,尽可能将相邻字段合并到同一次裁剪中。
B.4:使用精确定位坐标创建 fields.json
使用 image_width 和 image_height(表示图片坐标)创建 fields.json:
{
"pages": [
{"page_number": 1, "image_width": 1700, "image_height": 2200}
],
"form_fields": [
{
"page_number": 1,
"description": "姓氏填写字段",
"field_label": "Last Name",
"label_bounding_box": [120, 175, 242, 198],
"entry_bounding_box": [255, 175, 720, 218],
"entry_text": {"text": "Smith", "font_size": 10}
}
]
}
重要:使用 image_width/image_height 以及缩放分析中得到的精确像素坐标。
B.5:验证边界框
在填写之前,检查你的边界框是否有错误:
python scripts/check_bounding_boxes.py fields.json
该脚本会检查边界框是否存在相交,以及填写区域是否因字号过大而空间不足。修复所有报告的错误后再进行填写。
混合方法:结构 + 视觉
当结构提取对大多数字段有效,但遗漏了部分元素(如圆形复选框、非标准表单控件)时使用此方法。
- 对 form_structure.json 中检测到的字段使用方法 A
- 将 PDF 转换为图片,用于对缺失字段进行视觉分析
- 对缺失字段使用缩放精确定位(来自方法 B)
- 合并坐标:对于从结构提取获得的字段,使用
pdf_width/pdf_height。对于视觉估算的字段,需要将图片坐标转换为 PDF 坐标:- pdf_x = image_x * (pdf_width / image_width)
- pdf_y = image_y * (pdf_height / image_height)
- 在 fields.json 中使用统一的坐标系统——将所有坐标转换为 PDF 坐标,配合
pdf_width/pdf_height
第二步:填写前验证
在填写之前务必验证边界框:
python scripts/check_bounding_boxes.py fields.json
该脚本检查:
- 边界框是否相交(会导致文本重叠)
- 填写区域是否因指定字号过小而空间不足
修复 fields.json 中报告的所有错误后再继续。
第三步:填写表单
填写脚本会自动检测坐标系统并处理转换:
python scripts/fill_pdf_form_with_annotations.py <input.pdf> fields.json <output.pdf>
第四步:验证输出
将填写后的 PDF 转换为图片,验证文本位置:
python scripts/convert_pdf_to_images.py <output.pdf> <verify_images/>
如果文本位置不正确:
- 方法 A:确认你使用了 form_structure.json 中的 PDF 坐标并配合
pdf_width/pdf_height - 方法 B:确认图片尺寸匹配且坐标是精确的像素值
- 混合方法:确保视觉估算字段的坐标转换正确