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,297 @@
1
+ """Connector (line) shape and related objects.
2
+
3
+ A connector is a line shape having end-points that can be connected to other
4
+ objects (but not to other connectors). A connector can be straight, have
5
+ elbows, or can be curved.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pptx2.dml.line import LineFormat
11
+ from pptx2.enum.shapes import MSO_SHAPE_TYPE
12
+ from pptx2.shapes.base import BaseShape
13
+ from pptx2.util import Emu, lazyproperty
14
+
15
+
16
+ class Connector(BaseShape):
17
+ """Connector (line) shape.
18
+
19
+ A connector is a linear shape having end-points that can be connected to
20
+ other objects (but not to other connectors). A connector can be straight,
21
+ have elbows, or can be curved.
22
+ """
23
+
24
+ def begin_connect(self, shape, cxn_pt_idx):
25
+ """
26
+ **EXPERIMENTAL** - *The current implementation only works properly
27
+ with rectangular shapes, such as pictures and rectangles. Use with
28
+ other shape types may cause unexpected visual alignment of the
29
+ connected end-point and could lead to a load error if cxn_pt_idx
30
+ exceeds the connection point count available on the connected shape.
31
+ That said, a quick test should reveal what to expect when using this
32
+ method with other shape types.*
33
+
34
+ Connect the beginning of this connector to *shape* at the connection
35
+ point specified by *cxn_pt_idx*. Each shape has zero or more
36
+ connection points and they are identified by index, starting with 0.
37
+ Generally, the first connection point of a shape is at the top center
38
+ of its bounding box and numbering proceeds counter-clockwise from
39
+ there. However this is only a convention and may vary, especially
40
+ with non built-in shapes.
41
+ """
42
+ self._connect_begin_to(shape, cxn_pt_idx)
43
+ self._move_begin_to_cxn(shape, cxn_pt_idx)
44
+
45
+ @property
46
+ def begin_x(self):
47
+ """
48
+ Return the X-position of the begin point of this connector, in
49
+ English Metric Units (as a |Length| object).
50
+ """
51
+ cxnSp = self._element
52
+ x, cx, flipH = cxnSp.x, cxnSp.cx, cxnSp.flipH
53
+ begin_x = x + cx if flipH else x
54
+ return Emu(begin_x)
55
+
56
+ @begin_x.setter
57
+ def begin_x(self, value):
58
+ cxnSp = self._element
59
+ x, cx, flipH, new_x = cxnSp.x, cxnSp.cx, cxnSp.flipH, int(value)
60
+
61
+ if flipH:
62
+ old_x = x + cx
63
+ dx = abs(new_x - old_x)
64
+ if new_x >= old_x:
65
+ cxnSp.cx = cx + dx
66
+ elif dx <= cx:
67
+ cxnSp.cx = cx - dx
68
+ else:
69
+ cxnSp.flipH = False
70
+ cxnSp.x = new_x
71
+ cxnSp.cx = dx - cx
72
+ else:
73
+ dx = abs(new_x - x)
74
+ if new_x <= x:
75
+ cxnSp.x = new_x
76
+ cxnSp.cx = cx + dx
77
+ elif dx <= cx:
78
+ cxnSp.x = new_x
79
+ cxnSp.cx = cx - dx
80
+ else:
81
+ cxnSp.flipH = True
82
+ cxnSp.x = x + cx
83
+ cxnSp.cx = dx - cx
84
+
85
+ @property
86
+ def begin_y(self):
87
+ """
88
+ Return the Y-position of the begin point of this connector, in
89
+ English Metric Units (as a |Length| object).
90
+ """
91
+ cxnSp = self._element
92
+ y, cy, flipV = cxnSp.y, cxnSp.cy, cxnSp.flipV
93
+ begin_y = y + cy if flipV else y
94
+ return Emu(begin_y)
95
+
96
+ @begin_y.setter
97
+ def begin_y(self, value):
98
+ cxnSp = self._element
99
+ y, cy, flipV, new_y = cxnSp.y, cxnSp.cy, cxnSp.flipV, int(value)
100
+
101
+ if flipV:
102
+ old_y = y + cy
103
+ dy = abs(new_y - old_y)
104
+ if new_y >= old_y:
105
+ cxnSp.cy = cy + dy
106
+ elif dy <= cy:
107
+ cxnSp.cy = cy - dy
108
+ else:
109
+ cxnSp.flipV = False
110
+ cxnSp.y = new_y
111
+ cxnSp.cy = dy - cy
112
+ else:
113
+ dy = abs(new_y - y)
114
+ if new_y <= y:
115
+ cxnSp.y = new_y
116
+ cxnSp.cy = cy + dy
117
+ elif dy <= cy:
118
+ cxnSp.y = new_y
119
+ cxnSp.cy = cy - dy
120
+ else:
121
+ cxnSp.flipV = True
122
+ cxnSp.y = y + cy
123
+ cxnSp.cy = dy - cy
124
+
125
+ def end_connect(self, shape, cxn_pt_idx):
126
+ """
127
+ **EXPERIMENTAL** - *The current implementation only works properly
128
+ with rectangular shapes, such as pictures and rectangles. Use with
129
+ other shape types may cause unexpected visual alignment of the
130
+ connected end-point and could lead to a load error if cxn_pt_idx
131
+ exceeds the connection point count available on the connected shape.
132
+ That said, a quick test should reveal what to expect when using this
133
+ method with other shape types.*
134
+
135
+ Connect the ending of this connector to *shape* at the connection
136
+ point specified by *cxn_pt_idx*.
137
+ """
138
+ self._connect_end_to(shape, cxn_pt_idx)
139
+ self._move_end_to_cxn(shape, cxn_pt_idx)
140
+
141
+ @property
142
+ def end_x(self):
143
+ """
144
+ Return the X-position of the end point of this connector, in English
145
+ Metric Units (as a |Length| object).
146
+ """
147
+ cxnSp = self._element
148
+ x, cx, flipH = cxnSp.x, cxnSp.cx, cxnSp.flipH
149
+ end_x = x if flipH else x + cx
150
+ return Emu(end_x)
151
+
152
+ @end_x.setter
153
+ def end_x(self, value):
154
+ cxnSp = self._element
155
+ x, cx, flipH, new_x = cxnSp.x, cxnSp.cx, cxnSp.flipH, int(value)
156
+
157
+ if flipH:
158
+ dx = abs(new_x - x)
159
+ if new_x <= x:
160
+ cxnSp.x = new_x
161
+ cxnSp.cx = cx + dx
162
+ elif dx <= cx:
163
+ cxnSp.x = new_x
164
+ cxnSp.cx = cx - dx
165
+ else:
166
+ cxnSp.flipH = False
167
+ cxnSp.x = x + cx
168
+ cxnSp.cx = dx - cx
169
+ else:
170
+ old_x = x + cx
171
+ dx = abs(new_x - old_x)
172
+ if new_x >= old_x:
173
+ cxnSp.cx = cx + dx
174
+ elif dx <= cx:
175
+ cxnSp.cx = cx - dx
176
+ else:
177
+ cxnSp.flipH = True
178
+ cxnSp.x = new_x
179
+ cxnSp.cx = dx - cx
180
+
181
+ @property
182
+ def end_y(self):
183
+ """
184
+ Return the Y-position of the end point of this connector, in English
185
+ Metric Units (as a |Length| object).
186
+ """
187
+ cxnSp = self._element
188
+ y, cy, flipV = cxnSp.y, cxnSp.cy, cxnSp.flipV
189
+ end_y = y if flipV else y + cy
190
+ return Emu(end_y)
191
+
192
+ @end_y.setter
193
+ def end_y(self, value):
194
+ cxnSp = self._element
195
+ y, cy, flipV, new_y = cxnSp.y, cxnSp.cy, cxnSp.flipV, int(value)
196
+
197
+ if flipV:
198
+ dy = abs(new_y - y)
199
+ if new_y <= y:
200
+ cxnSp.y = new_y
201
+ cxnSp.cy = cy + dy
202
+ elif dy <= cy:
203
+ cxnSp.y = new_y
204
+ cxnSp.cy = cy - dy
205
+ else:
206
+ cxnSp.flipV = False
207
+ cxnSp.y = y + cy
208
+ cxnSp.cy = dy - cy
209
+ else:
210
+ old_y = y + cy
211
+ dy = abs(new_y - old_y)
212
+ if new_y >= old_y:
213
+ cxnSp.cy = cy + dy
214
+ elif dy <= cy:
215
+ cxnSp.cy = cy - dy
216
+ else:
217
+ cxnSp.flipV = True
218
+ cxnSp.y = new_y
219
+ cxnSp.cy = dy - cy
220
+
221
+ def get_or_add_ln(self):
222
+ """Helper method required by |LineFormat|."""
223
+ return self._element.spPr.get_or_add_ln()
224
+
225
+ @lazyproperty
226
+ def line(self):
227
+ """|LineFormat| instance for this connector.
228
+
229
+ Provides access to line properties such as line color, width, and
230
+ line style.
231
+ """
232
+ return LineFormat(self)
233
+
234
+ @property
235
+ def ln(self):
236
+ """Helper method required by |LineFormat|.
237
+
238
+ The ``<a:ln>`` element containing the line format properties such as
239
+ line color and width. |None| if no `<a:ln>` element is present.
240
+ """
241
+ return self._element.spPr.ln
242
+
243
+ @property
244
+ def shape_type(self):
245
+ """Member of `MSO_SHAPE_TYPE` identifying the type of this shape.
246
+
247
+ Unconditionally `MSO_SHAPE_TYPE.LINE` for a `Connector` object.
248
+ """
249
+ return MSO_SHAPE_TYPE.LINE
250
+
251
+ def _connect_begin_to(self, shape, cxn_pt_idx):
252
+ """
253
+ Add or update a stCxn element for this connector that connects its
254
+ begin point to the connection point of *shape* specified by
255
+ *cxn_pt_idx*.
256
+ """
257
+ cNvCxnSpPr = self._element.nvCxnSpPr.cNvCxnSpPr
258
+ stCxn = cNvCxnSpPr.get_or_add_stCxn()
259
+ stCxn.id = shape.shape_id
260
+ stCxn.idx = cxn_pt_idx
261
+
262
+ def _connect_end_to(self, shape, cxn_pt_idx):
263
+ """
264
+ Add or update an endCxn element for this connector that connects its
265
+ end point to the connection point of *shape* specified by
266
+ *cxn_pt_idx*.
267
+ """
268
+ cNvCxnSpPr = self._element.nvCxnSpPr.cNvCxnSpPr
269
+ endCxn = cNvCxnSpPr.get_or_add_endCxn()
270
+ endCxn.id = shape.shape_id
271
+ endCxn.idx = cxn_pt_idx
272
+
273
+ def _move_begin_to_cxn(self, shape, cxn_pt_idx):
274
+ """
275
+ Move the begin point of this connector to coordinates of the
276
+ connection point of *shape* specified by *cxn_pt_idx*.
277
+ """
278
+ x, y, cx, cy = shape.left, shape.top, shape.width, shape.height
279
+ self.begin_x, self.begin_y = {
280
+ 0: (int(x + cx / 2), y),
281
+ 1: (x, int(y + cy / 2)),
282
+ 2: (int(x + cx / 2), y + cy),
283
+ 3: (x + cx, int(y + cy / 2)),
284
+ }[cxn_pt_idx]
285
+
286
+ def _move_end_to_cxn(self, shape, cxn_pt_idx):
287
+ """
288
+ Move the end point of this connector to the coordinates of the
289
+ connection point of *shape* specified by *cxn_pt_idx*.
290
+ """
291
+ x, y, cx, cy = shape.left, shape.top, shape.width, shape.height
292
+ self.end_x, self.end_y = {
293
+ 0: (int(x + cx / 2), y),
294
+ 1: (x, int(y + cy / 2)),
295
+ 2: (int(x + cx / 2), y + cy),
296
+ 3: (x + cx, int(y + cy / 2)),
297
+ }[cxn_pt_idx]
@@ -0,0 +1,337 @@
1
+ """Objects related to construction of freeform shapes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Iterable, Iterator, Sequence
6
+
7
+ from pptx2.util import Emu, lazyproperty
8
+
9
+ if TYPE_CHECKING:
10
+ from typing_extensions import TypeAlias
11
+
12
+ from pptx2.oxml.shapes.autoshape import (
13
+ CT_Path2D,
14
+ CT_Path2DClose,
15
+ CT_Path2DLineTo,
16
+ CT_Path2DMoveTo,
17
+ CT_Shape,
18
+ )
19
+ from pptx2.shapes.shapetree import _BaseGroupShapes # pyright: ignore[reportPrivateUsage]
20
+ from pptx2.util import Length
21
+
22
+ CT_DrawingOperation: TypeAlias = "CT_Path2DClose | CT_Path2DLineTo | CT_Path2DMoveTo"
23
+ DrawingOperation: TypeAlias = "_LineSegment | _MoveTo | _Close"
24
+
25
+
26
+ class FreeformBuilder(Sequence[DrawingOperation]):
27
+ """Allows a freeform shape to be specified and created.
28
+
29
+ The initial pen position is provided on construction. From there, drawing proceeds using
30
+ successive calls to draw line segments. The freeform shape may be closed by calling the
31
+ :meth:`close` method.
32
+
33
+ A shape may have more than one contour, in which case overlapping areas are "subtracted". A
34
+ contour is a sequence of line segments beginning with a "move-to" operation. A move-to
35
+ operation is automatically inserted in each new freeform; additional move-to ops can be
36
+ inserted with the `.move_to()` method.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ shapes: _BaseGroupShapes,
42
+ start_x: Length,
43
+ start_y: Length,
44
+ x_scale: float,
45
+ y_scale: float,
46
+ ):
47
+ super(FreeformBuilder, self).__init__()
48
+ self._shapes = shapes
49
+ self._start_x = start_x
50
+ self._start_y = start_y
51
+ self._x_scale = x_scale
52
+ self._y_scale = y_scale
53
+
54
+ def __getitem__( # pyright: ignore[reportIncompatibleMethodOverride]
55
+ self, idx: int
56
+ ) -> DrawingOperation:
57
+ return self._drawing_operations.__getitem__(idx)
58
+
59
+ def __iter__(self) -> Iterator[DrawingOperation]:
60
+ return self._drawing_operations.__iter__()
61
+
62
+ def __len__(self):
63
+ return self._drawing_operations.__len__()
64
+
65
+ @classmethod
66
+ def new(
67
+ cls,
68
+ shapes: _BaseGroupShapes,
69
+ start_x: float,
70
+ start_y: float,
71
+ x_scale: float,
72
+ y_scale: float,
73
+ ):
74
+ """Return a new |FreeformBuilder| object.
75
+
76
+ The initial pen location is specified (in local coordinates) by
77
+ (`start_x`, `start_y`).
78
+ """
79
+ return cls(shapes, Emu(int(round(start_x))), Emu(int(round(start_y))), x_scale, y_scale)
80
+
81
+ def add_line_segments(self, vertices: Iterable[tuple[float, float]], close: bool = True):
82
+ """Add a straight line segment to each point in `vertices`.
83
+
84
+ `vertices` must be an iterable of (x, y) pairs (2-tuples). Each x and y value is rounded
85
+ to the nearest integer before use. The optional `close` parameter determines whether the
86
+ resulting contour is `closed` or left `open`.
87
+
88
+ Returns this |FreeformBuilder| object so it can be used in chained calls.
89
+ """
90
+ for x, y in vertices:
91
+ self._add_line_segment(x, y)
92
+ if close:
93
+ self._add_close()
94
+ return self
95
+
96
+ def convert_to_shape(self, origin_x: Length = Emu(0), origin_y: Length = Emu(0)):
97
+ """Return new freeform shape positioned relative to specified offset.
98
+
99
+ `origin_x` and `origin_y` locate the origin of the local coordinate system in slide
100
+ coordinates (EMU), perhaps most conveniently by use of a |Length| object.
101
+
102
+ Note that this method may be called more than once to add multiple shapes of the same
103
+ geometry in different locations on the slide.
104
+ """
105
+ sp = self._add_freeform_sp(origin_x, origin_y)
106
+ path = self._start_path(sp)
107
+ for drawing_operation in self:
108
+ drawing_operation.apply_operation_to(path)
109
+ return self._shapes._shape_factory(sp) # pyright: ignore[reportPrivateUsage]
110
+
111
+ def move_to(self, x: float, y: float):
112
+ """Move pen to (x, y) (local coordinates) without drawing line.
113
+
114
+ Returns this |FreeformBuilder| object so it can be used in chained calls.
115
+ """
116
+ self._drawing_operations.append(_MoveTo.new(self, x, y))
117
+ return self
118
+
119
+ @property
120
+ def shape_offset_x(self) -> Length:
121
+ """Return x distance of shape origin from local coordinate origin.
122
+
123
+ The returned integer represents the leftmost extent of the freeform shape, in local
124
+ coordinates. Note that the bounding box of the shape need not start at the local origin.
125
+ """
126
+ min_x = self._start_x
127
+ for drawing_operation in self:
128
+ if isinstance(drawing_operation, _Close):
129
+ continue
130
+ min_x = min(min_x, drawing_operation.x)
131
+ return Emu(min_x)
132
+
133
+ @property
134
+ def shape_offset_y(self) -> Length:
135
+ """Return y distance of shape origin from local coordinate origin.
136
+
137
+ The returned integer represents the topmost extent of the freeform shape, in local
138
+ coordinates. Note that the bounding box of the shape need not start at the local origin.
139
+ """
140
+ min_y = self._start_y
141
+ for drawing_operation in self:
142
+ if isinstance(drawing_operation, _Close):
143
+ continue
144
+ min_y = min(min_y, drawing_operation.y)
145
+ return Emu(min_y)
146
+
147
+ def _add_close(self):
148
+ """Add a close |_Close| operation to the drawing sequence."""
149
+ self._drawing_operations.append(_Close.new())
150
+
151
+ def _add_freeform_sp(self, origin_x: Length, origin_y: Length):
152
+ """Add a freeform `p:sp` element having no drawing elements.
153
+
154
+ `origin_x` and `origin_y` are specified in slide coordinates, and represent the location
155
+ of the local coordinates origin on the slide.
156
+ """
157
+ spTree = self._shapes._spTree # pyright: ignore[reportPrivateUsage]
158
+ return spTree.add_freeform_sp(
159
+ origin_x + self._left, origin_y + self._top, self._width, self._height
160
+ )
161
+
162
+ def _add_line_segment(self, x: float, y: float) -> None:
163
+ """Add a |_LineSegment| operation to the drawing sequence."""
164
+ self._drawing_operations.append(_LineSegment.new(self, x, y))
165
+
166
+ @lazyproperty
167
+ def _drawing_operations(self) -> list[DrawingOperation]:
168
+ """Return the sequence of drawing operation objects for freeform."""
169
+ return []
170
+
171
+ @property
172
+ def _dx(self) -> Length:
173
+ """Return width of this shape's path in local units."""
174
+ min_x = max_x = self._start_x
175
+ for drawing_operation in self:
176
+ if isinstance(drawing_operation, _Close):
177
+ continue
178
+ min_x = min(min_x, drawing_operation.x)
179
+ max_x = max(max_x, drawing_operation.x)
180
+ return Emu(max_x - min_x)
181
+
182
+ @property
183
+ def _dy(self) -> Length:
184
+ """Return integer height of this shape's path in local units."""
185
+ min_y = max_y = self._start_y
186
+ for drawing_operation in self:
187
+ if isinstance(drawing_operation, _Close):
188
+ continue
189
+ min_y = min(min_y, drawing_operation.y)
190
+ max_y = max(max_y, drawing_operation.y)
191
+ return Emu(max_y - min_y)
192
+
193
+ @property
194
+ def _height(self):
195
+ """Return vertical size of this shape's path in slide coordinates.
196
+
197
+ This value is based on the actual extents of the shape and does not include any
198
+ positioning offset.
199
+ """
200
+ return int(round(self._dy * self._y_scale))
201
+
202
+ @property
203
+ def _left(self):
204
+ """Return leftmost extent of this shape's path in slide coordinates.
205
+
206
+ Note that this value does not include any positioning offset; it assumes the drawing
207
+ (local) coordinate origin is at (0, 0) on the slide.
208
+ """
209
+ return int(round(self.shape_offset_x * self._x_scale))
210
+
211
+ def _local_to_shape(self, local_x: Length, local_y: Length) -> tuple[Length, Length]:
212
+ """Translate local coordinates point to shape coordinates.
213
+
214
+ Shape coordinates have the same unit as local coordinates, but are offset such that the
215
+ origin of the shape coordinate system (0, 0) is located at the top-left corner of the
216
+ shape bounding box.
217
+ """
218
+ return Emu(local_x - self.shape_offset_x), Emu(local_y - self.shape_offset_y)
219
+
220
+ def _start_path(self, sp: CT_Shape) -> CT_Path2D:
221
+ """Return a newly created `a:path` element added to `sp`.
222
+
223
+ The returned `a:path` element has an `a:moveTo` element representing the shape starting
224
+ point as its only child.
225
+ """
226
+ path = sp.add_path(w=self._dx, h=self._dy)
227
+ path.add_moveTo(*self._local_to_shape(self._start_x, self._start_y))
228
+ return path
229
+
230
+ @property
231
+ def _top(self):
232
+ """Return topmost extent of this shape's path in slide coordinates.
233
+
234
+ Note that this value does not include any positioning offset; it assumes the drawing
235
+ (local) coordinate origin is located at slide coordinates (0, 0) (top-left corner of
236
+ slide).
237
+ """
238
+ return int(round(self.shape_offset_y * self._y_scale))
239
+
240
+ @property
241
+ def _width(self):
242
+ """Return width of this shape's path in slide coordinates.
243
+
244
+ This value is based on the actual extents of the shape path and does not include any
245
+ positioning offset.
246
+ """
247
+ return int(round(self._dx * self._x_scale))
248
+
249
+
250
+ class _BaseDrawingOperation(object):
251
+ """Base class for freeform drawing operations.
252
+
253
+ A drawing operation has at least one location (x, y) in local coordinates.
254
+ """
255
+
256
+ def __init__(self, freeform_builder: FreeformBuilder, x: Length, y: Length):
257
+ super(_BaseDrawingOperation, self).__init__()
258
+ self._freeform_builder = freeform_builder
259
+ self._x = x
260
+ self._y = y
261
+
262
+ def apply_operation_to(self, path: CT_Path2D) -> CT_DrawingOperation:
263
+ """Add the XML element(s) implementing this operation to `path`.
264
+
265
+ Must be implemented by each subclass.
266
+ """
267
+ raise NotImplementedError("must be implemented by each subclass")
268
+
269
+ @property
270
+ def x(self) -> Length:
271
+ """Return the horizontal (x) target location of this operation.
272
+
273
+ The returned value is an integer in local coordinates.
274
+ """
275
+ return self._x
276
+
277
+ @property
278
+ def y(self) -> Length:
279
+ """Return the vertical (y) target location of this operation.
280
+
281
+ The returned value is an integer in local coordinates.
282
+ """
283
+ return self._y
284
+
285
+
286
+ class _Close(object):
287
+ """Specifies adding a `<a:close/>` element to the current contour."""
288
+
289
+ @classmethod
290
+ def new(cls) -> _Close:
291
+ """Return a new _Close object."""
292
+ return cls()
293
+
294
+ def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DClose:
295
+ """Add `a:close` element to `path`."""
296
+ return path.add_close()
297
+
298
+
299
+ class _LineSegment(_BaseDrawingOperation):
300
+ """Specifies a straight line segment ending at the specified point."""
301
+
302
+ @classmethod
303
+ def new(cls, freeform_builder: FreeformBuilder, x: float, y: float) -> _LineSegment:
304
+ """Return a new _LineSegment object ending at point *(x, y)*.
305
+
306
+ Both `x` and `y` are rounded to the nearest integer before use.
307
+ """
308
+ return cls(freeform_builder, Emu(int(round(x))), Emu(int(round(y))))
309
+
310
+ def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DLineTo:
311
+ """Add `a:lnTo` element to `path` for this line segment.
312
+
313
+ Returns the `a:lnTo` element newly added to the path.
314
+ """
315
+ return path.add_lnTo(
316
+ Emu(self._x - self._freeform_builder.shape_offset_x),
317
+ Emu(self._y - self._freeform_builder.shape_offset_y),
318
+ )
319
+
320
+
321
+ class _MoveTo(_BaseDrawingOperation):
322
+ """Specifies a new pen position."""
323
+
324
+ @classmethod
325
+ def new(cls, freeform_builder: FreeformBuilder, x: float, y: float) -> _MoveTo:
326
+ """Return a new _MoveTo object for move to point `(x, y)`.
327
+
328
+ Both `x` and `y` are rounded to the nearest integer before use.
329
+ """
330
+ return cls(freeform_builder, Emu(int(round(x))), Emu(int(round(y))))
331
+
332
+ def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DMoveTo:
333
+ """Add `a:moveTo` element to `path` for this line segment."""
334
+ return path.add_moveTo(
335
+ Emu(self._x - self._freeform_builder.shape_offset_x),
336
+ Emu(self._y - self._freeform_builder.shape_offset_y),
337
+ )