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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
249 # Write the <xdr:col> element.
250 self._xml_data_element("xdr:col", data)
251
252 def _write_col_off(self, data) -> None:
253 # Write the <xdr:colOff> element.
254 self._xml_data_element("xdr:colOff", data)
255
256 def _write_row(self, data) -> None:
257 # Write the <xdr:row> element.
258 self._xml_data_element("xdr:row", data)
259
260 def _write_row_off(self, data) -> None:
261 # Write the <xdr:rowOff> element.
262 self._xml_data_element("xdr:rowOff", data)
263
264 def _write_pos(self, x, y) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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 ) -> None:
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) -> None:
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) -> None:
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 ) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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) -> None:
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 ) -> None:
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(
699 self, col_absolute, row_absolute, width, height, shape=None
700 ) -> None:
701 # Write the <a:xfrm> element.
702 attributes = []
703
704 if shape:
705 if shape.rotation:
706 rotation = shape.rotation
707 rotation *= 60000
708 attributes.append(("rot", rotation))
709
710 if shape.flip_h:
711 attributes.append(("flipH", 1))
712 if shape.flip_v:
713 attributes.append(("flipV", 1))
714
715 self._xml_start_tag("a:xfrm", attributes)
716
717 # Write the a:off element.
718 self._write_a_off(col_absolute, row_absolute)
719
720 # Write the a:ext element.
721 self._write_a_ext(width, height)
722
723 self._xml_end_tag("a:xfrm")
724
725 def _write_a_off(self, x, y) -> None:
726 # Write the <a:off> element.
727 attributes = [
728 ("x", x),
729 ("y", y),
730 ]
731
732 self._xml_empty_tag("a:off", attributes)
733
734 def _write_a_ext(self, cx, cy) -> None:
735 # Write the <a:ext> element.
736 attributes = [
737 ("cx", cx),
738 ("cy", cy),
739 ]
740
741 self._xml_empty_tag("a:ext", attributes)
742
743 def _write_a_prst_geom(self, shape=None) -> None:
744 # Write the <a:prstGeom> element.
745 attributes = [("prst", "rect")]
746
747 self._xml_start_tag("a:prstGeom", attributes)
748
749 # Write the a:avLst element.
750 self._write_a_av_lst(shape)
751
752 self._xml_end_tag("a:prstGeom")
753
754 def _write_a_av_lst(self, shape=None) -> None:
755 # Write the <a:avLst> element.
756 adjustments = []
757
758 if shape and shape.adjustments:
759 adjustments = shape.adjustments
760
761 if adjustments:
762 self._xml_start_tag("a:avLst")
763
764 i = 0
765 for adj in adjustments:
766 i += 1
767 # Only connectors have multiple adjustments.
768 if shape.connect:
769 suffix = i
770 else:
771 suffix = ""
772
773 # Scale Adjustments: 100,000 = 100%.
774 adj_int = str(int(adj * 1000))
775
776 attributes = [("name", "adj" + suffix), ("fmla", "val" + adj_int)]
777
778 self._xml_empty_tag("a:gd", attributes)
779
780 self._xml_end_tag("a:avLst")
781 else:
782 self._xml_empty_tag("a:avLst")
783
784 def _write_a_solid_fill(self, color: Color) -> None:
785 # Write the <a:solidFill> element.
786 self._xml_start_tag("a:solidFill")
787
788 # Write the a:srgbClr element.
789 self._write_a_srgb_clr(color)
790
791 self._xml_end_tag("a:solidFill")
792
793 def _write_a_solid_fill_scheme(self, named_color, shade=None) -> None:
794 attributes = [("val", named_color)]
795
796 self._xml_start_tag("a:solidFill")
797
798 if shade:
799 self._xml_start_tag("a:schemeClr", attributes)
800 self._write_a_shade(shade)
801 self._xml_end_tag("a:schemeClr")
802 else:
803 self._xml_empty_tag("a:schemeClr", attributes)
804
805 self._xml_end_tag("a:solidFill")
806
807 def _write_a_ln(self, line) -> None:
808 # Write the <a:ln> element.
809 width = line.get("width", 0.75)
810
811 # Round width to nearest 0.25, like Excel.
812 width = int((width + 0.125) * 4) / 4.0
813
814 # Convert to internal units.
815 width = int(0.5 + (12700 * width))
816
817 attributes = [("w", width), ("cmpd", "sng")]
818
819 self._xml_start_tag("a:ln", attributes)
820
821 if "none" in line:
822 # Write the a:noFill element.
823 self._xml_empty_tag("a:noFill")
824
825 elif "color" in line:
826 # Write the a:solidFill element.
827 self._write_a_solid_fill(line["color"])
828
829 else:
830 # Write the a:solidFill element.
831 self._write_a_solid_fill_scheme("lt1", "50000")
832
833 # Write the line/dash type.
834 line_type = line.get("dash_type")
835 if line_type:
836 # Write the a:prstDash element.
837 self._write_a_prst_dash(line_type)
838
839 self._xml_end_tag("a:ln")
840
841 def _write_tx_body(self, shape) -> None:
842 # Write the <xdr:txBody> element.
843 attributes = []
844
845 if shape.text_rotation != 0:
846 if shape.text_rotation == 90:
847 attributes.append(("vert", "vert270"))
848 if shape.text_rotation == -90:
849 attributes.append(("vert", "vert"))
850 if shape.text_rotation == 270:
851 attributes.append(("vert", "wordArtVert"))
852 if shape.text_rotation == 271:
853 attributes.append(("vert", "eaVert"))
854
855 attributes.append(("wrap", "square"))
856 attributes.append(("rtlCol", "0"))
857
858 if not shape.align["defined"]:
859 attributes.append(("anchor", "t"))
860 else:
861 if "vertical" in shape.align:
862 align = shape.align["vertical"]
863 if align == "top":
864 attributes.append(("anchor", "t"))
865 elif align == "middle":
866 attributes.append(("anchor", "ctr"))
867 elif align == "bottom":
868 attributes.append(("anchor", "b"))
869 else:
870 attributes.append(("anchor", "t"))
871
872 if "horizontal" in shape.align:
873 align = shape.align["horizontal"]
874 if align == "center":
875 attributes.append(("anchorCtr", "1"))
876 else:
877 attributes.append(("anchorCtr", "0"))
878
879 self._xml_start_tag("xdr:txBody")
880 self._xml_empty_tag("a:bodyPr", attributes)
881 self._xml_empty_tag("a:lstStyle")
882
883 lines = shape.text.split("\n")
884
885 # Set the font attributes.
886 font = shape.font
887 # pylint: disable=protected-access
888 style_attrs = Shape._get_font_style_attributes(font)
889 latin_attrs = Shape._get_font_latin_attributes(font)
890 style_attrs.insert(0, ("lang", font["lang"]))
891
892 if shape.textlink != "":
893 attributes = [
894 ("id", "{B8ADDEFE-BF52-4FD4-8C5D-6B85EF6FF707}"),
895 ("type", "TxLink"),
896 ]
897
898 self._xml_start_tag("a:p")
899 self._xml_start_tag("a:fld", attributes)
900
901 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
902
903 self._xml_data_element("a:t", shape.text)
904 self._xml_end_tag("a:fld")
905
906 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
907
908 self._xml_end_tag("a:p")
909 else:
910 for line in lines:
911 self._xml_start_tag("a:p")
912
913 if line == "":
914 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
915 self._xml_end_tag("a:p")
916 continue
917
918 if "text" in shape.align:
919 if shape.align["text"] == "left":
920 self._xml_empty_tag("a:pPr", [("algn", "l")])
921 if shape.align["text"] == "center":
922 self._xml_empty_tag("a:pPr", [("algn", "ctr")])
923 if shape.align["text"] == "right":
924 self._xml_empty_tag("a:pPr", [("algn", "r")])
925
926 self._xml_start_tag("a:r")
927
928 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
929
930 self._xml_data_element("a:t", line)
931
932 self._xml_end_tag("a:r")
933 self._xml_end_tag("a:p")
934
935 self._xml_end_tag("xdr:txBody")
936
937 def _write_font_run(self, font, style_attrs, latin_attrs, run_type) -> None:
938 # Write a:rPr or a:endParaRPr.
939 has_color = font.get("color") is not None
940
941 if latin_attrs or has_color:
942 self._xml_start_tag(run_type, style_attrs)
943
944 if has_color:
945 self._write_a_solid_fill(font["color"])
946
947 if latin_attrs:
948 self._write_a_latin(latin_attrs)
949 self._write_a_cs(latin_attrs)
950
951 self._xml_end_tag(run_type)
952 else:
953 self._xml_empty_tag(run_type, style_attrs)
954
955 def _write_style(self) -> None:
956 # Write the <xdr:style> element.
957 self._xml_start_tag("xdr:style")
958
959 # Write the a:lnRef element.
960 self._write_a_ln_ref()
961
962 # Write the a:fillRef element.
963 self._write_a_fill_ref()
964
965 # Write the a:effectRef element.
966 self._write_a_effect_ref()
967
968 # Write the a:fontRef element.
969 self._write_a_font_ref()
970
971 self._xml_end_tag("xdr:style")
972
973 def _write_a_ln_ref(self) -> None:
974 # Write the <a:lnRef> element.
975 attributes = [("idx", "0")]
976
977 self._xml_start_tag("a:lnRef", attributes)
978
979 # Write the a:scrgbClr element.
980 self._write_a_scrgb_clr()
981
982 self._xml_end_tag("a:lnRef")
983
984 def _write_a_fill_ref(self) -> None:
985 # Write the <a:fillRef> element.
986 attributes = [("idx", "0")]
987
988 self._xml_start_tag("a:fillRef", attributes)
989
990 # Write the a:scrgbClr element.
991 self._write_a_scrgb_clr()
992
993 self._xml_end_tag("a:fillRef")
994
995 def _write_a_effect_ref(self) -> None:
996 # Write the <a:effectRef> element.
997 attributes = [("idx", "0")]
998
999 self._xml_start_tag("a:effectRef", attributes)
1000
1001 # Write the a:scrgbClr element.
1002 self._write_a_scrgb_clr()
1003
1004 self._xml_end_tag("a:effectRef")
1005
1006 def _write_a_scrgb_clr(self) -> None:
1007 # Write the <a:scrgbClr> element.
1008
1009 attributes = [
1010 ("r", "0"),
1011 ("g", "0"),
1012 ("b", "0"),
1013 ]
1014
1015 self._xml_empty_tag("a:scrgbClr", attributes)
1016
1017 def _write_a_font_ref(self) -> None:
1018 # Write the <a:fontRef> element.
1019 attributes = [("idx", "minor")]
1020
1021 self._xml_start_tag("a:fontRef", attributes)
1022
1023 # Write the a:schemeClr element.
1024 self._write_a_scheme_clr("dk1")
1025
1026 self._xml_end_tag("a:fontRef")
1027
1028 def _write_a_scheme_clr(self, val) -> None:
1029 # Write the <a:schemeClr> element.
1030 attributes = [("val", val)]
1031
1032 self._xml_empty_tag("a:schemeClr", attributes)
1033
1034 def _write_a_shade(self, shade) -> None:
1035 # Write the <a:shade> element.
1036 attributes = [("val", shade)]
1037
1038 self._xml_empty_tag("a:shade", attributes)
1039
1040 def _write_a_prst_dash(self, val) -> None:
1041 # Write the <a:prstDash> element.
1042
1043 attributes = [("val", val)]
1044
1045 self._xml_empty_tag("a:prstDash", attributes)
1046
1047 def _write_a_grad_fill(self, gradient) -> None:
1048 # Write the <a:gradFill> element.
1049
1050 attributes = [("flip", "none"), ("rotWithShape", "1")]
1051
1052 if gradient["type"] == "linear":
1053 attributes = []
1054
1055 self._xml_start_tag("a:gradFill", attributes)
1056
1057 # Write the a:gsLst element.
1058 self._write_a_gs_lst(gradient)
1059
1060 if gradient["type"] == "linear":
1061 # Write the a:lin element.
1062 self._write_a_lin(gradient["angle"])
1063 else:
1064 # Write the a:path element.
1065 self._write_a_path(gradient["type"])
1066
1067 # Write the a:tileRect element.
1068 self._write_a_tile_rect(gradient["type"])
1069
1070 self._xml_end_tag("a:gradFill")
1071
1072 def _write_a_gs_lst(self, gradient) -> None:
1073 # Write the <a:gsLst> element.
1074 positions = gradient["positions"]
1075 colors = gradient["colors"]
1076
1077 self._xml_start_tag("a:gsLst")
1078
1079 for i, color in enumerate(colors):
1080 pos = int(positions[i] * 1000)
1081 attributes = [("pos", pos)]
1082 self._xml_start_tag("a:gs", attributes)
1083
1084 # Write the a:srgbClr element.
1085 self._write_a_srgb_clr(color)
1086
1087 self._xml_end_tag("a:gs")
1088
1089 self._xml_end_tag("a:gsLst")
1090
1091 def _write_a_lin(self, angle) -> None:
1092 # Write the <a:lin> element.
1093
1094 angle = int(60000 * angle)
1095
1096 attributes = [
1097 ("ang", angle),
1098 ("scaled", "0"),
1099 ]
1100
1101 self._xml_empty_tag("a:lin", attributes)
1102
1103 def _write_a_path(self, gradient_type) -> None:
1104 # Write the <a:path> element.
1105
1106 attributes = [("path", gradient_type)]
1107
1108 self._xml_start_tag("a:path", attributes)
1109
1110 # Write the a:fillToRect element.
1111 self._write_a_fill_to_rect(gradient_type)
1112
1113 self._xml_end_tag("a:path")
1114
1115 def _write_a_fill_to_rect(self, gradient_type) -> None:
1116 # Write the <a:fillToRect> element.
1117
1118 if gradient_type == "shape":
1119 attributes = [
1120 ("l", "50000"),
1121 ("t", "50000"),
1122 ("r", "50000"),
1123 ("b", "50000"),
1124 ]
1125 else:
1126 attributes = [
1127 ("l", "100000"),
1128 ("t", "100000"),
1129 ]
1130
1131 self._xml_empty_tag("a:fillToRect", attributes)
1132
1133 def _write_a_tile_rect(self, gradient_type) -> None:
1134 # Write the <a:tileRect> element.
1135
1136 if gradient_type == "shape":
1137 attributes = []
1138 else:
1139 attributes = [
1140 ("r", "-100000"),
1141 ("b", "-100000"),
1142 ]
1143
1144 self._xml_empty_tag("a:tileRect", attributes)
1145
1146 def _write_a_srgb_clr(self, color: Color) -> None:
1147 # Write the <a:srgbClr> element.
1148 attributes = [("val", color._rgb_hex_value())]
1149
1150 self._xml_empty_tag("a:srgbClr", attributes)
1151
1152 def _write_a_latin(self, attributes) -> None:
1153 # Write the <a:latin> element.
1154 self._xml_empty_tag("a:latin", attributes)
1155
1156 def _write_a_cs(self, attributes) -> None:
1157 # Write the <a:latin> element.
1158 self._xml_empty_tag("a:cs", attributes)