dimcode-darwin-x64 0.1.2-beta.1 → 0.1.2

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 (148) hide show
  1. package/bin/dimcode +0 -0
  2. package/package.json +1 -1
  3. package/bin/runtime/sandbox/dim-sandbox-runner +0 -0
  4. package/bin/runtime/sandbox/manifest.json +0 -15
  5. package/bin/skills-assets/deep-investigate/SKILL.md +0 -101
  6. package/bin/skills-assets/deep-investigate/references/prompts.md +0 -75
  7. package/bin/skills-assets/deep-investigate/references/templates.md +0 -73
  8. package/bin/skills-assets/deep-investigate/references/thinking-tools.md +0 -36
  9. package/bin/skills-assets/docs-sprint/SKILL.md +0 -73
  10. package/bin/skills-assets/docs-sprint/agents/openai.yaml +0 -4
  11. package/bin/skills-assets/docs-sprint/references/contract-discipline.md +0 -30
  12. package/bin/skills-assets/docs-sprint/references/delivery-plan.md +0 -162
  13. package/bin/skills-assets/docs-sprint/references/documentation-system.md +0 -109
  14. package/bin/skills-assets/docs-sprint/references/ui-layout.md +0 -73
  15. package/bin/skills-assets/docs-sprint/references/worktree-guide.md +0 -45
  16. package/bin/skills-assets/docx/SKILL.md +0 -273
  17. package/bin/skills-assets/docx/assets/styles/academic_styles.xml +0 -250
  18. package/bin/skills-assets/docx/assets/styles/corporate_styles.xml +0 -284
  19. package/bin/skills-assets/docx/assets/styles/default_styles.xml +0 -449
  20. package/bin/skills-assets/docx/assets/xsd/aesthetic-rules.xsd +0 -470
  21. package/bin/skills-assets/docx/assets/xsd/business-rules.xsd +0 -130
  22. package/bin/skills-assets/docx/assets/xsd/common-types.xsd +0 -159
  23. package/bin/skills-assets/docx/assets/xsd/wml-subset.xsd +0 -589
  24. package/bin/skills-assets/docx/references/cjk_typography.md +0 -357
  25. package/bin/skills-assets/docx/references/cjk_university_template_guide.md +0 -184
  26. package/bin/skills-assets/docx/references/comments_guide.md +0 -191
  27. package/bin/skills-assets/docx/references/design_good_bad_examples.md +0 -829
  28. package/bin/skills-assets/docx/references/design_principles.md +0 -819
  29. package/bin/skills-assets/docx/references/openxml_element_order.md +0 -308
  30. package/bin/skills-assets/docx/references/openxml_encyclopedia_part1.md +0 -4061
  31. package/bin/skills-assets/docx/references/openxml_encyclopedia_part2.md +0 -2820
  32. package/bin/skills-assets/docx/references/openxml_encyclopedia_part3.md +0 -3381
  33. package/bin/skills-assets/docx/references/openxml_namespaces.md +0 -82
  34. package/bin/skills-assets/docx/references/openxml_units.md +0 -72
  35. package/bin/skills-assets/docx/references/scenario_a_create.md +0 -284
  36. package/bin/skills-assets/docx/references/scenario_b_edit_content.md +0 -295
  37. package/bin/skills-assets/docx/references/scenario_c_apply_template.md +0 -456
  38. package/bin/skills-assets/docx/references/track_changes_guide.md +0 -200
  39. package/bin/skills-assets/docx/references/troubleshooting.md +0 -506
  40. package/bin/skills-assets/docx/references/typography_guide.md +0 -294
  41. package/bin/skills-assets/docx/references/xsd_validation_guide.md +0 -158
  42. package/bin/skills-assets/docx/scripts/doc_to_docx.sh +0 -40
  43. package/bin/skills-assets/docx/scripts/docx_preview.sh +0 -37
  44. package/bin/skills-assets/docx/scripts/dotnet/Docx.Cli/Docx.Cli.csproj +0 -19
  45. package/bin/skills-assets/docx/scripts/dotnet/Docx.Cli/Program.cs +0 -18
  46. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/AnalyzeCommand.cs +0 -147
  47. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/ApplyTemplateCommand.cs +0 -322
  48. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/CreateCommand.cs +0 -324
  49. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/DiffCommand.cs +0 -155
  50. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/EditContentCommand.cs +0 -487
  51. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/FixOrderCommand.cs +0 -108
  52. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/MergeRunsCommand.cs +0 -122
  53. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Commands/ValidateCommand.cs +0 -107
  54. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Docx.Core.csproj +0 -15
  55. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/CommentSynchronizer.cs +0 -169
  56. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/ElementOrder.cs +0 -80
  57. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/NamespaceConstants.cs +0 -42
  58. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/RunMerger.cs +0 -81
  59. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/StyleAnalyzer.cs +0 -81
  60. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/TrackChangesHelper.cs +0 -99
  61. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/OpenXml/UnitConverter.cs +0 -23
  62. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples.cs +0 -1832
  63. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch1.cs +0 -910
  64. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch2.cs +0 -999
  65. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch3.cs +0 -1048
  66. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/AestheticRecipeSamples_Batch4.cs +0 -1038
  67. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/CharacterFormattingSamples.cs +0 -1020
  68. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/DocumentCreationSamples.cs +0 -1121
  69. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/FieldAndTocSamples.cs +0 -624
  70. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/FootnoteAndCommentSamples.cs +0 -675
  71. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/HeaderFooterSamples.cs +0 -838
  72. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ImageSamples.cs +0 -917
  73. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ListAndNumberingSamples.cs +0 -826
  74. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/ParagraphFormattingSamples.cs +0 -1199
  75. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/StyleSystemSamples.cs +0 -1487
  76. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/TableSamples.cs +0 -1163
  77. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Samples/TrackChangesSamples.cs +0 -595
  78. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/CjkHelper.cs +0 -39
  79. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/FontDefaults.cs +0 -24
  80. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Typography/PageSizes.cs +0 -20
  81. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/BusinessRuleValidator.cs +0 -224
  82. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/GateCheckValidator.cs +0 -148
  83. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/ValidationResult.cs +0 -23
  84. package/bin/skills-assets/docx/scripts/dotnet/Docx.Core/Validation/XsdValidator.cs +0 -69
  85. package/bin/skills-assets/docx/scripts/dotnet/Docx.slnx +0 -4
  86. package/bin/skills-assets/docx/scripts/env_check.sh +0 -196
  87. package/bin/skills-assets/docx/scripts/setup.ps1 +0 -274
  88. package/bin/skills-assets/docx/scripts/setup.sh +0 -504
  89. package/bin/skills-assets/pdf/README.md +0 -222
  90. package/bin/skills-assets/pdf/SKILL.md +0 -191
  91. package/bin/skills-assets/pdf/design/design.md +0 -381
  92. package/bin/skills-assets/pdf/scripts/cover.py +0 -1579
  93. package/bin/skills-assets/pdf/scripts/fill_inspect.py +0 -200
  94. package/bin/skills-assets/pdf/scripts/fill_write.py +0 -242
  95. package/bin/skills-assets/pdf/scripts/make.sh +0 -491
  96. package/bin/skills-assets/pdf/scripts/merge.py +0 -112
  97. package/bin/skills-assets/pdf/scripts/palette.py +0 -521
  98. package/bin/skills-assets/pdf/scripts/reformat_parse.py +0 -374
  99. package/bin/skills-assets/pdf/scripts/render_body.py +0 -1052
  100. package/bin/skills-assets/pdf/scripts/render_cover.js +0 -111
  101. package/bin/skills-assets/pptx-generator/SKILL.md +0 -248
  102. package/bin/skills-assets/pptx-generator/references/design-system.md +0 -392
  103. package/bin/skills-assets/pptx-generator/references/editing.md +0 -162
  104. package/bin/skills-assets/pptx-generator/references/pitfalls.md +0 -112
  105. package/bin/skills-assets/pptx-generator/references/pptxgenjs.md +0 -420
  106. package/bin/skills-assets/pptx-generator/references/slide-types.md +0 -413
  107. package/bin/skills-assets/skill-creator/SKILL.md +0 -368
  108. package/bin/skills-assets/skill-creator/agents/openai.yaml +0 -5
  109. package/bin/skills-assets/skill-creator/assets/skill-creator-small.svg +0 -3
  110. package/bin/skills-assets/skill-creator/assets/skill-creator.png +0 -0
  111. package/bin/skills-assets/skill-creator/license.txt +0 -202
  112. package/bin/skills-assets/skill-creator/references/openai_yaml.md +0 -49
  113. package/bin/skills-assets/skill-creator/scripts/generate_openai_yaml.py +0 -226
  114. package/bin/skills-assets/skill-creator/scripts/init_skill.py +0 -397
  115. package/bin/skills-assets/skill-creator/scripts/quick_validate.py +0 -101
  116. package/bin/skills-assets/skill-installer/LICENSE.txt +0 -202
  117. package/bin/skills-assets/skill-installer/SKILL.md +0 -58
  118. package/bin/skills-assets/skill-installer/agents/openai.yaml +0 -5
  119. package/bin/skills-assets/skill-installer/assets/skill-installer-small.svg +0 -3
  120. package/bin/skills-assets/skill-installer/assets/skill-installer.png +0 -0
  121. package/bin/skills-assets/skill-installer/scripts/github_utils.py +0 -21
  122. package/bin/skills-assets/skill-installer/scripts/install-skill-from-github.py +0 -308
  123. package/bin/skills-assets/skill-installer/scripts/list-skills.py +0 -107
  124. package/bin/skills-assets/xlsx/SKILL.md +0 -137
  125. package/bin/skills-assets/xlsx/references/create.md +0 -691
  126. package/bin/skills-assets/xlsx/references/edit.md +0 -684
  127. package/bin/skills-assets/xlsx/references/fix.md +0 -37
  128. package/bin/skills-assets/xlsx/references/format.md +0 -768
  129. package/bin/skills-assets/xlsx/references/ooxml-cheatsheet.md +0 -231
  130. package/bin/skills-assets/xlsx/references/read-analyze.md +0 -97
  131. package/bin/skills-assets/xlsx/references/validate.md +0 -772
  132. package/bin/skills-assets/xlsx/scripts/formula_check.py +0 -422
  133. package/bin/skills-assets/xlsx/scripts/libreoffice_recalc.py +0 -248
  134. package/bin/skills-assets/xlsx/scripts/shared_strings_builder.py +0 -163
  135. package/bin/skills-assets/xlsx/scripts/style_audit.py +0 -575
  136. package/bin/skills-assets/xlsx/scripts/xlsx_add_column.py +0 -395
  137. package/bin/skills-assets/xlsx/scripts/xlsx_insert_row.py +0 -274
  138. package/bin/skills-assets/xlsx/scripts/xlsx_pack.py +0 -87
  139. package/bin/skills-assets/xlsx/scripts/xlsx_reader.py +0 -362
  140. package/bin/skills-assets/xlsx/scripts/xlsx_shift_rows.py +0 -396
  141. package/bin/skills-assets/xlsx/scripts/xlsx_unpack.py +0 -130
  142. package/bin/skills-assets/xlsx/templates/minimal_xlsx/[Content_Types].xml +0 -9
  143. package/bin/skills-assets/xlsx/templates/minimal_xlsx/_rels/.rels +0 -6
  144. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +0 -19
  145. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +0 -33
  146. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/styles.xml +0 -160
  147. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/workbook.xml +0 -30
  148. package/bin/skills-assets/xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +0 -70
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- fill_inspect.py — Inspect form fields in an existing PDF.
4
-
5
- Usage:
6
- python3 fill_inspect.py --input form.pdf
7
- python3 fill_inspect.py --input form.pdf --out fields.json
8
-
9
- Outputs a JSON summary of every fillable field: name, type, current value,
10
- allowed values (for checkboxes / dropdowns), and page number.
11
-
12
- Exit codes: 0 success, 1 bad args / file not found, 2 dep missing, 3 read error
13
- """
14
-
15
- import argparse
16
- import json
17
- import sys
18
- import importlib.util
19
- import os
20
-
21
-
22
-
23
-
24
- def ensure_deps():
25
- if importlib.util.find_spec("pypdf") is None:
26
- import subprocess
27
- subprocess.check_call(
28
- [sys.executable, "-m", "pip", "install", "--break-system-packages", "-q", "pypdf"]
29
- )
30
-
31
-
32
- ensure_deps()
33
- from pypdf import PdfReader
34
- from pypdf.generic import ArrayObject, DictionaryObject, NameObject, TextStringObject
35
-
36
-
37
- # ── Field type resolution ──────────────────────────────────────────────────────
38
- def _field_type(field) -> str:
39
- ft = field.get("/FT")
40
- if ft is None:
41
- return "unknown"
42
- ft = str(ft)
43
- if ft == "/Tx":
44
- return "text"
45
- if ft == "/Btn":
46
- ff = int(field.get("/Ff", 0))
47
- return "radio" if ff & (1 << 15) else "checkbox"
48
- if ft == "/Ch":
49
- ff = int(field.get("/Ff", 0))
50
- return "dropdown" if ff & (1 << 17) else "listbox"
51
- if ft == "/Sig":
52
- return "signature"
53
- return "unknown"
54
-
55
-
56
- def _field_value(field) -> str | None:
57
- v = field.get("/V")
58
- return str(v) if v is not None else None
59
-
60
-
61
- def _field_options(field, ftype: str) -> dict:
62
- extra = {}
63
- if ftype in ("checkbox",):
64
- ap = field.get("/AP")
65
- if ap and "/N" in ap:
66
- states = [str(k) for k in ap["/N"]]
67
- extra["states"] = states
68
- checked = next((s for s in states if s != "/Off"), None)
69
- if checked:
70
- extra["checked_value"] = checked
71
- if ftype in ("dropdown", "listbox"):
72
- opt = field.get("/Opt")
73
- if opt:
74
- choices = []
75
- for item in opt:
76
- if isinstance(item, (list, ArrayObject)) and len(item) >= 2:
77
- choices.append({"value": str(item[0]), "label": str(item[1])})
78
- else:
79
- choices.append({"value": str(item), "label": str(item)})
80
- extra["choices"] = choices
81
- if ftype == "radio":
82
- kids = field.get("/Kids")
83
- if kids:
84
- values = []
85
- for kid in kids:
86
- ap = kid.get("/AP")
87
- if ap and "/N" in ap:
88
- for k in ap["/N"]:
89
- if str(k) != "/Off":
90
- values.append(str(k))
91
- extra["radio_values"] = values
92
- return extra
93
-
94
-
95
- def _walk_fields(fields, page_map: dict, parent_name: str = "") -> list:
96
- """Recursively collect all leaf fields."""
97
- result = []
98
- for field in fields:
99
- name = str(field.get("/T", ""))
100
- full = f"{parent_name}.{name}" if parent_name else name
101
-
102
- kids = field.get("/Kids")
103
- # Kids that have /T are sub-fields (groups), not widget annotations
104
- if kids:
105
- named_kids = [k for k in kids if "/T" in k]
106
- if named_kids:
107
- result.extend(_walk_fields(named_kids, page_map, full))
108
- continue
109
-
110
- ftype = _field_type(field)
111
- if ftype == "unknown":
112
- continue
113
-
114
- entry = {
115
- "name": full,
116
- "type": ftype,
117
- "value": _field_value(field),
118
- }
119
- entry.update(_field_options(field, ftype))
120
-
121
- # Page lookup via /P indirect reference
122
- p_ref = field.get("/P")
123
- if p_ref and hasattr(p_ref, "idnum"):
124
- entry["page"] = page_map.get(p_ref.idnum, "?")
125
-
126
- result.append(entry)
127
- return result
128
-
129
-
130
- def inspect(pdf_path: str) -> dict:
131
- try:
132
- reader = PdfReader(pdf_path)
133
- except Exception as e:
134
- return {"status": "error", "error": str(e)}
135
-
136
- # Build page-number lookup: {object_id: 1-based page number}
137
- page_map = {}
138
- for i, page in enumerate(reader.pages):
139
- if hasattr(page, "indirect_reference") and page.indirect_reference:
140
- page_map[page.indirect_reference.idnum] = i + 1
141
-
142
- acroform = reader.trailer.get("/Root", {}).get("/AcroForm")
143
- if acroform is None or "/Fields" not in acroform:
144
- return {
145
- "status": "ok",
146
- "has_fields": False,
147
- "field_count": 0,
148
- "fields": [],
149
- "note": "This PDF has no fillable form fields.",
150
- }
151
-
152
- fields = _walk_fields(list(acroform["/Fields"]), page_map)
153
-
154
- return {
155
- "status": "ok",
156
- "has_fields": bool(fields),
157
- "field_count": len(fields),
158
- "fields": fields,
159
- }
160
-
161
-
162
- def main():
163
- parser = argparse.ArgumentParser(description="Inspect PDF form fields")
164
- parser.add_argument("--input", required=True, help="PDF file to inspect")
165
- parser.add_argument("--out", default="", help="Write JSON to file (optional)")
166
- args = parser.parse_args()
167
-
168
- if not os.path.exists(args.input):
169
- print(json.dumps({"status": "error", "error": f"File not found: {args.input}"}),
170
- file=sys.stderr)
171
- sys.exit(1)
172
-
173
- result = inspect(args.input)
174
-
175
- output = json.dumps(result, indent=2, ensure_ascii=False)
176
-
177
- if args.out:
178
- with open(args.out, "w") as f:
179
- f.write(output)
180
-
181
- print(output)
182
-
183
- # Human-readable summary
184
- if result["status"] == "ok" and result["has_fields"]:
185
- print(f"\n── Fields in {args.input} ──────────────────────────────",
186
- file=sys.stderr)
187
- for f in result["fields"]:
188
- pg = f" p.{f['page']}" if "page" in f else ""
189
- val = f" = {f['value']}" if f.get("value") else ""
190
- extra = ""
191
- if "choices" in f:
192
- extra = f" [{', '.join(c['value'] for c in f['choices'][:4])}{'…' if len(f['choices'])>4 else ''}]"
193
- elif "states" in f:
194
- extra = f" {f['states']}"
195
- print(f" {f['type']:12} {f['name']}{pg}{val}{extra}", file=sys.stderr)
196
- print("", file=sys.stderr)
197
-
198
-
199
- if __name__ == "__main__":
200
- main()
@@ -1,242 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- fill_write.py — Write values into PDF form fields.
4
-
5
- Usage:
6
- # From a JSON data file
7
- python3 fill_write.py --input form.pdf --data values.json --out filled.pdf
8
-
9
- # Inline JSON
10
- python3 fill_write.py --input form.pdf --out filled.pdf \
11
- --values '{"FirstName": "Jane", "Agree": "true"}'
12
-
13
- values format:
14
- {
15
- "FieldName": "text value", # text field
16
- "CheckBox1": "true", # checkbox (true / false)
17
- "Dropdown1": "OptionValue", # dropdown (must match an existing choice value)
18
- "Radio1": "/Choice2" # radio (must match a radio value)
19
- }
20
-
21
- Exit codes: 0 success, 1 bad args, 2 dep missing, 3 read/write error, 4 validation error
22
- """
23
-
24
- import argparse
25
- import json
26
- import os
27
- import sys
28
- import importlib.util
29
-
30
-
31
-
32
-
33
- def ensure_deps():
34
- if importlib.util.find_spec("pypdf") is None:
35
- import subprocess
36
- subprocess.check_call(
37
- [sys.executable, "-m", "pip", "install", "--break-system-packages", "-q", "pypdf"]
38
- )
39
-
40
-
41
- ensure_deps()
42
- from pypdf import PdfReader, PdfWriter
43
- from pypdf.generic import NameObject, TextStringObject, BooleanObject
44
-
45
-
46
- # ── Field helpers ─────────────────────────────────────────────────────────────
47
- def _field_type(field) -> str:
48
- ft = str(field.get("/FT", ""))
49
- if ft == "/Tx": return "text"
50
- if ft == "/Btn":
51
- ff = int(field.get("/Ff", 0))
52
- return "radio" if ff & (1 << 15) else "checkbox"
53
- if ft == "/Ch":
54
- ff = int(field.get("/Ff", 0))
55
- return "dropdown" if ff & (1 << 17) else "listbox"
56
- return "unknown"
57
-
58
-
59
- def _get_checkbox_on_value(field) -> str:
60
- """Return the /AP /N key that means 'checked' (anything except /Off)."""
61
- ap = field.get("/AP")
62
- if ap and "/N" in ap:
63
- for k in ap["/N"]:
64
- if str(k) != "/Off":
65
- return str(k)
66
- return "/Yes"
67
-
68
-
69
- def _get_dropdown_values(field) -> list[str]:
70
- opt = field.get("/Opt")
71
- if not opt:
72
- return []
73
- values = []
74
- for item in opt:
75
- try:
76
- from pypdf.generic import ArrayObject
77
- if isinstance(item, (list, ArrayObject)) and len(item) >= 1:
78
- values.append(str(item[0]))
79
- else:
80
- values.append(str(item))
81
- except Exception:
82
- values.append(str(item))
83
- return values
84
-
85
-
86
- # ── Walk + fill ───────────────────────────────────────────────────────────────
87
- def _walk_and_fill(fields, data: dict, filled: list, errors: list, parent: str = ""):
88
- for field in fields:
89
- name = str(field.get("/T", ""))
90
- full = f"{parent}.{name}" if parent else name
91
-
92
- # Recurse into named groups
93
- kids = field.get("/Kids")
94
- if kids:
95
- named = [k for k in kids if "/T" in k]
96
- if named:
97
- _walk_and_fill(named, data, filled, errors, full)
98
- continue
99
-
100
- if full not in data:
101
- continue
102
-
103
- value = data[full]
104
- ftype = _field_type(field)
105
-
106
- if ftype == "text":
107
- field.update({
108
- NameObject("/V"): TextStringObject(str(value)),
109
- NameObject("/DV"): TextStringObject(str(value)),
110
- })
111
- filled.append(full)
112
-
113
- elif ftype == "checkbox":
114
- truthy = str(value).lower() in ("true", "1", "yes", "on")
115
- on_val = _get_checkbox_on_value(field)
116
- pdf_val = on_val if truthy else "/Off"
117
- field.update({
118
- NameObject("/V"): NameObject(pdf_val),
119
- NameObject("/AS"): NameObject(pdf_val),
120
- })
121
- filled.append(full)
122
-
123
- elif ftype in ("dropdown", "listbox"):
124
- allowed = _get_dropdown_values(field)
125
- if allowed and str(value) not in allowed:
126
- errors.append({
127
- "field": full,
128
- "error": f"Value '{value}' not in allowed choices: {allowed}"
129
- })
130
- continue
131
- field.update({NameObject("/V"): TextStringObject(str(value))})
132
- filled.append(full)
133
-
134
- elif ftype == "radio":
135
- # Radio value must start with /
136
- pdf_val = str(value) if str(value).startswith("/") else f"/{value}"
137
- field.update({
138
- NameObject("/V"): NameObject(pdf_val),
139
- NameObject("/AS"): NameObject(pdf_val),
140
- })
141
- filled.append(full)
142
-
143
- else:
144
- errors.append({"field": full, "error": f"Unsupported field type: {ftype}"})
145
-
146
-
147
- def fill(pdf_path: str, out_path: str, data: dict) -> dict:
148
- try:
149
- reader = PdfReader(pdf_path)
150
- except Exception as e:
151
- return {"status": "error", "error": str(e)}
152
-
153
- writer = PdfWriter()
154
- writer.clone_document_from_reader(reader)
155
-
156
- acroform = writer._root_object.get("/AcroForm") # type: ignore[attr-defined]
157
- if acroform is None or "/Fields" not in acroform:
158
- return {
159
- "status": "error",
160
- "error": "This PDF has no fillable form fields.",
161
- "hint": "Run fill_inspect.py first to confirm the PDF has fields.",
162
- }
163
-
164
- # Enable appearance regeneration so viewers show the new values
165
- acroform.update({NameObject("/NeedAppearances"): BooleanObject(True)})
166
-
167
- filled: list[str] = []
168
- errors: list[dict] = []
169
- _walk_and_fill(list(acroform["/Fields"]), data, filled, errors)
170
-
171
- # Warn about requested fields that were never found
172
- not_found = [k for k in data if k not in filled and not any(e["field"] == k for e in errors)]
173
-
174
- try:
175
- os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
176
- with open(out_path, "wb") as f:
177
- writer.write(f)
178
- except Exception as e:
179
- return {"status": "error", "error": f"Write failed: {e}"}
180
-
181
- result = {
182
- "status": "ok",
183
- "out": out_path,
184
- "filled_count": len(filled),
185
- "filled_fields": filled,
186
- "size_kb": os.path.getsize(out_path) // 1024,
187
- }
188
- if errors:
189
- result["validation_errors"] = errors
190
- if not_found:
191
- result["not_found"] = not_found
192
- result["hint"] = "Run fill_inspect.py to see all available field names."
193
- return result
194
-
195
-
196
- def main():
197
- parser = argparse.ArgumentParser(description="Fill PDF form fields")
198
- parser.add_argument("--input", required=True, help="Input PDF with form fields")
199
- parser.add_argument("--out", required=True, help="Output PDF path")
200
- group = parser.add_mutually_exclusive_group(required=True)
201
- group.add_argument("--data", help="Path to JSON file with field values")
202
- group.add_argument("--values", help="Inline JSON string with field values")
203
- args = parser.parse_args()
204
-
205
- if not os.path.exists(args.input):
206
- print(json.dumps({"status": "error", "error": f"File not found: {args.input}"}),
207
- file=sys.stderr)
208
- sys.exit(1)
209
-
210
- # Load data
211
- try:
212
- if args.data:
213
- with open(args.data) as f:
214
- data = json.load(f)
215
- else:
216
- data = json.loads(args.values)
217
- except Exception as e:
218
- print(json.dumps({"status": "error", "error": f"JSON parse error: {e}"}),
219
- file=sys.stderr)
220
- sys.exit(1)
221
-
222
- result = fill(args.input, args.out, data)
223
- print(json.dumps(result, indent=2, ensure_ascii=False))
224
-
225
- if result["status"] == "ok":
226
- print(f"\n── Fill complete ───────────────────────────────────────",
227
- file=sys.stderr)
228
- print(f" Output : {result['out']}", file=sys.stderr)
229
- print(f" Filled : {result['filled_count']} field(s)", file=sys.stderr)
230
- if result.get("validation_errors"):
231
- print(f" Errors :", file=sys.stderr)
232
- for e in result["validation_errors"]:
233
- print(f" • {e['field']}: {e['error']}", file=sys.stderr)
234
- if result.get("not_found"):
235
- print(f" Not found: {result['not_found']}", file=sys.stderr)
236
- print("", file=sys.stderr)
237
- else:
238
- sys.exit(3)
239
-
240
-
241
- if __name__ == "__main__":
242
- main()