forked from scanny/python-pptx
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathgroupshape.py
More file actions
379 lines (337 loc) · 13.7 KB
/
Copy pathgroupshape.py
File metadata and controls
379 lines (337 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# encoding: utf-8
"""
The group shape, the structure that holds a set of sub-shapes.
"""
from __future__ import (
absolute_import, division, print_function, unicode_literals
)
from .base import BaseShape
from .autoshape import AutoShapeType
from ..enum.shapes import PP_PLACEHOLDER
#from .factory import BaseShapeFactory
from ..oxml.shapes.graphfrm import CT_GraphicalObjectFrame
from ..oxml.simpletypes import ST_Direction
class BaseGroupShape(BaseShape):
"""
Base class for a shape collection.
"""
def __init__(self, grpSp, parent):
super(BaseGroupShape, self).__init__(grpSp, parent)
self._grpSp = grpSp
def __getitem__(self, idx):
"""
Return shape at *idx* in sequence, e.g. ``shapes[2]``.
"""
shape_elms = list(self._iter_member_elms())
try:
shape_elm = shape_elms[idx]
except IndexError:
raise IndexError('shape index out of range')
return self._shape_factory(shape_elm)
def __iter__(self):
"""
Generate a reference to each shape in the collection, in sequence.
"""
for shape_elm in self._iter_member_elms():
yield self._shape_factory(shape_elm)
def __len__(self):
"""
Return count of shapes in this shape tree. A group shape contributes
1 to the total, without regard to the number of shapes contained in
the group.
"""
shape_elms = list(self._iter_member_elms())
return len(shape_elms)
@staticmethod
def _is_member_elm(shape_elm):
"""
Return true if *shape_elm* represents a member of this collection,
False otherwise.
"""
return True
def _iter_member_elms(self):
"""
Generate each child of the ``<p:spTree>`` element that corresponds to
a shape, in the sequence they appear in the XML.
"""
grp = self._sp
for shape_elm in grp.iter_shape_elms():
if self._is_member_elm(shape_elm):
yield shape_elm
@property
def _next_shape_id(self):
"""
Next available positive integer drawing object id in shape tree,
starting from 1 and making use of any gaps in numbering. In practice,
the minimum id is 2 because the spTree element is always assigned
id="1".
"""
id_str_lst = self._sp.xpath('//@id')
used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()]
for n in range(1, len(used_ids)+2):
if n not in used_ids:
return n
def _shape_factory(self, shape_elm):
"""
Return an instance of the appropriate shape proxy class for
*shape_elm*.
"""
from .factory import BaseShapeFactory
return BaseShapeFactory(shape_elm, self)
@property
def _sp(self):
"""
Return the pointer to the current element. In a regular GroupShape,
this will be self._grpSp. In a SlideShapeTree, this will be
self._spTree
"""
return self._grpSp
class GroupShape(BaseGroupShape):
"""
Grouping of shapes within a SlideShapeTree. The first shape in the sequence
is the backmost in z-order and the last shape is topmost. Supports indexed
access, len(), index(), and iteration.
"""
def add_chart(self, chart_type, x, y, cx, cy, chart_data):
"""
Add a new chart of *chart_type* to the slide, positioned at (*x*,
*y*), having size (*cx*, *cy*), and depicting *chart_data*.
*chart_type* is one of the :ref:`XlChartType` enumeration values.
*chart_data* is a |ChartData| object populated with the categories
and series values for the chart. Note that a |GraphicFrame| shape
object is returned, not the |Chart| object contained in that graphic
frame shape. The chart object may be accessed using the :attr:`chart`
property of the returned |GraphicFrame| object.
"""
rId = self.part.add_chart_part(chart_type, chart_data)
graphic_frame = self._add_chart_graphic_frame(rId, x, y, cx, cy)
return graphic_frame
def add_picture(self, image_file, left, top, width=None, height=None):
"""
Add picture shape displaying image in *image_file*, where
*image_file* can be either a path to a file (a string) or a file-like
object.
"""
image_part, rId = self._sp.get_or_add_image_part(image_file)
pic = self._add_pic_from_image_part(
image_part, rId, left, top, width, height
)
picture = self._shape_factory(pic)
return picture
def add_shape(self, autoshape_type_id, left, top, width, height):
"""
Add auto shape of type specified by *autoshape_type_id* (like
``MSO_SHAPE.RECTANGLE``) and of specified size at specified position.
"""
autoshape_type = AutoShapeType(autoshape_type_id)
sp = self._add_sp_from_autoshape_type(
autoshape_type, left, top, width, height
)
shape = self._shape_factory(sp)
return shape
def add_table(self, rows, cols, left, top, width, height):
"""
Add a |GraphicFrame| object containing a table with the specified
number of *rows* and *cols* and the specified position and size.
*width* is evenly distributed between the columns of the new table.
Likewise, *height* is evenly distributed between the rows. Note that
the ``.table`` property on the returned |GraphicFrame| shape must be
used to access the enclosed |Table| object.
"""
graphicFrame = self._add_graphicFrame_containing_table(
rows, cols, left, top, width, height
)
graphic_frame = self._shape_factory(graphicFrame)
return graphic_frame
def add_textbox(self, left, top, width, height):
"""
Add text box shape of specified size at specified position on slide.
"""
sp = self._add_textbox_sp(left, top, width, height)
textbox = self._shape_factory(sp)
return textbox
def add_custom_geometry(self, left, top, width, height):
"""
Add a custom geometry shpane of specified size at specified position
on slide.
"""
sp = self._add_cust_geom_sp(left, top, width, height)
cust_geom = self._shape_factory(sp)
return cust_geom
def add_groupshape(self, left, top, width, height):
"""
Add groupshape of specified size at specified position on slide.
"""
sp = self._add_groupshape_sp(left, top, width, height)
groupshape = self._shape_factory(sp)
return groupshape
def clone_layout_placeholders(self, slide_layout):
"""
Add placeholder shapes based on those in *slide_layout*. Z-order of
placeholders is preserved. Latent placeholders (date, slide number,
and footer) are not cloned.
"""
for placeholder in slide_layout.iter_cloneable_placeholders():
self._clone_layout_placeholder(placeholder)
def index(self, shape):
"""
Return the index of *shape* in this sequence, raising |ValueError| if
*shape* is not in the collection.
"""
shape_elm = shape.element
for idx, elm in enumerate(self._grpSp.iter_shape_elms()):
if elm is shape_elm:
return idx
raise ValueError('shape not in collection')
@property
def placeholders(self):
"""
Instance of |_SlidePlaceholders| containing sequence of placeholder
shapes in this slide.
"""
return self._sp.placeholders
@property
def title(self):
"""
The title placeholder shape on the slide or |None| if the slide has
no title placeholder.
"""
for elm in self._grpSp.iter_shape_elms():
if elm.ph_idx == 0:
return self._shape_factory(elm)
return None
def _add_chart_graphicFrame(self, rId, x, y, cx, cy):
"""
Add a new ``<p:graphicFrame>`` element to this shape tree having the
specified position and size and referring to the chart part
identified by *rId*.
"""
shape_id = self._next_shape_id
name = 'Chart %d' % (shape_id-1)
graphicFrame = CT_GraphicalObjectFrame.new_chart_graphicFrame(
shape_id, name, rId, x, y, cx, cy
)
self._grpSp.append(graphicFrame)
return graphicFrame
def _add_chart_graphic_frame(self, rId, x, y, cx, cy):
"""
Return a |GraphicFrame| object having the specified position and size
and referring to the chart part identified by *rId*.
"""
graphicFrame = self._add_chart_graphicFrame(rId, x, y, cx, cy)
graphic_frame = self._shape_factory(graphicFrame)
return graphic_frame
def _add_graphicFrame_containing_table(self, rows, cols, x, y, cx, cy):
"""
Return a newly added ``<p:graphicFrame>`` element containing a table
as specified by the parameters.
"""
id_ = self._next_shape_id
name = 'Table %d' % (id_-1)
graphicFrame = self._grpSp.add_table(
id_, name, rows, cols, x, y, cx, cy
)
return graphicFrame
def _add_pic_from_image_part(self, image_part, rId, x, y, cx, cy):
"""
Return a newly added ``<p:pic>`` element specifying a picture shape
displaying *image_part* with size and position specified by *x*, *y*,
*cx*, and *cy*. The element is appended to the shape tree, causing it
to be displayed first in z-order on the slide.
"""
id = self._next_shape_id
name = 'Picture %d' % (id-1)
desc = image_part.desc
scaled_cx, scaled_cy = image_part.scale(cx, cy)
pic = self._grpSp.add_pic(
id, name, desc, rId, x, y, scaled_cx, scaled_cy
)
return pic
def _add_sp_from_autoshape_type(self, autoshape_type, x, y, cx, cy):
"""
Return a newly-added ``<p:sp>`` element for a shape of
*autoshape_type* at position (x, y) and of size (cx, cy).
"""
id_ = self._next_shape_id
name = '%s %d' % (autoshape_type.basename, id_-1)
sp = self._grpSp.add_autoshape(
id_, name, autoshape_type.prst, x, y, cx, cy
)
return sp
def _add_textbox_sp(self, x, y, cx, cy):
"""
Return a newly-added textbox ``<p:sp>`` element at position (x, y)
and of size (cx, cy).
"""
id_ = self._next_shape_id
name = 'TextBox %d' % (id_-1)
sp = self._grpSp.add_textbox(id_, name, x, y, cx, cy)
return sp
def _add_cust_geom_sp(self, x, y, cx, cy):
"""
Return a newly-added custom geometry ``<p:sp>`` element
"""
id_ = self._next_shape_id
name = 'CustGeom %s' % id_
sp = self._grpSp.add_custom_geometry(id_, name, x, y, cx, cy)
return sp
def _add_groupshape_sp(self, x, y, cx, cy):
"""
"""
id_ = self._next_shape_id
name = 'GroupShape %d' % (id_-1)
sp = self._grpSp.add_groupshape(id_, name, x, y, cx, cy)
return sp
def _clone_layout_placeholder(self, layout_placeholder):
"""
Add a new placeholder shape based on the slide layout placeholder
*layout_ph*.
"""
id_ = self._next_shape_id
ph_type = layout_placeholder.ph_type
orient = layout_placeholder.orient
name = self._next_ph_name(ph_type, id_, orient)
sz = layout_placeholder.sz
idx = layout_placeholder.idx
self._grpSp.add_placeholder(id_, name, ph_type, orient, sz, idx)
def _next_ph_name(self, ph_type, id, orient):
"""
Next unique placeholder name for placeholder shape of type *ph_type*,
with id number *id* and orientation *orient*. Usually will be standard
placeholder root name suffixed with id-1, e.g.
_next_ph_name(ST_PlaceholderType.TBL, 4, 'horz') ==>
'Table Placeholder 3'. The number is incremented as necessary to make
the name unique within the collection. If *orient* is ``'vert'``, the
placeholder name is prefixed with ``'Vertical '``.
"""
basename = {
# BODY is named 'Notes Placeholder' in a notes master
PP_PLACEHOLDER.BODY: 'Text Placeholder',
PP_PLACEHOLDER.CHART: 'Chart Placeholder',
PP_PLACEHOLDER.BITMAP: 'ClipArt Placeholder',
PP_PLACEHOLDER.CENTER_TITLE: 'Title',
PP_PLACEHOLDER.ORG_CHART: 'SmartArt Placeholder',
PP_PLACEHOLDER.DATE: 'Date Placeholder',
PP_PLACEHOLDER.FOOTER: 'Footer Placeholder',
PP_PLACEHOLDER.HEADER: 'Header Placeholder',
PP_PLACEHOLDER.MEDIA_CLIP: 'Media Placeholder',
PP_PLACEHOLDER.OBJECT: 'Content Placeholder',
PP_PLACEHOLDER.PICTURE: 'Picture Placeholder',
# PP_PLACEHOLDER.SLIDE_IMAGE: 'Slide Image Placeholder',
PP_PLACEHOLDER.SLIDE_NUMBER: 'Slide Number Placeholder',
PP_PLACEHOLDER.SUBTITLE: 'Subtitle',
PP_PLACEHOLDER.TABLE: 'Table Placeholder',
PP_PLACEHOLDER.TITLE: 'Title',
}[ph_type]
# prefix rootname with 'Vertical ' if orient is 'vert'
if orient == ST_Direction.VERT:
basename = 'Vertical %s' % basename
# increment numpart as necessary to make name unique
numpart = id - 1
names = self._grpSp.xpath('//p:cNvPr/@name')
while True:
name = '%s %d' % (basename, numpart)
if name not in names:
break
numpart += 1
return name