athena-python-pptx 0.1.74__tar.gz → 0.1.75__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.74 → athena_python_pptx-0.1.75}/CHANGELOG.md +54 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/PKG-INFO +1 -1
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/__init__.py +1 -1
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/client.py +17 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/__init__.py +7 -1
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/text/__init__.py +25 -1
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/typing.py +21 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/units.py +20 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pyproject.toml +1 -1
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/.gitignore +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/CLAUDE.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/README.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/docs/API_PARITY_EXCEPTIONS.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/action.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/batching.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/commands.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/errors.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/exc.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/media.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/package.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/presentation.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/shared.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/slide.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/slides.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/spec.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/table.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/types.py +0 -0
- {athena_python_pptx-0.1.74 → athena_python_pptx-0.1.75}/pptx/util.py +0 -0
|
@@ -2,6 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.75
|
|
6
|
+
|
|
7
|
+
**Critical bug fix — invisible text from `font.size = Pt(X)`.**
|
|
8
|
+
|
|
9
|
+
A user surfaced that agent-generated decks rendered as blank
|
|
10
|
+
rectangles in the Olympus editor. Investigation traced the symptom to
|
|
11
|
+
`fontSizePt: 355600` (= 28 × 12700) landing on the wire instead of
|
|
12
|
+
`fontSizePt: 28` — making the renderer treat text as 355,600-point
|
|
13
|
+
glyphs and rendering them effectively invisible.
|
|
14
|
+
|
|
15
|
+
### Root cause
|
|
16
|
+
|
|
17
|
+
`Length.__truediv__` returns a `Length` subclass — e.g.
|
|
18
|
+
`Pt(28) / EMU_PER_PT` returns a `Pt` instance with raw int value `28`
|
|
19
|
+
(meaning 28 EMU, semantically "28 points worth"). The SDK's font-size
|
|
20
|
+
serializer relies on this to convert EMU → points:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
style["fontSizePt"] = self._size / EMU_PER_PT # Pt(355600) / 12700
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The trouble: `Command.to_dict()` calls `dataclasses.asdict()` which
|
|
27
|
+
**deep-copies all nested values**. `copy.deepcopy(Pt(28))` reconstructs
|
|
28
|
+
via `Pt.__new__(28)`, and `Pt.__new__` interprets its argument as
|
|
29
|
+
*points* (not EMU) and multiplies by 12700. So a Pt instance whose
|
|
30
|
+
internal int was 28 EMU came back out as 28 × 12700 = 355600 EMU.
|
|
31
|
+
|
|
32
|
+
By the time the JSON went over the wire, `fontSizePt` was 355600.
|
|
33
|
+
Server stored it, renderer rendered text at 355,600pt → invisible.
|
|
34
|
+
|
|
35
|
+
### Fix
|
|
36
|
+
|
|
37
|
+
Override `Length.__reduce_ex__` so `copy.deepcopy` (and `pickle`)
|
|
38
|
+
reconstruct via the new module-level `_length_reconstruct`, which uses
|
|
39
|
+
`Length._from_emu` (preserves raw EMU value). Three lines in
|
|
40
|
+
`pptx/units.py`; behaviour-preserving for every existing call site.
|
|
41
|
+
|
|
42
|
+
### Regression test
|
|
43
|
+
|
|
44
|
+
New `test_length_round_trips_through_deepcopy` in `tests/test_units.py`
|
|
45
|
+
asserts that `copy.deepcopy(Pt(28))` and `asdict({...Pt(28) / 12700...})`
|
|
46
|
+
both preserve the raw EMU value. Would have caught this bug on day one.
|
|
47
|
+
|
|
48
|
+
### Impact
|
|
49
|
+
|
|
50
|
+
Every agent-authored deck where the agent set `run.font.size = Pt(X)`
|
|
51
|
+
through the SDK was silently shipping `fontSizePt: X * 12700` to the
|
|
52
|
+
server. Existing decks may need to be regenerated by the agent or
|
|
53
|
+
manually corrected. Future agent runs against v0.1.75+ will render
|
|
54
|
+
text correctly.
|
|
55
|
+
|
|
56
|
+
The new SDK was published to PyPI as 0.1.75 and the Daytona
|
|
57
|
+
`presentation-exec` snapshot bumped to v38.
|
|
58
|
+
|
|
5
59
|
## 0.1.74
|
|
6
60
|
|
|
7
61
|
**True drop-in import-path parity.** A behavioural-fidelity audit found
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.75
|
|
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
|
|
@@ -542,6 +542,7 @@ class Client:
|
|
|
542
542
|
MasterSnapshot,
|
|
543
543
|
PlaceholderSnapshot,
|
|
544
544
|
SlideSnapshot,
|
|
545
|
+
TextFramePropertiesSnapshot,
|
|
545
546
|
ThemeHierarchySnapshot,
|
|
546
547
|
Transform,
|
|
547
548
|
)
|
|
@@ -577,6 +578,21 @@ class Client:
|
|
|
577
578
|
local_transform_override=placeholder_data.get("localTransformOverride"),
|
|
578
579
|
)
|
|
579
580
|
|
|
581
|
+
# Server stores margins under `marginLeftEmu` after any SDK update
|
|
582
|
+
# and under `marginLeft` immediately after ingest. Read both.
|
|
583
|
+
tfp_data = elem_data.get("textFrameProperties")
|
|
584
|
+
text_frame_properties: Optional[TextFramePropertiesSnapshot] = None
|
|
585
|
+
if isinstance(tfp_data, dict):
|
|
586
|
+
text_frame_properties = TextFramePropertiesSnapshot(
|
|
587
|
+
word_wrap=tfp_data.get("wordWrap"),
|
|
588
|
+
auto_size=tfp_data.get("autoSize"),
|
|
589
|
+
vertical_anchor=tfp_data.get("verticalAnchor"),
|
|
590
|
+
margin_left=tfp_data.get("marginLeftEmu", tfp_data.get("marginLeft")),
|
|
591
|
+
margin_right=tfp_data.get("marginRightEmu", tfp_data.get("marginRight")),
|
|
592
|
+
margin_top=tfp_data.get("marginTopEmu", tfp_data.get("marginTop")),
|
|
593
|
+
margin_bottom=tfp_data.get("marginBottomEmu", tfp_data.get("marginBottom")),
|
|
594
|
+
)
|
|
595
|
+
|
|
580
596
|
elements[elem_id] = ElementSnapshot(
|
|
581
597
|
id=elem_data["id"],
|
|
582
598
|
type=elem_data["type"],
|
|
@@ -596,6 +612,7 @@ class Client:
|
|
|
596
612
|
source=elem_data.get("source"),
|
|
597
613
|
table_data=elem_data.get("tableData"),
|
|
598
614
|
asset_id=elem_data.get("assetId"),
|
|
615
|
+
text_frame_properties=text_frame_properties,
|
|
599
616
|
)
|
|
600
617
|
|
|
601
618
|
theme_hierarchy_raw = data.get("themeHierarchy")
|
|
@@ -26,7 +26,7 @@ from ..chart.category import Categories
|
|
|
26
26
|
from ..dml.color import RGBColor, ColorFormat
|
|
27
27
|
from ..errors import UnsupportedFeatureError
|
|
28
28
|
from ..text import TextFrame
|
|
29
|
-
from ..typing import ElementSnapshot, ShapeId, Transform, PlaceholderSnapshot, PP_PLACEHOLDER
|
|
29
|
+
from ..typing import ElementSnapshot, ShapeId, Transform, PlaceholderSnapshot, PP_PLACEHOLDER, TextFramePropertiesSnapshot
|
|
30
30
|
from ..units import Emu, Length, ensure_emu
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
@@ -2516,6 +2516,7 @@ class Shape:
|
|
|
2516
2516
|
properties: Optional[dict[str, Any]] = None,
|
|
2517
2517
|
placeholder: Optional[PlaceholderSnapshot] = None,
|
|
2518
2518
|
source: Optional[str] = None,
|
|
2519
|
+
text_frame_properties: Optional[TextFramePropertiesSnapshot] = None,
|
|
2519
2520
|
):
|
|
2520
2521
|
self._shape_id = shape_id
|
|
2521
2522
|
self._slide = slide
|
|
@@ -2536,6 +2537,7 @@ class Shape:
|
|
|
2536
2537
|
buffer=buffer,
|
|
2537
2538
|
preview_text=preview_text,
|
|
2538
2539
|
rich_content=rich_content,
|
|
2540
|
+
text_frame_properties=text_frame_properties,
|
|
2539
2541
|
)
|
|
2540
2542
|
|
|
2541
2543
|
# Known misspelled/non-existent attributes with specific fix guidance
|
|
@@ -5159,6 +5161,7 @@ class Shapes:
|
|
|
5159
5161
|
properties=elem.properties,
|
|
5160
5162
|
placeholder=elem.placeholder,
|
|
5161
5163
|
source=elem.source,
|
|
5164
|
+
text_frame_properties=elem.text_frame_properties,
|
|
5162
5165
|
)
|
|
5163
5166
|
elif elem.type == "connector":
|
|
5164
5167
|
shape = Connector(
|
|
@@ -5170,6 +5173,7 @@ class Shapes:
|
|
|
5170
5173
|
properties=elem.properties,
|
|
5171
5174
|
placeholder=elem.placeholder,
|
|
5172
5175
|
source=elem.source,
|
|
5176
|
+
text_frame_properties=elem.text_frame_properties,
|
|
5173
5177
|
)
|
|
5174
5178
|
elif elem.type == "group":
|
|
5175
5179
|
shape = GroupShape(
|
|
@@ -5181,6 +5185,7 @@ class Shapes:
|
|
|
5181
5185
|
properties=elem.properties,
|
|
5182
5186
|
placeholder=elem.placeholder,
|
|
5183
5187
|
source=elem.source,
|
|
5188
|
+
text_frame_properties=elem.text_frame_properties,
|
|
5184
5189
|
)
|
|
5185
5190
|
else:
|
|
5186
5191
|
shape = Shape(
|
|
@@ -5193,6 +5198,7 @@ class Shapes:
|
|
|
5193
5198
|
properties=elem.properties,
|
|
5194
5199
|
placeholder=elem.placeholder,
|
|
5195
5200
|
source=elem.source,
|
|
5201
|
+
text_frame_properties=elem.text_frame_properties,
|
|
5196
5202
|
)
|
|
5197
5203
|
self._shapes.append(shape)
|
|
5198
5204
|
self._shapes_by_id[elem.id] = shape
|
|
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Iterator, Optional, Union
|
|
|
10
10
|
from ..commands import SetText, SetRunStyle, SetParagraphStyle, SetTextFrameProperties, FitText
|
|
11
11
|
from ..dml.color import RGBColor, ColorFormat
|
|
12
12
|
from ..errors import UnsupportedFeatureError
|
|
13
|
-
from ..typing import ShapeId, TextStyle
|
|
13
|
+
from ..typing import ShapeId, TextStyle, TextFramePropertiesSnapshot
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from ..batching import CommandBuffer
|
|
@@ -1328,12 +1328,36 @@ class TextFrame:
|
|
|
1328
1328
|
buffer: Optional[CommandBuffer],
|
|
1329
1329
|
preview_text: Optional[str] = None,
|
|
1330
1330
|
rich_content: Optional[dict[str, Any]] = None,
|
|
1331
|
+
text_frame_properties: Optional[TextFramePropertiesSnapshot] = None,
|
|
1331
1332
|
):
|
|
1332
1333
|
self._shape_id = shape_id
|
|
1333
1334
|
self._buffer = buffer
|
|
1334
1335
|
self._preview_text = preview_text or ""
|
|
1335
1336
|
self._paragraphs: list[Paragraph] = []
|
|
1336
1337
|
|
|
1338
|
+
# Seed internal text-frame-properties state from the source snapshot so
|
|
1339
|
+
# subsequent setter emits (which always rebroadcast the full state)
|
|
1340
|
+
# preserve the props the user hasn't touched. Without this, setting
|
|
1341
|
+
# `text_frame.margin_left = 0` would emit a SetTextFrameProperties
|
|
1342
|
+
# command without `autoSize`, the server's existing autoSize would be
|
|
1343
|
+
# the only thing keeping the box auto-fit — and it isn't there for
|
|
1344
|
+
# shapes created via AddTextBox, so the box renders without auto-fit.
|
|
1345
|
+
if text_frame_properties is not None:
|
|
1346
|
+
if text_frame_properties.word_wrap is not None:
|
|
1347
|
+
self._word_wrap = text_frame_properties.word_wrap
|
|
1348
|
+
if text_frame_properties.auto_size is not None:
|
|
1349
|
+
self._auto_size = text_frame_properties.auto_size
|
|
1350
|
+
if text_frame_properties.vertical_anchor is not None:
|
|
1351
|
+
self._vertical_anchor = text_frame_properties.vertical_anchor
|
|
1352
|
+
if text_frame_properties.margin_left is not None:
|
|
1353
|
+
self._margin_left = text_frame_properties.margin_left
|
|
1354
|
+
if text_frame_properties.margin_right is not None:
|
|
1355
|
+
self._margin_right = text_frame_properties.margin_right
|
|
1356
|
+
if text_frame_properties.margin_top is not None:
|
|
1357
|
+
self._margin_top = text_frame_properties.margin_top
|
|
1358
|
+
if text_frame_properties.margin_bottom is not None:
|
|
1359
|
+
self._margin_bottom = text_frame_properties.margin_bottom
|
|
1360
|
+
|
|
1337
1361
|
# Initialize paragraphs from rich content or plain text
|
|
1338
1362
|
if rich_content and rich_content.get("paragraphs"):
|
|
1339
1363
|
for i, para_data in enumerate(rich_content["paragraphs"]):
|
|
@@ -162,6 +162,26 @@ class PlaceholderSnapshot:
|
|
|
162
162
|
local_transform_override: Optional[bool] = None
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
@dataclass
|
|
166
|
+
class TextFramePropertiesSnapshot:
|
|
167
|
+
"""Snapshot of a text frame's structural properties (auto-size, margins, etc.).
|
|
168
|
+
|
|
169
|
+
Carries the source PPTX's <a:bodyPr> state through `from_url` so the SDK's
|
|
170
|
+
TextFrame can populate its internal `_auto_size`/`_word_wrap`/`_margin_*`
|
|
171
|
+
fields. Without this, a user setter (`text_frame.margin_left = 0`) emits a
|
|
172
|
+
SetTextFrameProperties command that omits the un-touched props, the server
|
|
173
|
+
keeps whatever it has, and round-trip is lossy.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
word_wrap: Optional[bool] = None
|
|
177
|
+
auto_size: Optional[Literal["none", "shape_to_fit_text", "text_to_fit_shape"]] = None
|
|
178
|
+
vertical_anchor: Optional[Literal["top", "middle", "bottom"]] = None
|
|
179
|
+
margin_left: Optional[int] = None
|
|
180
|
+
margin_right: Optional[int] = None
|
|
181
|
+
margin_top: Optional[int] = None
|
|
182
|
+
margin_bottom: Optional[int] = None
|
|
183
|
+
|
|
184
|
+
|
|
165
185
|
@dataclass
|
|
166
186
|
class ElementSnapshot:
|
|
167
187
|
"""Snapshot of an element's state."""
|
|
@@ -176,6 +196,7 @@ class ElementSnapshot:
|
|
|
176
196
|
source: Optional[Literal["ingested", "sdk", "layout", "master"]] = None
|
|
177
197
|
table_data: Optional[dict[str, Any]] = None
|
|
178
198
|
asset_id: Optional[str] = None
|
|
199
|
+
text_frame_properties: Optional[TextFramePropertiesSnapshot] = None
|
|
179
200
|
|
|
180
201
|
|
|
181
202
|
@dataclass
|
|
@@ -31,6 +31,17 @@ EMU_PER_CENTIPOINT = 127
|
|
|
31
31
|
EMU_PER_PX = 9525 # At 96 DPI
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def _length_reconstruct(cls: type, emu_value: int): # noqa: ANN201
|
|
35
|
+
"""Top-level reconstructor for :class:`Length` subclasses.
|
|
36
|
+
|
|
37
|
+
Used by :meth:`Length.__reduce_ex__` so that ``copy.deepcopy`` (and
|
|
38
|
+
``pickle``) round-trip a :class:`Length` subclass without going
|
|
39
|
+
through the unit-converting ``__new__``. Must be a module-level
|
|
40
|
+
function so pickle can locate it by qualified name.
|
|
41
|
+
"""
|
|
42
|
+
return cls._from_emu(emu_value)
|
|
43
|
+
|
|
44
|
+
|
|
34
45
|
class Length(int):
|
|
35
46
|
"""
|
|
36
47
|
Length expressed in English Metric Units (EMU) — base unit class.
|
|
@@ -45,6 +56,15 @@ class Length(int):
|
|
|
45
56
|
def __new__(cls, value: Union[int, float, "Length"]) -> "Length":
|
|
46
57
|
return super().__new__(cls, int(round(value)))
|
|
47
58
|
|
|
59
|
+
# ``copy.deepcopy`` (used by ``dataclasses.asdict``) reconstructs ints
|
|
60
|
+
# by invoking ``Type(int_value)``. For our subclasses that means
|
|
61
|
+
# ``Pt(355600)`` etc., which interpret the value as user-units and
|
|
62
|
+
# multiply by the unit's EMU factor → ``355600 → 4,516,120,000``.
|
|
63
|
+
# Override the pickle/reduce protocol so deep-copy round-trips via
|
|
64
|
+
# ``_from_emu`` (treats the value as raw EMU), preserving identity.
|
|
65
|
+
def __reduce_ex__(self, protocol: int): # type: ignore[override]
|
|
66
|
+
return (_length_reconstruct, (type(self), int(self)))
|
|
67
|
+
|
|
48
68
|
@property
|
|
49
69
|
def inches(self) -> float:
|
|
50
70
|
"""Return value in inches."""
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-pptx"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.75"
|
|
8
8
|
description = "Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|