292 lines
11 KiB
Markdown
292 lines
11 KiB
Markdown
# 可填写字段
|
||
|
||
如果 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_width` 和 `pdf_height`(表示 PDF 坐标)创建 fields.json:
|
||
```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 创建缩放裁剪图:**
|
||
```bash
|
||
magick <page_image> -crop <width>x<height>+<x>+<y> +repage <crop_output.png>
|
||
```
|
||
|
||
其中:
|
||
- `<x>, <y>` = 裁剪区域左上角(使用你的大致估算值减去边距)
|
||
- `<width>, <height>` = 裁剪区域的大小(字段区域加上每边约 50px 的边距)
|
||
|
||
**示例:** 精确定位大约在 (100, 150) 位置的「姓名」字段:
|
||
```bash
|
||
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_width` 和 `image_height`(表示图片坐标)创建 fields.json:
|
||
```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**:确认图片尺寸匹配且坐标是精确的像素值
|
||
- **混合方法**:确保视觉估算字段的坐标转换正确
|