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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Merge, split, reorder, rotate, delete pages
|
|
2
2
|
|
|
3
|
-
Pages are
|
|
3
|
+
Pages are referenced from a page tree; reordering or removing one changes references, not content. Nothing is re-flowed, so a page moved into a differently sized document keeps its own box and fonts.
|
|
4
4
|
|
|
5
5
|
## Fast path: `scripts/page_ops.py`
|
|
6
6
|
|
|
@@ -9,14 +9,14 @@ Pages are objects referenced from a page tree; changing the order or removing a
|
|
|
9
9
|
--rotate 90 --pages 0,2 --remove 3 --merge "<other.pdf>" --order 2,0,1
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- `--order` keeps and reorders; pages
|
|
15
|
-
- `--remove` drops indices from the list
|
|
12
|
+
- Fixed operation order: merge, then order, then remove, then rotate — a command always means the same thing.
|
|
13
|
+
- Indices are zero-based over the combined page list (input pages first, appended pages after).
|
|
14
|
+
- `--order` keeps and reorders; unlisted pages are dropped, and a repeated index duplicates it.
|
|
15
|
+
- `--remove` drops indices from the list `--order` leaves.
|
|
16
16
|
- `--rotate` takes 90, 180, or 270 clockwise and defaults to every remaining page; `--pages` limits it.
|
|
17
17
|
- `--out` defaults to `<input>-edited.pdf`; the input is never overwritten.
|
|
18
18
|
|
|
19
|
-
The script carries
|
|
19
|
+
The script carries content streams over unchanged and reopens the result to report page count, rotation, and page size per page. When one call cannot express the operation, chain two calls into two output files rather than reusing a name.
|
|
20
20
|
|
|
21
21
|
## Metadata
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ The script carries page content streams over as they are and reopens the result
|
|
|
24
24
|
"<python>" "<skill>/scripts/metadata.py" --input "<file.pdf>" --set "/Title=Field report" --set "/Author=Ada" --out "<file-edited.pdf>"
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Keys are the document information fields (`Title`, `Author`, `Subject`, `Keywords`, `Creator`, `Producer`, `CreationDate`, `ModDate`), plain or `/Name`, in any case. The script writes a new file and reopens it
|
|
27
|
+
Keys are the document information fields (`Title`, `Author`, `Subject`, `Keywords`, `Creator`, `Producer`, `CreationDate`, `ModDate`), plain or `/Name`, in any case. The script writes a new file and reopens it; `applied`/`not_applied` show what persisted.
|
|
28
28
|
|
|
29
29
|
## Split
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ Keys are the document information fields (`Title`, `Author`, `Subject`, `Keyword
|
|
|
32
32
|
"<python>" "<skill>/scripts/split_pages.py" --input "<file.pdf>" --out-dir "<parts>" --ranges "1-2,3,5-"
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
1-based inclusive ranges, `--every N` for equal chunks
|
|
35
|
+
1-based inclusive ranges, or `--every N` for equal chunks; the default is one file per page. The script copies the source metadata, refuses overlapping or out-of-range ranges, and fails when the parts do not add up to the source page count — the check that catches a typo in the range string. `--prefix` sets the filename stem.
|
|
36
36
|
|
|
37
37
|
## Direct page-tree work
|
|
38
38
|
|
|
@@ -53,19 +53,19 @@ print("pages", len(writer.pages))
|
|
|
53
53
|
PY
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
`add_page` copies the page and its resources
|
|
56
|
+
`add_page` copies the page and its resources; annotations pointing across pages (links) can break when their target is removed. After a reorder or deletion, check links and bookmarks on the render, not only the text.
|
|
57
57
|
|
|
58
58
|
## Rotate
|
|
59
59
|
|
|
60
|
-
Page
|
|
60
|
+
Page `/Rotate` (multiples of 90) is applied by viewers and does not transpose the content stream. `page.rotate(90)` in pypdf, or `page_ops.py --rotate 90 --pages ...`, is enough. A page scanned sideways that also carries a stale `/Rotate` accumulates both — the render shows which one you are looking at, and `scripts/render_pages.py` reports each page's rotation.
|
|
61
61
|
|
|
62
62
|
## Verify
|
|
63
63
|
|
|
64
64
|
After any structural edit:
|
|
65
65
|
|
|
66
66
|
- compare page counts (`pdf read` or the split report) — one missing page is easy to miss in a long render;
|
|
67
|
-
- render
|
|
68
|
-
- for merges, confirm
|
|
69
|
-
- for deletions of numbered pages, tell the user which numbers were removed, because the remaining page labels
|
|
67
|
+
- render the first and last page of every part, plus every page you rotated or reordered;
|
|
68
|
+
- for merges, confirm appended pages kept their size and orientation, and that page numbers or bates stamps do not collide;
|
|
69
|
+
- for deletions of numbered pages, tell the user which numbers were removed, because the remaining page labels shift.
|
|
70
70
|
|
|
71
|
-
`scripts/render_pages.py --input "<file.pdf>" --region 0,0,1,1 --json-out ...` prints per-page character and drawing counts
|
|
71
|
+
`scripts/render_pages.py --input "<file.pdf>" --region 0,0,1,1 --json-out ...` prints per-page character and drawing counts — a cheap blank-page check before delivery.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AcroForm inspection and filling
|
|
2
2
|
|
|
3
|
-
A form PDF carries `/AcroForm` in the catalog.
|
|
3
|
+
A form PDF carries `/AcroForm` in the catalog. `/Fields` holds field dictionaries: `/T` name, `/FT` type, `/V` value, `/Ff` flags, `/DA` default appearance, `/AP` appearance stream. Widgets carry `/Rect`, `/P`, `/AS`, and a widget can be a separate object reached through `/Parent`. Full field mechanics: `references/form-fields.md`. Text in a filled field usually lives in the widget's `/AP /N`, not the page content stream, so "value in `/V`" and "page shows the value" are two different facts.
|
|
4
4
|
|
|
5
5
|
## Inspect first
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ A form PDF carries `/AcroForm` in the catalog. Its `/Fields` array holds field d
|
|
|
8
8
|
"<python>" "<skill>/scripts/forms.py" inspect --input "<file.pdf>"
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Reports
|
|
11
|
+
Reports per field: name, type, value, read-only/required, widget count, pages, button states, and whether each widget has `/AP`. Read it all before touching values: duplicate names, missing appearances, and read-only flags are why a fill "does nothing".
|
|
12
12
|
|
|
13
13
|
## Fill
|
|
14
14
|
|
|
@@ -20,20 +20,20 @@ Reports, per field: id/name, type, value, read-only/required, widget count, page
|
|
|
20
20
|
{ "full_name": "Ada Lovelace", "email": "ada@example.org", "agree": true, "plan": "/basic" }
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Text fields take strings
|
|
23
|
+
Text fields take strings; checkboxes booleans or the state name; radio groups the state to select; choice fields a string or list. Keys are field names (`/T`, nested names joined with dots). `--flatten` bakes the values in and drops the form. The script exits non-zero on an unknown name (it never skips silently), a value outside the allowed states, or a saved file that fails the verification below.
|
|
24
24
|
|
|
25
25
|
## The discipline that makes a fill real
|
|
26
26
|
|
|
27
|
-
pypdf writes `/V` and rebuilds the widget appearance
|
|
27
|
+
pypdf writes `/V` and rebuilds the widget appearance; a writer can do one without the other, so check both after saving.
|
|
28
28
|
|
|
29
29
|
1. Reopen the saved file.
|
|
30
|
-
2.
|
|
31
|
-
3.
|
|
30
|
+
2. Read `/V` for every field you set and compare it with the requested value.
|
|
31
|
+
3. Confirm every widget has a non-empty `/AP /N` stream with a `/BBox`; for radio and checkbox groups, `/AS` must name a state present in `/AP /N`.
|
|
32
32
|
4. Render the page and look at the field.
|
|
33
33
|
|
|
34
|
-
Steps 1–3 catch a stale or missing appearance; step 4 catches a `/V`
|
|
34
|
+
Steps 1–3 catch a stale or missing appearance; step 4 catches a right `/V` over old text. The script does 1–3 and prints `applied`/`not_applied`; `not_applied` is a failure to fix, not a warning.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
By hand:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
"<python>" - <<'PY'
|
|
@@ -49,19 +49,19 @@ for name, field in fields.items():
|
|
|
49
49
|
PY
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Radio groups
|
|
52
|
+
Radio groups fail most: pypdf updates the group `/V` but leaves the old button's `/AS`, so two look selected. The script resyncs every widget's `/AS` from the group value; do the same by hand.
|
|
53
53
|
|
|
54
54
|
## Flatten
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Flatten when the file must stay uneditable: fill with `flatten=True` (the value is painted into page content), remove every widget annotation (`subtypes="/Widget"`), and drop `/AcroForm` from the catalog. Then confirm no `/Widget` and no `/AcroForm` remain; otherwise readers still treat the file as editable.
|
|
57
57
|
|
|
58
58
|
## What cannot be filled, and the fix
|
|
59
59
|
|
|
60
|
-
- **
|
|
61
|
-
- **A signature field (`/FT /Sig`)**:
|
|
62
|
-
- **
|
|
63
|
-
- **A flat, scanned form**: no field tree
|
|
60
|
+
- **No appearance stream and a broken `/DA`**: the reader may not show a new value. Render after filling; if invisible, rebuild that widget's appearance with reportlab and merge it over the page.
|
|
61
|
+
- **A signature field (`/FT /Sig`)**: needs a certificate and a signer this runtime does not ship, and text on a signed field invalidates the signature. Say it cannot be created here; it needs a certificate plus a signing tool.
|
|
62
|
+
- **Values drawn as static text**: not a form. If `page.get_text()` shows the labels and lines and `reader.get_fields()` is empty, treat it as an edit job.
|
|
63
|
+
- **A flat, scanned form**: no field tree; draw text at coordinates (a stamp overlay) and let the render decide whether it lines up.
|
|
64
64
|
|
|
65
65
|
## Verify
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Reopen, check `/V`, check `/AP`, then `"<python>" "<cli>" pdf render --input "<file-filled.pdf>" --out-dir "render"` and `read_image`. The value must be visible inside its widget box, not clipped or over the border.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Read text, tables, and page images
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Extraction reads the page content stream; glyph runs carry position and font, which a library groups into words and lines. A page without text operators (a scan, a photo export) yields nothing, whatever the extractor — a property of the file, not of the tool.
|
|
4
4
|
|
|
5
5
|
## Fast path: plain text and structure
|
|
6
6
|
|
|
@@ -16,7 +16,7 @@ Returns `page_count`, `word_count`, document metadata, per-page text (with chara
|
|
|
16
16
|
"<python>" "<skill>/scripts/extract_text.py" --input "<file.pdf>" --json-out "<tables.json>"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Detection uses the page's ruling lines and word spacing, so a borderless table often comes back as running text; `--layout` keeps the original line spacing for a manual look.
|
|
20
20
|
|
|
21
21
|
When the detection looks wrong, inspect rather than guess:
|
|
22
22
|
|
|
@@ -32,11 +32,11 @@ with pdfplumber.open("in.pdf") as pdf:
|
|
|
32
32
|
PY
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
`chars`, `lines`, and `rects` tell you whether the table has rules at all.
|
|
35
|
+
`chars`, `lines`, and `rects` tell you whether the table has rules at all. With no lines, read it from `extract_text` with `--layout`, where column gaps survive as spaces.
|
|
36
36
|
|
|
37
37
|
## Page images
|
|
38
38
|
|
|
39
|
-
A page may
|
|
39
|
+
A page may embed rasters that text extraction does not show. List them with PyMuPDF:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
"<python>" - <<'PY'
|
|
@@ -48,15 +48,15 @@ for number, page in enumerate(doc, 1):
|
|
|
48
48
|
PY
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
Extract one with `page.get_pixmap(clip=...)` or `doc.extract_image(xref)` when the user needs the picture itself.
|
|
51
|
+
Extract one with `page.get_pixmap(clip=...)` or `doc.extract_image(xref)` when the user needs the picture itself. To *see* it, render the page at 150–200 dpi and crop with `read_image`.
|
|
52
52
|
|
|
53
53
|
## What fails and how to resolve it
|
|
54
54
|
|
|
55
|
-
- **Scanned page, no text**:
|
|
56
|
-
- **Empty string from a page that shows text**:
|
|
57
|
-
- **Wrong reading order in multi-column layouts**:
|
|
55
|
+
- **Scanned page, no text**: no text layer exists; say so, with resolutions from `references/limits.md`. Never claim extraction succeeded on an empty result.
|
|
56
|
+
- **Empty string from a page that shows text**: broken ToUnicode map, or text drawn as vector outlines. Check the render; outlines cannot be recovered by extraction.
|
|
57
|
+
- **Wrong reading order in multi-column layouts**: extraction follows operator order. Crop each column with a rect and extract per column; `get_textbox(rect)` on a PyMuPDF page is the simplest split.
|
|
58
58
|
- **Ligatures and soft hyphens**: extraction may glue or split words; quote extracted text only after reading the same passage on the render.
|
|
59
59
|
|
|
60
60
|
## Verify
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
The render is the check: the characters you return must be visible in it. Before building on extracted text, check for a page whose text is suspiciously short (missing text layer), duplicated (headers repeated per page), or full of replacement glyphs (broken encoding).
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Redact a region of a page
|
|
2
2
|
|
|
3
|
-
Redaction
|
|
3
|
+
Redaction deletes glyphs and vector paths inside the rectangle from the content stream, so no extractor recovers them. A black box that covers text is not a redaction: "black out then flatten" leaks the original.
|
|
4
4
|
|
|
5
5
|
## Fast path: the region script
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
"<python>" "<skill>/scripts/redact_region.py" --input "<file.pdf>" --out "<file-redacted.pdf>"
|
|
9
|
-
--annotations '{"page":1,"region":{"left":0.064,"top":0.318,"width":0.891,"height":0.153}}'
|
|
8
|
+
"<python>" "<skill>/scripts/redact_region.py" --input "<file.pdf>" --out "<file-redacted.pdf>" \\
|
|
9
|
+
--annotations '{"page":1,"region":{"left":0.064,"top":0.318,"width":0.891,"height":0.153}}' \\
|
|
10
10
|
--json-out "<redact.json>"
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The payload is
|
|
13
|
+
The payload is a 1-based `page` plus a region normalised 0–1 against the rendered page. The script converts it, expands the rectangle by `--pad` points (default 2.5), calls `add_redact_annot(rect, fill=None)` then `apply_redactions(images=PDF_REDACT_IMAGE_NONE, graphics=PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED, text=PDF_REDACT_TEXT_REMOVE)`, and reports `removed_chars`, `removed_drawings`, per-page before/after counts, and `leftover_chars_in_rect`. `--region`/`--page` take single values without JSON; multiple annotations apply page by page.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Padding
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Glyphs overhang the geometric text box and preview boxes are approximate. Expand by 2–3 points to catch ascenders, descenders, rules touching the text, and the anti-aliased edge without touching neighbours. Measure: `get_textbox(rect)` lists what the rectangle takes; `leftover_chars_in_rect` must be zero afterwards.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
By hand, for another shape or a dry run:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
"<python>" - <<'PY'
|
|
@@ -40,26 +40,26 @@ PY
|
|
|
40
40
|
|
|
41
41
|
## The flags are not interchangeable
|
|
42
42
|
|
|
43
|
-
- `fill=None` erases without painting.
|
|
44
|
-
- `images=PDF_REDACT_IMAGE_NONE` leaves pictures untouched
|
|
45
|
-
- `graphics=PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED` deletes paths
|
|
46
|
-
- Text removal is the default (`PDF_REDACT_TEXT_REMOVE`); keeping
|
|
43
|
+
- `fill=None` erases without painting. `fill=(1, 1, 1)` paints a rectangle that becomes a new drawing and hides the page's background tint; the drawing count goes up, not down.
|
|
44
|
+
- `images=PDF_REDACT_IMAGE_NONE` leaves pictures untouched, so a scanned region stays visible. `PDF_REDACT_IMAGE_PIXELS` blanks the pixels a rectangle covers inside an image; use a stronger setting only when the whole image should go.
|
|
45
|
+
- `graphics=PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED` deletes intersecting paths; paths entirely inside are always removed. `PDF_REDACT_LINE_ART_NONE` keeps all line art, for text-only removal.
|
|
46
|
+
- Text removal is the default (`PDF_REDACT_TEXT_REMOVE`); keeping text while erasing its surroundings is almost never what the user means.
|
|
47
47
|
|
|
48
48
|
## Counting the result
|
|
49
49
|
|
|
50
|
-
`removed_chars` is the page-level difference in extracted text; `region_chars_removed` is
|
|
50
|
+
`removed_chars` is the page-level difference in extracted text; `region_chars_removed` is what was inside the rectangle. They differ by the newline or space joining the block to its neighbours: expect equal or one apart. Fewer characters removed than the region held means the rectangle missed part of the block — enlarge `--pad` or redo the geometry.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Sample: page 1 region `{left: 0.064, top: 0.318, width: 0.891, height: 0.153}` deletes the "Latest News" block alone — 16 drawings → 5, page text 1049 → 879 (block 169), `leftover_chars_in_rect` 0, and the body paragraph, header, and footer still extract and render.
|
|
53
53
|
|
|
54
54
|
## Leftovers redaction does not cover
|
|
55
55
|
|
|
56
|
-
- **Metadata and embedded files**: the info dictionary and attachments are not page content
|
|
57
|
-
- **Previous revisions**: incrementally saved files
|
|
58
|
-
- **Images inside the region**:
|
|
56
|
+
- **Metadata and embedded files**: the info dictionary and attachments are not page content; clear them for a clean file (`scripts/metadata.py`). An ID/xref may still fingerprint the file.
|
|
57
|
+
- **Previous revisions**: incrementally saved files keep old objects; save with `garbage=3, deflate=True`. For forensic certainty, say the output was rebuilt and confirm by text search that the words are gone.
|
|
58
|
+
- **Images inside the region**: they stay by default. For a screenshot of the sensitive text, use `--images pixels` or rebuild the page.
|
|
59
59
|
|
|
60
60
|
## Verify
|
|
61
61
|
|
|
62
|
-
Render
|
|
62
|
+
Render the page: the region empty, the surrounding text intact, and ruled boxes of the block gone rather than half-erased. Then search the output for the removed words:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
"<python>" - <<'PY'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Stamp a footer, header, or label
|
|
2
2
|
|
|
3
|
-
An overlay is a
|
|
3
|
+
An overlay is a small one-page PDF drawn with reportlab and merged onto the target page, so the original content stream is untouched and existing widgets keep working. Positions are points from the bottom-left of the page box; an A4 page is 842 points tall.
|
|
4
4
|
|
|
5
5
|
## Fast path: the stamp script
|
|
6
6
|
|
|
@@ -9,11 +9,11 @@ An overlay is a tiny one-page PDF drawn with reportlab and merged onto the targe
|
|
|
9
9
|
--text "Confidential - page {page} of {pages}" --position bottom-center --render-dir "<render>"
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
`{page}` and `{pages}` are substituted per page. Positions: `bottom-left`, `bottom-center`, `bottom-right`, `top-left`, `top-center`, `top-right
|
|
12
|
+
`{page}` and `{pages}` are substituted per page. Positions: `bottom-left`, `bottom-center`, `bottom-right`, `top-left`, `top-center`, `top-right`. `--pages "1,3-5"` limits the stamp; `--font-size`, `--font`, `--color`, `--margin`, and `--opacity` control the look. The script merges page by page with pypdf, refuses to overwrite the input, and can render the stamped pages for a look.
|
|
13
13
|
|
|
14
14
|
## Draw it yourself
|
|
15
15
|
|
|
16
|
-
Use reportlab directly
|
|
16
|
+
Use reportlab directly for a bar, a rotated line, or a block of text:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
"<python>" - <<'PY'
|
|
@@ -45,16 +45,16 @@ with open("out.pdf", "wb") as handle:
|
|
|
45
45
|
PY
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Use
|
|
48
|
+
Use each page's own `mediabox` when sizes may mix; a stamp drawn for A4 lands in the wrong place on a landscape page. `merge_page` paints over the page, so a solid bar hides what it covers — put it in a margin or inside a band you mean to cover.
|
|
49
49
|
|
|
50
50
|
## Pitfalls
|
|
51
51
|
|
|
52
|
-
- **
|
|
53
|
-
- **Rotated pages**: a
|
|
54
|
-
- **Repeated stamping**: merging the same overlay twice doubles the opacity
|
|
55
|
-
- **Fonts**: Helvetica handles Latin; a CJK stamp needs a registered TTF,
|
|
56
|
-
- **Opacity**: `setFillAlpha`
|
|
52
|
+
- **Existing footers**: check the bottom margin first; the render is the proof, not the coordinate math. If a page number is already there, skip the stamp or use the opposite side.
|
|
53
|
+
- **Rotated pages**: a `/Rotate 90` page renders sideways, so text drawn unrotated appears rotated. Rotate the stamp (`c.rotate(90)`, adjusting the origin), or rotate the page back first.
|
|
54
|
+
- **Repeated stamping**: merging the same overlay twice doubles the opacity at the same position; re-run from the original input when iterating.
|
|
55
|
+
- **Fonts**: Helvetica handles Latin; a CJK stamp needs a registered TTF, or the text renders as blank boxes. Check the render.
|
|
56
|
+
- **Opacity**: `setFillAlpha` works only in viewers that honour transparency groups; a stamp that must show on any viewer is safer at full opacity.
|
|
57
57
|
|
|
58
58
|
## Verify
|
|
59
59
|
|
|
60
|
-
Render every stamped page
|
|
60
|
+
Render every stamped page: the text sits inside the margin, does not overlap body text or an existing footer, is the size the user asked for, and appears on exactly the selected pages. Then `"<python>" "<cli>" pdf read --input "<file-stamped.pdf>"` — the stamp text joins the page text, so searching for the label confirms it landed once, not twice.
|
|
@@ -1,94 +1,80 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Create, edit, inspect, and verify PowerPoint .pptx slide decks — slides, templates, charts, render — with python-pptx and the bundled Office helper CLI.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Presentations (.pptx)
|
|
6
6
|
|
|
7
|
-
A
|
|
8
|
-
workbooks, notes, media
|
|
9
|
-
|
|
10
|
-
before delivering.
|
|
7
|
+
A `.pptx` is a zip of XML parts — slides, layouts, masters, charts with embedded
|
|
8
|
+
workbooks, notes, media — and the reader sees only those parts. Author them, then
|
|
9
|
+
read the file back.
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
cover ([`recipes/`](recipes/README.md)), runnable tools for mutation and gates
|
|
19
|
-
in [`container_tools/`](container_tools/), template-following in
|
|
20
|
-
[`template_following_scripts/`](template_following_scripts/).
|
|
21
|
-
|
|
22
|
-
Authoring is `python-pptx`. Not a page-description format, not a screenshot: real
|
|
23
|
-
shapes, real chart parts, real notes.
|
|
11
|
+
`<python> "<cli>" pptx {create|read|render}` plus `office_status` builds from a
|
|
12
|
+
JSON spec, reads structure and renders; it does not mutate. Everything else is
|
|
13
|
+
here: [recipes](recipes/README.md), mutation tools and gates in
|
|
14
|
+
[container_tools/](container_tools/), templates in
|
|
15
|
+
[template_following_scripts/](template_following_scripts/). Author with
|
|
16
|
+
`python-pptx` — real shapes, chart parts, notes, never a screenshot.
|
|
24
17
|
|
|
25
18
|
## Before any file work
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
claim a deck was created or verified without a runtime to create and verify
|
|
33
|
-
it with.
|
|
20
|
+
Call `office_status`. Read the `## Office runtime` JSON block appended to this
|
|
21
|
+
file for the absolute `python`, `cli`, `skill` and `shared` values; quote paths.
|
|
22
|
+
No working runtime → stop, give the user the `node "..."` remedy command it
|
|
23
|
+
names, call `office_status` again. Never claim a deck was created or verified
|
|
24
|
+
without a runtime.
|
|
34
25
|
|
|
35
26
|
## Delivery
|
|
36
27
|
|
|
37
|
-
|
|
28
|
+
`present` the finished file, then state the change in one sentence. A bare path
|
|
29
|
+
is not delivery: chat renders `.docx`/`.pptx`/`.xlsx`/`.pdf` links as
|
|
30
|
+
"二进制文件,无法显示改动".
|
|
38
31
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
32
|
+
- Create: write into the user's workspace, verify, `present`.
|
|
33
|
+
- Edit: never overwrite the original. Write `<original>-edited.<ext>` (or the
|
|
34
|
+
user's name), verify, `present`, and give both paths.
|
|
41
35
|
|
|
42
36
|
## Annotations
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
An `office-annotations` block — `[{ page, text, requirement, region: { left,
|
|
39
|
+
top, width, height } }]` — is a change request, not editor state. `page` is the
|
|
40
|
+
1-based preview slide; `region` is 0–1 on it, mapped to inches by
|
|
41
|
+
`left_in = region.left * slide_width_in`. Select shapes by bounding-box overlap,
|
|
42
|
+
`text` breaking ties. Apply the smallest edit satisfying `requirement`, re-render,
|
|
43
|
+
confirm. Unmappable → say what you cannot locate; never guess.
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`left_in = region.left * slide_width_in`, and likewise for top/width/height.
|
|
51
|
-
`text` disambiguates overlapping shapes (a title over a background panel).
|
|
52
|
-
Resolve candidates from
|
|
53
|
-
`container_tools/inspect_presentation_layout_geometry.py --json-out geometry.json`,
|
|
54
|
-
then apply the edit through a mutation tool or a recipe. Full procedure:
|
|
55
|
-
[tasks/apply-annotations.md](tasks/apply-annotations.md).
|
|
45
|
+
Full procedure, mapping arithmetic and the edit table:
|
|
46
|
+
[tasks/apply-annotations.md](tasks/apply-annotations.md). Candidates come from
|
|
47
|
+
`container_tools/inspect_presentation_layout_geometry.py --json-out
|
|
48
|
+
geometry.json`.
|
|
56
49
|
|
|
57
50
|
## Verify
|
|
58
51
|
|
|
59
|
-
Render, look at every page, then deliver
|
|
52
|
+
Render, look at every page, then deliver.
|
|
60
53
|
|
|
61
54
|
```bash
|
|
62
55
|
"<python>" "<skill>/container_tools/render_slides.py" --input "<file.pptx>" --out-dir "<render-dir>"
|
|
63
56
|
```
|
|
64
57
|
|
|
65
|
-
`render_slides.py` uses the CLI's `pptx render`
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
used in `engine` (`libreoffice-kit` means a faithful render).
|
|
58
|
+
`render_slides.py` uses the CLI's `pptx render` (DSH LibreOffice kit) and falls
|
|
59
|
+
back to a draft preview without the kit, so it is always the right entry point.
|
|
60
|
+
JSON `engine: "libreoffice-kit"` means a faithful render.
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
`"<skill>/template_following_scripts/make_contact_sheet.py"
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
`read_image` every PNG, or one sheet via
|
|
63
|
+
`"<skill>/template_following_scripts/make_contact_sheet.py"`. Check: titles one
|
|
64
|
+
or two lines, body ≥ 18 pt, nothing clipped or off-slide, no empty placeholder,
|
|
65
|
+
table and chart not overlapping, chart readable at thumbnail size.
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
typography — say which one you used.
|
|
67
|
+
No office suite: `--engine draft` is a PyMuPDF geometry preview — placement, not
|
|
68
|
+
typography. Say which you used.
|
|
78
69
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`
|
|
82
|
-
`native_quantitative_chart_gate.py`, `native_table_arithmetic_gate.py`,
|
|
83
|
-
`template_source_fidelity_gate.py`. Details:
|
|
84
|
-
[tasks/inspect-and-verify.md](tasks/inspect-and-verify.md).
|
|
70
|
+
The six blocking gates and their flags are owned by
|
|
71
|
+
[tasks/inspect-and-verify.md](tasks/inspect-and-verify.md); a gate that reports
|
|
72
|
+
`passed: false` stops delivery.
|
|
85
73
|
|
|
86
74
|
Never present a deck you have not rendered and looked at.
|
|
87
75
|
|
|
88
76
|
## Tasks
|
|
89
77
|
|
|
90
|
-
One file per job:
|
|
91
|
-
|
|
92
78
|
| Job | Start here |
|
|
93
79
|
|---|---|
|
|
94
80
|
| Create a deck | [tasks/create-deck.md](tasks/create-deck.md) |
|
|
@@ -100,32 +86,27 @@ One file per job:
|
|
|
100
86
|
|
|
101
87
|
## Tools and recipes
|
|
102
88
|
|
|
103
|
-
Mutations
|
|
104
|
-
`<original>-edited.pptx
|
|
89
|
+
Mutations: one JSON object, non-zero exit on failure, default
|
|
90
|
+
`<original>-edited.pptx`.
|
|
105
91
|
|
|
106
92
|
| Need | Command |
|
|
107
93
|
|---|---|
|
|
108
|
-
|
|
|
94
|
+
| Replace text, run-level | `"<skill>/container_tools/replace_text.py" --input "<deck>" --find "old" --replace "new"` |
|
|
109
95
|
| Append slides | `"<skill>/container_tools/add_slides.py" --input "<deck>" --spec "<slides.json>"` |
|
|
110
|
-
|
|
|
111
|
-
| Render
|
|
112
|
-
| Contact sheet
|
|
96
|
+
| Core properties | `"<skill>/container_tools/core_properties.py" --input "<deck>" --title "..."` |
|
|
97
|
+
| Render | `"<skill>/container_tools/render_slides.py" --input "<deck>" --out-dir "<dir>"` |
|
|
98
|
+
| Contact sheet | `"<skill>/template_following_scripts/make_contact_sheet.py" --dir "<dir>" --out "<sheet.png>"` |
|
|
113
99
|
|
|
114
|
-
Template flow: `inspect_template_deck.py` →
|
|
100
|
+
Template flow: `inspect_template_deck.py` → frame map →
|
|
115
101
|
`validate_template_plan.py` → `prepare_template_starter_deck.py` →
|
|
116
102
|
`check_template_fidelity.py`.
|
|
117
103
|
|
|
118
|
-
Recipes (
|
|
119
|
-
[
|
|
120
|
-
[
|
|
121
|
-
[
|
|
122
|
-
[
|
|
123
|
-
[
|
|
124
|
-
[masters and layouts](recipes/master-and-layouts.md) ·
|
|
125
|
-
[speaker notes](recipes/speaker-notes.md) ·
|
|
126
|
-
[theme tokens](recipes/theme-tokens.md) ·
|
|
127
|
-
[rich text runs](recipes/rich-text-runs.md) ·
|
|
128
|
-
[images](recipes/images.md) ·
|
|
104
|
+
Recipes: [README](recipes/README.md) · [textbox at a
|
|
105
|
+
coordinate](recipes/textbox-at-coordinate.md) · [auto-layout](recipes/autolayout.md) ·
|
|
106
|
+
[tables](recipes/tables.md) · [native charts](recipes/native-charts.md) ·
|
|
107
|
+
[masters and layouts](recipes/master-and-layouts.md) · [speaker
|
|
108
|
+
notes](recipes/speaker-notes.md) · [theme tokens](recipes/theme-tokens.md) ·
|
|
109
|
+
[rich text runs](recipes/rich-text-runs.md) · [images](recipes/images.md) ·
|
|
129
110
|
[connectors](recipes/connectors.md).
|
|
130
111
|
|
|
131
112
|
Guidance: [authoring workflow](references/authoring-workflow.md) ·
|
|
@@ -135,31 +116,30 @@ Guidance: [authoring workflow](references/authoring-workflow.md) ·
|
|
|
135
116
|
[templates](references/templates.md) ·
|
|
136
117
|
[finalization](references/finalization.md) ·
|
|
137
118
|
[finance decks](domain_guidance/finance.md) ·
|
|
138
|
-
[Google Slides
|
|
119
|
+
[Google Slides](routing/google_slides.md).
|
|
139
120
|
|
|
140
121
|
## Non-negotiables
|
|
141
122
|
|
|
142
123
|
- One idea per slide; a title is a claim, not a label.
|
|
143
124
|
- Body ≥ 18 pt, title ≥ 28 pt, captions ≥ 12 pt.
|
|
144
125
|
- Native charts and tables, never a picture of a spreadsheet.
|
|
145
|
-
- Every placeholder filled or deleted; no "Chart Title", no trailing full stop
|
|
146
|
-
|
|
147
|
-
- Numbers in the deck
|
|
148
|
-
- New output
|
|
126
|
+
- Every placeholder filled or deleted; no "Chart Title", no trailing full stop on
|
|
127
|
+
a heading.
|
|
128
|
+
- Numbers in the deck agree with the numbers supplied.
|
|
129
|
+
- New output in the user's workspace; an edit is a new file.
|
|
149
130
|
|
|
150
131
|
## Genuine gaps
|
|
151
132
|
|
|
152
|
-
-
|
|
153
|
-
render
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- Fonts the user
|
|
157
|
-
|
|
158
|
-
- Google Slides import fidelity is
|
|
133
|
+
- DSH LibreOffice kit missing (`office_status` reports `renderers.available: false`) →
|
|
134
|
+
no true render. Use `--engine draft` and say the check was geometric; only a harness
|
|
135
|
+
install shipping the kit fixes it (the plugin records its path in `config.json`
|
|
136
|
+
on load), not a standalone Office/LibreOffice.
|
|
137
|
+
- Fonts the user lacks are substituted by any renderer; the pack cannot install
|
|
138
|
+
them.
|
|
139
|
+
- Google Slides import fidelity is outside our control; see
|
|
159
140
|
[routing/google_slides.md](routing/google_slides.md).
|
|
160
141
|
|
|
161
142
|
## Shared workflow
|
|
162
143
|
|
|
163
|
-
Use `"<shared>/common-workflow.md"` for the cross-format delivery and
|
|
164
|
-
|
|
165
|
-
annotation payload into an edit.
|
|
144
|
+
Use `"<shared>/common-workflow.md"` for the cross-format delivery loop, and
|
|
145
|
+
`"<shared>/annotation-recipes.md"` to turn an annotation payload into an edit.
|
|
@@ -58,7 +58,7 @@ def rasterise_pdf(pdf_path: Path, out_dir: Path, dpi: int) -> list[str]:
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def render_with_office(source: Path, out_dir: Path, dpi: int) -> dict:
|
|
61
|
-
"""Export through the CLI, which
|
|
61
|
+
"""Export through the CLI, which renders with the bundled LibreOffice kit."""
|
|
62
62
|
scratch = Path(tempfile.mkdtemp(prefix="dsh-render-"))
|
|
63
63
|
try:
|
|
64
64
|
completed = subprocess.run(
|