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.
- pptx2/__init__.py +152 -0
- pptx2/_color.py +75 -0
- pptx2/_slide_importer.py +597 -0
- pptx2/_svg.py +155 -0
- pptx2/_template_applier.py +292 -0
- pptx2/_textstyle.py +187 -0
- pptx2/accessibility.py +365 -0
- pptx2/action.py +270 -0
- pptx2/animation.py +2237 -0
- pptx2/api.py +49 -0
- pptx2/audit.py +258 -0
- pptx2/chart/__init__.py +0 -0
- pptx2/chart/analytics.py +381 -0
- pptx2/chart/axis.py +543 -0
- pptx2/chart/category.py +200 -0
- pptx2/chart/chart.py +670 -0
- pptx2/chart/data.py +864 -0
- pptx2/chart/datalabel.py +406 -0
- pptx2/chart/legend.py +86 -0
- pptx2/chart/marker.py +70 -0
- pptx2/chart/palettes.py +129 -0
- pptx2/chart/plot.py +462 -0
- pptx2/chart/point.py +101 -0
- pptx2/chart/quick_layouts.py +325 -0
- pptx2/chart/series.py +334 -0
- pptx2/chart/xlsx.py +272 -0
- pptx2/chart/xmlwriter.py +1845 -0
- pptx2/compose/__init__.py +28 -0
- pptx2/compose/from_spec.py +1094 -0
- pptx2/design/__init__.py +8 -0
- pptx2/design/components.py +607 -0
- pptx2/design/figures.py +389 -0
- pptx2/design/layout.py +370 -0
- pptx2/design/recipes.py +1967 -0
- pptx2/design/style.py +209 -0
- pptx2/design/tokens.py +915 -0
- pptx2/diagrams.py +754 -0
- pptx2/dml/__init__.py +0 -0
- pptx2/dml/chtfmt.py +40 -0
- pptx2/dml/color.py +496 -0
- pptx2/dml/effect.py +909 -0
- pptx2/dml/fill.py +691 -0
- pptx2/dml/line.py +287 -0
- pptx2/dml/picture.py +212 -0
- pptx2/dml/three_d.py +381 -0
- pptx2/enum/__init__.py +0 -0
- pptx2/enum/action.py +71 -0
- pptx2/enum/animation.py +31 -0
- pptx2/enum/base.py +218 -0
- pptx2/enum/chart.py +574 -0
- pptx2/enum/dml.py +740 -0
- pptx2/enum/lang.py +685 -0
- pptx2/enum/presentation.py +133 -0
- pptx2/enum/shapes.py +1029 -0
- pptx2/enum/text.py +230 -0
- pptx2/exc.py +42 -0
- pptx2/formats.py +139 -0
- pptx2/geometry.py +420 -0
- pptx2/inherit.py +109 -0
- pptx2/lint.py +2256 -0
- pptx2/math.py +177 -0
- pptx2/media.py +197 -0
- pptx2/opc/__init__.py +0 -0
- pptx2/opc/constants.py +332 -0
- pptx2/opc/oxml.py +188 -0
- pptx2/opc/package.py +762 -0
- pptx2/opc/packuri.py +109 -0
- pptx2/opc/serialized.py +296 -0
- pptx2/opc/shared.py +20 -0
- pptx2/opc/spec.py +45 -0
- pptx2/oxml/__init__.py +555 -0
- pptx2/oxml/action.py +53 -0
- pptx2/oxml/chart/__init__.py +0 -0
- pptx2/oxml/chart/axis.py +337 -0
- pptx2/oxml/chart/chart.py +481 -0
- pptx2/oxml/chart/datalabel.py +253 -0
- pptx2/oxml/chart/legend.py +72 -0
- pptx2/oxml/chart/marker.py +61 -0
- pptx2/oxml/chart/plot.py +365 -0
- pptx2/oxml/chart/series.py +425 -0
- pptx2/oxml/chart/shared.py +220 -0
- pptx2/oxml/coreprops.py +288 -0
- pptx2/oxml/dml/__init__.py +0 -0
- pptx2/oxml/dml/color.py +135 -0
- pptx2/oxml/dml/effect.py +213 -0
- pptx2/oxml/dml/fill.py +316 -0
- pptx2/oxml/dml/line.py +12 -0
- pptx2/oxml/dml/three_d.py +110 -0
- pptx2/oxml/ns.py +135 -0
- pptx2/oxml/presentation.py +313 -0
- pptx2/oxml/shapes/__init__.py +19 -0
- pptx2/oxml/shapes/autoshape.py +467 -0
- pptx2/oxml/shapes/connector.py +107 -0
- pptx2/oxml/shapes/graphfrm.py +347 -0
- pptx2/oxml/shapes/groupshape.py +329 -0
- pptx2/oxml/shapes/picture.py +270 -0
- pptx2/oxml/shapes/shared.py +577 -0
- pptx2/oxml/simpletypes.py +1027 -0
- pptx2/oxml/slide.py +563 -0
- pptx2/oxml/table.py +650 -0
- pptx2/oxml/text.py +815 -0
- pptx2/oxml/theme.py +36 -0
- pptx2/oxml/xmlchemy.py +717 -0
- pptx2/package.py +222 -0
- pptx2/parts/__init__.py +0 -0
- pptx2/parts/chart.py +95 -0
- pptx2/parts/coreprops.py +167 -0
- pptx2/parts/diagram.py +37 -0
- pptx2/parts/embeddedpackage.py +93 -0
- pptx2/parts/image.py +275 -0
- pptx2/parts/media.py +37 -0
- pptx2/parts/presentation.py +136 -0
- pptx2/parts/slide.py +371 -0
- pptx2/presentation.py +408 -0
- pptx2/py.typed +0 -0
- pptx2/render.py +586 -0
- pptx2/section.py +272 -0
- pptx2/shapes/__init__.py +26 -0
- pptx2/shapes/autoshape.py +442 -0
- pptx2/shapes/base.py +1078 -0
- pptx2/shapes/connector.py +297 -0
- pptx2/shapes/freeform.py +337 -0
- pptx2/shapes/graphfrm.py +316 -0
- pptx2/shapes/group.py +264 -0
- pptx2/shapes/picture.py +422 -0
- pptx2/shapes/placeholder.py +468 -0
- pptx2/shapes/shapetree.py +2027 -0
- pptx2/shared.py +82 -0
- pptx2/skill/SKILL.md +450 -0
- pptx2/skill/__init__.py +78 -0
- pptx2/skill/__main__.py +64 -0
- pptx2/skill/references/animations.md +189 -0
- pptx2/skill/references/basics.md +421 -0
- pptx2/skill/references/charts.md +254 -0
- pptx2/skill/references/compose.md +234 -0
- pptx2/skill/references/design.md +366 -0
- pptx2/skill/references/effects.md +249 -0
- pptx2/skill/references/end-to-end-deck.md +231 -0
- pptx2/skill/references/geometry-and-arrows.md +334 -0
- pptx2/skill/references/lint.md +275 -0
- pptx2/skill/references/math.md +86 -0
- pptx2/skill/references/picture-effects.md +129 -0
- pptx2/skill/references/render.md +151 -0
- pptx2/skill/references/smart-art.md +75 -0
- pptx2/skill/references/space-aware-authoring.md +249 -0
- pptx2/skill/references/tables.md +244 -0
- pptx2/skill/references/theme.md +127 -0
- pptx2/skill/references/three-d.md +109 -0
- pptx2/skill/references/transitions.md +100 -0
- pptx2/slide.py +1244 -0
- pptx2/smart_art.py +220 -0
- pptx2/spec.py +633 -0
- pptx2/table.py +1181 -0
- pptx2/table_styles.py +184 -0
- pptx2/templates/default.pptx +0 -0
- pptx2/templates/docx-icon.emf +0 -0
- pptx2/templates/generic-icon.emf +0 -0
- pptx2/templates/notes.xml +23 -0
- pptx2/templates/notesMaster.xml +352 -0
- pptx2/templates/pptx-icon.emf +0 -0
- pptx2/templates/theme.xml +321 -0
- pptx2/templates/xlsx-icon.emf +0 -0
- pptx2/text/__init__.py +0 -0
- pptx2/text/fonts.py +482 -0
- pptx2/text/layout.py +374 -0
- pptx2/text/text.py +1272 -0
- pptx2/theme.py +721 -0
- pptx2/types.py +36 -0
- pptx2/util.py +263 -0
- python_pptx2-2.13.0.dist-info/METADATA +351 -0
- python_pptx2-2.13.0.dist-info/RECORD +175 -0
- python_pptx2-2.13.0.dist-info/WHEEL +5 -0
- python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
- python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
- 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.
|