athena-python-pptx 0.9.1__tar.gz → 0.13.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.9.1 → athena_python_pptx-0.13.0}/CHANGELOG.md +202 -0
- athena_python_pptx-0.13.0/CLAUDE.md +5 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/PKG-INFO +2 -3
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/docs/API_PARITY_EXCEPTIONS.md +254 -11
- athena_python_pptx-0.13.0/pptx/__init__.py +347 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/batching.py +316 -19
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/data.py +15 -7
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/client.py +87 -9
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/commands.py +112 -3
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/enum/shapes.py +4 -2
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/errors.py +9 -0
- athena_python_pptx-0.13.0/pptx/kit.py +1329 -0
- athena_python_pptx-0.13.0/pptx/oxml/__init__.py +43 -0
- athena_python_pptx-0.13.0/pptx/oxml/ns.py +91 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/presentation.py +113 -74
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/__init__.py +491 -182
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/slides.py +598 -103
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/text/__init__.py +64 -8
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/typing.py +6 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pyproject.toml +6 -2
- athena_python_pptx-0.9.1/pptx/__init__.py +0 -208
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/.gitignore +0 -0
- /athena_python_pptx-0.9.1/CLAUDE.md → /athena_python_pptx-0.13.0/AGENTS.md +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/README.md +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/_citations.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/_references.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/action.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/exc.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/media.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/package.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/shared.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/slide.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/spec.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/table.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/types.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/units.py +0 -0
- {athena_python_pptx-0.9.1 → athena_python_pptx-0.13.0}/pptx/util.py +0 -0
|
@@ -2,6 +2,208 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## Unreleased — `pptx.kit` composition kit
|
|
6
|
+
|
|
7
|
+
- New Athena-only module `pptx.kit` (`from pptx.kit import Kit, Palette,
|
|
8
|
+
Fonts, Region`; also re-exported from `pptx`). `Kit(prs)` composes slides
|
|
9
|
+
from regions derived from the slide size: `header` (small-caps kicker,
|
|
10
|
+
action title auto-fit to a two-line band, footer rule + page number +
|
|
11
|
+
`kit.footer_text`), `footer`, `takeaway` (full-width bar one gap above the
|
|
12
|
+
footer band), `content_region`, `columns`, `grid`, `kpi_row` / `kpi_card`
|
|
13
|
+
(surface card, accent stripe, big auto-fit value, auto-fit label), `card`,
|
|
14
|
+
`text` and `bullets`.
|
|
15
|
+
- Text asked to fit is sized to its box by the studio's `/measure-text-fit`
|
|
16
|
+
layouter (same client pattern as `TextFitter.best_fit_font_size`, result
|
|
17
|
+
floored and cached per kit, one failed probe latches the fallback) or, when
|
|
18
|
+
the studio is unreachable, by a conservative greedy wrap on the
|
|
19
|
+
`_estimate_text_width_emu` glyph table at 1.2 line height. It never grows
|
|
20
|
+
past the requested size; when nothing fits even at the minimum size the kit
|
|
21
|
+
uses the minimum and emits a `RuntimeWarning` naming the text; `fit=False`
|
|
22
|
+
keeps the size as given.
|
|
23
|
+
- Regions are clamped to the slide and the footer band is reserved, so
|
|
24
|
+
nothing lands outside the slide or under the footer; a region with nothing
|
|
25
|
+
left raises instead of silently placing nothing. `content_region` keeps the
|
|
26
|
+
takeaway band free by default so a bar added after the body cannot hide it,
|
|
27
|
+
and the `y` / `h` overrides of `columns` / `grid` / `kpi_row` are clamped to
|
|
28
|
+
the content region. Per-slide state is keyed by the stable `slide.slide_id`.
|
|
29
|
+
- Pure python-pptx public API underneath (`add_shape` / `add_textbox` /
|
|
30
|
+
`fill` / `line` / `text_frame` / `font`), emitted through the
|
|
31
|
+
presentation's command buffer — build each slide inside `with prs.batch():`
|
|
32
|
+
for one round-trip. Reaches agents with the next `athena-python-pptx` bump.
|
|
33
|
+
Documented in `docs/API_PARITY_EXCEPTIONS.md`; demo in
|
|
34
|
+
`examples/kit_demo.py`.
|
|
35
|
+
|
|
36
|
+
## Unreleased — validate() finds overflow and hidden text, drops containment noise
|
|
37
|
+
|
|
38
|
+
Reworks `Slide.validate()` around the defects that actually ship in agent
|
|
39
|
+
decks (verified 2026-09-14 against two 8-slide agent builds: 167 and 127
|
|
40
|
+
warnings, ~75% of them a background card behind its own label). Same
|
|
41
|
+
signature; an empty list is once again reachable by a designed slide.
|
|
42
|
+
|
|
43
|
+
- **Containment overlaps are no longer reported** unless both shapes carry
|
|
44
|
+
text (text on text is always reported). Partial overlaps are reported
|
|
45
|
+
only when at least one shape carries text; hairline overlaps thinner
|
|
46
|
+
than a text-frame inset (0.05in) and overlaps between two text-less
|
|
47
|
+
shapes are ignored. Tables count as text-bearing.
|
|
48
|
+
- **New: text overflow for non-table shapes** — per-paragraph font-metric
|
|
49
|
+
estimate (`_estimate_text_width_emu`, run size → paragraph default →
|
|
50
|
+
shape default → 18pt; word wrap on → `ceil(width / inner_width)` lines,
|
|
51
|
+
off → horizontal check; `lines × font × 1.2 × line_spacing` + spacing vs
|
|
52
|
+
box height minus margins). Frames set to auto-fit are skipped. When the
|
|
53
|
+
presentation has a studio client each hit whose paragraphs share one
|
|
54
|
+
font size and weight is confirmed with one `MeasureTextFit` round-trip
|
|
55
|
+
pinned at that size (`fits: true` drops it; capped at 12 per call; every
|
|
56
|
+
network error falls back to the heuristic verdict); mixed-font frames keep
|
|
57
|
+
the heuristic verdict since the server measures at a single font.
|
|
58
|
+
- **New: hidden text (occlusion)** — a text shape at least half covered by
|
|
59
|
+
a later (higher z-order) shape with a solid, non-transparent fill and no
|
|
60
|
+
text: `"text in 'X' is covered by 'Y'"`. Unknown fills (none, gradient,
|
|
61
|
+
pattern, pictures) count as not opaque; any transparency on the proxy or
|
|
62
|
+
a snapshot `fillTransparency` disables the rule for that shape.
|
|
63
|
+
- Warnings are deduplicated, ordered overflow → occlusion → text-on-text →
|
|
64
|
+
other (off-slide, one-text partial overlaps, empty placeholders), capped
|
|
65
|
+
at 15 per slide with a trailing `"… and N more (M containment overlaps
|
|
66
|
+
ignored)"` line. Rotated shapes near 90°/270° use their swapped visual
|
|
67
|
+
bbox in the pairwise rules.
|
|
68
|
+
- `bring_to_front()` / `send_to_back()` / `bring_forward()` /
|
|
69
|
+
`send_backward()` now mirror the move on the local `slide.shapes` order
|
|
70
|
+
(previously only the server saw it), so `validate()` reads the intended
|
|
71
|
+
stack without a refresh.
|
|
72
|
+
- New module-level `pptx.validate_all_touched() -> dict[int, list[str]]`
|
|
73
|
+
(Athena extension) for the executor postamble: `CommandBuffer` records
|
|
74
|
+
the slide indexes / slide ids / shape ids of every command the server
|
|
75
|
+
applied (`CommandBuffer.take_touched()` returns and clears them);
|
|
76
|
+
`validate_all_touched()` resolves them to slides on every live
|
|
77
|
+
`Presentation`, validates only those, and returns the slides with
|
|
78
|
+
warnings. Recorded indexes are shifted for slide insertions, deletions
|
|
79
|
+
and reorders applied later in the run; destination-bearing commands
|
|
80
|
+
(`CloneShape`, `CopySlideFromDeck`, `ReplaceText`) record their
|
|
81
|
+
destination; `SetPresentationSize` and a deck-wide `ReplaceText` validate
|
|
82
|
+
every slide. Commands that cannot change a layout (notes, names,
|
|
83
|
+
citations, deck metadata) never mark a slide; never raises for
|
|
84
|
+
validation reasons.
|
|
85
|
+
|
|
86
|
+
## 0.12.0 — snapshot hydration, ReplaceChartData emission, level dirty-flag, discard_all
|
|
87
|
+
|
|
88
|
+
SDK halves of the issue #30178 Cluster D server work (#30204 / #30211 /
|
|
89
|
+
#30212 / #30213).
|
|
90
|
+
|
|
91
|
+
**⚠️ Server ordering:** `Chart.replace_data` now emits a single
|
|
92
|
+
`ReplaceChartData` patch, which servers without #30211 reject (400) — the
|
|
93
|
+
`presentation-exec` snapshot bump to 0.12.0 must wait for the pptx-studio
|
|
94
|
+
deploy that ships the op.
|
|
95
|
+
|
|
96
|
+
- `Shape.shadow` hydrates from the snapshot's `shadow` bag: a partial edit
|
|
97
|
+
like `shadow.blur_radius = Pt(6)` re-emits the ingested shadow's
|
|
98
|
+
`visible=True` / type / distance / direction / color / transparency
|
|
99
|
+
instead of the un-hydrated defaults (`visible=False` used to DELETE the
|
|
100
|
+
ingested shadow server-side). Hydrated shadows read back
|
|
101
|
+
`inherit == False`.
|
|
102
|
+
- `Shape.click_action` hydrates from the snapshot's `clickAction` bag —
|
|
103
|
+
same deletion-bug class (`click_action.tooltip = ...` used to emit
|
|
104
|
+
`action_type='none'` and delete the ingested link).
|
|
105
|
+
- `Presentation.core_properties` hydrates from the snapshot's
|
|
106
|
+
`coreProperties` (server #30204): setting one property no longer wipes
|
|
107
|
+
the deck's other metadata to `None`. `refresh()` re-hydrates.
|
|
108
|
+
- `Chart.replace_data` emits ONE `ReplaceChartData` patch carrying the
|
|
109
|
+
full categories + series matrix (python-pptx semantics: shrink drops
|
|
110
|
+
trailing points, grow adds them, extra series are removed) instead of
|
|
111
|
+
per-index `UpdateSeries*` / `UpdateCategoryLabel` patches that couldn't
|
|
112
|
+
change the data's shape. The local `chartSpec` mirror still updates,
|
|
113
|
+
preserving server series ids by index. A zero-series `chart_data`
|
|
114
|
+
raises `ValidationError` before touching the local mirror (the op
|
|
115
|
+
requires at least one series; silently returning would desync local
|
|
116
|
+
reads from the server).
|
|
117
|
+
- `run.hyperlink.target_slide = n` no longer rides with `address=''` (the
|
|
118
|
+
clear convention) — exactly one of address / targetSlideIndex is sent,
|
|
119
|
+
aligning with server #30212. The SetText re-emit carries
|
|
120
|
+
`hyperlinkTargetSlideIndex`, and snapshot rich content hydrates it back.
|
|
121
|
+
`SetRunHyperlinkTarget.validate()` rejects setting both.
|
|
122
|
+
- `paragraph.level` is sent only when the paragraph is indented or the
|
|
123
|
+
caller explicitly assigned it (explicit `level = 0` still reaches the
|
|
124
|
+
wire); an untouched level-0 paragraph omits the field so unrelated style
|
|
125
|
+
edits don't stomp the server-side outline level (belt-and-suspenders
|
|
126
|
+
with the server-side lvl=0 canonicalization in #30213).
|
|
127
|
+
- New module-level `pptx.discard_all()` (mirrors athena-python-docx):
|
|
128
|
+
drops all buffered, un-flushed commands across open decks without
|
|
129
|
+
posting and returns the dropped count. The agora executor postamble
|
|
130
|
+
calls it when user code raised, so failed scripts discard their
|
|
131
|
+
half-buffered tail instead of posting it.
|
|
132
|
+
|
|
133
|
+
## 0.11.0 — silent no-op & data-loss fixes (deep-dive remediation)
|
|
134
|
+
|
|
135
|
+
Eliminates a cluster of silent no-ops, read-after-write desyncs, and
|
|
136
|
+
buffer-poisoning bugs found in the 2026-08 presentation-asset deep dive.
|
|
137
|
+
All fixes preserve python-pptx API parity (behavior, not surface, changes).
|
|
138
|
+
|
|
139
|
+
- `paragraph.level = 0` now reaches the wire (was dropped by the None-drop),
|
|
140
|
+
so resetting an indented paragraph works.
|
|
141
|
+
- `font.subscript = False` / `font.superscript = False` now send `baseline: 0`
|
|
142
|
+
(the server's clear signal) instead of omitting the field, so they actually
|
|
143
|
+
un-sub/superscript existing text.
|
|
144
|
+
- A bare `shape.fill.solid()` now emits, matching `gradient()`/`patterned()`.
|
|
145
|
+
- Chart `has_legend` / `has_title` / `chart_title` now update the local
|
|
146
|
+
snapshot on write, so reads after a write return the new value.
|
|
147
|
+
- `rows.insert()` / `columns.insert()` now shift the cached row-height /
|
|
148
|
+
column-width lists, fixing corrupted height/width reads after an insert.
|
|
149
|
+
- `GroupShape.shapes` now resolves child shapes via `childIds` (was hardcoded
|
|
150
|
+
to `[]`).
|
|
151
|
+
- `PicturePlaceholder.insert_picture` converts WebP/SVG/AVIF to PNG (was
|
|
152
|
+
mislabeling them `png`) and honors its `width`/`height` params.
|
|
153
|
+
- `batch()` discards the buffer when its body raises (was committing the
|
|
154
|
+
partial prefix).
|
|
155
|
+
- A server-rejected command in a batch is dropped (not re-queued), ending the
|
|
156
|
+
permanent buffer-poison loop; the rest of the rolled-back chunk is restored.
|
|
157
|
+
- Command chunks carry a stable `txn.id` across retries so an ambiguous-timeout
|
|
158
|
+
resend dedups server-side instead of double-applying.
|
|
159
|
+
- `flush_all(strict=True)` raises `FlushAllError` on a per-buffer flush failure
|
|
160
|
+
(the sandbox executor opts in) instead of silently swallowing it.
|
|
161
|
+
- `Presentation` no longer pins its buffer/client/session via the atexit hook
|
|
162
|
+
(weakref closure); `close()`/`__del__` release the HTTP session.
|
|
163
|
+
- Export 404s fail fast for a deck this client already read (deleted deck),
|
|
164
|
+
instead of burning the ~85s post-upload retry budget.
|
|
165
|
+
- `XyChartData` / `BubbleChartData` `add_series` raise on malformed inline
|
|
166
|
+
points instead of silently dropping them.
|
|
167
|
+
- `mso_shape_to_string` raises on an unmapped autoshape id instead of silently
|
|
168
|
+
authoring a rectangle.
|
|
169
|
+
- `AddMovie` emits `posterBase64` (the key the server reads) and always sends a
|
|
170
|
+
poster (a 1x1 placeholder when none is supplied), so `add_movie`/`add_video`
|
|
171
|
+
no longer fail the whole batch.
|
|
172
|
+
|
|
173
|
+
## 0.10.1 — unnamed-shape parity, browser UA on image fetches, honest SVG errors
|
|
174
|
+
|
|
175
|
+
Fixes the three `execute_presentation_code` failures observed in the
|
|
176
|
+
2026-08-12 production deck-build session (thread_d7eb9dd6).
|
|
177
|
+
|
|
178
|
+
- `Shape.name` now always returns a `str` (python-pptx parity —
|
|
179
|
+
`cNvPr@name` is a schema-required attribute upstream). Elements
|
|
180
|
+
persisted before names were stored (e.g. pictures added by pre-0.10
|
|
181
|
+
SDKs) read back as `""` instead of `None`, so idioms like
|
|
182
|
+
`'Picture' in shape.name` no longer raise `TypeError`.
|
|
183
|
+
- `add_picture(url)` fetches now send browser-equivalent
|
|
184
|
+
`User-Agent`/`Accept` headers. Image hosts and proxies behind bot
|
|
185
|
+
rules (e.g. `wsrv.nl`, Cloudflare-fronted CDNs) 403 the default
|
|
186
|
+
`python-requests/x.y` UA while serving the identical URL to a browser
|
|
187
|
+
UA. The Accept header advertises only formats the deck pipeline
|
|
188
|
+
handles (PNG/JPEG native, WebP via Pillow) so format-negotiating CDNs
|
|
189
|
+
don't switch the body to AVIF or SVG.
|
|
190
|
+
- AVIF is now detected explicitly (any `avif`/`avis` brand in the
|
|
191
|
+
ISO-BMFF `ftyp` box — major or compatible, so `mif1`-major HEIF
|
|
192
|
+
containers are covered) instead of falling through the sniffer as
|
|
193
|
+
`png` — it converts via Pillow where the build supports AVIF, and
|
|
194
|
+
otherwise raises the actionable convert-or-fail error rather than
|
|
195
|
+
shipping undecodable bytes that render as a silent broken-image
|
|
196
|
+
placeholder.
|
|
197
|
+
- `Shapes.by_name("")` / `get_by_name("")` no longer match unnamed
|
|
198
|
+
legacy shapes — `""` is the no-name sentinel, not an addressable
|
|
199
|
+
name.
|
|
200
|
+
- The SVG `add_picture` error no longer recommends `pip install svglib`
|
|
201
|
+
(rasterization also needs the `rlPyCairo` backend and the native cairo
|
|
202
|
+
library, so that advice cannot work in a stock sandbox). WebP and SVG
|
|
203
|
+
now get separate, accurate remediation hints. Pairs with the
|
|
204
|
+
presentation-exec v59 snapshot, which bundles `svglib` + `rlPyCairo`
|
|
205
|
+
so SVG→PNG conversion works in-sandbox.
|
|
206
|
+
|
|
5
207
|
## 0.9.0 — eager validation, table overflow diagnostics, and text caps
|
|
6
208
|
|
|
7
209
|
Publishes the SDK changes already deployed with PPTX Studio after 0.8.1.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -31,7 +31,6 @@ Requires-Dist: responses>=0.23; extra == 'dev'
|
|
|
31
31
|
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
32
32
|
Provides-Extra: e2e
|
|
33
33
|
Requires-Dist: pytest>=7.0; extra == 'e2e'
|
|
34
|
-
Requires-Dist: python-pptx>=0.6.21; extra == 'e2e'
|
|
35
34
|
Provides-Extra: visual
|
|
36
35
|
Requires-Dist: numpy>=1.24; extra == 'visual'
|
|
37
36
|
Requires-Dist: pillow>=10.0; extra == 'visual'
|
|
@@ -158,6 +158,119 @@ very large inline image) cannot be split — it is sent alone and may still
|
|
|
158
158
|
be rejected with a teaching `IMAGE_TOO_LARGE` error, but it no longer
|
|
159
159
|
takes the rest of the queued edits down with it.
|
|
160
160
|
|
|
161
|
+
### `Chart.replace_data()` emits one `ReplaceChartData` op (v0.12.0 — requires a server with ReplaceChartData support)
|
|
162
|
+
|
|
163
|
+
python-pptx's `chart.replace_data(chart_data)` rewrites the chart's whole
|
|
164
|
+
data matrix. Before v0.12.0 the SDK approximated that with per-index
|
|
165
|
+
`UpdateSeries*` / `UpdateCategoryLabel` patches, which could neither shrink
|
|
166
|
+
nor grow the data (stale trailing `c:pt` entries and orphaned series
|
|
167
|
+
corrupted the chart part). As of v0.12.0 each `replace_data` call emits
|
|
168
|
+
exactly **one** `ReplaceChartData` patch carrying the full
|
|
169
|
+
`categories` + `series` matrix, so shrinking drops trailing points, growing
|
|
170
|
+
adds them, and extra series are removed — matching python-pptx semantics.
|
|
171
|
+
|
|
172
|
+
**Deployment note:** a pptx-studio server without the `ReplaceChartData`
|
|
173
|
+
patch op (pre-#30211) rejects the whole command batch with a Zod validation
|
|
174
|
+
error. SDK 0.12.0 must only be rolled into sandbox snapshots after the
|
|
175
|
+
studio deploy that ships the op.
|
|
176
|
+
|
|
177
|
+
### `pptx.flush_all()` / `pptx.discard_all()` — module-level buffer drains (Athena-only)
|
|
178
|
+
|
|
179
|
+
The sandbox executor postamble ends every `execute_presentation_code` run
|
|
180
|
+
with one of these:
|
|
181
|
+
|
|
182
|
+
- `flush_all(strict=False)` — flush every live presentation's buffered
|
|
183
|
+
commands. `strict=True` (used by the executor) raises `FlushAllError`
|
|
184
|
+
when any buffer's flush is rejected instead of silently swallowing it.
|
|
185
|
+
- `discard_all()` (v0.12.0) — drop all buffered, un-flushed commands
|
|
186
|
+
across open decks **without posting them** and return the dropped count.
|
|
187
|
+
Called when user code raised: posting a dead script's half-buffered tail
|
|
188
|
+
would write content whose prerequisites failed (and duplicate it when
|
|
189
|
+
the agent retries the whole script). Best-effort — a batch already
|
|
190
|
+
handed to an in-flight background flush cannot be recalled.
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
import pptx
|
|
194
|
+
dropped = pptx.discard_all() # -> int, 0 when nothing was buffered
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Stock python-pptx has no equivalent; local scripts normally never need
|
|
198
|
+
either function (buffers auto-flush on idle and at interpreter exit).
|
|
199
|
+
|
|
200
|
+
### `pptx.validate_all_touched() -> dict[int, list[str]]` — validate the slides a run edited (Athena-only)
|
|
201
|
+
|
|
202
|
+
The third executor hook, called after `flush_all(strict=True)`. Every
|
|
203
|
+
`CommandBuffer` remembers the slide indexes, slide ids (including the
|
|
204
|
+
`sld_…` client ids of slides added in the run) and shape ids of the commands
|
|
205
|
+
the server **applied** since the last call (`CommandBuffer.take_touched()`);
|
|
206
|
+
`validate_all_touched()` resolves them against each live `Presentation`'s
|
|
207
|
+
slide proxies, runs [`slide.validate()`](#slidevalidate---liststr) on exactly
|
|
208
|
+
those slides, and returns `{slide_index: warnings}` for the slides that had
|
|
209
|
+
warnings — `{}` means every touched slide is clean.
|
|
210
|
+
|
|
211
|
+
- Only flushed commands count — buffered edits are not yet "touched".
|
|
212
|
+
- The touched set is consumed by the call; a second call with no flush in
|
|
213
|
+
between returns `{}`.
|
|
214
|
+
- Commands that cannot change a layout (notes, names, citations, deck
|
|
215
|
+
metadata) never mark a slide. `DeleteSlide` and `ReorderSlides` mark
|
|
216
|
+
nothing but shift the indexes recorded before them, as do slide
|
|
217
|
+
insertions (`AddSlide`, `CloneSlide`, `CopySlideFromDeck`), so an index
|
|
218
|
+
recorded early in a batch still names the same slide at the end.
|
|
219
|
+
- Destination-bearing commands record their destination: `CloneShape`
|
|
220
|
+
marks `target_slide_index` (the source shape only when cloning in place),
|
|
221
|
+
`CopySlideFromDeck` its `destination_index`, `UngroupShapes` the group
|
|
222
|
+
shape's slide, `ReplaceText` its shape or slide. `SetPresentationSize`
|
|
223
|
+
and a deck-wide `ReplaceText` set `all_slides` and validate every slide.
|
|
224
|
+
- Never raises for validation reasons: a slide whose `validate()` fails
|
|
225
|
+
contributes a single `"validate() failed: …"` line.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
import pptx
|
|
229
|
+
pptx.flush_all(strict=True)
|
|
230
|
+
for index, warnings in pptx.validate_all_touched().items():
|
|
231
|
+
print(f"slide {index + 1}:", *warnings, sep="\n ")
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Stock python-pptx has no equivalent.
|
|
235
|
+
|
|
236
|
+
### Snapshot-hydrated full-state setters (v0.12.0)
|
|
237
|
+
|
|
238
|
+
`Shape.shadow`, `Shape.click_action`, and `Presentation.core_properties`
|
|
239
|
+
re-emit their **full** state on every partial write (their wire commands
|
|
240
|
+
have no partial form). As of v0.12.0 they hydrate that state from the
|
|
241
|
+
snapshot at construction, so a partial edit preserves what was ingested:
|
|
242
|
+
|
|
243
|
+
- `shape.shadow.blur_radius = Pt(6)` keeps the ingested shadow's
|
|
244
|
+
`visible=True` / type / distance / direction / color / transparency
|
|
245
|
+
(previously it emitted the un-hydrated `visible=False` default, deleting
|
|
246
|
+
the shadow server-side). A hydrated shadow also reads back
|
|
247
|
+
`shadow.inherit == False`.
|
|
248
|
+
- `shape.click_action.tooltip = "..."` keeps the ingested link's action
|
|
249
|
+
type / URL / slide target (previously it emitted `action_type='none'`,
|
|
250
|
+
deleting the link).
|
|
251
|
+
- `prs.core_properties.author = "..."` keeps the deck's existing title /
|
|
252
|
+
subject / keywords / comments / category (previously they were wiped to
|
|
253
|
+
`None`). `Presentation.refresh()` re-hydrates from the fresh snapshot.
|
|
254
|
+
|
|
255
|
+
### `paragraph.level` rides the wire only when meaningful (v0.12.0)
|
|
256
|
+
|
|
257
|
+
`SetParagraphStyle` re-emits the paragraph's full style on every setter.
|
|
258
|
+
`level` is now included only when the paragraph is indented (`level > 0`)
|
|
259
|
+
or the caller explicitly assigned `paragraph.level` (including an explicit
|
|
260
|
+
reset to `0`, which must reach the wire). An untouched level-0 paragraph
|
|
261
|
+
omits the field so unrelated style edits (alignment, spacing, …) don't
|
|
262
|
+
stomp the server-side outline level.
|
|
263
|
+
|
|
264
|
+
### Run-level slide jumps send exactly one of address / targetSlideIndex (v0.12.0)
|
|
265
|
+
|
|
266
|
+
`run.hyperlink.target_slide = n` emits `SetRunHyperlinkTarget` with
|
|
267
|
+
`targetSlideIndex` only — it no longer rides with `address=''` (the
|
|
268
|
+
clear-the-link convention), which servers interpret as "clear". An
|
|
269
|
+
explicit `run.hyperlink.address = None` with no slide target still sends
|
|
270
|
+
`address=''` to clear. The rich-content re-emit carries the jump as
|
|
271
|
+
`hyperlinkTargetSlideIndex` on the run, and snapshot rich content with
|
|
272
|
+
that key hydrates back into `run.hyperlink`.
|
|
273
|
+
|
|
161
274
|
---
|
|
162
275
|
|
|
163
276
|
## Agent-Friendly Additions (not in python-pptx)
|
|
@@ -288,12 +401,18 @@ the iterate-and-rebuild pattern in the v0.1.77 retrospective. None of them
|
|
|
288
401
|
exist in upstream python-pptx — they're Athena additions and code that uses
|
|
289
402
|
them is *not* portable to stock python-pptx.
|
|
290
403
|
|
|
291
|
-
### `slide.clear_shapes() -> int`
|
|
404
|
+
### `slide.clear_shapes(*, delete_unmodeled_source_content=False) -> int`
|
|
292
405
|
|
|
293
|
-
Delete every slide-local shape
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
406
|
+
Delete every slide-local shape with one slide-scoped command. Returns the
|
|
407
|
+
number of modeled shapes removed. Unmodeled visual OOXML from the imported
|
|
408
|
+
slide is preserved by default, because Athena not rendering an object is not
|
|
409
|
+
authorization to delete it. Inherited layout / master placeholders and
|
|
410
|
+
slide-level background/notes are left alone.
|
|
411
|
+
|
|
412
|
+
Pass `delete_unmodeled_source_content=True` only after the user explicitly
|
|
413
|
+
authorizes replacing the complete imported visual layer. That option can
|
|
414
|
+
delete text boxes, SmartArt, controls, and other PowerPoint content that is
|
|
415
|
+
not visible in Athena.
|
|
297
416
|
|
|
298
417
|
**Why we added it:** the previous workaround was a manual
|
|
299
418
|
`for shape in slide.shapes: shape.delete()` loop. Combined with the silent
|
|
@@ -308,12 +427,47 @@ slide.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(2))
|
|
|
308
427
|
|
|
309
428
|
### `slide.validate() -> list[str]`
|
|
310
429
|
|
|
311
|
-
Report layout
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
430
|
+
Report the layout defects a reader would notice, without rendering. An
|
|
431
|
+
empty list means the slide is clean — and a designed slide (cards behind
|
|
432
|
+
their labels, icons inside tiles) reaches it. Warnings come back in this
|
|
433
|
+
order, at most 15 per slide, then a count line
|
|
434
|
+
`"… and N more (M containment overlaps ignored)"`:
|
|
435
|
+
|
|
436
|
+
1. **Text overflow** — `"text in 'X' likely overflows its box (needs ~N
|
|
437
|
+
lines of ~M at Spt; …)"` for non-table text whose estimated height
|
|
438
|
+
(per-paragraph font-metric width → wrapped lines → lines × font × 1.2 +
|
|
439
|
+
spacing) exceeds the box minus its text-frame margins. Word-wrap-off
|
|
440
|
+
frames are checked horizontally; auto-fit frames are skipped. When the
|
|
441
|
+
presentation has a studio client, each hit whose paragraphs share one
|
|
442
|
+
font size and weight is confirmed with one `POST /measure-text-fit`
|
|
443
|
+
pinned at that size (`fits: true` drops the warning, at most 12
|
|
444
|
+
round-trips per call, network errors keep the heuristic verdict); a
|
|
445
|
+
mixed-font frame (60pt heading over 12pt body) keeps the heuristic
|
|
446
|
+
verdict because the server lays the frame out at a single font. Table
|
|
447
|
+
cells whose single-line text is wider than the column are reported here
|
|
448
|
+
too.
|
|
449
|
+
2. **Hidden text** — `"text in 'X' is covered by 'Y'"`: a text shape at
|
|
450
|
+
least half covered by a *later* (higher z-order) shape with a solid fill,
|
|
451
|
+
no transparency and no text. A solid fill the snapshot reports without
|
|
452
|
+
transparency data is taken as opaque (PowerPoint's default); any
|
|
453
|
+
transparency set on the proxy or carried as `fillTransparency` disables
|
|
454
|
+
the rule for that shape. `send_to_back()` / `bring_to_front()` now mirror
|
|
455
|
+
the move on `slide.shapes` order so the rule sees the intended stack
|
|
456
|
+
without a refresh.
|
|
457
|
+
3. **Text on text** — two text-bearing shapes (tables count) whose bboxes
|
|
458
|
+
overlap or nest.
|
|
459
|
+
4. **Other** — off-slide shapes; partial overlaps where exactly one shape
|
|
460
|
+
carries text (a title running under a card); empty slide-level
|
|
461
|
+
placeholders.
|
|
462
|
+
|
|
463
|
+
Not reported: one shape fully inside another unless both carry text,
|
|
464
|
+
overlaps between two text-less shapes, and hairline overlaps thinner than
|
|
465
|
+
a text-frame inset (0.05in). Rotated shapes near 90°/270° use their swapped
|
|
466
|
+
visual bbox.
|
|
467
|
+
|
|
468
|
+
The overflow estimate uses average glyph widths, so offline it can be off
|
|
469
|
+
by a line either way; render the slide before rebuilding a layout around a
|
|
470
|
+
single warning.
|
|
317
471
|
|
|
318
472
|
```python
|
|
319
473
|
warnings = slide.validate()
|
|
@@ -390,6 +544,15 @@ upstream auto-assigns names like `"Rectangle 47"` and only exposes
|
|
|
390
544
|
construction. **Portable code that needs to run against stock python-pptx
|
|
391
545
|
should omit `name=` and assign after the fact.**
|
|
392
546
|
|
|
547
|
+
When `name=` is omitted, Athena's generated name skips any identical name in
|
|
548
|
+
the slide's current SDK snapshot. This matters after clone/delete workflows
|
|
549
|
+
where the simple shape-count candidate can already exist. The selected name is
|
|
550
|
+
also available immediately on the returned proxy, before a snapshot refresh.
|
|
551
|
+
This is snapshot-local rather than a distributed uniqueness guarantee: a
|
|
552
|
+
concurrent collaborator can add the same name after the snapshot was loaded.
|
|
553
|
+
Keep the returned proxy or `shape_id` for cross-client-safe follow-up edits;
|
|
554
|
+
use names as human-readable selectors after refreshing the deck.
|
|
555
|
+
|
|
393
556
|
```python
|
|
394
557
|
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
|
|
395
558
|
Inches(1), Inches(1), Inches(4), Inches(2),
|
|
@@ -1049,6 +1212,14 @@ work in addition to SDK surface and are tracked separately.
|
|
|
1049
1212
|
|
|
1050
1213
|
### Shape-level additions
|
|
1051
1214
|
|
|
1215
|
+
- **`Shape.z_order = "to_back" | "to_front" | "backward" | "forward"`**
|
|
1216
|
+
is a write-only convenience alias for `send_to_back()`,
|
|
1217
|
+
`bring_to_front()`, `send_backward()`, and `bring_forward()`. Assignment
|
|
1218
|
+
emits the same `SetShapeZOrder` command as the method form; unsupported
|
|
1219
|
+
strings raise `ValueError` instead of creating a misleading local
|
|
1220
|
+
attribute. Reading `shape.z_order` raises `AttributeError` because deck
|
|
1221
|
+
snapshots do not expose a stable numeric z-order value.
|
|
1222
|
+
|
|
1052
1223
|
- **`Shape.duplicate(target_slide=None, offset_x=None, offset_y=None)`**
|
|
1053
1224
|
(closes python-pptx#533, #620, 10-comment thread requesting a public
|
|
1054
1225
|
duplicate API). Alias for the existing `Shape.clone()` — both verbs
|
|
@@ -1503,3 +1674,75 @@ prior in-module definitions. `AssetReference` remains a thin in-module wrapper
|
|
|
1503
1674
|
that additionally accepts a plain `dict` `meta` (the historical SDK shape) and
|
|
1504
1675
|
serializes it verbatim, preserving byte-identical `to_dict()` output for
|
|
1505
1676
|
existing callers.
|
|
1677
|
+
|
|
1678
|
+
---
|
|
1679
|
+
|
|
1680
|
+
## `pptx.kit` — composition kit (Athena-only, unreleased)
|
|
1681
|
+
|
|
1682
|
+
`from pptx.kit import Kit, Palette, Fonts, Region` (also re-exported from the
|
|
1683
|
+
`pptx` package). No upstream counterpart — python-pptx has no layout layer.
|
|
1684
|
+
The module carries the module-level `__athena_extension_module__` marker and
|
|
1685
|
+
each of the four classes is decorated `@athena_extension`, so the whole
|
|
1686
|
+
surface walks through `iter_athena_extensions`.
|
|
1687
|
+
|
|
1688
|
+
**Why.** Two agent-built 8-slide decks (2026-09-14) each hand-placed 200+
|
|
1689
|
+
shapes with EMU arithmetic and both agents wrote the same six helpers
|
|
1690
|
+
(`rect`, `text`, `header`, `kpi`, `card`, `takeaway`) from scratch. Every
|
|
1691
|
+
visual defect was placement maths: text wrapping in a too-narrow box, a title
|
|
1692
|
+
under the KPI cards, body text hidden under a takeaway bar added later. The
|
|
1693
|
+
model should compose, not compute EMUs.
|
|
1694
|
+
|
|
1695
|
+
**Surface.**
|
|
1696
|
+
|
|
1697
|
+
| Member | Purpose |
|
|
1698
|
+
|--------|---------|
|
|
1699
|
+
| `Kit(prs, palette=None, fonts=None)` | Reads slide size, layouts and the command buffer from `prs`. `kit.footer_text` is the footer text `header()` places on every slide; `kit.reserve_takeaway` (default `True`) keeps the takeaway band free in `content_region()` before a bar exists. |
|
|
1700
|
+
| `Palette(...)` | `RRGGBB` hex fields (`#` / lowercase normalised). Athena defaults: primary `0B1F3A`, accent `00A6A6`, highlight `F2A900`, ink `1F2937`, muted `6B7280`, surface `F4F6FA`, line `D9DEE7`, on_dark `FFFFFF`. Any `color=` / `accent=` argument may name a field (`"accent"`) or pass hex / `RGBColor`. |
|
|
1701
|
+
| `Fonts(heading=None, body=None, …)` | Family names (`None` inherits the deck theme) plus the point-size scale each helper starts from (`title_pt=30`, `title_min_pt=18`, `kicker_pt=11`, `body_pt=14`, `small_pt=11`, `kpi_value_pt=36`, `card_title_pt=14`, `card_body_pt=12`, `takeaway_pt=14`, `footer_pt=9`, `min_pt=8`). Auto-fit only ever shrinks. |
|
|
1702
|
+
| `Region(x, y, w, h)` | Frozen EMU rectangle (accepts `Length`); `.right`, `.bottom`, `.inset(dx, dy=None)`, `.split_h(n_or_weights, gap=None)`, `.split_v(...)`, `.contains`, `.overlaps`, `.intersect`. |
|
|
1703
|
+
| `kit.new_slide(layout_name="Blank") -> Slide` | `slide_layouts.by_name(...)`, falling back to the first layout without body placeholders, then the first layout. |
|
|
1704
|
+
| `kit.header(slide, title, kicker=None, page=None) -> Region` | Small-caps accent kicker, action title auto-fit to a two-line band, footer rule + `footer_text` + page number. Returns the title band. |
|
|
1705
|
+
| `kit.footer(slide, text, page=None) -> Region` | Rule, `text` (left) and `page` (right) inside the reserved footer band; idempotent per slide. |
|
|
1706
|
+
| `kit.takeaway(slide, text) -> Region` | Full-width primary bar with an accent stripe, one gap above the footer band; text auto-fit, `on_dark`. |
|
|
1707
|
+
| `kit.content_region(slide, *, takeaway=None) -> Region` | Between the title band (if a header was placed) and the takeaway band / footer band. |
|
|
1708
|
+
| `kit.columns(slide, n, y=None, h=None, gap=None)` / `kit.grid(slide, rows, cols, y=None, h=None, gap=None)` | Partition the content region; `n` / `rows` / `cols` may be weights. |
|
|
1709
|
+
| `kit.kpi_row(slide, items, y=None, h=None, accents=None)` / `kit.kpi_card(slide, region, value, label, accent=None)` | Surface cards with an accent stripe, a big auto-fit value and an auto-fit label; `kpi_row` returns the card regions. |
|
|
1710
|
+
| `kit.card(slide, region, title=None, body=None, accent=None)` | Card with a bold title and a body (a string = paragraphs, a list = bullets). |
|
|
1711
|
+
| `kit.text(slide, region, text_or_paragraphs, size=None, bold=False, color=None, align=None, anchor=None, fit=True)` / `kit.bullets(slide, region, items, size=None, fit=True)` | A text box filling the region; returns the shape. Bullet items may be `(text, level)` tuples. |
|
|
1712
|
+
| `kit.fit_size(paragraphs, region, max_size, *, min_size=None, bold=False, family=None)` | The fitting size the helpers use, exposed for custom boxes. |
|
|
1713
|
+
|
|
1714
|
+
**Rules the kit enforces.**
|
|
1715
|
+
|
|
1716
|
+
- Every position derives from `prs.slide_width` / `prs.slide_height`: margin
|
|
1717
|
+
5% of the width, gutter 1.5%, top margin 6% of the height, title band 15%,
|
|
1718
|
+
footer band 6%, takeaway bar 10%, KPI row 20%.
|
|
1719
|
+
- Regions are clamped to the slide and to the canvas above the footer band;
|
|
1720
|
+
a region with nothing left raises `ValueError` rather than placing nothing.
|
|
1721
|
+
The `y` / `h` overrides of `columns`, `grid` and `kpi_row` only ever narrow
|
|
1722
|
+
the content region — they are clamped to it, so they cannot reach the title
|
|
1723
|
+
band or the reserved takeaway band. Header, takeaway and footer bands never
|
|
1724
|
+
overlap each other; footer text always leaves the page-number column free.
|
|
1725
|
+
- With `fit=True` the largest size ≤ the requested size that fits is used.
|
|
1726
|
+
When the presentation's client has a base URL the kit POSTs a
|
|
1727
|
+
`MeasureTextFit` query to `/measure-text-fit` (the same client pattern as
|
|
1728
|
+
`TextFitter.best_fit_font_size`; result floored, cached per kit; one failed
|
|
1729
|
+
probe latches the fallback for the kit's lifetime). Offline it greedy-wraps
|
|
1730
|
+
on the `_estimate_text_width_emu` glyph table at 1.2 line height. When
|
|
1731
|
+
nothing fits even at the minimum size (the server's `fits: false`, or the
|
|
1732
|
+
estimate) the kit uses the minimum and emits a `RuntimeWarning` naming the
|
|
1733
|
+
text, so the author can shorten it or enlarge the region. Fitted boxes also
|
|
1734
|
+
get `auto_size = TEXT_TO_FIT_SHAPE` as a render-time safety net;
|
|
1735
|
+
`fit=False` keeps the requested size and touches no auto-size.
|
|
1736
|
+
- Pure python-pptx public API underneath — `add_shape(MSO_SHAPE.RECTANGLE, …)`,
|
|
1737
|
+
`add_textbox`, `fill.solid()` / `fore_color.rgb`, `line.fill.background()`,
|
|
1738
|
+
`text_frame`, `font` — so the deck is an ordinary deck and every command
|
|
1739
|
+
rides the presentation's `CommandBuffer`; build each slide inside
|
|
1740
|
+
`with prs.batch():` for one round-trip.
|
|
1741
|
+
|
|
1742
|
+
Per-slide state (header bottom, takeaway top, footer parts) is keyed by
|
|
1743
|
+
`slide.slide_id` — the SDK's stable slide identity, so it survives reorders,
|
|
1744
|
+
inserts and deletes — for the life of the `Kit`. Compose a slide through one
|
|
1745
|
+
`Slide` proxy (normally inside one `with prs.batch():`); a batch-created slide
|
|
1746
|
+
re-fetched after a refresh carries its server id and starts with fresh state.
|
|
1747
|
+
Reaches agents with the next `athena-python-pptx` bump. Demo:
|
|
1748
|
+
`examples/kit_demo.py`; tests: `tests/test_kit.py`.
|