ps-python-pptx 1.1.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 (117) hide show
  1. pptx/__init__.py +84 -0
  2. pptx/action.py +270 -0
  3. pptx/api.py +49 -0
  4. pptx/chart/__init__.py +0 -0
  5. pptx/chart/axis.py +523 -0
  6. pptx/chart/category.py +200 -0
  7. pptx/chart/chart.py +280 -0
  8. pptx/chart/data.py +864 -0
  9. pptx/chart/datalabel.py +288 -0
  10. pptx/chart/legend.py +79 -0
  11. pptx/chart/marker.py +70 -0
  12. pptx/chart/plot.py +412 -0
  13. pptx/chart/point.py +101 -0
  14. pptx/chart/series.py +258 -0
  15. pptx/chart/xlsx.py +272 -0
  16. pptx/chart/xmlwriter.py +1840 -0
  17. pptx/dml/__init__.py +0 -0
  18. pptx/dml/chtfmt.py +40 -0
  19. pptx/dml/color.py +301 -0
  20. pptx/dml/effect.py +41 -0
  21. pptx/dml/fill.py +468 -0
  22. pptx/dml/line.py +100 -0
  23. pptx/enum/__init__.py +0 -0
  24. pptx/enum/action.py +71 -0
  25. pptx/enum/base.py +175 -0
  26. pptx/enum/chart.py +492 -0
  27. pptx/enum/dml.py +405 -0
  28. pptx/enum/lang.py +685 -0
  29. pptx/enum/shapes.py +1029 -0
  30. pptx/enum/text.py +371 -0
  31. pptx/exc.py +23 -0
  32. pptx/media.py +197 -0
  33. pptx/opc/__init__.py +0 -0
  34. pptx/opc/constants.py +331 -0
  35. pptx/opc/oxml.py +188 -0
  36. pptx/opc/package.py +762 -0
  37. pptx/opc/packuri.py +109 -0
  38. pptx/opc/serialized.py +296 -0
  39. pptx/opc/shared.py +20 -0
  40. pptx/opc/spec.py +44 -0
  41. pptx/oxml/__init__.py +492 -0
  42. pptx/oxml/action.py +53 -0
  43. pptx/oxml/chart/__init__.py +0 -0
  44. pptx/oxml/chart/axis.py +297 -0
  45. pptx/oxml/chart/chart.py +282 -0
  46. pptx/oxml/chart/datalabel.py +252 -0
  47. pptx/oxml/chart/legend.py +72 -0
  48. pptx/oxml/chart/marker.py +61 -0
  49. pptx/oxml/chart/plot.py +345 -0
  50. pptx/oxml/chart/series.py +254 -0
  51. pptx/oxml/chart/shared.py +219 -0
  52. pptx/oxml/coreprops.py +288 -0
  53. pptx/oxml/dml/__init__.py +0 -0
  54. pptx/oxml/dml/color.py +111 -0
  55. pptx/oxml/dml/fill.py +197 -0
  56. pptx/oxml/dml/line.py +12 -0
  57. pptx/oxml/ns.py +129 -0
  58. pptx/oxml/presentation.py +130 -0
  59. pptx/oxml/shapes/__init__.py +19 -0
  60. pptx/oxml/shapes/autoshape.py +455 -0
  61. pptx/oxml/shapes/connector.py +107 -0
  62. pptx/oxml/shapes/graphfrm.py +342 -0
  63. pptx/oxml/shapes/groupshape.py +280 -0
  64. pptx/oxml/shapes/picture.py +270 -0
  65. pptx/oxml/shapes/shared.py +523 -0
  66. pptx/oxml/simpletypes.py +740 -0
  67. pptx/oxml/slide.py +347 -0
  68. pptx/oxml/table.py +588 -0
  69. pptx/oxml/text.py +748 -0
  70. pptx/oxml/theme.py +29 -0
  71. pptx/oxml/xmlchemy.py +717 -0
  72. pptx/package.py +222 -0
  73. pptx/parts/__init__.py +0 -0
  74. pptx/parts/chart.py +95 -0
  75. pptx/parts/coreprops.py +167 -0
  76. pptx/parts/embeddedpackage.py +93 -0
  77. pptx/parts/image.py +275 -0
  78. pptx/parts/media.py +37 -0
  79. pptx/parts/presentation.py +126 -0
  80. pptx/parts/slide.py +297 -0
  81. pptx/parts/theme.py +13 -0
  82. pptx/presentation.py +113 -0
  83. pptx/py.typed +0 -0
  84. pptx/shapes/__init__.py +26 -0
  85. pptx/shapes/autoshape.py +355 -0
  86. pptx/shapes/base.py +293 -0
  87. pptx/shapes/connector.py +297 -0
  88. pptx/shapes/freeform.py +337 -0
  89. pptx/shapes/graphfrm.py +166 -0
  90. pptx/shapes/group.py +69 -0
  91. pptx/shapes/picture.py +203 -0
  92. pptx/shapes/placeholder.py +407 -0
  93. pptx/shapes/shapetree.py +1190 -0
  94. pptx/shared.py +82 -0
  95. pptx/slide.py +577 -0
  96. pptx/spec.py +632 -0
  97. pptx/table.py +520 -0
  98. pptx/templates/default.pptx +0 -0
  99. pptx/templates/docx-icon.emf +0 -0
  100. pptx/templates/generic-icon.emf +0 -0
  101. pptx/templates/notes.xml +23 -0
  102. pptx/templates/notesMaster.xml +352 -0
  103. pptx/templates/pptx-icon.emf +0 -0
  104. pptx/templates/theme.xml +321 -0
  105. pptx/templates/xlsx-icon.emf +0 -0
  106. pptx/text/__init__.py +0 -0
  107. pptx/text/fonts.py +399 -0
  108. pptx/text/layout.py +325 -0
  109. pptx/text/text.py +770 -0
  110. pptx/theme.py +135 -0
  111. pptx/types.py +36 -0
  112. pptx/util.py +284 -0
  113. ps_python_pptx-1.1.0.dist-info/METADATA +75 -0
  114. ps_python_pptx-1.1.0.dist-info/RECORD +117 -0
  115. ps_python_pptx-1.1.0.dist-info/WHEEL +5 -0
  116. ps_python_pptx-1.1.0.dist-info/licenses/LICENSE +20 -0
  117. ps_python_pptx-1.1.0.dist-info/top_level.txt +1 -0
