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,422 @@
1
+ """Shapes based on the `p:pic` element, including Picture and Movie."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from pptx2.dml.line import LineFormat
8
+ from pptx2.dml.picture import PictureEffects
9
+ from pptx2.enum.shapes import MSO_SHAPE, MSO_SHAPE_TYPE, PP_MEDIA_TYPE
10
+ from pptx2.shapes.base import BaseShape
11
+ from pptx2.shared import ParentedElementProxy
12
+ from pptx2.util import lazyproperty
13
+
14
+ if TYPE_CHECKING:
15
+ from pptx2.oxml.shapes.picture import CT_Picture
16
+ from pptx2.oxml.shapes.shared import CT_LineProperties
17
+ from pptx2.types import ProvidesPart
18
+
19
+
20
+ class _BasePicture(BaseShape):
21
+ """Base class for shapes based on a `p:pic` element."""
22
+
23
+ def __init__(self, pic: CT_Picture, parent: ProvidesPart):
24
+ super(_BasePicture, self).__init__(pic, parent)
25
+ self._pic = pic
26
+
27
+ @property
28
+ def crop_bottom(self) -> float:
29
+ """|float| representing relative portion cropped from shape bottom.
30
+
31
+ Read/write. 1.0 represents 100%. For example, 25% is represented by 0.25. Negative values
32
+ are valid as are values greater than 1.0.
33
+ """
34
+ return self._pic.srcRect_b
35
+
36
+ @crop_bottom.setter
37
+ def crop_bottom(self, value: float):
38
+ self._pic.srcRect_b = value
39
+
40
+ @property
41
+ def crop_left(self) -> float:
42
+ """|float| representing relative portion cropped from left of shape.
43
+
44
+ Read/write. 1.0 represents 100%. A negative value extends the side beyond the image
45
+ boundary.
46
+ """
47
+ return self._pic.srcRect_l
48
+
49
+ @crop_left.setter
50
+ def crop_left(self, value: float):
51
+ self._pic.srcRect_l = value
52
+
53
+ @property
54
+ def crop_right(self) -> float:
55
+ """|float| representing relative portion cropped from right of shape.
56
+
57
+ Read/write. 1.0 represents 100%.
58
+ """
59
+ return self._pic.srcRect_r
60
+
61
+ @crop_right.setter
62
+ def crop_right(self, value: float):
63
+ self._pic.srcRect_r = value
64
+
65
+ @property
66
+ def crop_top(self) -> float:
67
+ """|float| representing relative portion cropped from shape top.
68
+
69
+ Read/write. 1.0 represents 100%.
70
+ """
71
+ return self._pic.srcRect_t
72
+
73
+ @crop_top.setter
74
+ def crop_top(self, value: float):
75
+ self._pic.srcRect_t = value
76
+
77
+ def get_or_add_ln(self):
78
+ """Return the `a:ln` element for this `p:pic`-based image.
79
+
80
+ The `a:ln` element contains the line format properties XML.
81
+ """
82
+ return self._pic.get_or_add_ln()
83
+
84
+ @lazyproperty
85
+ def line(self) -> LineFormat:
86
+ """Provides access to properties of the picture outline, such as its color and width."""
87
+ return LineFormat(self)
88
+
89
+ @property
90
+ def ln(self) -> CT_LineProperties | None:
91
+ """The `a:ln` element for this `p:pic`.
92
+
93
+ Contains the line format properties such as line color and width. |None| if no `a:ln`
94
+ element is present.
95
+ """
96
+ return self._pic.ln
97
+
98
+
99
+ class Movie(_BasePicture):
100
+ """A movie shape, one that places a video on a slide.
101
+
102
+ Like |Picture|, a movie shape is based on the `p:pic` element. A movie is composed of a video
103
+ and a *poster frame*, the placeholder image that represents the video before it is played.
104
+ """
105
+
106
+ @lazyproperty
107
+ def media_format(self) -> _MediaFormat:
108
+ """The |_MediaFormat| object for this movie.
109
+
110
+ The |_MediaFormat| object provides access to formatting properties for the movie.
111
+ """
112
+ return _MediaFormat(self._pic, self)
113
+
114
+ @property
115
+ def media_type(self) -> PP_MEDIA_TYPE:
116
+ """Member of :ref:`PpMediaType` describing this shape.
117
+
118
+ The return value is unconditionally `PP_MEDIA_TYPE.MOVIE` in this case.
119
+ """
120
+ return PP_MEDIA_TYPE.MOVIE
121
+
122
+ @property
123
+ def poster_frame(self):
124
+ """Return |Image| object containing poster frame for this movie.
125
+
126
+ Returns |None| if this movie has no poster frame (uncommon).
127
+ """
128
+ slide_part, rId = self.part, self._pic.blip_rId
129
+ if rId is None:
130
+ return None
131
+ return slide_part.get_image(rId)
132
+
133
+ @property
134
+ def shape_type(self) -> MSO_SHAPE_TYPE:
135
+ """Return member of :ref:`MsoShapeType` describing this shape.
136
+
137
+ The return value is unconditionally `MSO_SHAPE_TYPE.MEDIA` in this
138
+ case.
139
+ """
140
+ return MSO_SHAPE_TYPE.MEDIA
141
+
142
+
143
+ class Picture(_BasePicture):
144
+ """A picture shape, one that places an image on a slide.
145
+
146
+ Based on the `p:pic` element.
147
+ """
148
+
149
+ def replace_with(self, builder, *, padding=0):
150
+ """Delete this picture and call ``builder(slide, bbox)`` in its place.
151
+
152
+ The picture's current bounding box is snapshotted (minus an
153
+ optional ``padding`` inset), then the picture is removed from
154
+ the slide. ``builder`` is invoked with ``(slide, bbox)`` where
155
+ ``bbox`` is a :class:`~pptx2.geometry.BBox` — the typical
156
+ usage is to draw native shapes in the area a broken /
157
+ suboptimal picture used to occupy::
158
+
159
+ def diagram(slide, bbox):
160
+ left, right = bbox.split_h([1, 1], gap=Inches(0.1))
161
+ slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *left)
162
+ slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, *right)
163
+
164
+ picture.replace_with(diagram, padding=Inches(0.1))
165
+
166
+ ``padding`` is an integer EMU (or :class:`~pptx2.util.Length`)
167
+ applied as a uniform inset on all four sides. Negative values
168
+ expand the area outward.
169
+
170
+ Returns whatever ``builder`` returned.
171
+ """
172
+ from pptx2.geometry import BBox
173
+
174
+ bbox = BBox.from_shape(self)
175
+ if padding:
176
+ bbox = bbox.inset(all=int(padding))
177
+ # Walk up through the proxy to find the owning slide.
178
+ try:
179
+ slide = self.part.slide
180
+ except AttributeError as exc:
181
+ raise ValueError(
182
+ "replace_with() requires the picture to be on a slide"
183
+ ) from exc
184
+ self.delete()
185
+ return builder(slide, bbox)
186
+
187
+ def enclosing_container(
188
+ self,
189
+ *,
190
+ exclude_text: bool = True,
191
+ shrink_around: bool = True,
192
+ ):
193
+ """Return the smallest rectangle on the slide enclosing this picture.
194
+
195
+ Useful when a picture sits inside a "card" rectangle plus a
196
+ heading — replacing the picture's own bbox would lose the
197
+ heading; replacing the enclosing container area keeps the
198
+ layout intact.
199
+
200
+ * ``exclude_text=True`` (default) skips other shapes that hold
201
+ live text — those are content, not chrome.
202
+ * ``shrink_around=True`` (default) trims the returned box so it
203
+ doesn't overlap any sibling content-bearing shape; the
204
+ biggest empty sub-rectangle of the enclosing card is returned.
205
+
206
+ Returns a :class:`~pptx2.geometry.BBox` or ``None`` when no
207
+ enclosing shape (other than the slide itself) is found.
208
+ """
209
+ from pptx2.geometry import BBox
210
+
211
+ try:
212
+ slide = self.part.slide
213
+ except AttributeError:
214
+ return None
215
+
216
+ my_box = BBox.from_shape(self)
217
+ candidates: list[tuple[int, BaseShape, BBox]] = []
218
+ for shape in slide.shapes:
219
+ if shape is self:
220
+ continue
221
+ try:
222
+ box = BBox.from_shape(shape)
223
+ except Exception:
224
+ continue
225
+ if box.area <= my_box.area:
226
+ continue
227
+ # Skip if this shape is itself a placeholder for text we
228
+ # want to keep visible.
229
+ if exclude_text and getattr(shape, "has_text_frame", False):
230
+ tf = getattr(shape, "text_frame", None)
231
+ if tf is not None and tf.text.strip():
232
+ continue
233
+ if box.contains(my_box):
234
+ candidates.append((box.area, shape, box))
235
+ if not candidates:
236
+ return None
237
+ # Smallest enclosing box.
238
+ candidates.sort(key=lambda t: t[0])
239
+ _, _container, container_box = candidates[0]
240
+ if not shrink_around:
241
+ return container_box
242
+
243
+ # Trim around other content shapes that sit inside the container.
244
+ # For each obstacle, try the four edge-pushes that would exclude
245
+ # it (top edge down past obstacle.bottom, bottom edge up past
246
+ # obstacle.top, etc.) and pick the smallest waste that still
247
+ # keeps the picture (``my_box``) inside the trimmed result.
248
+ trimmed = container_box
249
+ for shape in slide.shapes:
250
+ if shape is self or shape is _container:
251
+ continue
252
+ try:
253
+ box = BBox.from_shape(shape)
254
+ except Exception:
255
+ continue
256
+ if not trimmed.contains(box):
257
+ continue
258
+ trimmed = _exclude_obstacle(trimmed, box, must_contain=my_box)
259
+ return trimmed
260
+
261
+ @property
262
+ def auto_shape_type(self) -> MSO_SHAPE | None:
263
+ """Member of MSO_SHAPE indicating masking shape.
264
+
265
+ A picture can be masked by any of the so-called "auto-shapes" available in PowerPoint,
266
+ such as an ellipse or triangle. When a picture is masked by a shape, the shape assumes the
267
+ same dimensions as the picture and the portion of the picture outside the shape boundaries
268
+ does not appear. Note the default value for a newly-inserted picture is
269
+ `MSO_AUTO_SHAPE_TYPE.RECTANGLE`, which performs no cropping because the extents of the
270
+ rectangle exactly correspond to the extents of the picture.
271
+
272
+ The available shapes correspond to the members of :ref:`MsoAutoShapeType`.
273
+
274
+ The return value can also be |None|, indicating the picture either has no geometry (not
275
+ expected) or has custom geometry, like a freeform shape. A picture with no geometry will
276
+ have no visible representation on the slide, although it can be selected. This is because
277
+ without geometry, there is no "inside-the-shape" for it to appear in.
278
+ """
279
+ prstGeom = self._pic.spPr.prstGeom
280
+ if prstGeom is None: # ---generally means cropped with freeform---
281
+ return None
282
+ return prstGeom.prst
283
+
284
+ @auto_shape_type.setter
285
+ def auto_shape_type(self, member: MSO_SHAPE):
286
+ MSO_SHAPE.validate(member)
287
+ spPr = self._pic.spPr
288
+ prstGeom = spPr.prstGeom
289
+ if prstGeom is None:
290
+ spPr._remove_custGeom() # pyright: ignore[reportPrivateUsage]
291
+ prstGeom = spPr._add_prstGeom() # pyright: ignore[reportPrivateUsage]
292
+ prstGeom.prst = member
293
+
294
+ @lazyproperty
295
+ def effects(self) -> PictureEffects:
296
+ """Provides access to image-level effects: transparency, brightness, contrast, recolor.
297
+
298
+ The underlying ``<a:blip>`` element must be present (which it always is for a
299
+ normal embedded-image picture).
300
+ """
301
+ blip = self._pic.blipFill.blip
302
+ if blip is None:
303
+ raise ValueError("picture has no embedded image blip element")
304
+ return PictureEffects(blip)
305
+
306
+ @property
307
+ def image(self):
308
+ """The |Image| object for this picture.
309
+
310
+ Provides access to the properties and bytes of the image in this picture shape.
311
+ """
312
+ slide_part, rId = self.part, self._pic.blip_rId
313
+ if rId is None:
314
+ raise ValueError("no embedded image")
315
+ return slide_part.get_image(rId)
316
+
317
+ @property
318
+ def shape_type(self) -> MSO_SHAPE_TYPE:
319
+ """Unconditionally `MSO_SHAPE_TYPE.PICTURE` in this case."""
320
+ return MSO_SHAPE_TYPE.PICTURE
321
+
322
+
323
+ def _exclude_obstacle(trimmed, obstacle, *, must_contain):
324
+ """Shrink *trimmed* to exclude *obstacle* while still containing *must_contain*.
325
+
326
+ Tries each of the four edge-trims (push top down past obstacle,
327
+ push bottom up past obstacle, etc.), discards any that would also
328
+ exclude *must_contain*, and returns the candidate with the least
329
+ area lost. Returns *trimmed* unchanged when no valid trim exists.
330
+
331
+ Earlier versions of ``Picture.enclosing_container(shrink_around=
332
+ True)`` used a different heuristic that could collapse the
333
+ container onto the obstacle (e.g. a title strip above the picture
334
+ caused the bottom edge to be chosen, returning the top strip
335
+ instead). This helper picks the right edge by construction: it
336
+ only emits a candidate that excludes the obstacle *and* keeps the
337
+ picture inside.
338
+ """
339
+ from pptx2.geometry import BBox
340
+ from pptx2.util import Emu
341
+
342
+ candidates: list[tuple[int, BBox]] = []
343
+ # Push TOP edge down past obstacle.bottom
344
+ new_top = int(obstacle.bottom)
345
+ if (
346
+ new_top > int(trimmed.top)
347
+ and new_top <= int(must_contain.top)
348
+ and new_top < int(trimmed.bottom)
349
+ ):
350
+ cost = new_top - int(trimmed.top)
351
+ candidates.append(
352
+ (
353
+ cost,
354
+ BBox(
355
+ trimmed.left, Emu(new_top), trimmed.width,
356
+ Emu(int(trimmed.bottom) - new_top),
357
+ ),
358
+ )
359
+ )
360
+ # Push BOTTOM edge up past obstacle.top
361
+ new_bottom = int(obstacle.top)
362
+ if (
363
+ new_bottom < int(trimmed.bottom)
364
+ and new_bottom >= int(must_contain.bottom)
365
+ and new_bottom > int(trimmed.top)
366
+ ):
367
+ cost = int(trimmed.bottom) - new_bottom
368
+ candidates.append(
369
+ (
370
+ cost,
371
+ BBox(
372
+ trimmed.left, trimmed.top, trimmed.width,
373
+ Emu(new_bottom - int(trimmed.top)),
374
+ ),
375
+ )
376
+ )
377
+ # Push LEFT edge right past obstacle.right
378
+ new_left = int(obstacle.right)
379
+ if (
380
+ new_left > int(trimmed.left)
381
+ and new_left <= int(must_contain.left)
382
+ and new_left < int(trimmed.right)
383
+ ):
384
+ cost = new_left - int(trimmed.left)
385
+ candidates.append(
386
+ (
387
+ cost,
388
+ BBox(
389
+ Emu(new_left), trimmed.top,
390
+ Emu(int(trimmed.right) - new_left), trimmed.height,
391
+ ),
392
+ )
393
+ )
394
+ # Push RIGHT edge left past obstacle.left
395
+ new_right = int(obstacle.left)
396
+ if (
397
+ new_right < int(trimmed.right)
398
+ and new_right >= int(must_contain.right)
399
+ and new_right > int(trimmed.left)
400
+ ):
401
+ cost = int(trimmed.right) - new_right
402
+ candidates.append(
403
+ (
404
+ cost,
405
+ BBox(
406
+ trimmed.left, trimmed.top,
407
+ Emu(new_right - int(trimmed.left)), trimmed.height,
408
+ ),
409
+ )
410
+ )
411
+ if not candidates:
412
+ return trimmed
413
+ candidates.sort(key=lambda c: c[0])
414
+ return candidates[0][1]
415
+
416
+
417
+ class _MediaFormat(ParentedElementProxy):
418
+ """Provides access to formatting properties for a Media object.
419
+
420
+ Media format properties are things like start point, volume, and
421
+ compression type.
422
+ """