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,1190 @@
1
+ """The shape tree, the structure that holds a slide's shapes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import os
7
+ from typing import IO, TYPE_CHECKING, Callable, Iterable, Iterator, cast
8
+
9
+ from pptx.enum.shapes import PP_PLACEHOLDER, PROG_ID
10
+ from pptx.media import SPEAKER_IMAGE_BYTES, Video
11
+ from pptx.opc.constants import CONTENT_TYPE as CT
12
+ from pptx.oxml.ns import qn
13
+ from pptx.oxml.shapes.autoshape import CT_Shape
14
+ from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame
15
+ from pptx.oxml.shapes.picture import CT_Picture
16
+ from pptx.oxml.simpletypes import ST_Direction
17
+ from pptx.shapes.autoshape import AutoShapeType, Shape
18
+ from pptx.shapes.base import BaseShape
19
+ from pptx.shapes.connector import Connector
20
+ from pptx.shapes.freeform import FreeformBuilder
21
+ from pptx.shapes.graphfrm import GraphicFrame
22
+ from pptx.shapes.group import GroupShape
23
+ from pptx.shapes.picture import Movie, Picture
24
+ from pptx.shapes.placeholder import (
25
+ ChartPlaceholder,
26
+ LayoutPlaceholder,
27
+ MasterPlaceholder,
28
+ NotesSlidePlaceholder,
29
+ PicturePlaceholder,
30
+ PlaceholderGraphicFrame,
31
+ PlaceholderPicture,
32
+ SlidePlaceholder,
33
+ TablePlaceholder,
34
+ )
35
+ from pptx.shared import ParentedElementProxy
36
+ from pptx.util import Emu, lazyproperty
37
+
38
+ if TYPE_CHECKING:
39
+ from pptx.chart.chart import Chart
40
+ from pptx.chart.data import ChartData
41
+ from pptx.enum.chart import XL_CHART_TYPE
42
+ from pptx.enum.shapes import MSO_CONNECTOR_TYPE, MSO_SHAPE
43
+ from pptx.oxml.shapes import ShapeElement
44
+ from pptx.oxml.shapes.connector import CT_Connector
45
+ from pptx.oxml.shapes.groupshape import CT_GroupShape
46
+ from pptx.parts.image import ImagePart
47
+ from pptx.parts.slide import SlidePart
48
+ from pptx.slide import Slide, SlideLayout
49
+ from pptx.types import ProvidesPart
50
+ from pptx.util import Length
51
+
52
+ # +-- _BaseShapes
53
+ # | |
54
+ # | +-- _BaseGroupShapes
55
+ # | | |
56
+ # | | +-- GroupShapes
57
+ # | | |
58
+ # | | +-- SlideShapes
59
+ # | |
60
+ # | +-- LayoutShapes
61
+ # | |
62
+ # | +-- MasterShapes
63
+ # | |
64
+ # | +-- NotesSlideShapes
65
+ # | |
66
+ # | +-- BasePlaceholders
67
+ # | |
68
+ # | +-- LayoutPlaceholders
69
+ # | |
70
+ # | +-- MasterPlaceholders
71
+ # | |
72
+ # | +-- NotesSlidePlaceholders
73
+ # |
74
+ # +-- SlidePlaceholders
75
+
76
+
77
+ class _BaseShapes(ParentedElementProxy):
78
+ """Base class for a shape collection appearing in a slide-type object.
79
+
80
+ Subclasses include Slide, SlideLayout, and SlideMaster. Provides common methods.
81
+ """
82
+
83
+ def __init__(self, spTree: CT_GroupShape, parent: ProvidesPart):
84
+ super(_BaseShapes, self).__init__(spTree, parent)
85
+ self._spTree = spTree
86
+ self._cached_max_shape_id = None
87
+
88
+ def __getitem__(self, idx: int) -> BaseShape:
89
+ """Return shape at `idx` in sequence, e.g. `shapes[2]`."""
90
+ shape_elms = list(self._iter_member_elms())
91
+ try:
92
+ shape_elm = shape_elms[idx]
93
+ except IndexError:
94
+ raise IndexError("shape index out of range")
95
+ return self._shape_factory(shape_elm)
96
+
97
+ def __iter__(self) -> Iterator[BaseShape]:
98
+ """Generate a reference to each shape in the collection, in sequence."""
99
+ for shape_elm in self._iter_member_elms():
100
+ yield self._shape_factory(shape_elm)
101
+
102
+ def __len__(self) -> int:
103
+ """Return count of shapes in this shape tree.
104
+
105
+ A group shape contributes 1 to the total, without regard to the number of shapes contained
106
+ in the group.
107
+ """
108
+ shape_elms = list(self._iter_member_elms())
109
+ return len(shape_elms)
110
+
111
+ def clone_placeholder(self, placeholder: LayoutPlaceholder) -> None:
112
+ """Add a new placeholder shape based on `placeholder`."""
113
+ sp = placeholder.element
114
+ ph_type, orient, sz, idx = (sp.ph_type, sp.ph_orient, sp.ph_sz, sp.ph_idx)
115
+ id_ = self._next_shape_id
116
+ name = self._next_ph_name(ph_type, id_, orient)
117
+ self._spTree.add_placeholder(id_, name, ph_type, orient, sz, idx)
118
+
119
+ def ph_basename(self, ph_type: PP_PLACEHOLDER) -> str:
120
+ """Return the base name for a placeholder of `ph_type` in this shape collection.
121
+
122
+ There is some variance between slide types, for example a notes slide uses a different
123
+ name for the body placeholder, so this method can be overriden by subclasses.
124
+ """
125
+ return {
126
+ PP_PLACEHOLDER.BITMAP: "ClipArt Placeholder",
127
+ PP_PLACEHOLDER.BODY: "Text Placeholder",
128
+ PP_PLACEHOLDER.CENTER_TITLE: "Title",
129
+ PP_PLACEHOLDER.CHART: "Chart Placeholder",
130
+ PP_PLACEHOLDER.DATE: "Date Placeholder",
131
+ PP_PLACEHOLDER.FOOTER: "Footer Placeholder",
132
+ PP_PLACEHOLDER.HEADER: "Header Placeholder",
133
+ PP_PLACEHOLDER.MEDIA_CLIP: "Media Placeholder",
134
+ PP_PLACEHOLDER.OBJECT: "Content Placeholder",
135
+ PP_PLACEHOLDER.ORG_CHART: "SmartArt Placeholder",
136
+ PP_PLACEHOLDER.PICTURE: "Picture Placeholder",
137
+ PP_PLACEHOLDER.SLIDE_NUMBER: "Slide Number Placeholder",
138
+ PP_PLACEHOLDER.SUBTITLE: "Subtitle",
139
+ PP_PLACEHOLDER.TABLE: "Table Placeholder",
140
+ PP_PLACEHOLDER.TITLE: "Title",
141
+ }[ph_type]
142
+
143
+ @property
144
+ def turbo_add_enabled(self) -> bool:
145
+ """True if "turbo-add" mode is enabled. Read/Write.
146
+
147
+ EXPERIMENTAL: This feature can radically improve performance when adding large numbers
148
+ (hundreds of shapes) to a slide. It works by caching the last shape ID used and
149
+ incrementing that value to assign the next shape id. This avoids repeatedly searching all
150
+ shape ids in the slide each time a new ID is required.
151
+
152
+ Performance is not noticeably improved for a slide with a relatively small number of
153
+ shapes, but because the search time rises with the square of the shape count, this option
154
+ can be useful for optimizing generation of a slide composed of many shapes.
155
+
156
+ Shape-id collisions can occur (causing a repair error on load) if more than one |Slide|
157
+ object is used to interact with the same slide in the presentation. Note that the |Slides|
158
+ collection creates a new |Slide| object each time a slide is accessed (e.g. `slide =
159
+ prs.slides[0]`, so you must be careful to limit use to a single |Slide| object.
160
+ """
161
+ return self._cached_max_shape_id is not None
162
+
163
+ @turbo_add_enabled.setter
164
+ def turbo_add_enabled(self, value: bool):
165
+ enable = bool(value)
166
+ self._cached_max_shape_id = self._spTree.max_shape_id if enable else None
167
+
168
+ @staticmethod
169
+ def _is_member_elm(shape_elm: ShapeElement) -> bool:
170
+ """Return true if `shape_elm` represents a member of this collection, False otherwise."""
171
+ return True
172
+
173
+ def _iter_member_elms(self) -> Iterator[ShapeElement]:
174
+ """Generate each child of the `p:spTree` element that corresponds to a shape.
175
+
176
+ Items appear in XML document order.
177
+ """
178
+ for shape_elm in self._spTree.iter_shape_elms():
179
+ if self._is_member_elm(shape_elm):
180
+ yield shape_elm
181
+
182
+ def _next_ph_name(self, ph_type: PP_PLACEHOLDER, id: int, orient: str) -> str:
183
+ """Next unique placeholder name for placeholder shape of type `ph_type`.
184
+
185
+ Usually will be standard placeholder root name suffixed with id-1, e.g.
186
+ _next_ph_name(ST_PlaceholderType.TBL, 4, 'horz') ==> 'Table Placeholder 3'. The number is
187
+ incremented as necessary to make the name unique within the collection. If `orient` is
188
+ `'vert'`, the placeholder name is prefixed with `'Vertical '`.
189
+ """
190
+ basename = self.ph_basename(ph_type)
191
+
192
+ # prefix rootname with 'Vertical ' if orient is 'vert'
193
+ if orient == ST_Direction.VERT:
194
+ basename = "Vertical %s" % basename
195
+
196
+ # increment numpart as necessary to make name unique
197
+ numpart = id - 1
198
+ names = self._spTree.xpath("//p:cNvPr/@name")
199
+ while True:
200
+ name = "%s %d" % (basename, numpart)
201
+ if name not in names:
202
+ break
203
+ numpart += 1
204
+
205
+ return name
206
+
207
+ @property
208
+ def _next_shape_id(self) -> int:
209
+ """Return a unique shape id suitable for use with a new shape.
210
+
211
+ The returned id is 1 greater than the maximum shape id used so far. In practice, the
212
+ minimum id is 2 because the spTree element is always assigned id="1".
213
+ """
214
+ # ---presence of cached-max-shape-id indicates turbo mode is on---
215
+ if self._cached_max_shape_id is not None:
216
+ self._cached_max_shape_id += 1
217
+ return self._cached_max_shape_id
218
+
219
+ return self._spTree.max_shape_id + 1
220
+
221
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
222
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
223
+ return BaseShapeFactory(shape_elm, self)
224
+
225
+
226
+ class _BaseGroupShapes(_BaseShapes):
227
+ """Base class for shape-trees that can add shapes."""
228
+
229
+ part: SlidePart # pyright: ignore[reportIncompatibleMethodOverride]
230
+ _element: CT_GroupShape
231
+
232
+ def __init__(self, grpSp: CT_GroupShape, parent: ProvidesPart):
233
+ super(_BaseGroupShapes, self).__init__(grpSp, parent)
234
+ self._grpSp = grpSp
235
+
236
+ def add_chart(
237
+ self,
238
+ chart_type: XL_CHART_TYPE,
239
+ x: Length,
240
+ y: Length,
241
+ cx: Length,
242
+ cy: Length,
243
+ chart_data: ChartData,
244
+ ) -> Chart:
245
+ """Add a new chart of `chart_type` to the slide.
246
+
247
+ The chart is positioned at (`x`, `y`), has size (`cx`, `cy`), and depicts `chart_data`.
248
+ `chart_type` is one of the :ref:`XlChartType` enumeration values. `chart_data` is a
249
+ |ChartData| object populated with the categories and series values for the chart.
250
+
251
+ Note that a |GraphicFrame| shape object is returned, not the |Chart| object contained in
252
+ that graphic frame shape. The chart object may be accessed using the :attr:`chart`
253
+ property of the returned |GraphicFrame| object.
254
+ """
255
+ rId = self.part.add_chart_part(chart_type, chart_data)
256
+ graphicFrame = self._add_chart_graphicFrame(rId, x, y, cx, cy)
257
+ self._recalculate_extents()
258
+ return cast("Chart", self._shape_factory(graphicFrame))
259
+
260
+ def add_connector(
261
+ self,
262
+ connector_type: MSO_CONNECTOR_TYPE,
263
+ begin_x: Length,
264
+ begin_y: Length,
265
+ end_x: Length,
266
+ end_y: Length,
267
+ ) -> Connector:
268
+ """Add a newly created connector shape to the end of this shape tree.
269
+
270
+ `connector_type` is a member of the :ref:`MsoConnectorType` enumeration and the end-point
271
+ values are specified as EMU values. The returned connector is of type `connector_type` and
272
+ has begin and end points as specified.
273
+ """
274
+ cxnSp = self._add_cxnSp(connector_type, begin_x, begin_y, end_x, end_y)
275
+ self._recalculate_extents()
276
+ return cast(Connector, self._shape_factory(cxnSp))
277
+
278
+ def add_group_shape(self, shapes: Iterable[BaseShape] = ()) -> GroupShape:
279
+ """Return a |GroupShape| object newly appended to this shape tree.
280
+
281
+ The group shape is empty and must be populated with shapes using methods on its shape
282
+ tree, available on its `.shapes` property. The position and extents of the group shape are
283
+ determined by the shapes it contains; its position and extents are recalculated each time
284
+ a shape is added to it.
285
+ """
286
+ shapes = tuple(shapes)
287
+ grpSp = self._element.add_grpSp()
288
+ for shape in shapes:
289
+ grpSp.insert_element_before(
290
+ shape._element, "p:extLst" # pyright: ignore[reportPrivateUsage]
291
+ )
292
+ if shapes:
293
+ grpSp.recalculate_extents()
294
+ return cast(GroupShape, self._shape_factory(grpSp))
295
+
296
+ def add_ole_object(
297
+ self,
298
+ object_file: str | IO[bytes],
299
+ prog_id: str,
300
+ left: Length,
301
+ top: Length,
302
+ width: Length | None = None,
303
+ height: Length | None = None,
304
+ icon_file: str | IO[bytes] | None = None,
305
+ icon_width: Length | None = None,
306
+ icon_height: Length | None = None,
307
+ ) -> GraphicFrame:
308
+ """Return newly-created GraphicFrame shape embedding `object_file`.
309
+
310
+ The returned graphic-frame shape contains `object_file` as an embedded OLE object. It is
311
+ displayed as an icon at `left`, `top` with size `width`, `height`. `width` and `height`
312
+ may be omitted when `prog_id` is a member of `PROG_ID`, in which case the default icon
313
+ size is used. This is advised for best appearance where applicable because it avoids an
314
+ icon with a "stretched" appearance.
315
+
316
+ `object_file` may either be a str path to a file or file-like object (such as
317
+ `io.BytesIO`) containing the bytes of the object to be embedded (such as an Excel file).
318
+
319
+ `prog_id` can be either a member of `pptx.enum.shapes.PROG_ID` or a str value like
320
+ `"Adobe.Exchange.7"` determined by inspecting the XML generated by PowerPoint for an
321
+ object of the desired type.
322
+
323
+ `icon_file` may either be a str path to an image file or a file-like object containing the
324
+ image. The image provided will be displayed in lieu of the OLE object; double-clicking on
325
+ the image opens the object (subject to operating-system limitations). The image file can
326
+ be any supported image file. Those produced by PowerPoint itself are generally EMF and can
327
+ be harvested from a PPTX package that embeds such an object. PNG and JPG also work fine.
328
+
329
+ `icon_width` and `icon_height` are `Length` values (e.g. Emu() or Inches()) that describe
330
+ the size of the icon image within the shape. These should be omitted unless a custom
331
+ `icon_file` is provided. The dimensions must be discovered by inspecting the XML.
332
+ Automatic resizing of the OLE-object shape can occur when the icon is double-clicked if
333
+ these values are not as set by PowerPoint. This behavior may only manifest in the Windows
334
+ version of PowerPoint.
335
+ """
336
+ graphicFrame = _OleObjectElementCreator.graphicFrame(
337
+ self,
338
+ self._next_shape_id,
339
+ object_file,
340
+ prog_id,
341
+ left,
342
+ top,
343
+ width,
344
+ height,
345
+ icon_file,
346
+ icon_width,
347
+ icon_height,
348
+ )
349
+ self._spTree.append(graphicFrame)
350
+ self._recalculate_extents()
351
+ return cast(GraphicFrame, self._shape_factory(graphicFrame))
352
+
353
+ def add_picture(
354
+ self,
355
+ image_file: str | IO[bytes],
356
+ left: Length,
357
+ top: Length,
358
+ width: Length | None = None,
359
+ height: Length | None = None,
360
+ ) -> Picture:
361
+ """Add picture shape displaying image in `image_file`.
362
+
363
+ `image_file` can be either a path to a file (a string) or a file-like object. The picture
364
+ is positioned with its top-left corner at (`top`, `left`). If `width` and `height` are
365
+ both |None|, the native size of the image is used. If only one of `width` or `height` is
366
+ used, the unspecified dimension is calculated to preserve the aspect ratio of the image.
367
+ If both are specified, the picture is stretched to fit, without regard to its native
368
+ aspect ratio.
369
+ """
370
+ image_part, rId = self.part.get_or_add_image_part(image_file)
371
+ pic = self._add_pic_from_image_part(image_part, rId, left, top, width, height)
372
+ self._recalculate_extents()
373
+ return cast(Picture, self._shape_factory(pic))
374
+
375
+ def add_shape(
376
+ self, autoshape_type_id: MSO_SHAPE, left: Length, top: Length, width: Length, height: Length
377
+ ) -> Shape:
378
+ """Return new |Shape| object appended to this shape tree.
379
+
380
+ `autoshape_type_id` is a member of :ref:`MsoAutoShapeType` e.g. `MSO_SHAPE.RECTANGLE`
381
+ specifying the type of shape to be added. The remaining arguments specify the new shape's
382
+ position and size.
383
+ """
384
+ autoshape_type = AutoShapeType(autoshape_type_id)
385
+ sp = self._add_sp(autoshape_type, left, top, width, height)
386
+ self._recalculate_extents()
387
+ return cast(Shape, self._shape_factory(sp))
388
+
389
+ def add_textbox(self, left: Length, top: Length, width: Length, height: Length) -> Shape:
390
+ """Return newly added text box shape appended to this shape tree.
391
+
392
+ The text box is of the specified size, located at the specified position on the slide.
393
+ """
394
+ sp = self._add_textbox_sp(left, top, width, height)
395
+ self._recalculate_extents()
396
+ return cast(Shape, self._shape_factory(sp))
397
+
398
+ def build_freeform(
399
+ self, start_x: float = 0, start_y: float = 0, scale: tuple[float, float] | float = 1.0
400
+ ) -> FreeformBuilder:
401
+ """Return |FreeformBuilder| object to specify a freeform shape.
402
+
403
+ The optional `start_x` and `start_y` arguments specify the starting pen position in local
404
+ coordinates. They will be rounded to the nearest integer before use and each default to
405
+ zero.
406
+
407
+ The optional `scale` argument specifies the size of local coordinates proportional to
408
+ slide coordinates (EMU). If the vertical scale is different than the horizontal scale
409
+ (local coordinate units are "rectangular"), a pair of numeric values can be provided as
410
+ the `scale` argument, e.g. `scale=(1.0, 2.0)`. In this case the first number is
411
+ interpreted as the horizontal (X) scale and the second as the vertical (Y) scale.
412
+
413
+ A convenient method for calculating scale is to divide a |Length| object by an equivalent
414
+ count of local coordinate units, e.g. `scale = Inches(1)/1000` for 1000 local units per
415
+ inch.
416
+ """
417
+ x_scale, y_scale = scale if isinstance(scale, tuple) else (scale, scale)
418
+
419
+ return FreeformBuilder.new(self, start_x, start_y, x_scale, y_scale)
420
+
421
+ def index(self, shape: BaseShape) -> int:
422
+ """Return the index of `shape` in this sequence.
423
+
424
+ Raises |ValueError| if `shape` is not in the collection.
425
+ """
426
+ shape_elms = list(self._element.iter_shape_elms())
427
+ return shape_elms.index(shape.element)
428
+
429
+ def _add_chart_graphicFrame(
430
+ self, rId: str, x: Length, y: Length, cx: Length, cy: Length
431
+ ) -> CT_GraphicalObjectFrame:
432
+ """Return new `p:graphicFrame` element appended to this shape tree.
433
+
434
+ The `p:graphicFrame` element has the specified position and size and refers to the chart
435
+ part identified by `rId`.
436
+ """
437
+ shape_id = self._next_shape_id
438
+ name = "Chart %d" % (shape_id - 1)
439
+ graphicFrame = CT_GraphicalObjectFrame.new_chart_graphicFrame(
440
+ shape_id, name, rId, x, y, cx, cy
441
+ )
442
+ self._spTree.append(graphicFrame)
443
+ return graphicFrame
444
+
445
+ def _add_cxnSp(
446
+ self,
447
+ connector_type: MSO_CONNECTOR_TYPE,
448
+ begin_x: Length,
449
+ begin_y: Length,
450
+ end_x: Length,
451
+ end_y: Length,
452
+ ) -> CT_Connector:
453
+ """Return a newly-added `p:cxnSp` element as specified.
454
+
455
+ The `p:cxnSp` element is for a connector of `connector_type` beginning at (`begin_x`,
456
+ `begin_y`) and extending to (`end_x`, `end_y`).
457
+ """
458
+ id_ = self._next_shape_id
459
+ name = "Connector %d" % (id_ - 1)
460
+
461
+ flipH, flipV = begin_x > end_x, begin_y > end_y
462
+ x, y = min(begin_x, end_x), min(begin_y, end_y)
463
+ cx, cy = abs(end_x - begin_x), abs(end_y - begin_y)
464
+
465
+ return self._element.add_cxnSp(id_, name, connector_type, x, y, cx, cy, flipH, flipV)
466
+
467
+ def _add_pic_from_image_part(
468
+ self,
469
+ image_part: ImagePart,
470
+ rId: str,
471
+ x: Length,
472
+ y: Length,
473
+ cx: Length | None,
474
+ cy: Length | None,
475
+ ) -> CT_Picture:
476
+ """Return a newly appended `p:pic` element as specified.
477
+
478
+ The `p:pic` element displays the image in `image_part` with size and position specified by
479
+ `x`, `y`, `cx`, and `cy`. The element is appended to the shape tree, causing it to be
480
+ displayed first in z-order on the slide.
481
+ """
482
+ id_ = self._next_shape_id
483
+ scaled_cx, scaled_cy = image_part.scale(cx, cy)
484
+ name = "Picture %d" % (id_ - 1)
485
+ desc = image_part.desc
486
+ pic = self._grpSp.add_pic(id_, name, desc, rId, x, y, scaled_cx, scaled_cy)
487
+ return pic
488
+
489
+ def _add_sp(
490
+ self, autoshape_type: AutoShapeType, x: Length, y: Length, cx: Length, cy: Length
491
+ ) -> CT_Shape:
492
+ """Return newly-added `p:sp` element as specified.
493
+
494
+ `p:sp` element is of `autoshape_type` at position (`x`, `y`) and of size (`cx`, `cy`).
495
+ """
496
+ id_ = self._next_shape_id
497
+ name = "%s %d" % (autoshape_type.basename, id_ - 1)
498
+ sp = self._grpSp.add_autoshape(id_, name, autoshape_type.prst, x, y, cx, cy)
499
+ return sp
500
+
501
+ def _add_textbox_sp(self, x: Length, y: Length, cx: Length, cy: Length) -> CT_Shape:
502
+ """Return newly-appended textbox `p:sp` element.
503
+
504
+ Element has position (`x`, `y`) and size (`cx`, `cy`).
505
+ """
506
+ id_ = self._next_shape_id
507
+ name = "TextBox %d" % (id_ - 1)
508
+ sp = self._spTree.add_textbox(id_, name, x, y, cx, cy)
509
+ return sp
510
+
511
+ def _recalculate_extents(self) -> None:
512
+ """Adjust position and size to incorporate all contained shapes.
513
+
514
+ This would typically be called when a contained shape is added, removed, or its position
515
+ or size updated.
516
+ """
517
+ # ---default behavior is to do nothing, GroupShapes overrides to
518
+ # produce the distinctive behavior of groups and subgroups.---
519
+ pass
520
+
521
+
522
+ class GroupShapes(_BaseGroupShapes):
523
+ """The sequence of child shapes belonging to a group shape.
524
+
525
+ Note that this collection can itself contain a group shape, making this part of a recursive,
526
+ tree data structure (acyclic graph).
527
+ """
528
+
529
+ def _recalculate_extents(self) -> None:
530
+ """Adjust position and size to incorporate all contained shapes.
531
+
532
+ This would typically be called when a contained shape is added, removed, or its position
533
+ or size updated.
534
+ """
535
+ self._grpSp.recalculate_extents()
536
+
537
+
538
+ class SlideShapes(_BaseGroupShapes):
539
+ """Sequence of shapes appearing on a slide.
540
+
541
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
542
+ Supports indexed access, len(), index(), and iteration.
543
+ """
544
+
545
+ parent: Slide # pyright: ignore[reportIncompatibleMethodOverride]
546
+
547
+ def add_movie(
548
+ self,
549
+ movie_file: str | IO[bytes],
550
+ left: Length,
551
+ top: Length,
552
+ width: Length,
553
+ height: Length,
554
+ poster_frame_image: str | IO[bytes] | None = None,
555
+ mime_type: str = CT.VIDEO,
556
+ ) -> GraphicFrame:
557
+ """Return newly added movie shape displaying video in `movie_file`.
558
+
559
+ **EXPERIMENTAL.** This method has important limitations:
560
+
561
+ * The size must be specified; no auto-scaling such as that provided by :meth:`add_picture`
562
+ is performed.
563
+ * The MIME type of the video file should be specified, e.g. 'video/mp4'. The provided
564
+ video file is not interrogated for its type. The MIME type `video/unknown` is used by
565
+ default (and works fine in tests as of this writing).
566
+ * A poster frame image must be provided, it cannot be automatically extracted from the
567
+ video file. If no poster frame is provided, the default "media loudspeaker" image will
568
+ be used.
569
+
570
+ Return a newly added movie shape to the slide, positioned at (`left`, `top`), having size
571
+ (`width`, `height`), and containing `movie_file`. Before the video is started,
572
+ `poster_frame_image` is displayed as a placeholder for the video.
573
+ """
574
+ movie_pic = _MoviePicElementCreator.new_movie_pic(
575
+ self,
576
+ self._next_shape_id,
577
+ movie_file,
578
+ left,
579
+ top,
580
+ width,
581
+ height,
582
+ poster_frame_image,
583
+ mime_type,
584
+ )
585
+ self._spTree.append(movie_pic)
586
+ self._add_video_timing(movie_pic)
587
+ return cast(GraphicFrame, self._shape_factory(movie_pic))
588
+
589
+ def add_table(
590
+ self, rows: int, cols: int, left: Length, top: Length, width: Length, height: Length
591
+ ) -> GraphicFrame:
592
+ """Add a |GraphicFrame| object containing a table.
593
+
594
+ The table has the specified number of `rows` and `cols` and the specified position and
595
+ size. `width` is evenly distributed between the columns of the new table. Likewise,
596
+ `height` is evenly distributed between the rows. Note that the `.table` property on the
597
+ returned |GraphicFrame| shape must be used to access the enclosed |Table| object.
598
+ """
599
+ graphicFrame = self._add_graphicFrame_containing_table(rows, cols, left, top, width, height)
600
+ return cast(GraphicFrame, self._shape_factory(graphicFrame))
601
+
602
+ def clone_layout_placeholders(self, slide_layout: SlideLayout) -> None:
603
+ """Add placeholder shapes based on those in `slide_layout`.
604
+
605
+ Z-order of placeholders is preserved. Latent placeholders (date, slide number, and footer)
606
+ are not cloned.
607
+ """
608
+ for placeholder in slide_layout.iter_cloneable_placeholders():
609
+ self.clone_placeholder(placeholder)
610
+
611
+ @property
612
+ def placeholders(self) -> SlidePlaceholders:
613
+ """Sequence of placeholder shapes in this slide."""
614
+ return self.parent.placeholders
615
+
616
+ @property
617
+ def title(self) -> Shape | None:
618
+ """The title placeholder shape on the slide.
619
+
620
+ |None| if the slide has no title placeholder.
621
+ """
622
+ for elm in self._spTree.iter_ph_elms():
623
+ if elm.ph_idx == 0:
624
+ return cast(Shape, self._shape_factory(elm))
625
+ return None
626
+
627
+ def _add_graphicFrame_containing_table(
628
+ self, rows: int, cols: int, x: Length, y: Length, cx: Length, cy: Length
629
+ ) -> CT_GraphicalObjectFrame:
630
+ """Return a newly added `p:graphicFrame` element containing a table as specified."""
631
+ _id = self._next_shape_id
632
+ name = "Table %d" % (_id - 1)
633
+ graphicFrame = self._spTree.add_table(_id, name, rows, cols, x, y, cx, cy)
634
+ return graphicFrame
635
+
636
+ def _add_video_timing(self, pic: CT_Picture) -> None:
637
+ """Add a `p:video` element under `p:sld/p:timing`.
638
+
639
+ The element will refer to the specified `pic` element by its shape id, and cause the video
640
+ play controls to appear for that video.
641
+ """
642
+ sld = self._spTree.xpath("/p:sld")[0]
643
+ childTnLst = sld.get_or_add_childTnLst()
644
+ childTnLst.add_video(pic.shape_id)
645
+
646
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
647
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
648
+ return SlideShapeFactory(shape_elm, self)
649
+
650
+
651
+ class LayoutShapes(_BaseShapes):
652
+ """Sequence of shapes appearing on a slide layout.
653
+
654
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
655
+ Supports indexed access, len(), index(), and iteration.
656
+ """
657
+
658
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
659
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
660
+ return _LayoutShapeFactory(shape_elm, self)
661
+
662
+
663
+ class MasterShapes(_BaseShapes):
664
+ """Sequence of shapes appearing on a slide master.
665
+
666
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
667
+ Supports indexed access, len(), and iteration.
668
+ """
669
+
670
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
671
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
672
+ return _MasterShapeFactory(shape_elm, self)
673
+
674
+
675
+ class NotesSlideShapes(_BaseShapes):
676
+ """Sequence of shapes appearing on a notes slide.
677
+
678
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
679
+ Supports indexed access, len(), index(), and iteration.
680
+ """
681
+
682
+ def ph_basename(self, ph_type: PP_PLACEHOLDER) -> str:
683
+ """Return the base name for a placeholder of `ph_type` in this shape collection.
684
+
685
+ A notes slide uses a different name for the body placeholder and has some unique
686
+ placeholder types, so this method overrides the default in the base class.
687
+ """
688
+ return {
689
+ PP_PLACEHOLDER.BODY: "Notes Placeholder",
690
+ PP_PLACEHOLDER.DATE: "Date Placeholder",
691
+ PP_PLACEHOLDER.FOOTER: "Footer Placeholder",
692
+ PP_PLACEHOLDER.HEADER: "Header Placeholder",
693
+ PP_PLACEHOLDER.SLIDE_IMAGE: "Slide Image Placeholder",
694
+ PP_PLACEHOLDER.SLIDE_NUMBER: "Slide Number Placeholder",
695
+ }[ph_type]
696
+
697
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
698
+ """Return appropriate shape object for `shape_elm` appearing on a notes slide."""
699
+ return _NotesSlideShapeFactory(shape_elm, self)
700
+
701
+
702
+ class BasePlaceholders(_BaseShapes):
703
+ """Base class for placeholder collections.
704
+
705
+ Subclasses differentiate behaviors for a master, layout, and slide. By default, placeholder
706
+ shapes are constructed using |BaseShapeFactory|. Subclasses should override
707
+ :method:`_shape_factory` to use custom placeholder classes.
708
+ """
709
+
710
+ @staticmethod
711
+ def _is_member_elm(shape_elm: ShapeElement) -> bool:
712
+ """True if `shape_elm` is a placeholder shape, False otherwise."""
713
+ return shape_elm.has_ph_elm
714
+
715
+
716
+ class LayoutPlaceholders(BasePlaceholders):
717
+ """Sequence of |LayoutPlaceholder| instance for each placeholder shape on a slide layout."""
718
+
719
+ __iter__: Callable[ # pyright: ignore[reportIncompatibleMethodOverride]
720
+ [], Iterator[LayoutPlaceholder]
721
+ ]
722
+
723
+ def get(self, idx: int, default: LayoutPlaceholder | None = None) -> LayoutPlaceholder | None:
724
+ """The first placeholder shape with matching `idx` value, or `default` if not found."""
725
+ for placeholder in self:
726
+ if placeholder.element.ph_idx == idx:
727
+ return placeholder
728
+ return default
729
+
730
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
731
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
732
+ return _LayoutShapeFactory(shape_elm, self)
733
+
734
+
735
+ class MasterPlaceholders(BasePlaceholders):
736
+ """Sequence of MasterPlaceholder representing the placeholder shapes on a slide master."""
737
+
738
+ __iter__: Callable[ # pyright: ignore[reportIncompatibleMethodOverride]
739
+ [], Iterator[MasterPlaceholder]
740
+ ]
741
+
742
+ def get(self, ph_type: PP_PLACEHOLDER, default: MasterPlaceholder | None = None):
743
+ """Return the first placeholder shape with type `ph_type` (e.g. 'body').
744
+
745
+ Returns `default` if no such placeholder shape is present in the collection.
746
+ """
747
+ for placeholder in self:
748
+ if placeholder.ph_type == ph_type:
749
+ return placeholder
750
+ return default
751
+
752
+ def _shape_factory( # pyright: ignore[reportIncompatibleMethodOverride]
753
+ self, placeholder_elm: CT_Shape
754
+ ) -> MasterPlaceholder:
755
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
756
+ return cast(MasterPlaceholder, _MasterShapeFactory(placeholder_elm, self))
757
+
758
+
759
+ class NotesSlidePlaceholders(MasterPlaceholders):
760
+ """Sequence of placeholder shapes on a notes slide."""
761
+
762
+ __iter__: Callable[ # pyright: ignore[reportIncompatibleMethodOverride]
763
+ [], Iterator[NotesSlidePlaceholder]
764
+ ]
765
+
766
+ def _shape_factory( # pyright: ignore[reportIncompatibleMethodOverride]
767
+ self, placeholder_elm: CT_Shape
768
+ ) -> NotesSlidePlaceholder:
769
+ """Return an instance of the appropriate placeholder proxy class for `placeholder_elm`."""
770
+ return cast(NotesSlidePlaceholder, _NotesSlideShapeFactory(placeholder_elm, self))
771
+
772
+
773
+ class SlidePlaceholders(ParentedElementProxy):
774
+ """Collection of placeholder shapes on a slide.
775
+
776
+ Supports iteration, :func:`len`, and dictionary-style lookup on the `idx` value of the
777
+ placeholders it contains.
778
+ """
779
+
780
+ _element: CT_GroupShape
781
+
782
+ def __getitem__(self, idx: int):
783
+ """Access placeholder shape having `idx`.
784
+
785
+ Note that while this looks like list access, idx is actually a dictionary key and will
786
+ raise |KeyError| if no placeholder with that idx value is in the collection.
787
+ """
788
+ for e in self._element.iter_ph_elms():
789
+ if e.ph_idx == idx:
790
+ return SlideShapeFactory(e, self)
791
+ raise KeyError("no placeholder on this slide with idx == %d" % idx)
792
+
793
+ def __iter__(self):
794
+ """Generate placeholder shapes in `idx` order."""
795
+ ph_elms = sorted([e for e in self._element.iter_ph_elms()], key=lambda e: e.ph_idx)
796
+ return (SlideShapeFactory(e, self) for e in ph_elms)
797
+
798
+ def __len__(self) -> int:
799
+ """Return count of placeholder shapes."""
800
+ return len(list(self._element.iter_ph_elms()))
801
+
802
+
803
+ def BaseShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
804
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
805
+ tag = shape_elm.tag
806
+
807
+ if isinstance(shape_elm, CT_Picture):
808
+ videoFiles = shape_elm.xpath("./p:nvPicPr/p:nvPr/a:videoFile")
809
+ if videoFiles:
810
+ return Movie(shape_elm, parent)
811
+ return Picture(shape_elm, parent)
812
+
813
+ shape_cls = {
814
+ qn("p:cxnSp"): Connector,
815
+ qn("p:grpSp"): GroupShape,
816
+ qn("p:sp"): Shape,
817
+ qn("p:graphicFrame"): GraphicFrame,
818
+ }.get(tag, BaseShape)
819
+
820
+ return shape_cls(shape_elm, parent) # pyright: ignore[reportArgumentType]
821
+
822
+
823
+ def _LayoutShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
824
+ """Return appropriate shape object for `shape_elm` on a slide layout."""
825
+ if isinstance(shape_elm, CT_Shape) and shape_elm.has_ph_elm:
826
+ return LayoutPlaceholder(shape_elm, parent)
827
+ return BaseShapeFactory(shape_elm, parent)
828
+
829
+
830
+ def _MasterShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
831
+ """Return appropriate shape object for `shape_elm` on a slide master."""
832
+ if isinstance(shape_elm, CT_Shape) and shape_elm.has_ph_elm:
833
+ return MasterPlaceholder(shape_elm, parent)
834
+ return BaseShapeFactory(shape_elm, parent)
835
+
836
+
837
+ def _NotesSlideShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
838
+ """Return appropriate shape object for `shape_elm` on a notes slide."""
839
+ if isinstance(shape_elm, CT_Shape) and shape_elm.has_ph_elm:
840
+ return NotesSlidePlaceholder(shape_elm, parent)
841
+ return BaseShapeFactory(shape_elm, parent)
842
+
843
+
844
+ def _SlidePlaceholderFactory(shape_elm: ShapeElement, parent: ProvidesPart):
845
+ """Return a placeholder shape of the appropriate type for `shape_elm`."""
846
+ tag = shape_elm.tag
847
+ if tag == qn("p:sp"):
848
+ Constructor = {
849
+ PP_PLACEHOLDER.BITMAP: PicturePlaceholder,
850
+ PP_PLACEHOLDER.CHART: ChartPlaceholder,
851
+ PP_PLACEHOLDER.PICTURE: PicturePlaceholder,
852
+ PP_PLACEHOLDER.TABLE: TablePlaceholder,
853
+ }.get(shape_elm.ph_type, SlidePlaceholder)
854
+ elif tag == qn("p:graphicFrame"):
855
+ Constructor = PlaceholderGraphicFrame
856
+ elif tag == qn("p:pic"):
857
+ Constructor = PlaceholderPicture
858
+ else:
859
+ Constructor = BaseShapeFactory
860
+ return Constructor(shape_elm, parent) # pyright: ignore[reportArgumentType]
861
+
862
+
863
+ def SlideShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
864
+ """Return appropriate shape object for `shape_elm` on a slide."""
865
+ if shape_elm.has_ph_elm:
866
+ return _SlidePlaceholderFactory(shape_elm, parent)
867
+ return BaseShapeFactory(shape_elm, parent)
868
+
869
+
870
+ class _MoviePicElementCreator(object):
871
+ """Functional service object for creating a new movie p:pic element.
872
+
873
+ It's entire external interface is its :meth:`new_movie_pic` class method that returns a new
874
+ `p:pic` element containing the specified video. This class is not intended to be constructed
875
+ or an instance of it retained by the caller; it is a "one-shot" object, really a function
876
+ wrapped in a object such that its helper methods can be organized here.
877
+ """
878
+
879
+ def __init__(
880
+ self,
881
+ shapes: SlideShapes,
882
+ shape_id: int,
883
+ movie_file: str | IO[bytes],
884
+ x: Length,
885
+ y: Length,
886
+ cx: Length,
887
+ cy: Length,
888
+ poster_frame_file: str | IO[bytes] | None,
889
+ mime_type: str | None,
890
+ ):
891
+ super(_MoviePicElementCreator, self).__init__()
892
+ self._shapes = shapes
893
+ self._shape_id = shape_id
894
+ self._movie_file = movie_file
895
+ self._x, self._y, self._cx, self._cy = x, y, cx, cy
896
+ self._poster_frame_file = poster_frame_file
897
+ self._mime_type = mime_type
898
+
899
+ @classmethod
900
+ def new_movie_pic(
901
+ cls,
902
+ shapes: SlideShapes,
903
+ shape_id: int,
904
+ movie_file: str | IO[bytes],
905
+ x: Length,
906
+ y: Length,
907
+ cx: Length,
908
+ cy: Length,
909
+ poster_frame_image: str | IO[bytes] | None,
910
+ mime_type: str | None,
911
+ ) -> CT_Picture:
912
+ """Return a new `p:pic` element containing video in `movie_file`.
913
+
914
+ If `mime_type` is None, 'video/unknown' is used. If `poster_frame_file` is None, the
915
+ default "media loudspeaker" image is used.
916
+ """
917
+ return cls(shapes, shape_id, movie_file, x, y, cx, cy, poster_frame_image, mime_type)._pic
918
+
919
+ @property
920
+ def _media_rId(self) -> str:
921
+ """Return the rId of RT.MEDIA relationship to video part.
922
+
923
+ For historical reasons, there are two relationships to the same part; one is the video rId
924
+ and the other is the media rId.
925
+ """
926
+ return self._video_part_rIds[0]
927
+
928
+ @lazyproperty
929
+ def _pic(self) -> CT_Picture:
930
+ """Return the new `p:pic` element referencing the video."""
931
+ return CT_Picture.new_video_pic(
932
+ self._shape_id,
933
+ self._shape_name,
934
+ self._video_rId,
935
+ self._media_rId,
936
+ self._poster_frame_rId,
937
+ self._x,
938
+ self._y,
939
+ self._cx,
940
+ self._cy,
941
+ )
942
+
943
+ @lazyproperty
944
+ def _poster_frame_image_file(self) -> str | IO[bytes]:
945
+ """Return the image file for video placeholder image.
946
+
947
+ If no poster frame file is provided, the default "media loudspeaker" image is used.
948
+ """
949
+ poster_frame_file = self._poster_frame_file
950
+ if poster_frame_file is None:
951
+ return io.BytesIO(SPEAKER_IMAGE_BYTES)
952
+ return poster_frame_file
953
+
954
+ @lazyproperty
955
+ def _poster_frame_rId(self) -> str:
956
+ """Return the rId of relationship to poster frame image.
957
+
958
+ The poster frame is the image used to represent the video before it's played.
959
+ """
960
+ _, poster_frame_rId = self._slide_part.get_or_add_image_part(self._poster_frame_image_file)
961
+ return poster_frame_rId
962
+
963
+ @property
964
+ def _shape_name(self) -> str:
965
+ """Return the appropriate shape name for the p:pic shape.
966
+
967
+ A movie shape is named with the base filename of the video.
968
+ """
969
+ return self._video.filename
970
+
971
+ @property
972
+ def _slide_part(self) -> SlidePart:
973
+ """Return SlidePart object for slide containing this movie."""
974
+ return self._shapes.part
975
+
976
+ @lazyproperty
977
+ def _video(self) -> Video:
978
+ """Return a |Video| object containing the movie file."""
979
+ return Video.from_path_or_file_like(self._movie_file, self._mime_type)
980
+
981
+ @lazyproperty
982
+ def _video_part_rIds(self) -> tuple[str, str]:
983
+ """Return the rIds for relationships to media part for video.
984
+
985
+ This is where the media part and its relationships to the slide are actually created.
986
+ """
987
+ media_rId, video_rId = self._slide_part.get_or_add_video_media_part(self._video)
988
+ return media_rId, video_rId
989
+
990
+ @property
991
+ def _video_rId(self) -> str:
992
+ """Return the rId of RT.VIDEO relationship to video part.
993
+
994
+ For historical reasons, there are two relationships to the same part; one is the video rId
995
+ and the other is the media rId.
996
+ """
997
+ return self._video_part_rIds[1]
998
+
999
+
1000
+ class _OleObjectElementCreator(object):
1001
+ """Functional service object for creating a new OLE-object p:graphicFrame element.
1002
+
1003
+ It's entire external interface is its :meth:`graphicFrame` class method that returns a new
1004
+ `p:graphicFrame` element containing the specified embedded OLE-object shape. This class is not
1005
+ intended to be constructed or an instance of it retained by the caller; it is a "one-shot"
1006
+ object, really a function wrapped in a object such that its helper methods can be organized
1007
+ here.
1008
+ """
1009
+
1010
+ def __init__(
1011
+ self,
1012
+ shapes: _BaseGroupShapes,
1013
+ shape_id: int,
1014
+ ole_object_file: str | IO[bytes],
1015
+ prog_id: PROG_ID | str,
1016
+ x: Length,
1017
+ y: Length,
1018
+ cx: Length | None,
1019
+ cy: Length | None,
1020
+ icon_file: str | IO[bytes] | None,
1021
+ icon_width: Length | None,
1022
+ icon_height: Length | None,
1023
+ ):
1024
+ self._shapes = shapes
1025
+ self._shape_id = shape_id
1026
+ self._ole_object_file = ole_object_file
1027
+ self._prog_id_arg = prog_id
1028
+ self._x = x
1029
+ self._y = y
1030
+ self._cx_arg = cx
1031
+ self._cy_arg = cy
1032
+ self._icon_file_arg = icon_file
1033
+ self._icon_width_arg = icon_width
1034
+ self._icon_height_arg = icon_height
1035
+
1036
+ @classmethod
1037
+ def graphicFrame(
1038
+ cls,
1039
+ shapes: _BaseGroupShapes,
1040
+ shape_id: int,
1041
+ ole_object_file: str | IO[bytes],
1042
+ prog_id: PROG_ID | str,
1043
+ x: Length,
1044
+ y: Length,
1045
+ cx: Length | None,
1046
+ cy: Length | None,
1047
+ icon_file: str | IO[bytes] | None,
1048
+ icon_width: Length | None,
1049
+ icon_height: Length | None,
1050
+ ) -> CT_GraphicalObjectFrame:
1051
+ """Return new `p:graphicFrame` element containing embedded `ole_object_file`."""
1052
+ return cls(
1053
+ shapes,
1054
+ shape_id,
1055
+ ole_object_file,
1056
+ prog_id,
1057
+ x,
1058
+ y,
1059
+ cx,
1060
+ cy,
1061
+ icon_file,
1062
+ icon_width,
1063
+ icon_height,
1064
+ )._graphicFrame
1065
+
1066
+ @lazyproperty
1067
+ def _graphicFrame(self) -> CT_GraphicalObjectFrame:
1068
+ """Newly-created `p:graphicFrame` element referencing embedded OLE-object."""
1069
+ return CT_GraphicalObjectFrame.new_ole_object_graphicFrame(
1070
+ self._shape_id,
1071
+ self._shape_name,
1072
+ self._ole_object_rId,
1073
+ self._progId,
1074
+ self._icon_rId,
1075
+ self._x,
1076
+ self._y,
1077
+ self._cx,
1078
+ self._cy,
1079
+ self._icon_width,
1080
+ self._icon_height,
1081
+ )
1082
+
1083
+ @lazyproperty
1084
+ def _cx(self) -> Length:
1085
+ """Emu object specifying width of "show-as-icon" image for OLE shape."""
1086
+ # --- a user-specified width overrides any default ---
1087
+ if self._cx_arg is not None:
1088
+ return self._cx_arg
1089
+
1090
+ # --- the default width is specified by the PROG_ID member if prog_id is one,
1091
+ # --- otherwise it gets the default icon width.
1092
+ return (
1093
+ Emu(self._prog_id_arg.width) if isinstance(self._prog_id_arg, PROG_ID) else Emu(965200)
1094
+ )
1095
+
1096
+ @lazyproperty
1097
+ def _cy(self) -> Length:
1098
+ """Emu object specifying height of "show-as-icon" image for OLE shape."""
1099
+ # --- a user-specified width overrides any default ---
1100
+ if self._cy_arg is not None:
1101
+ return self._cy_arg
1102
+
1103
+ # --- the default height is specified by the PROG_ID member if prog_id is one,
1104
+ # --- otherwise it gets the default icon height.
1105
+ return (
1106
+ Emu(self._prog_id_arg.height) if isinstance(self._prog_id_arg, PROG_ID) else Emu(609600)
1107
+ )
1108
+
1109
+ @lazyproperty
1110
+ def _icon_height(self) -> Length:
1111
+ """Vertical size of enclosed EMF icon within the OLE graphic-frame.
1112
+
1113
+ This must be specified when a custom icon is used, to avoid stretching of the image and
1114
+ possible undesired resizing by PowerPoint when the OLE shape is double-clicked to open it.
1115
+
1116
+ The correct size can be determined by creating an example PPTX using PowerPoint and then
1117
+ inspecting the XML of the OLE graphics-frame (p:oleObj.imgH).
1118
+ """
1119
+ return self._icon_height_arg if self._icon_height_arg is not None else Emu(609600)
1120
+
1121
+ @lazyproperty
1122
+ def _icon_image_file(self) -> str | IO[bytes]:
1123
+ """Reference to image file containing icon to show in lieu of this object.
1124
+
1125
+ This can be either a str path or a file-like object (io.BytesIO typically).
1126
+ """
1127
+ # --- a user-specified icon overrides any default ---
1128
+ if self._icon_file_arg is not None:
1129
+ return self._icon_file_arg
1130
+
1131
+ # --- A prog_id belonging to PROG_ID gets its icon filename from there. A
1132
+ # --- user-specified (str) prog_id gets the default icon.
1133
+ icon_filename = (
1134
+ self._prog_id_arg.icon_filename
1135
+ if isinstance(self._prog_id_arg, PROG_ID)
1136
+ else "generic-icon.emf"
1137
+ )
1138
+
1139
+ _thisdir = os.path.split(__file__)[0]
1140
+ return os.path.abspath(os.path.join(_thisdir, "..", "templates", icon_filename))
1141
+
1142
+ @lazyproperty
1143
+ def _icon_rId(self) -> str:
1144
+ """str rId like "rId7" of rel to icon (image) representing OLE-object part."""
1145
+ _, rId = self._slide_part.get_or_add_image_part(self._icon_image_file)
1146
+ return rId
1147
+
1148
+ @lazyproperty
1149
+ def _icon_width(self) -> Length:
1150
+ """Width of enclosed EMF icon within the OLE graphic-frame.
1151
+
1152
+ This must be specified when a custom icon is used, to avoid stretching of the image and
1153
+ possible undesired resizing by PowerPoint when the OLE shape is double-clicked to open it.
1154
+ """
1155
+ return self._icon_width_arg if self._icon_width_arg is not None else Emu(965200)
1156
+
1157
+ @lazyproperty
1158
+ def _ole_object_rId(self) -> str:
1159
+ """str rId like "rId6" of relationship to embedded ole_object part.
1160
+
1161
+ This is where the ole_object part and its relationship to the slide are actually created.
1162
+ """
1163
+ return self._slide_part.add_embedded_ole_object_part(
1164
+ self._prog_id_arg, self._ole_object_file
1165
+ )
1166
+
1167
+ @lazyproperty
1168
+ def _progId(self) -> str:
1169
+ """str like "Excel.Sheet.12" identifying program used to open object.
1170
+
1171
+ This value appears in the `progId` attribute of the `p:oleObj` element for the object.
1172
+ """
1173
+ prog_id_arg = self._prog_id_arg
1174
+
1175
+ # --- member of PROG_ID enumeration knows its progId keyphrase, otherwise caller
1176
+ # --- has specified it explicitly (as str)
1177
+ return prog_id_arg.progId if isinstance(prog_id_arg, PROG_ID) else prog_id_arg
1178
+
1179
+ @lazyproperty
1180
+ def _shape_name(self) -> str:
1181
+ """str name like "Object 1" for the embedded ole_object shape.
1182
+
1183
+ The name is formed from the prefix "Object " and the shape-id decremented by 1.
1184
+ """
1185
+ return "Object %d" % (self._shape_id - 1)
1186
+
1187
+ @lazyproperty
1188
+ def _slide_part(self) -> SlidePart:
1189
+ """SlidePart object for this slide."""
1190
+ return self._shapes.part