Claude Code struggles with PDF form filling: comb digit boxes, font sizing, rect alignment, and duplicate-name fields
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
When asked to programmatically fill a PDF form template (an Italian energy contract - Fastweb Preventivo) using PyMuPDF, Claude Code went through 13+ iterations without producing a fully correct result. The task involved filling comb/digit box fields, text fields, checkboxes, and handling duplicate-name fields.
Core Issues Encountered
1. Comb/Digit Box Font Sizing
Claude repeatedly failed to calculate the correct font size for comb fields (one character per cell). Characters were either:
- Too large, causing clipping/truncation at cell boundaries
- Set to
fontsize=0(auto), which let PyMuPDF choose a size that didn't fit the cell width
Multiple iterations were needed to arrive at cell_w * 0.8 as the correct multiplier.
2. Rect Extension Logic
The template's digit box fields had MaxLen values smaller than the actual visual grid cells. Claude needed to extend the field Rect to cover the full visual grid. Issues:
- Coordinate system confusion: In v10, Claude wrote PyMuPDF coordinates (top-left origin) directly to the PDF xref
Rectkey (which uses bottom-left origin), causing ALL digit fields to disappear from the page - Over-extension: Extending left caused overlap with adjacent fields (e.g., CAP overlapping civico field). Extending right caused digits to appear outside the visual grid boundary
- Required multiple iterations to identify which fields needed right-only extension vs. no extension at all
3. Duplicate-Name Field Handling (Most Complex)
Two fields (Testo28 for dates, Prov for province/ATECO) had duplicate names in the PDF template. This means they share the same V (value) key. The last widget.update() overwrites the V for ALL widgets with the same name.
Claude tried 5 different approaches before finding one that worked:
rewrite_widget_ap()+NeedAppearances=false— macOS Preview ignores the flag and regenerates AP from shared Vpage.insert_text()directly on page — form field annotations render ON TOP of page content, covering the drawn textpage.add_freetext_annot()— FreeText overlay showed correct text but the form field text bled through underneathpage.add_redact_annot()+page.apply_redactions()— redactions remove page content but not widget annotations- Writing correct text directly into the widget's AP stream with white background fill — this finally worked
4. Text Vertical Alignment
Text in form fields was floating above the baseline. Required custom AP streams with explicit y_offset positioning.
5. Iterative Trial-and-Error
Despite having PDF specification knowledge, Claude Code could not predict viewer behavior (macOS Preview vs. Adobe Acrobat), made the same category of mistakes multiple times, and required user screenshots after each iteration.
Environment
- OS: macOS Tahoe 26.3.1 (25D2128)
- Python: 3.9
- PyMuPDF (fitz): latest via pip
- PDF Viewer: macOS Preview
- Claude Code model: claude-opus-4-6
Impact
The user ultimately abandoned the automated approach and filled the form by hand after 13+ failed iterations.
What Should Happen?
Claude Code should be able to fill a PDF form with comb/digit box fields in 2-3 iterations maximum, not 13+. Specifically:
- Know PDF coordinate systems upfront — never mix PyMuPDF top-left coords with PDF bottom-left coords when writing to xref
- Calculate comb font size correctly on first attempt — cell_width / maxlen is the basic formula, font should be ~80% of cell width
- Understand duplicate-name field behavior — recognize that shared V requires AP stream manipulation, and that macOS Preview ignores NeedAppearances=false
- Predict viewer rendering behavior — know that annotations render above page content, so page.insert_text() cannot override a form field widget
- Validate output programmatically — render the filled PDF with PyMuPDF and check field content before presenting to user, rather than relying on user screenshots for each iteration
Error Messages/Logs
## Key technical mistakes made by Claude Code across 13+ iterations:
### v8→v9: widget.update() used stale Python object properties
- Set MaxLen and Ff flags via xref AFTER widget.update()
- But widget.update() generates AP from Python object properties, not xref
- Fix: set widget.text_maxlen and widget.field_flags BEFORE widget.update()
### v10: Coordinate system confusion (CRITICAL)
- doc.xref_set_key(widget.xref, "Rect", f"[{x0} {y0} {x1} {y1}]")
- Wrote PyMuPDF coords (top-left origin) to PDF Rect (bottom-left origin)
- ALL digit fields disappeared from the page
- Fix: NEVER write Rect via xref — let widget.update() handle conversion
### v10→v13: Duplicate-name fields — 5 failed approaches
# Approach 1: rewrite_widget_ap + NeedAppearances=false
# Result: macOS Preview ignores NeedAppearances, shows shared V value
# Approach 2: page.insert_text() on page content
# Result: widget annotation renders ON TOP, covering drawn text
# Approach 3: page.add_freetext_annot() overlay
# Result: both FreeText and widget visible, text overlaps
# Approach 4: page.add_redact_annot() + apply_redactions()
# Result: redaction removes page content but NOT widget annotations
# Approach 5 (final): write text into widget AP stream with white fill
stream = "/Tx BMC\nq\n1 1 1 rg\n0 0 W H re\nf\nBT\n/Helv fs Tf\n0 0 0 rg\n2 2 Td\n(text) Tj\nET\nQ\nEMC"
# Result: works — AP stream controls widget display, white fill covers old content
### Font sizing iterations
# v10: fontsize=0 (auto) — too large for narrow comb cells, chars clipped
# v11: max_fs capped at 9 — still too large for some fields
# v12: max_fs capped at 7 — better but inconsistent
# v13: cell_w * 1.2 — still clipping
# v13 final: cell_w * 0.8 — finally fits all cells
### Rect extension iterations
# v9: extend left by 1 cell + right by (extra-1) — CAP overlaps civico field
# v11: right_only for CAP — digits exit visual grid boundary
# v13: NO extension for CAP/prefix — fits within original rect with smaller font
Steps to Reproduce
- Have an editable PDF form template with:
- Comb/digit box fields (MaxLen + Comb flag) where the template MaxLen doesn't match visual cell count
- Text fields on dotted baselines
- Duplicate-name fields that need different values (e.g., two fields named "Testo28" for different dates)
- Checkboxes
- IBAN field with 27-char comb
- Ask Claude Code: "Fill this PDF form with the following data using PyMuPDF" and provide field names + values
- Observe Claude Code iterating through 13+ script versions:
- v1-v7: basic form filling, font/alignment issues
- v8-v9: MaxLen/comb flag ordering bug
- v10: coordinate system bug (fields disappear)
- v10b: rect extension causing overflow
- v11-v12: font size too large, text overflows
- v13a-v13f: duplicate field handling (5 failed approaches)
- Each iteration requires user to open PDF in macOS Preview, screenshot problem areas, and describe issues back to Claude
- After 13+ iterations, several fields still not perfect (CAP digits partially outside grid, some fonts still clipped)
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
macOS Tahoe 26.3.1
Claude Code Version
claude-opus-4-6 (latest)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
macOS Version: Tahoe 26.3.1 (25D2128)
Suggestions for improvement:
- Claude should understand PDF coordinate systems (top-left for PyMuPDF vs bottom-left for raw PDF xref) and never mix them
- Claude should recognize duplicate-name PDF fields early and handle shared V values from the start
- When dealing with comb digit boxes, Claude should calculate font size from cell width (cell_w = rect_width / maxlen) rather than guessing
- Claude should test AP stream rendering behavior differences between macOS Preview and other PDF viewers
- Claude should not require 13+ iterations to solve well-documented PDF form filling patterns
Final script version: v13 (genera_fastweb_pdf.py) — user abandoned automated approach due to accumulated errors and filled the form by hand.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