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.
- package/assets/skills/excel-live-control/SKILL.md +119 -152
- package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
- package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
- package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
- package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
- package/assets/skills/excel-live-control/references/office-automation.md +127 -153
- package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
- package/assets/skills/excel-live-control/style_guidelines.md +56 -64
- package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
- package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
- package/assets/skills/office-documents/SKILL.md +24 -60
- package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
- package/assets/skills/office-documents/ooxml/comments.md +8 -8
- package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
- package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
- package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
- package/assets/skills/office-documents/references/docx-structure.md +11 -13
- package/assets/skills/office-documents/references/template-elicitation.md +11 -13
- package/assets/skills/office-documents/references/writing-quality.md +20 -20
- package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
- package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
- package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
- package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
- package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
- package/assets/skills/office-documents/tasks/create_edit.md +14 -16
- package/assets/skills/office-documents/tasks/equations.md +17 -28
- package/assets/skills/office-documents/tasks/fields_update.md +14 -16
- package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
- package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
- package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
- package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
- package/assets/skills/office-documents/tasks/images_figures.md +15 -25
- package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
- package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
- package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
- package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
- package/assets/skills/office-documents/tasks/read_review.md +9 -20
- package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
- package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
- package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
- package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
- package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
- package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
- package/assets/skills/office-documents/tasks/verify_render.md +11 -23
- package/assets/skills/office-documents/tasks/watermarks_background.md +11 -15
- package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +6 -6
- package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
- package/assets/skills/office-pdf/SKILL.md +36 -51
- package/assets/skills/office-pdf/references/form-fields.md +20 -20
- package/assets/skills/office-pdf/references/geometry.md +10 -10
- package/assets/skills/office-pdf/references/layout-quality.md +13 -13
- package/assets/skills/office-pdf/references/limits.md +12 -56
- package/assets/skills/office-pdf/tasks/annotations.md +5 -5
- package/assets/skills/office-pdf/tasks/create.md +6 -9
- package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
- package/assets/skills/office-pdf/tasks/forms.md +15 -15
- package/assets/skills/office-pdf/tasks/read.md +9 -9
- package/assets/skills/office-pdf/tasks/redact.md +18 -18
- package/assets/skills/office-pdf/tasks/stamp.md +10 -10
- package/assets/skills/office-presentations/SKILL.md +71 -91
- package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
- package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
- package/assets/skills/office-presentations/recipes/README.md +24 -35
- package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
- package/assets/skills/office-presentations/recipes/connectors.md +11 -12
- package/assets/skills/office-presentations/recipes/images.md +11 -12
- package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
- package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
- package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
- package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
- package/assets/skills/office-presentations/recipes/tables.md +14 -14
- package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
- package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
- package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
- package/assets/skills/office-presentations/references/finalization.md +10 -12
- package/assets/skills/office-presentations/references/native-bullets.md +14 -18
- package/assets/skills/office-presentations/references/native-evidence.md +18 -32
- package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
- package/assets/skills/office-presentations/references/templates.md +31 -60
- package/assets/skills/office-presentations/routing/google_slides.md +4 -4
- package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
- package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
- package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
- package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
- package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
- package/assets/skills/office-presentations/tasks/template-following.md +23 -28
- package/assets/skills/office-spreadsheets/SKILL.md +32 -39
- package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
- package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
- package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
- package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
- package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
- package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
- package/assets/skills/office-spreadsheets/references/charts.md +15 -34
- package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
- package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
- package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
- package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
- package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
- package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
- package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
- package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
- package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
- package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
- package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
- package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
- package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
- package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
- package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
- package/assets/skills/shared/annotation-recipes.md +98 -120
- package/assets/skills/shared/common-workflow.md +70 -80
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|
"<python>" "<cli>" status
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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** —
|
|
9
|
-
3. **Content, in batches** —
|
|
10
|
-
4. **Numbers** — captions, cross-references
|
|
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
|
|
16
|
-
- A
|
|
17
|
-
- The skeleton proves the
|
|
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
|
|
22
|
-
- Keep a `manifest.json` listing section files in order
|
|
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
|
|
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
|
|
32
|
-
- When the user changes a decision mid-job (page size, heading style), record it
|
|
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,
|
|
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
|
|
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 |
|
|
10
|
-
| --- | --- |
|
|
11
|
-
| Look at a page, crop, or measure geometry |
|
|
12
|
-
| Extract text
|
|
13
|
-
| Inspect or fill an AcroForm |
|
|
14
|
-
| Remove a region (redaction) |
|
|
15
|
-
| Read or rewrite metadata
|
|
16
|
-
| Merge, split, reorder, rotate, delete pages |
|
|
17
|
-
| Stamp a footer, header, or
|
|
18
|
-
| Build a new document |
|
|
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
|
|
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
|
-
- **
|
|
33
|
-
- **
|
|
34
|
-
- **A script covers the job** → run it, then
|
|
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
|
|
37
|
-
- **
|
|
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
|
-
| `
|
|
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
|
|
48
|
-
| `chars: 0` on a page that shows text | Scanned page, no text layer | Say so; follow `references/limits.md
|
|
49
|
-
| Render shows the change, `pdf read` does not | The edit landed in pixels only | Fix the content stream or note the limitation
|
|
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,
|
|
54
|
-
- Prove every structural change on a render of the changed page, not
|
|
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
|
-
-
|
|
57
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
8
|
-
- `/FT` —
|
|
9
|
-
- `/V` — current value
|
|
10
|
-
- `/Ff` — flags bitmask
|
|
11
|
-
- `/DA` — default appearance string;
|
|
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
|
|
15
|
-
- `/NeedAppearances` in `/AcroForm` —
|
|
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
|
|
20
|
-
2. The widget's `/AP /N` exists
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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`
|
|
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.
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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` (
|
|
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
|
|
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
|
|
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
|
|
8
|
-
- Every page has the intended size and orientation —
|
|
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
|
|
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
|
|
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 —
|
|
18
|
-
- Extracted text matches
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
pypdf
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
62
|
+
- Full layout checklist: `references/layout-quality.md`.
|
|
66
63
|
|
|
67
64
|
## Verify
|
|
68
65
|
|
|
69
|
-
Render
|
|
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
|
|
72
|
+
Then confirm the page count and per-page text with `"<python>" "<cli>" pdf read --input "out.pdf"` before `present`.
|