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