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.
Files changed (175) hide show
  1. pptx2/__init__.py +152 -0
  2. pptx2/_color.py +75 -0
  3. pptx2/_slide_importer.py +597 -0
  4. pptx2/_svg.py +155 -0
  5. pptx2/_template_applier.py +292 -0
  6. pptx2/_textstyle.py +187 -0
  7. pptx2/accessibility.py +365 -0
  8. pptx2/action.py +270 -0
  9. pptx2/animation.py +2237 -0
  10. pptx2/api.py +49 -0
  11. pptx2/audit.py +258 -0
  12. pptx2/chart/__init__.py +0 -0
  13. pptx2/chart/analytics.py +381 -0
  14. pptx2/chart/axis.py +543 -0
  15. pptx2/chart/category.py +200 -0
  16. pptx2/chart/chart.py +670 -0
  17. pptx2/chart/data.py +864 -0
  18. pptx2/chart/datalabel.py +406 -0
  19. pptx2/chart/legend.py +86 -0
  20. pptx2/chart/marker.py +70 -0
  21. pptx2/chart/palettes.py +129 -0
  22. pptx2/chart/plot.py +462 -0
  23. pptx2/chart/point.py +101 -0
  24. pptx2/chart/quick_layouts.py +325 -0
  25. pptx2/chart/series.py +334 -0
  26. pptx2/chart/xlsx.py +272 -0
  27. pptx2/chart/xmlwriter.py +1845 -0
  28. pptx2/compose/__init__.py +28 -0
  29. pptx2/compose/from_spec.py +1094 -0
  30. pptx2/design/__init__.py +8 -0
  31. pptx2/design/components.py +607 -0
  32. pptx2/design/figures.py +389 -0
  33. pptx2/design/layout.py +370 -0
  34. pptx2/design/recipes.py +1967 -0
  35. pptx2/design/style.py +209 -0
  36. pptx2/design/tokens.py +915 -0
  37. pptx2/diagrams.py +754 -0
  38. pptx2/dml/__init__.py +0 -0
  39. pptx2/dml/chtfmt.py +40 -0
  40. pptx2/dml/color.py +496 -0
  41. pptx2/dml/effect.py +909 -0
  42. pptx2/dml/fill.py +691 -0
  43. pptx2/dml/line.py +287 -0
  44. pptx2/dml/picture.py +212 -0
  45. pptx2/dml/three_d.py +381 -0
  46. pptx2/enum/__init__.py +0 -0
  47. pptx2/enum/action.py +71 -0
  48. pptx2/enum/animation.py +31 -0
  49. pptx2/enum/base.py +218 -0
  50. pptx2/enum/chart.py +574 -0
  51. pptx2/enum/dml.py +740 -0
  52. pptx2/enum/lang.py +685 -0
  53. pptx2/enum/presentation.py +133 -0
  54. pptx2/enum/shapes.py +1029 -0
  55. pptx2/enum/text.py +230 -0
  56. pptx2/exc.py +42 -0
  57. pptx2/formats.py +139 -0
  58. pptx2/geometry.py +420 -0
  59. pptx2/inherit.py +109 -0
  60. pptx2/lint.py +2256 -0
  61. pptx2/math.py +177 -0
  62. pptx2/media.py +197 -0
  63. pptx2/opc/__init__.py +0 -0
  64. pptx2/opc/constants.py +332 -0
  65. pptx2/opc/oxml.py +188 -0
  66. pptx2/opc/package.py +762 -0
  67. pptx2/opc/packuri.py +109 -0
  68. pptx2/opc/serialized.py +296 -0
  69. pptx2/opc/shared.py +20 -0
  70. pptx2/opc/spec.py +45 -0
  71. pptx2/oxml/__init__.py +555 -0
  72. pptx2/oxml/action.py +53 -0
  73. pptx2/oxml/chart/__init__.py +0 -0
  74. pptx2/oxml/chart/axis.py +337 -0
  75. pptx2/oxml/chart/chart.py +481 -0
  76. pptx2/oxml/chart/datalabel.py +253 -0
  77. pptx2/oxml/chart/legend.py +72 -0
  78. pptx2/oxml/chart/marker.py +61 -0
  79. pptx2/oxml/chart/plot.py +365 -0
  80. pptx2/oxml/chart/series.py +425 -0
  81. pptx2/oxml/chart/shared.py +220 -0
  82. pptx2/oxml/coreprops.py +288 -0
  83. pptx2/oxml/dml/__init__.py +0 -0
  84. pptx2/oxml/dml/color.py +135 -0
  85. pptx2/oxml/dml/effect.py +213 -0
  86. pptx2/oxml/dml/fill.py +316 -0
  87. pptx2/oxml/dml/line.py +12 -0
  88. pptx2/oxml/dml/three_d.py +110 -0
  89. pptx2/oxml/ns.py +135 -0
  90. pptx2/oxml/presentation.py +313 -0
  91. pptx2/oxml/shapes/__init__.py +19 -0
  92. pptx2/oxml/shapes/autoshape.py +467 -0
  93. pptx2/oxml/shapes/connector.py +107 -0
  94. pptx2/oxml/shapes/graphfrm.py +347 -0
  95. pptx2/oxml/shapes/groupshape.py +329 -0
  96. pptx2/oxml/shapes/picture.py +270 -0
  97. pptx2/oxml/shapes/shared.py +577 -0
  98. pptx2/oxml/simpletypes.py +1027 -0
  99. pptx2/oxml/slide.py +563 -0
  100. pptx2/oxml/table.py +650 -0
  101. pptx2/oxml/text.py +815 -0
  102. pptx2/oxml/theme.py +36 -0
  103. pptx2/oxml/xmlchemy.py +717 -0
  104. pptx2/package.py +222 -0
  105. pptx2/parts/__init__.py +0 -0
  106. pptx2/parts/chart.py +95 -0
  107. pptx2/parts/coreprops.py +167 -0
  108. pptx2/parts/diagram.py +37 -0
  109. pptx2/parts/embeddedpackage.py +93 -0
  110. pptx2/parts/image.py +275 -0
  111. pptx2/parts/media.py +37 -0
  112. pptx2/parts/presentation.py +136 -0
  113. pptx2/parts/slide.py +371 -0
  114. pptx2/presentation.py +408 -0
  115. pptx2/py.typed +0 -0
  116. pptx2/render.py +586 -0
  117. pptx2/section.py +272 -0
  118. pptx2/shapes/__init__.py +26 -0
  119. pptx2/shapes/autoshape.py +442 -0
  120. pptx2/shapes/base.py +1078 -0
  121. pptx2/shapes/connector.py +297 -0
  122. pptx2/shapes/freeform.py +337 -0
  123. pptx2/shapes/graphfrm.py +316 -0
  124. pptx2/shapes/group.py +264 -0
  125. pptx2/shapes/picture.py +422 -0
  126. pptx2/shapes/placeholder.py +468 -0
  127. pptx2/shapes/shapetree.py +2027 -0
  128. pptx2/shared.py +82 -0
  129. pptx2/skill/SKILL.md +450 -0
  130. pptx2/skill/__init__.py +78 -0
  131. pptx2/skill/__main__.py +64 -0
  132. pptx2/skill/references/animations.md +189 -0
  133. pptx2/skill/references/basics.md +421 -0
  134. pptx2/skill/references/charts.md +254 -0
  135. pptx2/skill/references/compose.md +234 -0
  136. pptx2/skill/references/design.md +366 -0
  137. pptx2/skill/references/effects.md +249 -0
  138. pptx2/skill/references/end-to-end-deck.md +231 -0
  139. pptx2/skill/references/geometry-and-arrows.md +334 -0
  140. pptx2/skill/references/lint.md +275 -0
  141. pptx2/skill/references/math.md +86 -0
  142. pptx2/skill/references/picture-effects.md +129 -0
  143. pptx2/skill/references/render.md +151 -0
  144. pptx2/skill/references/smart-art.md +75 -0
  145. pptx2/skill/references/space-aware-authoring.md +249 -0
  146. pptx2/skill/references/tables.md +244 -0
  147. pptx2/skill/references/theme.md +127 -0
  148. pptx2/skill/references/three-d.md +109 -0
  149. pptx2/skill/references/transitions.md +100 -0
  150. pptx2/slide.py +1244 -0
  151. pptx2/smart_art.py +220 -0
  152. pptx2/spec.py +633 -0
  153. pptx2/table.py +1181 -0
  154. pptx2/table_styles.py +184 -0
  155. pptx2/templates/default.pptx +0 -0
  156. pptx2/templates/docx-icon.emf +0 -0
  157. pptx2/templates/generic-icon.emf +0 -0
  158. pptx2/templates/notes.xml +23 -0
  159. pptx2/templates/notesMaster.xml +352 -0
  160. pptx2/templates/pptx-icon.emf +0 -0
  161. pptx2/templates/theme.xml +321 -0
  162. pptx2/templates/xlsx-icon.emf +0 -0
  163. pptx2/text/__init__.py +0 -0
  164. pptx2/text/fonts.py +482 -0
  165. pptx2/text/layout.py +374 -0
  166. pptx2/text/text.py +1272 -0
  167. pptx2/theme.py +721 -0
  168. pptx2/types.py +36 -0
  169. pptx2/util.py +263 -0
  170. python_pptx2-2.13.0.dist-info/METADATA +351 -0
  171. python_pptx2-2.13.0.dist-info/RECORD +175 -0
  172. python_pptx2-2.13.0.dist-info/WHEEL +5 -0
  173. python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
  174. python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
  175. 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)