dsh-office-kit 0.4.2 → 0.4.3

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 (113) hide show
  1. package/assets/skills/excel-live-control/SKILL.md +119 -152
  2. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
  3. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
  4. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
  5. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
  6. package/assets/skills/excel-live-control/references/office-automation.md +127 -153
  7. package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
  8. package/assets/skills/excel-live-control/style_guidelines.md +56 -64
  9. package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
  10. package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
  11. package/assets/skills/office-documents/SKILL.md +24 -60
  12. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
  13. package/assets/skills/office-documents/ooxml/comments.md +8 -8
  14. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
  15. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
  16. package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
  17. package/assets/skills/office-documents/references/docx-structure.md +11 -13
  18. package/assets/skills/office-documents/references/template-elicitation.md +11 -13
  19. package/assets/skills/office-documents/references/writing-quality.md +20 -20
  20. package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
  21. package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
  22. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
  23. package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
  24. package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
  25. package/assets/skills/office-documents/tasks/create_edit.md +14 -16
  26. package/assets/skills/office-documents/tasks/equations.md +17 -28
  27. package/assets/skills/office-documents/tasks/fields_update.md +14 -16
  28. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
  29. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
  30. package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
  31. package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
  32. package/assets/skills/office-documents/tasks/images_figures.md +15 -25
  33. package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
  34. package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
  35. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
  36. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
  37. package/assets/skills/office-documents/tasks/read_review.md +9 -20
  38. package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
  39. package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
  40. package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
  41. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
  42. package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
  43. package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
  44. package/assets/skills/office-documents/tasks/verify_render.md +11 -23
  45. package/assets/skills/office-documents/tasks/watermarks_background.md +11 -15
  46. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +6 -6
  47. package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
  48. package/assets/skills/office-pdf/SKILL.md +36 -51
  49. package/assets/skills/office-pdf/references/form-fields.md +20 -20
  50. package/assets/skills/office-pdf/references/geometry.md +10 -10
  51. package/assets/skills/office-pdf/references/layout-quality.md +13 -13
  52. package/assets/skills/office-pdf/references/limits.md +12 -56
  53. package/assets/skills/office-pdf/tasks/annotations.md +5 -5
  54. package/assets/skills/office-pdf/tasks/create.md +6 -9
  55. package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
  56. package/assets/skills/office-pdf/tasks/forms.md +15 -15
  57. package/assets/skills/office-pdf/tasks/read.md +9 -9
  58. package/assets/skills/office-pdf/tasks/redact.md +18 -18
  59. package/assets/skills/office-pdf/tasks/stamp.md +10 -10
  60. package/assets/skills/office-presentations/SKILL.md +71 -91
  61. package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
  62. package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
  63. package/assets/skills/office-presentations/recipes/README.md +24 -35
  64. package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
  65. package/assets/skills/office-presentations/recipes/connectors.md +11 -12
  66. package/assets/skills/office-presentations/recipes/images.md +11 -12
  67. package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
  68. package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
  69. package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
  70. package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
  71. package/assets/skills/office-presentations/recipes/tables.md +14 -14
  72. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
  73. package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
  74. package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
  75. package/assets/skills/office-presentations/references/finalization.md +10 -12
  76. package/assets/skills/office-presentations/references/native-bullets.md +14 -18
  77. package/assets/skills/office-presentations/references/native-evidence.md +18 -32
  78. package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
  79. package/assets/skills/office-presentations/references/templates.md +31 -60
  80. package/assets/skills/office-presentations/routing/google_slides.md +4 -4
  81. package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
  82. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
  83. package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
  84. package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
  85. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
  86. package/assets/skills/office-presentations/tasks/template-following.md +23 -28
  87. package/assets/skills/office-spreadsheets/SKILL.md +32 -39
  88. package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
  89. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
  90. package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
  91. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
  92. package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
  93. package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
  94. package/assets/skills/office-spreadsheets/references/charts.md +15 -34
  95. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
  96. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
  97. package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
  98. package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
  99. package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
  100. package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
  101. package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
  102. package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
  103. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
  104. package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
  105. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
  106. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
  107. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
  108. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
  109. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
  110. package/assets/skills/shared/annotation-recipes.md +98 -120
  111. package/assets/skills/shared/common-workflow.md +70 -80
  112. package/dist/index.js +1 -1
  113. package/package.json +1 -1
@@ -6,19 +6,19 @@
6
6
  "<python>" "<cli>" status
