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
pptx2/parts/image.py ADDED
@@ -0,0 +1,275 @@
1
+ """ImagePart and related objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import io
7
+ import os
8
+ from typing import IO, TYPE_CHECKING, Any, cast
9
+
10
+ from PIL import Image as PIL_Image
11
+
12
+ from pptx2.opc.package import Part
13
+ from pptx2.opc.spec import image_content_types
14
+ from pptx2.util import Emu, lazyproperty
15
+
16
+ if TYPE_CHECKING:
17
+ from pptx2.opc.packuri import PackURI
18
+ from pptx2.package import Package
19
+ from pptx2.util import Length
20
+
21
+
22
+ class ImagePart(Part):
23
+ """An image part.
24
+
25
+ An image part generally has a partname matching the regex `ppt/media/image[1-9][0-9]*.*`.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ partname: PackURI,
31
+ content_type: str,
32
+ package: Package,
33
+ blob: bytes,
34
+ filename: str | None = None,
35
+ ):
36
+ super(ImagePart, self).__init__(partname, content_type, package, blob)
37
+ self._blob = blob
38
+ self._filename = filename
39
+
40
+ @classmethod
41
+ def new(cls, package: Package, image: Image) -> ImagePart:
42
+ """Return new |ImagePart| instance containing `image`.
43
+
44
+ `image` is an |Image| object.
45
+ """
46
+ return cls(
47
+ package.next_image_partname(image.ext),
48
+ image.content_type,
49
+ package,
50
+ image.blob,
51
+ image.filename,
52
+ )
53
+
54
+ @property
55
+ def desc(self) -> str:
56
+ """The filename associated with this image.
57
+
58
+ Either the filename of the original image or a generic name of the form `image.ext` where
59
+ `ext` is appropriate to the image file format, e.g. `'jpg'`. An image created using a path
60
+ will have that filename; one created with a file-like object will have a generic name.
61
+ """
62
+ # -- return generic filename if original filename is unknown --
63
+ if self._filename is None:
64
+ return f"image.{self.ext}"
65
+ return self._filename
66
+
67
+ @property
68
+ def ext(self) -> str:
69
+ """File-name extension for this image e.g. `'png'`."""
70
+ return self.partname.ext
71
+
72
+ @property
73
+ def image(self) -> Image:
74
+ """An |Image| object containing the image in this image part.
75
+
76
+ Note this is a `pptx2.image.Image` object, not a PIL Image.
77
+ """
78
+ return Image(self._blob, self.desc)
79
+
80
+ def scale(self, scaled_cx: int | None, scaled_cy: int | None) -> tuple[int, int]:
81
+ """Return scaled image dimensions in EMU based on the combination of parameters supplied.
82
+
83
+ If `scaled_cx` and `scaled_cy` are both |None|, the native image size is returned. If
84
+ neither `scaled_cx` nor `scaled_cy` is |None|, their values are returned unchanged. If a
85
+ value is provided for either `scaled_cx` or `scaled_cy` and the other is |None|, the
86
+ missing value is calculated such that the image's aspect ratio is preserved.
87
+ """
88
+ image_cx, image_cy = self._native_size
89
+
90
+ if scaled_cx and scaled_cy:
91
+ return scaled_cx, scaled_cy
92
+
93
+ if scaled_cx and not scaled_cy:
94
+ scaling_factor = float(scaled_cx) / float(image_cx)
95
+ scaled_cy = int(round(image_cy * scaling_factor))
96
+ return scaled_cx, scaled_cy
97
+
98
+ if not scaled_cx and scaled_cy:
99
+ scaling_factor = float(scaled_cy) / float(image_cy)
100
+ scaled_cx = int(round(image_cx * scaling_factor))
101
+ return scaled_cx, scaled_cy
102
+
103
+ # -- only remaining case is both `scaled_cx` and `scaled_cy` are `None` --
104
+ return image_cx, image_cy
105
+
106
+ @lazyproperty
107
+ def sha1(self) -> str:
108
+ """The 40-character SHA1 hash digest for the image binary of this image part.
109
+
110
+ like: `"1be010ea47803b00e140b852765cdf84f491da47"`.
111
+ """
112
+ return hashlib.sha1(self._blob).hexdigest()
113
+
114
+ @property
115
+ def _dpi(self) -> tuple[int, int]:
116
+ """(horz_dpi, vert_dpi) pair representing the dots-per-inch resolution of this image."""
117
+ image = Image.from_blob(self._blob)
118
+ return image.dpi
119
+
120
+ @property
121
+ def _native_size(self) -> tuple[Length, Length]:
122
+ """A (width, height) 2-tuple representing the native dimensions of the image in EMU.
123
+
124
+ Calculated based on the image DPI value, if present, assuming 72 dpi as a default.
125
+ """
126
+ EMU_PER_INCH = 914400
127
+ horz_dpi, vert_dpi = self._dpi
128
+ width_px, height_px = self._px_size
129
+
130
+ width = EMU_PER_INCH * width_px / horz_dpi
131
+ height = EMU_PER_INCH * height_px / vert_dpi
132
+
133
+ return Emu(int(width)), Emu(int(height))
134
+
135
+ @property
136
+ def _px_size(self) -> tuple[int, int]:
137
+ """A (width, height) 2-tuple representing the dimensions of this image in pixels."""
138
+ image = Image.from_blob(self._blob)
139
+ return image.size
140
+
141
+
142
+ class Image(object):
143
+ """Immutable value object representing an image such as a JPEG, PNG, or GIF."""
144
+
145
+ def __init__(self, blob: bytes, filename: str | None):
146
+ super(Image, self).__init__()
147
+ self._blob = blob
148
+ self._filename = filename
149
+
150
+ @classmethod
151
+ def from_blob(cls, blob: bytes, filename: str | None = None) -> Image:
152
+ """Return a new |Image| object loaded from the image binary in `blob`."""
153
+ return cls(blob, filename)
154
+
155
+ @classmethod
156
+ def from_file(cls, image_file: str | IO[bytes]) -> Image:
157
+ """Return a new |Image| object loaded from `image_file`.
158
+
159
+ `image_file` can be either a path (str) or a file-like object.
160
+ """
161
+ if isinstance(image_file, str):
162
+ # treat image_file as a path
163
+ with open(image_file, "rb") as f:
164
+ blob = f.read()
165
+ filename = os.path.basename(image_file)
166
+ else:
167
+ # assume image_file is a file-like object
168
+ # ---reposition file cursor if it has one---
169
+ if callable(getattr(image_file, "seek")):
170
+ image_file.seek(0)
171
+ blob = image_file.read()
172
+ filename = None
173
+
174
+ return cls.from_blob(blob, filename)
175
+
176
+ @property
177
+ def blob(self) -> bytes:
178
+ """The binary image bytestream of this image."""
179
+ return self._blob
180
+
181
+ @lazyproperty
182
+ def content_type(self) -> str:
183
+ """MIME-type of this image, e.g. `"image/jpeg"`."""
184
+ return image_content_types[self.ext]
185
+
186
+ @lazyproperty
187
+ def dpi(self) -> tuple[int, int]:
188
+ """A (horz_dpi, vert_dpi) 2-tuple specifying the dots-per-inch resolution of this image.
189
+
190
+ A default value of (72, 72) is used if the dpi is not specified in the image file.
191
+ """
192
+
193
+ def int_dpi(dpi: Any):
194
+ """Return an integer dots-per-inch value corresponding to `dpi`.
195
+
196
+ If `dpi` is |None|, a non-numeric type, less than 1 or greater than 2048, 72 is
197
+ returned.
198
+ """
199
+ try:
200
+ int_dpi = int(round(float(dpi)))
201
+ if int_dpi < 1 or int_dpi > 2048:
202
+ int_dpi = 72
203
+ except (TypeError, ValueError):
204
+ int_dpi = 72
205
+ return int_dpi
206
+
207
+ def normalize_pil_dpi(pil_dpi: tuple[int, int] | None):
208
+ """Return a (horz_dpi, vert_dpi) 2-tuple corresponding to `pil_dpi`.
209
+
210
+ The value for the 'dpi' key in the `info` dict of a PIL image. If the 'dpi' key is not
211
+ present or contains an invalid value, `(72, 72)` is returned.
212
+ """
213
+ if isinstance(pil_dpi, tuple):
214
+ return (int_dpi(pil_dpi[0]), int_dpi(pil_dpi[1]))
215
+ return (72, 72)
216
+
217
+ return normalize_pil_dpi(self._pil_props[2])
218
+
219
+ @lazyproperty
220
+ def ext(self) -> str:
221
+ """Canonical file extension for this image e.g. `'png'`.
222
+
223
+ The returned extension is all lowercase and is the canonical extension for the content type
224
+ of this image, regardless of what extension may have been used in its filename, if any.
225
+ """
226
+ ext_map = {
227
+ "BMP": "bmp",
228
+ "GIF": "gif",
229
+ "JPEG": "jpg",
230
+ "PNG": "png",
231
+ "TIFF": "tiff",
232
+ "WMF": "wmf",
233
+ }
234
+ format = self._format
235
+ if format not in ext_map:
236
+ tmpl = "unsupported image format, expected one of: %s, got '%s'"
237
+ raise ValueError(tmpl % (ext_map.keys(), format))
238
+ return ext_map[format]
239
+
240
+ @property
241
+ def filename(self) -> str | None:
242
+ """Filename from path used to load this image, if loaded from the filesystem.
243
+
244
+ |None| if no filename was used in loading, such as when loaded from an in-memory stream.
245
+ """
246
+ return self._filename
247
+
248
+ @lazyproperty
249
+ def sha1(self) -> str:
250
+ """SHA1 hash digest of the image blob."""
251
+ return hashlib.sha1(self._blob).hexdigest()
252
+
253
+ @lazyproperty
254
+ def size(self) -> tuple[int, int]:
255
+ """A (width, height) 2-tuple specifying the dimensions of this image in pixels."""
256
+ return self._pil_props[1]
257
+
258
+ @property
259
+ def _format(self) -> str | None:
260
+ """The PIL Image format of this image, e.g. 'PNG'."""
261
+ return self._pil_props[0]
262
+
263
+ @lazyproperty
264
+ def _pil_props(self) -> tuple[str | None, tuple[int, int], tuple[int, int] | None]:
265
+ """tuple of image properties extracted from this image using Pillow."""
266
+ stream = io.BytesIO(self._blob)
267
+ pil_image = PIL_Image.open(stream) # pyright: ignore[reportUnknownMemberType]
268
+ format = pil_image.format
269
+ width_px, height_px = pil_image.size
270
+ dpi = cast(
271
+ "tuple[int, int] | None",
272
+ pil_image.info.get("dpi"), # pyright: ignore[reportUnknownMemberType]
273
+ )
274
+ stream.close()
275
+ return (format, (width_px, height_px), dpi)
pptx2/parts/media.py ADDED
@@ -0,0 +1,37 @@
1
+ """MediaPart and related objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+
7
+ from pptx2.opc.package import Part
8
+ from pptx2.util import lazyproperty
9
+
10
+
11
+ class MediaPart(Part):
12
+ """A media part, containing an audio or video resource.
13
+
14
+ A media part generally has a partname matching the regex
15
+ `ppt/media/media[1-9][0-9]*.*`.
16
+ """
17
+
18
+ @classmethod
19
+ def new(cls, package, media):
20
+ """Return new |MediaPart| instance containing `media`.
21
+
22
+ `media` must be a |Media| object.
23
+ """
24
+ return cls(
25
+ package.next_media_partname(media.ext),
26
+ media.content_type,
27
+ package,
28
+ media.blob,
29
+ )
30
+
31
+ @lazyproperty
32
+ def sha1(self):
33
+ """The SHA1 hash digest for the media binary of this media part.
34
+
35
+ Example: `'1be010ea47803b00e140b852765cdf84f491da47'`
36
+ """
37
+ return hashlib.sha1(self._blob).hexdigest()
@@ -0,0 +1,136 @@
1
+ """Presentation part, the main part in a .pptx package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import IO, TYPE_CHECKING, Iterable
6
+
7
+ from pptx2.opc.constants import RELATIONSHIP_TYPE as RT
8
+ from pptx2.opc.package import XmlPart
9
+ from pptx2.opc.packuri import PackURI
10
+ from pptx2.parts.slide import NotesMasterPart, SlidePart
11
+ from pptx2.presentation import Presentation
12
+ from pptx2.util import lazyproperty
13
+
14
+ if TYPE_CHECKING:
15
+ from pptx2.parts.coreprops import CorePropertiesPart
16
+ from pptx2.slide import NotesMaster, Slide, SlideLayout, SlideMaster
17
+
18
+
19
+ class PresentationPart(XmlPart):
20
+ """Top level class in object model.
21
+
22
+ Represents the contents of the /ppt directory of a .pptx file.
23
+ """
24
+
25
+ def add_slide(self, slide_layout: SlideLayout):
26
+ """Return (rId, slide) pair of a newly created blank slide.
27
+
28
+ New slide inherits appearance from `slide_layout`.
29
+ """
30
+ partname = self._next_slide_partname
31
+ slide_layout_part = slide_layout.part
32
+ slide_part = SlidePart.new(partname, self.package, slide_layout_part)
33
+ rId = self.relate_to(slide_part, RT.SLIDE)
34
+ return rId, slide_part.slide
35
+
36
+ @property
37
+ def core_properties(self) -> CorePropertiesPart:
38
+ """A |CoreProperties| object for the presentation.
39
+
40
+ Provides read/write access to the Dublin Core properties of this presentation.
41
+ """
42
+ return self.package.core_properties
43
+
44
+ def get_slide(self, slide_id: int) -> Slide | None:
45
+ """Return optional related |Slide| object identified by `slide_id`.
46
+
47
+ Returns |None| if no slide with `slide_id` is related to this presentation.
48
+ """
49
+ for sldId in self._element.sldIdLst:
50
+ if sldId.id == slide_id:
51
+ return self.related_part(sldId.rId).slide
52
+ return None
53
+
54
+ @lazyproperty
55
+ def notes_master(self) -> NotesMaster:
56
+ """
57
+ Return the |NotesMaster| object for this presentation. If the
58
+ presentation does not have a notes master, one is created from
59
+ a default template. The same single instance is returned on each
60
+ call.
61
+ """
62
+ return self.notes_master_part.notes_master
63
+
64
+ @lazyproperty
65
+ def notes_master_part(self) -> NotesMasterPart:
66
+ """Return the |NotesMasterPart| object for this presentation.
67
+
68
+ If the presentation does not have a notes master, one is created from a default template.
69
+ The same single instance is returned on each call.
70
+ """
71
+ try:
72
+ return self.part_related_by(RT.NOTES_MASTER)
73
+ except KeyError:
74
+ notes_master_part = NotesMasterPart.create_default(self.package)
75
+ self.relate_to(notes_master_part, RT.NOTES_MASTER)
76
+ return notes_master_part
77
+
78
+ @lazyproperty
79
+ def presentation(self):
80
+ """
81
+ A |Presentation| object providing access to the content of this
82
+ presentation.
83
+ """
84
+ return Presentation(self._element, self)
85
+
86
+ def related_slide(self, rId: str) -> Slide:
87
+ """Return |Slide| object for related |SlidePart| related by `rId`."""
88
+ return self.related_part(rId).slide
89
+
90
+ def related_slide_master(self, rId: str) -> SlideMaster:
91
+ """Return |SlideMaster| object for |SlideMasterPart| related by `rId`."""
92
+ return self.related_part(rId).slide_master
93
+
94
+ def rename_slide_parts(self, rIds: Iterable[str]):
95
+ """Assign incrementing partnames to the slide parts identified by `rIds`.
96
+
97
+ Partnames are like `/ppt/slides/slide9.xml` and are assigned in the order their id appears
98
+ in the `rIds` sequence. The name portion is always `slide`. The number part forms a
99
+ continuous sequence starting at 1 (e.g. 1, 2, ... 10, ...). The extension is always
100
+ `.xml`.
101
+ """
102
+ for idx, rId in enumerate(rIds):
103
+ slide_part = self.related_part(rId)
104
+ slide_part.partname = PackURI("/ppt/slides/slide%d.xml" % (idx + 1))
105
+
106
+ def save(self, path_or_stream: str | IO[bytes]):
107
+ """Save this presentation package to `path_or_stream`.
108
+
109
+ `path_or_stream` can be either a path to a filesystem location (a string) or a
110
+ file-like object.
111
+ """
112
+ self.package.save(path_or_stream)
113
+
114
+ def slide_id(self, slide_part):
115
+ """Return the slide-id associated with `slide_part`."""
116
+ for sldId in self._element.sldIdLst:
117
+ if self.related_part(sldId.rId) is slide_part:
118
+ return sldId.id
119
+ raise ValueError("matching slide_part not found")
120
+
121
+ @property
122
+ def _next_slide_partname(self):
123
+ """Return |PackURI| instance containing next available slide partname.
124
+
125
+ Scans the actual partnames in the package rather than counting
126
+ `p:sldIdLst` entries — the package can hold slide parts beyond those
127
+ registered in the id list (e.g. after a cross-deck import), and
128
+ writing two different parts under one partname corrupts the package.
129
+ """
130
+ existing = {part.partname for part in self.package.iter_parts()}
131
+ n = len(self._element.get_or_add_sldIdLst()) + 1
132
+ while True:
133
+ candidate = PackURI("/ppt/slides/slide%d.xml" % n)
134
+ if candidate not in existing:
135
+ return candidate
136
+ n += 1