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/slide.py
ADDED
|
@@ -0,0 +1,1244 @@
|
|
|
1
|
+
"""Slide-related objects, including masters, layouts, and notes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from typing import TYPE_CHECKING, Iterator, Sequence, cast
|
|
7
|
+
|
|
8
|
+
from pptx2.dml.fill import FillFormat
|
|
9
|
+
from pptx2.enum.presentation import (
|
|
10
|
+
MSO_TRANSITION_TYPE,
|
|
11
|
+
P14_TRANSITION_NAMES,
|
|
12
|
+
P159_TRANSITION_NAMES,
|
|
13
|
+
)
|
|
14
|
+
from pptx2.enum.shapes import PP_PLACEHOLDER
|
|
15
|
+
from pptx2.oxml.ns import qn
|
|
16
|
+
from pptx2.shapes.shapetree import (
|
|
17
|
+
LayoutPlaceholders,
|
|
18
|
+
LayoutShapes,
|
|
19
|
+
MasterPlaceholders,
|
|
20
|
+
MasterShapes,
|
|
21
|
+
NotesSlidePlaceholders,
|
|
22
|
+
NotesSlideShapes,
|
|
23
|
+
SlidePlaceholders,
|
|
24
|
+
SlideShapes,
|
|
25
|
+
)
|
|
26
|
+
from pptx2.shared import ElementProxy, ParentedElementProxy, PartElementProxy
|
|
27
|
+
from pptx2.util import lazyproperty
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from pptx2.animation import SlideAnimations
|
|
31
|
+
from pptx2.lint import SlideLintReport
|
|
32
|
+
from pptx2.oxml.presentation import CT_SlideIdList, CT_SlideMasterIdList
|
|
33
|
+
from pptx2.oxml.slide import (
|
|
34
|
+
CT_CommonSlideData,
|
|
35
|
+
CT_NotesSlide,
|
|
36
|
+
CT_Slide,
|
|
37
|
+
CT_SlideLayoutIdList,
|
|
38
|
+
CT_SlideMaster,
|
|
39
|
+
)
|
|
40
|
+
from pptx2.parts.presentation import PresentationPart
|
|
41
|
+
from pptx2.parts.slide import SlideLayoutPart, SlideMasterPart, SlidePart
|
|
42
|
+
from pptx2.presentation import Presentation
|
|
43
|
+
from pptx2.shapes.placeholder import LayoutPlaceholder, MasterPlaceholder
|
|
44
|
+
from pptx2.shapes.shapetree import NotesSlidePlaceholder
|
|
45
|
+
from pptx2.smart_art import SmartArtCollection
|
|
46
|
+
from pptx2.text.text import TextFrame
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _BaseSlide(PartElementProxy):
|
|
50
|
+
"""Base class for slide objects, including masters, layouts and notes."""
|
|
51
|
+
|
|
52
|
+
_element: CT_Slide
|
|
53
|
+
|
|
54
|
+
@lazyproperty
|
|
55
|
+
def background(self) -> _Background:
|
|
56
|
+
"""|_Background| object providing slide background properties.
|
|
57
|
+
|
|
58
|
+
This property returns a |_Background| object whether or not the
|
|
59
|
+
slide, master, or layout has an explicitly defined background.
|
|
60
|
+
|
|
61
|
+
The same |_Background| object is returned on every call for the same
|
|
62
|
+
slide object.
|
|
63
|
+
"""
|
|
64
|
+
return _Background(self._element.cSld)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def name(self) -> str:
|
|
68
|
+
"""String representing the internal name of this slide.
|
|
69
|
+
|
|
70
|
+
Returns an empty string (`''`) if no name is assigned. Assigning an empty string or |None|
|
|
71
|
+
to this property causes any name to be removed.
|
|
72
|
+
"""
|
|
73
|
+
return self._element.cSld.name
|
|
74
|
+
|
|
75
|
+
@name.setter
|
|
76
|
+
def name(self, value: str | None):
|
|
77
|
+
new_value = "" if value is None else value
|
|
78
|
+
self._element.cSld.name = new_value
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _BaseMaster(_BaseSlide):
|
|
82
|
+
"""Base class for master objects such as |SlideMaster| and |NotesMaster|.
|
|
83
|
+
|
|
84
|
+
Provides access to placeholders and regular shapes.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
@lazyproperty
|
|
88
|
+
def placeholders(self) -> MasterPlaceholders:
|
|
89
|
+
"""|MasterPlaceholders| collection of placeholder shapes in this master.
|
|
90
|
+
|
|
91
|
+
Sequence sorted in `idx` order.
|
|
92
|
+
"""
|
|
93
|
+
return MasterPlaceholders(self._element.spTree, self)
|
|
94
|
+
|
|
95
|
+
@lazyproperty
|
|
96
|
+
def shapes(self):
|
|
97
|
+
"""
|
|
98
|
+
Instance of |MasterShapes| containing sequence of shape objects
|
|
99
|
+
appearing on this slide.
|
|
100
|
+
"""
|
|
101
|
+
return MasterShapes(self._element.spTree, self)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class NotesMaster(_BaseMaster):
|
|
105
|
+
"""Proxy for the notes master XML document.
|
|
106
|
+
|
|
107
|
+
Provides access to shapes, the most commonly used of which are placeholders.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class NotesSlide(_BaseSlide):
|
|
112
|
+
"""Notes slide object.
|
|
113
|
+
|
|
114
|
+
Provides access to slide notes placeholder and other shapes on the notes handout
|
|
115
|
+
page.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
element: CT_NotesSlide # pyright: ignore[reportIncompatibleMethodOverride]
|
|
119
|
+
|
|
120
|
+
def clone_master_placeholders(self, notes_master: NotesMaster) -> None:
|
|
121
|
+
"""Selectively add placeholder shape elements from `notes_master`.
|
|
122
|
+
|
|
123
|
+
Selected placeholder shape elements from `notes_master` are added to the shapes
|
|
124
|
+
collection of this notes slide. Z-order of placeholders is preserved. Certain
|
|
125
|
+
placeholders (header, date, footer) are not cloned.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def iter_cloneable_placeholders() -> Iterator[MasterPlaceholder]:
|
|
129
|
+
"""Generate a reference to each cloneable placeholder in `notes_master`.
|
|
130
|
+
|
|
131
|
+
These are the placeholders that should be cloned to a notes slide when the a new notes
|
|
132
|
+
slide is created.
|
|
133
|
+
"""
|
|
134
|
+
cloneable = (
|
|
135
|
+
PP_PLACEHOLDER.SLIDE_IMAGE,
|
|
136
|
+
PP_PLACEHOLDER.BODY,
|
|
137
|
+
PP_PLACEHOLDER.SLIDE_NUMBER,
|
|
138
|
+
)
|
|
139
|
+
for placeholder in notes_master.placeholders:
|
|
140
|
+
if placeholder.element.ph_type in cloneable:
|
|
141
|
+
yield placeholder
|
|
142
|
+
|
|
143
|
+
shapes = self.shapes
|
|
144
|
+
for placeholder in iter_cloneable_placeholders():
|
|
145
|
+
shapes.clone_placeholder(cast("LayoutPlaceholder", placeholder))
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def notes_placeholder(self) -> NotesSlidePlaceholder | None:
|
|
149
|
+
"""the notes placeholder on this notes slide, the shape that contains the actual notes text.
|
|
150
|
+
|
|
151
|
+
Return |None| if no notes placeholder is present; while this is probably uncommon, it can
|
|
152
|
+
happen if the notes master does not have a body placeholder, or if the notes placeholder
|
|
153
|
+
has been deleted from the notes slide.
|
|
154
|
+
"""
|
|
155
|
+
for placeholder in self.placeholders:
|
|
156
|
+
if placeholder.placeholder_format.type == PP_PLACEHOLDER.BODY:
|
|
157
|
+
return placeholder
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def notes_text_frame(self) -> TextFrame | None:
|
|
162
|
+
"""The text frame of the notes placeholder on this notes slide.
|
|
163
|
+
|
|
164
|
+
|None| if there is no notes placeholder. This is a shortcut to accommodate the common case
|
|
165
|
+
of simply adding "notes" text to the notes "page".
|
|
166
|
+
"""
|
|
167
|
+
notes_placeholder = self.notes_placeholder
|
|
168
|
+
if notes_placeholder is None:
|
|
169
|
+
return None
|
|
170
|
+
return notes_placeholder.text_frame
|
|
171
|
+
|
|
172
|
+
@lazyproperty
|
|
173
|
+
def placeholders(self) -> NotesSlidePlaceholders:
|
|
174
|
+
"""Instance of |NotesSlidePlaceholders| for this notes-slide.
|
|
175
|
+
|
|
176
|
+
Contains the sequence of placeholder shapes in this notes slide.
|
|
177
|
+
"""
|
|
178
|
+
return NotesSlidePlaceholders(self.element.spTree, self)
|
|
179
|
+
|
|
180
|
+
@lazyproperty
|
|
181
|
+
def shapes(self) -> NotesSlideShapes:
|
|
182
|
+
"""Sequence of shape objects appearing on this notes slide."""
|
|
183
|
+
return NotesSlideShapes(self._element.spTree, self)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class SlideTransition(object):
|
|
187
|
+
"""Provides access to the transition into a slide.
|
|
188
|
+
|
|
189
|
+
A |SlideTransition| object is returned by :attr:`Slide.transition`
|
|
190
|
+
whether or not an explicit ``<p:transition>`` element is present on the
|
|
191
|
+
slide; reads on properties of an absent transition return |None| and
|
|
192
|
+
never mutate the underlying XML, so theme inheritance is preserved.
|
|
193
|
+
|
|
194
|
+
Setting any property creates the ``<p:transition>`` element on demand;
|
|
195
|
+
use :meth:`clear` to remove the element entirely (restoring the default
|
|
196
|
+
"no explicit transition" state).
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def __init__(self, sld_elm):
|
|
200
|
+
self._sld = sld_elm
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def kind(self) -> MSO_TRANSITION_TYPE | None:
|
|
204
|
+
"""Transition kind as :ref:`MsoTransitionType`, or |None| if not set."""
|
|
205
|
+
transition = self._sld.transition
|
|
206
|
+
if transition is None:
|
|
207
|
+
return None
|
|
208
|
+
kind_elm = transition.kind_element
|
|
209
|
+
if kind_elm is None:
|
|
210
|
+
# explicit `<p:transition/>` with no child means "cut" / no animation
|
|
211
|
+
return MSO_TRANSITION_TYPE.NONE
|
|
212
|
+
local = kind_elm.tag.rsplit("}", 1)[-1]
|
|
213
|
+
try:
|
|
214
|
+
return MSO_TRANSITION_TYPE.from_xml(local)
|
|
215
|
+
except ValueError:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
@kind.setter
|
|
219
|
+
def kind(self, value: MSO_TRANSITION_TYPE | None) -> None:
|
|
220
|
+
if value is None:
|
|
221
|
+
self.clear()
|
|
222
|
+
return
|
|
223
|
+
if not isinstance(value, MSO_TRANSITION_TYPE):
|
|
224
|
+
raise TypeError(
|
|
225
|
+
"kind must be a MSO_TRANSITION_TYPE member or None, got %r" % (value,)
|
|
226
|
+
)
|
|
227
|
+
transition = self._sld.get_or_add_transition()
|
|
228
|
+
# remove any pre-existing kind child
|
|
229
|
+
existing = transition.kind_element
|
|
230
|
+
if existing is not None:
|
|
231
|
+
transition.remove(existing)
|
|
232
|
+
if value is MSO_TRANSITION_TYPE.NONE:
|
|
233
|
+
return
|
|
234
|
+
local = value.xml_value
|
|
235
|
+
if local in P159_TRANSITION_NAMES:
|
|
236
|
+
prefix = "p159"
|
|
237
|
+
elif local in P14_TRANSITION_NAMES:
|
|
238
|
+
prefix = "p14"
|
|
239
|
+
else:
|
|
240
|
+
prefix = "p"
|
|
241
|
+
kind_elm = etree.Element(
|
|
242
|
+
qn("%s:%s" % (prefix, local)),
|
|
243
|
+
nsmap={prefix: _PREFIX_TO_URI[prefix]},
|
|
244
|
+
)
|
|
245
|
+
# insert at position 0 (before any sndAc/extLst)
|
|
246
|
+
transition.insert(0, kind_elm)
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def duration(self) -> int | None:
|
|
250
|
+
"""Transition duration in milliseconds, or |None| if not explicitly set.
|
|
251
|
+
|
|
252
|
+
Resolves the ``p14:dur`` attribute (PowerPoint 2010+ extension) if
|
|
253
|
+
present; falls back to mapping the legacy ``spd`` bucket
|
|
254
|
+
(``slow``/``med``/``fast`` ↔ 1000/750/500 ms) otherwise.
|
|
255
|
+
"""
|
|
256
|
+
transition = self._sld.transition
|
|
257
|
+
if transition is None:
|
|
258
|
+
return None
|
|
259
|
+
dur_attr = transition.get(qn("p14:dur"))
|
|
260
|
+
if dur_attr is not None:
|
|
261
|
+
try:
|
|
262
|
+
return int(dur_attr)
|
|
263
|
+
except ValueError:
|
|
264
|
+
return None
|
|
265
|
+
spd = transition.spd
|
|
266
|
+
if spd is None:
|
|
267
|
+
return None
|
|
268
|
+
return _SPD_TO_MS.get(spd)
|
|
269
|
+
|
|
270
|
+
@duration.setter
|
|
271
|
+
def duration(self, ms: int | None) -> None:
|
|
272
|
+
if ms is None:
|
|
273
|
+
# clearing on a slide that inherits should be a no-op, not a
|
|
274
|
+
# mutation that introduces an empty `<p:transition>` element
|
|
275
|
+
transition = self._sld.transition
|
|
276
|
+
if transition is None:
|
|
277
|
+
return
|
|
278
|
+
transition.attrib.pop(qn("p14:dur"), None)
|
|
279
|
+
# also drop the legacy `spd` bucket; otherwise the getter falls
|
|
280
|
+
# back to it and reads as still-explicitly-set
|
|
281
|
+
transition.spd = None
|
|
282
|
+
return
|
|
283
|
+
if ms < 0:
|
|
284
|
+
raise ValueError("duration must be a non-negative integer (milliseconds)")
|
|
285
|
+
transition = self._sld.get_or_add_transition()
|
|
286
|
+
transition.set(qn("p14:dur"), str(int(ms)))
|
|
287
|
+
# writing an explicit ms duration supersedes any legacy bucket
|
|
288
|
+
transition.spd = None
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def advance_on_click(self) -> bool | None:
|
|
292
|
+
"""Whether the slide advances on mouse-click; |None| if unset."""
|
|
293
|
+
transition = self._sld.transition
|
|
294
|
+
if transition is None:
|
|
295
|
+
return None
|
|
296
|
+
return transition.advClick
|
|
297
|
+
|
|
298
|
+
@advance_on_click.setter
|
|
299
|
+
def advance_on_click(self, value: bool | None) -> None:
|
|
300
|
+
if value is None:
|
|
301
|
+
transition = self._sld.transition
|
|
302
|
+
if transition is None:
|
|
303
|
+
return
|
|
304
|
+
transition.advClick = None
|
|
305
|
+
return
|
|
306
|
+
transition = self._sld.get_or_add_transition()
|
|
307
|
+
transition.advClick = bool(value)
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def advance_after(self) -> int | None:
|
|
311
|
+
"""Auto-advance time (milliseconds), or |None| if not auto-advancing."""
|
|
312
|
+
transition = self._sld.transition
|
|
313
|
+
if transition is None:
|
|
314
|
+
return None
|
|
315
|
+
return transition.advTm
|
|
316
|
+
|
|
317
|
+
@advance_after.setter
|
|
318
|
+
def advance_after(self, ms: int | None) -> None:
|
|
319
|
+
if ms is None:
|
|
320
|
+
transition = self._sld.transition
|
|
321
|
+
if transition is None:
|
|
322
|
+
return
|
|
323
|
+
transition.advTm = None
|
|
324
|
+
return
|
|
325
|
+
if ms < 0:
|
|
326
|
+
raise ValueError("advance_after must be a non-negative integer (milliseconds)")
|
|
327
|
+
transition = self._sld.get_or_add_transition()
|
|
328
|
+
transition.advTm = int(ms)
|
|
329
|
+
|
|
330
|
+
def clear(self) -> None:
|
|
331
|
+
"""Remove the ``<p:transition>`` element entirely.
|
|
332
|
+
|
|
333
|
+
After this call, the slide has no explicit transition; reads return
|
|
334
|
+
|None| again. Idempotent: safe to call when no transition is set.
|
|
335
|
+
"""
|
|
336
|
+
self._sld._remove_transition()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
_SPD_TO_MS = {"slow": 1000, "med": 750, "fast": 500}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
_PREFIX_TO_URI = {
|
|
343
|
+
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
|
|
344
|
+
"p14": "http://schemas.microsoft.com/office/powerpoint/2010/main",
|
|
345
|
+
"p159": "http://schemas.microsoft.com/office/powerpoint/2015/09/main",
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# -- imported here to avoid a circular import at module load time --
|
|
350
|
+
from lxml import etree # noqa: E402
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class Slide(_BaseSlide):
|
|
354
|
+
"""Slide object. Provides access to shapes and slide-level properties."""
|
|
355
|
+
|
|
356
|
+
part: SlidePart # pyright: ignore[reportIncompatibleMethodOverride]
|
|
357
|
+
|
|
358
|
+
@lazyproperty
|
|
359
|
+
def animations(self) -> SlideAnimations:
|
|
360
|
+
"""Return a |SlideAnimations| object for adding animation effects to this slide.
|
|
361
|
+
|
|
362
|
+
Animations are appended to the slide's timing tree in the order they
|
|
363
|
+
are added. Existing animations (e.g. authored in PowerPoint) are
|
|
364
|
+
preserved and new effects are appended after them.
|
|
365
|
+
|
|
366
|
+
Example::
|
|
367
|
+
|
|
368
|
+
from pptx2.animation import Entrance, Trigger
|
|
369
|
+
|
|
370
|
+
Entrance.fade(slide, shape)
|
|
371
|
+
Entrance.fly_in(slide, shape2, direction="left",
|
|
372
|
+
trigger=Trigger.WITH_PREVIOUS)
|
|
373
|
+
"""
|
|
374
|
+
from pptx2.animation import SlideAnimations
|
|
375
|
+
|
|
376
|
+
return SlideAnimations(self)
|
|
377
|
+
|
|
378
|
+
def render_thumbnail(self, **kwargs):
|
|
379
|
+
"""Render this slide to a PNG via headless LibreOffice.
|
|
380
|
+
|
|
381
|
+
Thin wrapper around :func:`pptx2.render.render_slide_thumbnail`.
|
|
382
|
+
Forwards `out_path`, `soffice_bin`, `timeout`, and `return_bytes`
|
|
383
|
+
keyword arguments. Requires ``soffice`` on PATH.
|
|
384
|
+
"""
|
|
385
|
+
from pptx2.render import render_slide_thumbnail
|
|
386
|
+
|
|
387
|
+
return render_slide_thumbnail(self, **kwargs)
|
|
388
|
+
|
|
389
|
+
def lint_group(self, name: str | None, *shapes) -> None:
|
|
390
|
+
"""Tag every shape in *shapes* with ``lint_group = name``.
|
|
391
|
+
|
|
392
|
+
Convenience batch form of ``shape.lint_group = name``. Shapes that
|
|
393
|
+
share a non-empty ``lint_group`` are allowed to overlap without
|
|
394
|
+
producing :class:`~pptx2.lint.ShapeCollision` warnings.
|
|
395
|
+
|
|
396
|
+
Example::
|
|
397
|
+
|
|
398
|
+
slide.lint_group("kpi-card-1", card, accent_bar, label_box, value_box)
|
|
399
|
+
|
|
400
|
+
Passing ``name=None`` clears the tag on each supplied shape.
|
|
401
|
+
"""
|
|
402
|
+
for shape in shapes:
|
|
403
|
+
shape.lint_group = name
|
|
404
|
+
|
|
405
|
+
def lint_group_overlaps(self, *shapes, name: str | None = None) -> str:
|
|
406
|
+
"""Tag *shapes* as a co-overlapping design group, returning the group name.
|
|
407
|
+
|
|
408
|
+
Convenience over :meth:`lint_group` that also auto-generates a
|
|
409
|
+
unique-on-the-slide group name when one isn't supplied, so
|
|
410
|
+
callers don't have to invent ``"kpi-card-1"`` / ``"kpi-card-2"``
|
|
411
|
+
labels by hand. Inspired by IMPROVEMENT_PLAN.md item 12 — the
|
|
412
|
+
single-line equivalent of:
|
|
413
|
+
|
|
414
|
+
slide.lint_group(f"design-group-{n}", *shapes)
|
|
415
|
+
|
|
416
|
+
Example::
|
|
417
|
+
|
|
418
|
+
slide.lint_group_overlaps(card, accent_bar, label, value)
|
|
419
|
+
|
|
420
|
+
When *name* is supplied it is used verbatim (matching
|
|
421
|
+
:meth:`lint_group`). Otherwise a name of the form
|
|
422
|
+
``"design-group-N"`` is chosen, where ``N`` starts at 1 and
|
|
423
|
+
increments to the smallest positive integer that doesn't
|
|
424
|
+
already appear as a ``lint_group`` tag on this slide.
|
|
425
|
+
"""
|
|
426
|
+
if not shapes:
|
|
427
|
+
raise ValueError(
|
|
428
|
+
"lint_group_overlaps requires at least one shape; got 0"
|
|
429
|
+
)
|
|
430
|
+
if name is None:
|
|
431
|
+
existing = {
|
|
432
|
+
getattr(s, "lint_group", None)
|
|
433
|
+
for s in self.shapes
|
|
434
|
+
} - {None, ""}
|
|
435
|
+
n = 1
|
|
436
|
+
while f"design-group-{n}" in existing:
|
|
437
|
+
n += 1
|
|
438
|
+
name = f"design-group-{n}"
|
|
439
|
+
for shape in shapes:
|
|
440
|
+
shape.lint_group = name
|
|
441
|
+
return name
|
|
442
|
+
|
|
443
|
+
@contextmanager
|
|
444
|
+
def design_group(self, name: str):
|
|
445
|
+
"""Context manager that auto-tags shapes added inside the block.
|
|
446
|
+
|
|
447
|
+
Any shape appended to this slide's shape tree while the block is
|
|
448
|
+
active receives ``lint_group = name`` (provided it doesn't already
|
|
449
|
+
have a non-empty group, so nested ``design_group`` calls behave
|
|
450
|
+
intuitively — the innermost label wins).
|
|
451
|
+
|
|
452
|
+
Example::
|
|
453
|
+
|
|
454
|
+
with slide.design_group("kpi-card-1"):
|
|
455
|
+
slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, ...) # card
|
|
456
|
+
slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, ...) # accent
|
|
457
|
+
slide.shapes.add_textbox(...) # label
|
|
458
|
+
slide.shapes.add_textbox(...) # value
|
|
459
|
+
# All four now share ``lint_group = "kpi-card-1"``.
|
|
460
|
+
"""
|
|
461
|
+
if not isinstance(name, str) or not name:
|
|
462
|
+
raise ValueError("design_group name must be a non-empty string")
|
|
463
|
+
|
|
464
|
+
sp_tree = self._element.spTree
|
|
465
|
+
before = {id(elm) for elm in sp_tree.iter_shape_elms()}
|
|
466
|
+
try:
|
|
467
|
+
yield
|
|
468
|
+
finally:
|
|
469
|
+
from pptx2.lint import _read_lint_group, _write_lint_group
|
|
470
|
+
|
|
471
|
+
for elm in sp_tree.iter_shape_elms():
|
|
472
|
+
if id(elm) in before:
|
|
473
|
+
continue
|
|
474
|
+
try:
|
|
475
|
+
cNvPr = elm._nvXxPr.cNvPr
|
|
476
|
+
except AttributeError:
|
|
477
|
+
continue
|
|
478
|
+
# Don't overwrite an explicit group set by the caller or by
|
|
479
|
+
# an inner ``design_group`` block that already tagged this
|
|
480
|
+
# shape.
|
|
481
|
+
if _read_lint_group(cNvPr):
|
|
482
|
+
continue
|
|
483
|
+
_write_lint_group(cNvPr, name)
|
|
484
|
+
|
|
485
|
+
def lint(
|
|
486
|
+
self,
|
|
487
|
+
*,
|
|
488
|
+
include_effect_bleed: bool = False,
|
|
489
|
+
disable=(),
|
|
490
|
+
min_severity="info",
|
|
491
|
+
) -> SlideLintReport:
|
|
492
|
+
"""Inspect this slide for geometric and typographic issues.
|
|
493
|
+
|
|
494
|
+
Returns a |SlideLintReport| with a list of detected issues (text
|
|
495
|
+
overflow, shapes off-slide, shape collisions). The report is
|
|
496
|
+
generated fresh on each call.
|
|
497
|
+
|
|
498
|
+
*include_effect_bleed* (opt-in, default |False|) widens each
|
|
499
|
+
shape's bbox by its shadow blur radius before the OffSlide and
|
|
500
|
+
ShapeCollision checks run. Bleed-only issues are emitted as
|
|
501
|
+
``OffSlideShadow`` / ``ShapeCollisionShadow`` so callers can
|
|
502
|
+
suppress them via ``shape.lint_skip`` without losing real
|
|
503
|
+
geometry warnings.
|
|
504
|
+
|
|
505
|
+
*disable* is an iterable of issue ``code`` values to skip
|
|
506
|
+
entirely — e.g. ``disable=["ShapeCollision"]``.
|
|
507
|
+
|
|
508
|
+
*min_severity* drops issues below the named threshold from the
|
|
509
|
+
report (``"info"`` / ``"warning"`` / ``"error"``).
|
|
510
|
+
|
|
511
|
+
Example::
|
|
512
|
+
|
|
513
|
+
report = slide.lint()
|
|
514
|
+
if report.has_errors:
|
|
515
|
+
print(report.summary())
|
|
516
|
+
"""
|
|
517
|
+
from pptx2.lint import lint_slide
|
|
518
|
+
|
|
519
|
+
return lint_slide(
|
|
520
|
+
self,
|
|
521
|
+
include_effect_bleed=include_effect_bleed,
|
|
522
|
+
disable=disable,
|
|
523
|
+
min_severity=min_severity,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
@property
|
|
527
|
+
def follow_master_background(self):
|
|
528
|
+
"""|True| if this slide inherits the slide master background.
|
|
529
|
+
|
|
530
|
+
Read-only. Inheritance is broken as a side-effect of giving the
|
|
531
|
+
slide its own background rather than by assigning here: touching
|
|
532
|
+
:attr:`background` adds a ``<p:bg>`` element, after which this
|
|
533
|
+
reports |False|.
|
|
534
|
+
|
|
535
|
+
::
|
|
536
|
+
|
|
537
|
+
slide.follow_master_background # True
|
|
538
|
+
slide.background.fill.solid()
|
|
539
|
+
slide.background.fill.fore_color.rgb = "#102030"
|
|
540
|
+
slide.follow_master_background # now False
|
|
541
|
+
|
|
542
|
+
To restore inheritance, remove the slide's own background.
|
|
543
|
+
"""
|
|
544
|
+
return self._element.bg is None
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def has_notes_slide(self) -> bool:
|
|
548
|
+
"""`True` if this slide has a notes slide, `False` otherwise.
|
|
549
|
+
|
|
550
|
+
A notes slide is created by :attr:`.notes_slide` when one doesn't exist; use this property
|
|
551
|
+
to test for a notes slide without the possible side effect of creating one.
|
|
552
|
+
"""
|
|
553
|
+
return self.part.has_notes_slide
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def notes_slide(self) -> NotesSlide:
|
|
557
|
+
"""The |NotesSlide| instance for this slide.
|
|
558
|
+
|
|
559
|
+
If the slide does not have a notes slide, one is created. The same single instance is
|
|
560
|
+
returned on each call.
|
|
561
|
+
"""
|
|
562
|
+
return self.part.notes_slide
|
|
563
|
+
|
|
564
|
+
@property
|
|
565
|
+
def notes(self) -> str:
|
|
566
|
+
"""The speaker-notes text for this slide as a |str|.
|
|
567
|
+
|
|
568
|
+
Returns an empty string when this slide has no notes slide or its notes
|
|
569
|
+
placeholder is empty. This is a first-class, LLM-friendly shortcut over
|
|
570
|
+
:attr:`.notes_slide` / :attr:`~.NotesSlide.notes_text_frame`; reading it
|
|
571
|
+
never creates a notes slide.
|
|
572
|
+
|
|
573
|
+
Assigning a string writes the notes text, creating the notes slide (and
|
|
574
|
+
therefore its notes placeholder) on demand::
|
|
575
|
+
|
|
576
|
+
slide.notes = "Remember to thank the sponsors."
|
|
577
|
+
"""
|
|
578
|
+
if not self.has_notes_slide:
|
|
579
|
+
return ""
|
|
580
|
+
text_frame = self.notes_slide.notes_text_frame
|
|
581
|
+
if text_frame is None:
|
|
582
|
+
return ""
|
|
583
|
+
return text_frame.text
|
|
584
|
+
|
|
585
|
+
@notes.setter
|
|
586
|
+
def notes(self, text: str) -> None:
|
|
587
|
+
text_frame = self.notes_slide.notes_text_frame
|
|
588
|
+
if text_frame is None:
|
|
589
|
+
raise ValueError("notes slide has no notes placeholder; cannot set notes text")
|
|
590
|
+
text_frame.text = text
|
|
591
|
+
|
|
592
|
+
@lazyproperty
|
|
593
|
+
def placeholders(self) -> SlidePlaceholders:
|
|
594
|
+
"""Sequence of placeholder shapes in this slide."""
|
|
595
|
+
return SlidePlaceholders(self._element.spTree, self)
|
|
596
|
+
|
|
597
|
+
@lazyproperty
|
|
598
|
+
def shapes(self) -> SlideShapes:
|
|
599
|
+
"""Sequence of shape objects appearing on this slide."""
|
|
600
|
+
return SlideShapes(self._element.spTree, self)
|
|
601
|
+
|
|
602
|
+
def slide_bbox(self):
|
|
603
|
+
"""Return the slide's full area as a :class:`~pptx2.geometry.BBox`."""
|
|
604
|
+
from pptx2.geometry import BBox
|
|
605
|
+
|
|
606
|
+
return BBox.from_slide(self)
|
|
607
|
+
|
|
608
|
+
def content_bbox(self, *, include_decorative: bool = False):
|
|
609
|
+
"""Return the bounding box covering all non-decorative shapes.
|
|
610
|
+
|
|
611
|
+
``include_decorative=False`` (the default) skips slide-spanning
|
|
612
|
+
backgrounds (any shape whose width and height each exceed 95%
|
|
613
|
+
of the slide area), so the returned box reflects "where the
|
|
614
|
+
real content is" rather than the full slide.
|
|
615
|
+
|
|
616
|
+
Returns ``None`` when the slide has no qualifying shapes.
|
|
617
|
+
"""
|
|
618
|
+
from pptx2.geometry import BBox
|
|
619
|
+
|
|
620
|
+
slide_box = BBox.from_slide(self)
|
|
621
|
+
union_box: BBox | None = None
|
|
622
|
+
threshold_w = int(slide_box.width) * 0.95
|
|
623
|
+
threshold_h = int(slide_box.height) * 0.95
|
|
624
|
+
for shape in self.shapes:
|
|
625
|
+
try:
|
|
626
|
+
box = BBox.from_shape(shape)
|
|
627
|
+
except Exception:
|
|
628
|
+
continue
|
|
629
|
+
if not include_decorative:
|
|
630
|
+
if (
|
|
631
|
+
int(box.width) >= threshold_w
|
|
632
|
+
and int(box.height) >= threshold_h
|
|
633
|
+
):
|
|
634
|
+
continue
|
|
635
|
+
union_box = box if union_box is None else union_box.union(box)
|
|
636
|
+
return union_box
|
|
637
|
+
|
|
638
|
+
def find_empty_region(
|
|
639
|
+
self,
|
|
640
|
+
*,
|
|
641
|
+
near=None,
|
|
642
|
+
min_width=0,
|
|
643
|
+
min_height=0,
|
|
644
|
+
):
|
|
645
|
+
"""Return a :class:`BBox` of an unused region on the slide.
|
|
646
|
+
|
|
647
|
+
Walks a coarse grid over the slide and returns the largest cell
|
|
648
|
+
(or cluster of cells) that doesn't overlap any existing
|
|
649
|
+
shape. ``near`` is an optional BBox / Shape; when given, the
|
|
650
|
+
cell whose centre is nearest its centre is preferred over
|
|
651
|
+
strictly the largest free area.
|
|
652
|
+
|
|
653
|
+
``min_width`` / ``min_height`` filter out tiny free pockets in
|
|
654
|
+
EMU. Returns ``None`` when no region meets the criteria.
|
|
655
|
+
|
|
656
|
+
Approximate by design — for one-off LLM placement decisions,
|
|
657
|
+
not pixel-perfect packing.
|
|
658
|
+
"""
|
|
659
|
+
from pptx2.geometry import BBox
|
|
660
|
+
|
|
661
|
+
slide_box = BBox.from_slide(self)
|
|
662
|
+
# 12×8 sample grid is fine-grained enough for typical decks.
|
|
663
|
+
cells = slide_box.grid(12, 8)
|
|
664
|
+
existing = []
|
|
665
|
+
for shape in self.shapes:
|
|
666
|
+
try:
|
|
667
|
+
existing.append(BBox.from_shape(shape))
|
|
668
|
+
except Exception:
|
|
669
|
+
pass
|
|
670
|
+
|
|
671
|
+
free = [c for c in cells if not any(c.intersects(s) for s in existing)]
|
|
672
|
+
if not free:
|
|
673
|
+
return None
|
|
674
|
+
|
|
675
|
+
# Merge horizontally-adjacent free cells in the same row.
|
|
676
|
+
merged: list[BBox] = []
|
|
677
|
+
for c in free:
|
|
678
|
+
if merged:
|
|
679
|
+
last = merged[-1]
|
|
680
|
+
if (
|
|
681
|
+
int(last.top) == int(c.top)
|
|
682
|
+
and int(last.right) == int(c.left)
|
|
683
|
+
):
|
|
684
|
+
merged[-1] = last.union(c)
|
|
685
|
+
continue
|
|
686
|
+
merged.append(c)
|
|
687
|
+
|
|
688
|
+
candidates = [
|
|
689
|
+
m for m in merged
|
|
690
|
+
if int(m.width) >= int(min_width)
|
|
691
|
+
and int(m.height) >= int(min_height)
|
|
692
|
+
]
|
|
693
|
+
if not candidates:
|
|
694
|
+
return None
|
|
695
|
+
|
|
696
|
+
if near is not None:
|
|
697
|
+
from pptx2.shapes.base import BaseShape
|
|
698
|
+
|
|
699
|
+
if isinstance(near, BaseShape):
|
|
700
|
+
target = BBox.from_shape(near)
|
|
701
|
+
elif isinstance(near, BBox):
|
|
702
|
+
target = near
|
|
703
|
+
else:
|
|
704
|
+
raise TypeError(
|
|
705
|
+
"find_empty_region(near=...) must be a BaseShape or BBox; "
|
|
706
|
+
f"got {type(near).__name__}"
|
|
707
|
+
)
|
|
708
|
+
tx, ty = int(target.cx), int(target.cy)
|
|
709
|
+
candidates.sort(
|
|
710
|
+
key=lambda b: (int(b.cx) - tx) ** 2 + (int(b.cy) - ty) ** 2
|
|
711
|
+
)
|
|
712
|
+
return candidates[0]
|
|
713
|
+
candidates.sort(key=lambda b: -b.area)
|
|
714
|
+
return candidates[0]
|
|
715
|
+
|
|
716
|
+
def tidy(
|
|
717
|
+
self,
|
|
718
|
+
*,
|
|
719
|
+
fix_offslide: bool = True,
|
|
720
|
+
fix_overflow: bool = True,
|
|
721
|
+
fix_grid_drift: bool = False,
|
|
722
|
+
fix_layer_order: bool = True,
|
|
723
|
+
) -> list[str]:
|
|
724
|
+
"""One-call cleanup: lint then auto-fix the safe subset.
|
|
725
|
+
|
|
726
|
+
Wraps :meth:`lint` + :meth:`SlideLintReport.auto_fix` with the
|
|
727
|
+
flags most decks want by default. Returns the list of fixes
|
|
728
|
+
applied (the same shape as ``auto_fix()``).
|
|
729
|
+
|
|
730
|
+
* ``fix_offslide`` (default ``True``) clamps shapes back
|
|
731
|
+
on-slide.
|
|
732
|
+
* ``fix_overflow`` (default ``True``) flips overflowing text
|
|
733
|
+
frames to ``MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE``.
|
|
734
|
+
* ``fix_grid_drift`` (default ``False``) snaps minor grid drift
|
|
735
|
+
— off by default because the snap can move a shape by several
|
|
736
|
+
EMU when the inferred grid is wrong.
|
|
737
|
+
* ``fix_layer_order`` (default ``True``) restacks shapes whose
|
|
738
|
+
``layer_above`` declaration is contradicted by the drawing
|
|
739
|
+
order. On by default because it only enforces an ordering
|
|
740
|
+
the author already declared, and it never moves geometry.
|
|
741
|
+
"""
|
|
742
|
+
disable: list[str] = []
|
|
743
|
+
if not fix_offslide:
|
|
744
|
+
disable.append("OffSlide")
|
|
745
|
+
if not fix_overflow:
|
|
746
|
+
disable.append("TextOverflow")
|
|
747
|
+
if not fix_grid_drift:
|
|
748
|
+
disable.append("OffGridDrift")
|
|
749
|
+
if not fix_layer_order:
|
|
750
|
+
disable.append("LayerOrderViolation")
|
|
751
|
+
report = self.lint(disable=disable)
|
|
752
|
+
return report.auto_fix()
|
|
753
|
+
|
|
754
|
+
@property
|
|
755
|
+
def smart_art(self) -> SmartArtCollection:
|
|
756
|
+
"""Return a |SmartArtCollection| for this slide.
|
|
757
|
+
|
|
758
|
+
Provides indexed access to SmartArt graphics on the slide. Each item
|
|
759
|
+
is a |SmartArtShape| whose :attr:`~SmartArtShape.texts` property gives
|
|
760
|
+
the current text list and :meth:`~SmartArtShape.set_text` replaces it.
|
|
761
|
+
|
|
762
|
+
Example::
|
|
763
|
+
|
|
764
|
+
org_chart = slide.smart_art[0]
|
|
765
|
+
print(org_chart.texts) # ['CEO', 'CTO', 'CFO']
|
|
766
|
+
org_chart.set_text(['Alice', 'Bob', 'Carol'])
|
|
767
|
+
|
|
768
|
+
Returns an empty collection (length 0) when there are no SmartArt
|
|
769
|
+
shapes on the slide.
|
|
770
|
+
"""
|
|
771
|
+
from pptx2.smart_art import SmartArtCollection
|
|
772
|
+
|
|
773
|
+
return SmartArtCollection(self)
|
|
774
|
+
|
|
775
|
+
@property
|
|
776
|
+
def slide_id(self) -> int:
|
|
777
|
+
"""Integer value that uniquely identifies this slide within this presentation.
|
|
778
|
+
|
|
779
|
+
The slide id does not change if the position of this slide in the slide sequence is changed
|
|
780
|
+
by adding, rearranging, or deleting slides.
|
|
781
|
+
"""
|
|
782
|
+
return self.part.slide_id
|
|
783
|
+
|
|
784
|
+
@property
|
|
785
|
+
def slide_layout(self) -> SlideLayout:
|
|
786
|
+
"""|SlideLayout| object this slide inherits appearance from."""
|
|
787
|
+
return self.part.slide_layout
|
|
788
|
+
|
|
789
|
+
@property
|
|
790
|
+
def color_variant(self) -> str | None:
|
|
791
|
+
"""Per-slide color-mapping variant: ``"light"`` / ``"dark"`` / |None|.
|
|
792
|
+
|
|
793
|
+
Reads / writes the ``<p:clrMapOvr>`` element to apply a built-in
|
|
794
|
+
light or dark variant of the deck's master color map. ``"light"``
|
|
795
|
+
is the master's default mapping (``bg1=lt1``, ``tx1=dk1``, …);
|
|
796
|
+
``"dark"`` swaps backgrounds and text (``bg1=dk1``, ``tx1=lt1``,
|
|
797
|
+
…) for a dark-on-light slide without changing the deck theme.
|
|
798
|
+
|
|
799
|
+
Reading returns:
|
|
800
|
+
|
|
801
|
+
* ``"dark"`` — slide has an explicit override that swaps bg/tx.
|
|
802
|
+
* ``"light"`` — slide inherits from the master (or has an
|
|
803
|
+
explicit ``<a:masterClrMapping/>`` element).
|
|
804
|
+
* ``None`` — slide has a custom override that doesn't match
|
|
805
|
+
either of the two named variants.
|
|
806
|
+
|
|
807
|
+
Assigning ``None`` removes the override entirely (returning to
|
|
808
|
+
master inheritance).
|
|
809
|
+
|
|
810
|
+
For more flexible control, use :meth:`set_clr_map_override`.
|
|
811
|
+
"""
|
|
812
|
+
clr_ovr = self._element.clrMapOvr
|
|
813
|
+
if clr_ovr is None:
|
|
814
|
+
return "light"
|
|
815
|
+
# If <a:masterClrMapping/> is present we're inheriting.
|
|
816
|
+
for child in clr_ovr:
|
|
817
|
+
local = child.tag.rsplit("}", 1)[-1]
|
|
818
|
+
if local == "masterClrMapping":
|
|
819
|
+
return "light"
|
|
820
|
+
if local == "overrideClrMapping":
|
|
821
|
+
if (
|
|
822
|
+
child.get("bg1") == "dk1"
|
|
823
|
+
and child.get("tx1") == "lt1"
|
|
824
|
+
and child.get("bg2") == "dk2"
|
|
825
|
+
and child.get("tx2") == "lt2"
|
|
826
|
+
):
|
|
827
|
+
return "dark"
|
|
828
|
+
return None
|
|
829
|
+
return None
|
|
830
|
+
|
|
831
|
+
@color_variant.setter
|
|
832
|
+
def color_variant(self, value: str | None) -> None:
|
|
833
|
+
if value is None:
|
|
834
|
+
self._element._remove_clrMapOvr()
|
|
835
|
+
return
|
|
836
|
+
if value == "light":
|
|
837
|
+
self.set_clr_map_override(masterClrMapping=True)
|
|
838
|
+
return
|
|
839
|
+
if value == "dark":
|
|
840
|
+
self.set_clr_map_override(
|
|
841
|
+
bg1="dk1", tx1="lt1", bg2="dk2", tx2="lt2",
|
|
842
|
+
accent1="accent1", accent2="accent2", accent3="accent3",
|
|
843
|
+
accent4="accent4", accent5="accent5", accent6="accent6",
|
|
844
|
+
hlink="hlink", folHlink="folHlink",
|
|
845
|
+
)
|
|
846
|
+
return
|
|
847
|
+
raise ValueError(
|
|
848
|
+
f"color_variant must be 'light', 'dark', or None; got {value!r}"
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
def set_clr_map_override(self, *, masterClrMapping: bool = False, **mapping: str) -> None:
|
|
852
|
+
"""Set this slide's ``<p:clrMapOvr>`` element directly.
|
|
853
|
+
|
|
854
|
+
With ``masterClrMapping=True`` (and no other args), removes any
|
|
855
|
+
existing override and writes ``<a:masterClrMapping/>`` so the
|
|
856
|
+
slide inherits the master's color map.
|
|
857
|
+
|
|
858
|
+
Otherwise, writes an ``<a:overrideClrMapping>`` with the supplied
|
|
859
|
+
attributes. Standard mapping attributes are ``bg1``, ``tx1``,
|
|
860
|
+
``bg2``, ``tx2``, ``accent1``..``accent6``, ``hlink``,
|
|
861
|
+
``folHlink``; each value is the slot it should resolve to
|
|
862
|
+
(e.g. ``bg1="dk1"`` redirects "background 1" lookups to the
|
|
863
|
+
``dk1`` palette slot).
|
|
864
|
+
|
|
865
|
+
Use :attr:`color_variant` for the common light/dark presets.
|
|
866
|
+
"""
|
|
867
|
+
clr_ovr = self._element.get_or_add_clrMapOvr()
|
|
868
|
+
# Clear existing children.
|
|
869
|
+
for child in list(clr_ovr):
|
|
870
|
+
clr_ovr.remove(child)
|
|
871
|
+
|
|
872
|
+
a_ns = "http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
873
|
+
if masterClrMapping and not mapping:
|
|
874
|
+
child = etree.SubElement(clr_ovr, "{%s}masterClrMapping" % a_ns)
|
|
875
|
+
return
|
|
876
|
+
child = etree.SubElement(clr_ovr, "{%s}overrideClrMapping" % a_ns)
|
|
877
|
+
for k, v in mapping.items():
|
|
878
|
+
child.set(k, v)
|
|
879
|
+
|
|
880
|
+
@lazyproperty
|
|
881
|
+
def transition(self) -> SlideTransition:
|
|
882
|
+
"""|SlideTransition| object describing the transition into this slide.
|
|
883
|
+
|
|
884
|
+
The same instance is returned on each call. Reads on individual
|
|
885
|
+
properties of the returned object are non-mutating; the underlying
|
|
886
|
+
``<p:transition>`` element is created only when a property is
|
|
887
|
+
assigned.
|
|
888
|
+
"""
|
|
889
|
+
return SlideTransition(self._element)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
class Slides(ParentedElementProxy):
|
|
893
|
+
"""Sequence of slides belonging to an instance of |Presentation|.
|
|
894
|
+
|
|
895
|
+
Has list semantics for access to individual slides. Supports indexed access, len(), and
|
|
896
|
+
iteration.
|
|
897
|
+
"""
|
|
898
|
+
|
|
899
|
+
part: PresentationPart # pyright: ignore[reportIncompatibleMethodOverride]
|
|
900
|
+
|
|
901
|
+
def __init__(self, sldIdLst: CT_SlideIdList, prs: Presentation):
|
|
902
|
+
super(Slides, self).__init__(sldIdLst, prs)
|
|
903
|
+
self._sldIdLst = sldIdLst
|
|
904
|
+
|
|
905
|
+
def __getitem__(self, idx: int) -> Slide:
|
|
906
|
+
"""Provide indexed access, (e.g. 'slides[0]')."""
|
|
907
|
+
try:
|
|
908
|
+
sldId = self._sldIdLst.sldId_lst[idx]
|
|
909
|
+
except IndexError:
|
|
910
|
+
raise IndexError("slide index out of range")
|
|
911
|
+
return self.part.related_slide(sldId.rId)
|
|
912
|
+
|
|
913
|
+
def __iter__(self) -> Iterator[Slide]:
|
|
914
|
+
"""Support iteration, e.g. `for slide in slides:`."""
|
|
915
|
+
for sldId in self._sldIdLst.sldId_lst:
|
|
916
|
+
yield self.part.related_slide(sldId.rId)
|
|
917
|
+
|
|
918
|
+
def __len__(self) -> int:
|
|
919
|
+
"""Support len() built-in function, e.g. `len(slides) == 4`."""
|
|
920
|
+
return len(self._sldIdLst)
|
|
921
|
+
|
|
922
|
+
def add_slide(self, slide_layout: SlideLayout) -> Slide:
|
|
923
|
+
"""Return a newly added slide that inherits layout from `slide_layout`."""
|
|
924
|
+
rId, slide = self.part.add_slide(slide_layout)
|
|
925
|
+
slide.shapes.clone_layout_placeholders(slide_layout)
|
|
926
|
+
sldId = self._sldIdLst.add_sldId(rId)
|
|
927
|
+
self._add_to_final_section(sldId.id)
|
|
928
|
+
return slide
|
|
929
|
+
|
|
930
|
+
def _add_to_final_section(self, slide_id: int) -> None:
|
|
931
|
+
"""Register a newly appended slide with the deck's final section.
|
|
932
|
+
|
|
933
|
+
PowerPoint keeps the 2010 section extension a complete partition of
|
|
934
|
+
the deck — every slide belongs to exactly one section. A slide
|
|
935
|
+
appended at the end of a sectioned deck therefore falls into the
|
|
936
|
+
last section, so mirror that here to keep the `p14:sectionLst` in
|
|
937
|
+
sync. A no-op when the deck has no sections.
|
|
938
|
+
"""
|
|
939
|
+
prs_elm = self._sldIdLst.getparent()
|
|
940
|
+
sectionLst = getattr(prs_elm, "sectionLst", None)
|
|
941
|
+
if sectionLst is None or not sectionLst.section_lst:
|
|
942
|
+
return
|
|
943
|
+
sectionLst.section_lst[-1].add_sldId(slide_id)
|
|
944
|
+
|
|
945
|
+
def get(self, slide_id: int, default: Slide | None = None) -> Slide | None:
|
|
946
|
+
"""Return the slide identified by int `slide_id` in this presentation.
|
|
947
|
+
|
|
948
|
+
Returns `default` if not found.
|
|
949
|
+
"""
|
|
950
|
+
slide = self.part.get_slide(slide_id)
|
|
951
|
+
if slide is None:
|
|
952
|
+
return default
|
|
953
|
+
return slide
|
|
954
|
+
|
|
955
|
+
def index(self, slide: Slide) -> int:
|
|
956
|
+
"""Map `slide` to its zero-based position in this slide sequence.
|
|
957
|
+
|
|
958
|
+
Raises |ValueError| on *slide* not present.
|
|
959
|
+
"""
|
|
960
|
+
for idx, this_slide in enumerate(self):
|
|
961
|
+
if this_slide == slide:
|
|
962
|
+
return idx
|
|
963
|
+
raise ValueError("%s is not in slide collection" % slide)
|
|
964
|
+
|
|
965
|
+
def move(self, old_index: int, new_index: int) -> None:
|
|
966
|
+
"""Relocate the slide at `old_index` to `new_index`, shifting the rest.
|
|
967
|
+
|
|
968
|
+
Both indices are zero-based and must be in range. PowerPoint slide order
|
|
969
|
+
follows the order of `p:sldId` children in `p:sldIdLst`, so this simply
|
|
970
|
+
detaches the corresponding element and re-inserts it at the new
|
|
971
|
+
position::
|
|
972
|
+
|
|
973
|
+
prs.slides.move(0, 2) # send the first slide to position 2
|
|
974
|
+
|
|
975
|
+
Raises |IndexError| if either index is out of range.
|
|
976
|
+
"""
|
|
977
|
+
count = len(self._sldIdLst)
|
|
978
|
+
old_index = self._normalized_index(old_index, count)
|
|
979
|
+
new_index = self._normalized_index(new_index, count)
|
|
980
|
+
sldId_lst = self._sldIdLst.sldId_lst
|
|
981
|
+
sldId = sldId_lst[old_index]
|
|
982
|
+
self._sldIdLst.remove(sldId)
|
|
983
|
+
# -- recompute the insertion reference against the post-removal order --
|
|
984
|
+
remaining = self._sldIdLst.sldId_lst
|
|
985
|
+
if new_index >= len(remaining):
|
|
986
|
+
self._sldIdLst.append(sldId)
|
|
987
|
+
else:
|
|
988
|
+
remaining[new_index].addprevious(sldId)
|
|
989
|
+
|
|
990
|
+
def reorder(self, new_order: "Sequence[int | Slide]") -> None:
|
|
991
|
+
"""Rearrange the slides into the permutation given by `new_order`.
|
|
992
|
+
|
|
993
|
+
`new_order` is a full permutation of the collection, expressed either as
|
|
994
|
+
zero-based indices into the *current* order or as the |Slide| objects
|
|
995
|
+
themselves (the two forms may not be mixed). After the call, the slide
|
|
996
|
+
that was at ``new_order[0]`` becomes the first slide, and so on::
|
|
997
|
+
|
|
998
|
+
prs.slides.reorder([2, 0, 1]) # by index
|
|
999
|
+
prs.slides.reorder([s2, s0, s1]) # by Slide object
|
|
1000
|
+
|
|
1001
|
+
Raises |ValueError| if `new_order` is not a permutation of exactly the
|
|
1002
|
+
slides in this collection (wrong length, duplicates, or unknown items).
|
|
1003
|
+
"""
|
|
1004
|
+
count = len(self._sldIdLst)
|
|
1005
|
+
order = list(new_order)
|
|
1006
|
+
if len(order) != count:
|
|
1007
|
+
raise ValueError(
|
|
1008
|
+
"new_order must contain exactly %d items, got %d" % (count, len(order))
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
sldId_lst = self._sldIdLst.sldId_lst
|
|
1012
|
+
indices: list[int] = []
|
|
1013
|
+
for item in order:
|
|
1014
|
+
if isinstance(item, Slide):
|
|
1015
|
+
indices.append(self.index(item))
|
|
1016
|
+
else:
|
|
1017
|
+
indices.append(self._normalized_index(int(item), count))
|
|
1018
|
+
|
|
1019
|
+
if sorted(indices) != list(range(count)):
|
|
1020
|
+
raise ValueError("new_order must be a permutation of the slides in this collection")
|
|
1021
|
+
|
|
1022
|
+
ordered_sldIds = [sldId_lst[i] for i in indices]
|
|
1023
|
+
for sldId in ordered_sldIds:
|
|
1024
|
+
self._sldIdLst.remove(sldId)
|
|
1025
|
+
for sldId in ordered_sldIds:
|
|
1026
|
+
self._sldIdLst.append(sldId)
|
|
1027
|
+
|
|
1028
|
+
@staticmethod
|
|
1029
|
+
def _normalized_index(idx: int, count: int) -> int:
|
|
1030
|
+
"""Return `idx` resolved against `count`, supporting negative indexing.
|
|
1031
|
+
|
|
1032
|
+
Raises |IndexError| when out of range.
|
|
1033
|
+
"""
|
|
1034
|
+
original = idx
|
|
1035
|
+
if idx < 0:
|
|
1036
|
+
idx += count
|
|
1037
|
+
if idx < 0 or idx >= count:
|
|
1038
|
+
raise IndexError("slide index out of range: %r" % original)
|
|
1039
|
+
return idx
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
class SlideLayout(_BaseSlide):
|
|
1043
|
+
"""Slide layout object.
|
|
1044
|
+
|
|
1045
|
+
Provides access to placeholders, regular shapes, and slide layout-level properties.
|
|
1046
|
+
"""
|
|
1047
|
+
|
|
1048
|
+
part: SlideLayoutPart # pyright: ignore[reportIncompatibleMethodOverride]
|
|
1049
|
+
|
|
1050
|
+
def iter_cloneable_placeholders(self) -> Iterator[LayoutPlaceholder]:
|
|
1051
|
+
"""Generate layout-placeholders on this slide-layout that should be cloned to a new slide.
|
|
1052
|
+
|
|
1053
|
+
Used when creating a new slide from this slide-layout.
|
|
1054
|
+
"""
|
|
1055
|
+
latent_ph_types = (
|
|
1056
|
+
PP_PLACEHOLDER.DATE,
|
|
1057
|
+
PP_PLACEHOLDER.FOOTER,
|
|
1058
|
+
PP_PLACEHOLDER.SLIDE_NUMBER,
|
|
1059
|
+
)
|
|
1060
|
+
for ph in self.placeholders:
|
|
1061
|
+
if ph.element.ph_type not in latent_ph_types:
|
|
1062
|
+
yield ph
|
|
1063
|
+
|
|
1064
|
+
@lazyproperty
|
|
1065
|
+
def placeholders(self) -> LayoutPlaceholders:
|
|
1066
|
+
"""Sequence of placeholder shapes in this slide layout.
|
|
1067
|
+
|
|
1068
|
+
Placeholders appear in `idx` order.
|
|
1069
|
+
"""
|
|
1070
|
+
return LayoutPlaceholders(self._element.spTree, self)
|
|
1071
|
+
|
|
1072
|
+
@lazyproperty
|
|
1073
|
+
def shapes(self) -> LayoutShapes:
|
|
1074
|
+
"""Sequence of shapes appearing on this slide layout."""
|
|
1075
|
+
return LayoutShapes(self._element.spTree, self)
|
|
1076
|
+
|
|
1077
|
+
@property
|
|
1078
|
+
def slide_master(self) -> SlideMaster:
|
|
1079
|
+
"""Slide master from which this slide-layout inherits properties."""
|
|
1080
|
+
return self.part.slide_master
|
|
1081
|
+
|
|
1082
|
+
@property
|
|
1083
|
+
def used_by_slides(self):
|
|
1084
|
+
"""Tuple of slide objects based on this slide layout."""
|
|
1085
|
+
# ---getting Slides collection requires going around the horn a bit---
|
|
1086
|
+
slides = self.part.package.presentation_part.presentation.slides
|
|
1087
|
+
return tuple(s for s in slides if s.slide_layout == self)
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
class SlideLayouts(ParentedElementProxy):
|
|
1091
|
+
"""Sequence of slide layouts belonging to a slide-master.
|
|
1092
|
+
|
|
1093
|
+
Supports indexed access, len(), iteration, index() and remove().
|
|
1094
|
+
"""
|
|
1095
|
+
|
|
1096
|
+
part: SlideMasterPart # pyright: ignore[reportIncompatibleMethodOverride]
|
|
1097
|
+
|
|
1098
|
+
def __init__(self, sldLayoutIdLst: CT_SlideLayoutIdList, parent: SlideMaster):
|
|
1099
|
+
super(SlideLayouts, self).__init__(sldLayoutIdLst, parent)
|
|
1100
|
+
self._sldLayoutIdLst = sldLayoutIdLst
|
|
1101
|
+
|
|
1102
|
+
def __getitem__(self, idx: int) -> SlideLayout:
|
|
1103
|
+
"""Provides indexed access, e.g. `slide_layouts[2]`."""
|
|
1104
|
+
try:
|
|
1105
|
+
sldLayoutId = self._sldLayoutIdLst.sldLayoutId_lst[idx]
|
|
1106
|
+
except IndexError:
|
|
1107
|
+
raise IndexError("slide layout index out of range")
|
|
1108
|
+
return self.part.related_slide_layout(sldLayoutId.rId)
|
|
1109
|
+
|
|
1110
|
+
def __iter__(self) -> Iterator[SlideLayout]:
|
|
1111
|
+
"""Generate each |SlideLayout| in the collection, in sequence."""
|
|
1112
|
+
for sldLayoutId in self._sldLayoutIdLst.sldLayoutId_lst:
|
|
1113
|
+
yield self.part.related_slide_layout(sldLayoutId.rId)
|
|
1114
|
+
|
|
1115
|
+
def __len__(self) -> int:
|
|
1116
|
+
"""Support len() built-in function, e.g. `len(slides) == 4`."""
|
|
1117
|
+
return len(self._sldLayoutIdLst)
|
|
1118
|
+
|
|
1119
|
+
def get_by_name(self, name: str, default: SlideLayout | None = None) -> SlideLayout | None:
|
|
1120
|
+
"""Return SlideLayout object having `name`, or `default` if not found."""
|
|
1121
|
+
for slide_layout in self:
|
|
1122
|
+
if slide_layout.name == name:
|
|
1123
|
+
return slide_layout
|
|
1124
|
+
return default
|
|
1125
|
+
|
|
1126
|
+
def index(self, slide_layout: SlideLayout) -> int:
|
|
1127
|
+
"""Return zero-based index of `slide_layout` in this collection.
|
|
1128
|
+
|
|
1129
|
+
Raises `ValueError` if `slide_layout` is not present in this collection.
|
|
1130
|
+
"""
|
|
1131
|
+
for idx, this_layout in enumerate(self):
|
|
1132
|
+
if slide_layout == this_layout:
|
|
1133
|
+
return idx
|
|
1134
|
+
raise ValueError("layout not in this SlideLayouts collection")
|
|
1135
|
+
|
|
1136
|
+
def remove(self, slide_layout: SlideLayout) -> None:
|
|
1137
|
+
"""Remove `slide_layout` from the collection.
|
|
1138
|
+
|
|
1139
|
+
Raises ValueError when `slide_layout` is in use; a slide layout which is the basis for one
|
|
1140
|
+
or more slides cannot be removed.
|
|
1141
|
+
"""
|
|
1142
|
+
# ---raise if layout is in use---
|
|
1143
|
+
if slide_layout.used_by_slides:
|
|
1144
|
+
raise ValueError("cannot remove slide-layout in use by one or more slides")
|
|
1145
|
+
|
|
1146
|
+
# ---target layout is identified by its index in this collection---
|
|
1147
|
+
target_idx = self.index(slide_layout)
|
|
1148
|
+
|
|
1149
|
+
# --remove layout from p:sldLayoutIds of its master
|
|
1150
|
+
# --this stops layout from showing up, but doesn't remove it from package
|
|
1151
|
+
target_sldLayoutId = self._sldLayoutIdLst.sldLayoutId_lst[target_idx]
|
|
1152
|
+
self._sldLayoutIdLst.remove(target_sldLayoutId)
|
|
1153
|
+
|
|
1154
|
+
# --drop relationship from master to layout
|
|
1155
|
+
# --this removes layout from package, along with everything (only) it refers to,
|
|
1156
|
+
# --including images (not used elsewhere) and hyperlinks
|
|
1157
|
+
slide_layout.slide_master.part.drop_rel(target_sldLayoutId.rId)
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
class SlideMaster(_BaseMaster):
|
|
1161
|
+
"""Slide master object.
|
|
1162
|
+
|
|
1163
|
+
Provides access to slide layouts. Access to placeholders, regular shapes, and slide master-level
|
|
1164
|
+
properties is inherited from |_BaseMaster|.
|
|
1165
|
+
"""
|
|
1166
|
+
|
|
1167
|
+
_element: CT_SlideMaster # pyright: ignore[reportIncompatibleVariableOverride]
|
|
1168
|
+
|
|
1169
|
+
@lazyproperty
|
|
1170
|
+
def slide_layouts(self) -> SlideLayouts:
|
|
1171
|
+
"""|SlideLayouts| object providing access to this slide-master's layouts."""
|
|
1172
|
+
return SlideLayouts(self._element.get_or_add_sldLayoutIdLst(), self)
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
class SlideMasters(ParentedElementProxy):
|
|
1176
|
+
"""Sequence of |SlideMaster| objects belonging to a presentation.
|
|
1177
|
+
|
|
1178
|
+
Has list access semantics, supporting indexed access, len(), and iteration.
|
|
1179
|
+
"""
|
|
1180
|
+
|
|
1181
|
+
part: PresentationPart # pyright: ignore[reportIncompatibleMethodOverride]
|
|
1182
|
+
|
|
1183
|
+
def __init__(self, sldMasterIdLst: CT_SlideMasterIdList, parent: Presentation):
|
|
1184
|
+
super(SlideMasters, self).__init__(sldMasterIdLst, parent)
|
|
1185
|
+
self._sldMasterIdLst = sldMasterIdLst
|
|
1186
|
+
|
|
1187
|
+
def __getitem__(self, idx: int) -> SlideMaster:
|
|
1188
|
+
"""Provides indexed access, e.g. `slide_masters[2]`."""
|
|
1189
|
+
try:
|
|
1190
|
+
sldMasterId = self._sldMasterIdLst.sldMasterId_lst[idx]
|
|
1191
|
+
except IndexError:
|
|
1192
|
+
raise IndexError("slide master index out of range")
|
|
1193
|
+
return self.part.related_slide_master(sldMasterId.rId)
|
|
1194
|
+
|
|
1195
|
+
def __iter__(self):
|
|
1196
|
+
"""Generate each |SlideMaster| instance in the collection, in sequence."""
|
|
1197
|
+
for smi in self._sldMasterIdLst.sldMasterId_lst:
|
|
1198
|
+
yield self.part.related_slide_master(smi.rId)
|
|
1199
|
+
|
|
1200
|
+
def __len__(self):
|
|
1201
|
+
"""Support len() built-in function, e.g. `len(slide_masters) == 4`."""
|
|
1202
|
+
return len(self._sldMasterIdLst)
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
class _Background(ElementProxy):
|
|
1206
|
+
"""Provides access to slide background properties.
|
|
1207
|
+
|
|
1208
|
+
Note that the presence of this object does not by itself imply an
|
|
1209
|
+
explicitly-defined background; a slide with an inherited background still
|
|
1210
|
+
has a |_Background| object.
|
|
1211
|
+
"""
|
|
1212
|
+
|
|
1213
|
+
def __init__(self, cSld: CT_CommonSlideData):
|
|
1214
|
+
super(_Background, self).__init__(cSld)
|
|
1215
|
+
self._cSld = cSld
|
|
1216
|
+
|
|
1217
|
+
@lazyproperty
|
|
1218
|
+
def fill(self):
|
|
1219
|
+
"""|FillFormat| instance for this background.
|
|
1220
|
+
|
|
1221
|
+
This |FillFormat| object is used to interrogate or specify the fill
|
|
1222
|
+
of the slide background.
|
|
1223
|
+
|
|
1224
|
+
Note that accessing this property is potentially destructive. A slide
|
|
1225
|
+
background can also be specified by a background style reference and
|
|
1226
|
+
accessing this property will remove that reference, if present, and
|
|
1227
|
+
replace it with NoFill. This is frequently the case for a slide
|
|
1228
|
+
master background.
|
|
1229
|
+
|
|
1230
|
+
This is also the case when there is no explicitly defined background
|
|
1231
|
+
(background is inherited); merely accessing this property will cause
|
|
1232
|
+
the background to be set to NoFill and the inheritance link will be
|
|
1233
|
+
interrupted. This is frequently the case for a slide background.
|
|
1234
|
+
|
|
1235
|
+
Of course, if you are accessing this property in order to set the
|
|
1236
|
+
fill, then these changes are of no consequence, but the existing
|
|
1237
|
+
background cannot be reliably interrogated using this property unless
|
|
1238
|
+
you have already established it is an explicit fill.
|
|
1239
|
+
|
|
1240
|
+
If the background is already a fill, then accessing this property
|
|
1241
|
+
makes no changes to the current background.
|
|
1242
|
+
"""
|
|
1243
|
+
bgPr = self._cSld.get_or_add_bgPr()
|
|
1244
|
+
return FillFormat.from_fill_parent(bgPr)
|