athena-python-pptx 0.10.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.
Files changed (91) hide show
  1. athena_python_pptx-0.10.0/CLAUDE.md → athena_python_pptx-0.14.0/AGENTS.md +5 -0
  2. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/CHANGELOG.md +377 -0
  3. athena_python_pptx-0.14.0/CLAUDE.md +5 -0
  4. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/PKG-INFO +2 -2
  5. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/docs/API_PARITY_EXCEPTIONS.md +471 -17
  6. athena_python_pptx-0.14.0/pptx/__init__.py +348 -0
  7. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/batching.py +336 -19
  8. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/data.py +15 -7
  9. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/client.py +174 -65
  10. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/commands.py +28 -3
  11. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/enum/shapes.py +4 -2
  12. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/errors.py +9 -0
  13. athena_python_pptx-0.14.0/pptx/kit.py +1329 -0
  14. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/presentation.py +77 -27
  15. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/__init__.py +1624 -306
  16. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/slides.py +1606 -184
  17. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/text/__init__.py +202 -23
  18. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/typing.py +11 -0
  19. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pyproject.toml +1 -1
  20. athena_python_pptx-0.10.0/pptx/__init__.py +0 -208
  21. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/.gitignore +0 -0
  22. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/API_PARITY_REPORT.md +0 -0
  23. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/DEV-GUIDE.md +0 -0
  24. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/PARITY_QUESTIONS.md +0 -0
  25. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/PUBLISHING.md +0 -0
  26. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/README.md +0 -0
  27. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/docs/athena-api.json +0 -0
  28. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/docs/athena-api.md +0 -0
  29. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/_athena_extension.py +0 -0
  30. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/_citations.py +0 -0
  31. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/_ptc.py +0 -0
  32. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/_references.py +0 -0
  33. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/action.py +0 -0
  34. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/__init__.py +0 -0
  35. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/axis.py +0 -0
  36. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/category.py +0 -0
  37. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/chart.py +0 -0
  38. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/datalabel.py +0 -0
  39. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/legend.py +0 -0
  40. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/marker.py +0 -0
  41. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/plot.py +0 -0
  42. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/point.py +0 -0
  43. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/series.py +0 -0
  44. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/chart/xlsx.py +0 -0
  45. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/decorators.py +0 -0
  46. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/dml/__init__.py +0 -0
  47. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/dml/chtfmt.py +0 -0
  48. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/dml/color.py +0 -0
  49. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/dml/effect.py +0 -0
  50. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/dml/fill.py +0 -0
  51. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/dml/line.py +0 -0
  52. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/docgen.py +0 -0
  53. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/enum/__init__.py +0 -0
  54. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/enum/action.py +0 -0
  55. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/enum/chart.py +0 -0
  56. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/enum/dml.py +0 -0
  57. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/enum/lang.py +0 -0
  58. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/enum/text.py +0 -0
  59. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/exc.py +0 -0
  60. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/media.py +0 -0
  61. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/oxml/__init__.py +0 -0
  62. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/oxml/ns.py +0 -0
  63. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/package.py +0 -0
  64. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/__init__.py +0 -0
  65. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/_base.py +0 -0
  66. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/chart.py +0 -0
  67. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/coreprops.py +0 -0
  68. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/embeddedpackage.py +0 -0
  69. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/image.py +0 -0
  70. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/media.py +0 -0
  71. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/presentation.py +0 -0
  72. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/parts/slide.py +0 -0
  73. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/autoshape.py +0 -0
  74. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/base.py +0 -0
  75. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/connector.py +0 -0
  76. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/freeform.py +0 -0
  77. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/graphfrm.py +0 -0
  78. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/group.py +0 -0
  79. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/picture.py +0 -0
  80. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/placeholder.py +0 -0
  81. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shapes/shapetree.py +0 -0
  82. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/shared.py +0 -0
  83. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/slide.py +0 -0
  84. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/spec.py +0 -0
  85. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/table.py +0 -0
  86. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/text/fonts.py +0 -0
  87. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/text/layout.py +0 -0
  88. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/text/text.py +0 -0
  89. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/types.py +0 -0
  90. {athena_python_pptx-0.10.0 → athena_python_pptx-0.14.0}/pptx/units.py +0 -0
  91. {athena_python_pptx-0.10.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,6 +2,383 @@
2
2
 
3
3
  All notable changes to `athena-python-pptx` are documented in this file.
4
4
 
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
181
+
182
+ - New Athena-only module `pptx.kit` (`from pptx.kit import Kit, Palette,
183
+ Fonts, Region`; also re-exported from `pptx`). `Kit(prs)` composes slides
184
+ from regions derived from the slide size: `header` (small-caps kicker,
185
+ action title auto-fit to a two-line band, footer rule + page number +
186
+ `kit.footer_text`), `footer`, `takeaway` (full-width bar one gap above the
187
+ footer band), `content_region`, `columns`, `grid`, `kpi_row` / `kpi_card`
188
+ (surface card, accent stripe, big auto-fit value, auto-fit label), `card`,
189
+ `text` and `bullets`.
190
+ - Text asked to fit is sized to its box by the studio's `/measure-text-fit`
191
+ layouter (same client pattern as `TextFitter.best_fit_font_size`, result
192
+ floored and cached per kit, one failed probe latches the fallback) or, when
193
+ the studio is unreachable, by a conservative greedy wrap on the
194
+ `_estimate_text_width_emu` glyph table at 1.2 line height. It never grows
195
+ past the requested size; when nothing fits even at the minimum size the kit
196
+ uses the minimum and emits a `RuntimeWarning` naming the text; `fit=False`
197
+ keeps the size as given.
198
+ - Regions are clamped to the slide and the footer band is reserved, so
199
+ nothing lands outside the slide or under the footer; a region with nothing
200
+ left raises instead of silently placing nothing. `content_region` keeps the
201
+ takeaway band free by default so a bar added after the body cannot hide it,
202
+ and the `y` / `h` overrides of `columns` / `grid` / `kpi_row` are clamped to
203
+ the content region. Per-slide state is keyed by the stable `slide.slide_id`.
204
+ - Pure python-pptx public API underneath (`add_shape` / `add_textbox` /
205
+ `fill` / `line` / `text_frame` / `font`), emitted through the
206
+ presentation's command buffer — build each slide inside `with prs.batch():`
207
+ for one round-trip. Reaches agents with the next `athena-python-pptx` bump.
208
+ Documented in `docs/API_PARITY_EXCEPTIONS.md`; demo in
209
+ `examples/kit_demo.py`.
210
+
211
+ ## 0.13.0 — validate() finds overflow and hidden text, drops containment noise
212
+
213
+ Reworks `Slide.validate()` around the defects that actually ship in agent
214
+ decks (verified 2026-09-14 against two 8-slide agent builds: 167 and 127
215
+ warnings, ~75% of them a background card behind its own label). Same
216
+ signature; an empty list is once again reachable by a designed slide.
217
+
218
+ - **Containment overlaps are no longer reported** unless both shapes carry
219
+ text (text on text is always reported). Partial overlaps are reported
220
+ only when at least one shape carries text; hairline overlaps thinner
221
+ than a text-frame inset (0.05in) and overlaps between two text-less
222
+ shapes are ignored. Tables count as text-bearing.
223
+ - **New: text overflow for non-table shapes** — per-paragraph font-metric
224
+ estimate (`_estimate_text_width_emu`, run size → paragraph default →
225
+ shape default → 18pt; word wrap on → `ceil(width / inner_width)` lines,
226
+ off → horizontal check; `lines × font × 1.2 × line_spacing` + spacing vs
227
+ box height minus margins). Frames set to auto-fit are skipped. When the
228
+ presentation has a studio client each hit whose paragraphs share one
229
+ font size and weight is confirmed with one `MeasureTextFit` round-trip
230
+ pinned at that size (`fits: true` drops it; capped at 12 per call; every
231
+ network error falls back to the heuristic verdict); mixed-font frames keep
232
+ the heuristic verdict since the server measures at a single font.
233
+ - **New: hidden text (occlusion)** — a text shape at least half covered by
234
+ a later (higher z-order) shape with a solid, non-transparent fill and no
235
+ text: `"text in 'X' is covered by 'Y'"`. Unknown fills (none, gradient,
236
+ pattern, pictures) count as not opaque; any transparency on the proxy or
237
+ a snapshot `fillTransparency` disables the rule for that shape.
238
+ - Warnings are deduplicated, ordered overflow → occlusion → text-on-text →
239
+ other (off-slide, one-text partial overlaps, empty placeholders), capped
240
+ at 15 per slide with a trailing `"… and N more (M containment overlaps
241
+ ignored)"` line. Rotated shapes near 90°/270° use their swapped visual
242
+ bbox in the pairwise rules.
243
+ - `bring_to_front()` / `send_to_back()` / `bring_forward()` /
244
+ `send_backward()` now mirror the move on the local `slide.shapes` order
245
+ (previously only the server saw it), so `validate()` reads the intended
246
+ stack without a refresh.
247
+ - New module-level `pptx.validate_all_touched() -> dict[int, list[str]]`
248
+ (Athena extension) for the executor postamble: `CommandBuffer` records
249
+ the slide indexes / slide ids / shape ids of every command the server
250
+ applied (`CommandBuffer.take_touched()` returns and clears them);
251
+ `validate_all_touched()` resolves them to slides on every live
252
+ `Presentation`, validates only those, and returns the slides with
253
+ warnings. Recorded indexes are shifted for slide insertions, deletions
254
+ and reorders applied later in the run; destination-bearing commands
255
+ (`CloneShape`, `CopySlideFromDeck`, `ReplaceText`) record their
256
+ destination; `SetPresentationSize` and a deck-wide `ReplaceText` validate
257
+ every slide. Commands that cannot change a layout (notes, names,
258
+ citations, deck metadata) never mark a slide; never raises for
259
+ validation reasons.
260
+
261
+ ## 0.12.0 — snapshot hydration, ReplaceChartData emission, level dirty-flag, discard_all
262
+
263
+ SDK halves of the issue #30178 Cluster D server work (#30204 / #30211 /
264
+ #30212 / #30213).
265
+
266
+ **⚠️ Server ordering:** `Chart.replace_data` now emits a single
267
+ `ReplaceChartData` patch, which servers without #30211 reject (400) — the
268
+ `presentation-exec` snapshot bump to 0.12.0 must wait for the pptx-studio
269
+ deploy that ships the op.
270
+
271
+ - `Shape.shadow` hydrates from the snapshot's `shadow` bag: a partial edit
272
+ like `shadow.blur_radius = Pt(6)` re-emits the ingested shadow's
273
+ `visible=True` / type / distance / direction / color / transparency
274
+ instead of the un-hydrated defaults (`visible=False` used to DELETE the
275
+ ingested shadow server-side). Hydrated shadows read back
276
+ `inherit == False`.
277
+ - `Shape.click_action` hydrates from the snapshot's `clickAction` bag —
278
+ same deletion-bug class (`click_action.tooltip = ...` used to emit
279
+ `action_type='none'` and delete the ingested link).
280
+ - `Presentation.core_properties` hydrates from the snapshot's
281
+ `coreProperties` (server #30204): setting one property no longer wipes
282
+ the deck's other metadata to `None`. `refresh()` re-hydrates.
283
+ - `Chart.replace_data` emits ONE `ReplaceChartData` patch carrying the
284
+ full categories + series matrix (python-pptx semantics: shrink drops
285
+ trailing points, grow adds them, extra series are removed) instead of
286
+ per-index `UpdateSeries*` / `UpdateCategoryLabel` patches that couldn't
287
+ change the data's shape. The local `chartSpec` mirror still updates,
288
+ preserving server series ids by index. A zero-series `chart_data`
289
+ raises `ValidationError` before touching the local mirror (the op
290
+ requires at least one series; silently returning would desync local
291
+ reads from the server).
292
+ - `run.hyperlink.target_slide = n` no longer rides with `address=''` (the
293
+ clear convention) — exactly one of address / targetSlideIndex is sent,
294
+ aligning with server #30212. The SetText re-emit carries
295
+ `hyperlinkTargetSlideIndex`, and snapshot rich content hydrates it back.
296
+ `SetRunHyperlinkTarget.validate()` rejects setting both.
297
+ - `paragraph.level` is sent only when the paragraph is indented or the
298
+ caller explicitly assigned it (explicit `level = 0` still reaches the
299
+ wire); an untouched level-0 paragraph omits the field so unrelated style
300
+ edits don't stomp the server-side outline level (belt-and-suspenders
301
+ with the server-side lvl=0 canonicalization in #30213).
302
+ - New module-level `pptx.discard_all()` (mirrors athena-python-docx):
303
+ drops all buffered, un-flushed commands across open decks without
304
+ posting and returns the dropped count. The agora executor postamble
305
+ calls it when user code raised, so failed scripts discard their
306
+ half-buffered tail instead of posting it.
307
+
308
+ ## 0.11.0 — silent no-op & data-loss fixes (deep-dive remediation)
309
+
310
+ Eliminates a cluster of silent no-ops, read-after-write desyncs, and
311
+ buffer-poisoning bugs found in the 2026-08 presentation-asset deep dive.
312
+ All fixes preserve python-pptx API parity (behavior, not surface, changes).
313
+
314
+ - `paragraph.level = 0` now reaches the wire (was dropped by the None-drop),
315
+ so resetting an indented paragraph works.
316
+ - `font.subscript = False` / `font.superscript = False` now send `baseline: 0`
317
+ (the server's clear signal) instead of omitting the field, so they actually
318
+ un-sub/superscript existing text.
319
+ - A bare `shape.fill.solid()` now emits, matching `gradient()`/`patterned()`.
320
+ - Chart `has_legend` / `has_title` / `chart_title` now update the local
321
+ snapshot on write, so reads after a write return the new value.
322
+ - `rows.insert()` / `columns.insert()` now shift the cached row-height /
323
+ column-width lists, fixing corrupted height/width reads after an insert.
324
+ - `GroupShape.shapes` now resolves child shapes via `childIds` (was hardcoded
325
+ to `[]`).
326
+ - `PicturePlaceholder.insert_picture` converts WebP/SVG/AVIF to PNG (was
327
+ mislabeling them `png`) and honors its `width`/`height` params.
328
+ - `batch()` discards the buffer when its body raises (was committing the
329
+ partial prefix).
330
+ - A server-rejected command in a batch is dropped (not re-queued), ending the
331
+ permanent buffer-poison loop; the rest of the rolled-back chunk is restored.
332
+ - Command chunks carry a stable `txn.id` across retries so an ambiguous-timeout
333
+ resend dedups server-side instead of double-applying.
334
+ - `flush_all(strict=True)` raises `FlushAllError` on a per-buffer flush failure
335
+ (the sandbox executor opts in) instead of silently swallowing it.
336
+ - `Presentation` no longer pins its buffer/client/session via the atexit hook
337
+ (weakref closure); `close()`/`__del__` release the HTTP session.
338
+ - Export 404s fail fast for a deck this client already read (deleted deck),
339
+ instead of burning the ~85s post-upload retry budget.
340
+ - `XyChartData` / `BubbleChartData` `add_series` raise on malformed inline
341
+ points instead of silently dropping them.
342
+ - `mso_shape_to_string` raises on an unmapped autoshape id instead of silently
343
+ authoring a rectangle.
344
+ - `AddMovie` emits `posterBase64` (the key the server reads) and always sends a
345
+ poster (a 1x1 placeholder when none is supplied), so `add_movie`/`add_video`
346
+ no longer fail the whole batch.
347
+
348
+ ## 0.10.1 — unnamed-shape parity, browser UA on image fetches, honest SVG errors
349
+
350
+ Fixes the three `execute_presentation_code` failures observed in the
351
+ 2026-08-12 production deck-build session (thread_d7eb9dd6).
352
+
353
+ - `Shape.name` now always returns a `str` (python-pptx parity —
354
+ `cNvPr@name` is a schema-required attribute upstream). Elements
355
+ persisted before names were stored (e.g. pictures added by pre-0.10
356
+ SDKs) read back as `""` instead of `None`, so idioms like
357
+ `'Picture' in shape.name` no longer raise `TypeError`.
358
+ - `add_picture(url)` fetches now send browser-equivalent
359
+ `User-Agent`/`Accept` headers. Image hosts and proxies behind bot
360
+ rules (e.g. `wsrv.nl`, Cloudflare-fronted CDNs) 403 the default
361
+ `python-requests/x.y` UA while serving the identical URL to a browser
362
+ UA. The Accept header advertises only formats the deck pipeline
363
+ handles (PNG/JPEG native, WebP via Pillow) so format-negotiating CDNs
364
+ don't switch the body to AVIF or SVG.
365
+ - AVIF is now detected explicitly (any `avif`/`avis` brand in the
366
+ ISO-BMFF `ftyp` box — major or compatible, so `mif1`-major HEIF
367
+ containers are covered) instead of falling through the sniffer as
368
+ `png` — it converts via Pillow where the build supports AVIF, and
369
+ otherwise raises the actionable convert-or-fail error rather than
370
+ shipping undecodable bytes that render as a silent broken-image
371
+ placeholder.
372
+ - `Shapes.by_name("")` / `get_by_name("")` no longer match unnamed
373
+ legacy shapes — `""` is the no-name sentinel, not an addressable
374
+ name.
375
+ - The SVG `add_picture` error no longer recommends `pip install svglib`
376
+ (rasterization also needs the `rlPyCairo` backend and the native cairo
377
+ library, so that advice cannot work in a stock sandbox). WebP and SVG
378
+ now get separate, accurate remediation hints. Pairs with the
379
+ presentation-exec v59 snapshot, which bundles `svglib` + `rlPyCairo`
380
+ so SVG→PNG conversion works in-sandbox.
381
+
5
382
  ## 0.9.0 — eager validation, table overflow diagnostics, and text caps
6
383
 
7
384
  Publishes the SDK changes already deployed with PPTX Studio after 0.8.1.
@@ -0,0 +1,5 @@
1
+ @AGENTS.md
2
+
3
+ <!-- AGENTS.md next to this file is the single source of truth for agent
4
+ instructions. Claude Code does not read AGENTS.md natively, so this file
5
+ imports it. Add Claude-Code-only rules below this line. -->
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: athena-python-pptx
3
- Version: 0.10.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