python-pptx2 2.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. pptx2/__init__.py +152 -0
  2. pptx2/_color.py +75 -0
  3. pptx2/_slide_importer.py +597 -0
  4. pptx2/_svg.py +155 -0
  5. pptx2/_template_applier.py +292 -0
  6. pptx2/_textstyle.py +187 -0
  7. pptx2/accessibility.py +365 -0
  8. pptx2/action.py +270 -0
  9. pptx2/animation.py +2237 -0
  10. pptx2/api.py +49 -0
  11. pptx2/audit.py +258 -0
  12. pptx2/chart/__init__.py +0 -0
  13. pptx2/chart/analytics.py +381 -0
  14. pptx2/chart/axis.py +543 -0
  15. pptx2/chart/category.py +200 -0
  16. pptx2/chart/chart.py +670 -0
  17. pptx2/chart/data.py +864 -0
  18. pptx2/chart/datalabel.py +406 -0
  19. pptx2/chart/legend.py +86 -0
  20. pptx2/chart/marker.py +70 -0
  21. pptx2/chart/palettes.py +129 -0
  22. pptx2/chart/plot.py +462 -0
  23. pptx2/chart/point.py +101 -0
  24. pptx2/chart/quick_layouts.py +325 -0
  25. pptx2/chart/series.py +334 -0
  26. pptx2/chart/xlsx.py +272 -0
  27. pptx2/chart/xmlwriter.py +1845 -0
  28. pptx2/compose/__init__.py +28 -0
  29. pptx2/compose/from_spec.py +1094 -0
  30. pptx2/design/__init__.py +8 -0
  31. pptx2/design/components.py +607 -0
  32. pptx2/design/figures.py +389 -0
  33. pptx2/design/layout.py +370 -0
  34. pptx2/design/recipes.py +1967 -0
  35. pptx2/design/style.py +209 -0
  36. pptx2/design/tokens.py +915 -0
  37. pptx2/diagrams.py +754 -0
  38. pptx2/dml/__init__.py +0 -0
  39. pptx2/dml/chtfmt.py +40 -0
  40. pptx2/dml/color.py +496 -0
  41. pptx2/dml/effect.py +909 -0
  42. pptx2/dml/fill.py +691 -0
  43. pptx2/dml/line.py +287 -0
  44. pptx2/dml/picture.py +212 -0
  45. pptx2/dml/three_d.py +381 -0
  46. pptx2/enum/__init__.py +0 -0
  47. pptx2/enum/action.py +71 -0
  48. pptx2/enum/animation.py +31 -0
  49. pptx2/enum/base.py +218 -0
  50. pptx2/enum/chart.py +574 -0
  51. pptx2/enum/dml.py +740 -0
  52. pptx2/enum/lang.py +685 -0
  53. pptx2/enum/presentation.py +133 -0
  54. pptx2/enum/shapes.py +1029 -0
  55. pptx2/enum/text.py +230 -0
  56. pptx2/exc.py +42 -0
  57. pptx2/formats.py +139 -0
  58. pptx2/geometry.py +420 -0
  59. pptx2/inherit.py +109 -0
  60. pptx2/lint.py +2256 -0
  61. pptx2/math.py +177 -0
  62. pptx2/media.py +197 -0
  63. pptx2/opc/__init__.py +0 -0
  64. pptx2/opc/constants.py +332 -0
  65. pptx2/opc/oxml.py +188 -0
  66. pptx2/opc/package.py +762 -0
  67. pptx2/opc/packuri.py +109 -0
  68. pptx2/opc/serialized.py +296 -0
  69. pptx2/opc/shared.py +20 -0
  70. pptx2/opc/spec.py +45 -0
  71. pptx2/oxml/__init__.py +555 -0
  72. pptx2/oxml/action.py +53 -0
  73. pptx2/oxml/chart/__init__.py +0 -0
  74. pptx2/oxml/chart/axis.py +337 -0
  75. pptx2/oxml/chart/chart.py +481 -0
  76. pptx2/oxml/chart/datalabel.py +253 -0
  77. pptx2/oxml/chart/legend.py +72 -0
  78. pptx2/oxml/chart/marker.py +61 -0
  79. pptx2/oxml/chart/plot.py +365 -0
  80. pptx2/oxml/chart/series.py +425 -0
  81. pptx2/oxml/chart/shared.py +220 -0
  82. pptx2/oxml/coreprops.py +288 -0
  83. pptx2/oxml/dml/__init__.py +0 -0
  84. pptx2/oxml/dml/color.py +135 -0
  85. pptx2/oxml/dml/effect.py +213 -0
  86. pptx2/oxml/dml/fill.py +316 -0
  87. pptx2/oxml/dml/line.py +12 -0
  88. pptx2/oxml/dml/three_d.py +110 -0
  89. pptx2/oxml/ns.py +135 -0
  90. pptx2/oxml/presentation.py +313 -0
  91. pptx2/oxml/shapes/__init__.py +19 -0
  92. pptx2/oxml/shapes/autoshape.py +467 -0
  93. pptx2/oxml/shapes/connector.py +107 -0
  94. pptx2/oxml/shapes/graphfrm.py +347 -0
  95. pptx2/oxml/shapes/groupshape.py +329 -0
  96. pptx2/oxml/shapes/picture.py +270 -0
  97. pptx2/oxml/shapes/shared.py +577 -0
  98. pptx2/oxml/simpletypes.py +1027 -0
  99. pptx2/oxml/slide.py +563 -0
  100. pptx2/oxml/table.py +650 -0
  101. pptx2/oxml/text.py +815 -0
  102. pptx2/oxml/theme.py +36 -0
  103. pptx2/oxml/xmlchemy.py +717 -0
  104. pptx2/package.py +222 -0
  105. pptx2/parts/__init__.py +0 -0
  106. pptx2/parts/chart.py +95 -0
  107. pptx2/parts/coreprops.py +167 -0
  108. pptx2/parts/diagram.py +37 -0
  109. pptx2/parts/embeddedpackage.py +93 -0
  110. pptx2/parts/image.py +275 -0
  111. pptx2/parts/media.py +37 -0
  112. pptx2/parts/presentation.py +136 -0
  113. pptx2/parts/slide.py +371 -0
  114. pptx2/presentation.py +408 -0
  115. pptx2/py.typed +0 -0
  116. pptx2/render.py +586 -0
  117. pptx2/section.py +272 -0
  118. pptx2/shapes/__init__.py +26 -0
  119. pptx2/shapes/autoshape.py +442 -0
  120. pptx2/shapes/base.py +1078 -0
  121. pptx2/shapes/connector.py +297 -0
  122. pptx2/shapes/freeform.py +337 -0
  123. pptx2/shapes/graphfrm.py +316 -0
  124. pptx2/shapes/group.py +264 -0
  125. pptx2/shapes/picture.py +422 -0
  126. pptx2/shapes/placeholder.py +468 -0
  127. pptx2/shapes/shapetree.py +2027 -0
  128. pptx2/shared.py +82 -0
  129. pptx2/skill/SKILL.md +450 -0
  130. pptx2/skill/__init__.py +78 -0
  131. pptx2/skill/__main__.py +64 -0
  132. pptx2/skill/references/animations.md +189 -0
  133. pptx2/skill/references/basics.md +421 -0
  134. pptx2/skill/references/charts.md +254 -0
  135. pptx2/skill/references/compose.md +234 -0
  136. pptx2/skill/references/design.md +366 -0
  137. pptx2/skill/references/effects.md +249 -0
  138. pptx2/skill/references/end-to-end-deck.md +231 -0
  139. pptx2/skill/references/geometry-and-arrows.md +334 -0
  140. pptx2/skill/references/lint.md +275 -0
  141. pptx2/skill/references/math.md +86 -0
  142. pptx2/skill/references/picture-effects.md +129 -0
  143. pptx2/skill/references/render.md +151 -0
  144. pptx2/skill/references/smart-art.md +75 -0
  145. pptx2/skill/references/space-aware-authoring.md +249 -0
  146. pptx2/skill/references/tables.md +244 -0
  147. pptx2/skill/references/theme.md +127 -0
  148. pptx2/skill/references/three-d.md +109 -0
  149. pptx2/skill/references/transitions.md +100 -0
  150. pptx2/slide.py +1244 -0
  151. pptx2/smart_art.py +220 -0
  152. pptx2/spec.py +633 -0
  153. pptx2/table.py +1181 -0
  154. pptx2/table_styles.py +184 -0
  155. pptx2/templates/default.pptx +0 -0
  156. pptx2/templates/docx-icon.emf +0 -0
  157. pptx2/templates/generic-icon.emf +0 -0
  158. pptx2/templates/notes.xml +23 -0
  159. pptx2/templates/notesMaster.xml +352 -0
  160. pptx2/templates/pptx-icon.emf +0 -0
  161. pptx2/templates/theme.xml +321 -0
  162. pptx2/templates/xlsx-icon.emf +0 -0
  163. pptx2/text/__init__.py +0 -0
  164. pptx2/text/fonts.py +482 -0
  165. pptx2/text/layout.py +374 -0
  166. pptx2/text/text.py +1272 -0
  167. pptx2/theme.py +721 -0
  168. pptx2/types.py +36 -0
  169. pptx2/util.py +263 -0
  170. python_pptx2-2.13.0.dist-info/METADATA +351 -0
  171. python_pptx2-2.13.0.dist-info/RECORD +175 -0
  172. python_pptx2-2.13.0.dist-info/WHEEL +5 -0
  173. python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
  174. python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
  175. python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,316 @@
