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.
Files changed (34) hide show
  1. package/README.md +29 -3
  2. package/container/Dockerfile +9 -5
  3. package/container/Dockerfile.base +2 -2
  4. package/container/build.sh +7 -38
  5. package/docs/authoring-profiles.md +2 -2
  6. package/docs/container-lifecycle.md +7 -11
  7. package/docs/memory.md +0 -6
  8. package/docs/prd-config-load.md +1 -1
  9. package/examples/extensions/napkin/index.ts +17 -10
  10. package/examples/extensions/napkin/prompts/kb-distillation.md +6 -6
  11. package/examples/extensions/pdf/skill/SKILL.md +545 -178
  12. package/examples/extensions/pdf/skill/forms.md +273 -214
  13. package/examples/extensions/pdf/skill/reference.md +450 -480
  14. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +221 -58
  15. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +90 -5
  16. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +138 -22
  17. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +191 -27
  18. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +149 -104
  19. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +215 -91
  20. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +130 -86
  21. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +204 -87
  22. package/examples/extensions/tradestation/skill/SKILL.md +2 -2
  23. package/package.json +17 -1
  24. package/resources/templates/env.template +0 -3
  25. package/src/adapters/whatsapp-media.ts +5 -2
  26. package/src/agent/container-entry.ts +2 -2
  27. package/src/agent/container-runner.ts +19 -14
  28. package/src/bridges/telegram.ts +5 -2
  29. package/src/chat-shim.ts +26 -17
  30. package/src/core/media.ts +5 -2
  31. package/src/dashboard/tokens.css +1 -1
  32. package/src/extensions/image-builder.ts +2 -2
  33. package/src/main.ts +2 -2
  34. package/examples/extensions/pdf/skill/LICENSE.txt +0 -30
@@ -1,294 +1,353 @@
1
- **CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.**
1
+ # PDF Form Filling Guide
2
2
 
