ps-python-pptx 1.1.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 (117) hide show
  1. pptx/__init__.py +84 -0
  2. pptx/action.py +270 -0
  3. pptx/api.py +49 -0
  4. pptx/chart/__init__.py +0 -0
  5. pptx/chart/axis.py +523 -0
  6. pptx/chart/category.py +200 -0
  7. pptx/chart/chart.py +280 -0
  8. pptx/chart/data.py +864 -0
  9. pptx/chart/datalabel.py +288 -0
  10. pptx/chart/legend.py +79 -0
  11. pptx/chart/marker.py +70 -0
  12. pptx/chart/plot.py +412 -0
  13. pptx/chart/point.py +101 -0
  14. pptx/chart/series.py +258 -0
  15. pptx/chart/xlsx.py +272 -0
  16. pptx/chart/xmlwriter.py +1840 -0
  17. pptx/dml/__init__.py +0 -0
  18. pptx/dml/chtfmt.py +40 -0
  19. pptx/dml/color.py +301 -0
  20. pptx/dml/effect.py +41 -0
  21. pptx/dml/fill.py +468 -0
  22. pptx/dml/line.py +100 -0
  23. pptx/enum/__init__.py +0 -0
  24. pptx/enum/action.py +71 -0
  25. pptx/enum/base.py +175 -0
  26. pptx/enum/chart.py +492 -0
  27. pptx/enum/dml.py +405 -0
  28. pptx/enum/lang.py +685 -0
  29. pptx/enum/shapes.py +1029 -0
  30. pptx/enum/text.py +371 -0
  31. pptx/exc.py +23 -0
  32. pptx/media.py +197 -0
  33. pptx/opc/__init__.py +0 -0
  34. pptx/opc/constants.py +331 -0
  35. pptx/opc/oxml.py +188 -0
  36. pptx/opc/package.py +762 -0
  37. pptx/opc/packuri.py +109 -0
  38. pptx/opc/serialized.py +296 -0
  39. pptx/opc/shared.py +20 -0
  40. pptx/opc/spec.py +44 -0
  41. pptx/oxml/__init__.py +492 -0
  42. pptx/oxml/action.py +53 -0
  43. pptx/oxml/chart/__init__.py +0 -0
  44. pptx/oxml/chart/axis.py +297 -0
  45. pptx/oxml/chart/chart.py +282 -0
  46. pptx/oxml/chart/datalabel.py +252 -0
  47. pptx/oxml/chart/legend.py +72 -0
  48. pptx/oxml/chart/marker.py +61 -0
  49. pptx/oxml/chart/plot.py +345 -0
  50. pptx/oxml/chart/series.py +254 -0
  51. pptx/oxml/chart/shared.py +219 -0
  52. pptx/oxml/coreprops.py +288 -0
  53. pptx/oxml/dml/__init__.py +0 -0
  54. pptx/oxml/dml/color.py +111 -0
  55. pptx/oxml/dml/fill.py +197 -0
  56. pptx/oxml/dml/line.py +12 -0
  57. pptx/oxml/ns.py +129 -0
  58. pptx/oxml/presentation.py +130 -0
  59. pptx/oxml/shapes/__init__.py +19 -0
  60. pptx/oxml/shapes/autoshape.py +455 -0
  61. pptx/oxml/shapes/connector.py +107 -0
  62. pptx/oxml/shapes/graphfrm.py +342 -0
  63. pptx/oxml/shapes/groupshape.py +280 -0
  64. pptx/oxml/shapes/picture.py +270 -0
  65. pptx/oxml/shapes/shared.py +523 -0
  66. pptx/oxml/simpletypes.py +740 -0
  67. pptx/oxml/slide.py +347 -0
  68. pptx/oxml/table.py +588 -0
  69. pptx/oxml/text.py +748 -0
  70. pptx/oxml/theme.py +29 -0
  71. pptx/oxml/xmlchemy.py +717 -0
  72. pptx/package.py +222 -0
  73. pptx/parts/__init__.py +0 -0
  74. pptx/parts/chart.py +95 -0
  75. pptx/parts/coreprops.py +167 -0
  76. pptx/parts/embeddedpackage.py +93 -0
  77. pptx/parts/image.py +275 -0
  78. pptx/parts/media.py +37 -0
  79. pptx/parts/presentation.py +126 -0
  80. pptx/parts/slide.py +297 -0
  81. pptx/parts/theme.py +13 -0
  82. pptx/presentation.py +113 -0
  83. pptx/py.typed +0 -0
  84. pptx/shapes/__init__.py +26 -0
  85. pptx/shapes/autoshape.py +355 -0
  86. pptx/shapes/base.py +293 -0
  87. pptx/shapes/connector.py +297 -0
  88. pptx/shapes/freeform.py +337 -0
  89. pptx/shapes/graphfrm.py +166 -0
  90. pptx/shapes/group.py +69 -0
  91. pptx/shapes/picture.py +203 -0
  92. pptx/shapes/placeholder.py +407 -0
  93. pptx/shapes/shapetree.py +1190 -0
  94. pptx/shared.py +82 -0
  95. pptx/slide.py +577 -0
  96. pptx/spec.py +632 -0
  97. pptx/table.py +520 -0
  98. pptx/templates/default.pptx +0 -0
  99. pptx/templates/docx-icon.emf +0 -0
  100. pptx/templates/generic-icon.emf +0 -0
  101. pptx/templates/notes.xml +23 -0
  102. pptx/templates/notesMaster.xml +352 -0
  103. pptx/templates/pptx-icon.emf +0 -0
  104. pptx/templates/theme.xml +321 -0
  105. pptx/templates/xlsx-icon.emf +0 -0
  106. pptx/text/__init__.py +0 -0
  107. pptx/text/fonts.py +399 -0
  108. pptx/text/layout.py +325 -0
  109. pptx/text/text.py +770 -0
  110. pptx/theme.py +135 -0
  111. pptx/types.py +36 -0
  112. pptx/util.py +284 -0
  113. ps_python_pptx-1.1.0.dist-info/METADATA +75 -0
  114. ps_python_pptx-1.1.0.dist-info/RECORD +117 -0
  115. ps_python_pptx-1.1.0.dist-info/WHEEL +5 -0
  116. ps_python_pptx-1.1.0.dist-info/licenses/LICENSE +20 -0
  117. ps_python_pptx-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,355 @@
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 pptx.dml.fill import FillFormat
10
+ from pptx.dml.line import LineFormat
11
+ from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_SHAPE_TYPE
12
+ from pptx.shapes.base import BaseShape
13
+ from pptx.spec import autoshape_types
14
+ from pptx.text.text import TextFrame
15
+ from pptx.util import lazyproperty
16
+
17
+ if TYPE_CHECKING:
18
+ from pptx.oxml.shapes.autoshape import CT_GeomGuide, CT_PresetGeometry2D, CT_Shape
19
+ from pptx.spec import AdjustmentValue
20
+ from pptx.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 `pptx.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 `pptx.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 pptx.spec.autoshape_types" % autoshape_type_id
209
+ )
210
+ # -- otherwise initialize new instance --
211
+ autoshape_type = autoshape_types[autoshape_type_id]
212
+ self._autoshape_type_id = autoshape_type_id
213
+ self._basename = autoshape_type["basename"]
214
+ self._loaded = True
215
+
216
+ @property
217
+ def autoshape_type_id(self) -> MSO_AUTO_SHAPE_TYPE:
218
+ """MSO_AUTO_SHAPE_TYPE enumeration member identifying this auto shape type."""
219
+ return self._autoshape_type_id
220
+
221
+ @property
222
+ def basename(self) -> str:
223
+ """Base of shape name for this auto shape type.
224
+
225
+ A shape name is like "Rounded Rectangle 7" and appears as an XML attribute for example at
226
+ `p:sp/p:nvSpPr/p:cNvPr{name}`. This basename value is the name less the distinguishing
227
+ integer. This value is escaped because at least one autoshape-type name includes double
228
+ quotes ('"No" Symbol').
229
+ """
230
+ return saxutils.escape(self._basename, {'"': """})
231
+
232
+ @classmethod
233
+ def default_adjustment_values(cls, prst: MSO_AUTO_SHAPE_TYPE) -> tuple[AdjustmentValue, ...]:
234
+ """Sequence of (name, value) pair adjustment value defaults for `prst` autoshape-type."""
235
+ return autoshape_types[prst]["avLst"]
236
+
237
+ @classmethod
238
+ def id_from_prst(cls, prst: str) -> MSO_AUTO_SHAPE_TYPE:
239
+ """Select auto shape type with matching `prst`.
240
+
241
+ e.g. `MSO_SHAPE.RECTANGLE` corresponding to preset geometry keyword `"rect"`.
242
+ """
243
+ return MSO_AUTO_SHAPE_TYPE.from_xml(prst)
244
+
245
+ @property
246
+ def prst(self):
247
+ """
248
+ Preset geometry identifier string for this auto shape. Used in the
249
+ `prst` attribute of `a:prstGeom` element to specify the geometry
250
+ to be used in rendering the shape, for example `'roundRect'`.
251
+ """
252
+ return MSO_AUTO_SHAPE_TYPE.to_xml(self._autoshape_type_id)
253
+
254
+
255
+ class Shape(BaseShape):
256
+ """A shape that can appear on a slide.
257
+
258
+ Corresponds to the `p:sp` element that can appear in any of the slide-type parts
259
+ (slide, slideLayout, slideMaster, notesPage, notesMaster, handoutMaster).
260
+ """
261
+
262
+ def __init__(self, sp: CT_Shape, parent: ProvidesPart):
263
+ super(Shape, self).__init__(sp, parent)
264
+ self._sp = sp
265
+
266
+ @lazyproperty
267
+ def adjustments(self) -> AdjustmentCollection:
268
+ """Read-only reference to |AdjustmentCollection| instance for this shape."""
269
+ return AdjustmentCollection(self._sp.prstGeom)
270
+
271
+ @property
272
+ def auto_shape_type(self):
273
+ """Enumeration value identifying the type of this auto shape.
274
+
275
+ Like `MSO_SHAPE.ROUNDED_RECTANGLE`. Raises |ValueError| if this shape is not an auto shape.
276
+ """
277
+ if not self._sp.is_autoshape:
278
+ raise ValueError("shape is not an auto shape")
279
+ return self._sp.prst
280
+
281
+ @lazyproperty
282
+ def fill(self):
283
+ """|FillFormat| instance for this shape.
284
+
285
+ Provides access to fill properties such as fill color.
286
+ """
287
+ return FillFormat.from_fill_parent(self._sp.spPr)
288
+
289
+ def get_or_add_ln(self):
290
+ """Return the `a:ln` element containing the line format properties XML for this shape."""
291
+ return self._sp.get_or_add_ln()
292
+
293
+ @property
294
+ def has_text_frame(self) -> bool:
295
+ """|True| if this shape can contain text. Always |True| for an AutoShape."""
296
+ return True
297
+
298
+ @lazyproperty
299
+ def line(self):
300
+ """|LineFormat| instance for this shape.
301
+
302
+ Provides access to line properties such as line color.
303
+ """
304
+ return LineFormat(self)
305
+
306
+ @property
307
+ def ln(self):
308
+ """The `a:ln` element containing the line format properties such as line color and width.
309
+
310
+ |None| if no `a:ln` element is present.
311
+ """
312
+ return self._sp.ln
313
+
314
+ @property
315
+ def shape_type(self) -> MSO_SHAPE_TYPE:
316
+ """Unique integer identifying the type of this shape, like `MSO_SHAPE_TYPE.TEXT_BOX`."""
317
+ if self.is_placeholder:
318
+ return MSO_SHAPE_TYPE.PLACEHOLDER
319
+ if self._sp.has_custom_geometry:
320
+ return MSO_SHAPE_TYPE.FREEFORM
321
+ if self._sp.is_autoshape:
322
+ return MSO_SHAPE_TYPE.AUTO_SHAPE
323
+ if self._sp.is_textbox:
324
+ return MSO_SHAPE_TYPE.TEXT_BOX
325
+ raise NotImplementedError("Shape instance of unrecognized shape type")
326
+
327
+ @property
328
+ def text(self) -> str:
329
+ """Read/write. Text in shape as a single string.
330
+
331
+ The returned string will contain a newline character (`"\\n"`) separating each paragraph
332
+ and a vertical-tab (`"\\v"`) character for each line break (soft carriage return) in the
333
+ shape's text.
334
+
335
+ Assignment to `text` replaces any text previously contained in the shape, along with any
336
+ paragraph or font formatting applied to it. A newline character (`"\\n"`) in the assigned
337
+ text causes a new paragraph to be started. A vertical-tab (`"\\v"`) character in the
338
+ assigned text causes a line-break (soft carriage-return) to be inserted. (The vertical-tab
339
+ character appears in clipboard text copied from PowerPoint as its str encoding of
340
+ line-breaks.)
341
+ """
342
+ return self.text_frame.text
343
+
344
+ @text.setter
345
+ def text(self, text: str):
346
+ self.text_frame.text = text
347
+
348
+ @property
349
+ def text_frame(self):
350
+ """|TextFrame| instance for this shape.
351
+
352
+ Contains the text of the shape and provides access to text formatting properties.
353
+ """
354
+ txBody = self._sp.get_or_add_txBody()
355
+ return TextFrame(txBody, self)
pptx/shapes/base.py ADDED
@@ -0,0 +1,293 @@
1
+ """Base shape-related objects such as BaseShape."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, cast
6
+
7
+ from pptx.action import ActionSetting
8
+ from pptx.dml.effect import ShadowFormat
9
+ from pptx.shared import ElementProxy
10
+ from pptx.util import lazyproperty
11
+
12
+ if TYPE_CHECKING:
13
+ from pptx.enum.shapes import MSO_SHAPE_TYPE, PP_PLACEHOLDER
14
+ from pptx.oxml.shapes import ShapeElement
15
+ from pptx.oxml.shapes.shared import CT_Placeholder
16
+ from pptx.parts.slide import BaseSlidePart
17
+ from pptx.types import ProvidesPart
18
+ from pptx.util import Length
19
+
20
+
21
+ class BaseShape(object):
22
+ """Base class for shape objects.
23
+
24
+ Subclasses include |Shape|, |Picture|, and |GraphicFrame|.
25
+ """
26
+
27
+ def __init__(self, shape_elm: ShapeElement, parent: ProvidesPart):
28
+ super().__init__()
29
+ self._element = shape_elm
30
+ self._parent = parent
31
+
32
+ def __eq__(self, other: object) -> bool:
33
+ """|True| if this shape object proxies the same element as *other*.
34
+
35
+ Equality for proxy objects is defined as referring to the same XML element, whether or not
36
+ they are the same proxy object instance.
37
+ """
38
+ if not isinstance(other, BaseShape):
39
+ return False
40
+ return self._element is other._element
41
+
42
+ def __ne__(self, other: object) -> bool:
43
+ if not isinstance(other, BaseShape):
44
+ return True
45
+ return self._element is not other._element
46
+
47
+ def send_to_back(self) -> None:
48
+ """Move this shape behind its siblings, within its current slide or group."""
49
+ siblings = list(self._element.getparent().iter_shape_elms())
50
+ if siblings and siblings[0] is not self._element:
51
+ siblings[0].addprevious(self._element)
52
+
53
+ def bring_to_front(self) -> None:
54
+ """Move this shape in front of its siblings, within its current slide or group."""
55
+ siblings = list(self._element.getparent().iter_shape_elms())
56
+ if siblings and siblings[-1] is not self._element:
57
+ siblings[-1].addnext(self._element)
58
+
59
+ @lazyproperty
60
+ def click_action(self) -> ActionSetting:
61
+ """|ActionSetting| instance providing access to click behaviors.
62
+
63
+ Click behaviors are hyperlink-like behaviors including jumping to a hyperlink (web page)
64
+ or to another slide in the presentation. The click action is that defined on the overall
65
+ shape, not a run of text within the shape. An |ActionSetting| object is always returned,
66
+ even when no click behavior is defined on the shape.
67
+ """
68
+ cNvPr = self._element._nvXxPr.cNvPr # pyright: ignore[reportPrivateUsage]
69
+ return ActionSetting(cNvPr, self)
70
+
71
+ @property
72
+ def element(self) -> ShapeElement:
73
+ """`lxml` element for this shape, e.g. a CT_Shape instance.
74
+
75
+ Note that manipulating this element improperly can produce an invalid presentation file.
76
+ Make sure you know what you're doing if you use this to change the underlying XML.
77
+ """
78
+ return self._element
79
+
80
+ @property
81
+ def has_chart(self) -> bool:
82
+ """|True| if this shape is a graphic frame containing a chart object.
83
+
84
+ |False| otherwise. When |True|, the chart object can be accessed using the ``.chart``
85
+ property.
86
+ """
87
+ # This implementation is unconditionally False, the True version is
88
+ # on GraphicFrame subclass.
89
+ return False
90
+
91
+ @property
92
+ def has_table(self) -> bool:
93
+ """|True| if this shape is a graphic frame containing a table object.
94
+
95
+ |False| otherwise. When |True|, the table object can be accessed using the ``.table``
96
+ property.
97
+ """
98
+ # This implementation is unconditionally False, the True version is
99
+ # on GraphicFrame subclass.
100
+ return False
101
+
102
+ @property
103
+ def has_text_frame(self) -> bool:
104
+ """|True| if this shape can contain text."""
105
+ # overridden on Shape to return True. Only <p:sp> has text frame
106
+ return False
107
+
108
+ @property
109
+ def height(self) -> Length:
110
+ """Read/write. Integer distance between top and bottom extents of shape in EMUs."""
111
+ return self._element.cy
112
+
113
+ @height.setter
114
+ def height(self, value: Length):
115
+ self._element.cy = value
116
+
117
+ @property
118
+ def is_placeholder(self) -> bool:
119
+ """True if this shape is a placeholder.
120
+
121
+ A shape is a placeholder if it has a <p:ph> element.
122
+ """
123
+ return self._element.has_ph_elm
124
+
125
+ @property
126
+ def left(self) -> Length:
127
+ """Integer distance of the left edge of this shape from the left edge of the slide.
128
+
129
+ Read/write. Expressed in English Metric Units (EMU)
130
+ """
131
+ return self._element.x
132
+
133
+ @left.setter
134
+ def left(self, value: Length):
135
+ self._element.x = value
136
+
137
+ @property
138
+ def name(self) -> str:
139
+ """Name of this shape, e.g. 'Picture 7'."""
140
+ return self._element.shape_name
141
+
142
+ @name.setter
143
+ def name(self, value: str):
144
+ self._element._nvXxPr.cNvPr.name = value # pyright: ignore[reportPrivateUsage]
145
+
146
+ @property
147
+ def part(self) -> BaseSlidePart:
148
+ """The package part containing this shape.
149
+
150
+ A |BaseSlidePart| subclass in this case. Access to a slide part should only be required if
151
+ you are extending the behavior of |pp| API objects.
152
+ """
153
+ return cast("BaseSlidePart", self._parent.part)
154
+
155
+ @property
156
+ def placeholder_format(self) -> _PlaceholderFormat:
157
+ """Provides access to placeholder-specific properties such as placeholder type.
158
+
159
+ Raises |ValueError| on access if the shape is not a placeholder.
160
+ """
161
+ ph = self._element.ph
162
+ if ph is None:
163
+ raise ValueError("shape is not a placeholder")
164
+ return _PlaceholderFormat(ph)
165
+
166
+ @property
167
+ def rotation(self) -> float:
168
+ """Degrees of clockwise rotation.
169
+
170
+ Read/write float. Negative values can be assigned to indicate counter-clockwise rotation,
171
+ e.g. assigning -45.0 will change setting to 315.0.
172
+ """
173
+ return self._element.rot
174
+
175
+ @rotation.setter
176
+ def rotation(self, value: float):
177
+ self._element.rot = value
178
+
179
+ @lazyproperty
180
+ def shadow(self) -> ShadowFormat:
181
+ """|ShadowFormat| object providing access to shadow for this shape.
182
+
183
+ A |ShadowFormat| object is always returned, even when no shadow is
184
+ explicitly defined on this shape (i.e. it inherits its shadow
185
+ behavior).
186
+ """
187
+ return ShadowFormat(self._element.spPr)
188
+
189
+ @property
190
+ def shape_id(self) -> int:
191
+ """Read-only positive integer identifying this shape.
192
+
193
+ The id of a shape is unique among all shapes on a slide.
194
+ """
195
+ return self._element.shape_id
196
+
197
+ @property
198
+ def shape_type(self) -> MSO_SHAPE_TYPE:
199
+ """A member of MSO_SHAPE_TYPE classifying this shape by type.
200
+
201
+ Like ``MSO_SHAPE_TYPE.CHART``. Must be implemented by subclasses.
202
+ """
203
+ raise NotImplementedError(f"{type(self).__name__} does not implement `.shape_type`")
204
+
205
+ @property
206
+ def top(self) -> Length:
207
+ """Distance from the top edge of the slide to the top edge of this shape.
208
+
209
+ Read/write. Expressed in English Metric Units (EMU)
210
+ """
211
+ return self._element.y
212
+
213
+ @top.setter
214
+ def top(self, value: Length):
215
+ self._element.y = value
216
+
217
+ @property
218
+ def width(self) -> Length:
219
+ """Distance between left and right extents of this shape.
220
+
221
+ Read/write. Expressed in English Metric Units (EMU).
222
+ """
223
+ return self._element.cx
224
+
225
+ @width.setter
226
+ def width(self, value: Length):
227
+ self._element.cx = value
228
+
229
+ @property
230
+ def alt_text(self) -> str | None:
231
+ """Alternative-text description. None means absent, while an empty string is explicit."""
232
+ return self._element._nvXxPr.cNvPr.get("descr")
233
+
234
+ @alt_text.setter
235
+ def alt_text(self, value: str | None):
236
+ if value is not None and not isinstance(value, str):
237
+ raise TypeError("alternative text must be a string or None")
238
+ properties = self._element._nvXxPr.cNvPr
239
+ if value is None:
240
+ properties.attrib.pop("descr", None)
241
+ else:
242
+ properties.set("descr", value)
243
+
244
+ @alt_text.deleter
245
+ def alt_text(self):
246
+ self.alt_text = None
247
+
248
+ @property
249
+ def alt_text_title(self) -> str | None:
250
+ """Alternative-text title. None means absent, while an empty string is explicit."""
251
+ return self._element._nvXxPr.cNvPr.get("title")
252
+
253
+ @alt_text_title.setter
254
+ def alt_text_title(self, value: str | None):
255
+ if value is not None and not isinstance(value, str):
256
+ raise TypeError("alternative text must be a string or None")
257
+ properties = self._element._nvXxPr.cNvPr
258
+ if value is None:
259
+ properties.attrib.pop("title", None)
260
+ else:
261
+ properties.set("title", value)
262
+
263
+ @alt_text_title.deleter
264
+ def alt_text_title(self):
265
+ self.alt_text_title = None
266
+
267
+ class _PlaceholderFormat(ElementProxy):
268
+ """Provides properties specific to placeholders, such as the placeholder type.
269
+
270
+ Accessed via the :attr:`~.BaseShape.placeholder_format` property of a placeholder shape,
271
+ """
272
+
273
+ def __init__(self, element: CT_Placeholder):
274
+ super().__init__(element)
275
+ self._ph = element
276
+
277
+ @property
278
+ def element(self) -> CT_Placeholder:
279
+ """The `p:ph` element proxied by this object."""
280
+ return self._ph
281
+
282
+ @property
283
+ def idx(self) -> int:
284
+ """Integer placeholder 'idx' attribute."""
285
+ return self._ph.idx
286
+
287
+ @property
288
+ def type(self) -> PP_PLACEHOLDER:
289
+ """Placeholder type.
290
+
291
+ A member of the :ref:`PpPlaceholderType` enumeration, e.g. PP_PLACEHOLDER.CHART
292
+ """
293
+ return self._ph.type