athena-python-pptx 0.1.80__tar.gz → 0.3.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.1.80 → athena_python_pptx-0.3.0}/API_PARITY_REPORT.md +28 -5
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/PKG-INFO +1 -1
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/docs/API_PARITY_EXCEPTIONS.md +514 -2
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/__init__.py +1 -1
- athena_python_pptx-0.3.0/pptx/_athena_extension.py +374 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/_ptc.py +6 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/_references.py +6 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/batching.py +7 -0
- athena_python_pptx-0.3.0/pptx/chart/category.py +200 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/client.py +43 -1
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/commands.py +1190 -40
- athena_python_pptx-0.3.0/pptx/decorators.py +136 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/docgen.py +6 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/enum/chart.py +71 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/enum/dml.py +45 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/errors.py +48 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/chart.py +18 -2
- athena_python_pptx-0.3.0/pptx/parts/image.py +186 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/presentation.py +294 -12
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/__init__.py +2639 -70
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/slides.py +549 -20
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/text/__init__.py +656 -33
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/typing.py +15 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pyproject.toml +1 -1
- athena_python_pptx-0.1.80/pptx/chart/category.py +0 -96
- athena_python_pptx-0.1.80/pptx/decorators.py +0 -94
- athena_python_pptx-0.1.80/pptx/parts/image.py +0 -85
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/.gitignore +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/CHANGELOG.md +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/CLAUDE.md +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/README.md +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/action.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/exc.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/media.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/package.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/shared.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/slide.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/spec.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/table.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/types.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/units.py +0 -0
- {athena_python_pptx-0.1.80 → athena_python_pptx-0.3.0}/pptx/util.py +0 -0
|
@@ -110,13 +110,36 @@ via Tranches R–W:
|
|
|
110
110
|
| `*.element`, `*.part`, `*.ln`, `*.get_or_add_ln` | XML element / OPC package / direct DML XML access — REST SDK has no local XML or packages |
|
|
111
111
|
| `FillFormat.from_fill_parent()` | Internal XML constructor |
|
|
112
112
|
| `ColorFormat.from_colorchoice_parent()` | Internal XML constructor |
|
|
113
|
-
| `Font.fill` | Font fill format (uncommon) |
|
|
114
|
-
| `SlideLayout.background`, `SlideMaster.background` | Not yet exposed at layout / master level |
|
|
115
|
-
| `SlideLayout.iter_cloneable_placeholders`, `SlideLayout.used_by_slides` | Placeholder cloning is server-side |
|
|
116
|
-
| `SlideShapes.add_movie`, `add_ole_object`, `build_freeform` | Not implemented (uncommon) |
|
|
117
|
-
| `Slide.follow_master_background` | XML-level inheritance flag — REST SDK abstracts this |
|
|
118
113
|
| `Picture.image.blob` | Image bytes live in the studio's asset store — not materialized into Python |
|
|
119
114
|
|
|
115
|
+
## Closed Parity Gaps (added 2026-05-18)
|
|
116
|
+
|
|
117
|
+
The following members are now implemented at the SDK surface — every method is callable and emits the appropriate command (server-side handlers for the new commands ship as a follow-up where noted):
|
|
118
|
+
|
|
119
|
+
| Member | Implementation |
|
|
120
|
+
|--------|----------------|
|
|
121
|
+
| `Font.fill` | Returns ``_FontFillFormat`` proxy; ``fore_color`` delegates to ``Font.color`` |
|
|
122
|
+
| `Image.dpi` | Derived from PIL when blob loaded, ``(72, 72)`` fallback |
|
|
123
|
+
| `Slide.follow_master_background` | Getter + setter; emits ``SetSlideBackground`` with ``follow_master`` |
|
|
124
|
+
| `SlideLayout.background`, `SlideMaster.background` | Returns ``_LayoutBackground`` proxy (read/write local; server-side patch op pending) |
|
|
125
|
+
| `SlideLayout.iter_cloneable_placeholders` | Derived from placeholders, skipping latent types |
|
|
126
|
+
| `SlideLayout.used_by_slides` | Derived from ``prs.slides`` |
|
|
127
|
+
| `SlideLayouts.index(layout)` | List operation |
|
|
128
|
+
| `SlideLayouts.remove(layout)` | Emits ``RemoveLayout`` *(server handler pending)* |
|
|
129
|
+
| `Shapes.clone_placeholder` | Emits ``ClonePlaceholder`` *(server handler pending)* |
|
|
130
|
+
| `Shapes.clone_layout_placeholders` | Loops ``iter_cloneable_placeholders`` + ``clone_placeholder`` |
|
|
131
|
+
| `Shapes.ph_basename` / ``NotesSlideShapes.ph_basename`` | Pure mapping, notes-slide override |
|
|
132
|
+
| `Shapes.turbo_add_enabled` | Local property (REST SDK has no XML batch path) |
|
|
133
|
+
| `NotesSlide.shapes` / ``.placeholders`` / ``.notes_placeholder`` / ``.name`` / ``.background`` / ``.clone_master_placeholders`` | Empty collections + helpers; notes inheritance is automatic in REST |
|
|
134
|
+
| `GraphicFrame.chart_part` | Returns a real ``ChartPart`` with ``.chart`` |
|
|
135
|
+
| `GraphicFrame.ole_format` | Returns ``_OleFormat`` exposing ``prog_id`` / ``show_as_icon`` |
|
|
136
|
+
| `Table.notify_height_changed/width_changed` | Recomputes total + emits ``SetTransform`` |
|
|
137
|
+
| `FillFormat.gradient()` | Emits ``SetGradientFill`` immediately (previously only setters emitted) |
|
|
138
|
+
| `FillFormat.patterned()` + ``pattern`` setter | Emits ``SetPatternFill`` *(server handler pending)* |
|
|
139
|
+
| `FreeformBuilder.convert_to_shape()` | Emits ``AddFreeformShape`` *(server handler pending)* |
|
|
140
|
+
|
|
141
|
+
The 4 new client commands (``RemoveLayout``, ``ClonePlaceholder``, ``SetPatternFill``, ``AddFreeformShape``) ship in the same release; their pptx-studio handlers are tracked as a server-side follow-up.
|
|
142
|
+
|
|
120
143
|
## SDK Additions (Documented Deviations)
|
|
121
144
|
|
|
122
145
|
See `docs/API_PARITY_EXCEPTIONS.md` for the full list. Highlights:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -17,7 +17,6 @@ These standard python-pptx members are omitted because a REST SDK has no access
|
|
|
17
17
|
| `FillFormat.from_fill_parent()` | Internal constructor |
|
|
18
18
|
| `ColorFormat.from_colorchoice_parent()` | Internal constructor |
|
|
19
19
|
| `Font.fill` | Font fill format (uncommon) |
|
|
20
|
-
| `Font.language_id` | Implemented in v0.1.68 (returns `MSO_LANGUAGE_ID` enum, accepts enum or int LCID) |
|
|
21
20
|
|
|
22
21
|
---
|
|
23
22
|
|
|
@@ -216,6 +215,35 @@ python-pptx's `_Row.height` and `_Column.width` are read/write EMU properties. T
|
|
|
216
215
|
|
|
217
216
|
In python-pptx, `Table.rows` returns a `_RowCollection` of `_Row` objects (each with `.height`). This SDK matches that. In a prior version, `Table.rows` returned a flat collection of cell lists.
|
|
218
217
|
|
|
218
|
+
### `TextFrame.clear()` — also resets bullet on paragraph 0
|
|
219
|
+
|
|
220
|
+
In upstream python-pptx, `text_frame.clear()` removes every paragraph except the first, and on that first paragraph it removes every child element **except** `<a:pPr>`. Any bullet definition the author wrote into `<a:pPr>` survives the clear, and an inherited bullet from the master / layout's `<a:lstStyle>` is left to be re-resolved by PowerPoint at render time.
|
|
221
|
+
|
|
222
|
+
This SDK additionally emits a `SetParagraphStyle(paragraph_index=0, bullet='none')` after the empty `SetText`, explicitly overriding the bullet on the cleared paragraph.
|
|
223
|
+
|
|
224
|
+
The deviation exists because pptx-studio **pre-bakes** master/layout list-style inheritance into `richContent.paragraphs[i].bullet` at ingest time (and at slide materialization), then preserves that baked field through every subsequent `applySetText` via `preserveParagraphProps`. Without the explicit override, the python-pptx idiom
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
tf.clear()
|
|
228
|
+
p = tf.paragraphs[0]
|
|
229
|
+
run = p.add_run()
|
|
230
|
+
run.text = "Title"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
renders the title with a stray "•" whenever the placeholder lives on a layout whose master defines a level-1 `<a:buChar/>` (the common case for `<p:bodyStyle>` and any content placeholder). The explicit `bullet='none'` clears the baked inheritance so the literal "no pPr was authored" intent reaches the renderer.
|
|
234
|
+
|
|
235
|
+
To re-enable a bullet after `clear()`, set it back explicitly:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
tf.clear()
|
|
239
|
+
p = tf.paragraphs[0]
|
|
240
|
+
p.bullet = True # restores 'disc' default
|
|
241
|
+
run = p.add_run()
|
|
242
|
+
run.text = "Bullet item"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Only `bullet` is overridden — `alignment`, `level`, `line_spacing`, `space_before`, `space_after`, `margin_left`, `indent`, and `bullet_color` are left alone (they continue to follow upstream `<a:pPr>`-preservation semantics, since baked-inheritance surprises are far less common on those fields).
|
|
246
|
+
|
|
219
247
|
---
|
|
220
248
|
|
|
221
249
|
## Non-Standard Convenience Methods (candidates for future cleanup)
|
|
@@ -271,6 +299,35 @@ if warnings:
|
|
|
271
299
|
print(w)
|
|
272
300
|
```
|
|
273
301
|
|
|
302
|
+
### `add_picture(image_file=...)` accepts `http(s)://` URLs
|
|
303
|
+
|
|
304
|
+
python-pptx upstream treats a `str` `image_file` strictly as a local
|
|
305
|
+
filesystem path. athena-python-pptx **additionally** treats strings
|
|
306
|
+
starting with `http://` or `https://` as URLs and fetches their bytes
|
|
307
|
+
with `requests` before falling through to the standard AddPicture
|
|
308
|
+
pipeline (base64-encode → server-side SHA256 dedup → S3 upload).
|
|
309
|
+
|
|
310
|
+
This is the canonical handoff for "spreadsheet range → image asset →
|
|
311
|
+
slide": athena-openpyxl's `Worksheet.export_range_as_image_asset` returns
|
|
312
|
+
an `ImageAssetRef` whose `.url` is exactly the Olympus-proxied
|
|
313
|
+
`/asset/asset_<uuid>.png` form this branch accepts.
|
|
314
|
+
|
|
315
|
+
Athena asset URLs (any hostname under `athenaintel.com` /
|
|
316
|
+
`athenaintelligence.ai`) are fetched with the sandbox's
|
|
317
|
+
`ATHENA_PPTX_API_KEY` injected as a Bearer token so Olympus's catch-all
|
|
318
|
+
asset proxy can run the workspace ABAC permission check against the
|
|
319
|
+
calling user. Non-Athena URLs are fetched without auth — they're assumed
|
|
320
|
+
to be world-readable, like any plain image link.
|
|
321
|
+
|
|
322
|
+
**Portable code that needs to run against stock python-pptx should
|
|
323
|
+
download the URL externally and pass `bytes` or a file-like.**
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
# Inside a Daytona pptx sandbox:
|
|
327
|
+
img = ws.export_range_as_image_asset("A1:F20", name="Q4 Revenue")
|
|
328
|
+
slide.shapes.add_picture(img.url, Inches(1), Inches(1), width=Inches(6))
|
|
329
|
+
```
|
|
330
|
+
|
|
274
331
|
### `name=` kwarg on `add_textbox()` / `add_shape()` / `add_picture()`
|
|
275
332
|
|
|
276
333
|
Optional keyword for setting a stable shape name at creation. python-pptx
|
|
@@ -324,6 +381,28 @@ prs.batch():` that fills it (see the skill prompt). When `add_slide()`
|
|
|
324
381
|
runs outside the batch it flushes its own command, and a subsequent
|
|
325
382
|
failed batch leaves the empty slide on the deck.
|
|
326
383
|
|
|
384
|
+
### `<p:style><a:fontRef>` scheme is luminance-derived, not always `lt1`
|
|
385
|
+
|
|
386
|
+
Upstream python-pptx hardcodes `<a:fontRef idx="minor"><a:schemeClr val="lt1"/></a:fontRef>`
|
|
387
|
+
in the `<p:style>` block of every shape it creates. That fontRef is the
|
|
388
|
+
default text color resolved by PowerPoint when a run carries no explicit
|
|
389
|
+
`<a:rPr><a:solidFill>`, and `lt1` is white — so any shape that was filled
|
|
390
|
+
with a light color (white, pale grey, pale red) rendered invisible text
|
|
391
|
+
in PowerPoint. The bug surfaces on every cover-page logo box, agenda
|
|
392
|
+
description, and disclaimer banner that LLM-authored code produces.
|
|
393
|
+
|
|
394
|
+
Starting with the matching export-worker version, the SDK picks the
|
|
395
|
+
fontRef scheme based on the shape's fill luminance: `dk1` (black) when
|
|
396
|
+
the fill is light (sRGB luminance ≥ 0.55), `lt1` (white) otherwise. When
|
|
397
|
+
no `fill_color_hex` is supplied the emitter still falls back to `lt1`
|
|
398
|
+
for byte-stable parity with prior round-trips.
|
|
399
|
+
|
|
400
|
+
This is an export-side behavior change, not an API change — callers who
|
|
401
|
+
were already setting explicit run colors via `run.font.color.rgb` keep
|
|
402
|
+
binary-identical output. The luminance-derived `<p:style>` only matters
|
|
403
|
+
for shapes that rely on the shape preset style for their default text
|
|
404
|
+
color, which is exactly the failure mode the change is intended to fix.
|
|
405
|
+
|
|
327
406
|
### Background `CommandBuffer._deferred_error` propagation
|
|
328
407
|
|
|
329
408
|
Not a user-facing API; documented here because it changes the visible
|
|
@@ -378,7 +457,11 @@ that closed six more setters end-to-end (see the new table below):
|
|
|
378
457
|
setters.
|
|
379
458
|
- 3-D chart authoring (`XL_CHART_TYPE.THREE_D_*` raises
|
|
380
459
|
`UnsupportedFeatureError`).
|
|
381
|
-
- Combo / Stock
|
|
460
|
+
- Combo / Stock variants for `add_chart()` (also raise). Radar
|
|
461
|
+
(`XL_CHART_TYPE.RADAR` / `RADAR_FILLED` / `RADAR_MARKERS`) now authors
|
|
462
|
+
end-to-end — all three variants map to OOXML `<c:radarChart>` with
|
|
463
|
+
`<c:radarStyle val="standard"/>`; the marker / filled distinction
|
|
464
|
+
is collapsed to "standard" today (follow-up work to extend ChartSpec).
|
|
382
465
|
- Trendlines and error bars on series.
|
|
383
466
|
|
|
384
467
|
### Chart Patches Added in the Styling Residuals PR (6 new operations)
|
|
@@ -746,3 +829,432 @@ slide.shapes.add_picture(
|
|
|
746
829
|
The default `fit=None` preserves stock python-pptx behaviour. The only
|
|
747
830
|
other accepted value is `"contain"`; passing anything else raises
|
|
748
831
|
`ValueError` to fail loudly rather than silently no-op.
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## `Shapes.add_linked_table()` — Athena studio-linking extension (added in Phase 3)
|
|
836
|
+
|
|
837
|
+
Authors a **native PPT table** (`<a:tbl>`) whose cells are materialized
|
|
838
|
+
from an Athena source asset's anchor range at insert time. Unlike
|
|
839
|
+
``add_linked_ole_object``, which produces a frozen Excel preview /
|
|
840
|
+
embedding pair, this produces a real PowerPoint table that's
|
|
841
|
+
selectable, editable, and natively styled — but still carries a
|
|
842
|
+
``SourceBinding`` so refresh can re-pull current values from the
|
|
843
|
+
source spreadsheet.
|
|
844
|
+
|
|
845
|
+
This method has no python-pptx counterpart — python-pptx has no
|
|
846
|
+
concept of Athena asset references. Use ``add_table()`` (which IS in
|
|
847
|
+
python-pptx) for unlinked tables.
|
|
848
|
+
|
|
849
|
+
```python
|
|
850
|
+
from pptx._references import (
|
|
851
|
+
AssetReference,
|
|
852
|
+
SheetTableAnchor,
|
|
853
|
+
VersionPolicyLatest,
|
|
854
|
+
)
|
|
855
|
+
from pptx.util import Inches
|
|
856
|
+
|
|
857
|
+
slide.shapes.add_linked_table(
|
|
858
|
+
source=AssetReference(id='asset_xlsx_abc123'),
|
|
859
|
+
anchor=SheetTableAnchor(
|
|
860
|
+
sheet_id=0,
|
|
861
|
+
table_name='SalesQ4',
|
|
862
|
+
fallback_range='A1:F50',
|
|
863
|
+
),
|
|
864
|
+
left=Inches(1),
|
|
865
|
+
top=Inches(1),
|
|
866
|
+
width=Inches(8),
|
|
867
|
+
height=Inches(4),
|
|
868
|
+
freshness_mode='athena_refreshable', # default
|
|
869
|
+
version_policy=VersionPolicyLatest(), # default
|
|
870
|
+
)
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Prefer ``SheetTableAnchor`` over ``SheetRangeAnchor`` whenever the
|
|
874
|
+
source is an Excel named table — the table's ``displayName`` survives
|
|
875
|
+
sorts, inserts, and row additions that would shift an A1 range.
|
|
876
|
+
|
|
877
|
+
**Returns:** a ``GraphicFrame`` wrapping a ``Table`` proxy whose
|
|
878
|
+
``rows`` / ``cols`` start at ``0`` on the client side. The materialized
|
|
879
|
+
dimensions live on the server; fetch the deck snapshot if you need to
|
|
880
|
+
iterate cells programmatically.
|
|
881
|
+
|
|
882
|
+
**Refresh flow:** call the GraphQL ``refreshSourceBinding`` mutation
|
|
883
|
+
(or the Olympus "Refresh from source" UI action). The server
|
|
884
|
+
re-materializes the source range and writes the new cell values into
|
|
885
|
+
the same table element on the Y.Doc.
|
|
886
|
+
|
|
887
|
+
**`freshness_mode='office_live_link'` is reserved for Phase 5** and
|
|
888
|
+
currently raises ``ValueError`` locally.
|
|
889
|
+
|
|
890
|
+
---
|
|
891
|
+
|
|
892
|
+
## Athena extensions for most-requested upstream features (v0.1.81)
|
|
893
|
+
|
|
894
|
+
These surfaces are **additions** to python-pptx, not deviations — every
|
|
895
|
+
upstream class, method, property, and parameter remains 1:1 with
|
|
896
|
+
python-pptx 1.0.2. Each item below closes (or fundamentally improves)
|
|
897
|
+
a long-standing upstream feature gap. Issue numbers reference
|
|
898
|
+
`scanny/python-pptx`. Every entry is marked at runtime with
|
|
899
|
+
`@athena_extension(issue=…, since="0.1.81")` so the registry walker
|
|
900
|
+
picks them up; the legacy `@athena_only` markers continue to work for
|
|
901
|
+
back-compat.
|
|
902
|
+
|
|
903
|
+
This wave intentionally **excludes animations / Morph transitions**
|
|
904
|
+
(python-pptx#1106 / #942) — they need export-worker and Y.Doc-schema
|
|
905
|
+
work in addition to SDK surface and are tracked separately.
|
|
906
|
+
|
|
907
|
+
### Shape-level additions
|
|
908
|
+
|
|
909
|
+
- **`Shape.duplicate(target_slide=None, offset_x=None, offset_y=None)`**
|
|
910
|
+
(closes python-pptx#533, #620, 10-comment thread requesting a public
|
|
911
|
+
duplicate API). Alias for the existing `Shape.clone()` — both verbs
|
|
912
|
+
resolve identically. The clone preserves every server-resolved
|
|
913
|
+
property (transform, color, text, type) and accepts the same
|
|
914
|
+
`target_slide` / offset args as `clone()`.
|
|
915
|
+
|
|
916
|
+
- **`LineFormat.head_end_type` / `tail_end_type` / `head_end_length` /
|
|
917
|
+
`tail_end_length` / `head_end_width` / `tail_end_width`** (closes
|
|
918
|
+
python-pptx#1033). Connector / line arrow heads. Accept either
|
|
919
|
+
the upstream `MSO_ARROWHEAD` / `MSO_ARROWHEAD_LENGTH` /
|
|
920
|
+
`MSO_ARROWHEAD_WIDTH` enums or the OOXML short strings
|
|
921
|
+
(`"triangle"` / `"stealth"` / `"diamond"` / `"oval"` / `"arrow"` /
|
|
922
|
+
`"none"`, `"sm"` / `"med"` / `"lg"`). Each setter emits a
|
|
923
|
+
`SetConnectorArrows` wire op carrying all six attrs at once.
|
|
924
|
+
|
|
925
|
+
```python
|
|
926
|
+
from pptx.enum.dml import MSO_ARROWHEAD
|
|
927
|
+
conn = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT,
|
|
928
|
+
Inches(1), Inches(1),
|
|
929
|
+
Inches(5), Inches(3))
|
|
930
|
+
conn.line.tail_end_type = MSO_ARROWHEAD.TRIANGLE
|
|
931
|
+
conn.line.tail_end_length = "med"
|
|
932
|
+
conn.line.tail_end_width = "lg"
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
- **`Shapes.add_picture(svg_bytes_or_path, …)`** now natively
|
|
936
|
+
supports SVG (closes python-pptx#1112). The picture's content-type
|
|
937
|
+
is detected from the magic bytes (`<?xml … <svg`), the native
|
|
938
|
+
size is parsed from the `<svg width=/height=>` attributes (or the
|
|
939
|
+
`viewBox`), and the wire payload carries `image_format="svg"` so
|
|
940
|
+
the server stores the SVG as a first-class picture asset (PowerPoint
|
|
941
|
+
2016+ supports SVG natively).
|
|
942
|
+
|
|
943
|
+
- **`Shapes.add_connector(...)` int-coerces float endpoints**
|
|
944
|
+
(closes python-pptx#1058 — "Generating corrupted PPT when using
|
|
945
|
+
connectors"). Calls like `connector(top=shape.top + shape.height / 2)`
|
|
946
|
+
produce floats that legacy code wrote into `<a:off x="...">`
|
|
947
|
+
attributes that Google Slides and modern PowerPoint refuse to open.
|
|
948
|
+
We now route every endpoint through `int(round(float(...)))` so the
|
|
949
|
+
wire payload is integer EMU regardless of input type
|
|
950
|
+
(`float` / `numpy.float64` / `Decimal`).
|
|
951
|
+
|
|
952
|
+
### Text-frame and run additions
|
|
953
|
+
|
|
954
|
+
- **`Hyperlink.target_slide`** (closes python-pptx#1077). Run-level
|
|
955
|
+
internal slide jumps. Assign a `Slide` proxy or a zero-based int;
|
|
956
|
+
the SDK emits a `SetRunHyperlinkTarget` command with the
|
|
957
|
+
`targetSlideIndex` payload, and the server writes
|
|
958
|
+
`<a:hlinkClick action="ppaction://hlinksldjump">` with the matching
|
|
959
|
+
slide-jump relationship. Mutually exclusive with `address` (an
|
|
960
|
+
external URL); setting one nulls the other.
|
|
961
|
+
|
|
962
|
+
```python
|
|
963
|
+
run.hyperlink.target_slide = prs.slides[3]
|
|
964
|
+
# or
|
|
965
|
+
run.hyperlink.target_slide = 3
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
- **`Run.font.color.rgb` overrides the theme hyperlink color**
|
|
969
|
+
(closes python-pptx#940 — "Set/change font color when working with
|
|
970
|
+
Hyperlinks is impossible", 11-comment thread). The Hyperlink
|
|
971
|
+
emitter now reads the run's explicit color and ships an
|
|
972
|
+
`overrideColorHex` field on `SetRunHyperlinkTarget` so the server
|
|
973
|
+
writes the matching `<a:rPr><a:solidFill>` over the run instead of
|
|
974
|
+
letting the theme `hlink` color win.
|
|
975
|
+
|
|
976
|
+
- **`TextFrame.replace_text(pattern, replacement, *, regex=False,
|
|
977
|
+
match_case=False, whole_word=False, max_replacements=None)`**
|
|
978
|
+
(closes python-pptx#684, #1037, #884). Find-and-replace that
|
|
979
|
+
preserves run-level formatting (fonts / colors / sizes / bold /
|
|
980
|
+
italic) by splitting runs across the match boundary on the server.
|
|
981
|
+
Returns the count of substitutions performed.
|
|
982
|
+
|
|
983
|
+
- **`TextFitter.best_fit_font_size(text, extents, max_size, font_file)`**
|
|
984
|
+
no longer returns `max_size` unconditionally (closes
|
|
985
|
+
python-pptx#715, #936, #970, #1026). When a base URL is configured
|
|
986
|
+
(`ATHENA_PPTX_BASE_URL`), the classmethod issues a `MeasureTextFit`
|
|
987
|
+
query to the studio's text-measurement endpoint and returns the
|
|
988
|
+
largest size that fits. The upstream signature is preserved exactly
|
|
989
|
+
(additional knobs read from `ATHENA_PPTX_TEXTFITTER_*` env vars).
|
|
990
|
+
Falls back to `max_size` with a `RuntimeWarning` if no client is
|
|
991
|
+
reachable so offline tests keep working.
|
|
992
|
+
|
|
993
|
+
### Table additions
|
|
994
|
+
|
|
995
|
+
- **`_RowCollection.add(*, height=None, copy_format_from=None)`** and
|
|
996
|
+
**`_RowCollection.insert(index, *, height=None, copy_format_from=None)`**
|
|
997
|
+
(closes python-pptx#895, #1016, 17-comment thread). Restores the
|
|
998
|
+
`rows.add()` method removed in upstream 1.0.0 and adds an indexed
|
|
999
|
+
`insert()` companion. `copy_format_from=<row_index>` inherits the
|
|
1000
|
+
source row's height + every cell's formatting.
|
|
1001
|
+
|
|
1002
|
+
- **`_ColumnCollection.add(*, width=None, copy_format_from=None)`** and
|
|
1003
|
+
**`_ColumnCollection.insert(index, ...)`** — column-axis mirror.
|
|
1004
|
+
`Table.add_column()` / `Table.remove_column()` / `Table.delete_column()`
|
|
1005
|
+
/ `Table.delete_row()` are convenience wrappers on top.
|
|
1006
|
+
|
|
1007
|
+
```python
|
|
1008
|
+
table.rows.add(copy_format_from=0)
|
|
1009
|
+
table.columns.insert(2, width=Inches(1.5))
|
|
1010
|
+
table.delete_row(-1)
|
|
1011
|
+
table.delete_column(0)
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
### Chart additions
|
|
1015
|
+
|
|
1016
|
+
- **`DataLabel.show_value` / `show_series_name` / `show_category_name` /
|
|
1017
|
+
`show_percentage` / `number_format` / `text` / `font` / `position`**
|
|
1018
|
+
(closes python-pptx#1068, #1024, #1025). Per-point data-label
|
|
1019
|
+
overrides. Reach via `series.points[i].data_label.show_value = True`;
|
|
1020
|
+
each setter emits a `SetPointDataLabelStyle` wire op so the
|
|
1021
|
+
server can apply the override on top of the series default.
|
|
1022
|
+
|
|
1023
|
+
- **`_ChartTitle.left` / `top` / `width` / `height`** (closes
|
|
1024
|
+
python-pptx#1030 — "How to set manually chart_title position?").
|
|
1025
|
+
Fractional layout (0.0 – 1.0 of the plot area) for the manual
|
|
1026
|
+
title placement. Each setter emits `SetChartTitlePosition` which
|
|
1027
|
+
the server materialises as `<c:layout><c:manualLayout xMode="edge"
|
|
1028
|
+
yMode="edge" …>`. Setting any axis to `None` clears the override and
|
|
1029
|
+
falls back to PowerPoint's auto-placement.
|
|
1030
|
+
|
|
1031
|
+
- **Office 2016+ chart types**: `XL_CHART_TYPE.TREEMAP` (117),
|
|
1032
|
+
`SUNBURST` (118), `HISTOGRAM` (119), `PARETO` (120),
|
|
1033
|
+
`BOX_WHISKER` (121), `WATERFALL` (122), `FUNNEL` (123). Closes
|
|
1034
|
+
python-pptx#583 ("New Chart Types in Office 2016"), #944
|
|
1035
|
+
(Treemap / Scatter), #651 (Waterfall), #1047 (Box plot). These
|
|
1036
|
+
use the cx-namespace OOXML schema instead of the legacy c-schema;
|
|
1037
|
+
`slide.shapes.add_chart(...)` dispatches through a separate
|
|
1038
|
+
`AddChart2016` wire op so the server can author the cx-flavoured
|
|
1039
|
+
chart-part XML.
|
|
1040
|
+
|
|
1041
|
+
### Slide- and presentation-level additions
|
|
1042
|
+
|
|
1043
|
+
- **`Presentation.create(widescreen=True)` is now the default**
|
|
1044
|
+
(closes python-pptx#1066, 9-comment thread). The blank-template
|
|
1045
|
+
factory creates 16:9 widescreen decks at 14630400 × 8229600 EMU
|
|
1046
|
+
(PowerPoint's "Widescreen (16:9)" preset). Pass `widescreen=False`
|
|
1047
|
+
to fall back to the legacy 4:3 default.
|
|
1048
|
+
|
|
1049
|
+
- **`Presentation.open(deck_id, base_url=None, api_key=None)`**
|
|
1050
|
+
(closes python-pptx#1018 — "open a PPT in append mode"). Named
|
|
1051
|
+
factory that mirrors the iterate-and-edit verb the upstream thread
|
|
1052
|
+
filed under. Internally identical to `Presentation(deck_id=…)`
|
|
1053
|
+
but reads naturally next to `Presentation.create` / `.upload`.
|
|
1054
|
+
|
|
1055
|
+
- **`Presentation.copy_slide_from(source, source_slide_index, *,
|
|
1056
|
+
destination_index=-1)`** (closes python-pptx#1036, #696, #934,
|
|
1057
|
+
three threads totalling 20+ comments asking for cross-deck slide
|
|
1058
|
+
copy). Emits `CopySlideFromDeck`; the server runs the deep copy
|
|
1059
|
+
with full relationship + media-de-duplication handling. Accepts
|
|
1060
|
+
either a live `Presentation` instance (its pending edits are
|
|
1061
|
+
flushed first) or a raw deck id string.
|
|
1062
|
+
|
|
1063
|
+
- **`Slides.remove_slide(slide_or_index)`** (closes python-pptx#956).
|
|
1064
|
+
Alias for the existing `Slides.delete()`. Both verbs accept a
|
|
1065
|
+
`Slide` proxy or an integer index.
|
|
1066
|
+
|
|
1067
|
+
- **`SlideMaster.shapes.add_shape / add_picture / add_textbox /
|
|
1068
|
+
add_table`** and **`SlideLayout.shapes.add_*`** (closes
|
|
1069
|
+
python-pptx#575 — "Add shapes to Slide Master" — and #1044 —
|
|
1070
|
+
"Add textbox to layout"). Mutation surface on master / layout
|
|
1071
|
+
shape collections. Reads remain empty (REST snapshot doesn't
|
|
1072
|
+
materialise master / layout shapes as first-class read targets),
|
|
1073
|
+
but writes round-trip through `AddShapeOnMaster` so the addition
|
|
1074
|
+
is inherited by every slide using the master / layout.
|
|
1075
|
+
|
|
1076
|
+
```python
|
|
1077
|
+
master = prs.slide_masters[0]
|
|
1078
|
+
master.shapes.add_picture("logo.png",
|
|
1079
|
+
Inches(0.25), Inches(0.25),
|
|
1080
|
+
width=Inches(1))
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
### Audit notes
|
|
1084
|
+
|
|
1085
|
+
- **python-pptx#1085 / #925 / #899 (group-shape coords)** — verified
|
|
1086
|
+
not applicable to this SDK. The REST snapshot carries
|
|
1087
|
+
server-resolved absolute coordinates for every shape, including
|
|
1088
|
+
group children that surface as flat slide-level siblings, so the
|
|
1089
|
+
group-relative coord bug the upstream issues describe can't occur
|
|
1090
|
+
here.
|
|
1091
|
+
|
|
1092
|
+
When upstream python-pptx eventually lands native versions of any of
|
|
1093
|
+
these surfaces, the SDK should swap to the upstream signature in
|
|
1094
|
+
place — this section is the canonical reference for what each Athena
|
|
1095
|
+
extension wires up.
|
|
1096
|
+
|
|
1097
|
+
---
|
|
1098
|
+
|
|
1099
|
+
## Tier B — next-wave upstream features (v0.1.82)
|
|
1100
|
+
|
|
1101
|
+
Second wave from the open-issue spectrum. Same contract as v0.1.81:
|
|
1102
|
+
upstream surface preserved, additions tagged with
|
|
1103
|
+
`@athena_extension(issue=…, since="0.1.82")`. New wire commands in
|
|
1104
|
+
`pptx.commands`: `SetShapeHidden`, `SetPointInvertIfNegative`,
|
|
1105
|
+
`SetSeriesErrorBars`, `SetConnectorEndpoint`, `AddMovie`,
|
|
1106
|
+
`SetChartHyperlink`, `ApplyForeignLayout`, `HideChartSeries`.
|
|
1107
|
+
|
|
1108
|
+
### Shape and connector additions
|
|
1109
|
+
|
|
1110
|
+
- **`Shape.hidden`** (closes python-pptx#971). Boolean read/write
|
|
1111
|
+
toggle that emits `SetShapeHidden` so the `<p:cNvPr hidden="1">`
|
|
1112
|
+
flag round-trips. Hidden shapes are excluded from render + Selection
|
|
1113
|
+
Pane but stay in the OOXML tree.
|
|
1114
|
+
|
|
1115
|
+
- **`Connector.begin_connect` / `end_connect`** now emit
|
|
1116
|
+
`SetConnectorEndpoint` (closes python-pptx#946, #1017). The
|
|
1117
|
+
pre-0.1.82 local-state-only stubs were exposed only so `isinstance`-
|
|
1118
|
+
style code didn't `AttributeError`; the new wire op makes the
|
|
1119
|
+
binding survive the round-trip and tracks the bound shape if it
|
|
1120
|
+
moves.
|
|
1121
|
+
|
|
1122
|
+
- **`Connector.set_elbow_adjustment(fraction, endpoint="begin")`**
|
|
1123
|
+
(closes python-pptx#1017 specifically). Adjusts the midpoint of an
|
|
1124
|
+
elbow connector; `fraction` is the position along the major axis
|
|
1125
|
+
(`0.0` first endpoint, `1.0` second). No-op on STRAIGHT / CURVE
|
|
1126
|
+
connectors.
|
|
1127
|
+
|
|
1128
|
+
### Chart additions
|
|
1129
|
+
|
|
1130
|
+
- **`Point.invert_if_negative`** (closes python-pptx#776). Per-data-
|
|
1131
|
+
point override that emits `SetPointInvertIfNegative`.
|
|
1132
|
+
Series-level `invert_if_negative` is overridden the moment any
|
|
1133
|
+
point in the series has its own `<c:spPr>`, so per-point control
|
|
1134
|
+
is the only reliable color-inversion path once explicit colors
|
|
1135
|
+
are set.
|
|
1136
|
+
|
|
1137
|
+
- **`DataLabels.border_color_hex / border_width / border_dash_style`**
|
|
1138
|
+
(closes python-pptx#716, 17-comment thread). Adds outline styling
|
|
1139
|
+
to plot- and series-level data labels via the existing
|
|
1140
|
+
`SetDataLabelStyle` / `SetSeriesDataLabelStyle` patch envelopes.
|
|
1141
|
+
Both writes and reads work consistently with the rest of the
|
|
1142
|
+
`DataLabels` surface.
|
|
1143
|
+
|
|
1144
|
+
- **`Chart.set_hyperlink(url=..., target_slide_index=..., tooltip=...)`**
|
|
1145
|
+
(closes python-pptx#962). Routes chart-level click-actions through
|
|
1146
|
+
`SetChartHyperlink` since the canonical `SetClickAction`
|
|
1147
|
+
path doesn't carry chart-shape relationships.
|
|
1148
|
+
|
|
1149
|
+
- **`Chart.hide_series(*series_ids)`** (closes python-pptx#1043).
|
|
1150
|
+
Hides series from the rendered plot without dropping them from the
|
|
1151
|
+
underlying chart-data workbook — callers can run the same data
|
|
1152
|
+
through multiple chart templates that surface different subsets.
|
|
1153
|
+
|
|
1154
|
+
- **Office 2016+ `pareto` chart type round-trips** —
|
|
1155
|
+
`AddChart2016.validate()` now includes `"pareto"` in the valid
|
|
1156
|
+
set, fixing a pre-merge ratchet that rejected the documented
|
|
1157
|
+
enum value. (Already in Tier A spec, closed in PR #20711 follow-up.)
|
|
1158
|
+
|
|
1159
|
+
### Slide and presentation additions
|
|
1160
|
+
|
|
1161
|
+
- **`Slide.apply_foreign_layout(master_index, layout_index)`**
|
|
1162
|
+
(closes python-pptx#1109, #1028). Apply a layout from a different
|
|
1163
|
+
master to an existing slide via `ApplyForeignLayout`. The
|
|
1164
|
+
server runs the relationship + theme inheritance fix-up so the
|
|
1165
|
+
slide renders against any master's layout without a deep-copy
|
|
1166
|
+
workaround.
|
|
1167
|
+
|
|
1168
|
+
- **`Slides.remove_slide()` alias for `delete()`** (already in Tier
|
|
1169
|
+
A, restated here because the upstream issue #956 spelled it both
|
|
1170
|
+
ways — both verbs accept a `Slide` or an integer index).
|
|
1171
|
+
|
|
1172
|
+
### Text additions
|
|
1173
|
+
|
|
1174
|
+
- **`Font.resolve() -> dict`** (closes python-pptx#1063, #765, #883).
|
|
1175
|
+
Returns the *rendered* font properties (name / size_pt / bold /
|
|
1176
|
+
italic / underline / strike / color_hex / language_id) walking
|
|
1177
|
+
paragraph → shape → master → theme inheritance. Closes three
|
|
1178
|
+
threads asking for "what font does this run actually use?" since
|
|
1179
|
+
the bare `font.name` / `.size` surfaces only return the
|
|
1180
|
+
explicitly-set values, not the resolved chain.
|
|
1181
|
+
|
|
1182
|
+
- **`Paragraph.auto_number_prefix`** (closes python-pptx#939).
|
|
1183
|
+
Returns the rendered numbering prefix (`"1."`, `"A)"`, `"i."`) for
|
|
1184
|
+
numbered bullets. Upstream's `paragraph.text` strips the prefix
|
|
1185
|
+
because it's stored as `<a:buAutoNum>` metadata, not text;
|
|
1186
|
+
callers feeding screen-readers / Markdown exporters previously
|
|
1187
|
+
had to manually count siblings.
|
|
1188
|
+
|
|
1189
|
+
### Picture additions
|
|
1190
|
+
|
|
1191
|
+
- **`Picture.transparency`** (closes python-pptx#1020). Fractional
|
|
1192
|
+
alpha (0.0 opaque .. 1.0 fully transparent) read from the OOXML
|
|
1193
|
+
`<a:blipFill><a:blip><a:alphaModFix amt="…">` stored on the
|
|
1194
|
+
picture-fill. Read-only — write goes through the existing
|
|
1195
|
+
picture-fill set-transparency path.
|
|
1196
|
+
|
|
1197
|
+
- **`Picture.freeform_geometry`** (closes python-pptx#1020). Returns
|
|
1198
|
+
the freeform clip-path points for pictures whose `<p:spPr>`
|
|
1199
|
+
carries a `<a:custGeom>` (e.g. wave-clipped photos). `None` when
|
|
1200
|
+
the picture uses the default rectangular clip.
|
|
1201
|
+
|
|
1202
|
+
### Movie / video additions
|
|
1203
|
+
|
|
1204
|
+
- **`Shapes.add_movie(...)` and `Shapes.add_video(...)` alias**
|
|
1205
|
+
(closes python-pptx#811 start time, #1034 online video URL, #954
|
|
1206
|
+
corrupted-file workflow, #974 safe deletion). New first-class
|
|
1207
|
+
author path for video shapes via :class:`AddMovie`. Upstream's
|
|
1208
|
+
`Movie` class is read-only; users have to deepcopy the OOXML to
|
|
1209
|
+
add new movies, which routinely produces corrupt files for
|
|
1210
|
+
non-OOXML animations (#954). The signature mirrors python-pptx's
|
|
1211
|
+
positional surface (`movie_file, left, top, width, height,
|
|
1212
|
+
poster_frame_image, mime_type`) plus keyword-only Athena extras:
|
|
1213
|
+
`url=` (online video URL — closes #1034), `start_time=` /
|
|
1214
|
+
`end_time=` (in seconds — closes #811), `autoplay`,
|
|
1215
|
+
`show_controls`, `loop`, `name`.
|
|
1216
|
+
|
|
1217
|
+
### Read-only / shape-collection additions
|
|
1218
|
+
|
|
1219
|
+
- **`Shapes.by_name(name)` and `Shapes.get_by_name(name, default=None)`**
|
|
1220
|
+
(closes python-pptx#532, 5-year-old request for a
|
|
1221
|
+
Selection-Pane-equivalent listing). `by_name` raises `KeyError`
|
|
1222
|
+
with the list of available names on a miss; `get_by_name` returns
|
|
1223
|
+
the supplied default. Mirrors the shape `SlideLayouts.by_name` /
|
|
1224
|
+
`get_by_name` pair already exposed for layouts.
|
|
1225
|
+
|
|
1226
|
+
### Image / mime fixes
|
|
1227
|
+
|
|
1228
|
+
- **`Image.content_type` correctly identifies EMF files**
|
|
1229
|
+
(closes python-pptx#1042). Pre-0.1.82 the legacy fallback used
|
|
1230
|
+
the WMF magic bytes for any `.emf`, returning `image/x-wmf` /
|
|
1231
|
+
`.wmf`. The sniffer now checks the EMR_HEADER signature first
|
|
1232
|
+
and falls back to the filename suffix; EMF files round-trip with
|
|
1233
|
+
the correct `image/x-emf` / `.emf`.
|
|
1234
|
+
|
|
1235
|
+
### Audit notes
|
|
1236
|
+
|
|
1237
|
+
- **python-pptx#1099 (slide notes missing after slide 20)** —
|
|
1238
|
+
verified not applicable here. Notes are pre-parsed server-side
|
|
1239
|
+
and surfaced via the snapshot; the upstream XML-parser bug
|
|
1240
|
+
doesn't live in our SDK.
|
|
1241
|
+
|
|
1242
|
+
- **python-pptx#1051 (Keynote speaker-notes interop)** — same
|
|
1243
|
+
rationale: notes-master bootstrap runs in the ingest worker and
|
|
1244
|
+
is unaffected by the SDK surface.
|
|
1245
|
+
|
|
1246
|
+
- **python-pptx#1038 (`MSO_LANGUAGE_ID has no XML mapping for
|
|
1247
|
+
'en-CN'`)** — not applicable. The upstream error originates in
|
|
1248
|
+
python-pptx's XML-tag → enum lookup; the SDK consumes integer
|
|
1249
|
+
LCIDs from the server snapshot so the lookup never runs.
|
|
1250
|
+
|
|
1251
|
+
- **python-pptx#1035 (add_chart return type annotation)** —
|
|
1252
|
+
already correct in the SDK. `add_chart(...) -> "GraphicFrame"`
|
|
1253
|
+
(chart accessed via `.chart` on the frame).
|
|
1254
|
+
|
|
1255
|
+
- **python-pptx#1127 (PERCENT_40 typo)** — already correct in the
|
|
1256
|
+
SDK. The upstream issue was a missing leading "P"; our enum has
|
|
1257
|
+
the proper spelling `PERCENT_40 = 6`.
|
|
1258
|
+
|
|
1259
|
+
Total Tier B closures: 16 implemented, 6 audited (already
|
|
1260
|
+
applicable or N/A by REST architecture).
|