pptx/text/layout.py ADDED
@@ -0,0 +1,325 @@
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 pptx.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
23
+ ) -> int:
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`.
29
+ """
30
+ line_source = _LineSource(text)
31
+ text_fitter = cls(line_source, extents, font_file)
32
+ return text_fitter._best_fit_font_size(max_size)
33
+
34
+ def _best_fit_font_size(self, max_size):
35
+ """
36
+ Return the largest whole-number point size less than or equal to
37
+ *max_size* that this fitter can fit.
38
+ """
39
+ predicate = self._fits_inside_predicate
40
+ sizes = _BinarySearchTree.from_ordered_sequence(range(1, int(max_size) + 1))
41
+ return sizes.find_max(predicate)
42
+
43
+ def _break_line(self, line_source, point_size):
44
+ """
45
+ Return a (line, remainder) pair where *line* is the longest line in
46
+ *line_source* that will fit in this fitter's width and *remainder* is
47
+ a |_LineSource| object containing the text following the break point.
48
+ """
49
+ lines = _BinarySearchTree.from_ordered_sequence(line_source)
50
+ predicate = self._fits_in_width_predicate(point_size)
51
+ return lines.find_max(predicate)
52
+
53
+ def _fits_in_width_predicate(self, point_size):
54
+ """
55
+ Return a function taking a text string value and returns |True| if
56
+ that text fits in this fitter when rendered at *point_size*. Used as
57
+ predicate for _break_line()
58
+ """
59
+
60
+ def predicate(line):
61
+ """
62
+ Return |True| if *line* fits in this fitter when rendered at
63
+ *point_size*.
64
+ """
65
+ cx = _rendered_size(line.text, point_size, self._font_file)[0]
66
+ return cx <= self._width
67
+
68
+ return predicate
69
+
70
+ @property
71
+ def _fits_inside_predicate(self):
72
+ """Return function taking an integer point size argument.
73
+
74
+ The function returns |True| if the text in this fitter can be wrapped to fit
75
+ entirely within its extents when rendered at that point size.
76
+ """
77
+
78
+ def predicate(point_size):
79
+ """Return |True| when text in `line_source` can be wrapped to fit.
80
+
81
+ Fit means text can be broken into lines that fit entirely within `extents`
82
+ when rendered at `point_size` using the font defined in `font_file`.
83
+ """
84
+ text_lines = self._wrap_lines(self._line_source, point_size)
85
+ cy = _rendered_size("Ty", point_size, self._font_file)[1]
86
+ return (cy * len(text_lines)) <= self._height
87
+
88
+ return predicate
89
+
90
+ @property
91
+ def _font_file(self):
92
+ return self[3]
93
+
94
+ @property
95
+ def _height(self):
96
+ return self[2]
97
+
98
+ @property
99
+ def _line_source(self):
100
+ return self[0]
101
+
102
+ @property
103
+ def _width(self):
104
+ return self[1]
105
+
106
+ def _wrap_lines(self, line_source, point_size):
107
+ """
108
+ Return a sequence of str values representing the text in
109
+ *line_source* wrapped within this fitter when rendered at
110
+ *point_size*.
111
+ """
112
+ text, remainder = self._break_line(line_source, point_size)
113
+ lines = [text]
114
+ if remainder:
115
+ lines.extend(self._wrap_lines(remainder, point_size))
116
+ return lines
117
+
118
+
119
+ class _BinarySearchTree(object):
120
+ """
121
+ A node in a binary search tree. Uniform for root, subtree root, and leaf
122
+ nodes.
123
+ """
124
+
125
+ def __init__(self, value):
126
+ self._value = value
127
+ self._lesser = None
128
+ self._greater = None
129
+
130
+ def find_max(self, predicate, max_=None):
131
+ """
132
+ Return the largest item in or under this node that satisfies
133
+ *predicate*.
134
+ """
135
+ if predicate(self.value):
136
+ max_ = self.value
137
+ next_node = self._greater
138
+ else:
139
+ next_node = self._lesser
140
+ if next_node is None:
141
+ return max_
142
+ return next_node.find_max(predicate, max_)
143
+
144
+ @classmethod
145
+ def from_ordered_sequence(cls, iseq):
146
+ """
147
+ Return the root of a balanced binary search tree populated with the
148
+ values in iterable *iseq*.
149
+ """
150
+ seq = list(iseq)
151
+ # optimize for usually all fits by making longest first
152
+ bst = cls(seq.pop())
153
+ bst._insert_from_ordered_sequence(seq)
154
+ return bst
155
+
156
+ def insert(self, value):
157
+ """
158
+ Insert a new node containing *value* into this tree such that its
159
+ structure as a binary search tree is preserved.
160
+ """
161
+ side = "_lesser" if value < self.value else "_greater"
162
+ child = getattr(self, side)
163
+ if child is None:
164
+ setattr(self, side, _BinarySearchTree(value))
165
+ else:
166
+ child.insert(value)
167
+
168
+ def tree(self, level=0, prefix=""):
169
+ """
170
+ A string representation of the tree rooted in this node, useful for
171
+ debugging purposes.
172
+ """
173
+ text = "%s%s\n" % (prefix, self.value.text)
174
+ prefix = "%s└── " % (" " * level)
175
+ if self._lesser:
176
+ text += self._lesser.tree(level + 1, prefix)
177
+ if self._greater:
178
+ text += self._greater.tree(level + 1, prefix)
179
+ return text
180
+
181
+ @property
182
+ def value(self):
183
+ """
184
+ The value object contained in this node.
185
+ """
186
+ return self._value
187
+
188
+ @staticmethod
189
+ def _bisect(seq):
190
+ """
191
+ Return a (medial_value, greater_values, lesser_values) 3-tuple
192
+ obtained by bisecting sequence *seq*.
193
+ """
194
+ if len(seq) == 0:
195
+ return [], None, []
196
+ mid_idx = int(len(seq) / 2)
197
+ mid = seq[mid_idx]
198
+ greater = seq[mid_idx + 1 :]
199
+ lesser = seq[:mid_idx]
200
+ return mid, greater, lesser
201
+
202
+ def _insert_from_ordered_sequence(self, seq):
203
+ """
204
+ Insert the new values contained in *seq* into this tree such that
205
+ a balanced tree is produced.
206
+ """
207
+ if len(seq) == 0:
208
+ return
209
+ mid, greater, lesser = self._bisect(seq)
210
+ self.insert(mid)
211
+ self._insert_from_ordered_sequence(greater)
212
+ self._insert_from_ordered_sequence(lesser)
213
+
214
+
215
+ class _LineSource(object):
216
+ """
217
+ Generates all the possible even-word line breaks in a string of text,
218
+ each in the form of a (line, remainder) 2-tuple where *line* contains the
219
+ text before the break and *remainder* the text after as a |_LineSource|
220
+ object. Its boolean value is |True| when it contains text, |False| when
221
+ its text is the empty string or whitespace only.
222
+ """
223
+
224
+ def __init__(self, text):
225
+ self._text = text
226
+
227
+ def __bool__(self):
228
+ """
229
+ Gives this object boolean behaviors (in Python 3). bool(line_source)
230
+ is False if it contains the empty string or whitespace only.
231
+ """
232
+ return self._text.strip() != ""
233
+
234
+ def __eq__(self, other):
235
+ return self._text == other._text
236
+
237
+ def __iter__(self):
238
+ """
239
+ Generate a (text, remainder) pair for each possible even-word line
240
+ break in this line source, where *text* is a str value and remainder
241
+ is a |_LineSource| value.
242
+ """
243
+ words = self._text.split()
244
+ for idx in range(1, len(words) + 1):
245
+ line_text = " ".join(words[:idx])
246
+ remainder_text = " ".join(words[idx:])
247
+ remainder = _LineSource(remainder_text)
248
+ yield _Line(line_text, remainder)
249
+
250
+ def __nonzero__(self):
251
+ """
252
+ Gives this object boolean behaviors (in Python 2). bool(line_source)
253
+ is False if it contains the empty string or whitespace only.
254
+ """
255
+ return self._text.strip() != ""
256
+
257
+ def __repr__(self):
258
+ return "<_LineSource('%s')>" % self._text
259
+
260
+
261
+ class _Line(tuple):
262
+ """
263
+ A candidate line broken at an even word boundary from a string of text,
264
+ and a |_LineSource| value containing the text that remains after the line
265
+ is broken at this spot.
266
+ """
267
+
268
+ def __new__(cls, text, remainder):
269
+ return tuple.__new__(cls, (text, remainder))
270
+
271
+ def __gt__(self, other):
272
+ return len(self.text) > len(other.text)
273
+
274
+ def __lt__(self, other):
275
+ return not self.__gt__(other)
276
+
277
+ def __len__(self):
278
+ return len(self.text)
279
+
280
+ def __repr__(self):
281
+ return "'%s' => '%s'" % (self.text, self.remainder)
282
+
283
+ @property
284
+ def remainder(self):
285
+ return self[1]
286
+
287
+ @property
288
+ def text(self):
289
+ return self[0]
290
+
291
+
292
+ class _Fonts(object):
293
+ """
294
+ A memoizing cache for ImageFont objects.
295
+ """
296
+
297
+ fonts = {}
298
+
299
+ @classmethod
300
+ def font(cls, font_path, point_size):
301
+ if (font_path, point_size) not in cls.fonts:
302
+ cls.fonts[(font_path, point_size)] = ImageFont.truetype(font_path, point_size)
303
+ return cls.fonts[(font_path, point_size)]
304
+
305
+
306
+ def _rendered_size(text, point_size, font_file):
307
+ """
308
+ Return a (width, height) pair representing the size of *text* in English
309
+ Metric Units (EMU) when rendered at *point_size* in the font defined in
310
+ *font_file*.
311
+ """
312
+ emu_per_inch = 914400
313
+ px_per_inch = 72.0
314
+
315
+ font = _Fonts.font(font_file, point_size)
316
+ try:
317
+ px_width, px_height = font.getsize(text)
318
+ except AttributeError:
319
+ left, top, right, bottom = font.getbbox(text)
320
+ px_width, px_height = right - left, bottom - top
321
+
322
+ emu_width = int(px_width / px_per_inch * emu_per_inch)
323
+ emu_height = int(px_height / px_per_inch * emu_per_inch)
324
+
325
+ return emu_width, emu_height