dsh-office-kit 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CAPABILITY.md +147 -68
  2. package/README.md +3 -1
  3. package/assets/skills/excel-live-control/SKILL.md +234 -0
  4. package/assets/skills/excel-live-control/container_tools/live_excel.py +1589 -0
  5. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +86 -0
  6. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +58 -0
  7. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +53 -0
  8. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +53 -0
  9. package/assets/skills/excel-live-control/references/office-automation.md +207 -0
  10. package/assets/skills/excel-live-control/references/read_only_qna.md +59 -0
  11. package/assets/skills/excel-live-control/style_guidelines.md +87 -0
  12. package/assets/skills/excel-live-control/workflows/create_workflows.md +78 -0
  13. package/assets/skills/excel-live-control/workflows/edit_workflows.md +72 -0
  14. package/assets/skills/office-documents/SKILL.md +90 -77
  15. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +86 -0
  16. package/assets/skills/office-documents/ooxml/comments.md +40 -0
  17. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +66 -0
  18. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +62 -0
  19. package/assets/skills/office-documents/ooxml/tracked_changes.md +53 -0
  20. package/assets/skills/office-documents/references/docx-structure.md +83 -0
  21. package/assets/skills/office-documents/references/template-elicitation.md +49 -0
  22. package/assets/skills/office-documents/references/writing-quality.md +49 -0
  23. package/assets/skills/office-documents/render_docx.py +99 -0
  24. package/assets/skills/office-documents/scripts/a11y_audit.py +201 -0
  25. package/assets/skills/office-documents/scripts/accept_tracked_changes.py +158 -0
  26. package/assets/skills/office-documents/scripts/add_tracked_replacements.py +155 -0
  27. package/assets/skills/office-documents/scripts/append_blocks.py +115 -0
  28. package/assets/skills/office-documents/scripts/apply_template_styles.py +111 -0
  29. package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +109 -0
  30. package/assets/skills/office-documents/scripts/comments_add.py +143 -0
  31. package/assets/skills/office-documents/scripts/comments_apply_patch.py +121 -0
  32. package/assets/skills/office-documents/scripts/comments_extract.py +92 -0
  33. package/assets/skills/office-documents/scripts/comments_strip.py +81 -0
  34. package/assets/skills/office-documents/scripts/content_controls.py +199 -0
  35. package/assets/skills/office-documents/scripts/core_properties.py +131 -0
  36. package/assets/skills/office-documents/scripts/docx_kit.py +594 -0
  37. package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +232 -0
  38. package/assets/skills/office-documents/scripts/docx_table_to_csv.py +106 -0
  39. package/assets/skills/office-documents/scripts/fields_materialize.py +73 -0
  40. package/assets/skills/office-documents/scripts/fields_report.py +66 -0
  41. package/assets/skills/office-documents/scripts/flatten_ref_fields.py +69 -0
  42. package/assets/skills/office-documents/scripts/footnotes_report.py +84 -0
  43. package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +103 -0
  44. package/assets/skills/office-documents/scripts/heading_audit.py +88 -0
  45. package/assets/skills/office-documents/scripts/images_audit.py +87 -0
  46. package/assets/skills/office-documents/scripts/insert_note.py +133 -0
  47. package/assets/skills/office-documents/scripts/insert_ref_fields.py +97 -0
  48. package/assets/skills/office-documents/scripts/insert_toc.py +91 -0
  49. package/assets/skills/office-documents/scripts/internal_nav.py +153 -0
  50. package/assets/skills/office-documents/scripts/make_fixtures.py +176 -0
  51. package/assets/skills/office-documents/scripts/merge_docx_append.py +246 -0
  52. package/assets/skills/office-documents/scripts/privacy_scrub.py +138 -0
  53. package/assets/skills/office-documents/scripts/redact_docx.py +120 -0
  54. package/assets/skills/office-documents/scripts/render_and_diff.py +108 -0
  55. package/assets/skills/office-documents/scripts/replace_text.py +161 -0
  56. package/assets/skills/office-documents/scripts/section_audit.py +98 -0
  57. package/assets/skills/office-documents/scripts/set_protection.py +99 -0
  58. package/assets/skills/office-documents/scripts/style_lint.py +118 -0
  59. package/assets/skills/office-documents/scripts/style_normalize.py +110 -0
  60. package/assets/skills/office-documents/scripts/table_geometry.py +174 -0
  61. package/assets/skills/office-documents/scripts/watermark_add.py +55 -0
  62. package/assets/skills/office-documents/scripts/watermark_audit_remove.py +74 -0
  63. package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +124 -0
  64. package/assets/skills/office-documents/tasks/accessibility_a11y.md +45 -0
  65. package/assets/skills/office-documents/tasks/captions_crossrefs.md +49 -0
  66. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +49 -0
  67. package/assets/skills/office-documents/tasks/comments_manage.md +59 -0
  68. package/assets/skills/office-documents/tasks/compare_diff.md +40 -0
  69. package/assets/skills/office-documents/tasks/create_edit.md +107 -0
  70. package/assets/skills/office-documents/tasks/equations.md +59 -0
  71. package/assets/skills/office-documents/tasks/fields_update.md +44 -0
  72. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +50 -0
  73. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +38 -0
  74. package/assets/skills/office-documents/tasks/forms_content_controls.md +48 -0
  75. package/assets/skills/office-documents/tasks/headings_numbering.md +55 -0
  76. package/assets/skills/office-documents/tasks/images_figures.md +63 -0
  77. package/assets/skills/office-documents/tasks/multi_doc_merge.md +43 -0
  78. package/assets/skills/office-documents/tasks/navigation_internal_links.md +38 -0
  79. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +45 -0
  80. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +41 -0
  81. package/assets/skills/office-documents/tasks/read_review.md +50 -0
  82. package/assets/skills/office-documents/tasks/redaction_anonymization.md +42 -0
  83. package/assets/skills/office-documents/tasks/sections_layout.md +54 -0
  84. package/assets/skills/office-documents/tasks/style_lint_normalize.md +44 -0
  85. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +64 -0
  86. package/assets/skills/office-documents/tasks/templates_style_packs.md +53 -0
  87. package/assets/skills/office-documents/tasks/toc_workflow.md +39 -0
  88. package/assets/skills/office-documents/tasks/verify_render.md +50 -0
  89. package/assets/skills/office-documents/tasks/watermarks_background.md +35 -0
  90. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +47 -0
  91. package/assets/skills/office-documents/troubleshooting/run_splitting.md +36 -0
  92. package/assets/skills/office-pdf/SKILL.md +73 -56
  93. package/assets/skills/office-pdf/references/form-fields.md +56 -0
  94. package/assets/skills/office-pdf/references/geometry.md +44 -0
  95. package/assets/skills/office-pdf/references/layout-quality.md +41 -0
  96. package/assets/skills/office-pdf/references/limits.md +58 -0
  97. package/assets/skills/office-pdf/scripts/extract_text.py +172 -0
  98. package/assets/skills/office-pdf/scripts/forms.py +556 -0
  99. package/assets/skills/office-pdf/scripts/metadata.py +161 -0
  100. package/assets/skills/office-pdf/scripts/page_ops.py +198 -0
  101. package/assets/skills/office-pdf/scripts/redact_region.py +347 -0
  102. package/assets/skills/office-pdf/scripts/render_pages.py +207 -0
  103. package/assets/skills/office-pdf/scripts/split_pages.py +155 -0
  104. package/assets/skills/office-pdf/scripts/stamp_overlay.py +199 -0
  105. package/assets/skills/office-pdf/tasks/annotations.md +98 -0
  106. package/assets/skills/office-pdf/tasks/create.md +75 -0
  107. package/assets/skills/office-pdf/tasks/edit-pages.md +71 -0
  108. package/assets/skills/office-pdf/tasks/forms.md +67 -0
  109. package/assets/skills/office-pdf/tasks/read.md +62 -0
  110. package/assets/skills/office-pdf/tasks/redact.md +72 -0
  111. package/assets/skills/office-pdf/tasks/stamp.md +60 -0
  112. package/assets/skills/office-presentations/SKILL.md +141 -87
  113. package/assets/skills/office-presentations/container_tools/add_slides.py +143 -0
  114. package/assets/skills/office-presentations/container_tools/core_properties.py +127 -0
  115. package/assets/skills/office-presentations/container_tools/create_montage.py +133 -0
  116. package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +292 -0
  117. package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +164 -0
  118. package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +151 -0
  119. package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +206 -0
  120. package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +219 -0
  121. package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +352 -0
  122. package/assets/skills/office-presentations/container_tools/preview_render.py +382 -0
  123. package/assets/skills/office-presentations/container_tools/render_slides.py +146 -0
  124. package/assets/skills/office-presentations/container_tools/replace_text.py +175 -0
  125. package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +236 -0
  126. package/assets/skills/office-presentations/domain_guidance/finance.md +66 -0
  127. package/assets/skills/office-presentations/recipes/README.md +71 -0
  128. package/assets/skills/office-presentations/recipes/autolayout.md +88 -0
  129. package/assets/skills/office-presentations/recipes/connectors.md +83 -0
  130. package/assets/skills/office-presentations/recipes/images.md +80 -0
  131. package/assets/skills/office-presentations/recipes/master-and-layouts.md +127 -0
  132. package/assets/skills/office-presentations/recipes/native-charts.md +87 -0
  133. package/assets/skills/office-presentations/recipes/rich-text-runs.md +86 -0
  134. package/assets/skills/office-presentations/recipes/speaker-notes.md +65 -0
  135. package/assets/skills/office-presentations/recipes/tables.md +88 -0
  136. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +71 -0
  137. package/assets/skills/office-presentations/recipes/theme-tokens.md +87 -0
  138. package/assets/skills/office-presentations/references/authoring-workflow.md +65 -0
  139. package/assets/skills/office-presentations/references/finalization.md +63 -0
  140. package/assets/skills/office-presentations/references/native-bullets.md +78 -0
  141. package/assets/skills/office-presentations/references/native-evidence.md +67 -0
  142. package/assets/skills/office-presentations/references/style-guidelines.md +66 -0
  143. package/assets/skills/office-presentations/references/templates.md +76 -0
  144. package/assets/skills/office-presentations/routing/google_slides.md +45 -0
  145. package/assets/skills/office-presentations/tasks/apply-annotations.md +83 -0
  146. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +79 -0
  147. package/assets/skills/office-presentations/tasks/create-deck.md +106 -0
  148. package/assets/skills/office-presentations/tasks/edit-deck.md +93 -0
  149. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +107 -0
  150. package/assets/skills/office-presentations/tasks/template-following.md +132 -0
  151. package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py +183 -0
  152. package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +161 -0
  153. package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +118 -0
  154. package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +246 -0
  155. package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +192 -0
  156. package/assets/skills/office-spreadsheets/SKILL.md +105 -101
  157. package/assets/skills/office-spreadsheets/container_tools/add_chart.py +243 -0
  158. package/assets/skills/office-spreadsheets/container_tools/add_image.py +114 -0
  159. package/assets/skills/office-spreadsheets/container_tools/append_rows.py +129 -0
  160. package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +313 -0
  161. package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +405 -0
  162. package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +287 -0
  163. package/assets/skills/office-spreadsheets/container_tools/set_cells.py +142 -0
  164. package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +141 -0
  165. package/assets/skills/office-spreadsheets/container_tools/style_cells.py +140 -0
  166. package/assets/skills/office-spreadsheets/recipes/charts.md +66 -0
  167. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +69 -0
  168. package/assets/skills/office-spreadsheets/recipes/data-tables.md +58 -0
  169. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +41 -0
  170. package/assets/skills/office-spreadsheets/recipes/number-formats.md +72 -0
  171. package/assets/skills/office-spreadsheets/recipes/sparklines.md +43 -0
  172. package/assets/skills/office-spreadsheets/references/charts.md +63 -0
  173. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +40 -0
  174. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +22 -0
  175. package/assets/skills/office-spreadsheets/references/domain/marketing.md +28 -0
  176. package/assets/skills/office-spreadsheets/references/domain/research.md +22 -0
  177. package/assets/skills/office-spreadsheets/references/formula-patterns.md +78 -0
  178. package/assets/skills/office-spreadsheets/references/style-guidelines.md +69 -0
  179. package/assets/skills/office-spreadsheets/references/workbook-structure.md +60 -0
  180. package/assets/skills/office-spreadsheets/routing/google-sheets.md +36 -0
  181. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +37 -0
  182. package/assets/skills/office-spreadsheets/tasks/annotations.md +51 -0
  183. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +66 -0
  184. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +38 -0
  185. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +69 -0
  186. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +38 -0
  187. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +57 -0
  188. package/assets/skills/shared/annotation-recipes.md +252 -0
  189. package/assets/skills/shared/check_runtime.py +260 -0
  190. package/assets/skills/shared/common-workflow.md +139 -0
  191. package/assets/skills/shared/scripts/ensure_raster_image.py +187 -0
  192. package/dist/client.js +4 -4
  193. package/dist/client.js.map +1 -1
  194. package/dist/index.js +246 -89
  195. package/package.json +1 -1
  196. package/postinstall.mjs +92 -82
  197. package/python/office_cli.py +213 -254
