1###############################################################################
2#
3# Drawing - A class for writing the Excel XLSX Drawing file.
4#
5# SPDX-License-Identifier: BSD-2-Clause
6# Copyright 2013-2024, John McNamara, jmcnamara@cpan.org
7#
8
9from . import xmlwriter
10from .shape import Shape
11from .utility import get_rgb_color
12
13
14class Drawing(xmlwriter.XMLwriter):
15 """
16 A class for writing the Excel XLSX Drawing file.
17
18
19 """
20
21 ###########################################################################
22 #
23 # Public API.
24 #
25 ###########################################################################
26
27 def __init__(self):
28 """
29 Constructor.
30
31 """
32
33 super(Drawing, self).__init__()
34
35 self.drawings = []
36 self.embedded = 0
37 self.orientation = 0
38
39 ###########################################################################
40 #
41 # Private API.
42 #
43 ###########################################################################
44
45 def _assemble_xml_file(self):
46 # Assemble and write the XML file.
47
48 # Write the XML declaration.
49 self._xml_declaration()
50
51 # Write the xdr:wsDr element.
52 self._write_drawing_workspace()
53
54 if self.embedded:
55 index = 0
56 for drawing_properties in self.drawings:
57 # Write the xdr:twoCellAnchor element.
58 index += 1
59 self._write_two_cell_anchor(index, drawing_properties)
60
61 else:
62 # Write the xdr:absoluteAnchor element.
63 self._write_absolute_anchor(1)
64
65 self._xml_end_tag("xdr:wsDr")
66
67 # Close the file.
68 self._xml_close()
69
70 def _add_drawing_object(self):
71 # Add a chart, image or shape sub object to the drawing.
72
73 drawing_object = {
74 "anchor_type": None,
75 "dimensions": [],
76 "width": 0,
77 "height": 0,
78 "shape": None,
79 "anchor": None,
80 "rel_index": 0,
81 "url_rel_index": 0,
82 "tip": None,
83 "name": None,
84 "description": None,
85 "decorative": False,
86 }
87
88 self.drawings.append(drawing_object)
89
90 return drawing_object
91
92 ###########################################################################
93 #
94 # XML methods.
95 #
96 ###########################################################################
97
98 def _write_drawing_workspace(self):
99 # Write the <xdr:wsDr> element.
100 schema = "http://schemas.openxmlformats.org/drawingml/"
101 xmlns_xdr = schema + "2006/spreadsheetDrawing"
102 xmlns_a = schema + "2006/main"
103
104 attributes = [
105 ("xmlns:xdr", xmlns_xdr),
106 ("xmlns:a", xmlns_a),
107 ]
108
109 self._xml_start_tag("xdr:wsDr", attributes)
110
111 def _write_two_cell_anchor(self, index, drawing_properties):
112 # Write the <xdr:twoCellAnchor> element.
113 anchor_type = drawing_properties["type"]
114 dimensions = drawing_properties["dimensions"]
115 col_from = dimensions[0]
116 row_from = dimensions[1]
117 col_from_offset = dimensions[2]
118 row_from_offset = dimensions[3]
119 col_to = dimensions[4]
120 row_to = dimensions[5]
121 col_to_offset = dimensions[6]
122 row_to_offset = dimensions[7]
123 col_absolute = dimensions[8]
124 row_absolute = dimensions[9]
125 width = drawing_properties["width"]
126 height = drawing_properties["height"]
127 shape = drawing_properties["shape"]
128 anchor = drawing_properties["anchor"]
129 rel_index = drawing_properties["rel_index"]
130 url_rel_index = drawing_properties["url_rel_index"]
131 tip = drawing_properties["tip"]
132 name = drawing_properties["name"]
133 description = drawing_properties["description"]
134 decorative = drawing_properties["decorative"]
135
136 attributes = []
137
138 # Add attribute for positioning.
139 if anchor == 2:
140 attributes.append(("editAs", "oneCell"))
141 elif anchor == 3:
142 attributes.append(("editAs", "absolute"))
143
144 # Add editAs attribute for shapes.
145 if shape and shape.edit_as:
146 attributes.append(("editAs", shape.edit_as))
147
148 self._xml_start_tag("xdr:twoCellAnchor", attributes)
149
150 # Write the xdr:from element.
151 self._write_from(col_from, row_from, col_from_offset, row_from_offset)
152
153 # Write the xdr:from element.
154 self._write_to(col_to, row_to, col_to_offset, row_to_offset)
155
156 if anchor_type == 1:
157 # Graphic frame.
158 # Write the xdr:graphicFrame element for charts.
159 self._write_graphic_frame(index, rel_index, name, description, decorative)
160 elif anchor_type == 2:
161 # Write the xdr:pic element.
162 self._write_pic(
163 index,
164 rel_index,
165 col_absolute,
166 row_absolute,
167 width,
168 height,
169 shape,
170 description,
171 url_rel_index,
172 tip,
173 decorative,
174 )
175 else:
176 # Write the xdr:sp element for shapes.
177 self._write_sp(
178 index,
179 col_absolute,
180 row_absolute,
181 width,
182 height,
183 shape,
184 description,
185 url_rel_index,
186 tip,
187 decorative,
188 )
189
190 # Write the xdr:clientData element.
191 self._write_client_data()
192
193 self._xml_end_tag("xdr:twoCellAnchor")
194
195 def _write_absolute_anchor(self, frame_index):
196 self._xml_start_tag("xdr:absoluteAnchor")
197 # Write the <xdr:absoluteAnchor> element.
198
199 # Different coordinates for horizontal (= 0) and vertical (= 1).
200 if self.orientation == 0:
201 # Write the xdr:pos element.
202 self._write_pos(0, 0)
203
204 # Write the xdr:ext element.
205 self._write_xdr_ext(9308969, 6078325)
206
207 else:
208 # Write the xdr:pos element.
209 self._write_pos(0, -47625)
210
211 # Write the xdr:ext element.
212 self._write_xdr_ext(6162675, 6124575)
213
214 # Write the xdr:graphicFrame element.
215 self._write_graphic_frame(frame_index, frame_index)
216
217 # Write the xdr:clientData element.
218 self._write_client_data()
219
220 self._xml_end_tag("xdr:absoluteAnchor")
221
222 def _write_from(self, col, row, col_offset, row_offset):
223 # Write the <xdr:from> element.
224 self._xml_start_tag("xdr:from")
225
226 # Write the xdr:col element.
227 self._write_col(col)
228
229 # Write the xdr:colOff element.
230 self._write_col_off(col_offset)
231
232 # Write the xdr:row element.
233 self._write_row(row)
234
235 # Write the xdr:rowOff element.
236 self._write_row_off(row_offset)
237
238 self._xml_end_tag("xdr:from")
239
240 def _write_to(self, col, row, col_offset, row_offset):
241 # Write the <xdr:to> element.
242 self._xml_start_tag("xdr:to")
243
244 # Write the xdr:col element.
245 self._write_col(col)
246
247 # Write the xdr:colOff element.
248 self._write_col_off(col_offset)
249
250 # Write the xdr:row element.
251 self._write_row(row)
252
253 # Write the xdr:rowOff element.
254 self._write_row_off(row_offset)
255
256 self._xml_end_tag("xdr:to")
257
258 def _write_col(self, data):
259 # Write the <xdr:col> element.
260 self._xml_data_element("xdr:col", data)
261
262 def _write_col_off(self, data):
263 # Write the <xdr:colOff> element.
264 self._xml_data_element("xdr:colOff", data)
265
266 def _write_row(self, data):
267 # Write the <xdr:row> element.
268 self._xml_data_element("xdr:row", data)
269
270 def _write_row_off(self, data):
271 # Write the <xdr:rowOff> element.
272 self._xml_data_element("xdr:rowOff", data)
273
274 def _write_pos(self, x, y):
275 # Write the <xdr:pos> element.
276
277 attributes = [("x", x), ("y", y)]
278
279 self._xml_empty_tag("xdr:pos", attributes)
280
281 def _write_xdr_ext(self, cx, cy):
282 # Write the <xdr:ext> element.
283
284 attributes = [("cx", cx), ("cy", cy)]
285
286 self._xml_empty_tag("xdr:ext", attributes)
287
288 def _write_graphic_frame(
289 self, index, rel_index, name=None, description=None, decorative=None
290 ):
291 # Write the <xdr:graphicFrame> element.
292 attributes = [("macro", "")]
293
294 self._xml_start_tag("xdr:graphicFrame", attributes)
295
296 # Write the xdr:nvGraphicFramePr element.
297 self._write_nv_graphic_frame_pr(index, name, description, decorative)
298
299 # Write the xdr:xfrm element.
300 self._write_xfrm()
301
302 # Write the a:graphic element.
303 self._write_atag_graphic(rel_index)
304
305 self._xml_end_tag("xdr:graphicFrame")
306
307 def _write_nv_graphic_frame_pr(self, index, name, description, decorative):
308 # Write the <xdr:nvGraphicFramePr> element.
309
310 if not name:
311 name = "Chart " + str(index)
312
313 self._xml_start_tag("xdr:nvGraphicFramePr")
314
315 # Write the xdr:cNvPr element.
316 self._write_c_nv_pr(index + 1, name, description, None, None, decorative)
317
318 # Write the xdr:cNvGraphicFramePr element.
319 self._write_c_nv_graphic_frame_pr()
320
321 self._xml_end_tag("xdr:nvGraphicFramePr")
322
323 def _write_c_nv_pr(self, index, name, description, url_rel_index, tip, decorative):
324 # Write the <xdr:cNvPr> element.
325 attributes = [("id", index), ("name", name)]
326
327 # Add description attribute for images.
328 if description and not decorative:
329 attributes.append(("descr", description))
330
331 if url_rel_index or decorative:
332 self._xml_start_tag("xdr:cNvPr", attributes)
333
334 if url_rel_index:
335 self._write_a_hlink_click(url_rel_index, tip)
336
337 if decorative:
338 self._write_decorative()
339
340 self._xml_end_tag("xdr:cNvPr")
341 else:
342 self._xml_empty_tag("xdr:cNvPr", attributes)
343
344 def _write_decorative(self):
345 self._xml_start_tag("a:extLst")
346
347 self._write_uri_ext("{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}")
348 self._write_a16_creation_id()
349 self._xml_end_tag("a:ext")
350
351 self._write_uri_ext("{C183D7F6-B498-43B3-948B-1728B52AA6E4}")
352 self._write_adec_decorative()
353 self._xml_end_tag("a:ext")
354
355 self._xml_end_tag("a:extLst")
356
357 def _write_uri_ext(self, uri):
358 # Write the <a:ext> element.
359 attributes = [("uri", uri)]
360
361 self._xml_start_tag("a:ext", attributes)
362
363 def _write_adec_decorative(self):
364 # Write the <adec:decorative> element.
365 xmlns = "http://schemas.microsoft.com/office/drawing/2017/decorative"
366 val = "1"
367
368 attributes = [
369 ("xmlns:adec", xmlns),
370 ("val", val),
371 ]
372
373 self._xml_empty_tag("adec:decorative", attributes)
374
375 def _write_a16_creation_id(self):
376 # Write the <a16:creationId> element.
377
378 xmlns_a_16 = "http://schemas.microsoft.com/office/drawing/2014/main"
379 creation_id = "{00000000-0008-0000-0000-000002000000}"
380
381 attributes = [
382 ("xmlns:a16", xmlns_a_16),
383 ("id", creation_id),
384 ]
385
386 self._xml_empty_tag("a16:creationId", attributes)
387
388 def _write_a_hlink_click(self, rel_index, tip):
389 # Write the <a:hlinkClick> element.
390 schema = "http://schemas.openxmlformats.org/officeDocument/"
391 xmlns_r = schema + "2006/relationships"
392
393 attributes = [
394 ("xmlns:r", xmlns_r),
395 ("r:id", "rId" + str(rel_index)),
396 ]
397
398 if tip:
399 attributes.append(("tooltip", tip))
400
401 self._xml_empty_tag("a:hlinkClick", attributes)
402
403 def _write_c_nv_graphic_frame_pr(self):
404 # Write the <xdr:cNvGraphicFramePr> element.
405 if self.embedded:
406 self._xml_empty_tag("xdr:cNvGraphicFramePr")
407 else:
408 self._xml_start_tag("xdr:cNvGraphicFramePr")
409
410 # Write the a:graphicFrameLocks element.
411 self._write_a_graphic_frame_locks()
412
413 self._xml_end_tag("xdr:cNvGraphicFramePr")
414
415 def _write_a_graphic_frame_locks(self):
416 # Write the <a:graphicFrameLocks> element.
417 attributes = [("noGrp", 1)]
418
419 self._xml_empty_tag("a:graphicFrameLocks", attributes)
420
421 def _write_xfrm(self):
422 # Write the <xdr:xfrm> element.
423 self._xml_start_tag("xdr:xfrm")
424
425 # Write the xfrmOffset element.
426 self._write_xfrm_offset()
427
428 # Write the xfrmOffset element.
429 self._write_xfrm_extension()
430
431 self._xml_end_tag("xdr:xfrm")
432
433 def _write_xfrm_offset(self):
434 # Write the <a:off> xfrm sub-element.
435
436 attributes = [
437 ("x", 0),
438 ("y", 0),
439 ]
440
441 self._xml_empty_tag("a:off", attributes)
442
443 def _write_xfrm_extension(self):
444 # Write the <a:ext> xfrm sub-element.
445
446 attributes = [
447 ("cx", 0),
448 ("cy", 0),
449 ]
450
451 self._xml_empty_tag("a:ext", attributes)
452
453 def _write_atag_graphic(self, index):
454 # Write the <a:graphic> element.
455 self._xml_start_tag("a:graphic")
456
457 # Write the a:graphicData element.
458 self._write_atag_graphic_data(index)
459
460 self._xml_end_tag("a:graphic")
461
462 def _write_atag_graphic_data(self, index):
463 # Write the <a:graphicData> element.
464 uri = "http://schemas.openxmlformats.org/drawingml/2006/chart"
465
466 attributes = [
467 (
468 "uri",
469 uri,
470 )
471 ]
472
473 self._xml_start_tag("a:graphicData", attributes)
474
475 # Write the c:chart element.
476 self._write_c_chart("rId" + str(index))
477
478 self._xml_end_tag("a:graphicData")
479
480 def _write_c_chart(self, r_id):
481 # Write the <c:chart> element.
482
483 schema = "http://schemas.openxmlformats.org/"
484 xmlns_c = schema + "drawingml/2006/chart"
485 xmlns_r = schema + "officeDocument/2006/relationships"
486
487 attributes = [
488 ("xmlns:c", xmlns_c),
489 ("xmlns:r", xmlns_r),
490 ("r:id", r_id),
491 ]
492
493 self._xml_empty_tag("c:chart", attributes)
494
495 def _write_client_data(self):
496 # Write the <xdr:clientData> element.
497 self._xml_empty_tag("xdr:clientData")
498
499 def _write_sp(
500 self,
501 index,
502 col_absolute,
503 row_absolute,
504 width,
505 height,
506 shape,
507 description,
508 url_rel_index,
509 tip,
510 decorative,
511 ):
512 # Write the <xdr:sp> element.
513
514 if shape and shape.connect:
515 attributes = [("macro", "")]
516 self._xml_start_tag("xdr:cxnSp", attributes)
517
518 # Write the xdr:nvCxnSpPr element.
519 self._write_nv_cxn_sp_pr(index, shape)
520
521 # Write the xdr:spPr element.
522 self._write_xdr_sp_pr(
523 index, col_absolute, row_absolute, width, height, shape
524 )
525
526 self._xml_end_tag("xdr:cxnSp")
527 else:
528 # Add attribute for shapes.
529 attributes = [("macro", ""), ("textlink", shape.textlink)]
530
531 self._xml_start_tag("xdr:sp", attributes)
532
533 # Write the xdr:nvSpPr element.
534 self._write_nv_sp_pr(
535 index, shape, url_rel_index, tip, description, decorative
536 )
537
538 # Write the xdr:spPr element.
539 self._write_xdr_sp_pr(
540 index, col_absolute, row_absolute, width, height, shape
541 )
542
543 # Write the xdr:style element.
544 self._write_style()
545
546 # Write the xdr:txBody element.
547 if shape.text is not None:
548 self._write_tx_body(col_absolute, row_absolute, width, height, shape)
549
550 self._xml_end_tag("xdr:sp")
551
552 def _write_nv_cxn_sp_pr(self, index, shape):
553 # Write the <xdr:nvCxnSpPr> element.
554 self._xml_start_tag("xdr:nvCxnSpPr")
555
556 name = shape.name + " " + str(index)
557 if name is not None:
558 self._write_c_nv_pr(index, name, None, None, None, None)
559
560 self._xml_start_tag("xdr:cNvCxnSpPr")
561
562 attributes = [("noChangeShapeType", "1")]
563 self._xml_empty_tag("a:cxnSpLocks", attributes)
564
565 if shape.start:
566 attributes = [("id", shape.start), ("idx", shape.start_index)]
567 self._xml_empty_tag("a:stCxn", attributes)
568
569 if shape.end:
570 attributes = [("id", shape.end), ("idx", shape.end_index)]
571 self._xml_empty_tag("a:endCxn", attributes)
572
573 self._xml_end_tag("xdr:cNvCxnSpPr")
574 self._xml_end_tag("xdr:nvCxnSpPr")
575
576 def _write_nv_sp_pr(
577 self, index, shape, url_rel_index, tip, description, decorative
578 ):
579 # Write the <xdr:NvSpPr> element.
580 attributes = []
581
582 self._xml_start_tag("xdr:nvSpPr")
583
584 name = shape.name + " " + str(index)
585
586 self._write_c_nv_pr(
587 index + 1, name, description, url_rel_index, tip, decorative
588 )
589
590 if shape.name == "TextBox":
591 attributes = [("txBox", 1)]
592
593 self._xml_empty_tag("xdr:cNvSpPr", attributes)
594
595 self._xml_end_tag("xdr:nvSpPr")
596
597 def _write_pic(
598 self,
599 index,
600 rel_index,
601 col_absolute,
602 row_absolute,
603 width,
604 height,
605 shape,
606 description,
607 url_rel_index,
608 tip,
609 decorative,
610 ):
611 # Write the <xdr:pic> element.
612 self._xml_start_tag("xdr:pic")
613
614 # Write the xdr:nvPicPr element.
615 self._write_nv_pic_pr(
616 index, rel_index, description, url_rel_index, tip, decorative
617 )
618 # Write the xdr:blipFill element.
619 self._write_blip_fill(rel_index)
620
621 # Write the xdr:spPr element.
622 self._write_sp_pr(col_absolute, row_absolute, width, height, shape)
623
624 self._xml_end_tag("xdr:pic")
625
626 def _write_nv_pic_pr(
627 self, index, rel_index, description, url_rel_index, tip, decorative
628 ):
629 # Write the <xdr:nvPicPr> element.
630 self._xml_start_tag("xdr:nvPicPr")
631
632 # Write the xdr:cNvPr element.
633 self._write_c_nv_pr(
634 index + 1,
635 "Picture " + str(index),
636 description,
637 url_rel_index,
638 tip,
639 decorative,
640 )
641
642 # Write the xdr:cNvPicPr element.
643 self._write_c_nv_pic_pr()
644
645 self._xml_end_tag("xdr:nvPicPr")
646
647 def _write_c_nv_pic_pr(self):
648 # Write the <xdr:cNvPicPr> element.
649 self._xml_start_tag("xdr:cNvPicPr")
650
651 # Write the a:picLocks element.
652 self._write_a_pic_locks()
653
654 self._xml_end_tag("xdr:cNvPicPr")
655
656 def _write_a_pic_locks(self):
657 # Write the <a:picLocks> element.
658 attributes = [("noChangeAspect", 1)]
659
660 self._xml_empty_tag("a:picLocks", attributes)
661
662 def _write_blip_fill(self, index):
663 # Write the <xdr:blipFill> element.
664 self._xml_start_tag("xdr:blipFill")
665
666 # Write the a:blip element.
667 self._write_a_blip(index)
668
669 # Write the a:stretch element.
670 self._write_a_stretch()
671
672 self._xml_end_tag("xdr:blipFill")
673
674 def _write_a_blip(self, index):
675 # Write the <a:blip> element.
676 schema = "http://schemas.openxmlformats.org/officeDocument/"
677 xmlns_r = schema + "2006/relationships"
678 r_embed = "rId" + str(index)
679
680 attributes = [("xmlns:r", xmlns_r), ("r:embed", r_embed)]
681
682 self._xml_empty_tag("a:blip", attributes)
683
684 def _write_a_stretch(self):
685 # Write the <a:stretch> element.
686 self._xml_start_tag("a:stretch")
687
688 # Write the a:fillRect element.
689 self._write_a_fill_rect()
690
691 self._xml_end_tag("a:stretch")
692
693 def _write_a_fill_rect(self):
694 # Write the <a:fillRect> element.
695 self._xml_empty_tag("a:fillRect")
696
697 def _write_sp_pr(self, col_absolute, row_absolute, width, height, shape=None):
698 # Write the <xdr:spPr> element, for charts.
699
700 self._xml_start_tag("xdr:spPr")
701
702 # Write the a:xfrm element.
703 self._write_a_xfrm(col_absolute, row_absolute, width, height)
704
705 # Write the a:prstGeom element.
706 self._write_a_prst_geom(shape)
707
708 self._xml_end_tag("xdr:spPr")
709
710 def _write_xdr_sp_pr(self, index, col_absolute, row_absolute, width, height, shape):
711 # Write the <xdr:spPr> element for shapes.
712 self._xml_start_tag("xdr:spPr")
713
714 # Write the a:xfrm element.
715 self._write_a_xfrm(col_absolute, row_absolute, width, height, shape)
716
717 # Write the a:prstGeom element.
718 self._write_a_prst_geom(shape)
719
720 if shape.fill:
721 if not shape.fill["defined"]:
722 # Write the a:solidFill element.
723 self._write_a_solid_fill_scheme("lt1")
724 elif "none" in shape.fill:
725 # Write the a:noFill element.
726 self._xml_empty_tag("a:noFill")
727 elif "color" in shape.fill:
728 # Write the a:solidFill element.
729 self._write_a_solid_fill(get_rgb_color(shape.fill["color"]))
730
731 if shape.gradient:
732 # Write the a:gradFill element.
733 self._write_a_grad_fill(shape.gradient)
734
735 # Write the a:ln element.
736 self._write_a_ln(shape.line)
737
738 self._xml_end_tag("xdr:spPr")
739
740 def _write_a_xfrm(self, col_absolute, row_absolute, width, height, shape=None):
741 # Write the <a:xfrm> element.
742 attributes = []
743
744 if shape:
745 if shape.rotation:
746 rotation = shape.rotation
747 rotation *= 60000
748 attributes.append(("rot", rotation))
749
750 if shape.flip_h:
751 attributes.append(("flipH", 1))
752 if shape.flip_v:
753 attributes.append(("flipV", 1))
754
755 self._xml_start_tag("a:xfrm", attributes)
756
757 # Write the a:off element.
758 self._write_a_off(col_absolute, row_absolute)
759
760 # Write the a:ext element.
761 self._write_a_ext(width, height)
762
763 self._xml_end_tag("a:xfrm")
764
765 def _write_a_off(self, x, y):
766 # Write the <a:off> element.
767 attributes = [
768 ("x", x),
769 ("y", y),
770 ]
771
772 self._xml_empty_tag("a:off", attributes)
773
774 def _write_a_ext(self, cx, cy):
775 # Write the <a:ext> element.
776 attributes = [
777 ("cx", cx),
778 ("cy", cy),
779 ]
780
781 self._xml_empty_tag("a:ext", attributes)
782
783 def _write_a_prst_geom(self, shape=None):
784 # Write the <a:prstGeom> element.
785 attributes = [("prst", "rect")]
786
787 self._xml_start_tag("a:prstGeom", attributes)
788
789 # Write the a:avLst element.
790 self._write_a_av_lst(shape)
791
792 self._xml_end_tag("a:prstGeom")
793
794 def _write_a_av_lst(self, shape=None):
795 # Write the <a:avLst> element.
796 adjustments = []
797
798 if shape and shape.adjustments:
799 adjustments = shape.adjustments
800
801 if adjustments:
802 self._xml_start_tag("a:avLst")
803
804 i = 0
805 for adj in adjustments:
806 i += 1
807 # Only connectors have multiple adjustments.
808 if shape.connect:
809 suffix = i
810 else:
811 suffix = ""
812
813 # Scale Adjustments: 100,000 = 100%.
814 adj_int = str(int(adj * 1000))
815
816 attributes = [("name", "adj" + suffix), ("fmla", "val" + adj_int)]
817
818 self._xml_empty_tag("a:gd", attributes)
819
820 self._xml_end_tag("a:avLst")
821 else:
822 self._xml_empty_tag("a:avLst")
823
824 def _write_a_solid_fill(self, rgb):
825 # Write the <a:solidFill> element.
826 if rgb is None:
827 rgb = "FFFFFF"
828
829 self._xml_start_tag("a:solidFill")
830
831 # Write the a:srgbClr element.
832 self._write_a_srgb_clr(rgb)
833
834 self._xml_end_tag("a:solidFill")
835
836 def _write_a_solid_fill_scheme(self, color, shade=None):
837 attributes = [("val", color)]
838
839 self._xml_start_tag("a:solidFill")
840
841 if shade:
842 self._xml_start_tag("a:schemeClr", attributes)
843 self._write_a_shade(shade)
844 self._xml_end_tag("a:schemeClr")
845 else:
846 self._xml_empty_tag("a:schemeClr", attributes)
847
848 self._xml_end_tag("a:solidFill")
849
850 def _write_a_ln(self, line):
851 # Write the <a:ln> element.
852 width = line.get("width", 0.75)
853
854 # Round width to nearest 0.25, like Excel.
855 width = int((width + 0.125) * 4) / 4.0
856
857 # Convert to internal units.
858 width = int(0.5 + (12700 * width))
859
860 attributes = [("w", width), ("cmpd", "sng")]
861
862 self._xml_start_tag("a:ln", attributes)
863
864 if "none" in line:
865 # Write the a:noFill element.
866 self._xml_empty_tag("a:noFill")
867
868 elif "color" in line:
869 # Write the a:solidFill element.
870 self._write_a_solid_fill(get_rgb_color(line["color"]))
871
872 else:
873 # Write the a:solidFill element.
874 self._write_a_solid_fill_scheme("lt1", "50000")
875
876 # Write the line/dash type.
877 line_type = line.get("dash_type")
878 if line_type:
879 # Write the a:prstDash element.
880 self._write_a_prst_dash(line_type)
881
882 self._xml_end_tag("a:ln")
883
884 def _write_tx_body(self, col_absolute, row_absolute, width, height, shape):
885 # Write the <xdr:txBody> element.
886 attributes = []
887
888 if shape.text_rotation != 0:
889 if shape.text_rotation == 90:
890 attributes.append(("vert", "vert270"))
891 if shape.text_rotation == -90:
892 attributes.append(("vert", "vert"))
893 if shape.text_rotation == 270:
894 attributes.append(("vert", "wordArtVert"))
895 if shape.text_rotation == 271:
896 attributes.append(("vert", "eaVert"))
897
898 attributes.append(("wrap", "square"))
899 attributes.append(("rtlCol", "0"))
900
901 if not shape.align["defined"]:
902 attributes.append(("anchor", "t"))
903 else:
904 if "vertical" in shape.align:
905 align = shape.align["vertical"]
906 if align == "top":
907 attributes.append(("anchor", "t"))
908 elif align == "middle":
909 attributes.append(("anchor", "ctr"))
910 elif align == "bottom":
911 attributes.append(("anchor", "b"))
912 else:
913 attributes.append(("anchor", "t"))
914
915 if "horizontal" in shape.align:
916 align = shape.align["horizontal"]
917 if align == "center":
918 attributes.append(("anchorCtr", "1"))
919 else:
920 attributes.append(("anchorCtr", "0"))
921
922 self._xml_start_tag("xdr:txBody")
923 self._xml_empty_tag("a:bodyPr", attributes)
924 self._xml_empty_tag("a:lstStyle")
925
926 lines = shape.text.split("\n")
927
928 # Set the font attributes.
929 font = shape.font
930 style_attrs = Shape._get_font_style_attributes(font)
931 latin_attrs = Shape._get_font_latin_attributes(font)
932 style_attrs.insert(0, ("lang", font["lang"]))
933
934 if shape.textlink != "":
935 attributes = [
936 ("id", "{B8ADDEFE-BF52-4FD4-8C5D-6B85EF6FF707}"),
937 ("type", "TxLink"),
938 ]
939
940 self._xml_start_tag("a:p")
941 self._xml_start_tag("a:fld", attributes)
942
943 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
944
945 self._xml_data_element("a:t", shape.text)
946 self._xml_end_tag("a:fld")
947
948 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
949
950 self._xml_end_tag("a:p")
951 else:
952 for line in lines:
953 self._xml_start_tag("a:p")
954
955 if line == "":
956 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
957 self._xml_end_tag("a:p")
958 continue
959 elif "text" in shape.align:
960 if shape.align["text"] == "left":
961 self._xml_empty_tag("a:pPr", [("algn", "l")])
962 if shape.align["text"] == "center":
963 self._xml_empty_tag("a:pPr", [("algn", "ctr")])
964 if shape.align["text"] == "right":
965 self._xml_empty_tag("a:pPr", [("algn", "r")])
966
967 self._xml_start_tag("a:r")
968
969 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
970
971 self._xml_data_element("a:t", line)
972
973 self._xml_end_tag("a:r")
974 self._xml_end_tag("a:p")
975
976 self._xml_end_tag("xdr:txBody")
977
978 def _write_font_run(self, font, style_attrs, latin_attrs, run_type):
979 # Write a:rPr or a:endParaRPr.
980 if font.get("color") is not None:
981 has_color = True
982 else:
983 has_color = False
984
985 if latin_attrs or has_color:
986 self._xml_start_tag(run_type, style_attrs)
987
988 if has_color:
989 self._write_a_solid_fill(get_rgb_color(font["color"]))
990
991 if latin_attrs:
992 self._write_a_latin(latin_attrs)
993 self._write_a_cs(latin_attrs)
994
995 self._xml_end_tag(run_type)
996 else:
997 self._xml_empty_tag(run_type, style_attrs)
998
999 def _write_style(self):
1000 # Write the <xdr:style> element.
1001 self._xml_start_tag("xdr:style")
1002
1003 # Write the a:lnRef element.
1004 self._write_a_ln_ref()
1005
1006 # Write the a:fillRef element.
1007 self._write_a_fill_ref()
1008
1009 # Write the a:effectRef element.
1010 self._write_a_effect_ref()
1011
1012 # Write the a:fontRef element.
1013 self._write_a_font_ref()
1014
1015 self._xml_end_tag("xdr:style")
1016
1017 def _write_a_ln_ref(self):
1018 # Write the <a:lnRef> element.
1019 attributes = [("idx", "0")]
1020
1021 self._xml_start_tag("a:lnRef", attributes)
1022
1023 # Write the a:scrgbClr element.
1024 self._write_a_scrgb_clr()
1025
1026 self._xml_end_tag("a:lnRef")
1027
1028 def _write_a_fill_ref(self):
1029 # Write the <a:fillRef> element.
1030 attributes = [("idx", "0")]
1031
1032 self._xml_start_tag("a:fillRef", attributes)
1033
1034 # Write the a:scrgbClr element.
1035 self._write_a_scrgb_clr()
1036
1037 self._xml_end_tag("a:fillRef")
1038
1039 def _write_a_effect_ref(self):
1040 # Write the <a:effectRef> element.
1041 attributes = [("idx", "0")]
1042
1043 self._xml_start_tag("a:effectRef", attributes)
1044
1045 # Write the a:scrgbClr element.
1046 self._write_a_scrgb_clr()
1047
1048 self._xml_end_tag("a:effectRef")
1049
1050 def _write_a_scrgb_clr(self):
1051 # Write the <a:scrgbClr> element.
1052
1053 attributes = [
1054 ("r", "0"),
1055 ("g", "0"),
1056 ("b", "0"),
1057 ]
1058
1059 self._xml_empty_tag("a:scrgbClr", attributes)
1060
1061 def _write_a_font_ref(self):
1062 # Write the <a:fontRef> element.
1063 attributes = [("idx", "minor")]
1064
1065 self._xml_start_tag("a:fontRef", attributes)
1066
1067 # Write the a:schemeClr element.
1068 self._write_a_scheme_clr("dk1")
1069
1070 self._xml_end_tag("a:fontRef")
1071
1072 def _write_a_scheme_clr(self, val):
1073 # Write the <a:schemeClr> element.
1074 attributes = [("val", val)]
1075
1076 self._xml_empty_tag("a:schemeClr", attributes)
1077
1078 def _write_a_shade(self, shade):
1079 # Write the <a:shade> element.
1080 attributes = [("val", shade)]
1081
1082 self._xml_empty_tag("a:shade", attributes)
1083
1084 def _write_a_prst_dash(self, val):
1085 # Write the <a:prstDash> element.
1086
1087 attributes = [("val", val)]
1088
1089 self._xml_empty_tag("a:prstDash", attributes)
1090
1091 def _write_a_grad_fill(self, gradient):
1092 # Write the <a:gradFill> element.
1093
1094 attributes = [("flip", "none"), ("rotWithShape", "1")]
1095
1096 if gradient["type"] == "linear":
1097 attributes = []
1098
1099 self._xml_start_tag("a:gradFill", attributes)
1100
1101 # Write the a:gsLst element.
1102 self._write_a_gs_lst(gradient)
1103
1104 if gradient["type"] == "linear":
1105 # Write the a:lin element.
1106 self._write_a_lin(gradient["angle"])
1107 else:
1108 # Write the a:path element.
1109 self._write_a_path(gradient["type"])
1110
1111 # Write the a:tileRect element.
1112 self._write_a_tile_rect(gradient["type"])
1113
1114 self._xml_end_tag("a:gradFill")
1115
1116 def _write_a_gs_lst(self, gradient):
1117 # Write the <a:gsLst> element.
1118 positions = gradient["positions"]
1119 colors = gradient["colors"]
1120
1121 self._xml_start_tag("a:gsLst")
1122
1123 for i in range(len(colors)):
1124 pos = int(positions[i] * 1000)
1125 attributes = [("pos", pos)]
1126 self._xml_start_tag("a:gs", attributes)
1127
1128 # Write the a:srgbClr element.
1129 color = get_rgb_color(colors[i])
1130 self._write_a_srgb_clr(color)
1131
1132 self._xml_end_tag("a:gs")
1133
1134 self._xml_end_tag("a:gsLst")
1135
1136 def _write_a_lin(self, angle):
1137 # Write the <a:lin> element.
1138
1139 angle = int(60000 * angle)
1140
1141 attributes = [
1142 ("ang", angle),
1143 ("scaled", "0"),
1144 ]
1145
1146 self._xml_empty_tag("a:lin", attributes)
1147
1148 def _write_a_path(self, gradient_type):
1149 # Write the <a:path> element.
1150
1151 attributes = [("path", gradient_type)]
1152
1153 self._xml_start_tag("a:path", attributes)
1154
1155 # Write the a:fillToRect element.
1156 self._write_a_fill_to_rect(gradient_type)
1157
1158 self._xml_end_tag("a:path")
1159
1160 def _write_a_fill_to_rect(self, gradient_type):
1161 # Write the <a:fillToRect> element.
1162
1163 if gradient_type == "shape":
1164 attributes = [
1165 ("l", "50000"),
1166 ("t", "50000"),
1167 ("r", "50000"),
1168 ("b", "50000"),
1169 ]
1170 else:
1171 attributes = [
1172 ("l", "100000"),
1173 ("t", "100000"),
1174 ]
1175
1176 self._xml_empty_tag("a:fillToRect", attributes)
1177
1178 def _write_a_tile_rect(self, gradient_type):
1179 # Write the <a:tileRect> element.
1180
1181 if gradient_type == "shape":
1182 attributes = []
1183 else:
1184 attributes = [
1185 ("r", "-100000"),
1186 ("b", "-100000"),
1187 ]
1188
1189 self._xml_empty_tag("a:tileRect", attributes)
1190
1191 def _write_a_srgb_clr(self, val):
1192 # Write the <a:srgbClr> element.
1193
1194 attributes = [("val", val)]
1195
1196 self._xml_empty_tag("a:srgbClr", attributes)
1197
1198 def _write_a_latin(self, attributes):
1199 # Write the <a:latin> element.
1200 self._xml_empty_tag("a:latin", attributes)
1201
1202 def _write_a_cs(self, attributes):
1203 # Write the <a:latin> element.
1204 self._xml_empty_tag("a:cs", attributes)