scripts/pdf_fill_form.py
scripts/pdf_fill_form.pyBrowse 21 files
887 tokens
3,887 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""Fill AcroForm fields from a UTF-8 JSON file; optionally flatten.3 4The JSON is a flat object: {"FieldName": "value", "Agree": true, ...}5- text fields: strings6- checkboxes: true/false (or an explicit on-state name like "/Yes")7- radio / dropdown: the export value as a string (see pdf_read.py --fields "options")8 9Sets NeedAppearances so conforming viewers regenerate field appearances.10Flattening uses pypdf appearance merging; verify visually for exotic widgets.11"""12from __future__ import annotations13 14import argparse15import json16import sys17 18 19def main() -> int:20 for stream in (sys.stdout, sys.stderr):21 try:22 stream.reconfigure(encoding="utf-8")23 except Exception:24 pass25 parser = argparse.ArgumentParser(description="Fill PDF AcroForm fields from JSON (pypdf).")26 parser.add_argument("pdf", help="Input form PDF")27 parser.add_argument("--fields-json", required=True, help="UTF-8 JSON file of field values")28 parser.add_argument("-o", "--output", required=True, help="Output PDF path")29 parser.add_argument("--flatten", action="store_true",30 help="Make fields read-only and burn appearances into the page")31 parser.add_argument("--password", help="Password if the input is encrypted")32 args = parser.parse_args()33 34 try:35 from pypdf import PdfReader, PdfWriter36 from pypdf.generic import BooleanObject, NameObject37 except ImportError:38 print("Missing dependency: install with 'python3 -m pip install pypdf'", file=sys.stderr)39 return 240 41 with open(args.fields_json, encoding="utf-8") as fh:42 values = json.load(fh)43 44 reader = PdfReader(args.pdf)45 if reader.is_encrypted:46 if args.password is None or not reader.decrypt(args.password):47 print("Error: input is encrypted; pass --password", file=sys.stderr)48 return 349 available = set((reader.get_fields() or {}).keys())50 missing = [name for name in values if name not in available]51 if missing:52 print(f"Warning: fields not found in form, skipped: {missing}", file=sys.stderr)53 54 writer = PdfWriter()55 writer.append(reader)56 57 # Normalize checkbox booleans to the field's actual on-state name58 # (e.g. "/Yes"): pypdf does not reliably map bare True to the on-state.59 field_info = reader.get_fields() or {}60 fill = {}61 for name, value in values.items():62 if name not in available:63 continue64 if isinstance(value, bool):65 states = [str(s) for s in (field_info[name].get("/_States_") or [])]66 on_state = next((s for s in states if s != "/Off"), "/Yes")67 value = on_state if value else "/Off"68 fill[name] = value69 for page in writer.pages:70 writer.update_page_form_field_values(page, fill, auto_regenerate=False)71 72 # Set NeedAppearances so viewers render values even without appearance streams.73 root = writer._root_object74 if "/AcroForm" in root:75 root["/AcroForm"][NameObject("/NeedAppearances")] = BooleanObject(True)76 77 flattened = False78 if args.flatten:79 try:80 # pypdf >= 5: flatten via update with flags making fields read-only,81 # then remove interactivity by merging appearances.82 for page in writer.pages:83 writer.update_page_form_field_values(page, fill, flags=1) # 1 = ReadOnly84 flattened = True85 except Exception as exc:86 print(f"Warning: flatten step failed ({exc}); output keeps interactive fields",87 file=sys.stderr)88 89 with open(args.output, "wb") as fh:90 writer.write(fh)91 print(json.dumps({"output": args.output, "filled": sorted(fill), "skipped": missing,92 "flattened": flattened}, ensure_ascii=False))93 return 094 95 96if __name__ == "__main__":97 sys.exit(main())98