python-pptx2 2.13.0__py3-none-any.whl

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 (175) hide show
  1. pptx2/__init__.py +152 -0
  2. pptx2/_color.py +75 -0
  3. pptx2/_slide_importer.py +597 -0
  4. pptx2/_svg.py +155 -0
  5. pptx2/_template_applier.py +292 -0
  6. pptx2/_textstyle.py +187 -0
  7. pptx2/accessibility.py +365 -0
  8. pptx2/action.py +270 -0
  9. pptx2/animation.py +2237 -0
  10. pptx2/api.py +49 -0
  11. pptx2/audit.py +258 -0
  12. pptx2/chart/__init__.py +0 -0
  13. pptx2/chart/analytics.py +381 -0
  14. pptx2/chart/axis.py +543 -0
  15. pptx2/chart/category.py +200 -0
  16. pptx2/chart/chart.py +670 -0
  17. pptx2/chart/data.py +864 -0
  18. pptx2/chart/datalabel.py +406 -0
  19. pptx2/chart/legend.py +86 -0
  20. pptx2/chart/marker.py +70 -0
  21. pptx2/chart/palettes.py +129 -0
  22. pptx2/chart/plot.py +462 -0
  23. pptx2/chart/point.py +101 -0
  24. pptx2/chart/quick_layouts.py +325 -0
  25. pptx2/chart/series.py +334 -0
  26. pptx2/chart/xlsx.py +272 -0
  27. pptx2/chart/xmlwriter.py +1845 -0
  28. pptx2/compose/__init__.py +28 -0
  29. pptx2/compose/from_spec.py +1094 -0
  30. pptx2/design/__init__.py +8 -0
  31. pptx2/design/components.py +607 -0
  32. pptx2/design/figures.py +389 -0
  33. pptx2/design/layout.py +370 -0
  34. pptx2/design/recipes.py +1967 -0
  35. pptx2/design/style.py +209 -0
  36. pptx2/design/tokens.py +915 -0
  37. pptx2/diagrams.py +754 -0
  38. pptx2/dml/__init__.py +0 -0
  39. pptx2/dml/chtfmt.py +40 -0
  40. pptx2/dml/color.py +496 -0
  41. pptx2/dml/effect.py +909 -0
  42. pptx2/dml/fill.py +691 -0
  43. pptx2/dml/line.py +287 -0
  44. pptx2/dml/picture.py +212 -0
  45. pptx2/dml/three_d.py +381 -0
  46. pptx2/enum/__init__.py +0 -0
  47. pptx2/enum/action.py +71 -0
  48. pptx2/enum/animation.py +31 -0
  49. pptx2/enum/base.py +218 -0
  50. pptx2/enum/chart.py +574 -0
  51. pptx2/enum/dml.py +740 -0
  52. pptx2/enum/lang.py +685 -0
  53. pptx2/enum/presentation.py +133 -0
  54. pptx2/enum/shapes.py +1029 -0
  55. pptx2/enum/text.py +230 -0
  56. pptx2/exc.py +42 -0
  57. pptx2/formats.py +139 -0
  58. pptx2/geometry.py +420 -0
  59. pptx2/inherit.py +109 -0
  60. pptx2/lint.py +2256 -0
  61. pptx2/math.py +177 -0
  62. pptx2/media.py +197 -0
  63. pptx2/opc/__init__.py +0 -0
  64. pptx2/opc/constants.py +332 -0
  65. pptx2/opc/oxml.py +188 -0
  66. pptx2/opc/package.py +762 -0
  67. pptx2/opc/packuri.py +109 -0
  68. pptx2/opc/serialized.py +296 -0
  69. pptx2/opc/shared.py +20 -0
  70. pptx2/opc/spec.py +45 -0
  71. pptx2/oxml/__init__.py +555 -0
  72. pptx2/oxml/action.py +53 -0
  73. pptx2/oxml/chart/__init__.py +0 -0
  74. pptx2/oxml/chart/axis.py +337 -0
  75. pptx2/oxml/chart/chart.py +481 -0
  76. pptx2/oxml/chart/datalabel.py +253 -0
  77. pptx2/oxml/chart/legend.py +72 -0
  78. pptx2/oxml/chart/marker.py +61 -0
  79. pptx2/oxml/chart/plot.py +365 -0
  80. pptx2/oxml/chart/series.py +425 -0
  81. pptx2/oxml/chart/shared.py +220 -0
  82. pptx2/oxml/coreprops.py +288 -0
  83. pptx2/oxml/dml/__init__.py +0 -0
  84. pptx2/oxml/dml/color.py +135 -0
  85. pptx2/oxml/dml/effect.py +213 -0
  86. pptx2/oxml/dml/fill.py +316 -0
  87. pptx2/oxml/dml/line.py +12 -0
  88. pptx2/oxml/dml/three_d.py +110 -0
  89. pptx2/oxml/ns.py +135 -0
  90. pptx2/oxml/presentation.py +313 -0
  91. pptx2/oxml/shapes/__init__.py +19 -0
  92. pptx2/oxml/shapes/autoshape.py +467 -0
  93. pptx2/oxml/shapes/connector.py +107 -0
  94. pptx2/oxml/shapes/graphfrm.py +347 -0
  95. pptx2/oxml/shapes/groupshape.py +329 -0
  96. pptx2/oxml/shapes/picture.py +270 -0
  97. pptx2/oxml/shapes/shared.py +577 -0
  98. pptx2/oxml/simpletypes.py +1027 -0
  99. pptx2/oxml/slide.py +563 -0
  100. pptx2/oxml/table.py +650 -0
  101. pptx2/oxml/text.py +815 -0
  102. pptx2/oxml/theme.py +36 -0
  103. pptx2/oxml/xmlchemy.py +717 -0
  104. pptx2/package.py +222 -0
  105. pptx2/parts/__init__.py +0 -0
  106. pptx2/parts/chart.py +95 -0
  107. pptx2/parts/coreprops.py +167 -0
  108. pptx2/parts/diagram.py +37 -0
  109. pptx2/parts/embeddedpackage.py +93 -0
  110. pptx2/parts/image.py +275 -0
  111. pptx2/parts/media.py +37 -0
  112. pptx2/parts/presentation.py +136 -0
  113. pptx2/parts/slide.py +371 -0
  114. pptx2/presentation.py +408 -0
  115. pptx2/py.typed +0 -0
  116. pptx2/render.py +586 -0
  117. pptx2/section.py +272 -0
  118. pptx2/shapes/__init__.py +26 -0
  119. pptx2/shapes/autoshape.py +442 -0
  120. pptx2/shapes/base.py +1078 -0
  121. pptx2/shapes/connector.py +297 -0
  122. pptx2/shapes/freeform.py +337 -0
  123. pptx2/shapes/graphfrm.py +316 -0
  124. pptx2/shapes/group.py +264 -0
  125. pptx2/shapes/picture.py +422 -0
  126. pptx2/shapes/placeholder.py +468 -0
  127. pptx2/shapes/shapetree.py +2027 -0
  128. pptx2/shared.py +82 -0
  129. pptx2/skill/SKILL.md +450 -0
  130. pptx2/skill/__init__.py +78 -0
  131. pptx2/skill/__main__.py +64 -0
  132. pptx2/skill/references/animations.md +189 -0
  133. pptx2/skill/references/basics.md +421 -0
  134. pptx2/skill/references/charts.md +254 -0
  135. pptx2/skill/references/compose.md +234 -0
  136. pptx2/skill/references/design.md +366 -0
  137. pptx2/skill/references/effects.md +249 -0
  138. pptx2/skill/references/end-to-end-deck.md +231 -0
  139. pptx2/skill/references/geometry-and-arrows.md +334 -0
  140. pptx2/skill/references/lint.md +275 -0
  141. pptx2/skill/references/math.md +86 -0
  142. pptx2/skill/references/picture-effects.md +129 -0
  143. pptx2/skill/references/render.md +151 -0
  144. pptx2/skill/references/smart-art.md +75 -0
  145. pptx2/skill/references/space-aware-authoring.md +249 -0
  146. pptx2/skill/references/tables.md +244 -0
  147. pptx2/skill/references/theme.md +127 -0
  148. pptx2/skill/references/three-d.md +109 -0
  149. pptx2/skill/references/transitions.md +100 -0
  150. pptx2/slide.py +1244 -0
  151. pptx2/smart_art.py +220 -0
  152. pptx2/spec.py +633 -0
  153. pptx2/table.py +1181 -0
  154. pptx2/table_styles.py +184 -0
  155. pptx2/templates/default.pptx +0 -0
  156. pptx2/templates/docx-icon.emf +0 -0
  157. pptx2/templates/generic-icon.emf +0 -0
  158. pptx2/templates/notes.xml +23 -0
  159. pptx2/templates/notesMaster.xml +352 -0
  160. pptx2/templates/pptx-icon.emf +0 -0
  161. pptx2/templates/theme.xml +321 -0
  162. pptx2/templates/xlsx-icon.emf +0 -0
  163. pptx2/text/__init__.py +0 -0
  164. pptx2/text/fonts.py +482 -0
  165. pptx2/text/layout.py +374 -0
  166. pptx2/text/text.py +1272 -0
  167. pptx2/theme.py +721 -0
  168. pptx2/types.py +36 -0
  169. pptx2/util.py +263 -0
  170. python_pptx2-2.13.0.dist-info/METADATA +351 -0
  171. python_pptx2-2.13.0.dist-info/RECORD +175 -0
  172. python_pptx2-2.13.0.dist-info/WHEEL +5 -0
  173. python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
  174. python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
  175. python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,254 @@
