athena-python-pptx 0.13.0__tar.gz → 0.14.0__tar.gz
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.
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/AGENTS.md +5 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/CHANGELOG.md +177 -2
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/PKG-INFO +1 -1
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/docs/API_PARITY_EXCEPTIONS.md +279 -45
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/__init__.py +3 -2
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/batching.py +54 -1
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/client.py +104 -57
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/commands.py +14 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/presentation.py +14 -10
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/__init__.py +1310 -163
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/slides.py +1321 -381
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/text/__init__.py +138 -15
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/typing.py +6 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pyproject.toml +1 -1
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/.gitignore +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/CLAUDE.md +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/README.md +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/_citations.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/_references.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/action.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/errors.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/exc.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/kit.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/media.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/oxml/__init__.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/oxml/ns.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/package.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/shared.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/slide.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/spec.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/table.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/types.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/units.py +0 -0
- {athena_python_pptx-0.13.0 → athena_python_pptx-0.14.0}/pptx/util.py +0 -0
|
@@ -59,6 +59,11 @@ A small number of REST-SDK-specific departures are documented in
|
|
|
59
59
|
- **`_BaseSeriesData.index`** raises `NotImplementedError` (raising
|
|
60
60
|
rather than silently returning 0).
|
|
61
61
|
- **`Package.open()`** raises `UnsupportedFeatureError`.
|
|
62
|
+
- **Axis-title text frames** are one paragraph with one font: runs join
|
|
63
|
+
into one title string in the first run's font, with a one-time
|
|
64
|
+
`UserWarning` when that drops styling.
|
|
65
|
+
- **Table-cell text frames** are one paragraph with one run (bold, italic,
|
|
66
|
+
size, name, color); `font.underline` raises.
|
|
62
67
|
|
|
63
68
|
### Intentionally omitted (REST SDK limitations)
|
|
64
69
|
|
|
@@ -2,7 +2,182 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.14.0 — waterfall charts render and export as waterfalls
|
|
6
|
+
|
|
7
|
+
Requires a studio that authors `AddChart2016` waterfalls and accepts the
|
|
8
|
+
`SetWaterfallSubtotals` chart patch (older servers reject the batch).
|
|
9
|
+
|
|
10
|
+
- `add_chart(XL_CHART_TYPE.WATERFALL, ...)` now makes a real waterfall instead
|
|
11
|
+
of a clustered column chart: one series of deltas, bars floating on the
|
|
12
|
+
running total, data labels on. More than one series raises
|
|
13
|
+
`ValidationError`.
|
|
14
|
+
- New Athena extension `chart.plots[0].subtotals = [0, 5]` (also
|
|
15
|
+
`set_subtotals([...])`, a `WaterfallPlot`) marks the bars drawn from zero as
|
|
16
|
+
totals; a total left `None` shows the running total.
|
|
17
|
+
- `chart.chart_type` reads back `XL_CHART_TYPE.WATERFALL`.
|
|
18
|
+
- `chart.plots` on a reopened deck picks the plot class from the snapshot's
|
|
19
|
+
plot type, so `chart.plots[0]` is a `WaterfallPlot` (or `BarPlot`,
|
|
20
|
+
`LinePlot`, ...) instead of the bare base plot.
|
|
21
|
+
- `chart.replace_data()` keeps the plot's other settings (subtotals, gap
|
|
22
|
+
width, data labels) in the local mirror, as the server does; subtotal
|
|
23
|
+
indices past the new points are dropped on both sides.
|
|
24
|
+
- The exported deck carries a stacked-column waterfall (invisible base
|
|
25
|
+
series), so it opens in any PowerPoint version and stays editable; the studio
|
|
26
|
+
preview matches it (centred labels, no connector lines).
|
|
27
|
+
|
|
28
|
+
## 0.14.0 — chart series line and no-fill formatting reach the deck
|
|
29
|
+
|
|
30
|
+
Requires a studio with the `SetSeriesLine` / `SetSeriesFillVisible` /
|
|
31
|
+
`SetPointFillVisible` chart patch ops (older servers reject the batch).
|
|
32
|
+
|
|
33
|
+
- `series.format.line.color.rgb`, `.width`, `.dash_style` and
|
|
34
|
+
`.fill.background()` / `.fill.solid()` now emit `SetSeriesLine`: the plotted
|
|
35
|
+
line of a line / XY / radar series, the outline of a bar / area / pie /
|
|
36
|
+
bubble series. Previously these were local-state-only and a line chart kept
|
|
37
|
+
its palette colours. `dash_style` accepts `MSO_LINE_DASH_STYLE` members,
|
|
38
|
+
snake_case line tokens and `<a:prstDash>` presets, and reads back the enum
|
|
39
|
+
member (or the preset string for `dot` / `sysDashDot` / `sysDashDotDot`,
|
|
40
|
+
which have none); `None` clears width / dash.
|
|
41
|
+
- `series.format.fill.background()` emits `SetSeriesFillVisible` (the invisible
|
|
42
|
+
base series of a stacked-column waterfall now stays invisible); `solid()`
|
|
43
|
+
on a hidden fill, or assigning `fore_color`, shows it again.
|
|
44
|
+
`series.points[i].format.fill.background()` emits `SetPointFillVisible`; the
|
|
45
|
+
hidden state is kept on the series, so `solid()` through a new `format`
|
|
46
|
+
object (or after a refresh) shows the point again.
|
|
47
|
+
- Chart format writes that still have no patch op (axis line / fill, axis-title
|
|
48
|
+
and gridline fills, per-point line, marker hollow fill / line width / dash,
|
|
49
|
+
line `theme_color`, data-label borders) keep their local value and emit a
|
|
50
|
+
one-time `UserWarning`: `"<property> is not supported by the Athena studio
|
|
51
|
+
renderer and was ignored"`. They never raise. Data-label border setters no
|
|
52
|
+
longer send fields the server silently dropped.
|
|
53
|
+
- Gridline `dash_style` accepts `MSO_LINE_DASH_STYLE` members (previously only
|
|
54
|
+
preset strings).
|
|
55
|
+
|
|
56
|
+
## 0.14.0 — axis-title paragraphs and runs, per-cell font family
|
|
57
|
+
|
|
58
|
+
Two python-pptx idioms crashed agent scripts partway through a deck
|
|
59
|
+
(staging session thread_3de488f7), after earlier batches had committed:
|
|
60
|
+
|
|
61
|
+
- `axis.axis_title.text_frame.paragraphs[0].runs` raised `AttributeError`
|
|
62
|
+
(`AxisTitleTextFrame` exposed only `.text`). It is now a python-pptx
|
|
63
|
+
`TextFrame`: `paragraphs` holds one paragraph whose `text`, `runs`,
|
|
64
|
+
`font` and `add_run()` work, and each run's `text` / `font`. Text goes to
|
|
65
|
+
the title text and fonts to the `SetAxisTitle` font fields (size, bold,
|
|
66
|
+
`color.rgb`, name). The patch model is one string and one font, so several
|
|
67
|
+
runs join into one title string drawn in the first run's font over the
|
|
68
|
+
paragraph font; a `UserWarning` is emitted once per axis title when that
|
|
69
|
+
drops a run's styling or when a font property the patch can't carry
|
|
70
|
+
(italic, underline, strike, theme color, …) is set. An existing title's
|
|
71
|
+
text is now read from the deck snapshot.
|
|
72
|
+
- `cell.text_frame.paragraphs[0].runs[0].font.name = "Calibri"` raised
|
|
73
|
+
`UnsupportedFeatureError`. `SetTableCell` now carries `font_family` (wire
|
|
74
|
+
`fontFamily`) and `italic`; `font.name` / `font.italic` set them, the
|
|
75
|
+
studio draws them and the export writes `<a:latin typeface>` / `i="1"` on
|
|
76
|
+
the cell's runs, for SDK-authored and uploaded tables alike.
|
|
77
|
+
`paragraphs[0].font` is the cell's run font. Assigning `None` cannot clear
|
|
78
|
+
a value the session wrote (no inherit marker on the wire) and warns.
|
|
79
|
+
`font.underline` still raises.
|
|
80
|
+
|
|
81
|
+
Needs a PPTX Studio server with `SetTableCell.fontFamily` / `italic`
|
|
82
|
+
(older servers drop the fields without error).
|
|
83
|
+
|
|
84
|
+
## 0.14.0 — `add_run()` on an empty paragraph returns `runs[0]`
|
|
85
|
+
|
|
86
|
+
Fixes the empty run agent-built decks stored ahead of the real text in
|
|
87
|
+
almost every text box (staging `asset_e41e33eb`: 224 of 226), and the
|
|
88
|
+
doubled, default-styled titles and labels that later `runs[0].text = ...`
|
|
89
|
+
edits produced by writing that empty run.
|
|
90
|
+
|
|
91
|
+
- The first `paragraph.add_run()` on a paragraph whose only run is the SDK's
|
|
92
|
+
untouched empty seed returns that run as `runs[0]` instead of appending
|
|
93
|
+
after it, matching python-pptx. This covers new text boxes and autoshapes,
|
|
94
|
+
blank lines from `text_frame.text = ...`, and paragraphs emptied by
|
|
95
|
+
`paragraph.text = ""`. Repeated `add_run()` calls still create distinct
|
|
96
|
+
runs, and a seed run the caller wrote directly is kept.
|
|
97
|
+
- After a text reset the returned run is bare, as in python-pptx: the SDK
|
|
98
|
+
sends `SetRunText` with the new `resetFormatting` flag so the look the
|
|
99
|
+
server's `SetText` kept on that empty run is dropped. The same command
|
|
100
|
+
writes run 0 into snapshot paragraphs stored with no runs (ingested empty
|
|
101
|
+
placeholders), so a style set before the text resolves.
|
|
102
|
+
- Snapshot-loaded paragraphs keep their stored run indices, so decks written
|
|
103
|
+
before this fix keep their stored empty runs. `text_frame.clear()` +
|
|
104
|
+
`add_run()` is unchanged.
|
|
105
|
+
- Release after the pptx-studio server with `SetRunText.resetFormatting` is
|
|
106
|
+
deployed. Older servers ignore the flag, and the run returned after a text
|
|
107
|
+
reset then keeps the replaced text's look.
|
|
108
|
+
- Documented in `docs/API_PARITY_EXCEPTIONS.md`.
|
|
109
|
+
|
|
110
|
+
## 0.14.0 — `validate()` judges text by its ink and flags table rows only when they grow
|
|
111
|
+
|
|
112
|
+
Cuts the false layout warnings an agent had to reason about and dismiss
|
|
113
|
+
(staging session `thread_3de488f7`: collisions for value labels on bubbles,
|
|
114
|
+
captions inside wide title boxes and table cells in rows sized for two
|
|
115
|
+
lines). Same signatures.
|
|
116
|
+
|
|
117
|
+
- **Text is compared by its ink, not its frame.** Each text frame is laid out
|
|
118
|
+
on the glyph-width estimate — greedy word wrap at the inner width minus the
|
|
119
|
+
paragraph indent (the first-line indent on the first line; a bullet moves
|
|
120
|
+
the text at least 1.5em in and an unindented outline level 0.375in, as in
|
|
121
|
+
the studio renderer), per-paragraph size and line spacing — and every line is
|
|
122
|
+
placed by its paragraph's alignment and the frame's vertical anchor
|
|
123
|
+
(autoshapes centre by default, text boxes anchor at the top). Lines are
|
|
124
|
+
widened 10% within their frame so an under-estimate cannot hide a real
|
|
125
|
+
collision; a placeholder's alignment or anchor inherited from its layout
|
|
126
|
+
widens the ink to every place the text could be. Text whose ink sits
|
|
127
|
+
entirely on a shape (a label on a card or bubble, a caption on a picture)
|
|
128
|
+
counts as containment unless that shape is drawn over it with an opaque
|
|
129
|
+
fill; frames that overlap while their texts do not are not reported; and
|
|
130
|
+
text that runs over the edge of a visible shape (fill or outline — plain
|
|
131
|
+
text boxes show no edge) or partly under a later opaque shape gets its own
|
|
132
|
+
message. Hidden text is judged by the share of the *text* under the cover,
|
|
133
|
+
and a cover that carries text of its own still hides what is beneath it.
|
|
134
|
+
Overflowing text now spills out of its frame the way PowerPoint draws it,
|
|
135
|
+
so a paragraph running onto the box below is reported even though the two
|
|
136
|
+
frames never touch; a studio "fits" verdict pulls that ink back inside.
|
|
137
|
+
- **Table cells warn only when the row grows**: wrapped lines x line height
|
|
138
|
+
plus the cell's top/bottom margins taller than the row (every row a vertical
|
|
139
|
+
merge spans) — the case where PowerPoint grows the row, measured at the
|
|
140
|
+
weight and family the snapshot resolved (table-style headers are bold). A single word wider
|
|
141
|
+
than its column (a number that will break mid-word) keeps a warning of its
|
|
142
|
+
own. Both are confirmed with `POST /measure-text-fit` like text overflow,
|
|
143
|
+
within the same 12-call budget.
|
|
144
|
+
- **Readable overflow message**: `needs ~2 lines at 20pt but only ~1 fits
|
|
145
|
+
(~0.67in of text in a 0.40in-tall box)`, `needs ~1 line at 32pt but not
|
|
146
|
+
even one line fits (…)`, paragraph spacing called out when it alone tips
|
|
147
|
+
the box over, `12–40pt` for mixed sizes, and correct plurals.
|
|
148
|
+
- **Measurement sends the real inner box.** `/measure-text-fit` subtracts
|
|
149
|
+
PowerPoint's default insets from the box it gets; `validate()` sent the box
|
|
150
|
+
with the frame margins already removed, so the studio measured a box 0.2in
|
|
151
|
+
narrower and 0.1in shorter than the text's and over-confirmed overflow. The
|
|
152
|
+
box now goes out grown by those insets.
|
|
153
|
+
|
|
154
|
+
## 0.13.1 — in-batch `add_slide()` carries layout placeholders, proxies survive `refresh()`
|
|
155
|
+
|
|
156
|
+
Fixes the `'NoneType' object has no attribute 'text'` failure agents hit
|
|
157
|
+
with the textbook idiom `slide = prs.slides.add_slide(layout);
|
|
158
|
+
slide.shapes.title.text = ...` inside `with prs.batch():`, and the
|
|
159
|
+
follow-on where `prs.refresh()` left the held `slide` orphaned.
|
|
160
|
+
|
|
161
|
+
- A slide added inside a batch now projects its layout's placeholders
|
|
162
|
+
locally, from the snapshot's `themeHierarchy.layouts[].placeholders`
|
|
163
|
+
(new server projection), under the same `__inherited__:<slide id>:<layout
|
|
164
|
+
element id>` ids the server materializes on `AddSlide`. `shapes.title` /
|
|
165
|
+
`placeholders` resolve immediately with no flush or refresh, and writes to
|
|
166
|
+
them are sent in the same request as the `AddSlide`, so batch atomicity is
|
|
167
|
+
preserved. Decks whose snapshot lacks layout placeholder data keep the
|
|
168
|
+
previous behaviour (`title` is `None` until the slide is committed).
|
|
169
|
+
- `CommandBuffer.flush()` records `client_id -> server slide id` for every
|
|
170
|
+
`AddSlide` / `CopySlideFromDeck` it sends, from the response's
|
|
171
|
+
`created.slideIds` (same order). `Slides._update_from_snapshot()` uses
|
|
172
|
+
only those authoritative aliases to re-key the *existing* `Slide` object
|
|
173
|
+
(and its projected placeholder shape ids) to the server id instead of
|
|
174
|
+
dropping it and appending a fresh one. Pending proxies without an alias
|
|
175
|
+
are never paired positionally with unseen snapshot slides, which may
|
|
176
|
+
belong to other collaborators.
|
|
177
|
+
- No public API change; `slide.delete()` remains intentionally absent
|
|
178
|
+
(python-pptx parity) — use `prs.slides.delete(slide)`.
|
|
179
|
+
|
|
180
|
+
## 0.13.0 — `pptx.kit` composition kit
|
|
6
181
|
|
|
7
182
|
- New Athena-only module `pptx.kit` (`from pptx.kit import Kit, Palette,
|
|
8
183
|
Fonts, Region`; also re-exported from `pptx`). `Kit(prs)` composes slides
|
|
@@ -33,7 +208,7 @@ All notable changes to `athena-python-pptx` are documented in this file.
|
|
|
33
208
|
Documented in `docs/API_PARITY_EXCEPTIONS.md`; demo in
|
|
34
209
|
`examples/kit_demo.py`.
|
|
35
210
|
|
|
36
|
-
##
|
|
211
|
+
## 0.13.0 — validate() finds overflow and hidden text, drops containment noise
|
|
37
212
|
|
|
38
213
|
Reworks `Slide.validate()` around the defects that actually ship in agent
|
|
39
214
|
decks (verified 2026-09-14 against two 8-slide agent builds: 167 and 127
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration
|
|
5
5
|
Project-URL: Homepage, https://github.com/pptx-studio/python-sdk
|
|
6
6
|
Project-URL: Documentation, https://docs.pptx-studio.com/sdk/python
|
|
@@ -98,6 +98,63 @@ adapter. The REST SDK has no OPC; use `Presentation.upload(path)`
|
|
|
98
98
|
instead, which uploads the file to the studio backend and returns
|
|
99
99
|
a `Presentation` adapter.
|
|
100
100
|
|
|
101
|
+
### Axis titles are one paragraph with one font
|
|
102
|
+
|
|
103
|
+
`axis.axis_title.text_frame` is a python-pptx `TextFrame`: `text`,
|
|
104
|
+
`paragraphs`, and each paragraph's `text` / `runs` / `font` / `add_run()`
|
|
105
|
+
and each run's `text` / `font` work. The `SetAxisTitle` patch carries one
|
|
106
|
+
title string and one font (size, bold, `color.rgb`, name), so:
|
|
107
|
+
|
|
108
|
+
- `paragraphs` always holds one paragraph; `add_paragraph()` is absent.
|
|
109
|
+
- Several runs join into one title string drawn in the first run's font,
|
|
110
|
+
layered over `paragraphs[0].font` (which is `axis_title.font`). When a
|
|
111
|
+
later run's own styling differs, the SDK emits one `UserWarning` per axis
|
|
112
|
+
title instead of dropping it silently.
|
|
113
|
+
- `italic`, `underline`, `strike`, `color.theme_color`, `cap`, sub/superscript
|
|
114
|
+
and character spacing are not carried; setting them on the title font emits
|
|
115
|
+
one `UserWarning` per axis title.
|
|
116
|
+
- Replacing the text (`text_frame.text = …`, `paragraph.text = …`) keeps the
|
|
117
|
+
title's font. python-pptx starts an unformatted run, but `SetAxisTitle`
|
|
118
|
+
cannot clear a font field; assign the font fields to change them.
|
|
119
|
+
- An existing title's text is read from the deck snapshot, for SDK-authored and
|
|
120
|
+
uploaded charts (its font is not).
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
tf = chart.value_axis.axis_title.text_frame
|
|
124
|
+
tf.text = "Revenue ($M)"
|
|
125
|
+
run = tf.paragraphs[0].runs[0]
|
|
126
|
+
run.font.size = Pt(10)
|
|
127
|
+
run.font.bold = True
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`chart.chart_title.text_frame` is not limited this way — its runs keep their
|
|
131
|
+
own fonts through `SetChartTitleRich`.
|
|
132
|
+
|
|
133
|
+
### Table cells are one paragraph with one run
|
|
134
|
+
|
|
135
|
+
python-pptx cells hold any number of paragraphs and runs. The SDK's cell
|
|
136
|
+
`text_frame` is a single paragraph with a single run whose font is the cell's
|
|
137
|
+
font — `paragraphs[0].font` and `paragraphs[0].runs[0].font` are the same
|
|
138
|
+
object, and `add_run()` / `add_paragraph()` return the existing run /
|
|
139
|
+
paragraph.
|
|
140
|
+
|
|
141
|
+
- Supported run styling: `bold`, `italic`, `size`, `name` (font family) and
|
|
142
|
+
`color.rgb`, sent on `SetTableCell`; the server writes them to every run of
|
|
143
|
+
the cell and the export emits them as `a:rPr` attributes / `<a:latin>`.
|
|
144
|
+
- `font.underline` raises `UnsupportedFeatureError`.
|
|
145
|
+
- `font.name` / `font.italic` read back what this session wrote; a cell's
|
|
146
|
+
own font in an uploaded deck reads as `None`.
|
|
147
|
+
- Assigning `None` to `font.name` / `font.italic` cannot clear a value this
|
|
148
|
+
session wrote (the wire format has no inherit marker): the cell keeps it and
|
|
149
|
+
the SDK emits a `UserWarning`. Assign `False` to turn italic off.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
cell.text = "Revenue"
|
|
153
|
+
run = cell.text_frame.paragraphs[0].runs[0]
|
|
154
|
+
run.font.name = "Georgia"
|
|
155
|
+
run.font.italic = True
|
|
156
|
+
```
|
|
157
|
+
|
|
101
158
|
### `Presentation(pptx=...)` constructor kwarg — exposed via `Presentation.create(pptx=...)`
|
|
102
159
|
|
|
103
160
|
Upstream's `Presentation(pptx=None)` accepts a file path / stream
|
|
@@ -271,6 +328,46 @@ explicit `run.hyperlink.address = None` with no slide target still sends
|
|
|
271
328
|
`hyperlinkTargetSlideIndex` on the run, and snapshot rich content with
|
|
272
329
|
that key hydrates back into `run.hyperlink`.
|
|
273
330
|
|
|
331
|
+
### A run-less paragraph shows one empty run; `add_run()` returns it as `runs[0]` (Unreleased)
|
|
332
|
+
|
|
333
|
+
In python-pptx a new text box's or autoshape's paragraph has no runs, and so
|
|
334
|
+
does one emptied by `text_frame.text = ""` (or a blank line of
|
|
335
|
+
`text_frame.text = "a\n\nb"`) or by `paragraph.text = ""`; `add_run()` then
|
|
336
|
+
creates `runs[0]`. This SDK seeds such a paragraph with one empty run so
|
|
337
|
+
code that writes `paragraph.runs[0]` directly keeps working. That seed is the
|
|
338
|
+
remaining departure: `len(paragraph.runs)` is 1, not 0, until a run is added.
|
|
339
|
+
|
|
340
|
+
The first `add_run()` on a paragraph whose only run is that untouched seed
|
|
341
|
+
now returns the seed as `runs[0]`, so the stored element holds the runs
|
|
342
|
+
python-pptx would write and a later `runs[0].text = ...` replaces the text.
|
|
343
|
+
Earlier versions appended the new run at index 1 behind a stored empty run;
|
|
344
|
+
a later `runs[0].text` edit then wrote the empty run and doubled the text.
|
|
345
|
+
|
|
346
|
+
- The seed is returned once. A second `add_run()` appends a new run.
|
|
347
|
+
- A seed the caller already wrote (`runs[0].text`, `runs[0].font.*`,
|
|
348
|
+
`runs[0].hyperlink.*`) is a real run; `add_run()` appends after it.
|
|
349
|
+
- Snapshot-loaded paragraphs keep their stored runs and indices, so decks
|
|
350
|
+
written before this change keep their stored leading empty runs. A lone
|
|
351
|
+
stored empty run with no formatting (how the server stores an empty line)
|
|
352
|
+
is returned like the seed; a lone empty run that carries formatting is
|
|
353
|
+
kept and `add_run()` appends after it, as python-pptx does. The stored
|
|
354
|
+
form cannot tell an empty line from an empty run a caller added on
|
|
355
|
+
purpose, so after a reload such a run is reused as well. Export writes no
|
|
356
|
+
empty-text runs, so stock python-pptx reading the exported file also sees
|
|
357
|
+
that paragraph with no runs.
|
|
358
|
+
- After a text reset the returned run is bare, as in python-pptx. The
|
|
359
|
+
server's `SetText` keeps the replaced text's look on the empty run it
|
|
360
|
+
writes, so `add_run()` first sends `SetRunText` with `resetFormatting` to
|
|
361
|
+
make that run bare. A pptx-studio server older than the field ignores it,
|
|
362
|
+
and the run then keeps the replaced text's look. A paragraph stored with no
|
|
363
|
+
runs (an ingested empty `<a:p/>`) gets its run 0 written the same way, so a
|
|
364
|
+
style set before the text has a run to land on.
|
|
365
|
+
- `text_frame.clear()` is unchanged: it already leaves no local runs, and
|
|
366
|
+
its `add_run()` writes into the run the cleared paragraph keeps on the
|
|
367
|
+
server, which retains the cleared text's look under the `SetText`
|
|
368
|
+
style-retention contract for clear-and-rebuild. python-pptx returns a bare
|
|
369
|
+
run there.
|
|
370
|
+
|
|
274
371
|
---
|
|
275
372
|
|
|
276
373
|
## Agent-Friendly Additions (not in python-pptx)
|
|
@@ -429,45 +526,71 @@ slide.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(2))
|
|
|
429
526
|
|
|
430
527
|
Report the layout defects a reader would notice, without rendering. An
|
|
431
528
|
empty list means the slide is clean — and a designed slide (cards behind
|
|
432
|
-
their labels, icons inside tiles) reaches it.
|
|
433
|
-
order, at most 15 per slide, then a count line
|
|
434
|
-
`"… and N more (M containment overlaps ignored)"
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
529
|
+
their labels, value labels on bubbles, icons inside tiles) reaches it.
|
|
530
|
+
Warnings come back in this order, at most 15 per slide, then a count line
|
|
531
|
+
`"… and N more (M containment overlaps ignored)"`.
|
|
532
|
+
|
|
533
|
+
Text is judged by its **ink** — the boxes its laid-out lines occupy — not by
|
|
534
|
+
its frame. Each frame's text is word-wrapped (greedy, at spaces) on the
|
|
535
|
+
glyph-width estimate at the inner width minus the paragraph indent (the
|
|
536
|
+
first-line indent on the first line; a bullet moves the text at least 1.5em
|
|
537
|
+
in, and an outline level without a margin 0.375in, as the studio renders),
|
|
538
|
+
with per-paragraph size and line spacing; every line is placed by its paragraph's
|
|
539
|
+
alignment and the frame's vertical anchor (autoshapes centre their text by
|
|
540
|
+
default, text boxes anchor at the top) and widened 10% within its frame so an
|
|
541
|
+
under-estimate cannot hide a collision. A placeholder's alignment or anchor
|
|
542
|
+
inherited from its layout is unknown to the proxy, so its ink covers every
|
|
543
|
+
place the text could be. Text that overflows spills out of its frame the way
|
|
544
|
+
PowerPoint draws it (a studio "fits" verdict pulls it back inside).
|
|
545
|
+
|
|
546
|
+
1. **Text overflow** — `"text in 'X' likely overflows its box: needs ~2
|
|
547
|
+
lines at 20pt but only ~1 fits (~0.67in of text in a 0.40in-tall box) —
|
|
548
|
+
…"` for non-table text whose laid-out height (lines × font × 1.2 × line
|
|
549
|
+
spacing + paragraph spacing) exceeds the box minus its text-frame
|
|
550
|
+
margins (`"… but not even one line fits"` when a single line is too
|
|
551
|
+
tall). Word-wrap-off frames are checked horizontally; auto-fit frames are
|
|
552
|
+
skipped. When the presentation has a studio client, each hit whose
|
|
553
|
+
paragraphs share one font size and weight is confirmed with one
|
|
554
|
+
`POST /measure-text-fit` pinned at that size (`fits: true` drops the
|
|
555
|
+
warning, at most 12 round-trips per call, network errors keep the
|
|
556
|
+
heuristic verdict); a mixed-font frame (60pt heading over 12pt body)
|
|
557
|
+
keeps the heuristic verdict because the server lays the frame out at a
|
|
558
|
+
single font. The endpoint subtracts PowerPoint's default insets from the
|
|
559
|
+
box it gets, so the inner box goes out grown by exactly those insets.
|
|
560
|
+
Table cells are reported here too, when their wrapped text (lines × line
|
|
561
|
+
height + the cell's top/bottom margins) is taller than the row — every
|
|
562
|
+
row a vertical merge spans — which is when PowerPoint grows the row, or
|
|
563
|
+
when one word is wider than the column and PowerPoint breaks it mid-word.
|
|
564
|
+
Both are confirmed by the same endpoint within the same budget.
|
|
565
|
+
2. **Hidden text** — `"text in 'X' is covered by 'Y' (N% of its text …)"`:
|
|
566
|
+
text whose ink is at least half covered by a *later* (higher z-order)
|
|
567
|
+
shape with a solid fill and no transparency — whether or not that shape
|
|
568
|
+
carries text of its own. A solid fill the snapshot reports without
|
|
452
569
|
transparency data is taken as opaque (PowerPoint's default); any
|
|
453
570
|
transparency set on the proxy or carried as `fillTransparency` disables
|
|
454
|
-
the rule for that shape. `send_to_back()` / `bring_to_front()`
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
3. **Text on text** —
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
571
|
+
the rule for that shape. `send_to_back()` / `bring_to_front()` mirror the
|
|
572
|
+
move on `slide.shapes` order so the rule sees the intended stack without
|
|
573
|
+
a refresh.
|
|
574
|
+
3. **Text on text** — `"text in 'X' and text in 'Y' overlap by ~0.40x0.20in
|
|
575
|
+
— the two texts collide"`: two texts (tables count, by their frame) whose
|
|
576
|
+
inks overlap.
|
|
577
|
+
4. **Other** — off-slide shapes; text that runs over the edge of a visible
|
|
578
|
+
shape (`"… runs over the edge of 'Y' …"`) or sits partly under a later
|
|
579
|
+
opaque one (`"… is partly hidden by 'Y' …"`); empty slide-level
|
|
461
580
|
placeholders.
|
|
462
581
|
|
|
463
|
-
Not reported:
|
|
464
|
-
|
|
465
|
-
a
|
|
466
|
-
|
|
582
|
+
Not reported: text whose ink sits entirely on a shape (a label on a card or
|
|
583
|
+
bubble, a caption on a picture) unless that shape is drawn over it with an
|
|
584
|
+
opaque fill; a shape sitting in the empty part of a text box;
|
|
585
|
+
frames that overlap while their texts do not; text crossing the edge of a
|
|
586
|
+
plain text box that paints neither fill nor outline; overlaps between two
|
|
587
|
+
text-less shapes; and hairline overlaps thinner than a text-frame inset
|
|
588
|
+
(0.05in). Rotated shapes near 90°/270° use their swapped visual bbox, and
|
|
589
|
+
their ink turns with them.
|
|
467
590
|
|
|
468
|
-
The
|
|
469
|
-
|
|
470
|
-
|
|
591
|
+
The estimate uses average glyph widths, so offline it can be off by a line
|
|
592
|
+
either way; render the slide before rebuilding a layout around a single
|
|
593
|
+
warning.
|
|
471
594
|
|
|
472
595
|
```python
|
|
473
596
|
warnings = slide.validate()
|
|
@@ -730,7 +853,7 @@ charts:
|
|
|
730
853
|
| `series.marker.style / size / format.fill.fore_color.rgb / format.line.color.rgb` | `SetMarkerStyle` |
|
|
731
854
|
| `axis.major_gridlines.format.line.* / minor_gridlines.format.line.*` | `SetGridlineFormat` |
|
|
732
855
|
| `axis.major_tick_mark / minor_tick_mark / tick_label_position` | `SetAxisTickMarks` |
|
|
733
|
-
| `axis.axis_title.text_frame.text +
|
|
856
|
+
| `axis.axis_title.text_frame.text` / `.paragraphs[0]` (+ `.runs`) and their `font.*` | `SetAxisTitle` (one string, one font — see "Axis titles are one paragraph with one font") |
|
|
734
857
|
| `chart.legend.position` | `SetLegendPosition` |
|
|
735
858
|
| `chart.legend.font.*` | `SetLegendFont` |
|
|
736
859
|
|
|
@@ -794,6 +917,118 @@ picture.line.dash_style = "solid"
|
|
|
794
917
|
|
|
795
918
|
---
|
|
796
919
|
|
|
920
|
+
## Chart Series Line + Fill Visibility; Ignored Chart Format Writes (unreleased)
|
|
921
|
+
|
|
922
|
+
`series.format.line` and `series.format.fill.background()` used to be
|
|
923
|
+
local-state-only: the calls succeeded, read back, and never reached the deck
|
|
924
|
+
(a line chart kept its palette colours; the hidden base series of a
|
|
925
|
+
stacked-column waterfall drew as solid bars). They now emit chart patches that
|
|
926
|
+
both the SDK-authored path (`author.ts`) and the ingested path (`patcher.ts`)
|
|
927
|
+
honour, and the studio renderer draws them:
|
|
928
|
+
|
|
929
|
+
| Property | Patch op | Emitted OOXML |
|
|
930
|
+
|---|---|---|
|
|
931
|
+
| `series.format.line.color.rgb = RGBColor(...)` | `SetSeriesLine` (`lineColorHex`) | `c:ser/c:spPr/a:ln/a:solidFill` |
|
|
932
|
+
| `series.format.line.width = Pt(2)` | `SetSeriesLine` (`lineWidthEmu`) | `<a:ln w="25400">` |
|
|
933
|
+
| `series.format.line.dash_style = MSO_LINE_DASH_STYLE.DASH` | `SetSeriesLine` (`dashStyle`) | `<a:prstDash val="dash"/>` |
|
|
934
|
+
| `series.format.line.fill.background()` / `.fill.solid()` | `SetSeriesLine` (`noLine`) | `<a:ln><a:noFill/></a:ln>` / removed |
|
|
935
|
+
| `series.format.fill.background()` / `.fill.solid()` on a hidden fill | `SetSeriesFillVisible` | `c:ser/c:spPr/<a:noFill/>` / removed |
|
|
936
|
+
| `series.points[i].format.fill.background()` / `.fill.solid()` | `SetPointFillVisible` | `<c:dPt><c:spPr><a:noFill/>` / removed |
|
|
937
|
+
|
|
938
|
+
python-pptx semantics: on a line / XY / radar series the line is the plotted
|
|
939
|
+
line; on a bar / area / pie / bubble series it is the outline. Assigning `None` to
|
|
940
|
+
`width` or `dash_style` clears the override; assigning a line colour (or
|
|
941
|
+
`fill.fore_color.rgb`) makes a hidden line or fill visible again.
|
|
942
|
+
`dash_style` accepts `MSO_LINE_DASH_STYLE` members, the SDK's snake_case line
|
|
943
|
+
tokens (`"round_dot"`, `"long_dash"`, …) and the `<a:prstDash>` presets
|
|
944
|
+
(`"sysDot"`, `"lgDash"`, …). Gridline `dash_style` now accepts the same forms.
|
|
945
|
+
Reading `series.format.line.dash_style` returns the `MSO_LINE_DASH_STYLE`
|
|
946
|
+
member, or the preset string for the presets that have none (`"dot"`,
|
|
947
|
+
`"sysDashDot"`, `"sysDashDotDot"`; python-pptx cannot read those at all).
|
|
948
|
+
A point's hidden fill is kept on the series, so a later
|
|
949
|
+
`series.points[i].format.fill.solid()`, through a new `format` object or
|
|
950
|
+
after a refresh, shows it again.
|
|
951
|
+
|
|
952
|
+
**Ignored writes warn instead of vanishing.** Chart format properties that
|
|
953
|
+
still have no patch op keep their value locally (read-after-write works) and
|
|
954
|
+
emit a `UserWarning` — `"<property> is not supported by the Athena studio
|
|
955
|
+
renderer and was ignored"` — once per property per presentation session.
|
|
956
|
+
They never raise, so existing scripts keep running:
|
|
957
|
+
|
|
958
|
+
| Property | Why it is ignored |
|
|
959
|
+
|---|---|
|
|
960
|
+
| `axis.format.line.*` / `axis.format.fill.*` | no axis line / fill op (hide the axis with `axis.visible = False`) |
|
|
961
|
+
| `axis_title.format.line.*` / `.fill.*` | no axis-title shape op |
|
|
962
|
+
| `axis.major_gridlines.format.fill.*` / `.format.line.fill.*` / `.line.color.theme_color` | gridlines carry only colour / width / dash (hide them with `axis.has_major_gridlines = False`) |
|
|
963
|
+
| `series.points[i].format.line.*` | no per-point outline op |
|
|
964
|
+
| `series.format.line.color.theme_color` | `SetSeriesLine` carries an sRGB colour only |
|
|
965
|
+
| `series.marker.format.fill.background()` / `.fill.fore_color.theme_color` | `SetMarkerStyle` has no hollow or theme fill |
|
|
966
|
+
| `series.marker.format.line.width` / `.dash_style` / `.fill.*` / `.color.theme_color` | `SetMarkerStyle` carries the marker line colour only |
|
|
967
|
+
| `DataLabels.border_color_hex` / `border_width` / `border_dash_style` | the data-label ops carry no border (these were previously sent and silently dropped by the server) |
|
|
968
|
+
|
|
969
|
+
Other python-pptx chart setters that stay local-only without a warning
|
|
970
|
+
(they are layout or label flags, not format): `Legend.horz_offset` /
|
|
971
|
+
`include_in_layout`, `DataLabels.show_legend_key` / `number_format_is_linked`,
|
|
972
|
+
and field-level `Trendline` edits.
|
|
973
|
+
|
|
974
|
+
---
|
|
975
|
+
|
|
976
|
+
## Waterfall Charts (unreleased)
|
|
977
|
+
|
|
978
|
+
python-pptx has no waterfall chart. `slide.shapes.add_chart(XL_CHART_TYPE.WATERFALL,
|
|
979
|
+
...)` previously produced a plain clustered column chart with no way to mark
|
|
980
|
+
totals; it now authors a waterfall the studio renders and exports.
|
|
981
|
+
|
|
982
|
+
```python
|
|
983
|
+
cd = CategoryChartData()
|
|
984
|
+
cd.categories = ["FY24", "Price", "Volume", "FX", "FY25"]
|
|
985
|
+
cd.add_series("EBIT", (120, 18, -9, -4, None)) # one series of deltas
|
|
986
|
+
chart = slide.shapes.add_chart(XL_CHART_TYPE.WATERFALL, x, y, cx, cy, cd).chart
|
|
987
|
+
chart.plots[0].subtotals = [0, 4] # Athena extension
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
| Surface | Kind | Wire |
|
|
991
|
+
|---|---|---|
|
|
992
|
+
| `chart.plots[0]` on a waterfall | `WaterfallPlot` (a `BarPlot`: `gap_width`, `data_labels`, `has_data_labels` work) | — |
|
|
993
|
+
| `WaterfallPlot.subtotals` (get / set) | Athena extension | `SetWaterfallSubtotals` chart patch |
|
|
994
|
+
| `WaterfallPlot.set_subtotals(indices)` | Athena extension (same as the setter) | `SetWaterfallSubtotals` |
|
|
995
|
+
| `chart.chart_type` | reads back `XL_CHART_TYPE.WATERFALL` | — |
|
|
996
|
+
|
|
997
|
+
Semantics follow Excel's "Set as total": a subtotal point is drawn from zero
|
|
998
|
+
to its own value and resets the running total; a subtotal left `None` shows
|
|
999
|
+
the running total. Every other point floats from the running total by its
|
|
1000
|
+
delta; a `None` delta is a gap. Setting `subtotals` replaces the list (`[]`
|
|
1001
|
+
clears it); an index outside the series raises `IndexError`. Exactly one
|
|
1002
|
+
series is accepted — more raise `ValidationError` before anything is sent.
|
|
1003
|
+
Data labels start on (deltas on floating bars, totals on totals);
|
|
1004
|
+
`plots[0].data_labels.show_value = False` turns them off.
|
|
1005
|
+
Increase / decrease / total bars take theme accent 1 / 2 / 3; a series fill
|
|
1006
|
+
colour paints every bar and `series.points[i].format.fill` overrides one bar.
|
|
1007
|
+
`series.format.fill.background()` hides every bar's fill except the bars
|
|
1008
|
+
with their own point colour. `chart.replace_data(...)` keeps the totals by
|
|
1009
|
+
position and drops any past the new points. A reopened deck's
|
|
1010
|
+
`chart.plots[0]` is a `WaterfallPlot` with its `subtotals`.
|
|
1011
|
+
|
|
1012
|
+
**Export — a stacked-column waterfall, not a chartEx part.** There is no
|
|
1013
|
+
Office-2016 chartEx (`cx:`) author, and a chartEx part only renders in
|
|
1014
|
+
PowerPoint 2016+. The exported chart is the classic stacked-column
|
|
1015
|
+
construction: an invisible base series (`<a:noFill/>`, left out of the
|
|
1016
|
+
legend) plus Increase / Decrease / Total series, with bars that cross zero
|
|
1017
|
+
split into above- and below-zero parts because PowerPoint stacks positive and
|
|
1018
|
+
negative values separately. It renders in every PowerPoint version, Keynote,
|
|
1019
|
+
Google Slides and LibreOffice, stays editable through Edit Data, and a
|
|
1020
|
+
re-imported deck renders as the same waterfall. What that construction cannot
|
|
1021
|
+
carry, the studio does not draw either, so the preview matches the deck: no
|
|
1022
|
+
connector lines, labels centred in the bars (a stacked chart has no
|
|
1023
|
+
outside-end position), and no label on a floating bar that crosses zero.
|
|
1024
|
+
PowerPoint shows it as a stacked column chart rather than its native waterfall
|
|
1025
|
+
type.
|
|
1026
|
+
|
|
1027
|
+
Not yet covered: waterfalls ingested from uploaded decks (Office-2016 chartEx
|
|
1028
|
+
parts) still arrive without series data and render as a placeholder.
|
|
1029
|
+
|
|
1030
|
+
---
|
|
1031
|
+
|
|
797
1032
|
## Table Cell Borders (added in v0.1.78+)
|
|
798
1033
|
|
|
799
1034
|
python-pptx exposes table-cell border styling only through raw OOXML
|
|
@@ -1346,11 +1581,11 @@ work in addition to SDK surface and are tracked separately.
|
|
|
1346
1581
|
`SUNBURST` (118), `HISTOGRAM` (119), `PARETO` (120),
|
|
1347
1582
|
`BOX_WHISKER` (121), `WATERFALL` (122), `FUNNEL` (123). Closes
|
|
1348
1583
|
python-pptx#583 ("New Chart Types in Office 2016"), #944
|
|
1349
|
-
(Treemap / Scatter), #651 (Waterfall), #1047 (Box plot).
|
|
1350
|
-
|
|
1351
|
-
`
|
|
1352
|
-
|
|
1353
|
-
chart-
|
|
1584
|
+
(Treemap / Scatter), #651 (Waterfall), #1047 (Box plot).
|
|
1585
|
+
`slide.shapes.add_chart(...)` dispatches them through a separate
|
|
1586
|
+
`AddChart2016` wire op. `WATERFALL` authors a real waterfall (see
|
|
1587
|
+
"Waterfall Charts" below); the others are still authored as the
|
|
1588
|
+
nearest legacy chart family until a cx-namespace author exists.
|
|
1354
1589
|
|
|
1355
1590
|
### Slide- and presentation-level additions
|
|
1356
1591
|
|
|
@@ -1449,11 +1684,10 @@ upstream surface preserved, additions tagged with
|
|
|
1449
1684
|
are set.
|
|
1450
1685
|
|
|
1451
1686
|
- **`DataLabels.border_color_hex / border_width / border_dash_style`**
|
|
1452
|
-
(closes python-pptx#716, 17-comment thread).
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
`DataLabels` surface.
|
|
1687
|
+
(closes python-pptx#716 at the API level, 17-comment thread). The
|
|
1688
|
+
data-label patch ops carry no border fields, so the values read back
|
|
1689
|
+
locally and each setter warns once that it was ignored (see "Chart Series
|
|
1690
|
+
Line + Fill Visibility; Ignored Chart Format Writes").
|
|
1457
1691
|
|
|
1458
1692
|
- **`Chart.set_hyperlink(url=..., target_slide_index=..., tooltip=...)`**
|
|
1459
1693
|
(closes python-pptx#962). Routes chart-level click-actions through
|
|
@@ -205,7 +205,8 @@ def validate_all_touched() -> dict[int, list[str]]:
|
|
|
205
205
|
``ReplaceText`` validate every slide.
|
|
206
206
|
* Validation uses the local proxies (no snapshot refresh) plus, when the
|
|
207
207
|
presentation has a studio client, at most 12 text-measurement
|
|
208
|
-
round-trips per slide to confirm suspected text overflow
|
|
208
|
+
round-trips per slide to confirm suspected text overflow and table
|
|
209
|
+
row growth.
|
|
209
210
|
* When several decks are open, indexes of the LAST deck whose slide
|
|
210
211
|
carried warnings win on collision; the executor runs one deck per
|
|
211
212
|
script in practice.
|
|
@@ -264,7 +265,7 @@ def validate_all_touched() -> dict[int, list[str]]:
|
|
|
264
265
|
return results
|
|
265
266
|
|
|
266
267
|
|
|
267
|
-
__version__ = "0.
|
|
268
|
+
__version__ = "0.14.0"
|
|
268
269
|
|
|
269
270
|
__all__ = [
|
|
270
271
|
# Main entry point
|