3
- If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory:
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
- # Fillable fields
7
- If the PDF has fillable form fields:
8
- - Run this script from this file's directory: `python scripts/extract_form_field_info.py <input.pdf> <field_info.json>`. It will create a JSON file with a list of fields in this format:
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
- "field_id": (unique ID for the field),
13
- "page": (page number, 1-based),
14
- "rect": ([left, bottom, right, top] bounding box in PDF coordinates, y=0 is the bottom of the page),
15
- "type": ("text", "checkbox", "radio_group", or "choice"),
16
- },
17
- // Checkboxes have "checked_value" and "unchecked_value" properties:
18
- {
19
- "field_id": (unique ID for the field),
20
- "page": (page number, 1-based),
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
- - Convert the PDF to PNGs (one image for each page) with this script (run from this file's directory):
54
- `python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
55
- Then analyze the images to determine the purpose of each form field (make sure to convert the bounding box PDF coordinates to image coordinates).
56
- - Create a `field_values.json` file in this format with the values to be entered for each field:
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
- "field_id": "last_name", // Must match the field_id from `extract_form_field_info.py`
61
- "description": "The user's last name",
62
- "page": 1, // Must match the "page" value in field_info.json
63
- "value": "Simpson"
64
- },
65
- {
66
- "field_id": "Checkbox12",
67
- "description": "Checkbox to be checked if the user is 18 or over",
68
- "page": 1,
69
- "value": "/On" // If this is a checkbox, use its "checked_value" value to check it. If it's a radio button group, use one of the "value" values in "radio_options".
70
- },
71
- // more fields
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
- # Non-fillable fields
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
- ## Step 1: Try Structure Extraction First
98
+ ```bash
99
+ python3 scripts/fill_fillable_fields.py input.pdf values.json -o outbox/filled.pdf
100
+ ```
82
101
 
83
- Run this script to extract text labels, lines, and checkboxes with their exact PDF coordinates:
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
- This creates a JSON file containing:
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
- **Check the results**: If `form_structure.json` has meaningful labels (text elements that correspond to form fields), use **Approach A: Structure-Based Coordinates**. If the PDF is scanned/image-based and has few or no labels, use **Approach B: Visual Estimation**.
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
- ## Approach A: Structure-Based Coordinates (Preferred)
112
+ ### Inline code (without scripts)
97
113
 
98
- Use this when `extract_form_structure.py` found text labels in the PDF.
114
+ ```python
115
+ from pypdf import PdfReader, PdfWriter
99
116
 
100
- ### A.1: Analyze the Structure
117
+ reader = PdfReader("form.pdf")
118
+ writer = PdfWriter()
119
+ writer.append(reader)
101
120
 
102
- Read form_structure.json and identify:
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
- 1. **Label groups**: Adjacent text elements that form a single label (e.g., "Last" + "Name")
105
- 2. **Row structure**: Labels with similar `top` values are in the same row
106
- 3. **Field columns**: Entry areas start after label ends (x0 = label.x1 + gap)
107
- 4. **Checkboxes**: Use the checkbox coordinates directly from the structure
132
+ writer.write("outbox/filled.pdf")
133
+ writer.close()
134
+ ```
108
135
 
109
- **Coordinate system**: PDF coordinates where y=0 is at TOP of page, y increases downward.
136
+ **Important**: Always set `auto_regenerate=False`. The default `True` can corrupt field appearances in some PDFs.
110
137
 
111
- ### A.2: Check for Missing Elements
138
+ ### Handling checkboxes
112
139
 
113
- The structure extraction may not detect all form elements. Common cases:
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
- If you see form fields in the PDF images that aren't in form_structure.json, you'll need to use **visual analysis** for those specific fields (see "Hybrid Approach" below).
142
+ ```python
143
+ from pypdf import PdfReader
119
144
 
120
- ### A.3: Create fields.json with PDF Coordinates
145
+ reader = PdfReader("form.pdf")
146
+ fields = reader.get_fields()
121
147
 
122
- For each field, calculate entry coordinates from the extracted structure:
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
- **Text fields:**
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
- **Checkboxes:**
131
- - Use the checkbox rectangle coordinates directly from form_structure.json
132
- - entry_bounding_box = [checkbox.x0, checkbox.top, checkbox.x1, checkbox.bottom]
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
- Create fields.json using `pdf_width` and `pdf_height` (signals PDF coordinates):
135
- ```json
136
- {
137
- "pages": [
138
- {"page_number": 1, "pdf_width": 612, "pdf_height": 792}
139
- ],
140
- "form_fields": [
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
- **Important**: Use `pdf_width`/`pdf_height` and coordinates directly from form_structure.json.
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
- ### A.4: Validate Bounding Boxes
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
- Before filling, check your bounding boxes for errors:
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
- This checks for intersecting bounding boxes and entry boxes that are too small for the font size. Fix any reported errors before filling.
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
- ## Approach B: Visual Estimation (Fallback)
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
- Use this when the PDF is scanned/image-based and structure extraction found no usable text labels (e.g., all text shows as "(cid:X)" patterns).
206
+ ### Step 2: Extract form structure (heuristic)
175
207
 
176
- ### B.1: Convert PDF to Images
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
- `python scripts/convert_pdf_to_images.py <input.pdf> <images_dir/>`
214
+ The output includes both pdfplumber coordinates (top-left origin) and PDF coordinates (bottom-left origin) for each detected region.
179
215
 
180
- ### B.2: Initial Field Identification
216
+ ### Step 3: Define fill positions
181
217
 
182
- Examine each page image to identify form sections and get **rough estimates** of field locations:
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
- For each field, note approximate pixel coordinates (they don't need to be precise yet).
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
- ### B.3: Zoom Refinement (CRITICAL for accuracy)
242
+ Coordinates are in PDF points (1 point = 1/72 inch). Origin is bottom-left of the page.
190
243
 
191
- For each field, crop a region around the estimated position to refine coordinates precisely.
244
+ ### Step 4: Fill using overlay (default method)
192
245
 
193
- **Create a zoomed crop using ImageMagick:**
194
246
  ```bash
195
- magick <page_image> -crop <width>x<height>+<x>+<y> +repage <crop_output.png>
247
+ python3 scripts/fill_pdf_form_with_annotations.py input.pdf fill_spec.json -o outbox/filled.pdf
196
248
  ```
197
249
 
198
- Where:
199
- - `<x>, <y>` = top-left corner of crop region (use your rough estimate minus padding)
200
- - `<width>, <height>` = size of crop region (field area plus ~50px padding on each side)
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
- magick images_dir/page_1.png -crop 300x80+50+120 +repage crops/name_field.png
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
- (Note: if the `magick` command isn't available, try `convert` with the same arguments).
258
+ ### Alternative: manual overlay with reportlab
208
259
 
209
- **Examine the cropped image** to determine precise coordinates:
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
- **Convert crop coordinates back to full image coordinates:**
215
- - full_x = crop_x + crop_offset_x
216
- - full_y = crop_y + crop_offset_y
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
- Example: If the crop started at (50, 120) and the entry box starts at (52, 18) within the crop:
219
- - entry_x0 = 52 + 50 = 102
220
- - entry_top = 18 + 120 = 138
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
- **Repeat for each field**, grouping nearby fields into single crops when possible.
277
+ # Merge overlay onto original
278
+ original = PdfReader("input.pdf")
279
+ overlay = PdfReader(packet)
223
280
 
224
- ### B.4: Create fields.json with Refined Coordinates
281
+ writer = PdfWriter()
282
+ page = original.pages[0]
283
+ page.merge_page(overlay.pages[0])
284
+ writer.add_page(page)
225
285
 
226
- Create fields.json using `image_width` and `image_height` (signals image coordinates):
227
- ```json
228
- {
229
- "pages": [
230
- {"page_number": 1, "image_width": 1700, "image_height": 2200}
231
- ],
232
- "form_fields": [
233
- {
234
- "page_number": 1,
235
- "description": "Last name entry field",
236
- "field_label": "Last Name",
237
- "label_bounding_box": [120, 175, 242, 198],
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
- **Important**: Use `image_width`/`image_height` and the refined pixel coordinates from the zoom analysis.
300
+ Always visually inspect the result. Coordinates often need fine-tuning.
246
301
 
247
- ### B.5: Validate Bounding Boxes
302
+ ### Common pitfalls with non-fillable forms
248
303
 
249
- Before filling, check your bounding boxes for errors:
250
- `python scripts/check_bounding_boxes.py fields.json`
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
- This checks for intersecting bounding boxes and entry boxes that are too small for the font size. Fix any reported errors before filling.
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
- ## Hybrid Approach: Structure + Visual
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
- Use this when structure extraction works for most fields but misses some elements (e.g., circular checkboxes, unusual form controls).
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
- 1. **Use Approach A** for fields that were detected in form_structure.json
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
- ## Step 2: Validate Before Filling
327
+ ## Troubleshooting
271
328
 
272
- **Always validate bounding boxes before filling:**
273
- `python scripts/check_bounding_boxes.py fields.json`
329
+ ### Field values not visible after filling
274
330
 
275
- This checks for:
276
- - Intersecting bounding boxes (which would cause overlapping text)
277
- - Entry boxes that are too small for the specified font size
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
- Fix any reported errors in fields.json before proceeding.
335
+ ### Wrong font or encoding in filled fields
280
336
 
281
- ## Step 3: Fill the Form
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
- The fill script auto-detects the coordinate system and handles conversion:
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
- ## Step 4: Verify Output
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
- Convert the filled PDF to images and verify text placement:
289
- `python scripts/convert_pdf_to_images.py <output.pdf> <verify_images/>`
350
+ ### Large PDFs are slow
290
351
 
291
- If text is mispositioned:
292
- - **Approach A**: Check that you're using PDF coordinates from form_structure.json with `pdf_width`/`pdf_height`
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.