python-pptx2 2.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pptx2/__init__.py +152 -0
- pptx2/_color.py +75 -0
- pptx2/_slide_importer.py +597 -0
- pptx2/_svg.py +155 -0
- pptx2/_template_applier.py +292 -0
- pptx2/_textstyle.py +187 -0
- pptx2/accessibility.py +365 -0
- pptx2/action.py +270 -0
- pptx2/animation.py +2237 -0
- pptx2/api.py +49 -0
- pptx2/audit.py +258 -0
- pptx2/chart/__init__.py +0 -0
- pptx2/chart/analytics.py +381 -0
- pptx2/chart/axis.py +543 -0
- pptx2/chart/category.py +200 -0
- pptx2/chart/chart.py +670 -0
- pptx2/chart/data.py +864 -0
- pptx2/chart/datalabel.py +406 -0
- pptx2/chart/legend.py +86 -0
- pptx2/chart/marker.py +70 -0
- pptx2/chart/palettes.py +129 -0
- pptx2/chart/plot.py +462 -0
- pptx2/chart/point.py +101 -0
- pptx2/chart/quick_layouts.py +325 -0
- pptx2/chart/series.py +334 -0
- pptx2/chart/xlsx.py +272 -0
- pptx2/chart/xmlwriter.py +1845 -0
- pptx2/compose/__init__.py +28 -0
- pptx2/compose/from_spec.py +1094 -0
- pptx2/design/__init__.py +8 -0
- pptx2/design/components.py +607 -0
- pptx2/design/figures.py +389 -0
- pptx2/design/layout.py +370 -0
- pptx2/design/recipes.py +1967 -0
- pptx2/design/style.py +209 -0
- pptx2/design/tokens.py +915 -0
- pptx2/diagrams.py +754 -0
- pptx2/dml/__init__.py +0 -0
- pptx2/dml/chtfmt.py +40 -0
- pptx2/dml/color.py +496 -0
- pptx2/dml/effect.py +909 -0
- pptx2/dml/fill.py +691 -0
- pptx2/dml/line.py +287 -0
- pptx2/dml/picture.py +212 -0
- pptx2/dml/three_d.py +381 -0
- pptx2/enum/__init__.py +0 -0
- pptx2/enum/action.py +71 -0
- pptx2/enum/animation.py +31 -0
- pptx2/enum/base.py +218 -0
- pptx2/enum/chart.py +574 -0
- pptx2/enum/dml.py +740 -0
- pptx2/enum/lang.py +685 -0
- pptx2/enum/presentation.py +133 -0
- pptx2/enum/shapes.py +1029 -0
- pptx2/enum/text.py +230 -0
- pptx2/exc.py +42 -0
- pptx2/formats.py +139 -0
- pptx2/geometry.py +420 -0
- pptx2/inherit.py +109 -0
- pptx2/lint.py +2256 -0
- pptx2/math.py +177 -0
- pptx2/media.py +197 -0
- pptx2/opc/__init__.py +0 -0
- pptx2/opc/constants.py +332 -0
- pptx2/opc/oxml.py +188 -0
- pptx2/opc/package.py +762 -0
- pptx2/opc/packuri.py +109 -0
- pptx2/opc/serialized.py +296 -0
- pptx2/opc/shared.py +20 -0
- pptx2/opc/spec.py +45 -0
- pptx2/oxml/__init__.py +555 -0
- pptx2/oxml/action.py +53 -0
- pptx2/oxml/chart/__init__.py +0 -0
- pptx2/oxml/chart/axis.py +337 -0
- pptx2/oxml/chart/chart.py +481 -0
- pptx2/oxml/chart/datalabel.py +253 -0
- pptx2/oxml/chart/legend.py +72 -0
- pptx2/oxml/chart/marker.py +61 -0
- pptx2/oxml/chart/plot.py +365 -0
- pptx2/oxml/chart/series.py +425 -0
- pptx2/oxml/chart/shared.py +220 -0
- pptx2/oxml/coreprops.py +288 -0
- pptx2/oxml/dml/__init__.py +0 -0
- pptx2/oxml/dml/color.py +135 -0
- pptx2/oxml/dml/effect.py +213 -0
- pptx2/oxml/dml/fill.py +316 -0
- pptx2/oxml/dml/line.py +12 -0
- pptx2/oxml/dml/three_d.py +110 -0
- pptx2/oxml/ns.py +135 -0
- pptx2/oxml/presentation.py +313 -0
- pptx2/oxml/shapes/__init__.py +19 -0
- pptx2/oxml/shapes/autoshape.py +467 -0
- pptx2/oxml/shapes/connector.py +107 -0
- pptx2/oxml/shapes/graphfrm.py +347 -0
- pptx2/oxml/shapes/groupshape.py +329 -0
- pptx2/oxml/shapes/picture.py +270 -0
- pptx2/oxml/shapes/shared.py +577 -0
- pptx2/oxml/simpletypes.py +1027 -0
- pptx2/oxml/slide.py +563 -0
- pptx2/oxml/table.py +650 -0
- pptx2/oxml/text.py +815 -0
- pptx2/oxml/theme.py +36 -0
- pptx2/oxml/xmlchemy.py +717 -0
- pptx2/package.py +222 -0
- pptx2/parts/__init__.py +0 -0
- pptx2/parts/chart.py +95 -0
- pptx2/parts/coreprops.py +167 -0
- pptx2/parts/diagram.py +37 -0
- pptx2/parts/embeddedpackage.py +93 -0
- pptx2/parts/image.py +275 -0
- pptx2/parts/media.py +37 -0
- pptx2/parts/presentation.py +136 -0
- pptx2/parts/slide.py +371 -0
- pptx2/presentation.py +408 -0
- pptx2/py.typed +0 -0
- pptx2/render.py +586 -0
- pptx2/section.py +272 -0
- pptx2/shapes/__init__.py +26 -0
- pptx2/shapes/autoshape.py +442 -0
- pptx2/shapes/base.py +1078 -0
- pptx2/shapes/connector.py +297 -0
- pptx2/shapes/freeform.py +337 -0
- pptx2/shapes/graphfrm.py +316 -0
- pptx2/shapes/group.py +264 -0
- pptx2/shapes/picture.py +422 -0
- pptx2/shapes/placeholder.py +468 -0
- pptx2/shapes/shapetree.py +2027 -0
- pptx2/shared.py +82 -0
- pptx2/skill/SKILL.md +450 -0
- pptx2/skill/__init__.py +78 -0
- pptx2/skill/__main__.py +64 -0
- pptx2/skill/references/animations.md +189 -0
- pptx2/skill/references/basics.md +421 -0
- pptx2/skill/references/charts.md +254 -0
- pptx2/skill/references/compose.md +234 -0
- pptx2/skill/references/design.md +366 -0
- pptx2/skill/references/effects.md +249 -0
- pptx2/skill/references/end-to-end-deck.md +231 -0
- pptx2/skill/references/geometry-and-arrows.md +334 -0
- pptx2/skill/references/lint.md +275 -0
- pptx2/skill/references/math.md +86 -0
- pptx2/skill/references/picture-effects.md +129 -0
- pptx2/skill/references/render.md +151 -0
- pptx2/skill/references/smart-art.md +75 -0
- pptx2/skill/references/space-aware-authoring.md +249 -0
- pptx2/skill/references/tables.md +244 -0
- pptx2/skill/references/theme.md +127 -0
- pptx2/skill/references/three-d.md +109 -0
- pptx2/skill/references/transitions.md +100 -0
- pptx2/slide.py +1244 -0
- pptx2/smart_art.py +220 -0
- pptx2/spec.py +633 -0
- pptx2/table.py +1181 -0
- pptx2/table_styles.py +184 -0
- pptx2/templates/default.pptx +0 -0
- pptx2/templates/docx-icon.emf +0 -0
- pptx2/templates/generic-icon.emf +0 -0
- pptx2/templates/notes.xml +23 -0
- pptx2/templates/notesMaster.xml +352 -0
- pptx2/templates/pptx-icon.emf +0 -0
- pptx2/templates/theme.xml +321 -0
- pptx2/templates/xlsx-icon.emf +0 -0
- pptx2/text/__init__.py +0 -0
- pptx2/text/fonts.py +482 -0
- pptx2/text/layout.py +374 -0
- pptx2/text/text.py +1272 -0
- pptx2/theme.py +721 -0
- pptx2/types.py +36 -0
- pptx2/util.py +263 -0
- python_pptx2-2.13.0.dist-info/METADATA +351 -0
- python_pptx2-2.13.0.dist-info/RECORD +175 -0
- python_pptx2-2.13.0.dist-info/WHEEL +5 -0
- python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
- python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
- python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
pptx2/shapes/picture.py
ADDED
|
@@ -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
|
+
"""
|