dsh-office-kit 0.4.2 → 0.4.4
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/scripts/add_tracked_replacements.py +4 -3
- package/assets/skills/office-documents/scripts/docx_kit.py +68 -27
- package/assets/skills/office-documents/scripts/heading_audit.py +3 -4
- package/assets/skills/office-documents/scripts/internal_nav.py +3 -1
- package/assets/skills/office-documents/scripts/merge_docx_append.py +104 -0
- package/assets/skills/office-documents/scripts/table_geometry.py +13 -3
- package/assets/skills/office-documents/scripts/watermark_add.py +6 -5
- package/assets/skills/office-documents/scripts/watermark_audit_remove.py +6 -4
- 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 +10 -14
- 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/preview_render.py +4 -1
- 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
- package/python/office_cli.py +38 -4
|
@@ -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.
|
|
@@ -19,6 +19,7 @@ Exit codes: 0 ok, 2 unreadable input, 3 nothing rasterised.
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import json
|
|
22
|
+
import math
|
|
22
23
|
import sys
|
|
23
24
|
from pathlib import Path
|
|
24
25
|
from typing import Any
|
|
@@ -254,7 +255,9 @@ def _draw_chart(page, shape, rect: fitz.Rect, slide_no: int, notes: list[dict[st
|
|
|
254
255
|
for index, value in enumerate(values):
|
|
255
256
|
sweep = 360.0 * abs(value) / total
|
|
256
257
|
shade = shades[index % len(shades)]
|
|
257
|
-
|
|
258
|
+
# PyMuPDF 1.28 dropped Page.draw_pie; draw_sector has been stable since 1.16.
|
|
259
|
+
edge = fitz.Point(center.x + radius * math.cos(math.radians(start)), center.y + radius * math.sin(math.radians(start)))
|
|
260
|
+
page.draw_sector(center, edge, sweep, color=RULE, fill=(shade, shade, shade * 0.95 + 0.05), width=0.6)
|
|
258
261
|
start += sweep
|
|
259
262
|
return
|
|
260
263
|
|
|
@@ -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(
|
|
@@ -5,14 +5,14 @@ The mechanics are the same as any deck; the failure modes are specific.
|
|
|
5
5
|
## What a finance audience checks first
|
|
6
6
|
|
|
7
7
|
1. Does the total add up? A slide whose numbers do not sum loses the argument
|
|
8
|
-
before
|
|
8
|
+
before it starts. Run the arithmetic gate on every declared total.
|
|
9
9
|
2. Is the period stated? "Revenue up 12%" without a base period is not a claim.
|
|
10
|
-
3. Is the unit stated? Thousands, millions, currency,
|
|
11
|
-
|
|
12
|
-
4. Is the comparison like-for-like? Full quarter against full quarter; do not
|
|
13
|
-
|
|
14
|
-
5. Does the chart axis start at zero? For bars,
|
|
15
|
-
|
|
10
|
+
3. Is the unit stated? Thousands, millions, currency, gross or net. Put it in the
|
|
11
|
+
header, once.
|
|
12
|
+
4. Is the comparison like-for-like? Full quarter against full quarter; do not put
|
|
13
|
+
a 13-week quarter beside a 12-week one without saying so.
|
|
14
|
+
5. Does the chart axis start at zero? For bars, always — a truncated bar chart in
|
|
15
|
+
a finance deck reads as manipulation even when it is not.
|
|
16
16
|
|
|
17
17
|
## The financial slide kit
|
|
18
18
|
|
|
@@ -30,11 +30,11 @@ The mechanics are the same as any deck; the failure modes are specific.
|
|
|
30
30
|
- Two significant figures is usually enough for a headline (`£2.4m`, not
|
|
31
31
|
`£2,384,117`). Keep full precision in the appendix table.
|
|
32
32
|
- Percentages: one decimal at most, and say what they are a percentage *of*.
|
|
33
|
-
- Round consistently: if the headline says 2.4m, the table should not say
|
|
34
|
-
|
|
35
|
-
- Negative numbers:
|
|
36
|
-
|
|
37
|
-
- A variance is meaningless without its sign and
|
|
33
|
+
- Round consistently: if the headline says 2.4m, the table should not say 2.38m
|
|
34
|
+
in one row and 2.4m in the next.
|
|
35
|
+
- Negative numbers: one minus sign convention (`−12%`), parentheses kept for
|
|
36
|
+
tables where accounting convention is expected.
|
|
37
|
+
- A variance is meaningless without its sign and direction; state whether
|
|
38
38
|
favourable is up or down (revenue vs cost).
|
|
39
39
|
|
|
40
40
|
## Charts that survive a CFO
|
|
@@ -45,14 +45,14 @@ The mechanics are the same as any deck; the failure modes are specific.
|
|
|
45
45
|
- One series per chart unless the comparison *is* the point.
|
|
46
46
|
- Do not chart a table of six numbers; the table is more honest and faster to
|
|
47
47
|
read.
|
|
48
|
-
- Forecast periods are visually distinct (lighter fill, dashed line) and named
|
|
49
|
-
|
|
48
|
+
- Forecast periods are visually distinct (lighter fill, dashed line) and named in
|
|
49
|
+
the caption.
|
|
50
50
|
|
|
51
51
|
## Provenance
|
|
52
52
|
|
|
53
53
|
Every non-obvious number gets a note with: source file or system, extraction
|
|
54
|
-
date, period covered,
|
|
55
|
-
|
|
54
|
+
date, period covered, any adjustment made. The speaker notes are the right place;
|
|
55
|
+
a footnote-heavy slide is a slide nobody reads.
|
|
56
56
|
|
|
57
57
|
## Check before delivery
|
|
58
58
|
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# Slide mechanics and paste-runnable recipes
|
|
2
2
|
|
|
3
|
-
The helper CLI covers the common path:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Read the mechanics first, then paste the recipe. Every snippet runs as-is from a
|
|
9
|
-
shell, from any working directory:
|
|
3
|
+
The helper CLI covers the common path: title, bullets, table, chart, picture.
|
|
4
|
+
Everything else — exact coordinates, master edits, notes, theme tokens,
|
|
5
|
+
connectors — is `python-pptx` work. These recipes are the route and run as-is
|
|
6
|
+
from a shell, from any working directory:
|
|
10
7
|
|
|
11
8
|
```bash
|
|
12
9
|
"<python>" - <<'PY'
|
|
@@ -21,17 +18,16 @@ PY
|
|
|
21
18
|
|---|---|
|
|
22
19
|
| Where is this shape? | `left`, `top`, `width`, `height` in **EMU** (English Metric Units). |
|
|
23
20
|
| What is an inch? | `914400` EMU. `pptx.util.Inches(1)` is the polite wrapper. |
|
|
24
|
-
| What is a point? | `12700` EMU. Font sizes use points, geometry uses EMU
|
|
21
|
+
| What is a point? | `12700` EMU. Font sizes use points, geometry uses EMU; mixing them silently breaks layout. |
|
|
25
22
|
| What is a slide? | `prs.slide_width` / `prs.slide_height`; check them, never assume 16:9. |
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
`Pt(28) == Emu(355600)`, and a fraction of the slide is
|
|
24
|
+
`Emu(914400) == Inches(1)`, `Pt(28) == Emu(355600)`, and a slide fraction is
|
|
29
25
|
`int(0.1 * prs.slide_width)`.
|
|
30
26
|
|
|
31
27
|
## Z-order
|
|
32
28
|
|
|
33
|
-
Shapes paint in
|
|
34
|
-
|
|
29
|
+
Shapes paint in `slide.shapes` order; later covers earlier. Reorder without
|
|
30
|
+
deleting:
|
|
35
31
|
|
|
36
32
|
```python
|
|
37
33
|
tree = slide.shapes._spTree
|
|
@@ -40,32 +36,25 @@ tree.remove(shape._element)
|
|
|
40
36
|
tree.insert(2, shape._element) # after nvGrpSpPr and grpSpPr
|
|
41
37
|
```
|
|
42
38
|
|
|
43
|
-
Index `2` is the first painted shape
|
|
44
|
-
image belongs.
|
|
39
|
+
Index `2` is the first painted shape — where a full-bleed background belongs.
|
|
45
40
|
|
|
46
41
|
## What this pack ships
|
|
47
42
|
|
|
48
43
|
| Recipe | Use it when |
|
|
49
44
|
|---|---|
|
|
50
45
|
| [textbox-at-coordinate](textbox-at-coordinate.md) | Text must land on an exact rect, not "somewhere under the title". |
|
|
51
|
-
| [autolayout](autolayout.md) | The same build
|
|
52
|
-
| [tables](tables.md) | Numbers need
|
|
53
|
-
| [native-charts](native-charts.md) | Data must stay editable, not
|
|
54
|
-
| [master-and-layouts](master-and-layouts.md) |
|
|
55
|
-
| [speaker-notes](speaker-notes.md) |
|
|
56
|
-
| [theme-tokens](theme-tokens.md) | Colours and fonts
|
|
57
|
-
| [rich-text-runs](rich-text-runs.md) | One paragraph mixes bold, accent colour
|
|
58
|
-
| [images](images.md) | A
|
|
59
|
-
| [connectors](connectors.md) |
|
|
60
|
-
|
|
61
|
-
## Order of work
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
3. Set type sizes by the *smallest* screen the deck will be shown on; body 18 pt
|
|
67
|
-
and up, footnotes 12 pt and up.
|
|
68
|
-
4. Fill every placeholder you keep and delete the ones you do not — an empty
|
|
69
|
-
placeholder prints "Click to add text" in edit view and looks unfinished.
|
|
70
|
-
5. Render, look, then fix. `container_tools/render_slides.py` plus `read_image`
|
|
71
|
-
is the loop; a deck nobody looked at is not verified.
|
|
46
|
+
| [autolayout](autolayout.md) | The same build must fit 3 bullets or 9 without hand-tuning. |
|
|
47
|
+
| [tables](tables.md) | Numbers need column widths and per-cell formatting. |
|
|
48
|
+
| [native-charts](native-charts.md) | Data must stay editable, not a chart picture. |
|
|
49
|
+
| [master-and-layouts](master-and-layouts.md) | Whole-deck changes: title bar, accent line, footer. |
|
|
50
|
+
| [speaker-notes](speaker-notes.md) | A script attached to each slide. |
|
|
51
|
+
| [theme-tokens](theme-tokens.md) | Colours and fonts from one place. |
|
|
52
|
+
| [rich-text-runs](rich-text-runs.md) | One paragraph mixes bold, accent colour, footnote. |
|
|
53
|
+
| [images](images.md) | A crop, a cover fit or real alt text. |
|
|
54
|
+
| [connectors](connectors.md) | A line between boxes that survives a move. |
|
|
55
|
+
|
|
56
|
+
## Order of work
|
|
57
|
+
|
|
58
|
+
Follow the loop in [authoring-workflow](../references/authoring-workflow.md):
|
|
59
|
+
story → frame → place top-down in EMU → fill or delete placeholders → render and
|
|
60
|
+
look. Type sizes live in [style-guidelines](../references/style-guidelines.md).
|
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Mechanics
|
|
4
4
|
|
|
5
|
-
PowerPoint has two
|
|
6
|
-
trip:
|
|
5
|
+
PowerPoint has two "autofit" ideas; mixing them up wastes a round trip:
|
|
7
6
|
|
|
8
7
|
- `word_wrap` decides whether text wraps inside the width. Turn it on for every
|
|
9
8
|
authored box.
|
|
10
9
|
- `auto_size` decides whether the *frame* follows the text (`MSO_AUTO_SIZE.
|
|
11
10
|
SHAPE_TO_FIT_TEXT`) or the text shrinks to fit the frame
|
|
12
11
|
(`TEXT_TO_FIT_SHAPE`). `TEXT_TO_FIT_SHAPE` is a **render-time** behaviour:
|
|
13
|
-
PowerPoint recalculates it when the file
|
|
14
|
-
|
|
12
|
+
PowerPoint recalculates it when the file opens, so a deck that looks fine in a
|
|
13
|
+
draft preview can still shrink to 8 pt on a real screen.
|
|
15
14
|
|
|
16
|
-
The reliable pattern
|
|
17
|
-
|
|
15
|
+
The reliable pattern: fixed frame, explicit sizes, content measured against the
|
|
16
|
+
frame, no text-to-fit. Estimate, then verify by rendering.
|
|
18
17
|
|
|
19
18
|
## Recipe: fit a variable number of bullets
|
|
20
19
|
|
|
@@ -66,7 +65,7 @@ PY
|
|
|
66
65
|
## Rules that hold regardless of content
|
|
67
66
|
|
|
68
67
|
1. Body text is 18 pt or larger; below that the back row cannot read it.
|
|
69
|
-
2. Six bullets or three lines of body is
|
|
68
|
+
2. Six bullets or three lines of body is where a slide should split.
|
|
70
69
|
3. Longer text shrinks the number of *words*, not the type size. If the fit loop
|
|
71
70
|
lands below the body floor, cut copy or split the slide.
|
|
72
71
|
4. Keep 0.5 in of bottom margin for the footer and for projectors that crop.
|
|
@@ -77,12 +76,12 @@ PY
|
|
|
77
76
|
- `frame.text` then `paragraph.font.size` on paragraph 0 only: a multi-paragraph
|
|
78
77
|
assignment leaves paragraphs 1..n at the inherited 18 pt.
|
|
79
78
|
- Text-to-fit hiding the overflow: the file opens fine and the audience reads
|
|
80
|
-
9 pt. Measure
|
|
81
|
-
- A frame height
|
|
82
|
-
|
|
79
|
+
9 pt. Measure; do not delegate the fit to the renderer.
|
|
80
|
+
- A frame height exactly equal to the text height: one extra word on edit and the
|
|
81
|
+
block runs into the evidence below. Reserve 10% slack.
|
|
83
82
|
|
|
84
83
|
## CLI fast path
|
|
85
84
|
|
|
86
|
-
`pptx create` with `layout: "bullets"` fills the layout's body placeholder at
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
`pptx create` with `layout: "bullets"` fills the layout's body placeholder at its
|
|
86
|
+
own size. Use the loop above when the number of bullets is unknown before the
|
|
87
|
+
content is written.
|