@@ -0,0 +1,83 @@
1
+ # Connectors
2
+
3
+ ## Mechanics
4
+
5
+ A connector is a line with two attachment sites. `add_connector` takes a begin
6
+ and end shape *plus* connection indices, which is what makes the line follow the
7
+ boxes when they move. If you pass coordinates instead, the line is a floating
8
+ line: it looks right in the file you made and wrong the first time a box is
9
+ nudged.
10
+
11
+ - `MSO_CONNECTOR.STRAIGHT | ELBOW | CURVE`
12
+ - `connector.begin_connect(shape, idx)` / `end_connect(shape, idx)`; `idx` is
13
+ the site index on the shape's outline (0 = top, 1 = left, 2 = bottom,
14
+ 3 = right for a rectangle).
15
+ - Arrowheads are line properties: `line.end_arrowhead = MSO_ARROWHEAD.TRIANGLE`.
16
+
17
+ ## Recipe
18
+
19
+ ```python
20
+ "<python>" - <<'PY'
21
+ from pptx import Presentation
22
+ from pptx.util import Inches, Pt
23
+ from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR
24
+ from pptx.dml.color import RGBColor
25
+ from pptx.enum.dml import MSO_LINE_DASH_STYLE
26
+
27
+ prs = Presentation()
28
+ prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)
29
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
30
+
31
+ def box(left, top, text, width=2.6, height=1.1):
32
+ shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(left), Inches(top), Inches(width), Inches(height))
33
+ shape.text_frame.text = text
34
+ shape.text_frame.paragraphs[0].font.size = Pt(14)
35
+ shape.fill.solid()
36
+ shape.fill.fore_color.rgb = RGBColor(0xF2, 0xF5, 0xF9)
37
+ shape.line.color.rgb = RGBColor(0xC8, 0xD2, 0xDF)
38
+ return shape
39
+
40
+ order = box(0.9, 2.2, "Order booked")
41
+ pick = box(4.4, 2.2, "Picked")
42
+ ship = box(7.9, 2.2, "Shipped")
43
+
44
+ for start, end in ((order, pick), (pick, ship)):
45
+ connector = slide.shapes.add_connector(
46
+ MSO_CONNECTOR.STRAIGHT, 0, 0, Inches(0.1), Inches(0.1)
47
+ )
48
+ connector.begin_connect(start, 3) # right edge
49
+ connector.end_connect(end, 1) # left edge
50
+ connector.line.width = Pt(2)
51
+ connector.line.color.rgb = RGBColor(0x5F, 0x6B, 0x7A)
52
+ connector.line.end_arrowhead = 3 # triangle
53
+
54
+ prs.save("connectors.pptx")
55
+ from pptx.oxml.ns import qn
56
+ print("connectors:", sum(1 for s in slide.shapes if s._element.tag == qn("p:cxnSp")))
57
+ PY
58
+ ```
59
+
60
+ ## Rules
61
+
62
+ 1. Connect sides that face each other: right → left for a row, bottom → top for
63
+ a column. A line crossing its own boxes reads as an error.
64
+ 2. Elbow connectors for orthogonal flows, straight for a single hop, curve almost
65
+ never.
66
+ 3. Two crossing connectors in one diagram is the point where the diagram becomes
67
+ a picture of a diagram; split it.
68
+ 4. Label the connector ("12 min", "auto") with a small textbox near its middle,
69
+ not with arrow direction alone.
70
+
71
+ ## Pitfalls this avoids
72
+
73
+ - `add_connector` with free coordinates: the line is positioned but unattached,
74
+ and the deck's own edits break it silently.
75
+ - Arrowhead index confusion: arrowhead values are a bitmask enum, not a boolean.
76
+ - Connectors behind filled shapes because they were added before the boxes.
77
+
78
+ ## CLI fast path
79
+
80
+ There is no connector verb in `pptx create`; this recipe is the route. After
81
+ building, `container_tools/inspect_presentation_layout_geometry.py` flags
82
+ `connector_over_text` when a line crosses a text shape, which is the failure
83
+ mode you cannot see in a thumbnail.
@@ -0,0 +1,80 @@
1
+ # Images
2
+
3
+ ## Mechanics
4
+
5
+ `add_picture(path, left, top, width=…, height=…)`:
6
+
7
+ - Give **width only** and the height follows the aspect ratio. Give both and
8
+ the picture is stretched — a deliberate choice for a full-bleed background,
9
+ a mistake for a product photo.
10
+ - `picture.crop_left/right/top/bottom` are fractions of the *source*; they crop
11
+ without changing the frame, which is how a "cover" fit is done.
12
+ - The bytes live in `ppt/media/`; the same file added twice is stored once,
13
+ keyed by content hash.
14
+ - `picture._element.nvPicPr.cNvPr.set("descr", "…")` is the alt text. Decks get
15
+ read aloud and exported; an untagged picture is an empty sentence.
16
+
17
+ ## Recipe: cover-fit into a fixed frame
18
+
19
+ ```python
20
+ "<python>" - <<'PY'
21
+ from pptx import Presentation
22
+ from pptx.util import Inches
23
+ from PIL import Image
24
+
25
+ def cover(picture, frame_w_in, frame_h_in, source_w, source_h):
26
+ """Crop the picture so it fills the frame without distortion."""
27
+ frame_ratio = frame_w_in / frame_h_in
28
+ source_ratio = source_w / source_h
29
+ if source_ratio > frame_ratio: # too wide: crop the sides
30
+ keep = frame_ratio / source_ratio
31
+ picture.crop_left = picture.crop_right = (1 - keep) / 2
32
+ elif source_ratio < frame_ratio: # too tall: crop top and bottom
33
+ keep = source_ratio / frame_ratio
34
+ picture.crop_top = picture.crop_bottom = (1 - keep) / 2
35
+
36
+ prs = Presentation()
37
+ prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)
38
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
39
+
40
+ with Image.open("aisle.png") as image:
41
+ source_w, source_h = image.size
42
+
43
+ frame = slide.shapes.add_picture("aisle.png", Inches(0.9), Inches(1.7), Inches(8.0), Inches(4.5))
44
+ cover(frame, 8.0, 4.5, source_w, source_h)
45
+ frame._element.nvPicPr.cNvPr.set("descr", "Racking aisle in the pilot cell at peak hour")
46
+
47
+ # full-bleed background, sent to the back
48
+ background = slide.shapes.add_picture("aisle.png", 0, 0, prs.slide_width, prs.slide_height)
49
+ tree = slide.shapes._spTree
50
+ tree.remove(background._element)
51
+ tree.insert(2, background._element)
52
+ prs.save("image.pptx")
53
+ print("placed", source_w, "x", source_h)
54
+ PY
55
+ ```
56
+
57
+ ## Rules
58
+
59
+ 1. Never upscale past the source resolution: a 900 px wide image at 8 in is
60
+ ~110 ppi and will look soft on a projector. Check `Image.open(...).size`.
61
+ 2. One dominant image per slide. A slide with a hero and three thumbnails is two
62
+ slides.
63
+ 3. Full-bleed backgrounds need text contrast handled deliberately — a dark
64
+ scrim rectangle between picture and text, not hope.
65
+ 4. Alt text on every picture, in the language of the deck.
66
+
67
+ ## Pitfalls this avoids
68
+
69
+ - Passing both width and height and getting a squashed photograph nobody can
70
+ name as the cause.
71
+ - `add_picture` after the text: the picture covers the words. Add backgrounds
72
+ first, or move them to index 2.
73
+ - Re-using a screenshot of a chart or a table as "evidence": it is unreadable at
74
+ projector size and unverifiable. Use a native chart or table.
75
+
76
+ ## CLI fast path
77
+
78
+ `pptx create` accepts `{"image": {"path": "...", "left": 0.9, "top": 1.6,
79
+ "width": 8.0}}` and derives the height from the aspect ratio — no crop, no alt
80
+ text. Cropping, alt text and background placement are the recipe's job.
@@ -0,0 +1,127 @@
1
+ # Masters, layouts and slide families
2
+
3
+ ## Mechanics
4
+
5
+ A deck has three levels, and where you make a change decides how many slides it
6
+ fixes:
7
+
8
+ - **Slide master** (`ppt/slideMasters/`) — theme fonts, colours, background,
9
+ and the shapes every slide inherits (title bar, accent rule, logo).
10
+ - **Slide layout** (`ppt/slideLayouts/`) — one per slide family (title, title and
11
+ content, section, blank…), each with named placeholders at fixed rects.
12
+ - **Slide** — holds its own shapes plus *placeholder overrides*: text, and also
13
+ position, if someone dragged a placeholder.
14
+
15
+ Editing a placeholder's *text* is a per-slide change. Editing the layout's
16
+ placeholder *rect* moves that frame on every slide of that family. Editing the
17
+ master's background moves it everywhere.
18
+
19
+ `slide.slide_layout` is read-only; the family is fixed when the slide is created.
20
+ To change a slide's family, delete it and add a new one from the target layout —
21
+ losing its content, so plan families before filling them.
22
+
23
+ **One quirk to know:** `master.shapes` and `layout.shapes` are read-only in
24
+ `python-pptx` (`MasterShapes` has no `add_shape`). Chrome shapes are therefore
25
+ built on a scratch slide and their XML moved into the master's shape tree.
26
+
27
+ ## Recipe: restyle a family from the master
28
+
29
+ ```python
30
+ "<python>" - <<'PY'
31
+ import copy
32
+ from pptx import Presentation
33
+ from pptx.util import Inches, Pt
34
+ from pptx.dml.color import RGBColor
35
+ from pptx.enum.shapes import MSO_SHAPE
36
+
37
+ prs = Presentation("template.pptx")
38
+ master = prs.slide_masters[0]
39
+
40
+ master.background.fill.solid()
41
+ master.background.fill.fore_color.rgb = RGBColor(0x0F, 0x17, 0x22)
42
+
43
+ # Chrome rule on every slide: build it on a scratch slide, move the XML, drop the slide.
44
+ scratch = prs.slides.add_slide(prs.slide_layouts[6])
45
+ rule = scratch.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0), Inches(0), Inches(13.333), Inches(0.06))
46
+ rule.fill.solid()
47
+ rule.fill.fore_color.rgb = RGBColor(0xE0, 0x9B, 0x2D)
48
+ rule.line.fill.background()
49
+ master.shapes._spTree.append(copy.deepcopy(rule._element))
50
+
51
+ slide_id = prs.slides._sldIdLst[-1]
52
+ prs.part.drop_rel(slide_id.rId)
53
+ prs.slides._sldIdLst.remove(slide_id)
54
+
55
+ # Move the title frame for one family only.
56
+ layout = next(l for l in master.slide_layouts if "title and content" in l.name.lower())
57
+ title = next(p for p in layout.placeholders if p.placeholder_format.idx == 0)
58
+ title.left, title.top = Inches(0.9), Inches(0.55)
59
+ title.width, title.height = Inches(11.5), Inches(1.0)
60
+ for paragraph in title.text_frame.paragraphs:
61
+ paragraph.font.size = Pt(32)
62
+ paragraph.font.bold = True
63
+
64
+ prs.save("restyled.pptx")
65
+ print("layouts:", [l.name for l in master.slide_layouts], "slides:", len(prs.slides))
66
+ PY
67
+ ```
68
+
69
+ ## Recipe: delete the empty placeholders a layout leaves behind
70
+
71
+ A layout hands every slide a placeholder, filled or not. Section and title-only
72
+ layouts carry body slots that are empty by design; in edit view they print "Click
73
+ to add text" and there is no CLI verb for removing them, so do it in code:
74
+
75
+ ```python
76
+ "<python>" - <<'PY'
77
+ from pptx import Presentation
78
+
79
+ prs = Presentation("deck.pptx")
80
+ removed = []
81
+ for index, slide in enumerate(prs.slides, start=1):
82
+ for placeholder in list(slide.placeholders):
83
+ if placeholder.has_text_frame and not placeholder.text_frame.text.strip():
84
+ removed.append((index, placeholder.placeholder_format.idx, placeholder.name))
85
+ placeholder._element.getparent().remove(placeholder._element)
86
+ prs.save("deck-cleaned.pptx")
87
+ print("removed:", removed)
88
+ PY
89
+ ```
90
+
91
+ This is what clears the `empty_placeholder` review finding in
92
+ `container_tools/inspect_presentation_layout_geometry.py`. Keep a placeholder you
93
+ still intend to fill, and never delete the one slide numbers are linked to.
94
+
95
+ ## Reusing the source's own layouts
96
+
97
+ When the user supplies a template, do not rebuild its look — put content into
98
+ its placeholders. The inspection script prints each layout's placeholders with
99
+ index, kind and rect, which is what you need to address them:
100
+
101
+ ```bash
102
+ "<python>" container_tools/inspect_presentation_layout_geometry.py --input template.pptx --json-out geometry.json
103
+ ```
104
+
105
+ Then address a placeholder by index on the slide:
106
+ `slide.placeholders[1].text = "..."`. Slides created from a layout always have
107
+ a placeholder for every non-empty slot, so index-based addressing is stable.
108
+
109
+ ## Pitfalls this avoids
110
+
111
+ - Drawing the accent rule on every slide instead of the master: one edit later,
112
+ half the deck has two rules.
113
+ - Dragging a placeholder on a slide to fix one frame; the layout is untouched
114
+ and the next slide of the same family misbehaves again.
115
+ - Appending the scratch shape to the master without `drop_rel`: the orphan slide
116
+ part stays in the package and `inspect_presentation_package_integrity.py`
117
+ reports a slide count mismatch.
118
+ - Adding a *blank* slide and a floating textbox where the template has a title
119
+ placeholder: the deck no longer answers to its own master, and the outline
120
+ view is empty.
121
+
122
+ ## CLI fast path
123
+
124
+ `pptx create --spec` starts from the blank default template and maps
125
+ `title|bullets|section|title_only|blank` to built-in layouts. Copying a supplied
126
+ template is `template_following_scripts/prepare_template_starter_deck.py`, which
127
+ keeps and fills the template's own slides.
@@ -0,0 +1,87 @@
1
+ # Native charts
2
+
3
+ ## Mechanics
4
+
5
+ A chart added with `slide.shapes.add_chart` is a real chart part
6
+ (`ppt/charts/chartN.xml`) with an embedded workbook
7
+ (`ppt/embeddings/*.xlsx`). That matters for three reasons:
8
+
9
+ - The audience can edit the numbers in PowerPoint; a picture of a chart cannot.
10
+ - The data is in the file, so a gate can check it. A screenshot cannot be
11
+ checked, and nobody notices a wrong screenshot.
12
+ - The chart inherits the slide's theme for colour and type, so charts match the
13
+ deck without per-chart styling.
14
+
15
+ `CategoryChartData` holds the numbers; `chart.plots[0]` holds the geometry of
16
+ the marks. Category order is the series' own order — sort before you pass it,
17
+ because a chart does not sort.
18
+
19
+ ## Recipe
20
+
21
+ ```python
22
+ "<python>" - <<'PY'
23
+ from pptx import Presentation
24
+ from pptx.chart.data import CategoryChartData
25
+ from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION, XL_LABEL_POSITION
26
+ from pptx.util import Inches, Pt
27
+
28
+ prs = Presentation()
29
+ prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)
30
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
31
+
32
+ data = CategoryChartData()
33
+ data.categories = ["Q1", "Q2", "Q3", "Q4"]
34
+ data.add_series("Orders fulfilled", (100, 128, 154, 171))
35
+ data.add_series("Touches", (410, 402, 386, 371))
36
+
37
+ frame = slide.shapes.add_chart(
38
+ XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(0.9), Inches(1.6), Inches(11.5), Inches(5.1), data
39
+ )
40
+ chart = frame.chart
41
+ chart.has_title = True
42
+ chart.chart_title.text_frame.text = "Orders fulfilled per quarter"
43
+ chart.chart_title.text_frame.paragraphs[0].font.size = Pt(14)
44
+ chart.has_legend = True
45
+ chart.legend.position = XL_LEGEND_POSITION.BOTTOM
46
+ chart.legend.include_in_layout = False
47
+
48
+ plot = chart.plots[0]
49
+ plot.has_data_labels = True
50
+ plot.data_labels.number_format = "0"
51
+ plot.data_labels.number_format_is_linked = False
52
+ plot.data_labels.position = XL_LABEL_POSITION.OUTSIDE_END
53
+ plot.gap_width = 60
54
+
55
+ chart.value_axis.has_major_gridlines = True
56
+ chart.value_axis.tick_labels.font.size = Pt(11)
57
+ chart.category_axis.tick_labels.font.size = Pt(11)
58
+ prs.save("chart.pptx")
59
+ print("saved chart with", len(chart.series), "series")
60
+ PY
61
+ ```
62
+
63
+ ## Rules for charted numbers
64
+
65
+ 1. One idea per chart. Two series is fine; six is a table.
66
+ 2. No chart title that repeats the slide title — the title says what the chart
67
+ *means* ("Throughput rose while touches fell"), the chart shows the numbers.
68
+ 3. Start the value axis at zero for bars, and say so in the notes if you do not.
69
+ 4. Data labels beat a value axis grid; pick one, not both.
70
+ 5. Never leave the default "Chart Title" placeholder text in a shipped deck.
71
+
72
+ ## Pitfalls this avoids
73
+
74
+ - `number_format` without `number_format_is_linked = False` reverts to the
75
+ workbook's format the next time PowerPoint refreshes the chart.
76
+ - `legend.include_in_layout = False` is what stops the legend from stealing
77
+ plot height and squashing the bars.
78
+ - Deleting the embedded workbook (by zipping/unzipping carelessly) turns the
79
+ chart into an uneditable picture in every downstream viewer.
80
+
81
+ ## CLI fast path
82
+
83
+ `pptx create` accepts
84
+ `{"chart": {"type": "column", "title": "...", "categories": [...], "series":
85
+ [{"name": "...", "values": [...]}], "left": 0.9, "top": 1.45, "width": 11.5,
86
+ "height": 5.1}}`. The recipe above is the route when you need axis, label or
87
+ legend control.
@@ -0,0 +1,86 @@
1
+ # Rich text runs
2
+
3
+ ## Mechanics
4
+
5
+ Text lives at three levels: `text_frame` → `paragraph` → `run`. A run is a span
6
+ with one formatting state. Assigning `paragraph.text` (or `text_frame.text`)
7
+ replaces every run, so **format runs, never paragraphs**:
8
+
9
+ - `paragraph.text = "x"` — one run, all previous formatting gone.
10
+ - `run = paragraph.add_run(); run.text = "x"; run.font.bold = True` — formatting
11
+ survives and can differ inside the paragraph.
12
+ - `paragraph.font.size` is not a thing PowerPoint respects for mixed content; it
13
+ sets the default for later runs only. Set the size on every run you add.
14
+
15
+ Paragraph-level properties (`alignment`, `level`, `space_after`,
16
+ `line_spacing`) apply to the whole paragraph and are safe to set once.
17
+
18
+ ## Recipe
19
+
20
+ ```python
21
+ "<python>" - <<'PY'
22
+ from pptx import Presentation
23
+ from pptx.util import Inches, Pt
24
+ from pptx.dml.color import RGBColor
25
+ from pptx.enum.text import PP_ALIGN
26
+
27
+ prs = Presentation()
28
+ prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)
29
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
30
+
31
+ box = slide.shapes.add_textbox(Inches(0.9), Inches(2.0), Inches(11.0), Inches(1.6))
32
+ frame = box.text_frame
33
+ frame.word_wrap = True
34
+
35
+ MUTED = RGBColor(0x5F, 0x6B, 0x7A)
36
+ ACCENT = RGBColor(0xD9, 0x8B, 0x1E)
37
+
38
+ lead = frame.paragraphs[0]
39
+ lead.alignment = PP_ALIGN.LEFT
40
+ for text, bold, size, colour in (
41
+ ("Orders fulfilled ", False, Pt(28), RGBColor(0x16, 0x1D, 0x28)),
42
+ ("rose 71%", True, Pt(28), ACCENT),
43
+ (" across FY2026.", False, Pt(28), RGBColor(0x16, 0x1D, 0x28)),
44
+ ):
45
+ run = lead.add_run()
46
+ run.text = text
47
+ run.font.bold = bold
48
+ run.font.size = size
49
+ run.font.color.rgb = colour
50
+
51
+ footnote = frame.add_paragraph()
52
+ footnote.space_before = Pt(10)
53
+ note = footnote.add_run()
54
+ note.text = "Q4 is forecast; the other three quarters are actuals."
55
+ note.font.size = Pt(12)
56
+ note.font.italic = True
57
+ note.font.color.rgb = MUTED
58
+ prs.save("runs.pptx")
59
+ print("runs in paragraph 0:", len(lead.runs))
60
+ PY
61
+ ```
62
+
63
+ ## Rules
64
+
65
+ 1. A number you want the reader to remember gets its own run, and usually a
66
+ weight change — not a second bullet.
67
+ 2. Superscripts, footnote markers and unit symbols are runs, not characters
68
+ smuggled into the sentence.
69
+ 3. Keep one family of emphasis per deck: bold+accent for numbers, italic+muted
70
+ for provenance. Three competing emphases read as decoration.
71
+ 4. Strings that repeat (units, currency, product names) belong in a variable so
72
+ a later edit does not leave two spellings.
73
+
74
+ ## Pitfalls this avoids
75
+
76
+ - `paragraph.text = "Total: "` then `add_run()`: the first assignment already
77
+ replaced the frame's runs, so formatting set beforehand is gone.
78
+ - A run with no `font.size` inherits the layout's size, which is 44 pt on a
79
+ title layout — footnote markers become headlines.
80
+ - Building the paragraph by f-string with `**` markers that PowerPoint does not
81
+ interpret; markup in a deck is a run, not a syntax.
82
+
83
+ ## CLI fast path
84
+
85
+ `pptx create` writes plain paragraphs; bullets and titles are single-run. Mixed
86
+ formatting inside one line is the recipe's job.
@@ -0,0 +1,65 @@
1
+ # Speaker notes
2
+
3
+ ## Mechanics
4
+
5
+ Notes live in `ppt/notesMasters/` plus one `ppt/notesSlides/notesSlideN.xml` per
6
+ slide, tied to the slide by a relationship. In `python-pptx` this is
7
+ `slide.notes_slide.notes_text_frame` — the notes slide is created on first
8
+ access, so reading it in a loop silently adds one per slide. Only touch it when
9
+ you are writing.
10
+
11
+ Notes are not visible in the render. That means a notes problem cannot be caught
12
+ by looking at the PNGs: check them with `pptx read` or by reading the XML back.
13
+
14
+ ## Recipe
15
+
16
+ ```python
17
+ "<python>" - <<'PY'
18
+ from pptx import Presentation
19
+ from pptx.util import Pt
20
+
21
+ NOTES = {
22
+ 1: "Open with the ask: approve two cells, not three.",
23
+ 2: "41% comes from the Q3 time study; the two other numbers are from the driver logs.",
24
+ 4: "Say the total out loud: 553 orders over four quarters.",
25
+ 5: "Peak hour is 14:00-15:00; the bars are Q1-Q4, not hours.",
26
+ }
27
+
28
+ prs = Presentation("deck.pptx")
29
+ for index, slide in enumerate(prs.slides, start=1):
30
+ if index not in NOTES:
31
+ continue
32
+ frame = slide.notes_slide.notes_text_frame
33
+ frame.text = NOTES[index]
34
+ for paragraph in frame.paragraphs:
35
+ paragraph.font.size = Pt(12)
36
+ prs.save("deck-notes.pptx")
37
+ print("notes written:", len(NOTES))
38
+ PY
39
+ ```
40
+
41
+ ## What belongs in a note
42
+
43
+ - The sentence you will actually say, not a copy of the slide.
44
+ - The provenance of any number the audience may challenge ("Q3 time study,
45
+ 12 sites").
46
+ - The answer to the question the slide invites.
47
+ - Nothing that belongs on the slide: if the audience needs it, put it on the
48
+ slide, notes are invisible when the deck is sent as a PDF.
49
+
50
+ ## Pitfalls this avoids
51
+
52
+ - Iterating `prs.slides` and *reading* `notes_slide` on a template with none:
53
+ every slide gains an empty notes part and the file grows.
54
+ - Long notes pasted as one paragraph with line breaks that only look like
55
+ paragraphs; each intended paragraph should be `add_paragraph()` so the notes
56
+ pane and the presenter view indent them.
57
+ - Assuming notes render into the preview: they do not, so verify them from the
58
+ file.
59
+
60
+ ## CLI fast path
61
+
62
+ `pptx create` writes slides without notes. The recipe above is the route; then
63
+ `pptx read --input deck-notes.pptx` does not print notes, so read them back with
64
+ `ppt/slides` sibling `notesSlide` parts via
65
+ `inspect_presentation_package_integrity.py` (it counts `notesSlides`).
@@ -0,0 +1,88 @@
1
+ # Tables
2
+
3
+ ## Mechanics
4
+
5
+ A table in a deck is a grid of cells on a canvas; it does not reflow. Three
6
+ properties decide whether it reads as a table or as soup:
7
+
8
+ - **Column widths** are the layout. `table.columns[i].width = Inches(3.2)` —
9
+ setting them is mandatory, because `add_table` splits the width evenly.
10
+ - **Row heights** are a minimum. Text taller than the row pushes it open, which
11
+ then runs into whatever sits below.
12
+ - **Cell margins** default to 0.1 in left/right and 0.05 in top/bottom on a
13
+ 0.5 in row, which is most of the row. Reduce them when rows are tight.
14
+
15
+ Header row emphasis (`table.first_row = True`) is a table style switch, not a
16
+ formatting guess: it turns on the theme's header fill and bold.
17
+
18
+ ## Recipe
19
+
20
+ ```python
21
+ "<python>" - <<'PY'
22
+ from pptx import Presentation
23
+ from pptx.util import Inches, Pt
24
+ from pptx.dml.color import RGBColor
25
+
26
+ ROWS = [
27
+ ("Quarter", "Orders", "Touches"),
28
+ ("Q1", "100", "410"),
29
+ ("Q2", "128", "402"),
30
+ ("Q3", "154", "386"),
31
+ ("Q4", "171", "371"),
32
+ ("Total", "553", "1569"),
33
+ ]
34
+
35
+ prs = Presentation()
36
+ prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)
37
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
38
+
39
+ shape = slide.shapes.add_table(
40
+ len(ROWS), len(ROWS[0]), Inches(0.9), Inches(1.7), Inches(9.6), Inches(0.46 * len(ROWS))
41
+ )
42
+ table = shape.table
43
+ table.first_row = True
44
+ for index, width in enumerate((3.4, 3.1, 3.1)):
45
+ table.columns[index].width = Inches(width)
46
+ for row_index, row in enumerate(ROWS):
47
+ table.rows[row_index].height = Inches(0.46)
48
+ for column_index, value in enumerate(row):
49
+ cell = table.cell(row_index, column_index)
50
+ cell.text = value
51
+ cell.margin_left = cell.margin_right = Inches(0.12)
52
+ cell.margin_top = cell.margin_bottom = Inches(0.04)
53
+ for paragraph in cell.text_frame.paragraphs:
54
+ paragraph.font.size = Pt(16)
55
+ if row_index == 0:
56
+ paragraph.font.bold = True
57
+ if column_index > 0:
58
+ paragraph.alignment = 2 # right
59
+ total = table.cell(len(ROWS) - 1, 0)
60
+ for paragraph in total.text_frame.paragraphs:
61
+ paragraph.font.bold = True
62
+ prs.save("table.pptx")
63
+ print(f"rows={len(ROWS)} total_width={sum(c.width for c in table.columns) / 914400:.2f}in")
64
+ PY
65
+ ```
66
+
67
+ ## Numbers people trust
68
+
69
+ - Right-align numeric columns, left-align the label column, and keep decimals
70
+ consistent per column (`553`, not `553.0` next to `171`).
71
+ - A total row is a claim. Show enough rows that the reader can check it, and
72
+ let `container_tools/native_table_arithmetic_gate.py` prove it from the file.
73
+ - Units belong in the header (`Orders`, `Touches`), not repeated in every cell.
74
+ - Money keeps its currency in the header or the first row; `¥1,240` in every
75
+ cell of a dense table is noise.
76
+
77
+ ## Pitfalls this avoids
78
+
79
+ - Even column widths turning "Q1" into a 4 in column while numbers crowd.
80
+ - A table whose declared height is smaller than its content: the last row is
81
+ drawn below the intended block and collides with the footer.
82
+ - Colouring cells individually instead of letting `first_row` do the header.
83
+
84
+ ## CLI fast path
85
+
86
+ `pptx create` accepts `{"table": {"headers": [...], "rows": [[...]]}}` and
87
+ places it at 0.8 in from the left, full content width, at the running cursor.
88
+ For a chosen rect, column widths or right-aligned numbers, use the recipe.
@@ -0,0 +1,71 @@
1
+ # Textbox at a coordinate
2
+
3
+ ## Mechanics
4
+
5
+ A free textbox is placed by its rect, not by a layout slot. Four facts control
6
+ what you see:
7
+
8
+ - `left`/`top` are the **top-left corner** of the frame, not of the glyphs. A
9
+ 28 pt line has roughly 4 pt of space above the cap height inside its frame.
10
+ - `width` is the wrap width. `word_wrap = True` must be set explicitly on a new
11
+ box or long lines run past the slide edge instead of wrapping.
12
+ - `height` is the *frame*, not the content: PowerPoint lets text spill below it
13
+ (which overlaps whatever is under the box) unless autosize is turned on.
14
+ - Interior margins default to 0.1 in left/right and 0.05 in top/bottom. Setting
15
+ them to 0 makes the rect mean exactly what you measured.
16
+
17
+ ## Recipe
18
+
19
+ ```python
20
+ "<python>" - <<'PY'
21
+ from pptx import Presentation
22
+ from pptx.util import Inches, Pt
23
+ from pptx.enum.text import MSO_ANCHOR
24
+
25
+ prs = Presentation()
26
+ prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)
27
+ slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank
28
+
29
+ box = slide.shapes.add_textbox(Inches(0.9), Inches(1.35), Inches(6.2), Inches(1.1))
30
+ frame = box.text_frame
31
+ frame.word_wrap = True
32
+ frame.vertical_anchor = MSO_ANCHOR.TOP
33
+ frame.margin_left = frame.margin_right = 0
34
+ frame.margin_top = frame.margin_bottom = 0
35
+
36
+ paragraph = frame.paragraphs[0]
37
+ paragraph.text = "Picking is 41% of floor labour hours"
38
+ paragraph.font.size = Pt(28)
39
+ paragraph.font.bold = True
40
+ prs.save("textbox.pptx")
41
+ print("saved")
42
+ PY
43
+ ```
44
+
45
+ ## Measuring before you place
46
+
47
+ Do not guess whether the text fits — compute it. A line of 28 pt text fits
48
+ about `width_in * 96 / (0.55 * 28)` characters (0.55 average advance for a
49
+ humanist sans), and each line needs `1.2 * 28 / 72` inches of height. Leave the
50
+ estimate in the script, then let the render confirm it.
51
+
52
+ ```python
53
+ def fits(text: str, width_in: float, height_in: float, size_pt: float) -> bool:
54
+ per_line = max(1, int(width_in * 96 / (0.55 * size_pt)))
55
+ lines = -(-len(text) // per_line)
56
+ return lines * 1.2 * size_pt / 72 <= height_in
57
+ ```
58
+
59
+ ## Pitfalls this avoids
60
+
61
+ - A box sized to the text at 18 pt, later edited to 32 pt, now overlaps the
62
+ table below. Frame height follows the *largest* run, so size text first.
63
+ - `add_textbox` with `width=Inches(13.3)` on a 13.333 in slide plus default
64
+ margins = 0.2 in of overflow past the right edge. Subtract the margins.
65
+ - `MSO_ANCHOR.MIDDLE` on a title looks intentional on a cover and wrong on a
66
+ content slide; anchor per role, not globally.
67
+
68
+ ## CLI fast path
69
+
70
+ `pptx create` places titles and bullet bodies on the layout's own frames. For
71
+ anything at a coordinate, the snippet above is the route.