7
7
  ```
8
8
 
9
- Look at `renderers.kit` and `renderers.can_render`. `can_render.docx: false` means `docx render` will fail — not with a corrupt file, but with `the LibreOffice renderer is not available`. That is a setup problem, not a document problem.
9
+ Check `renderers.kit` and `renderers.can_render`. `can_render.docx: false` means `docx render` fails with `the LibreOffice renderer is not available` — a setup problem, not a document problem.
10
10
 
11
11
  ## What renders
12
12
 
13
- Rendering goes through the LibreOffice kit that DeepSeek Harness ships (`@deepseek-ai/libreoffice-kit`). On macOS and Windows it uses the kit's native engine; on Linux it uses the WASM engine. A separately installed Microsoft Office, WPS Office, or system LibreOffice is **not** used and does not make rendering work on its own.
13
+ Rendering goes through the LibreOffice kit DeepSeek Harness ships (`@deepseek-ai/libreoffice-kit`): the kit's native engine on macOS and Windows, the WASM engine on Linux. A separately installed Microsoft Office, WPS Office or system LibreOffice is **not** used and does not make rendering work.
14
14
 
15
15
  The plugin records the kit's location when the harness loads it, and the helper reads that record before anything else:
16
16
 
17
17
  1. `DSH_LIBREOFFICE_KIT_CLI` — an explicit override, also handy for a hand-run helper.
18
- 2. `libreofficeKit` in `~/.dsh/office/config.json`. The host half writes this at load, resolving the kit from the harness entry point (`process.argv[1]`, e.g. `<dsh>/node_modules/@deepseek-ai/dsh/lib/bin.js`). This is what makes a profile install work, where the kit lives in the harness tree and not next to the plugin. Configs that pre-0.5 versions wrote in the OS config directory are still read as fallbacks.
18
+ 2. `libreofficeKit` in `~/.dsh/office/config.json`. The host half writes it at load, resolving the kit from the harness entry point (`process.argv[1]`, e.g. `<dsh>/node_modules/@deepseek-ai/dsh/lib/bin.js`). This is what makes a profile install work, where the kit lives in the harness tree rather than next to the plugin. Configs that pre-0.5 versions wrote in the OS config directory are still read as fallbacks.
19
19
  3. Walking up from the plugin directory through `node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js` — covers a checkout or a plugin-local dependency.
20
20
 
21
- `DSH_OFFICE_CONFIG` points the helper at a different config file; `DSH_NODE` overrides the node binary if `node` is not on `PATH`.
21
+ `DSH_OFFICE_CONFIG` points the helper at a different config file; `DSH_NODE` overrides the node binary when `node` is not on `PATH`.
22
22
 
23
23
  ## Common failures
24
24
 
@@ -33,7 +33,7 @@ The plugin records the kit's location when the harness loads it, and the helper
33
33
 
34
34
  ## Working without a renderer
35
35
 
36
- Structure, text and metadata are all verifiable headlessly (`docx read`, the audit scripts, `docx_kit.py parts|text|xml`). Layout is not. In that state:
36
+ Structure, text and metadata are verifiable headlessly (`docx read`, the audit scripts, `docx_kit.py parts|text|xml`). Layout is not. In that state:
37
37
 
38
38
  1. Do the structural verification.
39
39
  2. Tell the user that page layout was not visually checked, and why.
@@ -43,4 +43,4 @@ Never describe a page you did not render.
43
43
 
44
44
  ## Keep renders disposable
45
45
 
46
- Render into a fresh temp directory each time (`--out-dir`). A stale PNG from a previous run reads as a pass and is the most common false confirmation.
46
+ Render into a fresh temp directory each time (`--out-dir`). A stale PNG from an earlier run reads as a pass — the most common false confirmation.
@@ -1,35 +1,35 @@
1
1
  # Splitting a long job
2
2
 
3
- A twenty-section document is not one command. Splitting it into bounded steps keeps each change verifiable and stops one failure from hiding inside a large edit.
3
+ A twenty-section document is not one command. Bounded steps keep each change verifiable and stop one failure hiding inside a large edit.
4
4
 
5
5
  ## One step per structural change
6
6
 
7
7
  1. **Plan** — the outline (headings and what each section must establish), written down before any file work.
8
- 2. **Skeleton** — create the file with the outline only: heading blocks, no body text.
9
- 3. **Content, in batches** — a batch per top-level section, appended with `append_blocks.py`.
10
- 4. **Numbers** — captions, cross-references, TOC once the structure is final, never while sections are still moving.
8
+ 2. **Skeleton** — the file with the outline only: heading blocks, no body text.
9
+ 3. **Content, in batches** — one batch per top-level section, appended with `append_blocks.py`.
10
+ 4. **Numbers** — captions, cross-references and TOC once the structure is final, never while sections are still moving.
11
11
  5. **Verify** — render, inspect, fix.
12
12
 
13
13
  ## Why this order
14
14
 
15
- - Captions and TOC are fields over structure; running them after every batch means renumbering noise and a TOC that points at deleted headings.
16
- - A batch of one section is small enough to redo when the layout is wrong.
17
- - The skeleton proves the heading hierarchy before the text makes it expensive to change.
15
+ - Captions and TOC are fields over structure; running them after every batch means renumbering noise and a TOC pointing at deleted headings.
16
+ - A one-section batch is small enough to redo when the layout is wrong.
17
+ - The skeleton proves the hierarchy before the text makes changing it expensive.
18
18
 
19
19
  ## Batch boundaries
20
20
 
21
- - One section per file: `section-01.docx`, `section-02.docx`, then `merge_docx_append.py`. This is the only sane way to draft in parallel or across sessions.
22
- - Keep a `manifest.json` listing section files in order. Merging is then mechanical and the order is not remembered from chat.
21
+ - One section per file — `section-01.docx`, `section-02.docx` — then `merge_docx_append.py`. This is the only sane way to draft in parallel or across sessions.
22
+ - Keep a `manifest.json` listing the section files in order, so merging is mechanical and the order is not remembered from chat.
23
23
  - Merge before numbering, not after.
24
24
 
25
25
  ## When a batch fails
26
26
 
27
- Do not patch a large edit inside a large document. Discard the failed batch's output, fix the batch, re-apply it to the previous good file. Keep the previous good file until the batch is verified.
27
+ Do not patch a large edit inside a large document. Discard the failed batch's output, fix the batch, re-apply it to the previous good file, and keep that good file until the batch is verified.
28
28
 
29
29
  ## Context hygiene
30
30
 
31
- - Long sessions lose the document design. Keep the plan and the design defaults (page size, font sizes, table borders) in a file in the workspace and re-read it, rather than trusting recollection.
32
- - When the user changes a decision mid-job (page size, heading style), record it at the point they said it, and apply it to batches already written.
31
+ - Long sessions lose the document design. Keep the plan and the design defaults (page size, font sizes, table borders) in a workspace file and re-read it instead of trusting recollection.
32
+ - When the user changes a decision mid-job (page size, heading style), record it where they said it and apply it to batches already written.
33
33
 
34
34
  ## Smoke test after the join
35
35
 
@@ -1,26 +1,30 @@
1
1
  ---
2
- description: Create, inspect, edit, redact, stamp, and render PDF files with the bundled Office helper CLI and self-contained Python scripts.
2
+ description: Create, extract, fill forms in, merge, split, redact, stamp, render, and verify PDF files with the bundled Office helper CLI and Python scripts.
3
3
  ---
4
4
 
5
5
  # PDF
6
6
 
7
- A PDF page is a content stream of text-showing and path-painting operators behind a page box. Every job in this pack either reads that stream (PyMuPDF, pdfplumber, pypdf), writes a new one (reportlab), or rasterises the result so you can look at it. Work from the mechanics outward: inspect first, choose the smallest operation that changes what the user asked for, then prove the change on a render. The helper CLI and the scripts are fast paths, not limits — each task file carries a runnable snippet for jobs they do not cover yet.
7
+ A PDF page is a content stream of text and path operators behind a page box. Every job here reads that stream (PyMuPDF, pdfplumber, pypdf), writes a new one (reportlab), or rasterises it to check the result. Inspect first, make the smallest change that satisfies the request, then prove it on a render.
8
8
 
9
- | User job | What actually happens | Fast path |
10
- | --- | --- | --- |
11
- | Look at a page, crop, or measure geometry | Rasterise the page box with PyMuPDF at a DPI, read page size, rotation, text, drawings, widgets | `scripts/render_pages.py`, `pdf render` |
12
- | Extract text or tables | pdfplumber reads characters and clusters table rules per page | `scripts/extract_text.py`, `pdf read` |
13
- | Inspect or fill an AcroForm | pypdf walks `/AcroForm` widgets, then rebuilds each `/AP` appearance from `/V` | `scripts/forms.py`, snippet in `tasks/forms.md` |
14
- | Remove a region (redaction) | PyMuPDF redaction annotations plus `apply_redactions` strip text and line art inside a rect | `scripts/redact_region.py`, snippet in `tasks/redact.md` |
15
- | Read or rewrite metadata | The document info dictionary; producer keys cannot change page content | `scripts/metadata.py` |
16
- | Merge, split, reorder, rotate, delete pages | Page-tree rearrangement; content is copied, not re-flowed | `scripts/page_ops.py`, `scripts/split_pages.py`, snippet in `tasks/edit-pages.md` |
17
- | Stamp a footer, header, or banner | reportlab draws an overlay page, pypdf merges it over the target pages | `scripts/stamp_overlay.py` |
18
- | Build a new document | reportlab draws primitives and text into a page | `pdf create`, `tasks/create.md` |
9
+ | User job | Fast path |
10
+ | --- | --- |
11
+ | Look at a page, crop, or measure geometry | `scripts/render_pages.py`, `pdf render` |
12
+ | Extract text, tables, or page images | `scripts/extract_text.py`, `pdf read`, `tasks/read.md` |
13
+ | Inspect or fill an AcroForm | `scripts/forms.py`, `tasks/forms.md` |
14
+ | Remove a region (redaction) | `scripts/redact_region.py`, `tasks/redact.md` |
15
+ | Read or rewrite metadata (info dictionary only, never page content) | `scripts/metadata.py`, `tasks/edit-pages.md` |
16
+ | Merge, split, reorder, rotate, delete pages | `scripts/page_ops.py`, `scripts/split_pages.py`, `tasks/edit-pages.md` |
17
+ | Stamp a footer, header, or label | `scripts/stamp_overlay.py`, `tasks/stamp.md` |
18
+ | Build a new document | `pdf create`, `tasks/create.md` |
19
+ | Resolve an `office-annotations` region | `tasks/annotations.md` |
20
+ | Decide what a PDF cannot do | `references/limits.md` |
21
+
22
+ Each task file explains the mechanism before giving the command. `references/geometry.md` covers page boxes and normalised regions, `references/layout-quality.md` is the pre-delivery visual checklist, and `references/form-fields.md` covers field flags, appearance streams, and flattening.
19
23
 
20
24
  ## Before any file work
21
25
 
22
26
  1. Call the `office_status` tool.
23
- 2. Read the `## Office runtime` JSON block at the end of this file and use its `python`, `cli`, `skill`, and `shared` values verbatim, quoted: `"<python>"`, `"<cli>"`, `"<skill>/scripts/<name>.py"`.
27
+ 2. Read the runtime block appended to this file at runtime; use its `python`, `cli`, `skill`, and `shared` values verbatim, quoted: `"<python>"`, `"<cli>"`, `"<skill>/scripts/<name>.py"`.
24
28
  3. If that block reports no working runtime, stop. Give the user the `node "..."` remedy command it names, then call `office_status` again. Never fabricate a PDF result you could not verify.
