athena-python-pptx 0.2.0__tar.gz → 0.3.1__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.2.0 → athena_python_pptx-0.3.1}/API_PARITY_REPORT.md +28 -5
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/CHANGELOG.md +41 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/PKG-INFO +1 -1
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/docs/API_PARITY_EXCEPTIONS.md +496 -2
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/__init__.py +1 -1
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/client.py +36 -1
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/commands.py +1089 -47
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/enum/chart.py +12 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/enum/dml.py +45 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/errors.py +48 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/chart.py +18 -2
- athena_python_pptx-0.3.1/pptx/parts/image.py +186 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/presentation.py +141 -12
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/__init__.py +2588 -83
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/slides.py +645 -20
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/text/__init__.py +605 -32
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pyproject.toml +1 -1
- athena_python_pptx-0.2.0/pptx/parts/image.py +0 -85
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/.gitignore +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/CLAUDE.md +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/README.md +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/_references.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/action.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/batching.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/exc.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/media.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/package.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/shared.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/slide.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/spec.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/table.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/types.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/typing.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/pptx/units.py +0 -0
- {athena_python_pptx-0.2.0 → athena_python_pptx-0.3.1}/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:
|
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.3.1
|
|
6
|
+
|
|
7
|
+
**Chart title runs, slide insertion, and shape-delete local state — Insight
|
|
8
|
+
Partners session feedback batch.**
|
|
9
|
+
|
|
10
|
+
Investigated [thread_7b324723-…](https://app.athenaintel.com/dashboard/spaces/?session_id=thread_7b324723-a478-4e99-ac8f-6e7a69766292)
|
|
11
|
+
where an agent built a 15-slide LP pitch deck and tripped over three
|
|
12
|
+
SDK rough edges. Each is addressed below.
|
|
13
|
+
|
|
14
|
+
- **`chart.chart_title.text_frame.paragraphs[0].runs[0].font` now works.**
|
|
15
|
+
The `_ChartTitleTextFrame` previously exposed only `text`; iterating
|
|
16
|
+
`paragraphs` raised `AttributeError`, and the agent fell back to
|
|
17
|
+
unstyled titles. The text frame now carries paragraph and run
|
|
18
|
+
proxies; per-run font writes coalesce into a single
|
|
19
|
+
`SetChartTitleRich` patch on the next flush. The simple
|
|
20
|
+
`text_frame.text = "..."` path still uses the cheaper `SetChartTitle`
|
|
21
|
+
patch — no wire-size regression on the no-formatting case.
|
|
22
|
+
|
|
23
|
+
- **`prs.slides.insert(idx, layout)` added.** Previously, placing a new
|
|
24
|
+
slide between two existing slides required `add_slide()` followed by
|
|
25
|
+
`clone(target_index=...)` to shuffle the list — a workaround that
|
|
26
|
+
cloned 11 shapes onto the new slide and then couldn't fully clear
|
|
27
|
+
them. `insert()` emits a single `AddSlide(index=...)` and refreshes
|
|
28
|
+
the local collection.
|
|
29
|
+
|
|
30
|
+
- **`shape.delete()` updates the local `slide.shapes` view immediately.**
|
|
31
|
+
The buffered delete left `len(slide.shapes)` stale, so the
|
|
32
|
+
"Before: 33, After: 33" symptom appeared even when the delete was
|
|
33
|
+
going through correctly server-side. `delete()` now removes the
|
|
34
|
+
shape from the parent `Shapes` collection in addition to queueing
|
|
35
|
+
the `DeleteShape` command. Layout-inherited shapes
|
|
36
|
+
(`source == "layout"`) no-op rather than queue a guaranteed-to-fail
|
|
37
|
+
delete.
|
|
38
|
+
|
|
39
|
+
- **Toolkit prompt updated** to surface the working chart APIs —
|
|
40
|
+
`series.format.fill.solid()` + `series.format.fill.fore_color.rgb`,
|
|
41
|
+
`chart.legend.position`, doughnut/pie chart enums — and the new
|
|
42
|
+
`slide.clear_shapes()` / `prs.slides.insert()` helpers. The
|
|
43
|
+
Insight Partners agent assumed series styling was unsupported and
|
|
44
|
+
skipped per-series brand colors entirely.
|
|
45
|
+
|
|
5
46
|
## 0.1.76
|
|
6
47
|
|
|
7
48
|
**Table cell paragraph alignment now round-trips to OOXML (baseline-parity Gap A2).**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -400,7 +457,11 @@ that closed six more setters end-to-end (see the new table below):
|
|
|
400
457
|
setters.
|
|
401
458
|
- 3-D chart authoring (`XL_CHART_TYPE.THREE_D_*` raises
|
|
402
459
|
`UnsupportedFeatureError`).
|
|
403
|
-
- 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).
|
|
404
465
|
- Trendlines and error bars on series.
|
|
405
466
|
|
|
406
467
|
### Chart Patches Added in the Styling Residuals PR (6 new operations)
|
|
@@ -769,6 +830,67 @@ The default `fit=None` preserves stock python-pptx behaviour. The only
|
|
|
769
830
|
other accepted value is `"contain"`; passing anything else raises
|
|
770
831
|
`ValueError` to fail loudly rather than silently no-op.
|
|
771
832
|
|
|
833
|
+
## `Shapes.add_athena_anchor()` — Athena-only cross-asset screenshot insert
|
|
834
|
+
|
|
835
|
+
Athena extension with no python-pptx analogue. Renders an Athena
|
|
836
|
+
`Anchor` (a structured reference to a region of another asset — sheet
|
|
837
|
+
cells, sheet ranges, etc.) to a PNG and inserts it as a regular
|
|
838
|
+
`Picture` shape on the current slide. The result is a one-shot raster
|
|
839
|
+
snapshot, **not** a live-linked binding — use `add_linked_table()` /
|
|
840
|
+
`add_linked_ole_object()` for refreshable bindings.
|
|
841
|
+
|
|
842
|
+
```python
|
|
843
|
+
from pptx._references import AssetReference, SheetRangeAnchor
|
|
844
|
+
|
|
845
|
+
slide.shapes.add_athena_anchor(
|
|
846
|
+
AssetReference(id="asset_20880ea3-95fb-4577-9a2e-ad11b7773939"),
|
|
847
|
+
SheetRangeAnchor(sheet_id=1, range="A1:F20"),
|
|
848
|
+
Inches(1), Inches(1),
|
|
849
|
+
width=Inches(8),
|
|
850
|
+
)
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
Signature:
|
|
854
|
+
|
|
855
|
+
```python
|
|
856
|
+
add_athena_anchor(
|
|
857
|
+
source: AssetReference,
|
|
858
|
+
anchor: Anchor,
|
|
859
|
+
left: Length,
|
|
860
|
+
top: Length,
|
|
861
|
+
width: Length | None = None,
|
|
862
|
+
height: Length | None = None,
|
|
863
|
+
*,
|
|
864
|
+
name: str | None = None,
|
|
865
|
+
fit: str | None = None, # "contain" — same semantics as add_picture
|
|
866
|
+
) -> Shape
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
Supported anchor types today:
|
|
870
|
+
|
|
871
|
+
| Anchor type | Behaviour |
|
|
872
|
+
|--------------------|----------------------------------------------------------|
|
|
873
|
+
| `SheetCellAnchor` | Renders the single cell as a 1×1 range PNG. |
|
|
874
|
+
| `SheetRangeAnchor` | Renders the A1 range via xlsx-studio's screenshot.png. |
|
|
875
|
+
| `SheetTableAnchor` | Raises `NotImplementedError` (resolve to A1 + retry). |
|
|
876
|
+
| `SlideAnchor` | Raises `NotImplementedError` (no slide-snapshot route). |
|
|
877
|
+
| anything else | Raises `NotImplementedError`. |
|
|
878
|
+
|
|
879
|
+
Sheet anchors route through `GET {ATHENA_XLSX_BASE_URL}/workbooks/:id/screenshot.png`
|
|
880
|
+
with the sandbox's `ATHENA_XLSX_API_KEY` forwarded as a Bearer token —
|
|
881
|
+
xlsx-studio's ownership middleware checks that the calling workspace
|
|
882
|
+
can read the source workbook. Render scale is fixed at 3 (matches
|
|
883
|
+
xlsx-studio's `range-as-asset` default — slide inserts sit at 6–9″
|
|
884
|
+
widths where scale=2 leaves text soft). When `ATHENA_XLSX_BASE_URL` /
|
|
885
|
+
`ATHENA_XLSX_API_KEY` aren't set (e.g. running outside a Daytona
|
|
886
|
+
presentation_exec sandbox), the call raises `ValueError` rather than
|
|
887
|
+
producing a broken picture.
|
|
888
|
+
|
|
889
|
+
For finer control (custom scale, `include_headers`, off-snapshot
|
|
890
|
+
post-processing), fetch the PNG bytes directly via athena-openpyxl's
|
|
891
|
+
`Worksheet.export_range_sync()` and call `add_picture(bytes, …)`
|
|
892
|
+
yourself.
|
|
893
|
+
|
|
772
894
|
---
|
|
773
895
|
|
|
774
896
|
## `Shapes.add_linked_table()` — Athena studio-linking extension (added in Phase 3)
|
|
@@ -825,3 +947,375 @@ the same table element on the Y.Doc.
|
|
|
825
947
|
|
|
826
948
|
**`freshness_mode='office_live_link'` is reserved for Phase 5** and
|
|
827
949
|
currently raises ``ValueError`` locally.
|
|
950
|
+
|
|
951
|
+
---
|
|
952
|
+
|
|
953
|
+
## Athena extensions for most-requested upstream features (v0.1.81)
|
|
954
|
+
|
|
955
|
+
These surfaces are **additions** to python-pptx, not deviations — every
|
|
956
|
+
upstream class, method, property, and parameter remains 1:1 with
|
|
957
|
+
python-pptx 1.0.2. Each item below closes (or fundamentally improves)
|
|
958
|
+
a long-standing upstream feature gap. Issue numbers reference
|
|
959
|
+
`scanny/python-pptx`. Every entry is marked at runtime with
|
|
960
|
+
`@athena_extension(issue=…, since="0.1.81")` so the registry walker
|
|
961
|
+
picks them up; the legacy `@athena_only` markers continue to work for
|
|
962
|
+
back-compat.
|
|
963
|
+
|
|
964
|
+
This wave intentionally **excludes animations / Morph transitions**
|
|
965
|
+
(python-pptx#1106 / #942) — they need export-worker and Y.Doc-schema
|
|
966
|
+
work in addition to SDK surface and are tracked separately.
|
|
967
|
+
|
|
968
|
+
### Shape-level additions
|
|
969
|
+
|
|
970
|
+
- **`Shape.duplicate(target_slide=None, offset_x=None, offset_y=None)`**
|
|
971
|
+
(closes python-pptx#533, #620, 10-comment thread requesting a public
|
|
972
|
+
duplicate API). Alias for the existing `Shape.clone()` — both verbs
|
|
973
|
+
resolve identically. The clone preserves every server-resolved
|
|
974
|
+
property (transform, color, text, type) and accepts the same
|
|
975
|
+
`target_slide` / offset args as `clone()`.
|
|
976
|
+
|
|
977
|
+
- **`LineFormat.head_end_type` / `tail_end_type` / `head_end_length` /
|
|
978
|
+
`tail_end_length` / `head_end_width` / `tail_end_width`** (closes
|
|
979
|
+
python-pptx#1033). Connector / line arrow heads. Accept either
|
|
980
|
+
the upstream `MSO_ARROWHEAD` / `MSO_ARROWHEAD_LENGTH` /
|
|
981
|
+
`MSO_ARROWHEAD_WIDTH` enums or the OOXML short strings
|
|
982
|
+
(`"triangle"` / `"stealth"` / `"diamond"` / `"oval"` / `"arrow"` /
|
|
983
|
+
`"none"`, `"sm"` / `"med"` / `"lg"`). Each setter emits a
|
|
984
|
+
`SetConnectorArrows` wire op carrying all six attrs at once.
|
|
985
|
+
|
|
986
|
+
```python
|
|
987
|
+
from pptx.enum.dml import MSO_ARROWHEAD
|
|
988
|
+
conn = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT,
|
|
989
|
+
Inches(1), Inches(1),
|
|
990
|
+
Inches(5), Inches(3))
|
|
991
|
+
conn.line.tail_end_type = MSO_ARROWHEAD.TRIANGLE
|
|
992
|
+
conn.line.tail_end_length = "med"
|
|
993
|
+
conn.line.tail_end_width = "lg"
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
- **`Shapes.add_picture(svg_bytes_or_path, …)`** now natively
|
|
997
|
+
supports SVG (closes python-pptx#1112). The picture's content-type
|
|
998
|
+
is detected from the magic bytes (`<?xml … <svg`), the native
|
|
999
|
+
size is parsed from the `<svg width=/height=>` attributes (or the
|
|
1000
|
+
`viewBox`), and the wire payload carries `image_format="svg"` so
|
|
1001
|
+
the server stores the SVG as a first-class picture asset (PowerPoint
|
|
1002
|
+
2016+ supports SVG natively).
|
|
1003
|
+
|
|
1004
|
+
- **`Shapes.add_connector(...)` int-coerces float endpoints**
|
|
1005
|
+
(closes python-pptx#1058 — "Generating corrupted PPT when using
|
|
1006
|
+
connectors"). Calls like `connector(top=shape.top + shape.height / 2)`
|
|
1007
|
+
produce floats that legacy code wrote into `<a:off x="...">`
|
|
1008
|
+
attributes that Google Slides and modern PowerPoint refuse to open.
|
|
1009
|
+
We now route every endpoint through `int(round(float(...)))` so the
|
|
1010
|
+
wire payload is integer EMU regardless of input type
|
|
1011
|
+
(`float` / `numpy.float64` / `Decimal`).
|
|
1012
|
+
|
|
1013
|
+
### Text-frame and run additions
|
|
1014
|
+
|
|
1015
|
+
- **`Hyperlink.target_slide`** (closes python-pptx#1077). Run-level
|
|
1016
|
+
internal slide jumps. Assign a `Slide` proxy or a zero-based int;
|
|
1017
|
+
the SDK emits a `SetRunHyperlinkTarget` command with the
|
|
1018
|
+
`targetSlideIndex` payload, and the server writes
|
|
1019
|
+
`<a:hlinkClick action="ppaction://hlinksldjump">` with the matching
|
|
1020
|
+
slide-jump relationship. Mutually exclusive with `address` (an
|
|
1021
|
+
external URL); setting one nulls the other.
|
|
1022
|
+
|
|
1023
|
+
```python
|
|
1024
|
+
run.hyperlink.target_slide = prs.slides[3]
|
|
1025
|
+
# or
|
|
1026
|
+
run.hyperlink.target_slide = 3
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
- **`Run.font.color.rgb` overrides the theme hyperlink color**
|
|
1030
|
+
(closes python-pptx#940 — "Set/change font color when working with
|
|
1031
|
+
Hyperlinks is impossible", 11-comment thread). The Hyperlink
|
|
1032
|
+
emitter now reads the run's explicit color and ships an
|
|
1033
|
+
`overrideColorHex` field on `SetRunHyperlinkTarget` so the server
|
|
1034
|
+
writes the matching `<a:rPr><a:solidFill>` over the run instead of
|
|
1035
|
+
letting the theme `hlink` color win.
|
|
1036
|
+
|
|
1037
|
+
- **`TextFrame.replace_text(pattern, replacement, *, regex=False,
|
|
1038
|
+
match_case=False, whole_word=False, max_replacements=None)`**
|
|
1039
|
+
(closes python-pptx#684, #1037, #884). Find-and-replace that
|
|
1040
|
+
preserves run-level formatting (fonts / colors / sizes / bold /
|
|
1041
|
+
italic) by splitting runs across the match boundary on the server.
|
|
1042
|
+
Returns the count of substitutions performed.
|
|
1043
|
+
|
|
1044
|
+
- **`TextFitter.best_fit_font_size(text, extents, max_size, font_file)`**
|
|
1045
|
+
no longer returns `max_size` unconditionally (closes
|
|
1046
|
+
python-pptx#715, #936, #970, #1026). When a base URL is configured
|
|
1047
|
+
(`ATHENA_PPTX_BASE_URL`), the classmethod issues a `MeasureTextFit`
|
|
1048
|
+
query to the studio's text-measurement endpoint and returns the
|
|
1049
|
+
largest size that fits. The upstream signature is preserved exactly
|
|
1050
|
+
(additional knobs read from `ATHENA_PPTX_TEXTFITTER_*` env vars).
|
|
1051
|
+
Falls back to `max_size` with a `RuntimeWarning` if no client is
|
|
1052
|
+
reachable so offline tests keep working.
|
|
1053
|
+
|
|
1054
|
+
### Table additions
|
|
1055
|
+
|
|
1056
|
+
- **`_RowCollection.add(*, height=None, copy_format_from=None)`** and
|
|
1057
|
+
**`_RowCollection.insert(index, *, height=None, copy_format_from=None)`**
|
|
1058
|
+
(closes python-pptx#895, #1016, 17-comment thread). Restores the
|
|
1059
|
+
`rows.add()` method removed in upstream 1.0.0 and adds an indexed
|
|
1060
|
+
`insert()` companion. `copy_format_from=<row_index>` inherits the
|
|
1061
|
+
source row's height + every cell's formatting.
|
|
1062
|
+
|
|
1063
|
+
- **`_ColumnCollection.add(*, width=None, copy_format_from=None)`** and
|
|
1064
|
+
**`_ColumnCollection.insert(index, ...)`** — column-axis mirror.
|
|
1065
|
+
`Table.add_column()` / `Table.remove_column()` / `Table.delete_column()`
|
|
1066
|
+
/ `Table.delete_row()` are convenience wrappers on top.
|
|
1067
|
+
|
|
1068
|
+
```python
|
|
1069
|
+
table.rows.add(copy_format_from=0)
|
|
1070
|
+
table.columns.insert(2, width=Inches(1.5))
|
|
1071
|
+
table.delete_row(-1)
|
|
1072
|
+
table.delete_column(0)
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
### Chart additions
|
|
1076
|
+
|
|
1077
|
+
- **`DataLabel.show_value` / `show_series_name` / `show_category_name` /
|
|
1078
|
+
`show_percentage` / `number_format` / `text` / `font` / `position`**
|
|
1079
|
+
(closes python-pptx#1068, #1024, #1025). Per-point data-label
|
|
1080
|
+
overrides. Reach via `series.points[i].data_label.show_value = True`;
|
|
1081
|
+
each setter emits a `SetPointDataLabelStyle` wire op so the
|
|
1082
|
+
server can apply the override on top of the series default.
|
|
1083
|
+
|
|
1084
|
+
- **`_ChartTitle.left` / `top` / `width` / `height`** (closes
|
|
1085
|
+
python-pptx#1030 — "How to set manually chart_title position?").
|
|
1086
|
+
Fractional layout (0.0 – 1.0 of the plot area) for the manual
|
|
1087
|
+
title placement. Each setter emits `SetChartTitlePosition` which
|
|
1088
|
+
the server materialises as `<c:layout><c:manualLayout xMode="edge"
|
|
1089
|
+
yMode="edge" …>`. Setting any axis to `None` clears the override and
|
|
1090
|
+
falls back to PowerPoint's auto-placement.
|
|
1091
|
+
|
|
1092
|
+
- **Office 2016+ chart types**: `XL_CHART_TYPE.TREEMAP` (117),
|
|
1093
|
+
`SUNBURST` (118), `HISTOGRAM` (119), `PARETO` (120),
|
|
1094
|
+
`BOX_WHISKER` (121), `WATERFALL` (122), `FUNNEL` (123). Closes
|
|
1095
|
+
python-pptx#583 ("New Chart Types in Office 2016"), #944
|
|
1096
|
+
(Treemap / Scatter), #651 (Waterfall), #1047 (Box plot). These
|
|
1097
|
+
use the cx-namespace OOXML schema instead of the legacy c-schema;
|
|
1098
|
+
`slide.shapes.add_chart(...)` dispatches through a separate
|
|
1099
|
+
`AddChart2016` wire op so the server can author the cx-flavoured
|
|
1100
|
+
chart-part XML.
|
|
1101
|
+
|
|
1102
|
+
### Slide- and presentation-level additions
|
|
1103
|
+
|
|
1104
|
+
- **`Presentation.create(widescreen=True)` is now the default**
|
|
1105
|
+
(closes python-pptx#1066, 9-comment thread). The blank-template
|
|
1106
|
+
factory creates 16:9 widescreen decks at 14630400 × 8229600 EMU
|
|
1107
|
+
(PowerPoint's "Widescreen (16:9)" preset). Pass `widescreen=False`
|
|
1108
|
+
to fall back to the legacy 4:3 default.
|
|
1109
|
+
|
|
1110
|
+
- **`Presentation.open(deck_id, base_url=None, api_key=None)`**
|
|
1111
|
+
(closes python-pptx#1018 — "open a PPT in append mode"). Named
|
|
1112
|
+
factory that mirrors the iterate-and-edit verb the upstream thread
|
|
1113
|
+
filed under. Internally identical to `Presentation(deck_id=…)`
|
|
1114
|
+
but reads naturally next to `Presentation.create` / `.upload`.
|
|
1115
|
+
|
|
1116
|
+
- **`Presentation.copy_slide_from(source, source_slide_index, *,
|
|
1117
|
+
destination_index=-1)`** (closes python-pptx#1036, #696, #934,
|
|
1118
|
+
three threads totalling 20+ comments asking for cross-deck slide
|
|
1119
|
+
copy). Emits `CopySlideFromDeck`; the server runs the deep copy
|
|
1120
|
+
with full relationship + media-de-duplication handling. Accepts
|
|
1121
|
+
either a live `Presentation` instance (its pending edits are
|
|
1122
|
+
flushed first) or a raw deck id string.
|
|
1123
|
+
|
|
1124
|
+
- **`Slides.remove_slide(slide_or_index)`** (closes python-pptx#956).
|
|
1125
|
+
Alias for the existing `Slides.delete()`. Both verbs accept a
|
|
1126
|
+
`Slide` proxy or an integer index.
|
|
1127
|
+
|
|
1128
|
+
- **`SlideMaster.shapes.add_shape / add_picture / add_textbox /
|
|
1129
|
+
add_table`** and **`SlideLayout.shapes.add_*`** (closes
|
|
1130
|
+
python-pptx#575 — "Add shapes to Slide Master" — and #1044 —
|
|
1131
|
+
"Add textbox to layout"). Mutation surface on master / layout
|
|
1132
|
+
shape collections. Reads remain empty (REST snapshot doesn't
|
|
1133
|
+
materialise master / layout shapes as first-class read targets),
|
|
1134
|
+
but writes round-trip through `AddShapeOnMaster` so the addition
|
|
1135
|
+
is inherited by every slide using the master / layout.
|
|
1136
|
+
|
|
1137
|
+
```python
|
|
1138
|
+
master = prs.slide_masters[0]
|
|
1139
|
+
master.shapes.add_picture("logo.png",
|
|
1140
|
+
Inches(0.25), Inches(0.25),
|
|
1141
|
+
width=Inches(1))
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
### Audit notes
|
|
1145
|
+
|
|
1146
|
+
- **python-pptx#1085 / #925 / #899 (group-shape coords)** — verified
|
|
1147
|
+
not applicable to this SDK. The REST snapshot carries
|
|
1148
|
+
server-resolved absolute coordinates for every shape, including
|
|
1149
|
+
group children that surface as flat slide-level siblings, so the
|
|
1150
|
+
group-relative coord bug the upstream issues describe can't occur
|
|
1151
|
+
here.
|
|
1152
|
+
|
|
1153
|
+
When upstream python-pptx eventually lands native versions of any of
|
|
1154
|
+
these surfaces, the SDK should swap to the upstream signature in
|
|
1155
|
+
place — this section is the canonical reference for what each Athena
|
|
1156
|
+
extension wires up.
|
|
1157
|
+
|
|
1158
|
+
---
|
|
1159
|
+
|
|
1160
|
+
## Tier B — next-wave upstream features (v0.1.82)
|
|
1161
|
+
|
|
1162
|
+
Second wave from the open-issue spectrum. Same contract as v0.1.81:
|
|
1163
|
+
upstream surface preserved, additions tagged with
|
|
1164
|
+
`@athena_extension(issue=…, since="0.1.82")`. New wire commands in
|
|
1165
|
+
`pptx.commands`: `SetShapeHidden`, `SetPointInvertIfNegative`,
|
|
1166
|
+
`SetSeriesErrorBars`, `SetConnectorEndpoint`, `AddMovie`,
|
|
1167
|
+
`SetChartHyperlink`, `ApplyForeignLayout`, `HideChartSeries`.
|
|
1168
|
+
|
|
1169
|
+
### Shape and connector additions
|
|
1170
|
+
|
|
1171
|
+
- **`Shape.hidden`** (closes python-pptx#971). Boolean read/write
|
|
1172
|
+
toggle that emits `SetShapeHidden` so the `<p:cNvPr hidden="1">`
|
|
1173
|
+
flag round-trips. Hidden shapes are excluded from render + Selection
|
|
1174
|
+
Pane but stay in the OOXML tree.
|
|
1175
|
+
|
|
1176
|
+
- **`Connector.begin_connect` / `end_connect`** now emit
|
|
1177
|
+
`SetConnectorEndpoint` (closes python-pptx#946, #1017). The
|
|
1178
|
+
pre-0.1.82 local-state-only stubs were exposed only so `isinstance`-
|
|
1179
|
+
style code didn't `AttributeError`; the new wire op makes the
|
|
1180
|
+
binding survive the round-trip and tracks the bound shape if it
|
|
1181
|
+
moves.
|
|
1182
|
+
|
|
1183
|
+
- **`Connector.set_elbow_adjustment(fraction, endpoint="begin")`**
|
|
1184
|
+
(closes python-pptx#1017 specifically). Adjusts the midpoint of an
|
|
1185
|
+
elbow connector; `fraction` is the position along the major axis
|
|
1186
|
+
(`0.0` first endpoint, `1.0` second). No-op on STRAIGHT / CURVE
|
|
1187
|
+
connectors.
|
|
1188
|
+
|
|
1189
|
+
### Chart additions
|
|
1190
|
+
|
|
1191
|
+
- **`Point.invert_if_negative`** (closes python-pptx#776). Per-data-
|
|
1192
|
+
point override that emits `SetPointInvertIfNegative`.
|
|
1193
|
+
Series-level `invert_if_negative` is overridden the moment any
|
|
1194
|
+
point in the series has its own `<c:spPr>`, so per-point control
|
|
1195
|
+
is the only reliable color-inversion path once explicit colors
|
|
1196
|
+
are set.
|
|
1197
|
+
|
|
1198
|
+
- **`DataLabels.border_color_hex / border_width / border_dash_style`**
|
|
1199
|
+
(closes python-pptx#716, 17-comment thread). Adds outline styling
|
|
1200
|
+
to plot- and series-level data labels via the existing
|
|
1201
|
+
`SetDataLabelStyle` / `SetSeriesDataLabelStyle` patch envelopes.
|
|
1202
|
+
Both writes and reads work consistently with the rest of the
|
|
1203
|
+
`DataLabels` surface.
|
|
1204
|
+
|
|
1205
|
+
- **`Chart.set_hyperlink(url=..., target_slide_index=..., tooltip=...)`**
|
|
1206
|
+
(closes python-pptx#962). Routes chart-level click-actions through
|
|
1207
|
+
`SetChartHyperlink` since the canonical `SetClickAction`
|
|
1208
|
+
path doesn't carry chart-shape relationships.
|
|
1209
|
+
|
|
1210
|
+
- **`Chart.hide_series(*series_ids)`** (closes python-pptx#1043).
|
|
1211
|
+
Hides series from the rendered plot without dropping them from the
|
|
1212
|
+
underlying chart-data workbook — callers can run the same data
|
|
1213
|
+
through multiple chart templates that surface different subsets.
|
|
1214
|
+
|
|
1215
|
+
- **Office 2016+ `pareto` chart type round-trips** —
|
|
1216
|
+
`AddChart2016.validate()` now includes `"pareto"` in the valid
|
|
1217
|
+
set, fixing a pre-merge ratchet that rejected the documented
|
|
1218
|
+
enum value. (Already in Tier A spec, closed in PR #20711 follow-up.)
|
|
1219
|
+
|
|
1220
|
+
### Slide and presentation additions
|
|
1221
|
+
|
|
1222
|
+
- **`Slide.apply_foreign_layout(master_index, layout_index)`**
|
|
1223
|
+
(closes python-pptx#1109, #1028). Apply a layout from a different
|
|
1224
|
+
master to an existing slide via `ApplyForeignLayout`. The
|
|
1225
|
+
server runs the relationship + theme inheritance fix-up so the
|
|
1226
|
+
slide renders against any master's layout without a deep-copy
|
|
1227
|
+
workaround.
|
|
1228
|
+
|
|
1229
|
+
- **`Slides.remove_slide()` alias for `delete()`** (already in Tier
|
|
1230
|
+
A, restated here because the upstream issue #956 spelled it both
|
|
1231
|
+
ways — both verbs accept a `Slide` or an integer index).
|
|
1232
|
+
|
|
1233
|
+
### Text additions
|
|
1234
|
+
|
|
1235
|
+
- **`Font.resolve() -> dict`** (closes python-pptx#1063, #765, #883).
|
|
1236
|
+
Returns the *rendered* font properties (name / size_pt / bold /
|
|
1237
|
+
italic / underline / strike / color_hex / language_id) walking
|
|
1238
|
+
paragraph → shape → master → theme inheritance. Closes three
|
|
1239
|
+
threads asking for "what font does this run actually use?" since
|
|
1240
|
+
the bare `font.name` / `.size` surfaces only return the
|
|
1241
|
+
explicitly-set values, not the resolved chain.
|
|
1242
|
+
|
|
1243
|
+
- **`Paragraph.auto_number_prefix`** (closes python-pptx#939).
|
|
1244
|
+
Returns the rendered numbering prefix (`"1."`, `"A)"`, `"i."`) for
|
|
1245
|
+
numbered bullets. Upstream's `paragraph.text` strips the prefix
|
|
1246
|
+
because it's stored as `<a:buAutoNum>` metadata, not text;
|
|
1247
|
+
callers feeding screen-readers / Markdown exporters previously
|
|
1248
|
+
had to manually count siblings.
|
|
1249
|
+
|
|
1250
|
+
### Picture additions
|
|
1251
|
+
|
|
1252
|
+
- **`Picture.transparency`** (closes python-pptx#1020). Fractional
|
|
1253
|
+
alpha (0.0 opaque .. 1.0 fully transparent) read from the OOXML
|
|
1254
|
+
`<a:blipFill><a:blip><a:alphaModFix amt="…">` stored on the
|
|
1255
|
+
picture-fill. Read-only — write goes through the existing
|
|
1256
|
+
picture-fill set-transparency path.
|
|
1257
|
+
|
|
1258
|
+
- **`Picture.freeform_geometry`** (closes python-pptx#1020). Returns
|
|
1259
|
+
the freeform clip-path points for pictures whose `<p:spPr>`
|
|
1260
|
+
carries a `<a:custGeom>` (e.g. wave-clipped photos). `None` when
|
|
1261
|
+
the picture uses the default rectangular clip.
|
|
1262
|
+
|
|
1263
|
+
### Movie / video additions
|
|
1264
|
+
|
|
1265
|
+
- **`Shapes.add_movie(...)` and `Shapes.add_video(...)` alias**
|
|
1266
|
+
(closes python-pptx#811 start time, #1034 online video URL, #954
|
|
1267
|
+
corrupted-file workflow, #974 safe deletion). New first-class
|
|
1268
|
+
author path for video shapes via :class:`AddMovie`. Upstream's
|
|
1269
|
+
`Movie` class is read-only; users have to deepcopy the OOXML to
|
|
1270
|
+
add new movies, which routinely produces corrupt files for
|
|
1271
|
+
non-OOXML animations (#954). The signature mirrors python-pptx's
|
|
1272
|
+
positional surface (`movie_file, left, top, width, height,
|
|
1273
|
+
poster_frame_image, mime_type`) plus keyword-only Athena extras:
|
|
1274
|
+
`url=` (online video URL — closes #1034), `start_time=` /
|
|
1275
|
+
`end_time=` (in seconds — closes #811), `autoplay`,
|
|
1276
|
+
`show_controls`, `loop`, `name`.
|
|
1277
|
+
|
|
1278
|
+
### Read-only / shape-collection additions
|
|
1279
|
+
|
|
1280
|
+
- **`Shapes.by_name(name)` and `Shapes.get_by_name(name, default=None)`**
|
|
1281
|
+
(closes python-pptx#532, 5-year-old request for a
|
|
1282
|
+
Selection-Pane-equivalent listing). `by_name` raises `KeyError`
|
|
1283
|
+
with the list of available names on a miss; `get_by_name` returns
|
|
1284
|
+
the supplied default. Mirrors the shape `SlideLayouts.by_name` /
|
|
1285
|
+
`get_by_name` pair already exposed for layouts.
|
|
1286
|
+
|
|
1287
|
+
### Image / mime fixes
|
|
1288
|
+
|
|
1289
|
+
- **`Image.content_type` correctly identifies EMF files**
|
|
1290
|
+
(closes python-pptx#1042). Pre-0.1.82 the legacy fallback used
|
|
1291
|
+
the WMF magic bytes for any `.emf`, returning `image/x-wmf` /
|
|
1292
|
+
`.wmf`. The sniffer now checks the EMR_HEADER signature first
|
|
1293
|
+
and falls back to the filename suffix; EMF files round-trip with
|
|
1294
|
+
the correct `image/x-emf` / `.emf`.
|
|
1295
|
+
|
|
1296
|
+
### Audit notes
|
|
1297
|
+
|
|
1298
|
+
- **python-pptx#1099 (slide notes missing after slide 20)** —
|
|
1299
|
+
verified not applicable here. Notes are pre-parsed server-side
|
|
1300
|
+
and surfaced via the snapshot; the upstream XML-parser bug
|
|
1301
|
+
doesn't live in our SDK.
|
|
1302
|
+
|
|
1303
|
+
- **python-pptx#1051 (Keynote speaker-notes interop)** — same
|
|
1304
|
+
rationale: notes-master bootstrap runs in the ingest worker and
|
|
1305
|
+
is unaffected by the SDK surface.
|
|
1306
|
+
|
|
1307
|
+
- **python-pptx#1038 (`MSO_LANGUAGE_ID has no XML mapping for
|
|
1308
|
+
'en-CN'`)** — not applicable. The upstream error originates in
|
|
1309
|
+
python-pptx's XML-tag → enum lookup; the SDK consumes integer
|
|
1310
|
+
LCIDs from the server snapshot so the lookup never runs.
|
|
1311
|
+
|
|
1312
|
+
- **python-pptx#1035 (add_chart return type annotation)** —
|
|
1313
|
+
already correct in the SDK. `add_chart(...) -> "GraphicFrame"`
|
|
1314
|
+
(chart accessed via `.chart` on the frame).
|
|
1315
|
+
|
|
1316
|
+
- **python-pptx#1127 (PERCENT_40 typo)** — already correct in the
|
|
1317
|
+
SDK. The upstream issue was a missing leading "P"; our enum has
|
|
1318
|
+
the proper spelling `PERCENT_40 = 6`.
|
|
1319
|
+
|
|
1320
|
+
Total Tier B closures: 16 implemented, 6 audited (already
|
|
1321
|
+
applicable or N/A by REST architecture).
|