1
+ # Charts: palettes, quick layouts, per-series fills (Phase 10)
2
+
3
+ The chart helpers below stack on top of the existing chart API; nothing
4
+ here replaces `chart_style` or the underlying series formatting — they
5
+ just make common operations one line each.
6
+
7
+ ## A baseline chart
8
+
9
+ ```python
10
+ from pptx2 import Presentation
11
+ from pptx2.chart.data import CategoryChartData
12
+ from pptx2.enum.chart import XL_CHART_TYPE
13
+ from pptx2.util import Inches
14
+
15
+ prs = Presentation()
16
+ slide = prs.slides.add_slide(prs.slide_layouts[5])
17
+ slide.shapes.title.text = "Run-rate metrics"
18
+
19
+ data = CategoryChartData()
20
+ data.categories = ["Q1", "Q2", "Q3", "Q4"]
21
+ data.add_series("ARR", (100, 130, 155, 182))
22
+ data.add_series("NDR (%)", (115, 118, 124, 131))
23
+
24
+ chart_shape = slide.shapes.add_chart(
25
+ XL_CHART_TYPE.COLUMN_CLUSTERED,
26
+ Inches(1), Inches(2), Inches(11), Inches(5),
27
+ data,
28
+ )
29
+ chart = chart_shape.chart
30
+ ```
31
+
32
+ ## Updating a chart's data in place
33
+
34
+ `replace_data` swaps the whole categories-and-series payload while
35
+ keeping the chart's formatting, palette and layout — the right move when
36
+ refreshing a templated deck, since rebuilding the chart would discard
37
+ every style you applied:
38
+
39
+ ```python
40
+ from pptx2.chart.data import CategoryChartData
41
+
42
+ data = CategoryChartData()
43
+ data.categories = ["Q1", "Q2", "Q3", "Q4"]
44
+ data.add_series("FY27", (18.2, 19.6, 21.4, 24.9))
45
+
46
+ chart.replace_data(data) # formatting survives; the embedded
47
+ # worksheet is rewritten to match
48
+ ```
49
+
50
+ Adding or removing series works too — the plot re-reads the series count
51
+ from the new data.
52
+
53
+ ## Recolouring (recommended entry point)
54
+
55
+ `Chart.recolour(palette, by="auto")` is the single entry point you
56
+ should reach for first. ``by="auto"`` (the default) dispatches per
57
+ chart type — per-point colouring on pie / doughnut / pie-of-pie
58
+ variants, per-series colouring on everything else — so a developer
59
+ who just wants "recolour my chart" doesn't have to remember which
60
+ helper applies. ``recolor`` is the US-spelling alias.
61
+
62
+ ```python
63
+ chart.recolour(["#4F9DFF", "#7FCFA1", "#F7B500"])
64
+
65
+ # Force a mode if you need to:
66
+ chart.recolour(palette, by="series") # always per-series
67
+ chart.recolour(palette, by="category") # always per-point
68
+ ```
69
+
70
+ For doughnut / pie charts specifically, `recolour` is the right
71
+ call — `apply_palette` is series-level and a doughnut has only one
72
+ series, so it would tint every slice the same. Calling
73
+ `apply_palette` on a doughnut now warns and routes through the
74
+ right method, but explicit `recolour` is cleaner.
75
+
76
+ ## Chart palettes
77
+
78
+ `Chart.apply_palette(palette)` recolors every series in declaration
79
+ order from a named built-in or an iterable of color-likes. Palettes
80
+ wrap when the chart has more series than colors:
81
+
82
+ ```python
83
+ chart.apply_palette("modern") # built-in
84
+ chart.apply_palette(["#4F9DFF", "#7FCFA1", "#F7B500"])
85
+
86
+ # Mix and match — any color-like works
87
+ from pptx2.dml.color import RGBColor
88
+ chart.apply_palette([
89
+ RGBColor(0x4F, 0x9D, 0xFF),
90
+ "#7FCFA1",
91
+ (247, 181, 0),
92
+ ])
93
+ ```
94
+
95
+ Six built-ins ship in `pptx2.chart.palettes`:
96
+
97
+ - `modern`
98
+ - `classic`
99
+ - `editorial`
100
+ - `vibrant`
101
+ - `monochrome_blue`
102
+ - `monochrome_warm`
103
+
104
+ ```python
105
+ from pptx2.chart.palettes import (
106
+ CHART_PALETTES,
107
+ palette_names,
108
+ resolve_palette,
109
+ )
110
+
111
+ print(palette_names()) # → ['modern', 'classic', ...]
112
+ colors = resolve_palette("editorial") # → list[RGBColor]
113
+ ```
114
+
115
+ `resolve_palette` is also handy for sharing the same colors with
116
+ non-chart shapes.
117
+
118
+ The `chart_style` integer is left untouched, so the palette overrides
119
+ only the per-series fill without rewriting the rest of the style.
120
+
121
+ ## Quick layouts
122
+
123
+ `Chart.apply_quick_layout(layout)` toggles title / legend / axis-title
124
+ / gridline visibility in opinionated combinations. Ten built-in
125
+ presets ship in `pptx2.chart.quick_layouts`:
126
+
127
+ ```python
128
+ chart.apply_quick_layout("title_legend_right")
129
+ chart.apply_quick_layout("title_legend_bottom")
130
+ chart.apply_quick_layout("title_legend_top")
131
+ chart.apply_quick_layout("title_legend_left")
132
+ chart.apply_quick_layout("title_no_legend")
133
+ chart.apply_quick_layout("no_title_no_legend")
134
+ chart.apply_quick_layout("title_axes_legend_right")
135
+ chart.apply_quick_layout("title_axes_legend_bottom")
136
+ chart.apply_quick_layout("minimal")
137
+ chart.apply_quick_layout("dense")
138
+ ```
139
+
140
+ Custom layouts can be supplied as a dict spec:
141
+
142
+ ```python
143
+ chart.apply_quick_layout({
144
+ "has_title": True,
145
+ "title_text": "ARR ($M)",
146
+ "has_legend": True,
147
+ "legend_position": "bottom",
148
+ "category_axis": {"has_major_gridlines": False},
149
+ "value_axis": {"has_major_gridlines": True,
150
+ "tick_labels": True},
151
+ })
152
+ ```
153
+
154
+ Missing keys leave the chart untouched so layouts compose cleanly.
155
+ Charts without category/value axes (e.g. pie) silently skip the
156
+ corresponding keys.
157
+
158
+ ## Per-series gradient and pattern fills
159
+
160
+ `chart.series[i].format.fill` is a regular `FillFormat`, so all four
161
+ gradient kinds and `MSO_PATTERN_TYPE` patterns work per-series with no
162
+ chart-specific shim:
163
+
164
+ ```python
165
+ fill = chart.series[0].format.fill
166
+ fill.gradient(kind="linear")
167
+ fill.gradient_stops.replace([
168
+ (0.0, "#0F2D6B"),
169
+ (1.0, "#4F9DFF"),
170
+ ])
171
+
172
+ # Patterned fill on the second series
173
+ from pptx2.enum.dml import MSO_PATTERN_TYPE
174
+ pat = chart.series[1].format.fill
175
+ pat.patterned()
176
+ pat.pattern = MSO_PATTERN_TYPE.WIDE_DOWNWARD_DIAGONAL
177
+ pat.fore_color.rgb = (0x10, 0xB9, 0x81)
178
+ pat.back_color.rgb = (0xFF, 0xFF, 0xFF)
179
+ ```
180
+
181
+ ## Dark-deck styling
182
+
183
+ For dark backgrounds you usually need to recolor every text-bearing
184
+ location *and* every axis line / gridline. Two write-only facades and
185
+ one one-call helper handle it:
186
+
187
+ ```python
188
+ chart.text_color = "#FFFFFF" # walks chart font, legend, title, data labels
189
+ chart.line_color = "#3A3E5F" # walks axis lines + gridlines (where present)
190
+
191
+ # Or, the one-liner:
192
+ chart.apply_dark_theme(text="#FFFFFF", line="#3A3E5F")
193
+ ```
194
+
195
+ `line_color` is conservative: it skips axes that don't exist (pie /
196
+ doughnut), and never materialises gridlines on axes that don't
197
+ already have them — appearance changes are opt-in.
198
+
199
+ ## Horizontal bar charts: reading order
200
+
201
+ Horizontal bar (``BAR_*``) charts now default to top-to-bottom reading
202
+ order. Feeding ``["A", "B", "C"]`` renders ``A`` at the top, matching
203
+ natural reading order. Column charts retain left-to-right ordering.
204
+ Override post-creation with:
205
+
206
+ ```python
207
+ chart.category_axis.reverse_order = False # legacy bottom-up ordering
208
+ ```
209
+
210
+ ## End-to-end: branded chart
211
+
212
+ ```python
213
+ chart.apply_palette("modern")
214
+ chart.apply_quick_layout("title_axes_legend_bottom")
215
+
216
+ # Override the title text
217
+ chart.chart_title.text_frame.text = "ARR & NDR ($M / %)"
218
+ ```
219
+
220
+ ## Plot & axis fine-tuning ("go to Excel for this" gaps)
221
+
222
+ Knobs that previously required hand-editing in Excel:
223
+
224
+ - **Doughnut hole size** — `plot.hole_size = 65` (int 10–90; default 50).
225
+ - **Smoothed lines** — `plot.smooth = True` on a `LinePlot` curve-fits
226
+ every series; reads back `True` only when all series are smoothed.
227
+ - **Logarithmic value axis** — `chart.value_axis.log_base = 10` (float
228
+ 2–1000); set `= None` to restore a linear axis.
229
+
230
+ ```python
231
+ plot = chart.plots[0]
232
+ plot.hole_size = 65 # doughnut
233
+ plot.smooth = True # line chart
234
+ chart.value_axis.log_base = 10
235
+ ```
236
+
237
+ ## Trendlines, error bars, dual axis (scientific decks)
238
+
239
+ ```python
240
+ s = chart.series[0]
241
+ s.trendlines.add("linear", show_equation=True, show_r_squared=True)
242
+ s.trendlines.add("poly", order=3) # polynomial
243
+ s.trendlines.add("movingAvg", period=2) # moving average
244
+
245
+ s.error_bars.fixed(0.5) # or .percentage(5),
246
+ s.error_bars.standard_deviation(1) # .standard_error(), .custom(plus, minus)
247
+
248
+ chart.secondary_value_axis # add right-hand value axis
249
+ chart.series[1].axis_group = "secondary" # move a series onto it
250
+ ```
251
+
252
+ Trendlines / error bars are available on bar, line, scatter, area, and
253
+ bubble series (not pie/radar). New axis ids stay signed-int32, so
254
+ PowerPoint never prompts to repair the file.
@@ -0,0 +1,234 @@
1
+ # Composition: from_spec, import_slide, apply_template (Phase 2 + 7)
2
+
3
+ The `pptx2.compose` package collects entry points for higher-level
4
+ authoring and cross-presentation operations.
5
+
6
+ ## JSON authoring with `from_spec`
7
+
8
+ The single entry point for generator scripts (LLM or otherwise). The
9
+ spec dict is validated for known keys and value shapes before
10
+ construction (no JSON Schema is involved):
11
+
12
+ ```python
13
+ from pptx2.compose import from_spec
14
+
15
+ prs = from_spec({
16
+ # 16:9 widescreen — the modern default. Other shorthands:
17
+ # "4:3", "16:10", "a4", "letter". Or pass an (w, h) pair / dict
18
+ # in inches.
19
+ "slide_size": "16:9",
20
+ # Brand tokens. Inline dict / preset / yaml / DesignTokens — all
21
+ # supported. ``"theme"`` is a friendly alias when ``"tokens"`` is
22
+ # absent.
23
+ "tokens": {"preset": "modern_light"},
24
+ "slides": [
25
+ {
26
+ "layout": "title",
27
+ "title": "Q4 Review",
28
+ "subtitle": "April 2026",
29
+ "transition": "morph",
30
+ },
31
+ {
32
+ "layout": "kpi",
33
+ "title": "Run-rate metrics",
34
+ "kpis": [
35
+ {"label": "ARR", "value": "$182M", "delta": +0.27},
36
+ {"label": "NDR", "value": "131%", "delta": +0.03},
37
+ ],
38
+ },
39
+ {
40
+ "layout": "bullets",
41
+ "title": "Customer impact",
42
+ "bullets": [
43
+ "Two flagship customers shipped this week.",
44
+ "NPS improved 8 points QoQ.",
45
+ ],
46
+ },
47
+ ],
48
+ "lint": "raise", # fail loudly on bad output
49
+ })
50
+
51
+ prs.save("q4-review.pptx")
52
+ ```
53
+
54
+ When `tokens` is present, the legacy alias names `"title"` and
55
+ `"bullets"` are silently upgraded to the styled recipes
56
+ (`"title_recipe"` / `"bullets_recipe"`); this used to be silent and
57
+ strand the deck on default placeholder styling. Pass an explicit
58
+ recipe layout name (e.g. `"kpi"`, `"chart"`, `"table"`, `"quote"`)
59
+ to skip the alias step and reach the styled recipe directly.
60
+
61
+ An unrecognized `"layout"` name raises `ValueError` (with the closest
62
+ valid layout suggested) rather than silently producing a blank slide —
63
+ so a typo like `"titel"` fails loudly. Use `"layout": "blank"`
64
+ explicitly when you actually want a blank slide.
65
+
66
+ `tokens` accepts five shapes:
67
+
68
+ - A preset by name: `{"preset": "modern_light"}` (optionally with
69
+ `"overrides": {...}` to layer brand-specific tweaks on top).
70
+ - A YAML brand file: `{"yaml": "brand.yml"}`.
71
+ - An inline dict matching `DesignTokens.from_dict` (palette,
72
+ typography, radii, shadows, spacings).
73
+ - A `DesignTokens` instance — handy when the same token bag is
74
+ reused between imperative recipe calls and `from_spec`.
75
+ - `None` (omitted) — falls through to the placeholder layouts on
76
+ the host template; the recipe slides render with their built-in
77
+ defaults.
78
+
79
+ `slide_size` is optional. With no setting, the bundled template's
80
+ 4:3 dimensions (10" × 7.5") are used. The shorthand resolves
81
+ through `_SLIDE_SIZE_PRESETS`; pass an inches pair like
82
+ `(13.333, 7.5)` for any custom dimension.
83
+
84
+ Layout names map either to Phase-9 design recipes (where supplied) or
85
+ to a small built-in set of layouts using the host presentation's
86
+ master.
87
+
88
+ The `lint` field accepts `"off"`, `"warn"`, or `"raise"`:
89
+
90
+ - ``"off"`` (default) — no lint pass.
91
+ - ``"warn"`` — log every issue through the stdlib ``logging`` module.
92
+ - ``"raise"`` — raise ``pptx2.exc.LintError`` if any error-severity
93
+ issue is found.
94
+
95
+ `from_spec` runs the lint pass internally; outside of `from_spec`,
96
+ iterate the slides yourself (see `lint.md`).
97
+
98
+ ## Free-standing shapes on a spec slide
99
+
100
+ Layouts and recipes place their own shapes. When you need something a
101
+ layout doesn't provide, a slide entry may carry a `shapes` list, applied
102
+ *after* the layout runs:
103
+
104
+ ```python
105
+ {
106
+ "layout": "blank",
107
+ "shapes": [
108
+ {"name": "card", "shape": "rounded_rectangle",
109
+ "left": 1, "top": 1, "width": 4, "height": 2,
110
+ "layer": "card"},
111
+ {"name": "badge", "shape": "oval", "text": "NEW",
112
+ "left": 4.4, "top": 0.7, "width": 1.2, "height": 0.8,
113
+ "layer_above": "card"},
114
+ ],
115
+ }
116
+ ```
117
+
118
+ Keys: `left` / `top` / `width` / `height` (required; inches, or a
119
+ `Length`), `name`, `shape` (an `MSO_SHAPE` member name,
120
+ case-insensitive — default `"textbox"`), `text`, and the four
121
+ overlap-intent fields below. Unknown keys are rejected with a
122
+ did-you-mean hint rather than silently ignored.
123
+
124
+ This is deliberately minimal — geometry, type, text, intent. It is not a
125
+ drawing DSL; reach for the Python API when you need fills, effects, or
126
+ anything structural.
127
+
128
+ ### Declaring intentional overlaps in a spec
129
+
130
+ An LLM writing a spec can declare *at generation time* that an overlap
131
+ is deliberate, so the built deck lints clean without a manual pass. All
132
+ three mechanisms from `lint.md` are spec-level fields:
133
+
134
+ ```python
135
+ {"name": "badge", ..., "lint_group": "kpi-1"} # n-ary tag
136
+ {"name": "badge", ..., "allow_overlap_with": "card"} # one pair
137
+ {"name": "badge", ..., "allow_overlap_with": ["card", "rule"]}
138
+ {"name": "card", ..., "layer": "card"} # asserts z-order
139
+ {"name": "badge", ..., "layer_above": "card"}
140
+ ```
141
+
142
+ `allow_overlap_with` names other shapes by their spec `name`, not by
143
+ shape id — ids don't exist until the deck is built. Resolution happens
144
+ after every shape on the slide exists, so a **forward reference works**:
145
+ naming a shape defined later in the same list is fine.
146
+
147
+ Names must be unique within a slide, and a reference must stay within
148
+ its slide — an allowance is keyed on shape id, and ids are only unique
149
+ per slide. Both mistakes raise a `ValueError` locating the bad entry as
150
+ `slides[i].shapes[j]`.
151
+
152
+ ## Cross-presentation operations
153
+
154
+ ```python
155
+ from pptx2 import Presentation
156
+ from pptx2.compose import import_slide, apply_template
157
+ ```
158
+
159
+ ### Importing a slide
160
+
161
+ ```python
162
+ src = Presentation("source.pptx")
163
+ dst = Presentation("destination.pptx")
164
+
165
+ # Clone src.slides[3] into dst, including its layout reference.
166
+ import_slide(dst, src.slides[3], merge_master="dedupe")
167
+ ```
168
+
169
+ Image-rename collisions, layout references, and master/theme parts are
170
+ handled automatically. Two strategies for masters:
171
+
172
+ - `merge_master="dedupe"` (default-ish, recommended) reuses an
173
+ equivalent master in the destination if one matches.
174
+ - `merge_master="clone"` always brings a fresh copy of the source
175
+ master alongside.
176
+
177
+ ### Applying a template
178
+
179
+ ```python
180
+ apply_template(dst, "brand-template.potx")
181
+ ```
182
+
183
+ Re-points every slide's layout/master/theme at masters from the
184
+ `.potx` (or `.pptx`). Slide content is preserved. Layout matching:
185
+ name → type → first layout. Unreferenced old masters / layouts /
186
+ themes are dropped from the saved package.
187
+
188
+ ## End-to-end pipeline
189
+
190
+ A typical "we have a master deck and need to bolt on N report slides"
191
+ script:
192
+
193
+ ```python
194
+ from pptx2 import Presentation
195
+ from pptx2.compose import import_slide, apply_template, from_spec
196
+
197
+ # 1. Generate the body slides from data
198
+ body = from_spec({
199
+ "slides": [
200
+ {"layout": "kpi_grid", "title": team["name"], "kpis": team["kpis"]}
201
+ for team in teams
202
+ ],
203
+ })
204
+
205
+ # 2. Open the cover deck and append the body slides
206
+ deck = Presentation("cover.pptx")
207
+ for slide in body.slides:
208
+ import_slide(deck, slide, merge_master="dedupe")
209
+
210
+ # 3. Re-skin everything against the latest brand template
211
+ apply_template(deck, "brand-2026.potx")
212
+
213
+ # 4. Lint and save (or set prs.lint_on_save = "raise" and just save)
214
+ from pptx2.exc import LintError
215
+
216
+ errors = []
217
+ for slide in deck.slides:
218
+ slide.lint().auto_fix()
219
+ errors.extend(
220
+ i for i in slide.lint().issues if i.severity.value == "error"
221
+ )
222
+ if errors:
223
+ raise LintError("; ".join(str(e) for e in errors))
224
+
225
+ deck.save("final.pptx")
226
+ ```
227
+
228
+ ## When NOT to use `from_spec`
229
+
230
+ `from_spec` is intentionally bounded — small built-in layouts plus the
231
+ recipes from `pptx2.design.recipes`. If you need something the recipe
232
+ library doesn't ship, drop down to direct shape construction (or write
233
+ a recipe and contribute it back). Don't try to express arbitrary
234
+ geometry through the spec dict.