25
29
 
26
30
  Scripts under `"<skill>/scripts/"` are standalone argparse CLIs. Run `<python> <script> --help` before using one you have not called in this session.
@@ -29,49 +33,45 @@ Scripts under `"<skill>/scripts/"` are standalone argparse CLIs. Run `<python> <
29
33
 
30
34
  Decide the route before you type a command. Every route ends in the same render check.
31
35
 
32
- - **The file does not exist** → create it from a `pdf create` spec, or with reportlab when the layout does not fit the block model (`tasks/create.md`).
33
- - **The file exists and must change** → read it first (`pdf read`, and a render), then mutate it with the script named in the task file. The helper CLI has no edit verb: it creates, reads, renders, and reports status only; every change is a `scripts/*.py` call.
34
- - **A script covers the job** → run it, then verify its JSON report and its rendered pages. Scripts refuse to overwrite their input and exit non-zero on a failed self-check; treat both as hard stops.
36
+ - **No file yet** → create it from a `pdf create` spec, or with reportlab when the layout does not fit the block model (`tasks/create.md`).
37
+ - **Existing file to change** → read it first (`pdf read`, plus a render), then mutate it with the script its task file names. The CLI has no edit verb: it creates, reads, renders, and reports status only, so every change is a `scripts/*.py` call.
38
+ - **A script covers the job** → run it, then check its JSON report and its rendered pages. A refused overwrite or a failed self-check exits non-zero: that is a hard stop.
35
39
  - **A read/render verb covers it** → `pdf read`, `pdf render`, or `pdf status`, then verify in the task's own way. Never route a mutation through the CLI.
36
- - **Neither covers it** → run the runnable snippet in the matching `tasks/*.md` file, or adapt it. Do not invent a CLI verb, and do not tell the user the job is impossible because no verb exists.
37
- - **Choose the smallest operation** that satisfies the request: metadata change before rebuild, page move before re-creation, one-line patch before re-typing the page.
40
+ - **Neither covers it** → run or adapt the snippet in the matching `tasks/*.md` file. Do not invent a CLI verb, and do not call the job impossible because no verb exists.
41
+ - **Take the smallest operation**: metadata change before rebuild, page move before re-creation, one-line patch before re-typing the page.
38
42
 
39
43
  ## Failure handling
40
44
 
41
45
  | Symptom | Meaning | Next step |
42
46
  | --- | --- | --- |
43
- | `{"ok": false, ...}` and non-zero exit | Invalid argument, missing file, or a refused overwrite | Read the `error` field, fix what it names, re-run. Never strip a step to make it pass. |
44
- | `encrypted` in the error | The file needs a password | Ask the user for a decrypted copy; do not guess passwords or write one into a command. |
45
- | `the LibreOffice renderer is not available` | Media conversion for docx/pptx/xlsx is unavailable because the DSH LibreOffice kit was not found | PDF rendering still works (PyMuPDF); for a PDF this is harmless. For other formats report that appearance could not be checked. |
47
+ | `encrypted` in the error | The file needs a password | Ask the user for a decrypted copy. Do not guess passwords or write one into a command. |
46
48
  | `leftover_chars_in_rect` greater than 0 | The redaction rectangle missed text | Enlarge `--pad` or reconvert the region; re-render before delivering. |
47
- | `not_applied` is not empty | A form value did not persist after save | Treat as a failure: check the field name, its allowed states, and whether the widget has an appearance stream. |
48
- | `chars: 0` on a page that shows text | Scanned page, no text layer | Say so; follow `references/limits.md` for the OCR situation. Do not return an empty extraction as success. |
49
- | Render shows the change, `pdf read` does not | The edit landed in pixels only | Fix the content stream or note the limitation; a visible-only change usually means a cover-up, not an edit. |
49
+ | `not_applied` is not empty | A form value did not persist after save | Treat as a failure: check the field name, its allowed states, and the widget's appearance stream. |
50
+ | `chars: 0` on a page that shows text | Scanned page, no text layer | Say so; follow `references/limits.md`. Never return an empty extraction as success. |
51
+ | Render shows the change, `pdf read` does not | The edit landed in pixels only | Fix the content stream or note the limitation. A visible-only change is usually a cover-up, not an edit. |
52
+
53
+ General CLI failures, missing runtimes, and the `the LibreOffice renderer is not available` result are covered by `"<shared>/common-workflow.md"`; `pdf render` never needs LibreOffice.
50
54
 
51
55
  ## Quality rules
52
56
 
53
- - One heading scale, one body size, and one margin system per document (`references/layout-quality.md`).
54
- - Prove every structural change on a render of the changed page, not only on a text dump.
57
+ - One heading scale, one body size, one margin system per document (`references/layout-quality.md`).
58
+ - Prove every structural change on a render of the changed page, not on a text dump.
55
59
  - Keep page geometry consistent when merging or stamping: use each page's own box.
56
- - Do not claim redaction while the words are still extractable.
57
- - Preserve the user's original: write `<original>-edited.pdf`, never over it.
58
- - Mark deliberate shortcuts (a rebuild instead of a reflow, a stamped patch instead of a content edit) in the reply, so the user knows what kind of change they received.
59
- - Deliver with `present`, then one sentence about what changed and where the file is.
60
+ - Never claim redaction while the words are still extractable.
61
+ - Mark deliberate shortcuts (a rebuild instead of a reflow, a stamped patch instead of a content edit) in the reply.
60
62
 
61
63
  ## Delivery
62
64
 
63
- Always hand the finished file to the user with the `present` tool, then summarise in one sentence what changed. A bare path is not delivery: the chat transcript renders `.docx`/`.pptx`/`.xlsx`/`.pdf` links as "二进制文件,无法显示改动" and the user cannot open them.
65
+ Hand the finished file to the user with the `present` tool, then one sentence on what changed. A bare path is not delivery: the transcript renders `.docx`/`.pptx`/`.xlsx`/`.pdf` links as "二进制文件,无法显示改动", so the user cannot open them.
64
66
 
65
67
  - Creating a file: write it into the user's workspace, verify it, then `present`.
66
68
  - Editing an existing file: never overwrite the original. Write `<original>-edited.<ext>` (or a name the user asked for), verify it, `present` it, and state both paths.
67
69
 
68
70
  ## Annotations
69
71
 
70
- The user may reply with an `office-annotations` block: `[{ page, text, requirement, region: { left, top, width, height } }]`. `region` is normalised 0–1 against the rendered preview page the user drew on; `requirement` is free-form natural language. Treat it as a change request, not as editor state: locate what the region covers, apply the smallest edit that satisfies `requirement`, re-render, and confirm the region now looks right.
71
-
72
- When the payload cannot be mapped to a concrete object (the region is empty or the file has no geometry), do not guess silently — say what you could not locate.
72
+ An `office-annotations` reply is a change request, not editor state: `[{ page, text, requirement, region: { left, top, width, height } }]`. `region` is normalised 0–1 against the rendered preview page the user drew on; `requirement` is free-form. Locate what the region covers, apply the smallest edit that satisfies `requirement`, re-render, confirm the region looks right, and say what you could not locate rather than guessing.
73
73
 
74
- For PDF the `page` value is the 1-based page of the file itself, and `region × page.rect` is the real place to operate on: convert once with PyMuPDF and use the device rect directly. `text` is the words under the mark and wins over the region when they disagree; `imageName`, when present, is a screenshot of the marked area that you can open with `read_image`. Full resolution recipe: `tasks/annotations.md` and `"<shared>/annotation-recipes.md"`.
74
+ For PDF, `page` is the 1-based page of the file, and `region × page.rect` is the real rectangle: convert once with PyMuPDF and work in the device rect; `text` wins over the region when they disagree, and `imageName`, when present, is a screenshot of the marked area to open with `read_image`. Full recipe: `tasks/annotations.md`, payload shapes: `"<shared>/annotation-recipes.md"`.
75
75
 
76
76
  ## Verify
77
77
 
@@ -81,22 +81,7 @@ Render before you present, and again after every fix:
81
81
  "<python>" "<cli>" pdf render --input "<file.pdf>" --out-dir "<render-dir>" --dpi 110
82
82
  ```
83
83
 
84
- Open each PNG with `read_image`, and compare it with the same page rendered before the change. Look for text clipped at the page box, overlapping stamps or form fields, fonts replaced by boxes, rotated pages that now sit sideways, tables spilling past the margin, and scanned pages whose text is an image. Then run `"<python>" "<cli>" pdf read --input "<file.pdf>"` and confirm the text, page count, and form fields match what you edited. Two or three render/patch passes are normal; if a defect survives three, say what still looks wrong instead of calling it fixed.
85
-
86
- ## Tasks
87
-
88
- | Task | Read |
89
- | --- | --- |
90
- | Create a PDF from a spec or reportlab | `tasks/create.md` |
91
- | Extract text, tables, or page images | `tasks/read.md` |
92
- | Inspect, fill, or flatten an AcroForm | `tasks/forms.md` |
93
- | Remove a region of a page | `tasks/redact.md` |
94
- | Merge, split, reorder, rotate, delete pages | `tasks/edit-pages.md` |
95
- | Stamp a footer, header, or label | `tasks/stamp.md` |
96
- | Resolve an `office-annotations` region | `tasks/annotations.md` |
97
- | Decide what a PDF cannot do | `references/limits.md` |
98
-
99
- `references/geometry.md` explains page boxes and normalised regions, `references/layout-quality.md` is the pre-delivery visual checklist, `references/form-fields.md` covers field flags, appearance streams, and flattening.
84
+ Open each PNG with `read_image` and compare it with the pre-change render, working through `references/layout-quality.md`. Then run `"<python>" "<cli>" pdf read --input "<file.pdf>"` and confirm the text, page count, and form fields match your edit. Two or three passes are normal; past three, say what still looks wrong instead of calling it fixed.
100
85
 
101
86
  ## Shared workflow
102
87
 
@@ -4,39 +4,39 @@
4
4
 
5
5
  `/AcroForm` in the catalog holds `/Fields`, the root field array. A field dictionary has:
6
6
 
7
- - `/T` — partial name; the full name is the partial names of ancestors joined with dots.
8
- - `/FT` — field type, inherited: `Tx` text, `Btn` button, `Ch` choice, `Sig` signature.
9
- - `/V` — current value. Text fields store a string; buttons store the selected state name (`/Yes`, `/Off`, `/basic`); choice fields store a string or an array of strings; signature fields store a dictionary.
10
- - `/Ff` — flags bitmask, inherited. Bit 1 (`1`) read-only, bit 2 (`2`) required, bit 13 (`4096`) multiselect for choices, bits 15–17 (`65536`/`131072`/`262144`) button behaviour (no-toggle, radio, pushbutton).
11
- - `/DA` — default appearance string; controls the font and colour an appearance generator uses.
7
+ - `/T` — partial name; the full name joins ancestor partial names with dots.
8
+ - `/FT` — type, inherited: `Tx` text, `Btn` button, `Ch` choice, `Sig` signature.
9
+ - `/V` — current value: a string for text; the selected state name (`/Yes`, `/Off`, `/basic`) for buttons; a string or array for choice fields; a dictionary for signatures.
10
+ - `/Ff` — inherited flags bitmask: bit 1 (`1`) read-only, bit 2 (`2`) required, bit 13 (`4096`) multiselect, bits 15–17 (`65536`/`131072`/`262144`) button behaviour (no-toggle, radio, pushbutton).
11
+ - `/DA` — default appearance string; font and colour an appearance generator uses.
12
12
  - `/Kids` — child fields or widgets.
13
13
  - `/AP` — appearance dictionary: `/N` normal, `/R` rollover, `/D` down. For a button, `/N` maps each state name to a form XObject; for text and choice fields, `/N` is one stream.
14
- - `/Rect`, `/P`, `/AS` — widget geometry, owning page, and the button state a widget currently shows.
15
- - `/NeedAppearances` in `/AcroForm` — documented flag that asks viewers to regenerate appearances; many viewers ignore it, which is why a file can hold a correct `/V` and show nothing.
14
+ - `/Rect`, `/P`, `/AS` — widget geometry, owning page, and the state the widget shows.
15
+ - `/NeedAppearances` in `/AcroForm` — asks viewers to regenerate appearances; many ignore it, which is why a file can hold a correct `/V` and show nothing.
16
16
 
17
17
  ## What a fill must satisfy
18
18
 
19
- 1. Field `/V` equals the requested value, in the field's expected representation (boolean → state name, radio → that button's on-state, choice → string or list).
20
- 2. The widget's `/AP /N` exists and is a stream or state map with a `/BBox` and `/Resources`; the stream's content paints the value.
21
- 3. The widget's `/AS` names a state present in `/AP /N`; for a radio group every widget's `/AS` must be the group's chosen state or `/Off`.
22
- 4. The value is visible on the page: rendered inside `/Rect`, not clipped, not hidden behind the page content.
19
+ 1. Field `/V` equals the requested value in the field's representation (boolean → state name, radio → that button's on-state, choice → string or list).
20
+ 2. The widget's `/AP /N` exists as a stream or state map with a `/BBox` and `/Resources`, and its content paints the value.
21
+ 3. The widget's `/AS` names a state present in `/AP /N`; for a radio group, every widget's `/AS` must be the group's chosen state or `/Off`.
22
+ 4. The value is visible on the page: rendered inside `/Rect`, not clipped, not hidden behind page content.
23
23
 
24
- Updating `/V` alone satisfies 1 and fails 2–4. That is the "filled but blank" failure. Rebuilding the appearance and forgetting to remove a stale `/AS` satisfies 1–3 and shows two selected radio buttons. Reopening and checking 1–3 after save, then rendering for 4, is the whole discipline.
24
+ Updating `/V` alone satisfies 1 and fails 2–4 — the "filled but blank" failure. Rebuilding the appearance with a stale `/AS` satisfies 1–3 and shows two selected radios. Check 1–3 after saving, then render for 4.
25
25
 
26
- pypdf's `update_page_form_field_values(writer, page, values, flags, auto_regenerate, flatten)` handles 1–3 for common field types when the writer cloned the document properly (and `writer.reattach_fields(page)` was called where needed). Button groups need the extra `/AS` synchronisation described in `tasks/forms.md`.
26
+ pypdf's `update_page_form_field_values(writer, page, values, flags, auto_regenerate, flatten)` handles 1–3 for common field types when the writer cloned the document properly (and `writer.reattach_fields(page)` was called where needed). Button groups need the extra `/AS` synchronisation in `tasks/forms.md`.
27
27
 
28
28
  ## Flattening
29
29
 
30
- Two meanings, both destructive in different ways:
30
+ Two destructive meanings:
31
31
 
32
32
  - `flatten=True` in `update_page_form_field_values` paints each widget's current appearance into the page content and removes the interactive field.
33
- - Post-process: remove `/Widget` annotations with `writer.remove_annotations(subtypes="/Widget")` and pop `/AcroForm` from the catalog. This alone leaves the widget's appearance in place, so the page looks unchanged while the fields stop responding.
33
+ - Post-process: remove `/Widget` annotations with `writer.remove_annotations(subtypes="/Widget")` and pop `/AcroForm` from the catalog. On its own this leaves the appearance, so the page looks unchanged while the fields stop responding.
34
34
 
35
- Do both when the user wants a non-editable form. Verify: zero `/Widget` annotations remain, no `/AcroForm` in the catalog, and the rendered page still shows the values.
35
+ Do both for a non-editable form. Verify zero `/Widget` annotations, no `/AcroForm` in the catalog, and the values still rendered.
36
36
 
37
37
  ## Appearance streams
38
38
 
39
- An appearance stream is a form XObject.
39
+ An appearance stream is a form XObject:
40
40
 
41
41
  ```
42
42
  /Type /XObject
@@ -45,12 +45,12 @@ An appearance stream is a form XObject.
45
45
  /Resources << /Font << /Helv 5 0 R >> >>
46
46
  ```
47
47
 
48
- `/BBox` is the drawing area in the widget's own coordinates; the widget's `/Rect` maps that area onto the page. A generated stream usually sets the font from `/DA`, positions text at a small inset, and clips to `/BBox`. When a field must look right and its own appearance is missing, drawing a replacement with reportlab at `/Rect` coordinates and merging it into the page produces the same visual result without lying about the field value.
48
+ `/BBox` is the drawing area in the widget's own coordinates; the widget's `/Rect` maps it onto the page. A generated stream usually sets the font from `/DA`, positions text at a small inset, and clips to `/BBox`. When a field must look right and its appearance is missing, draw a replacement with reportlab at `/Rect` coordinates and merge it into the page — same visual result, without lying about the field value.
49
49
 
50
50
  ## Signature fields
51
51
 
52
- `/FT /Sig` requires a certificate and a signing implementation. Filling text into a signature field, or editing any page of an already-signed document, breaks the signature. Say that plainly and offer the unsigned rebuild; do not present an edited document as signed.
52
+ `/FT /Sig` needs a certificate and a signing implementation. Filling text into a signature field, or editing any page of a signed document, breaks the signature. Say so plainly and offer the unsigned rebuild; never present an edited document as signed.
53
53
 
54
54
  ## Choice fields
55
55
 
56
- `/Opt` lists the allowed options; `/V` is the chosen value or values. A single-select choice takes a string; multi-select takes an array. If the value is not in `/Opt`, viewers show blank — validate against `/Opt` before writing, and report the allowed options when the requested value is missing.
56
+ `/Opt` lists the allowed options; `/V` is the chosen value or values. Single-select takes a string, multi-select an array. A value outside `/Opt` renders blank — validate against `/Opt`, and report the allowed options when the requested value is missing.
@@ -1,24 +1,24 @@
1
1
  # Page geometry
2
2
 
3
- PDF units are points: 1/72 inch. A4 is 595 × 842 points, Letter 612 × 792. Numbers in a spec, a region, or a stamp are points unless stated otherwise.
3
+ PDF units are points, 1/72 inch: A4 is 595 × 842, Letter 612 × 792. Numbers in a spec, a region, or a stamp are points unless stated otherwise.
4
4
 
5
5
  ## Boxes
6
6
 
7
7
  - **MediaBox** — the physical page, the outermost boundary.
8
- - **CropBox** — the visible area; defaults to MediaBox. A viewer shows the CropBox.
8
+ - **CropBox** — the visible area, defaulting to MediaBox; viewers show the CropBox.
9
9
  - **BleedBox / TrimBox / ArtBox** — print-production hints, usually absent in office output.
10
10
 
11
- PyMuPDF's `page.rect` is the CropBox (or MediaBox when no CropBox exists), with `x0, y0` normally 0 and `x1, y1` the width and height. pypdf exposes `.mediabox` and `.cropbox` directly. When you move content between documents, use the page's own box, not a constant: mixed-size sources are common after a merge.
11
+ PyMuPDF's `page.rect` is the CropBox (MediaBox when no CropBox exists), with `x0, y0` normally 0 and `x1, y1` the width and height; pypdf exposes `.mediabox` and `.cropbox`. When moving content between documents, use the page's own box, never a constant: mixed-size sources are common after a merge.
12
12
 
13
13
  ## Coordinates
14
14
 
15
- The origin is the bottom-left of the page box, x to the right, y upward. Library calls that take a rectangle (`page.get_textbox(rect)`, `add_redact_annot`, `get_pixmap(clip=...)`) use that system. Library calls that report word boxes (`page.get_text("words")`, `search_for`) report rectangles in the same system — a word's box is not a baseline, so a text redaction rect is close but may clip the line.
15
+ The origin is the bottom-left of the page box, x right, y up. Rectangle-taking calls (`page.get_textbox(rect)`, `add_redact_annot`, `get_pixmap(clip=...)`) and word-box calls (`page.get_text("words")`, `search_for`) all use that system — a word's box is not a baseline, so a text redaction rect may clip the line.
16
16
 
17
- reportlab's `drawString(x, y, text)` places a *baseline*, measured from the bottom. Converting a top-down measurement `top` to that system: `y = page_height - top - font_size * 0.8` puts the glyph body roughly where it was.
17
+ reportlab's `drawString(x, y, text)` places a *baseline* measured from the bottom. Convert a top-down measurement: `y = page_height - top - font_size * 0.8` puts the glyph body roughly where it was.
18
18
 
19
19
  ## Rotation
20
20
 
21
- `/Rotate` is a multiple of 90 stored per page; viewers apply it on top of the content. `page.rect` in PyMuPDF is already the rotated (visible) geometry, while `page.mediabox` stays unrotated. Redactions and text boxes use the unrotated content stream, so a rotated page needs the rectangle transformed: simplest is `page.set_rotation(0)`, do the work, then set the rotation back — the render proves whether you got it right.
21
+ `/Rotate` is a per-page multiple of 90 applied by viewers on top of the content. `page.rect` in PyMuPDF is the rotated (visible) geometry; `page.mediabox` stays unrotated. Redactions and text boxes use the unrotated content stream, so a rotated page needs the rectangle transformed — simplest is `page.set_rotation(0)`, do the work, set the rotation back, and let the render prove it.
22
22
 
23
23
  ## Normalised regions
24
24
 
@@ -31,14 +31,14 @@ y0 = page.y0 + top * page.height
31
31
  y1 = page.y0 + (top + height) * page.height
32
32
  ```
33
33
 
34
- That is the whole conversion for an unrotated page. For a rotated page, convert against the preview's own orientation and map back; the words under the converted rect tell you immediately if the mapping is off.
34
+ That is the whole conversion for an unrotated page. For a rotated page, convert against the preview's own orientation and map back; the words under the converted rect show at once if the mapping is off.
35
35
 
36
36
  ## Pixels and DPI
37
37
 
38
- A raster at `dpi` has `pixels = points * dpi / 72`. A 110 dpi A4 page is roughly 909 × 1287 pixels. When the user draws a region on a rendered image, the fraction is DPI-independent — you never need the pixel size to convert it. For image quality on print, 150 dpi is the minimum for a screenshot, 300 dpi for artwork.
38
+ A raster at `dpi` has `pixels = points * dpi / 72`; a 110 dpi A4 page is roughly 909 × 1287 pixels. A region drawn on a rendered image is a DPI-independent fraction, so you never need the pixel size to convert it. For print, 150 dpi is the minimum for a screenshot, 300 dpi for artwork.
39
39
 
40
40
  ## Text measurements
41
41
 
42
- `page.get_text("dict")` gives blocks → lines → spans, each span with `size` (font size in points), `font`, `origin` (the baseline point), and the text. Use the span measurements, not the bounding box, when you redraw a line: the box includes ascenders and descenders that change the apparent size.
42
+ `page.get_text("dict")` gives blocks → lines → spans, each span with `size` (points), `font`, `origin` (baseline point), and text. When redrawing a line, use the span measurements, not the bounding box: the box includes ascenders and descenders and changes the apparent size.
43
43
 
44
- `page.get_textbox(rect)` returns the characters whose boxes intersect `rect`, in reading order. That is the correct way to confirm what a region covers; `page.get_text("words", clip=rect)` returns word tuples when you need the boxes too.
44
+ `page.get_textbox(rect)` returns the characters whose boxes intersect `rect`, in reading order — the correct way to confirm what a region covers. `page.get_text("words", clip=rect)` returns word tuples when you need the boxes too.
@@ -1,41 +1,41 @@
1
1
  # Layout and delivery quality
2
2
 
3
- Run this list on the rendered pages before presenting a file. It is the difference between "the command exited 0" and "the document is right".
3
+ Run this list on the rendered pages before presenting a file: it separates "the command exited 0" from "the document is right".
4
4
 
5
5
  ## Page level
6
6
 
7
- - Page count matches the request; no stray blank page at the end from a trailing `page_break` or an empty `showPage`.
8
- - Every page has the intended size and orientation — a merged section with Letter pages inside an A4 report is visible in the render.
7
+ - Page count matches the request; no stray blank page from a trailing `page_break` or an empty `showPage`.
8
+ - Every page has the intended size and orientation — Letter pages inside an A4 merge show in the render.
9
9
  - Margins are consistent; nothing is clipped at the page box edge, including stamps and footers.
10
- - The page box is the CropBox the user expects; a crop that hides a footer is a bug, not a clean-up.
10
+ - The page box is the CropBox the user expects; a crop that hides a footer is a bug.
11
11
 
12
12
  ## Text
13
13
 
14
- - Body size, leading, and font are consistent; a patched line that uses a different size or face is a visible patch job.
14
+ - Body size, leading, and font are consistent; a patched line in another size or face is a visible patch job.
15
15
  - Headings descend in a sensible scale and do not collide with the body.
16
16
  - No overflow past the right margin, no hyphen-only line breaks, no replacement boxes for missing glyphs.
17
- - Non-Latin text renders as characters, not blanks — this needs a registered font with the right coverage.
18
- - Extracted text matches what is visible: run `pdf read` on the final file and spot-check the edited region.
17
+ - Non-Latin text renders as characters, not blanks — it needs a registered font with the right coverage.
18
+ - Extracted text matches the visible text: `pdf read` the final file and spot-check the edited region.
19
19
 
20
20
  ## Tables and figures
21
21
 
22
22
  - Table columns fit the text column; long cell text wraps rather than clipping.
23
- - A table that splits across pages repeats its header row.
23
+ - A table split across pages repeats its header row.
24
24
  - Images keep their aspect ratio; a stretched logo is a defect.
25
25
  - Ruled lines and fills from a redacted block are gone, not half-erased.
26
- - Charts and screenshots are legible at print size; text inside an image is not searchable, so keep the numbers in the body text too.
26
+ - Charts and screenshots are legible at print size; text inside an image is not searchable, so keep the numbers in body text too.
27
27
 
28
28
  ## Stamps and annotations
29
29
 
30
30
  - Footer/header text sits in the margin, not over body copy.
31
- - Page numbers are correct on the first and last page (off-by-one is the classic overlay bug).
31
+ - Page numbers are right on the first and last page (off-by-one is the classic overlay bug).
32
32
  - A stamp appears once per page; repeated merges double it.
33
- - Filled form values sit inside their widget boxes, are not clipped by the box border, and use the field's own font where the appearance stream was preserved.
33
+ - Filled form values sit inside their widget boxes, are not clipped by the box border, and keep the field's own font where the appearance stream was preserved.
34
34
 
35
35
  ## Fonts and links
36
36
 
37
- `page.get_fonts(full=True)` lists embedded fonts. A subset font is fine for display; it means the file is not intended for text-heavy editing. Check that no font is missing for the glyphs you wrote — the render shows boxes where coverage is absent. Links and bookmarks that pointed at reordered or deleted pages may dangle; the render shows clickable areas only, so trust the original page order rather than assuming the links moved with the content.
37
+ `page.get_fonts(full=True)` lists embedded fonts; a subset font is fine for display and means the file is not intended for text-heavy editing. Check that no font is missing for the glyphs you wrote — the render shows boxes where coverage is absent. Links and bookmarks that pointed at reordered or deleted pages may dangle: the render shows clickable areas only, so trust the original page order rather than assuming links moved with the content.
38
38
 
39
39
  ## Structural sanity
40
40
 
41
- Open the output one more time with the same route the user will use: `"<python>" "<cli>" pdf read --input "<file>"`. It confirms the file parses, the page count, the metadata, and that text is extractable. A file that renders but fails to parse is worse than an obvious error, because it fails later in someone else's viewer.
41
+ Open the output one more time with the route the user will use: `"<python>" "<cli>" pdf read --input "<file>"`. It confirms the file parses, the page count, the metadata, and that text extracts. A file that renders but fails to parse is worse than an obvious error, because it fails later in someone else's viewer.
@@ -1,58 +1,14 @@
1
1
  # When a PDF job is beyond this pack
2
2
 
3
- Every entry names the missing capability, what you can still deliver, and how the user resolves it. Never answer with a bare "unsupported": say what is missing, what the floor is, and give the next step.
4
-
5
- ## Scanned pages with no text layer
6
-
7
- The words exist only as pixels; extraction and search cannot see them. `render_pages.py` reports `chars: 0` for such a page, and the render shows the text as an image.
8
-
9
- - Still possible: render, crop with `read_image`, and transcribe the visible text; redact the image pixels with `--images pixels`; rebuild the page as a created PDF from the transcription.
10
- - Resolution: the user supplies a text-based copy of the file, or runs OCR where they have it (this runtime has no OCR engine, so do not promise the words will be extracted here).
11
-
12
- ## Encrypted or password-protected files
13
-
14
- pypdf and PyMuPDF can open with a password only if it is provided; every script here exits with "encrypted" and does no work.
15
-
16
- - Resolution: ask the user to decrypt the file first, then re-run. Never write a password into a command line that will be logged.
17
-
18
- ## Signing and certificates
19
-
20
- Creating a digital signature needs a certificate, a signer, and a signature field; the runtime has none of them. Editing a signed document invalidates its signature.
21
-
22
- - Still possible: fill unsigned fields, rebuild the content, deliver an unsigned file — and say clearly that the signature is gone.
23
- - Resolution: signing must happen in a tool with a certificate; the user applies it after the content is final.
24
-
25
- ## Paragraph-level re-editing
26
-
27
- PDF has no paragraph model: the content stream is positioned glyphs and paths. "Rewrite this paragraph" becomes a block-level operation — redact the old block, stamp the new text, and check the reflow yourself. Truly reflowing a page means rebuilding it from extracted content.
28
-
29
- - Still possible: replace a line or a block in place (see `tasks/annotations.md`), remove a block, stamp a note.
30
- - Resolution for reflow: rebuild the document from a spec with the corrected text, and say that it was rebuilt rather than patched.
31
-
32
- ## Lexical repair of broken files
33
-
34
- A file with a damaged cross-reference table may still render (viewers rebuild the xref) while strict parsers refuse it. If PyMuPDF or pypdf cannot open the file, do not silently produce an empty document.
35
-
36
- - Still possible: report the parse error, ask the user to open and re-save the file in a reader (which repairs it), then re-run.
37
- - Resolution: whichever tool originally produced the file should be told about the corruption.
38
-
39
- ## Tagged PDF and accessibility remediation
40
-
41
- Reading order, alternative text, and tags live in a structure tree that the libraries here can read but not author reliably. A visual edit does not add tags.
42
-
43
- - Still possible: keep the visual result correct and say the file is untagged.
44
- - Resolution: accessibility remediation needs an authoring tool that writes the structure tree.
45
-
46
- ## PDF/A and archival conformance
47
-
48
- Conformance requires font embedding rules, metadata schemas, and colour profiles that these libraries do not validate.
49
-
50
- - Still possible: many PDF/A inputs will survive a page-level edit and remain close, but claiming conformance would be a guess.
51
- - Resolution: validate with a dedicated PDF/A checker before delivery.
52
-
53
- ## Creating or restructuring form fields
54
-
55
- pypdf can fill existing fields; creating a new interactive field with a correct `/AP` state map is much more than a fill. reportlab can draw a form-like appearance, but without a widget tree it is not a form.
56
-
57
- - Still possible: stamp labels and values at coordinates so the printed result looks filled.
58
- - Resolution: an authoring tool for interactive forms, or accept a non-interactive output and say so.
3
+ State the missing capability, what you can still deliver, and how the user resolves it. Never answer with a bare "unsupported".
4
+
5
+ | Missing capability | Still possible | Resolution |
6
+ | --- | --- | --- |
7
+ | **Scanned pages with no text layer** — words are pixels only; `render_pages.py` reports `chars: 0` | Render, crop with `read_image`, transcribe; redact image pixels with `--images pixels`; rebuild the page from the transcription | The user supplies a text-based copy, or runs OCR where they have it — this runtime has no OCR engine |
8
+ | **Encrypted or password-protected files** — pypdf/PyMuPDF need the password; every script here exits with "encrypted" and does no work | Nothing until the file opens | Ask the user to decrypt first, then re-run. Never write a password into a command line that will be logged. |
9
+ | **Signing and certificates** — no certificate, signer, or signature field; editing a signed file invalidates its signature | Fill unsigned fields, rebuild the content, deliver unsigned — and say the signature is gone | Signing happens in a tool with a certificate, after the content is final |
10
+ | **Paragraph-level re-editing** — no paragraph model; the content stream is positioned glyphs and paths | Replace a line or block in place (`tasks/annotations.md`), remove a block, stamp a note | Reflow means rebuilding from a spec with the corrected text — say it was rebuilt, not patched |
11
+ | **Lexical repair of broken files** — a damaged xref may render in viewers but fail strict parsers; never emit an empty document | Report the parse error; ask the user to open and re-save in a reader (which repairs it), then re-run | Tell whichever tool produced the file about the corruption |
12
+ | **Tagged PDF and accessibility remediation** — the structure tree can be read but not authored reliably; a visual edit adds no tags | Keep the visual result correct and say the file is untagged | Remediation needs an authoring tool that writes the structure tree |
13
+ | **PDF/A and archival conformance** — font embedding, metadata schemas, and colour profiles are not validated here | Many PDF/A inputs survive a page-level edit and stay close, but claiming conformance would be a guess | Validate with a dedicated PDF/A checker before delivery |
14
+ | **Creating or restructuring form fields** — pypdf fills existing fields; a new interactive field needs a correct `/AP` state map, far beyond a fill | Stamp labels and values at coordinates so the printed result looks filled | An authoring tool for interactive forms, or accept non-interactive output and say so |
@@ -1,6 +1,6 @@
1
1
  # Resolve an `office-annotations` region on a PDF
2
2
 
3
- `page` is the 1-based page of the file, and `region × page.rect` is the real rectangle in the page's coordinate system. Convert once; then everything you do — read, redact, stamp, fill — uses that rect.
3
+ `page` is the 1-based page of the file, and `region × page.rect` is the real rectangle in the page's coordinate system. Convert once; then every operation — read, redact, stamp, fill — uses that rect.
4
4
 
5
5
  ## 1. Reproduce the page first
6
6
 
@@ -8,7 +8,7 @@
8
8
  "<python>" "<cli>" pdf render --input "<file.pdf>" --out-dir "<render>" --dpi 110
9
9
  ```
10
10
 
11
- Open `"<render>/page-<N>.png"` with `read_image`. The geometry is only meaningful against the page the user saw, so never resolve a region on a page you have not rendered.
11
+ Open `"<render>/page-<N>.png"` with `read_image`. Geometry is only meaningful against the page the user saw, so never resolve a region on a page you have not rendered.
12
12
 
13
13
  ## 2. Convert the region and read what it covers
14
14
 
@@ -43,7 +43,7 @@ Compare the words with the annotation's `text`. If `text` is present and those w
43
43
  | A single word or sentence to change | redact the old line, then stamp the new text (below) |
44
44
  | A picture to replace | overlay a cover rect and draw the new image with reportlab |
45
45
 
46
- A requirement like "make this sentence shorter" is a content edit; do not rebuild the whole page for it when the block is one line.
46
+ A requirement like "make this sentence shorter" is a content edit; do not rebuild the whole page when the block is one line.
47
47
 
48
48
  ## 4. Patch one line in place
49
49
 
@@ -91,8 +91,8 @@ Line height and font size must match the original text; read them from the origi
91
91
 
92
92
  ## 5. When the payload cannot be tied to an object
93
93
 
94
- Say which part you could not locate: the render page, the words you found under the region, and the item that is missing. Then ask for the text of the target or a clearer region. Genuinely blocked cases and their resolutions are in `references/limits.md` — name the missing capability and what the user can do about it, never a bare "unsupported".
94
+ Say which part you could not locate — render page, the words found under the region, the missing item — and ask for the target text or a clearer region. Blocked cases are in `references/limits.md`: name the missing capability and the way out, never a bare "unsupported".
95
95
 
96
96
  ## 6. Re-render and confirm
97
97
 
98
- Apply the edit, render the same page, and `read_image` it again. The region must show the requested result, the neighbouring text must be untouched, and the layout must still hold. Then check extraction (`pdf read`) so the change is real in the text layer, not only in pixels. Report what you resolved back to the user in the same terms they annotated with: page number and a one-line description of the change.
98
+ Apply the edit, render the same page, and `read_image` it again: the region must show the requested result, neighbouring text must be untouched, and the layout must hold. Then confirm with `pdf read` that the change is in the text layer, not only in pixels. Report in the user's own terms: page number plus a one-line description of the change.
@@ -1,7 +1,5 @@
1
1
  # Create a PDF
2
2
 
3
- A new PDF is a set of pages, each holding a content stream plus fonts and resources. Two routes exist here, and both end with the same verification.
4
-
5
3
  ## Fast path: the helper CLI spec
6
4
 
7
5
  ```bash
@@ -27,7 +25,7 @@ Blocks: `title`, `heading` (`level` 1–2), `paragraph`, `quote`, `bullets`, `nu
27
25
 
28
26
  ## Direct path: reportlab
29
27
 
30
- Reach for reportlab when the layout does not fit the block model — columns, absolute placement, custom rules, rotated text.
28
+ Reach for reportlab when the layout does not fit the block model: columns, absolute placement, custom rules, rotated text.
31
29
 
32
30
  ```bash
33
31
  "<python>" - <<'PY'
@@ -50,26 +48,25 @@ c.save()
50
48
  PY
51
49
  ```
52
50
 
53
- `drawString` takes a baseline point measured from the bottom-left corner, not the top. Convert top-down coordinates once (for a page height `h`, baseline `y = h - top - font_size`), or every following measurement will drift.
51
+ `drawString` takes a baseline point measured from the bottom-left corner, not the top. Convert top-down coordinates once (for a page height `h`, baseline `y = h - top - font_size`), or every later measurement drifts.
54
52
 
55
53
  ## Fonts
56
54
 
57
- Base-14 fonts (`Helvetica`, `Times-Roman`, `Courier`) need no embedding and cover Latin text. Non-Latin text needs a real font file registered with `pdfmetrics.registerFont(TTFont("Body", "<path>.ttf"))`; a font that cannot render a glyph produces a blank box in the output, which the render step will show. The CLI tries the first available CJK system font, and says so when none exists.
55
+ Base-14 fonts (`Helvetica`, `Times-Roman`, `Courier`) need no embedding and cover Latin text. Non-Latin text needs a real font file registered with `pdfmetrics.registerFont(TTFont("Body", "<path>.ttf"))`; a font that cannot render a glyph produces a blank box, which the render step shows. The CLI tries the first available CJK system font and says so when none exists.
58
56
 
59
57
  ## Quality rules
60
58
 
61
- - One heading scale, one body size, one margin system per document.
62
59
  - Keep body text inside 12–16 pt leading; below 9 pt text becomes unreliable on print.
63
60
  - ASCII hyphens only in prose; do not paste typographic dashes into generated copy unless the source used them.
64
61
  - Give every page a consistent header/footer treatment, or none at all.
65
- - Do not put a table wider than the text column; wrap long cell text instead of letting it clip.
62
+ - Full layout checklist: `references/layout-quality.md`.
66
63
 
67
64
  ## Verify
68
65
 
69
- Render and look at every page; a created PDF has no text layer surprises, so problems are layout problems: text past the margin, blocks overlapping, table columns squeezed, a `page_break` landing on an empty page, or a CJK line silently blank.
66
+ Render every page: a created PDF has no text-layer surprises, so problems are layout problems — text past the margin, blocks overlapping, table columns squeezed, a `page_break` landing on an empty page, a CJK line silently blank.
70
67
 
71
68
  ```bash
72
69
  "<python>" "<cli>" pdf render --input "out.pdf" --out-dir "render" --dpi 110
73
70
  ```
74
71
 
75
- Then confirm the structure you intended with `"<python>" "<cli>" pdf read --input "out.pdf"` (page count and per-page text) before `present`.
72
+ Then confirm the page count and per-page text with `"<python>" "<cli>" pdf read --input "out.pdf"` before `present`.