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/text/layout.py ADDED
@@ -0,0 +1,374 @@
1
+ """Objects related to layout of rendered text, such as TextFitter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PIL import ImageFont
8
+
9
+ if TYPE_CHECKING:
10
+ from pptx2.util import Length
11
+
12
+
13
+ class TextFitter(tuple):
14
+ """Value object that knows how to fit text into given rectangular extents."""
15
+
16
+ def __new__(cls, line_source, extents, font_file):
17
+ width, height = extents
18
+ return tuple.__new__(cls, (line_source, width, height, font_file))
19
+
20
+ @classmethod
21
+ def best_fit_font_size(
22
+ cls, text: str, extents: tuple[Length, Length], max_size: int, font_file: str | None
23
+ ) -> int | None:
24
+ """Return whole-number best fit point size less than or equal to `max_size`.
25
+
26
+ The return value is the largest whole-number point size less than or equal to
27
+ `max_size` that allows `text` to fit completely within `extents` when rendered
28
+ using font defined in `font_file`. Returns ``None`` when even 1pt overflows the
29
+ extents — callers are expected to surface that as an error (see
30
+ :meth:`TextFrame._best_fit_font_size`).
31
+ """
32
+ line_source = _LineSource(text)
33
+ text_fitter = cls(line_source, extents, font_file)
34
+ return text_fitter._best_fit_font_size(max_size)
35
+
36
+ def _best_fit_font_size(self, max_size: int) -> int | None:
37
+ """
38
+ Return the largest whole-number point size less than or equal to
39
+ *max_size* that this fitter can fit, or ``None`` when no size fits.
40
+ """
41
+ predicate = self._fits_inside_predicate
42
+ sizes = _BinarySearchTree.from_ordered_sequence(range(1, int(max_size) + 1))
43
+ return sizes.find_max(predicate)
44
+
45
+ def _break_line(self, line_source, point_size):
46
+ """
47
+ Return a (line, remainder) pair where *line* is the longest line in
48
+ *line_source* that will fit in this fitter's width and *remainder* is
49
+ a |_LineSource| object containing the text following the break point.
50
+ """
51
+ lines = _BinarySearchTree.from_ordered_sequence(line_source)
52
+ predicate = self._fits_in_width_predicate(point_size)
53
+ return lines.find_max(predicate)
54
+
55
+ def _fits_in_width_predicate(self, point_size):
56
+ """
57
+ Return a function taking a text string value and returns |True| if
58
+ that text fits in this fitter when rendered at *point_size*. Used as
59
+ predicate for _break_line()
60
+ """
61
+
62
+ def predicate(line):
63
+ """
64
+ Return |True| if *line* fits in this fitter when rendered at
65
+ *point_size*.
66
+ """
67
+ cx = _rendered_size(line.text, point_size, self._font_file)[0]
68
+ return cx <= self._width
69
+
70
+ return predicate
71
+
72
+ @property
73
+ def _fits_inside_predicate(self):
74
+ """Return function taking an integer point size argument.
75
+
76
+ The function returns |True| if the text in this fitter can be wrapped to fit
77
+ entirely within its extents when rendered at that point size.
78
+ """
79
+
80
+ def predicate(point_size):
81
+ """Return |True| when text in `line_source` can be wrapped to fit.
82
+
83
+ Fit means text can be broken into lines that fit entirely within `extents`
84
+ when rendered at `point_size` using the font defined in `font_file`.
85
+ """
86
+ text_lines = self._wrap_lines(self._line_source, point_size)
87
+ if text_lines is None:
88
+ # No whole word fits in the available width at this point
89
+ # size; treat as "doesn't fit" so the search continues to
90
+ # smaller sizes rather than crashing. Returning ``None``
91
+ # is the explicit no-fit signal from ``_wrap_lines`` /
92
+ # ``_break_line``.
93
+ return False
94
+ # Use the bigger of (Pillow ink-box height) and (1.2× the
95
+ # point-size baseline) per line. Pillow's getbbox on ``"Ty"``
96
+ # returns the *ink box* — descenders and ascender margins
97
+ # included, but not line leading — which under-estimates the
98
+ # vertical space a wrapped line actually occupies in
99
+ # PowerPoint / LibreOffice (where leading bumps each line to
100
+ # roughly 1.2× the point size). The pre-fix predicate
101
+ # accepted 72pt wrapping to 2 lines inside a 2-inch box
102
+ # because 2 × ink-box ≈ 1.6 inches; the rendered layout
103
+ # actually consumed 2.4 inches and overflowed. See
104
+ # IMPROVEMENTS item 7.
105
+ ink_cy = _rendered_size("Ty", point_size, self._font_file)[1]
106
+ leading_cy = int(point_size * 1.2 * 914400 / 72.0)
107
+ cy = max(ink_cy, leading_cy)
108
+ return (cy * len(text_lines)) <= self._height
109
+
110
+ return predicate
111
+
112
+ @property
113
+ def _font_file(self):
114
+ return self[3]
115
+
116
+ @property
117
+ def _height(self):
118
+ return self[2]
119
+
120
+ @property
121
+ def _line_source(self):
122
+ return self[0]
123
+
124
+ @property
125
+ def _width(self):
126
+ return self[1]
127
+
128
+ def _wrap_lines(self, line_source, point_size):
129
+ """
130
+ Return a sequence of str values representing the text in
131
+ *line_source* wrapped within this fitter when rendered at
132
+ *point_size*.
133
+
134
+ Returns ``None`` when no whole word from *line_source* fits in
135
+ the available width at *point_size* (``_break_line`` returns
136
+ ``None``), signalling "no-fit" to callers without raising.
137
+ """
138
+ result = self._break_line(line_source, point_size)
139
+ if result is None:
140
+ return None
141
+ text, remainder = result
142
+ lines = [text]
143
+ if remainder:
144
+ sub = self._wrap_lines(remainder, point_size)
145
+ if sub is None:
146
+ return None
147
+ lines.extend(sub)
148
+ return lines
149
+
150
+
151
+ class _BinarySearchTree(object):
152
+ """
153
+ A node in a binary search tree. Uniform for root, subtree root, and leaf
154
+ nodes.
155
+ """
156
+
157
+ def __init__(self, value):
158
+ self._value = value
159
+ self._lesser = None
160
+ self._greater = None
161
+
162
+ def find_max(self, predicate, max_=None):
163
+ """
164
+ Return the largest item in or under this node that satisfies
165
+ *predicate*.
166
+ """
167
+ if predicate(self.value):
168
+ max_ = self.value
169
+ next_node = self._greater
170
+ else:
171
+ next_node = self._lesser
172
+ if next_node is None:
173
+ return max_
174
+ return next_node.find_max(predicate, max_)
175
+
176
+ @classmethod
177
+ def from_ordered_sequence(cls, iseq):
178
+ """
179
+ Return the root of a balanced binary search tree populated with the
180
+ values in iterable *iseq*.
181
+ """
182
+ seq = list(iseq)
183
+ # optimize for usually all fits by making longest first
184
+ bst = cls(seq.pop())
185
+ bst._insert_from_ordered_sequence(seq)
186
+ return bst
187
+
188
+ def insert(self, value):
189
+ """
190
+ Insert a new node containing *value* into this tree such that its
191
+ structure as a binary search tree is preserved.
192
+ """
193
+ side = "_lesser" if value < self.value else "_greater"
194
+ child = getattr(self, side)
195
+ if child is None:
196
+ setattr(self, side, _BinarySearchTree(value))
197
+ else:
198
+ child.insert(value)
199
+
200
+ def tree(self, level=0, prefix=""):
201
+ """
202
+ A string representation of the tree rooted in this node, useful for
203
+ debugging purposes.
204
+ """
205
+ text = "%s%s\n" % (prefix, self.value.text)
206
+ prefix = "%s└── " % (" " * level)
207
+ if self._lesser:
208
+ text += self._lesser.tree(level + 1, prefix)
209
+ if self._greater:
210
+ text += self._greater.tree(level + 1, prefix)
211
+ return text
212
+
213
+ @property
214
+ def value(self):
215
+ """
216
+ The value object contained in this node.
217
+ """
218
+ return self._value
219
+
220
+ @staticmethod
221
+ def _bisect(seq):
222
+ """
223
+ Return a (medial_value, greater_values, lesser_values) 3-tuple
224
+ obtained by bisecting sequence *seq*.
225
+ """
226
+ if len(seq) == 0:
227
+ return [], None, []
228
+ mid_idx = int(len(seq) / 2)
229
+ mid = seq[mid_idx]
230
+ greater = seq[mid_idx + 1 :]
231
+ lesser = seq[:mid_idx]
232
+ return mid, greater, lesser
233
+
234
+ def _insert_from_ordered_sequence(self, seq):
235
+ """
236
+ Insert the new values contained in *seq* into this tree such that
237
+ a balanced tree is produced.
238
+ """
239
+ if len(seq) == 0:
240
+ return
241
+ mid, greater, lesser = self._bisect(seq)
242
+ self.insert(mid)
243
+ self._insert_from_ordered_sequence(greater)
244
+ self._insert_from_ordered_sequence(lesser)
245
+
246
+
247
+ class _LineSource(object):
248
+ """
249
+ Generates all the possible even-word line breaks in a string of text,
250
+ each in the form of a (line, remainder) 2-tuple where *line* contains the
251
+ text before the break and *remainder* the text after as a |_LineSource|
252
+ object. Its boolean value is |True| when it contains text, |False| when
253
+ its text is the empty string or whitespace only.
254
+ """
255
+
256
+ def __init__(self, text):
257
+ self._text = text
258
+
259
+ def __bool__(self):
260
+ """
261
+ Gives this object boolean behaviors (in Python 3). bool(line_source)
262
+ is False if it contains the empty string or whitespace only.
263
+ """
264
+ return self._text.strip() != ""
265
+
266
+ def __eq__(self, other):
267
+ return self._text == other._text
268
+
269
+ def __iter__(self):
270
+ """
271
+ Generate a (text, remainder) pair for each possible even-word line
272
+ break in this line source, where *text* is a str value and remainder
273
+ is a |_LineSource| value.
274
+ """
275
+ words = self._text.split()
276
+ for idx in range(1, len(words) + 1):
277
+ line_text = " ".join(words[:idx])
278
+ remainder_text = " ".join(words[idx:])
279
+ remainder = _LineSource(remainder_text)
280
+ yield _Line(line_text, remainder)
281
+
282
+ def __nonzero__(self):
283
+ """
284
+ Gives this object boolean behaviors (in Python 2). bool(line_source)
285
+ is False if it contains the empty string or whitespace only.
286
+ """
287
+ return self._text.strip() != ""
288
+
289
+ def __repr__(self):
290
+ return "<_LineSource('%s')>" % self._text
291
+
292
+
293
+ class _Line(tuple):
294
+ """
295
+ A candidate line broken at an even word boundary from a string of text,
296
+ and a |_LineSource| value containing the text that remains after the line
297
+ is broken at this spot.
298
+ """
299
+
300
+ def __new__(cls, text, remainder):
301
+ return tuple.__new__(cls, (text, remainder))
302
+
303
+ def __gt__(self, other):
304
+ return len(self.text) > len(other.text)
305
+
306
+ def __lt__(self, other):
307
+ return not self.__gt__(other)
308
+
309
+ def __len__(self):
310
+ return len(self.text)
311
+
312
+ def __repr__(self):
313
+ return "'%s' => '%s'" % (self.text, self.remainder)
314
+
315
+ @property
316
+ def remainder(self):
317
+ return self[1]
318
+
319
+ @property
320
+ def text(self):
321
+ return self[0]
322
+
323
+
324
+ class _Fonts(object):
325
+ """
326
+ A memoizing cache for ImageFont objects.
327
+ """
328
+
329
+ fonts = {}
330
+
331
+ @classmethod
332
+ def font(cls, font_path, point_size):
333
+ key = (font_path, point_size)
334
+ if key not in cls.fonts:
335
+ cls.fonts[key] = cls._load(font_path, point_size)
336
+ return cls.fonts[key]
337
+
338
+ @staticmethod
339
+ def _load(font_path, point_size):
340
+ if font_path is None:
341
+ # `font_path=None` opts in to Pillow's bundled default font; this
342
+ # is the fallback when no matching system font can be located, so
343
+ # `TextFrame.fit_text` works on platforms without the requested
344
+ # family installed (notably most Linux runtimes).
345
+ try:
346
+ return ImageFont.load_default(size=point_size)
347
+ except TypeError:
348
+ # Pillow < 10.1 does not accept a `size` argument; fall back
349
+ # to the unsized bitmap default. Less accurate but at least
350
+ # never blows up.
351
+ return ImageFont.load_default()
352
+ return ImageFont.truetype(font_path, point_size)
353
+
354
+
355
+ def _rendered_size(text, point_size, font_file):
356
+ """
357
+ Return a (width, height) pair representing the size of *text* in English
358
+ Metric Units (EMU) when rendered at *point_size* in the font defined in
359
+ *font_file*.
360
+ """
361
+ emu_per_inch = 914400
362
+ px_per_inch = 72.0
363
+
364
+ font = _Fonts.font(font_file, point_size)
365
+ try:
366
+ px_width, px_height = font.getsize(text)
367
+ except AttributeError:
368
+ left, top, right, bottom = font.getbbox(text)
369
+ px_width, px_height = right - left, bottom - top
370
+
371
+ emu_width = int(px_width / px_per_inch * emu_per_inch)
372
+ emu_height = int(px_height / px_per_inch * emu_per_inch)
373
+
374
+ return emu_width, emu_height