1
+ """Graphic Frame shape and related objects.
2
+
3
+ A graphic frame is a common container for table, chart, smart art, and media
4
+ objects.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, cast
10
+
11
+ from pptx2.dml.effect import (
12
+ BlurFormat,
13
+ GlowFormat,
14
+ ReflectionFormat,
15
+ ShadowFormat,
16
+ SoftEdgeFormat,
17
+ )
18
+ from pptx2.enum.shapes import MSO_SHAPE_TYPE
19
+ from pptx2.shapes.base import BaseShape
20
+ from pptx2.shared import ParentedElementProxy
21
+ from pptx2.spec import (
22
+ GRAPHIC_DATA_URI_CHART,
23
+ GRAPHIC_DATA_URI_OLEOBJ,
24
+ GRAPHIC_DATA_URI_TABLE,
25
+ )
26
+ from pptx2.table import Table
27
+ from pptx2.util import lazyproperty
28
+
29
+ if TYPE_CHECKING:
30
+ from pptx2.chart.chart import Chart
31
+ from pptx2.dml.effect import InnerShadowFormat, PresetShadowFormat, ShadowFormat
32
+ from pptx2.oxml.shapes.graphfrm import CT_GraphicalObjectData, CT_GraphicalObjectFrame
33
+ from pptx2.parts.chart import ChartPart
34
+ from pptx2.parts.slide import BaseSlidePart
35
+ from pptx2.types import ProvidesPart
36
+
37
+
38
+ class GraphicFrame(BaseShape):
39
+ """Container shape for table, chart, smart art, and media objects.
40
+
41
+ Corresponds to a `p:graphicFrame` element in the shape tree.
42
+ """
43
+
44
+ def __init__(self, graphicFrame: CT_GraphicalObjectFrame, parent: ProvidesPart):
45
+ super().__init__(graphicFrame, parent)
46
+ self._graphicFrame = graphicFrame
47
+
48
+ @property
49
+ def chart(self) -> Chart:
50
+ """The |Chart| object containing the chart in this graphic frame.
51
+
52
+ Raises |ValueError| if this graphic frame does not contain a chart.
53
+ """
54
+ if not self.has_chart:
55
+ raise ValueError("shape does not contain a chart")
56
+ chart = self.chart_part.chart
57
+ # Cache the parent shape ref on the chart so callers can reach
58
+ # back to the GraphicFrame without keeping the ``add_chart``
59
+ # return value around. ``Chart.shape`` reads this attribute.
60
+ chart._parent_shape = self # type: ignore[attr-defined]
61
+ return chart
62
+
63
+ @property
64
+ def chart_part(self) -> ChartPart:
65
+ """The |ChartPart| object containing the chart in this graphic frame."""
66
+ chart_rId = self._graphicFrame.chart_rId
67
+ if chart_rId is None:
68
+ raise ValueError("this graphic frame does not contain a chart")
69
+ return cast("ChartPart", self.part.related_part(chart_rId))
70
+
71
+ @property
72
+ def has_chart(self) -> bool:
73
+ """|True| if this graphic frame contains a chart object. |False| otherwise.
74
+
75
+ When |True|, the chart object can be accessed using the `.chart` property.
76
+ """
77
+ return self._graphicFrame.graphicData_uri == GRAPHIC_DATA_URI_CHART
78
+
79
+ @property
80
+ def has_table(self) -> bool:
81
+ """|True| if this graphic frame contains a table object, |False| otherwise.
82
+
83
+ When |True|, the table object can be accessed using the `.table` property.
84
+ """
85
+ return self._graphicFrame.graphicData_uri == GRAPHIC_DATA_URI_TABLE
86
+
87
+ @property
88
+ def ole_format(self) -> _OleFormat:
89
+ """_OleFormat object for this graphic-frame shape.
90
+
91
+ Raises `ValueError` on a GraphicFrame instance that does not contain an OLE object.
92
+
93
+ An shape that contains an OLE object will have `.shape_type` of either
94
+ `EMBEDDED_OLE_OBJECT` or `LINKED_OLE_OBJECT`.
95
+ """
96
+ if not self._graphicFrame.has_oleobj:
97
+ raise ValueError("not an OLE-object shape")
98
+ return _OleFormat(self._graphicFrame.graphicData, self._parent)
99
+
100
+ @lazyproperty
101
+ def blur(self) -> BlurFormat:
102
+ """Unconditionally raises |NotImplementedError|.
103
+
104
+ Gaussian blur access for graphic-frame objects is content-specific
105
+ (i.e. different for charts, tables, etc.) and has not yet been
106
+ implemented.
107
+ """
108
+ raise NotImplementedError("blur property on GraphicFrame not yet supported")
109
+
110
+ @lazyproperty
111
+ def glow(self) -> GlowFormat:
112
+ """Unconditionally raises |NotImplementedError|.
113
+
114
+ Glow effect access for graphic-frame objects is not yet implemented.
115
+ """
116
+ raise NotImplementedError("glow property on GraphicFrame not yet supported")
117
+
118
+ @lazyproperty
119
+ def reflection(self) -> ReflectionFormat:
120
+ """Unconditionally raises |NotImplementedError|.
121
+
122
+ Reflection effect access for graphic-frame objects is not yet
123
+ implemented.
124
+ """
125
+ raise NotImplementedError("reflection property on GraphicFrame not yet supported")
126
+
127
+ @lazyproperty
128
+ def shadow(self) -> ShadowFormat | None:
129
+ """Returns ``None``: shadow access on a |GraphicFrame| is unsupported.
130
+
131
+ Charts and tables expose their effect tree at content-specific
132
+ locations (e.g. ``c:spPr/a:effectLst`` on a chart) and the unified
133
+ :class:`~pptx2.dml.effect.ShadowFormat` facade doesn't apply.
134
+ Returning ``None`` keeps callers that probe ``shape.shadow`` across
135
+ every shape on a slide free from per-type ``try/except`` guards;
136
+ ``if shape.shadow is None`` is the supported "no facade available"
137
+ check.
138
+ """
139
+ return None
140
+
141
+ @lazyproperty
142
+ def inner_shadow(self) -> InnerShadowFormat | None:
143
+ """Returns ``None``: inner-shadow access on a |GraphicFrame| is unsupported.
144
+
145
+ A graphic frame has no ``spPr``, so (like :attr:`shadow`) the unified
146
+ effect facade doesn't apply; ``None`` lets callers probe every shape
147
+ without a per-type ``try/except``.
148
+ """
149
+ return None
150
+
151
+ @lazyproperty
152
+ def preset_shadow(self) -> PresetShadowFormat | None:
153
+ """Returns ``None``: preset-shadow access on a |GraphicFrame| is unsupported."""
154
+ return None
155
+
156
+ @lazyproperty
157
+ def soft_edges(self) -> SoftEdgeFormat:
158
+ """Unconditionally raises |NotImplementedError|.
159
+
160
+ Soft-edge effect access for graphic-frame objects is not yet implemented.
161
+ """
162
+ raise NotImplementedError("soft_edges property on GraphicFrame not yet supported")
163
+
164
+ @property
165
+ def three_d(self):
166
+ """Unconditionally raises |NotImplementedError|.
167
+
168
+ A graphic frame has no ``p:spPr``, so the |ThreeDFormat| facade does
169
+ not apply (mirrors :attr:`blur` / :attr:`glow` behaviour).
170
+ """
171
+ raise NotImplementedError("three_d property on GraphicFrame not yet supported")
172
+
173
+ @property
174
+ def shape_type(self) -> MSO_SHAPE_TYPE:
175
+ """Optional member of `MSO_SHAPE_TYPE` identifying the type of this shape.
176
+
177
+ Possible values are `MSO_SHAPE_TYPE.CHART`, `MSO_SHAPE_TYPE.TABLE`,
178
+ `MSO_SHAPE_TYPE.EMBEDDED_OLE_OBJECT`, `MSO_SHAPE_TYPE.LINKED_OLE_OBJECT`.
179
+
180
+ This value is `None` when none of these four types apply, for example when the shape
181
+ contains SmartArt.
182
+ """
183
+ graphicData_uri = self._graphicFrame.graphicData_uri
184
+ if graphicData_uri == GRAPHIC_DATA_URI_CHART:
185
+ return MSO_SHAPE_TYPE.CHART
186
+ elif graphicData_uri == GRAPHIC_DATA_URI_TABLE:
187
+ return MSO_SHAPE_TYPE.TABLE
188
+ elif graphicData_uri == GRAPHIC_DATA_URI_OLEOBJ:
189
+ return (
190
+ MSO_SHAPE_TYPE.EMBEDDED_OLE_OBJECT
191
+ if self._graphicFrame.is_embedded_ole_obj
192
+ else MSO_SHAPE_TYPE.LINKED_OLE_OBJECT
193
+ )
194
+ else:
195
+ return None # pyright: ignore[reportReturnType]
196
+
197
+ @property
198
+ def table(self) -> Table:
199
+ """The |Table| object contained in this graphic frame.
200
+
201
+ Raises |ValueError| if this graphic frame does not contain a table.
202
+ """
203
+ if not self.has_table:
204
+ raise ValueError("shape does not contain a table")
205
+ tbl = self._graphicFrame.graphic.graphicData.tbl
206
+ return Table(tbl, self)
207
+
208
+ def render_to_png(self, **kwargs):
209
+ """Render this graphic frame's region to a PNG.
210
+
211
+ Renders the parent slide via headless LibreOffice (the same path
212
+ used by :meth:`Slide.render_thumbnail`), then crops the result to
213
+ this frame's bounding box. Useful for getting a standalone
214
+ chart / table image out of a deck.
215
+
216
+ Forwards keyword arguments (``out_path``, ``soffice_bin``,
217
+ ``timeout``) to :meth:`Slide.render_thumbnail`. ``return_bytes``
218
+ is also supported and returns the cropped PNG bytes directly.
219
+
220
+ Requires :pypi:`Pillow` for cropping (already a python-pptx2
221
+ dependency) and ``soffice`` on PATH.
222
+ """
223
+ from io import BytesIO
224
+
225
+ try:
226
+ from PIL import Image
227
+ except ImportError as e: # pragma: no cover - Pillow is a dep
228
+ raise RuntimeError(
229
+ "render_to_png requires Pillow; install python-pptx2[render]"
230
+ ) from e
231
+
232
+ return_bytes = kwargs.pop("return_bytes", False)
233
+ out_path = kwargs.pop("out_path", None)
234
+
235
+ slide = self.part.slide
236
+ png_bytes = slide.render_thumbnail(return_bytes=True, **kwargs)
237
+
238
+ with Image.open(BytesIO(png_bytes)) as img:
239
+ # Compute pixel bounds of this graphic frame from the EMU-vs-pixel
240
+ # scale of the rendered slide.
241
+ prs = self.part.package.presentation_part.presentation
242
+ slide_w_emu = int(prs.slide_width or 9144000)
243
+ slide_h_emu = int(prs.slide_height or 6858000)
244
+ scale_x = img.width / slide_w_emu
245
+ scale_y = img.height / slide_h_emu
246
+ left = int(int(self.left) * scale_x)
247
+ top = int(int(self.top) * scale_y)
248
+ right = int((int(self.left) + int(self.width)) * scale_x)
249
+ bottom = int((int(self.top) + int(self.height)) * scale_y)
250
+ cropped = img.crop((left, top, right, bottom))
251
+ buf = BytesIO()
252
+ cropped.save(buf, format="PNG")
253
+ data = buf.getvalue()
254
+
255
+ if return_bytes:
256
+ return data
257
+ if out_path is not None:
258
+ from pathlib import Path
259
+
260
+ target = Path(out_path)
261
+ target.parent.mkdir(parents=True, exist_ok=True)
262
+ target.write_bytes(data)
263
+ return target
264
+ # No destination given: persist to a stable temp file.
265
+ import os
266
+ import tempfile
267
+ from pathlib import Path
268
+
269
+ fd, persistent = tempfile.mkstemp(prefix="pptx-frame-", suffix=".png")
270
+ os.close(fd)
271
+ try:
272
+ Path(persistent).write_bytes(data)
273
+ except Exception:
274
+ # Don't leak the empty temp file when the write fails.
275
+ try:
276
+ os.remove(persistent)
277
+ except OSError:
278
+ pass
279
+ raise
280
+ return Path(persistent)
281
+
282
+
283
+ class _OleFormat(ParentedElementProxy):
284
+ """Provides attributes on an embedded OLE object."""
285
+
286
+ part: BaseSlidePart # pyright: ignore[reportIncompatibleMethodOverride]
287
+
288
+ def __init__(self, graphicData: CT_GraphicalObjectData, parent: ProvidesPart):
289
+ super().__init__(graphicData, parent)
290
+ self._graphicData = graphicData
291
+
292
+ @property
293
+ def blob(self) -> bytes | None:
294
+ """Optional bytes of OLE object, suitable for loading or saving as a file.
295
+
296
+ This value is `None` if the embedded object does not represent a "file".
297
+ """
298
+ blob_rId = self._graphicData.blob_rId
299
+ if blob_rId is None:
300
+ return None
301
+ return self.part.related_part(blob_rId).blob
302
+
303
+ @property
304
+ def prog_id(self) -> str | None:
305
+ """str "progId" attribute of this embedded OLE object.
306
+
307
+ The progId is a str like "Excel.Sheet.12" that identifies the "file-type" of the embedded
308
+ object, or perhaps more precisely, the application (aka. "server" in OLE parlance) to be
309
+ used to open this object.
310
+ """
311
+ return self._graphicData.progId
312
+
313
+ @property
314
+ def show_as_icon(self) -> bool | None:
315
+ """True when OLE object should appear as an icon (rather than preview)."""
316
+ return self._graphicData.showAsIcon
pptx2/shapes/group.py ADDED
@@ -0,0 +1,264 @@
1
+ """GroupShape and related objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Iterator
6
+
7
+ from pptx2.dml.effect import (
8
+ BlurFormat,
9
+ GlowFormat,
10
+ InnerShadowFormat,
11
+ PresetShadowFormat,
12
+ ReflectionFormat,
13
+ ShadowFormat,
14
+ SoftEdgeFormat,
15
+ )
16
+ from pptx2.dml.fill import FillFormat
17
+ from pptx2.enum.shapes import MSO_SHAPE_TYPE
18
+ from pptx2.shapes.base import BaseShape
19
+ from pptx2.util import Emu, _coerce_emu, lazyproperty
20
+
21
+ if TYPE_CHECKING:
22
+ from pptx2.action import ActionSetting
23
+ from pptx2.oxml.shapes import ShapeElement
24
+ from pptx2.oxml.shapes.groupshape import CT_GroupShape
25
+ from pptx2.shapes.shapetree import GroupShapes
26
+ from pptx2.types import ProvidesPart
27
+ from pptx2.util import Length
28
+
29
+
30
+ class GroupShape(BaseShape):
31
+ """A shape that acts as a container for other shapes."""
32
+
33
+ def __init__(self, grpSp: CT_GroupShape, parent: ProvidesPart):
34
+ super().__init__(grpSp, parent)
35
+ self._grpSp = grpSp
36
+
37
+ @lazyproperty
38
+ def click_action(self) -> ActionSetting:
39
+ """Unconditionally raises `TypeError`.
40
+
41
+ A group shape cannot have a click action or hover action.
42
+ """
43
+ raise TypeError("a group shape cannot have a click action")
44
+
45
+ @property
46
+ def has_text_frame(self) -> bool:
47
+ """Unconditionally |False|.
48
+
49
+ A group shape does not have a textframe and cannot itself contain text. This does not
50
+ impact the ability of shapes contained by the group to each have their own text.
51
+ """
52
+ return False
53
+
54
+ @lazyproperty
55
+ def blur(self) -> BlurFormat:
56
+ """|BlurFormat| object representing the Gaussian blur on this group."""
57
+ return BlurFormat(self._grpSp.grpSpPr)
58
+
59
+ @lazyproperty
60
+ def glow(self) -> GlowFormat:
61
+ """|GlowFormat| object representing glow effect for this group."""
62
+ return GlowFormat(self._grpSp.grpSpPr)
63
+
64
+ @lazyproperty
65
+ def reflection(self) -> ReflectionFormat:
66
+ """|ReflectionFormat| object representing the reflection on this group."""
67
+ return ReflectionFormat(self._grpSp.grpSpPr)
68
+
69
+ @lazyproperty
70
+ def shadow(self) -> ShadowFormat:
71
+ """|ShadowFormat| object representing shadow effect for this group.
72
+
73
+ A |ShadowFormat| object is always returned, even when no shadow is explicitly defined on
74
+ this group shape (i.e. when the group inherits its shadow behavior).
75
+ """
76
+ return ShadowFormat(self._grpSp.grpSpPr)
77
+
78
+ @lazyproperty
79
+ def soft_edges(self) -> SoftEdgeFormat:
80
+ """|SoftEdgeFormat| object representing soft-edge effect for this group."""
81
+ return SoftEdgeFormat(self._grpSp.grpSpPr)
82
+
83
+ @lazyproperty
84
+ def inner_shadow(self) -> InnerShadowFormat:
85
+ """|InnerShadowFormat| for this group (over ``p:grpSpPr``).
86
+
87
+ Overrides the |BaseShape| version, which targets ``spPr`` — a group
88
+ exposes ``grpSpPr`` instead, so the inherited accessor would raise.
89
+ """
90
+ return InnerShadowFormat(self._grpSp.grpSpPr)
91
+
92
+ @lazyproperty
93
+ def preset_shadow(self) -> PresetShadowFormat:
94
+ """|PresetShadowFormat| for this group (over ``p:grpSpPr``)."""
95
+ return PresetShadowFormat(self._grpSp.grpSpPr)
96
+
97
+ @property
98
+ def three_d(self):
99
+ """Unconditionally raises |NotImplementedError|.
100
+
101
+ A group shape's ``p:grpSpPr`` legally carries ``a:scene3d`` but not
102
+ ``a:sp3d`` (bevel / extrusion), so the |ThreeDFormat| facade — which
103
+ writes both — cannot target it without emitting schema-invalid XML.
104
+ Apply 3-D formatting to the member shapes instead.
105
+ """
106
+ raise NotImplementedError("three_d property on GroupShape not supported")
107
+
108
+ @property
109
+ def fill(self) -> FillFormat:
110
+ """|FillFormat| instance for this group, providing access to fill properties.
111
+
112
+ A group's ``p:grpSpPr`` admits a fill (but, unlike a regular shape, *not* a
113
+ line — the OOXML schema does not allow ``a:ln`` on a group). Setting a fill
114
+ tints the whole group; member shapes that declare their own fill are
115
+ unaffected and paint on top. Use ``fill.solid()``, ``fill.gradient()``,
116
+ ``fill.background()`` (transparent), etc., exactly as on an autoshape::
117
+
118
+ group.fill.solid()
119
+ group.fill.fore_color.rgb = "1F4E79"
120
+ """
121
+ return FillFormat.from_fill_parent(self._grpSp.grpSpPr)
122
+
123
+ @property
124
+ def shape_type(self) -> MSO_SHAPE_TYPE:
125
+ """Member of :ref:`MsoShapeType` identifying the type of this shape.
126
+
127
+ Unconditionally `MSO_SHAPE_TYPE.GROUP` in this case
128
+ """
129
+ return MSO_SHAPE_TYPE.GROUP
130
+
131
+ @lazyproperty
132
+ def shapes(self) -> GroupShapes:
133
+ """|GroupShapes| object for this group.
134
+
135
+ The |GroupShapes| object provides access to the group's member shapes and provides methods
136
+ for adding new ones.
137
+ """
138
+ from pptx2.shapes.shapetree import GroupShapes
139
+
140
+ return GroupShapes(self._element, self)
141
+
142
+ def move(self, dx: Length | int | float, dy: Length | int | float) -> GroupShape:
143
+ """Translate the entire group by (*dx*, *dy*) and return self.
144
+
145
+ *dx* and *dy* are lengths (e.g. ``Inches(1)``, ``Emu(...)``, or a bare
146
+ EMU ``int``)::
147
+
148
+ group.move(Inches(0.5), Inches(-0.25))
149
+
150
+ The group's offset, its child-coordinate origin, and every member are
151
+ shifted by the same translation. Shifting the members + ``chOff`` (not
152
+ just the group's own ``off``) is what makes the move *durable*: a later
153
+ ``recalculate_extents()`` — triggered by ``group.shapes.add_*`` or
154
+ ``fit_to_children()`` — recomputes ``off`` from the member geometry, so
155
+ a move that only touched ``off`` would silently snap back.
156
+ """
157
+ grpSp = self._grpSp
158
+ edx = int(_coerce_emu(dx))
159
+ edy = int(_coerce_emu(dy))
160
+ # Express the translation in the group's child-coordinate space too, in
161
+ # case the group is scaled (ext != chExt); for the common unscaled
162
+ # group this is just (edx, edy).
163
+ ext_cx = int(self.width) if self.width is not None else 0
164
+ ext_cy = int(self.height) if self.height is not None else 0
165
+ chExt = grpSp.chExt
166
+ ch_cx, ch_cy = int(chExt.cx or 0), int(chExt.cy or 0)
167
+ cdx = round(edx * ch_cx / ext_cx) if ext_cx else edx
168
+ cdy = round(edy * ch_cy / ext_cy) if ext_cy else edy
169
+
170
+ self.left = Emu((int(self.left) if self.left is not None else 0) + edx)
171
+ self.top = Emu((int(self.top) if self.top is not None else 0) + edy)
172
+ chOff = grpSp.chOff
173
+ chOff.x = Emu(int(chOff.x or 0) + cdx)
174
+ chOff.y = Emu(int(chOff.y or 0) + cdy)
175
+ for elm in grpSp.iter_shape_elms():
176
+ elm.x = Emu(int(elm.x or 0) + cdx)
177
+ elm.y = Emu(int(elm.y or 0) + cdy)
178
+ return self
179
+
180
+ def walk(self) -> Iterator[BaseShape]:
181
+ """Generate every descendant shape, recursing into nested groups.
182
+
183
+ Yields shapes depth-first in document (z-order) order. Nested
184
+ |GroupShape| objects are themselves yielded *before* their children, so
185
+ callers that only want leaf shapes can filter with
186
+ ``s.shape_type != MSO_SHAPE_TYPE.GROUP``. This makes whole-tree layout,
187
+ measurement, and lint passes possible without hand-rolled recursion::
188
+
189
+ for shape in group.walk():
190
+ ...
191
+ """
192
+ for shape in self.shapes:
193
+ yield shape
194
+ if isinstance(shape, GroupShape):
195
+ yield from shape.walk()
196
+
197
+ def fit_to_children(self) -> GroupShape:
198
+ """Shrink-wrap the group's offset/extent to tightly bound its children.
199
+
200
+ Recalculates the group's position and size (``a:off`` / ``a:ext``) from
201
+ the current geometry of its member shapes — the same recalculation that
202
+ runs automatically when a shape is added through ``group.shapes.add_*``.
203
+ Call it after moving or resizing member shapes directly so the group's
204
+ ``bbox`` (and any lint that relies on it) stays accurate. Returns self.
205
+ """
206
+ self._grpSp.recalculate_extents()
207
+ return self
208
+
209
+ def ungroup(self) -> list[BaseShape]:
210
+ """Dissolve the group, promoting its member shapes to the parent and return them.
211
+
212
+ Each child is re-parented to the group's container (the slide shape tree
213
+ or an enclosing group) with its position and size transformed from the
214
+ group's child coordinate space into the container's space, so shapes do
215
+ not move or resize visually. Z-order is preserved: the promoted shapes
216
+ occupy the group's former slot. The (now empty) group element is removed.
217
+
218
+ Raises ``ValueError`` if the group is rotated or flipped — baking such a
219
+ transform into each child is ambiguous and unsupported; reset rotation
220
+ and flip to zero before ungrouping.
221
+ """
222
+ grpSp = self._grpSp
223
+ container = grpSp.getparent()
224
+ if container is None:
225
+ raise ValueError("cannot ungroup a group that is not in a shape tree")
226
+ if self.rotation or grpSp.flipH or grpSp.flipV:
227
+ raise ValueError(
228
+ "ungroup() does not support a rotated or flipped group; reset "
229
+ "rotation and flip to 0 before ungrouping"
230
+ )
231
+
232
+ # Group offset/extent (in the container's coordinate space) and the
233
+ # group's own child coordinate space (chOff/chExt).
234
+ off_x = int(self.left) if self.left is not None else 0
235
+ off_y = int(self.top) if self.top is not None else 0
236
+ ext_cx = int(self.width) if self.width is not None else 0
237
+ ext_cy = int(self.height) if self.height is not None else 0
238
+ chOff, chExt = grpSp.chOff, grpSp.chExt
239
+ ch_x, ch_y = int(chOff.x or 0), int(chOff.y or 0)
240
+ ch_cx, ch_cy = int(chExt.cx or 0), int(chExt.cy or 0)
241
+ sx = (ext_cx / ch_cx) if ch_cx else 1.0
242
+ sy = (ext_cy / ch_cy) if ch_cy else 1.0
243
+
244
+ # Pre-compute each child's container-space geometry from its child-space
245
+ # coords before re-parenting (so the read isn't affected by the moves).
246
+ placements: list[tuple[ShapeElement, int, int, int, int]] = []
247
+ for elm in grpSp.iter_shape_elms():
248
+ cx0 = int(elm.x or 0)
249
+ cy0 = int(elm.y or 0)
250
+ cw = int(elm.cx or 0)
251
+ chgt = int(elm.cy or 0)
252
+ new_x = off_x + round((cx0 - ch_x) * sx)
253
+ new_y = off_y + round((cy0 - ch_y) * sy)
254
+ placements.append((elm, new_x, new_y, round(cw * sx), round(chgt * sy)))
255
+
256
+ promoted: list[BaseShape] = []
257
+ for elm, nx, ny, nw, nh in placements:
258
+ grpSp.remove(elm)
259
+ container.insert(container.index(grpSp), elm)
260
+ elm.x, elm.y, elm.cx, elm.cy = Emu(nx), Emu(ny), Emu(nw), Emu(nh)
261
+ promoted.append(self._parent._shape_factory(elm)) # pyright: ignore[reportAttributeAccessIssue]
262
+
263
+ container.remove(grpSp)
264
+ return promoted