python-pptx2 2.13.0__py3-none-any.whl
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.
- pptx2/__init__.py +152 -0
- pptx2/_color.py +75 -0
- pptx2/_slide_importer.py +597 -0
- pptx2/_svg.py +155 -0
- pptx2/_template_applier.py +292 -0
- pptx2/_textstyle.py +187 -0
- pptx2/accessibility.py +365 -0
- pptx2/action.py +270 -0
- pptx2/animation.py +2237 -0
- pptx2/api.py +49 -0
- pptx2/audit.py +258 -0
- pptx2/chart/__init__.py +0 -0
- pptx2/chart/analytics.py +381 -0
- pptx2/chart/axis.py +543 -0
- pptx2/chart/category.py +200 -0
- pptx2/chart/chart.py +670 -0
- pptx2/chart/data.py +864 -0
- pptx2/chart/datalabel.py +406 -0
- pptx2/chart/legend.py +86 -0
- pptx2/chart/marker.py +70 -0
- pptx2/chart/palettes.py +129 -0
- pptx2/chart/plot.py +462 -0
- pptx2/chart/point.py +101 -0
- pptx2/chart/quick_layouts.py +325 -0
- pptx2/chart/series.py +334 -0
- pptx2/chart/xlsx.py +272 -0
- pptx2/chart/xmlwriter.py +1845 -0
- pptx2/compose/__init__.py +28 -0
- pptx2/compose/from_spec.py +1094 -0
- pptx2/design/__init__.py +8 -0
- pptx2/design/components.py +607 -0
- pptx2/design/figures.py +389 -0
- pptx2/design/layout.py +370 -0
- pptx2/design/recipes.py +1967 -0
- pptx2/design/style.py +209 -0
- pptx2/design/tokens.py +915 -0
- pptx2/diagrams.py +754 -0
- pptx2/dml/__init__.py +0 -0
- pptx2/dml/chtfmt.py +40 -0
- pptx2/dml/color.py +496 -0
- pptx2/dml/effect.py +909 -0
- pptx2/dml/fill.py +691 -0
- pptx2/dml/line.py +287 -0
- pptx2/dml/picture.py +212 -0
- pptx2/dml/three_d.py +381 -0
- pptx2/enum/__init__.py +0 -0
- pptx2/enum/action.py +71 -0
- pptx2/enum/animation.py +31 -0
- pptx2/enum/base.py +218 -0
- pptx2/enum/chart.py +574 -0
- pptx2/enum/dml.py +740 -0
- pptx2/enum/lang.py +685 -0
- pptx2/enum/presentation.py +133 -0
- pptx2/enum/shapes.py +1029 -0
- pptx2/enum/text.py +230 -0
- pptx2/exc.py +42 -0
- pptx2/formats.py +139 -0
- pptx2/geometry.py +420 -0
- pptx2/inherit.py +109 -0
- pptx2/lint.py +2256 -0
- pptx2/math.py +177 -0
- pptx2/media.py +197 -0
- pptx2/opc/__init__.py +0 -0
- pptx2/opc/constants.py +332 -0
- pptx2/opc/oxml.py +188 -0
- pptx2/opc/package.py +762 -0
- pptx2/opc/packuri.py +109 -0
- pptx2/opc/serialized.py +296 -0
- pptx2/opc/shared.py +20 -0
- pptx2/opc/spec.py +45 -0
- pptx2/oxml/__init__.py +555 -0
- pptx2/oxml/action.py +53 -0
- pptx2/oxml/chart/__init__.py +0 -0
- pptx2/oxml/chart/axis.py +337 -0
- pptx2/oxml/chart/chart.py +481 -0
- pptx2/oxml/chart/datalabel.py +253 -0
- pptx2/oxml/chart/legend.py +72 -0
- pptx2/oxml/chart/marker.py +61 -0
- pptx2/oxml/chart/plot.py +365 -0
- pptx2/oxml/chart/series.py +425 -0
- pptx2/oxml/chart/shared.py +220 -0
- pptx2/oxml/coreprops.py +288 -0
- pptx2/oxml/dml/__init__.py +0 -0
- pptx2/oxml/dml/color.py +135 -0
- pptx2/oxml/dml/effect.py +213 -0
- pptx2/oxml/dml/fill.py +316 -0
- pptx2/oxml/dml/line.py +12 -0
- pptx2/oxml/dml/three_d.py +110 -0
- pptx2/oxml/ns.py +135 -0
- pptx2/oxml/presentation.py +313 -0
- pptx2/oxml/shapes/__init__.py +19 -0
- pptx2/oxml/shapes/autoshape.py +467 -0
- pptx2/oxml/shapes/connector.py +107 -0
- pptx2/oxml/shapes/graphfrm.py +347 -0
- pptx2/oxml/shapes/groupshape.py +329 -0
- pptx2/oxml/shapes/picture.py +270 -0
- pptx2/oxml/shapes/shared.py +577 -0
- pptx2/oxml/simpletypes.py +1027 -0
- pptx2/oxml/slide.py +563 -0
- pptx2/oxml/table.py +650 -0
- pptx2/oxml/text.py +815 -0
- pptx2/oxml/theme.py +36 -0
- pptx2/oxml/xmlchemy.py +717 -0
- pptx2/package.py +222 -0
- pptx2/parts/__init__.py +0 -0
- pptx2/parts/chart.py +95 -0
- pptx2/parts/coreprops.py +167 -0
- pptx2/parts/diagram.py +37 -0
- pptx2/parts/embeddedpackage.py +93 -0
- pptx2/parts/image.py +275 -0
- pptx2/parts/media.py +37 -0
- pptx2/parts/presentation.py +136 -0
- pptx2/parts/slide.py +371 -0
- pptx2/presentation.py +408 -0
- pptx2/py.typed +0 -0
- pptx2/render.py +586 -0
- pptx2/section.py +272 -0
- pptx2/shapes/__init__.py +26 -0
- pptx2/shapes/autoshape.py +442 -0
- pptx2/shapes/base.py +1078 -0
- pptx2/shapes/connector.py +297 -0
- pptx2/shapes/freeform.py +337 -0
- pptx2/shapes/graphfrm.py +316 -0
- pptx2/shapes/group.py +264 -0
- pptx2/shapes/picture.py +422 -0
- pptx2/shapes/placeholder.py +468 -0
- pptx2/shapes/shapetree.py +2027 -0
- pptx2/shared.py +82 -0
- pptx2/skill/SKILL.md +450 -0
- pptx2/skill/__init__.py +78 -0
- pptx2/skill/__main__.py +64 -0
- pptx2/skill/references/animations.md +189 -0
- pptx2/skill/references/basics.md +421 -0
- pptx2/skill/references/charts.md +254 -0
- pptx2/skill/references/compose.md +234 -0
- pptx2/skill/references/design.md +366 -0
- pptx2/skill/references/effects.md +249 -0
- pptx2/skill/references/end-to-end-deck.md +231 -0
- pptx2/skill/references/geometry-and-arrows.md +334 -0
- pptx2/skill/references/lint.md +275 -0
- pptx2/skill/references/math.md +86 -0
- pptx2/skill/references/picture-effects.md +129 -0
- pptx2/skill/references/render.md +151 -0
- pptx2/skill/references/smart-art.md +75 -0
- pptx2/skill/references/space-aware-authoring.md +249 -0
- pptx2/skill/references/tables.md +244 -0
- pptx2/skill/references/theme.md +127 -0
- pptx2/skill/references/three-d.md +109 -0
- pptx2/skill/references/transitions.md +100 -0
- pptx2/slide.py +1244 -0
- pptx2/smart_art.py +220 -0
- pptx2/spec.py +633 -0
- pptx2/table.py +1181 -0
- pptx2/table_styles.py +184 -0
- pptx2/templates/default.pptx +0 -0
- pptx2/templates/docx-icon.emf +0 -0
- pptx2/templates/generic-icon.emf +0 -0
- pptx2/templates/notes.xml +23 -0
- pptx2/templates/notesMaster.xml +352 -0
- pptx2/templates/pptx-icon.emf +0 -0
- pptx2/templates/theme.xml +321 -0
- pptx2/templates/xlsx-icon.emf +0 -0
- pptx2/text/__init__.py +0 -0
- pptx2/text/fonts.py +482 -0
- pptx2/text/layout.py +374 -0
- pptx2/text/text.py +1272 -0
- pptx2/theme.py +721 -0
- pptx2/types.py +36 -0
- pptx2/util.py +263 -0
- python_pptx2-2.13.0.dist-info/METADATA +351 -0
- python_pptx2-2.13.0.dist-info/RECORD +175 -0
- python_pptx2-2.13.0.dist-info/WHEEL +5 -0
- python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
- python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
- python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Autoshape-related objects such as Shape and Adjustment."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from numbers import Number
|
|
6
|
+
from typing import TYPE_CHECKING, Iterable
|
|
7
|
+
from xml.sax import saxutils
|
|
8
|
+
|
|
9
|
+
from pptx2.dml.fill import FillFormat
|
|
10
|
+
from pptx2.dml.line import LineFormat
|
|
11
|
+
from pptx2.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_SHAPE_TYPE
|
|
12
|
+
from pptx2.shapes.base import BaseShape
|
|
13
|
+
from pptx2.spec import autoshape_types
|
|
14
|
+
from pptx2.text.text import TextFrame
|
|
15
|
+
from pptx2.util import Emu, Length, lazyproperty
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from pptx2.oxml.shapes.autoshape import CT_GeomGuide, CT_PresetGeometry2D, CT_Shape
|
|
19
|
+
from pptx2.spec import AdjustmentValue
|
|
20
|
+
from pptx2.types import ProvidesPart
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Adjustment:
|
|
24
|
+
"""An adjustment value for an autoshape.
|
|
25
|
+
|
|
26
|
+
An adjustment value corresponds to the position of an adjustment handle on an auto shape.
|
|
27
|
+
Adjustment handles are the small yellow diamond-shaped handles that appear on certain auto
|
|
28
|
+
shapes and allow the outline of the shape to be adjusted. For example, a rounded rectangle has
|
|
29
|
+
an adjustment handle that allows the radius of its corner rounding to be adjusted.
|
|
30
|
+
|
|
31
|
+
Values are |float| and generally range from 0.0 to 1.0, although the value can be negative or
|
|
32
|
+
greater than 1.0 in certain circumstances.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, name: str, def_val: int, actual: int | None = None):
|
|
36
|
+
super(Adjustment, self).__init__()
|
|
37
|
+
self.name = name
|
|
38
|
+
self.def_val = def_val
|
|
39
|
+
self.actual = actual
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def effective_value(self) -> float:
|
|
43
|
+
"""Read/write |float| representing normalized adjustment value for this adjustment.
|
|
44
|
+
|
|
45
|
+
Actual values are a large-ish integer expressed in shape coordinates, nominally between 0
|
|
46
|
+
and 100,000. The effective value is normalized to a corresponding value nominally between
|
|
47
|
+
0.0 and 1.0. Intuitively this represents the proportion of the width or height of the shape
|
|
48
|
+
at which the adjustment value is located from its starting point. For simple shapes such as
|
|
49
|
+
a rounded rectangle, this intuitive correspondence holds. For more complicated shapes and
|
|
50
|
+
at more extreme shape proportions (e.g. width is much greater than height), the value can
|
|
51
|
+
become negative or greater than 1.0.
|
|
52
|
+
"""
|
|
53
|
+
raw_value = self.actual if self.actual is not None else self.def_val
|
|
54
|
+
return self._normalize(raw_value)
|
|
55
|
+
|
|
56
|
+
@effective_value.setter
|
|
57
|
+
def effective_value(self, value: float):
|
|
58
|
+
if not isinstance(value, Number):
|
|
59
|
+
raise ValueError(f"adjustment value must be numeric, got {repr(value)}")
|
|
60
|
+
self.actual = self._denormalize(value)
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _denormalize(value: float) -> int:
|
|
64
|
+
"""Return integer corresponding to normalized `raw_value` on unit basis of 100,000.
|
|
65
|
+
|
|
66
|
+
See Adjustment.normalize for additional details.
|
|
67
|
+
"""
|
|
68
|
+
return int(value * 100000.0)
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _normalize(raw_value: int) -> float:
|
|
72
|
+
"""Return normalized value for `raw_value`.
|
|
73
|
+
|
|
74
|
+
A normalized value is a |float| between 0.0 and 1.0 for nominal raw values between 0 and
|
|
75
|
+
100,000. Raw values less than 0 and greater than 100,000 are valid and return values
|
|
76
|
+
calculated on the same unit basis of 100,000.
|
|
77
|
+
"""
|
|
78
|
+
return raw_value / 100000.0
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def val(self) -> int:
|
|
82
|
+
"""Denormalized effective value.
|
|
83
|
+
|
|
84
|
+
Expressed in shape coordinates, this is suitable for using in the XML.
|
|
85
|
+
"""
|
|
86
|
+
return self.actual if self.actual is not None else self.def_val
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AdjustmentCollection:
|
|
90
|
+
"""Sequence of |Adjustment| instances for an auto shape.
|
|
91
|
+
|
|
92
|
+
Each represents an available adjustment for a shape of its type. Supports `len()` and indexed
|
|
93
|
+
access, e.g. `shape.adjustments[1] = 0.15`.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, prstGeom: CT_PresetGeometry2D):
|
|
97
|
+
super(AdjustmentCollection, self).__init__()
|
|
98
|
+
self._adjustments_ = self._initialized_adjustments(prstGeom)
|
|
99
|
+
self._prstGeom = prstGeom
|
|
100
|
+
|
|
101
|
+
def __getitem__(self, idx: int) -> float:
|
|
102
|
+
"""Provides indexed access, (e.g. 'adjustments[9]')."""
|
|
103
|
+
return self._adjustments_[idx].effective_value
|
|
104
|
+
|
|
105
|
+
def __setitem__(self, idx: int, value: float):
|
|
106
|
+
"""Provides item assignment via an indexed expression, e.g. `adjustments[9] = 999.9`.
|
|
107
|
+
|
|
108
|
+
Causes all adjustment values in collection to be written to the XML.
|
|
109
|
+
"""
|
|
110
|
+
self._adjustments_[idx].effective_value = value
|
|
111
|
+
self._rewrite_guides()
|
|
112
|
+
|
|
113
|
+
def _initialized_adjustments(self, prstGeom: CT_PresetGeometry2D | None) -> list[Adjustment]:
|
|
114
|
+
"""Return an initialized list of adjustment values based on the contents of `prstGeom`."""
|
|
115
|
+
if prstGeom is None:
|
|
116
|
+
return []
|
|
117
|
+
davs = AutoShapeType.default_adjustment_values(prstGeom.prst)
|
|
118
|
+
adjustments = [Adjustment(name, def_val) for name, def_val in davs]
|
|
119
|
+
self._update_adjustments_with_actuals(adjustments, prstGeom.gd_lst)
|
|
120
|
+
return adjustments
|
|
121
|
+
|
|
122
|
+
def _rewrite_guides(self):
|
|
123
|
+
"""Write `a:gd` elements to the XML, one for each adjustment value.
|
|
124
|
+
|
|
125
|
+
Any existing guide elements are overwritten.
|
|
126
|
+
"""
|
|
127
|
+
guides = [(adj.name, adj.val) for adj in self._adjustments_]
|
|
128
|
+
self._prstGeom.rewrite_guides(guides)
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _update_adjustments_with_actuals(
|
|
132
|
+
adjustments: Iterable[Adjustment], guides: Iterable[CT_GeomGuide]
|
|
133
|
+
):
|
|
134
|
+
"""Update |Adjustment| instances in `adjustments` with actual values held in `guides`.
|
|
135
|
+
|
|
136
|
+
`guides` is a list of `a:gd` elements. Guides with a name that does not match an adjustment
|
|
137
|
+
object are skipped.
|
|
138
|
+
"""
|
|
139
|
+
adjustments_by_name = dict((adj.name, adj) for adj in adjustments)
|
|
140
|
+
for gd in guides:
|
|
141
|
+
name = gd.name
|
|
142
|
+
actual = int(gd.fmla[4:])
|
|
143
|
+
try:
|
|
144
|
+
adjustment = adjustments_by_name[name]
|
|
145
|
+
except KeyError:
|
|
146
|
+
continue
|
|
147
|
+
adjustment.actual = actual
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def _adjustments(self) -> tuple[Adjustment, ...]:
|
|
152
|
+
"""Sequence of |Adjustment| objects contained in collection."""
|
|
153
|
+
return tuple(self._adjustments_)
|
|
154
|
+
|
|
155
|
+
def __len__(self):
|
|
156
|
+
"""Implement built-in function len()"""
|
|
157
|
+
return len(self._adjustments_)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class AutoShapeType:
|
|
161
|
+
"""Provides access to metadata for an auto-shape of type identified by `autoshape_type_id`.
|
|
162
|
+
|
|
163
|
+
Instances are cached, so no more than one instance for a particular auto shape type is in
|
|
164
|
+
memory.
|
|
165
|
+
|
|
166
|
+
Instances provide the following attributes:
|
|
167
|
+
|
|
168
|
+
.. attribute:: autoshape_type_id
|
|
169
|
+
|
|
170
|
+
Integer uniquely identifying this auto shape type. Corresponds to a
|
|
171
|
+
value in `pptx2.constants.MSO` like `MSO_SHAPE.ROUNDED_RECTANGLE`.
|
|
172
|
+
|
|
173
|
+
.. attribute:: basename
|
|
174
|
+
|
|
175
|
+
Base part of shape name for auto shapes of this type, e.g. `Rounded
|
|
176
|
+
Rectangle` becomes `Rounded Rectangle 99` when the distinguishing
|
|
177
|
+
integer is added to the shape name.
|
|
178
|
+
|
|
179
|
+
.. attribute:: prst
|
|
180
|
+
|
|
181
|
+
String identifier for this auto shape type used in the `a:prstGeom`
|
|
182
|
+
element.
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
_instances: dict[MSO_AUTO_SHAPE_TYPE, AutoShapeType] = {}
|
|
187
|
+
|
|
188
|
+
def __new__(cls, autoshape_type_id: MSO_AUTO_SHAPE_TYPE) -> AutoShapeType:
|
|
189
|
+
"""Only create new instance on first call for content_type.
|
|
190
|
+
|
|
191
|
+
After that, use cached instance.
|
|
192
|
+
"""
|
|
193
|
+
# -- if there's not a matching instance in the cache, create one --
|
|
194
|
+
if autoshape_type_id not in cls._instances:
|
|
195
|
+
inst = super(AutoShapeType, cls).__new__(cls)
|
|
196
|
+
cls._instances[autoshape_type_id] = inst
|
|
197
|
+
# -- return the instance; note that __init__() gets called either way --
|
|
198
|
+
return cls._instances[autoshape_type_id]
|
|
199
|
+
|
|
200
|
+
def __init__(self, autoshape_type_id: MSO_AUTO_SHAPE_TYPE):
|
|
201
|
+
"""Initialize attributes from constant values in `pptx2.spec`."""
|
|
202
|
+
# -- skip loading if this instance is from the cache --
|
|
203
|
+
if hasattr(self, "_loaded"):
|
|
204
|
+
return
|
|
205
|
+
# -- raise on bad autoshape_type_id --
|
|
206
|
+
if autoshape_type_id not in autoshape_types:
|
|
207
|
+
raise KeyError(
|
|
208
|
+
"no autoshape type with id '%s' in pptx2.spec.autoshape_types"
|
|
209
|
+
% autoshape_type_id
|
|
210
|
+
)
|
|
211
|
+
# -- otherwise initialize new instance --
|
|
212
|
+
autoshape_type = autoshape_types[autoshape_type_id]
|
|
213
|
+
self._autoshape_type_id = autoshape_type_id
|
|
214
|
+
self._basename = autoshape_type["basename"]
|
|
215
|
+
self._loaded = True
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def autoshape_type_id(self) -> MSO_AUTO_SHAPE_TYPE:
|
|
219
|
+
"""MSO_AUTO_SHAPE_TYPE enumeration member identifying this auto shape type."""
|
|
220
|
+
return self._autoshape_type_id
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def basename(self) -> str:
|
|
224
|
+
"""Base of shape name for this auto shape type.
|
|
225
|
+
|
|
226
|
+
A shape name is like "Rounded Rectangle 7" and appears as an XML attribute for example at
|
|
227
|
+
`p:sp/p:nvSpPr/p:cNvPr{name}`. This basename value is the name less the distinguishing
|
|
228
|
+
integer. This value is escaped because at least one autoshape-type name includes double
|
|
229
|
+
quotes ('"No" Symbol').
|
|
230
|
+
"""
|
|
231
|
+
return saxutils.escape(self._basename, {'"': """})
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def default_adjustment_values(cls, prst: MSO_AUTO_SHAPE_TYPE) -> tuple[AdjustmentValue, ...]:
|
|
235
|
+
"""Sequence of (name, value) pair adjustment value defaults for `prst` autoshape-type."""
|
|
236
|
+
return autoshape_types[prst]["avLst"]
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def id_from_prst(cls, prst: str) -> MSO_AUTO_SHAPE_TYPE:
|
|
240
|
+
"""Select auto shape type with matching `prst`.
|
|
241
|
+
|
|
242
|
+
e.g. `MSO_SHAPE.RECTANGLE` corresponding to preset geometry keyword `"rect"`.
|
|
243
|
+
"""
|
|
244
|
+
return MSO_AUTO_SHAPE_TYPE.from_xml(prst)
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def prst(self):
|
|
248
|
+
"""
|
|
249
|
+
Preset geometry identifier string for this auto shape. Used in the
|
|
250
|
+
`prst` attribute of `a:prstGeom` element to specify the geometry
|
|
251
|
+
to be used in rendering the shape, for example `'roundRect'`.
|
|
252
|
+
"""
|
|
253
|
+
return MSO_AUTO_SHAPE_TYPE.to_xml(self._autoshape_type_id)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class Shape(BaseShape):
|
|
257
|
+
"""A shape that can appear on a slide.
|
|
258
|
+
|
|
259
|
+
Corresponds to the `p:sp` element that can appear in any of the slide-type parts
|
|
260
|
+
(slide, slideLayout, slideMaster, notesPage, notesMaster, handoutMaster).
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
def __init__(self, sp: CT_Shape, parent: ProvidesPart):
|
|
264
|
+
super(Shape, self).__init__(sp, parent)
|
|
265
|
+
self._sp = sp
|
|
266
|
+
|
|
267
|
+
@lazyproperty
|
|
268
|
+
def adjustments(self) -> AdjustmentCollection:
|
|
269
|
+
"""Read-only reference to |AdjustmentCollection| instance for this shape."""
|
|
270
|
+
return AdjustmentCollection(self._sp.prstGeom)
|
|
271
|
+
|
|
272
|
+
# Preset geometries whose first adjustment is a corner radius expressed as
|
|
273
|
+
# a fraction of the shorter side (``ss`` in the OOXML shape guides). Only
|
|
274
|
+
# ``adjustments[0]`` is written by `corner_radius`, so the second corner
|
|
275
|
+
# pair of the two-radius geometries stays under explicit
|
|
276
|
+
# ``adjustments[1]`` control.
|
|
277
|
+
_CORNER_RADIUS_GEOMS = frozenset(
|
|
278
|
+
("roundRect", "round1Rect", "round2DiagRect", "round2SameRect")
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def corner_radius(self) -> Length:
|
|
283
|
+
"""Corner radius of a rounded-rectangle auto shape, as a |Length|.
|
|
284
|
+
|
|
285
|
+
Read/write. OOXML stores this as `adjustments[0]`, a *fraction* of the
|
|
286
|
+
shorter side of the shape — so a "6pt radius" is a different fraction on
|
|
287
|
+
every differently-sized card. This property does the conversion, letting
|
|
288
|
+
corner radius be specified the way designers spec it::
|
|
289
|
+
|
|
290
|
+
card.corner_radius = Pt(6)
|
|
291
|
+
card.corner_radius.pt # -> 6.0
|
|
292
|
+
|
|
293
|
+
Reads report the radius as *rendered*. Each of these geometries pins its
|
|
294
|
+
adjustment with ``pin 0 adj 50000``, so a shape carrying an out-of-range
|
|
295
|
+
value — set through ``adjustments[0]`` or authored elsewhere — draws at
|
|
296
|
+
the nearest legal radius, and this property says so rather than
|
|
297
|
+
reporting a negative radius or one bigger than the shape.
|
|
298
|
+
|
|
299
|
+
Raises |ValueError| for a shape whose geometry has no corner-radius
|
|
300
|
+
adjustment (anything other than a rounded rectangle), when the shape has
|
|
301
|
+
no width/height yet, or when the assigned radius exceeds half the shorter
|
|
302
|
+
side — the maximum a preset rounded rectangle can express.
|
|
303
|
+
"""
|
|
304
|
+
extent = self._corner_radius_extent # validates geometry before indexing
|
|
305
|
+
pinned = min(max(self.adjustments[0], 0.0), 0.5)
|
|
306
|
+
return Emu(int(round(pinned * extent)))
|
|
307
|
+
|
|
308
|
+
@corner_radius.setter
|
|
309
|
+
def corner_radius(self, value: Length | int):
|
|
310
|
+
extent = self._corner_radius_extent
|
|
311
|
+
radius = int(value)
|
|
312
|
+
if radius < 0:
|
|
313
|
+
raise ValueError(f"corner radius must be non-negative, got {Emu(radius).pt}pt")
|
|
314
|
+
max_radius = extent / 2
|
|
315
|
+
if radius > max_radius:
|
|
316
|
+
raise ValueError(
|
|
317
|
+
f"corner radius of {Emu(radius).pt:.4g}pt exceeds half the shorter side of "
|
|
318
|
+
f"this shape ({Emu(int(max_radius)).pt:.4g}pt), the maximum a preset rounded "
|
|
319
|
+
f"rectangle can express; use a smaller radius or a larger shape"
|
|
320
|
+
)
|
|
321
|
+
if extent == 0:
|
|
322
|
+
# A collapsed shape (zero width or height) can only express a zero
|
|
323
|
+
# radius, and the guard above has already rejected anything larger.
|
|
324
|
+
# Accepting it keeps the setter consistent with the getter, which
|
|
325
|
+
# reports 0 for the same shape, and lets a shape be sized after it
|
|
326
|
+
# is styled.
|
|
327
|
+
self.adjustments[0] = 0.0
|
|
328
|
+
return
|
|
329
|
+
# Adjustment values are stored as integers on a 100,000 unit basis and
|
|
330
|
+
# `Adjustment._denormalize` *truncates*, so hand it a value already
|
|
331
|
+
# nudged past the integer it should land on — otherwise `Pt(6)` comes
|
|
332
|
+
# back as 5.999pt.
|
|
333
|
+
self.adjustments[0] = (round(radius / extent * 100000) + 0.5) / 100000.0
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def _corner_radius_extent(self) -> float:
|
|
337
|
+
"""The shorter side of this shape in EMU, the basis for a corner radius.
|
|
338
|
+
|
|
339
|
+
Raises |ValueError| when this shape's geometry has no corner-radius
|
|
340
|
+
adjustment or its extents aren't known yet.
|
|
341
|
+
"""
|
|
342
|
+
prstGeom = self._sp.prstGeom
|
|
343
|
+
prst = None if prstGeom is None else prstGeom.prst
|
|
344
|
+
if prst is None or str(getattr(prst, "xml_value", prst)) not in self._CORNER_RADIUS_GEOMS:
|
|
345
|
+
raise ValueError(
|
|
346
|
+
"corner_radius is only defined for rounded-rectangle auto shapes "
|
|
347
|
+
"(MSO_SHAPE.ROUNDED_RECTANGLE, ROUND_1_RECTANGLE, ROUND_2_SAME_RECTANGLE, "
|
|
348
|
+
f"ROUND_2_DIAG_RECTANGLE); this shape's geometry is {prst!r}"
|
|
349
|
+
)
|
|
350
|
+
width, height = self.width, self.height
|
|
351
|
+
if width is None or height is None:
|
|
352
|
+
raise ValueError(
|
|
353
|
+
"corner_radius requires a shape with a known width and height; "
|
|
354
|
+
"set shape.width and shape.height first"
|
|
355
|
+
)
|
|
356
|
+
return float(min(int(width), int(height)))
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def auto_shape_type(self):
|
|
360
|
+
"""Enumeration value identifying the type of this auto shape.
|
|
361
|
+
|
|
362
|
+
Like `MSO_SHAPE.ROUNDED_RECTANGLE`. Raises |ValueError| if this shape is not an auto shape.
|
|
363
|
+
"""
|
|
364
|
+
if not self._sp.is_autoshape:
|
|
365
|
+
raise ValueError("shape is not an auto shape")
|
|
366
|
+
return self._sp.prst
|
|
367
|
+
|
|
368
|
+
@lazyproperty
|
|
369
|
+
def fill(self):
|
|
370
|
+
"""|FillFormat| instance for this shape.
|
|
371
|
+
|
|
372
|
+
Provides access to fill properties such as fill color.
|
|
373
|
+
"""
|
|
374
|
+
return FillFormat.from_fill_parent(self._sp.spPr)
|
|
375
|
+
|
|
376
|
+
def get_or_add_ln(self):
|
|
377
|
+
"""Return the `a:ln` element containing the line format properties XML for this shape."""
|
|
378
|
+
return self._sp.get_or_add_ln()
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def has_text_frame(self) -> bool:
|
|
382
|
+
"""|True| if this shape can contain text. Always |True| for an AutoShape."""
|
|
383
|
+
return True
|
|
384
|
+
|
|
385
|
+
@lazyproperty
|
|
386
|
+
def line(self):
|
|
387
|
+
"""|LineFormat| instance for this shape.
|
|
388
|
+
|
|
389
|
+
Provides access to line properties such as line color.
|
|
390
|
+
"""
|
|
391
|
+
return LineFormat(self)
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def ln(self):
|
|
395
|
+
"""The `a:ln` element containing the line format properties such as line color and width.
|
|
396
|
+
|
|
397
|
+
|None| if no `a:ln` element is present.
|
|
398
|
+
"""
|
|
399
|
+
return self._sp.ln
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def shape_type(self) -> MSO_SHAPE_TYPE:
|
|
403
|
+
"""Unique integer identifying the type of this shape, like `MSO_SHAPE_TYPE.TEXT_BOX`."""
|
|
404
|
+
if self.is_placeholder:
|
|
405
|
+
return MSO_SHAPE_TYPE.PLACEHOLDER
|
|
406
|
+
if self._sp.has_custom_geometry:
|
|
407
|
+
return MSO_SHAPE_TYPE.FREEFORM
|
|
408
|
+
if self._sp.is_autoshape:
|
|
409
|
+
return MSO_SHAPE_TYPE.AUTO_SHAPE
|
|
410
|
+
if self._sp.is_textbox:
|
|
411
|
+
return MSO_SHAPE_TYPE.TEXT_BOX
|
|
412
|
+
raise NotImplementedError("Shape instance of unrecognized shape type")
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def text(self) -> str:
|
|
416
|
+
"""Read/write. Text in shape as a single string.
|
|
417
|
+
|
|
418
|
+
The returned string will contain a newline character (`"\\n"`) separating each paragraph
|
|
419
|
+
and a vertical-tab (`"\\v"`) character for each line break (soft carriage return) in the
|
|
420
|
+
shape's text.
|
|
421
|
+
|
|
422
|
+
Assignment to `text` replaces any text previously contained in the shape, along with any
|
|
423
|
+
paragraph or font formatting applied to it. A newline character (`"\\n"`) in the assigned
|
|
424
|
+
text causes a new paragraph to be started. A vertical-tab (`"\\v"`) character in the
|
|
425
|
+
assigned text causes a line-break (soft carriage-return) to be inserted. (The vertical-tab
|
|
426
|
+
character appears in clipboard text copied from PowerPoint as its str encoding of
|
|
427
|
+
line-breaks.)
|
|
428
|
+
"""
|
|
429
|
+
return self.text_frame.text
|
|
430
|
+
|
|
431
|
+
@text.setter
|
|
432
|
+
def text(self, text: str):
|
|
433
|
+
self.text_frame.text = text
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def text_frame(self):
|
|
437
|
+
"""|TextFrame| instance for this shape.
|
|
438
|
+
|
|
439
|
+
Contains the text of the shape and provides access to text formatting properties.
|
|
440
|
+
"""
|
|
441
|
+
txBody = self._sp.get_or_add_txBody()
|
|
442
|
+
return TextFrame(txBody, self)
|