mercury-agent 0.5.7 → 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -3
- package/container/Dockerfile +9 -5
- package/container/Dockerfile.base +2 -2
- package/container/build.sh +7 -38
- package/docs/authoring-profiles.md +2 -2
- package/docs/container-lifecycle.md +7 -11
- package/docs/memory.md +0 -6
- package/docs/prd-config-load.md +1 -1
- package/examples/extensions/napkin/index.ts +17 -10
- package/examples/extensions/napkin/prompts/kb-distillation.md +6 -6
- package/examples/extensions/pdf/skill/SKILL.md +545 -178
- package/examples/extensions/pdf/skill/forms.md +273 -214
- package/examples/extensions/pdf/skill/reference.md +450 -480
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +221 -58
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +90 -5
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +138 -22
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +191 -27
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +149 -104
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +215 -91
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +130 -86
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +204 -87
- package/examples/extensions/tradestation/skill/SKILL.md +2 -2
- package/package.json +17 -1
- package/resources/templates/env.template +0 -3
- package/src/adapters/whatsapp-media.ts +5 -2
- package/src/agent/container-entry.ts +2 -2
- package/src/agent/container-runner.ts +19 -14
- package/src/bridges/telegram.ts +5 -2
- package/src/chat-shim.ts +26 -17
- package/src/core/media.ts +5 -2
- package/src/dashboard/tokens.css +1 -1
- package/src/extensions/image-builder.ts +2 -2
- package/src/main.ts +2 -2
- package/examples/extensions/pdf/skill/LICENSE.txt +0 -30
|
@@ -1,294 +1,353 @@
|
|
|
1
|
-
|
|
1
|
+
# PDF Form Filling Guide
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`python scripts/check_fillable_fields <file.pdf>`, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions.
|
|
3
|
+
This guide covers filling PDF forms, both fillable (AcroForm) and non-fillable PDFs.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
There are two categories of PDF forms:
|
|
8
|
+
|
|
9
|
+
1. **Fillable PDFs** (AcroForm/XFA) -- contain interactive form fields. Users can click and type into fields. These are straightforward to fill programmatically.
|
|
10
|
+
2. **Non-fillable PDFs** -- just text and lines rendered as page content, with no interactive fields. Filling these requires overlaying text at precise coordinates using annotations or drawing.
|
|
11
|
+
|
|
12
|
+
## Workflow Summary
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Start
|
|
16
|
+
|
|
|
17
|
+
+- Run check_fillable_fields.py
|
|
18
|
+
| |
|
|
19
|
+
| +- Has fields -> Fillable path
|
|
20
|
+
| | +- extract_form_field_info.py -> get field names and types
|
|
21
|
+
| | +- check_bounding_boxes.py -> visualize field positions
|
|
22
|
+
| | +- fill_fillable_fields.py -> fill from JSON mapping
|
|
23
|
+
| | +- create_validation_image.py -> verify the result visually
|
|
24
|
+
| |
|
|
25
|
+
| +- No fields -> Non-fillable path
|
|
26
|
+
| +- convert_pdf_to_images.py -> render pages for visual inspection
|
|
27
|
+
| +- extract_form_structure.py -> detect text labels and guess field positions
|
|
28
|
+
| +- fill_pdf_form_with_annotations.py -> overlay text using annotations
|
|
29
|
+
| +- create_validation_image.py -> verify the result visually
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Part 1: Fillable PDF Forms
|
|
35
|
+
|
|
36
|
+
### Step 1: Detect fillable fields
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python3 scripts/check_fillable_fields.py input.pdf
|
|
9
40
|
```
|
|
41
|
+
|
|
42
|
+
This reports whether the PDF has AcroForm fields, lists their names, types, and current values.
|
|
43
|
+
|
|
44
|
+
### Step 2: Extract field metadata
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python3 scripts/extract_form_field_info.py input.pdf -o outbox/fields.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Produces a JSON file with all field details:
|
|
51
|
+
|
|
52
|
+
```json
|
|
10
53
|
[
|
|
11
54
|
{
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"type": "checkbox",
|
|
22
|
-
"checked_value": (Set the field to this value to check the checkbox),
|
|
23
|
-
"unchecked_value": (Set the field to this value to uncheck the checkbox),
|
|
24
|
-
},
|
|
25
|
-
// Radio groups have a "radio_options" list with the possible choices.
|
|
26
|
-
{
|
|
27
|
-
"field_id": (unique ID for the field),
|
|
28
|
-
"page": (page number, 1-based),
|
|
29
|
-
"type": "radio_group",
|
|
30
|
-
"radio_options": [
|
|
31
|
-
{
|
|
32
|
-
"value": (set the field to this value to select this radio option),
|
|
33
|
-
"rect": (bounding box for the radio button for this option)
|
|
34
|
-
},
|
|
35
|
-
// Other radio options
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
// Multiple choice fields have a "choice_options" list with the possible choices:
|
|
39
|
-
{
|
|
40
|
-
"field_id": (unique ID for the field),
|
|
41
|
-
"page": (page number, 1-based),
|
|
42
|
-
"type": "choice",
|
|
43
|
-
"choice_options": [
|
|
44
|
-
{
|
|
45
|
-
"value": (set the field to this value to select this option),
|
|
46
|
-
"text": (display text of the option)
|
|
47
|
-
},
|
|
48
|
-
// Other choice options
|
|
49
|
-
],
|
|
55
|
+
"name": "first_name",
|
|
56
|
+
"type": "/Tx",
|
|
57
|
+
"value": "",
|
|
58
|
+
"rect": [72.0, 700.0, 250.0, 720.0],
|
|
59
|
+
"page": 0,
|
|
60
|
+
"flags": 0,
|
|
61
|
+
"read_only": false,
|
|
62
|
+
"required": false,
|
|
63
|
+
"options": null
|
|
50
64
|
}
|
|
51
65
|
]
|
|
52
66
|
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
67
|
+
|
|
68
|
+
Field types:
|
|
69
|
+
- `/Tx` -- text field
|
|
70
|
+
- `/Btn` -- button (checkbox, radio)
|
|
71
|
+
- `/Ch` -- choice (dropdown, list)
|
|
72
|
+
- `/Sig` -- signature
|
|
73
|
+
|
|
74
|
+
### Step 3: Visualize field positions
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python3 scripts/check_bounding_boxes.py input.pdf -o outbox/fields_overlay.png
|
|
57
78
|
```
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
|
|
80
|
+
Renders each page with colored rectangles showing where fields are, labeled with field names. Use this to verify fields map to the right visual locations before filling.
|
|
81
|
+
|
|
82
|
+
### Step 4: Fill the form
|
|
83
|
+
|
|
84
|
+
Create a JSON file mapping field names to values:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"first_name": "Alice",
|
|
89
|
+
"last_name": "Smith",
|
|
90
|
+
"email": "alice@example.com",
|
|
91
|
+
"agree_terms": true,
|
|
92
|
+
"country": "Canada"
|
|
93
|
+
}
|
|
73
94
|
```
|
|
74
|
-
- Run the `fill_fillable_fields.py` script from this file's directory to create a filled-in PDF:
|
|
75
|
-
`python scripts/fill_fillable_fields.py <input pdf> <field_values.json> <output pdf>`
|
|
76
|
-
This script will verify that the field IDs and values you provide are valid; if it prints error messages, correct the appropriate fields and try again.
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
If the PDF doesn't have fillable form fields, you'll add text annotations. First try to extract coordinates from the PDF structure (more accurate), then fall back to visual estimation if needed.
|
|
96
|
+
Then fill:
|
|
80
97
|
|
|
81
|
-
|
|
98
|
+
```bash
|
|
99
|
+
python3 scripts/fill_fillable_fields.py input.pdf values.json -o outbox/filled.pdf
|
|
100
|
+
```
|
|
82
101
|
|
|
83
|
-
|
|
84
|
-
`python scripts/extract_form_structure.py <input.pdf> form_structure.json`
|
|
102
|
+
For checkboxes, use `true`/`false`. For radio buttons, use the export value string. For dropdowns, use the option text.
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
- **labels**: Every text element with exact coordinates (x0, top, x1, bottom in PDF points)
|
|
88
|
-
- **lines**: Horizontal lines that define row boundaries
|
|
89
|
-
- **checkboxes**: Small square rectangles that are checkboxes (with center coordinates)
|
|
90
|
-
- **row_boundaries**: Row top/bottom positions calculated from horizontal lines
|
|
104
|
+
### Step 5: Validate the result
|
|
91
105
|
|
|
92
|
-
|
|
106
|
+
```bash
|
|
107
|
+
python3 scripts/create_validation_image.py outbox/filled.pdf -o outbox/validation.png
|
|
108
|
+
```
|
|
93
109
|
|
|
94
|
-
|
|
110
|
+
Renders the filled PDF to an image. Visually inspect it to confirm all values appear correctly.
|
|
95
111
|
|
|
96
|
-
|
|
112
|
+
### Inline code (without scripts)
|
|
97
113
|
|
|
98
|
-
|
|
114
|
+
```python
|
|
115
|
+
from pypdf import PdfReader, PdfWriter
|
|
99
116
|
|
|
100
|
-
|
|
117
|
+
reader = PdfReader("form.pdf")
|
|
118
|
+
writer = PdfWriter()
|
|
119
|
+
writer.append(reader)
|
|
101
120
|
|
|
102
|
-
|
|
121
|
+
# Fill fields on page 0
|
|
122
|
+
writer.update_page_form_field_values(
|
|
123
|
+
writer.pages[0],
|
|
124
|
+
{
|
|
125
|
+
"first_name": "Alice",
|
|
126
|
+
"last_name": "Smith",
|
|
127
|
+
"date": "2025-01-15",
|
|
128
|
+
},
|
|
129
|
+
auto_regenerate=False,
|
|
130
|
+
)
|
|
103
131
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
4. **Checkboxes**: Use the checkbox coordinates directly from the structure
|
|
132
|
+
writer.write("outbox/filled.pdf")
|
|
133
|
+
writer.close()
|
|
134
|
+
```
|
|
108
135
|
|
|
109
|
-
**
|
|
136
|
+
**Important**: Always set `auto_regenerate=False`. The default `True` can corrupt field appearances in some PDFs.
|
|
110
137
|
|
|
111
|
-
###
|
|
138
|
+
### Handling checkboxes
|
|
112
139
|
|
|
113
|
-
|
|
114
|
-
- **Circular checkboxes**: Only square rectangles are detected as checkboxes
|
|
115
|
-
- **Complex graphics**: Decorative elements or non-standard form controls
|
|
116
|
-
- **Faded or light-colored elements**: May not be extracted
|
|
140
|
+
Checkboxes use specific values. To check a box, you need its "on" value:
|
|
117
141
|
|
|
118
|
-
|
|
142
|
+
```python
|
|
143
|
+
from pypdf import PdfReader
|
|
119
144
|
|
|
120
|
-
|
|
145
|
+
reader = PdfReader("form.pdf")
|
|
146
|
+
fields = reader.get_fields()
|
|
121
147
|
|
|
122
|
-
|
|
148
|
+
for name, field in fields.items():
|
|
149
|
+
if field.get("/FT") == "/Btn":
|
|
150
|
+
# The /AP dict contains the appearance states
|
|
151
|
+
widget = field.get("/Kids", [field])
|
|
152
|
+
print(f"Checkbox '{name}': possible values = {field.get('/V')}")
|
|
153
|
+
```
|
|
123
154
|
|
|
124
|
-
|
|
125
|
-
- entry x0 = label x1 + 5 (small gap after label)
|
|
126
|
-
- entry x1 = next label's x0, or row boundary
|
|
127
|
-
- entry top = same as label top
|
|
128
|
-
- entry bottom = row boundary line below, or label bottom + row_height
|
|
155
|
+
Typically checkboxes accept `/Yes` to check and `/Off` to uncheck:
|
|
129
156
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
```python
|
|
158
|
+
writer.update_page_form_field_values(
|
|
159
|
+
writer.pages[0],
|
|
160
|
+
{"agree_terms": "/Yes"},
|
|
161
|
+
auto_regenerate=False,
|
|
162
|
+
)
|
|
163
|
+
```
|
|
133
164
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
"page_number": 1,
|
|
143
|
-
"description": "Last name entry field",
|
|
144
|
-
"field_label": "Last Name",
|
|
145
|
-
"label_bounding_box": [43, 63, 87, 73],
|
|
146
|
-
"entry_bounding_box": [92, 63, 260, 79],
|
|
147
|
-
"entry_text": {"text": "Smith", "font_size": 10}
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
"page_number": 1,
|
|
151
|
-
"description": "US Citizen Yes checkbox",
|
|
152
|
-
"field_label": "Yes",
|
|
153
|
-
"label_bounding_box": [260, 200, 280, 210],
|
|
154
|
-
"entry_bounding_box": [285, 197, 292, 205],
|
|
155
|
-
"entry_text": {"text": "X"}
|
|
156
|
-
}
|
|
157
|
-
]
|
|
158
|
-
}
|
|
165
|
+
### Flattening forms
|
|
166
|
+
|
|
167
|
+
After filling, you may want to flatten the form so fields become static text (non-editable):
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Using qpdf (recommended)
|
|
171
|
+
qpdf --flatten-annotations=all filled.pdf flattened.pdf
|
|
159
172
|
```
|
|
160
173
|
|
|
161
|
-
|
|
174
|
+
### Common pitfalls with fillable forms
|
|
175
|
+
|
|
176
|
+
1. **Field names with dots**: Some PDFs use hierarchical names like `form1.page1.FirstName`. Always use the FULL qualified name from the extraction output.
|
|
162
177
|
|
|
163
|
-
|
|
178
|
+
2. **Checkbox export values**: Checkboxes have an "export value" (often "Yes", "1", or "On"). Setting `true` maps to `/Yes`. Check the `extract_form_field_info.py` output to see the actual export value.
|
|
164
179
|
|
|
165
|
-
|
|
166
|
-
`python scripts/check_bounding_boxes.py fields.json`
|
|
180
|
+
3. **Read-only fields**: Fields with the read-only flag (bit 1 of /Ff) cannot be filled. The script will warn about these.
|
|
167
181
|
|
|
168
|
-
|
|
182
|
+
4. **Appearance streams**: Some PDF viewers won't display values without proper appearance streams (/AP). If the filled PDF looks empty in a viewer, try flattening with qpdf.
|
|
183
|
+
|
|
184
|
+
5. **Font embedding**: If the original form uses a custom font, filled values may render in a fallback font. For non-ASCII text, consider the reportlab overlay approach where you control the font.
|
|
169
185
|
|
|
170
186
|
---
|
|
171
187
|
|
|
172
|
-
##
|
|
188
|
+
## Part 2: Non-Fillable PDF Forms
|
|
189
|
+
|
|
190
|
+
Non-fillable PDFs have no interactive fields. To "fill" them, you overlay text at specific coordinates using PDF annotations or by drawing on a separate layer and merging it.
|
|
191
|
+
|
|
192
|
+
### Strategy
|
|
193
|
+
|
|
194
|
+
1. Render the PDF to an image to see exactly where fields should be filled.
|
|
195
|
+
2. Identify the coordinates of blank areas where values need to go.
|
|
196
|
+
3. Use a reportlab overlay (default method, best compatibility) or FreeText annotations.
|
|
197
|
+
|
|
198
|
+
### Step 1: Render to image for inspection
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
python3 scripts/convert_pdf_to_images.py input.pdf -o outbox/ --dpi 200
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Open the resulting images to visually identify where to place text.
|
|
173
205
|
|
|
174
|
-
|
|
206
|
+
### Step 2: Extract form structure (heuristic)
|
|
175
207
|
|
|
176
|
-
|
|
208
|
+
```bash
|
|
209
|
+
python3 scripts/extract_form_structure.py input.pdf -o outbox/structure.json
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This script uses pdfplumber to detect text labels and underlines/boxes, then heuristically guesses where fillable regions might be. The output is approximate -- always verify with the rendered image.
|
|
177
213
|
|
|
178
|
-
|
|
214
|
+
The output includes both pdfplumber coordinates (top-left origin) and PDF coordinates (bottom-left origin) for each detected region.
|
|
179
215
|
|
|
180
|
-
###
|
|
216
|
+
### Step 3: Define fill positions
|
|
181
217
|
|
|
182
|
-
|
|
183
|
-
- Form field labels and their approximate positions
|
|
184
|
-
- Entry areas (lines, boxes, or blank spaces for text input)
|
|
185
|
-
- Checkboxes and their approximate locations
|
|
218
|
+
Create a JSON file specifying what to write and where:
|
|
186
219
|
|
|
187
|
-
|
|
220
|
+
```json
|
|
221
|
+
[
|
|
222
|
+
{
|
|
223
|
+
"page": 0,
|
|
224
|
+
"fields": [
|
|
225
|
+
{
|
|
226
|
+
"x": 200,
|
|
227
|
+
"y": 705,
|
|
228
|
+
"value": "Alice Smith",
|
|
229
|
+
"font_size": 11
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"x": 200,
|
|
233
|
+
"y": 680,
|
|
234
|
+
"value": "alice@example.com",
|
|
235
|
+
"font_size": 11
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
```
|
|
188
241
|
|
|
189
|
-
|
|
242
|
+
Coordinates are in PDF points (1 point = 1/72 inch). Origin is bottom-left of the page.
|
|
190
243
|
|
|
191
|
-
|
|
244
|
+
### Step 4: Fill using overlay (default method)
|
|
192
245
|
|
|
193
|
-
**Create a zoomed crop using ImageMagick:**
|
|
194
246
|
```bash
|
|
195
|
-
|
|
247
|
+
python3 scripts/fill_pdf_form_with_annotations.py input.pdf fill_spec.json -o outbox/filled.pdf
|
|
196
248
|
```
|
|
197
249
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
250
|
+
This creates a transparent overlay with reportlab and merges it onto the original PDF. The original content is preserved underneath.
|
|
251
|
+
|
|
252
|
+
To use FreeText annotations instead (original content completely untouched):
|
|
201
253
|
|
|
202
|
-
**Example:** To refine a "Name" field estimated around (100, 150):
|
|
203
254
|
```bash
|
|
204
|
-
|
|
255
|
+
python3 scripts/fill_pdf_form_with_annotations.py input.pdf fill_spec.json -o outbox/filled.pdf --method annot
|
|
205
256
|
```
|
|
206
257
|
|
|
207
|
-
|
|
258
|
+
### Alternative: manual overlay with reportlab
|
|
208
259
|
|
|
209
|
-
|
|
210
|
-
1. Identify the exact pixel where the entry area begins (after the label)
|
|
211
|
-
2. Identify where the entry area ends (before next field or edge)
|
|
212
|
-
3. Identify the top and bottom of the entry line/box
|
|
260
|
+
For full control over the overlay:
|
|
213
261
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
262
|
+
```python
|
|
263
|
+
import io
|
|
264
|
+
from pypdf import PdfReader, PdfWriter
|
|
265
|
+
from reportlab.pdfgen import canvas
|
|
266
|
+
from reportlab.lib.pagesizes import letter
|
|
217
267
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
268
|
+
# Create overlay
|
|
269
|
+
packet = io.BytesIO()
|
|
270
|
+
c = canvas.Canvas(packet, pagesize=letter)
|
|
271
|
+
c.setFont("Helvetica", 11)
|
|
272
|
+
c.drawString(200, 705, "Alice Smith")
|
|
273
|
+
c.drawString(200, 680, "alice@example.com")
|
|
274
|
+
c.save()
|
|
275
|
+
packet.seek(0)
|
|
221
276
|
|
|
222
|
-
|
|
277
|
+
# Merge overlay onto original
|
|
278
|
+
original = PdfReader("input.pdf")
|
|
279
|
+
overlay = PdfReader(packet)
|
|
223
280
|
|
|
224
|
-
|
|
281
|
+
writer = PdfWriter()
|
|
282
|
+
page = original.pages[0]
|
|
283
|
+
page.merge_page(overlay.pages[0])
|
|
284
|
+
writer.add_page(page)
|
|
225
285
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"entry_bounding_box": [255, 175, 720, 218],
|
|
239
|
-
"entry_text": {"text": "Smith", "font_size": 10}
|
|
240
|
-
}
|
|
241
|
-
]
|
|
242
|
-
}
|
|
286
|
+
# Copy remaining pages unchanged
|
|
287
|
+
for p in original.pages[1:]:
|
|
288
|
+
writer.add_page(p)
|
|
289
|
+
|
|
290
|
+
writer.write("outbox/filled.pdf")
|
|
291
|
+
writer.close()
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Step 5: Validate
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
python3 scripts/create_validation_image.py outbox/filled.pdf -o outbox/validation.png
|
|
243
298
|
```
|
|
244
299
|
|
|
245
|
-
|
|
300
|
+
Always visually inspect the result. Coordinates often need fine-tuning.
|
|
246
301
|
|
|
247
|
-
###
|
|
302
|
+
### Common pitfalls with non-fillable forms
|
|
248
303
|
|
|
249
|
-
|
|
250
|
-
|
|
304
|
+
1. **Coordinate mismatch**: The most common error. PDF Y-axis is bottom-up (origin at bottom-left), but screen/image Y-axis is top-down. Always convert:
|
|
305
|
+
```
|
|
306
|
+
pdf_x = pixel_x * 72 / dpi
|
|
307
|
+
pdf_y = page_height_points - (pixel_y * 72 / dpi)
|
|
308
|
+
```
|
|
251
309
|
|
|
252
|
-
|
|
310
|
+
2. **Page rotation**: If a page is rotated (e.g., 90 degrees), the coordinate system rotates too. Check `page.mediabox` and `page.rotation` and adjust.
|
|
253
311
|
|
|
254
|
-
|
|
312
|
+
3. **Different page sizes**: Not all pages in a PDF have the same dimensions. Always check each page's mediabox.
|
|
255
313
|
|
|
256
|
-
|
|
314
|
+
4. **Font metrics**: Characters have different widths. If you need to center text in a box, use `stringWidth()` from ReportLab:
|
|
315
|
+
```python
|
|
316
|
+
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
317
|
+
w = stringWidth("Jane Doe", "Helvetica", 10)
|
|
318
|
+
x = box_x + (box_width - w) / 2 # centered x position
|
|
319
|
+
```
|
|
257
320
|
|
|
258
|
-
|
|
321
|
+
5. **Overlapping with existing content**: If the form has lines or boxes, your text might overlap. Adjust the Y coordinate by 2-3 points upward from the line.
|
|
259
322
|
|
|
260
|
-
|
|
261
|
-
2. **Convert PDF to images** for visual analysis of missing fields
|
|
262
|
-
3. **Use zoom refinement** (from Approach B) for the missing fields
|
|
263
|
-
4. **Combine coordinates**: For fields from structure extraction, use `pdf_width`/`pdf_height`. For visually-estimated fields, you must convert image coordinates to PDF coordinates:
|
|
264
|
-
- pdf_x = image_x * (pdf_width / image_width)
|
|
265
|
-
- pdf_y = image_y * (pdf_height / image_height)
|
|
266
|
-
5. **Use a single coordinate system** in fields.json - convert all to PDF coordinates with `pdf_width`/`pdf_height`
|
|
323
|
+
6. **Checkbox simulation**: For non-fillable checkboxes, overlay "X" or a checkmark character at the checkbox coordinates.
|
|
267
324
|
|
|
268
325
|
---
|
|
269
326
|
|
|
270
|
-
##
|
|
327
|
+
## Troubleshooting
|
|
271
328
|
|
|
272
|
-
|
|
273
|
-
`python scripts/check_bounding_boxes.py fields.json`
|
|
329
|
+
### Field values not visible after filling
|
|
274
330
|
|
|
275
|
-
|
|
276
|
-
-
|
|
277
|
-
-
|
|
331
|
+
- Set `auto_regenerate=False` when calling `update_page_form_field_values`.
|
|
332
|
+
- Some PDF viewers cache field appearances. Try opening in a different viewer.
|
|
333
|
+
- Flatten the form with qpdf to force rendering: `qpdf --flatten-annotations=all filled.pdf output.pdf`
|
|
278
334
|
|
|
279
|
-
|
|
335
|
+
### Wrong font or encoding in filled fields
|
|
280
336
|
|
|
281
|
-
|
|
337
|
+
- pypdf uses the font already defined in the field. If the original font does not support your characters, the text may appear garbled.
|
|
338
|
+
- For non-ASCII text, consider the reportlab overlay approach where you control the font.
|
|
282
339
|
|
|
283
|
-
|
|
284
|
-
`python scripts/fill_pdf_form_with_annotations.py <input.pdf> fields.json <output.pdf>`
|
|
340
|
+
### Coordinates are wrong for non-fillable forms
|
|
285
341
|
|
|
286
|
-
|
|
342
|
+
- PDF coordinate origin is bottom-left, not top-left.
|
|
343
|
+
- To convert from pixel coordinates (in a rendered image) to PDF points:
|
|
344
|
+
```
|
|
345
|
+
pdf_x = pixel_x * 72 / dpi
|
|
346
|
+
pdf_y = page_height_points - (pixel_y * 72 / dpi)
|
|
347
|
+
```
|
|
348
|
+
- Use `check_bounding_boxes.py` or render the page to an image and measure pixel positions.
|
|
287
349
|
|
|
288
|
-
|
|
289
|
-
`python scripts/convert_pdf_to_images.py <output.pdf> <verify_images/>`
|
|
350
|
+
### Large PDFs are slow
|
|
290
351
|
|
|
291
|
-
|
|
292
|
-
-
|
|
293
|
-
- **Approach B**: Check that image dimensions match and coordinates are accurate pixels
|
|
294
|
-
- **Hybrid**: Ensure coordinate conversions are correct for visually-estimated fields
|
|
352
|
+
- Use pypdfium2 instead of pdf2image for rendering -- it is significantly faster.
|
|
353
|
+
- Process only the pages you need rather than the entire document.
|