Files
2026-07-13 21:35:39 +08:00

11 KiB
Raw Permalink Blame History

可填写字段

如果 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 并识别:

  1. 标签组:相邻的文本元素构成一个完整的标签(例如,"Last" + "Name"
  2. 行结构:具有相近 top 值的标签位于同一行
  3. 字段列:填写区域从标签结束之后开始(x0 = 标签.x1 + 间距)
  4. 复选框:直接从结构中获取复选框坐标

坐标系统: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_widthpdf_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。)

检查裁剪后的图片,确定精确坐标:

  1. 确定填写区域开始的精确像素位置(标签之后)
  2. 确定填写区域结束的位置(下一个字段或边缘之前)
  3. 确定填写线条/方框的顶部和底部位置

将裁剪坐标转换回完整图片坐标:

  • 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_widthimage_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

该脚本会检查边界框是否存在相交,以及填写区域是否因字号过大而空间不足。修复所有报告的错误后再进行填写。


混合方法:结构 + 视觉

当结构提取对大多数字段有效,但遗漏了部分元素(如圆形复选框、非标准表单控件)时使用此方法。

  1. 对 form_structure.json 中检测到的字段使用方法 A
  2. 将 PDF 转换为图片,用于对缺失字段进行视觉分析
  3. 对缺失字段使用缩放精确定位(来自方法 B
  4. 合并坐标:对于从结构提取获得的字段,使用 pdf_width/pdf_height。对于视觉估算的字段,需要将图片坐标转换为 PDF 坐标:
    • pdf_x = image_x * (pdf_width / image_width)
    • pdf_y = image_y * (pdf_height / image_height)
  5. 在 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:确认图片尺寸匹配且坐标是精确的像素值
  • 混合方法:确保视觉估算字段的坐标转换正确