Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/xlsxwriter/drawing.py: 16%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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#
10from enum import Enum
12from xlsxwriter import xmlwriter
13from xlsxwriter.color import Color
14from xlsxwriter.shape import Shape
15from xlsxwriter.url import Url
18class DrawingTypes(Enum):
19 """
20 Enum to represent different types of drawings in a worksheet.
21 """
23 NONE = 0
24 CHART = 1
25 IMAGE = 2
26 SHAPE = 3
29class DrawingInfo:
30 """
31 An internal class to represent a drawing object in an Excel worksheet.
33 """
35 def __init__(self) -> None:
36 """
37 Initialize a DrawingType instance with default values.
38 """
39 self._drawing_type = DrawingTypes.NONE
40 self._anchor_type = None
41 self._dimensions = []
42 self._width = 0
43 self._height = 0
44 self._shape = None
45 self._anchor = None
46 self._url = None
47 self._rel_index = 0
48 self._name = None
49 self._description = None
50 self._decorative = False
53class Drawing(xmlwriter.XMLwriter):
54 """
55 A class for writing the Excel XLSX Drawing file.
58 """
60 ###########################################################################
61 #
62 # Public API.
63 #
64 ###########################################################################
66 def __init__(self) -> None:
67 """
68 Constructor.
70 """
72 super().__init__()
74 self.drawings = []
75 self.embedded = 0
76 self.orientation = 0
78 ###########################################################################
79 #
80 # Private API.
81 #
82 ###########################################################################
84 def _assemble_xml_file(self) -> None:
85 # Assemble and write the XML file.
87 # Write the XML declaration.
88 self._xml_declaration()
90 # Write the xdr:wsDr element.
91 self._write_drawing_workspace()
93 if self.embedded:
94 index = 0
95 for drawing in self.drawings:
96 # Write the xdr:twoCellAnchor element.
97 index += 1
98 self._write_two_cell_anchor(index, drawing)
100 else:
101 # Write the xdr:absoluteAnchor element.
102 drawing = DrawingInfo()
103 drawing._rel_index = 1
104 self._write_absolute_anchor(1, drawing)
106 self._xml_end_tag("xdr:wsDr")
108 # Close the file.
109 self._xml_close()
111 def _add_drawing_object(self, drawing_object: DrawingInfo) -> None:
112 # Add a chart, image or shape sub object to the drawing.
113 self.drawings.append(drawing_object)
115 ###########################################################################
116 #
117 # XML methods.
118 #
119 ###########################################################################
121 def _write_drawing_workspace(self) -> None:
122 # Write the <xdr:wsDr> element.
123 schema = "http://schemas.openxmlformats.org/drawingml/"
124 xmlns_xdr = schema + "2006/spreadsheetDrawing"
125 xmlns_a = schema + "2006/main"
127 attributes = [
128 ("xmlns:xdr", xmlns_xdr),
129 ("xmlns:a", xmlns_a),
130 ]
132 self._xml_start_tag("xdr:wsDr", attributes)
134 def _write_two_cell_anchor(self, index: int, drawing: DrawingInfo) -> None:
135 # Write the <xdr:twoCellAnchor> element.
136 dimensions = drawing._dimensions
137 col_from = dimensions[0]
138 row_from = dimensions[1]
139 col_from_offset = dimensions[2]
140 row_from_offset = dimensions[3]
141 col_to = dimensions[4]
142 row_to = dimensions[5]
143 col_to_offset = dimensions[6]
144 row_to_offset = dimensions[7]
145 col_absolute = dimensions[8]
146 row_absolute = dimensions[9]
148 attributes = []
150 # Add attribute for positioning.
151 if drawing._anchor == 2:
152 attributes.append(("editAs", "oneCell"))
153 elif drawing._anchor == 3:
154 attributes.append(("editAs", "absolute"))
156 # Add editAs attribute for shapes.
157 if drawing._shape and drawing._shape.edit_as:
158 attributes.append(("editAs", drawing._shape.edit_as))
160 self._xml_start_tag("xdr:twoCellAnchor", attributes)
162 # Write the xdr:from element.
163 self._write_from(col_from, row_from, col_from_offset, row_from_offset)
165 # Write the xdr:from element.
166 self._write_to(col_to, row_to, col_to_offset, row_to_offset)
168 if drawing._drawing_type == DrawingTypes.CHART:
169 # Graphic frame.
170 # Write the xdr:graphicFrame element for charts.
171 self._write_graphic_frame(index, drawing)
172 elif drawing._drawing_type == DrawingTypes.IMAGE:
173 # Write the xdr:pic element.
174 self._write_pic(index, col_absolute, row_absolute, drawing)
175 else:
176 # Write the xdr:sp element for shapes.
177 self._write_sp(index, col_absolute, row_absolute, drawing)
179 # Write the xdr:clientData element.
180 self._write_client_data()
182 self._xml_end_tag("xdr:twoCellAnchor")
184 def _write_absolute_anchor(self, index: int, drawing: DrawingInfo) -> None:
185 self._xml_start_tag("xdr:absoluteAnchor")
186 # Write the <xdr:absoluteAnchor> element.
188 # Different coordinates for horizontal (= 0) and vertical (= 1).
189 if self.orientation == 0:
190 # Write the xdr:pos element.
191 self._write_pos(0, 0)
193 # Write the xdr:ext element.
194 self._write_xdr_ext(9308969, 6078325)
196 else:
197 # Write the xdr:pos element.
198 self._write_pos(0, -47625)
200 # Write the xdr:ext element.
201 self._write_xdr_ext(6162675, 6124575)
203 # Write the xdr:graphicFrame element.
204 self._write_graphic_frame(index, drawing)
206 # Write the xdr:clientData element.
207 self._write_client_data()
209 self._xml_end_tag("xdr:absoluteAnchor")
211 def _write_from(self, col: int, row: int, col_offset, row_offset) -> None:
212 # Write the <xdr:from> element.
213 self._xml_start_tag("xdr:from")
215 # Write the xdr:col element.
216 self._write_col(col)
218 # Write the xdr:colOff element.
219 self._write_col_off(col_offset)
221 # Write the xdr:row element.
222 self._write_row(row)
224 # Write the xdr:rowOff element.
225 self._write_row_off(row_offset)
227 self._xml_end_tag("xdr:from")
229 def _write_to(self, col: int, row: int, col_offset, row_offset) -> None:
230 # Write the <xdr:to> element.
231 self._xml_start_tag("xdr:to")
233 # Write the xdr:col element.
234 self._write_col(col)
236 # Write the xdr:colOff element.
237 self._write_col_off(col_offset)
239 # Write the xdr:row element.
240 self._write_row(row)
242 # Write the xdr:rowOff element.
243 self._write_row_off(row_offset)
245 self._xml_end_tag("xdr:to")
247 def _write_col(self, data) -> None:
248 # Write the <xdr:col> element.
249 self._xml_data_element("xdr:col", data)
251 def _write_col_off(self, data) -> None:
252 # Write the <xdr:colOff> element.
253 self._xml_data_element("xdr:colOff", data)
255 def _write_row(self, data) -> None:
256 # Write the <xdr:row> element.
257 self._xml_data_element("xdr:row", data)
259 def _write_row_off(self, data) -> None:
260 # Write the <xdr:rowOff> element.
261 self._xml_data_element("xdr:rowOff", data)
263 def _write_pos(self, x, y) -> None:
264 # Write the <xdr:pos> element.
266 attributes = [("x", x), ("y", y)]
268 self._xml_empty_tag("xdr:pos", attributes)
270 def _write_xdr_ext(self, cx, cy) -> None:
271 # Write the <xdr:ext> element.
273 attributes = [("cx", cx), ("cy", cy)]
275 self._xml_empty_tag("xdr:ext", attributes)
277 def _write_graphic_frame(self, index: int, drawing: DrawingInfo) -> None:
278 # Write the <xdr:graphicFrame> element.
279 attributes = [("macro", "")]
281 self._xml_start_tag("xdr:graphicFrame", attributes)
283 # Write the xdr:nvGraphicFramePr element.
284 self._write_nv_graphic_frame_pr(index, drawing)
286 # Write the xdr:xfrm element.
287 self._write_xfrm()
289 # Write the a:graphic element.
290 self._write_atag_graphic(drawing._rel_index)
292 self._xml_end_tag("xdr:graphicFrame")
294 def _write_nv_graphic_frame_pr(self, index: int, drawing: DrawingInfo) -> None:
295 # Write the <xdr:nvGraphicFramePr> element.
297 name = drawing._name
298 if not name:
299 name = "Chart " + str(index)
301 self._xml_start_tag("xdr:nvGraphicFramePr")
303 # Write the xdr:cNvPr element.
304 self._write_c_nv_pr(index + 1, drawing, name)
306 # Write the xdr:cNvGraphicFramePr element.
307 self._write_c_nv_graphic_frame_pr()
309 self._xml_end_tag("xdr:nvGraphicFramePr")
311 def _write_c_nv_pr(self, index: int, drawing: DrawingInfo, name: str) -> None:
312 # Write the <xdr:cNvPr> element.
313 attributes = [("id", index), ("name", name)]
315 # Add description attribute for images.
316 if drawing._description and not drawing._decorative:
317 attributes.append(("descr", drawing._description))
319 if drawing._url or drawing._decorative:
320 self._xml_start_tag("xdr:cNvPr", attributes)
322 if drawing._url:
323 self._write_a_hlink_click(drawing._url)
325 if drawing._decorative:
326 self._write_decorative()
328 self._xml_end_tag("xdr:cNvPr")
329 else:
330 self._xml_empty_tag("xdr:cNvPr", attributes)
332 def _write_decorative(self) -> None:
333 self._xml_start_tag("a:extLst")
335 self._write_uri_ext("{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}")
336 self._write_a16_creation_id()
337 self._xml_end_tag("a:ext")
339 self._write_uri_ext("{C183D7F6-B498-43B3-948B-1728B52AA6E4}")
340 self._write_adec_decorative()
341 self._xml_end_tag("a:ext")
343 self._xml_end_tag("a:extLst")
345 def _write_uri_ext(self, uri) -> None:
346 # Write the <a:ext> element.
347 attributes = [("uri", uri)]
349 self._xml_start_tag("a:ext", attributes)
351 def _write_adec_decorative(self) -> None:
352 # Write the <adec:decorative> element.
353 xmlns = "http://schemas.microsoft.com/office/drawing/2017/decorative"
354 val = "1"
356 attributes = [
357 ("xmlns:adec", xmlns),
358 ("val", val),
359 ]
361 self._xml_empty_tag("adec:decorative", attributes)
363 def _write_a16_creation_id(self) -> None:
364 # Write the <a16:creationId> element.
366 xmlns_a_16 = "http://schemas.microsoft.com/office/drawing/2014/main"
367 creation_id = "{00000000-0008-0000-0000-000002000000}"
369 attributes = [
370 ("xmlns:a16", xmlns_a_16),
371 ("id", creation_id),
372 ]
374 self._xml_empty_tag("a16:creationId", attributes)
376 def _write_a_hlink_click(self, url: Url) -> None:
377 # Write the <a:hlinkClick> element.
378 schema = "http://schemas.openxmlformats.org/officeDocument/"
379 xmlns_r = schema + "2006/relationships"
381 attributes = [
382 ("xmlns:r", xmlns_r),
383 ("r:id", "rId" + str(url._rel_index)),
384 ]
386 if url._tip:
387 attributes.append(("tooltip", url._tip))
389 self._xml_empty_tag("a:hlinkClick", attributes)
391 def _write_c_nv_graphic_frame_pr(self) -> None:
392 # Write the <xdr:cNvGraphicFramePr> element.
393 if self.embedded:
394 self._xml_empty_tag("xdr:cNvGraphicFramePr")
395 else:
396 self._xml_start_tag("xdr:cNvGraphicFramePr")
398 # Write the a:graphicFrameLocks element.
399 self._write_a_graphic_frame_locks()
401 self._xml_end_tag("xdr:cNvGraphicFramePr")
403 def _write_a_graphic_frame_locks(self) -> None:
404 # Write the <a:graphicFrameLocks> element.
405 attributes = [("noGrp", 1)]
407 self._xml_empty_tag("a:graphicFrameLocks", attributes)
409 def _write_xfrm(self) -> None:
410 # Write the <xdr:xfrm> element.
411 self._xml_start_tag("xdr:xfrm")
413 # Write the xfrmOffset element.
414 self._write_xfrm_offset()
416 # Write the xfrmOffset element.
417 self._write_xfrm_extension()
419 self._xml_end_tag("xdr:xfrm")
421 def _write_xfrm_offset(self) -> None:
422 # Write the <a:off> xfrm sub-element.
424 attributes = [
425 ("x", 0),
426 ("y", 0),
427 ]
429 self._xml_empty_tag("a:off", attributes)
431 def _write_xfrm_extension(self) -> None:
432 # Write the <a:ext> xfrm sub-element.
434 attributes = [
435 ("cx", 0),
436 ("cy", 0),
437 ]
439 self._xml_empty_tag("a:ext", attributes)
441 def _write_atag_graphic(self, index: int) -> None:
442 # Write the <a:graphic> element.
443 self._xml_start_tag("a:graphic")
445 # Write the a:graphicData element.
446 self._write_atag_graphic_data(index)
448 self._xml_end_tag("a:graphic")
450 def _write_atag_graphic_data(self, index: int) -> None:
451 # Write the <a:graphicData> element.
452 uri = "http://schemas.openxmlformats.org/drawingml/2006/chart"
454 attributes = [
455 (
456 "uri",
457 uri,
458 )
459 ]
461 self._xml_start_tag("a:graphicData", attributes)
463 # Write the c:chart element.
464 self._write_c_chart("rId" + str(index))
466 self._xml_end_tag("a:graphicData")
468 def _write_c_chart(self, r_id) -> None:
469 # Write the <c:chart> element.
471 schema = "http://schemas.openxmlformats.org/"
472 xmlns_c = schema + "drawingml/2006/chart"
473 xmlns_r = schema + "officeDocument/2006/relationships"
475 attributes = [
476 ("xmlns:c", xmlns_c),
477 ("xmlns:r", xmlns_r),
478 ("r:id", r_id),
479 ]
481 self._xml_empty_tag("c:chart", attributes)
483 def _write_client_data(self) -> None:
484 # Write the <xdr:clientData> element.
485 self._xml_empty_tag("xdr:clientData")
487 def _write_sp(
488 self,
489 index,
490 col_absolute,
491 row_absolute,
492 drawing: DrawingInfo,
493 ) -> None:
494 # Write the <xdr:sp> element.
496 if drawing._shape and drawing._shape.connect:
497 attributes = [("macro", "")]
498 self._xml_start_tag("xdr:cxnSp", attributes)
500 # Write the xdr:nvCxnSpPr element.
501 self._write_nv_cxn_sp_pr(drawing._shape)
503 # Write the xdr:spPr element.
504 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing)
506 self._xml_end_tag("xdr:cxnSp")
507 else:
508 # Add attribute for shapes.
509 attributes = [("macro", ""), ("textlink", drawing._shape.textlink)]
511 self._xml_start_tag("xdr:sp", attributes)
513 # Write the xdr:nvSpPr element.
514 self._write_nv_sp_pr(index, drawing)
516 # Write the xdr:spPr element.
517 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing)
519 # Write the xdr:style element.
520 self._write_style()
522 # Write the xdr:txBody element.
523 if drawing._shape.text is not None:
524 self._write_tx_body(drawing._shape)
526 self._xml_end_tag("xdr:sp")
528 def _write_nv_cxn_sp_pr(self, shape) -> None:
529 # Write the <xdr:nvCxnSpPr> element.
530 self._xml_start_tag("xdr:nvCxnSpPr")
532 self._xml_start_tag("xdr:cNvCxnSpPr")
534 attributes = [("noChangeShapeType", "1")]
535 self._xml_empty_tag("a:cxnSpLocks", attributes)
537 if shape.start:
538 attributes = [("id", shape.start), ("idx", shape.start_index)]
539 self._xml_empty_tag("a:stCxn", attributes)
541 if shape.end:
542 attributes = [("id", shape.end), ("idx", shape.end_index)]
543 self._xml_empty_tag("a:endCxn", attributes)
545 self._xml_end_tag("xdr:cNvCxnSpPr")
546 self._xml_end_tag("xdr:nvCxnSpPr")
548 def _write_nv_sp_pr(self, index: int, drawing: DrawingInfo) -> None:
549 # Write the <xdr:NvSpPr> element.
550 attributes = []
552 self._xml_start_tag("xdr:nvSpPr")
554 name = drawing._shape.name + " " + str(index)
556 self._write_c_nv_pr(index + 1, drawing, name)
558 if drawing._shape.name == "TextBox":
559 attributes = [("txBox", 1)]
561 self._xml_empty_tag("xdr:cNvSpPr", attributes)
563 self._xml_end_tag("xdr:nvSpPr")
565 def _write_pic(
566 self,
567 index: int,
568 col_absolute: int,
569 row_absolute: int,
570 drawing: DrawingInfo,
571 ) -> None:
572 # Write the <xdr:pic> element.
573 self._xml_start_tag("xdr:pic")
575 # Write the xdr:nvPicPr element.
576 self._write_nv_pic_pr(index, drawing)
577 # Write the xdr:blipFill element.
578 self._write_blip_fill(drawing._rel_index)
580 # Write the xdr:spPr element.
581 self._write_sp_pr(col_absolute, row_absolute, drawing)
583 self._xml_end_tag("xdr:pic")
585 def _write_nv_pic_pr(self, index: int, drawing: DrawingInfo) -> None:
586 # Write the <xdr:nvPicPr> element.
587 self._xml_start_tag("xdr:nvPicPr")
589 name = "Picture " + str(index)
591 # Write the xdr:cNvPr element.
592 self._write_c_nv_pr(index + 1, drawing, name)
594 # Write the xdr:cNvPicPr element.
595 self._write_c_nv_pic_pr()
597 self._xml_end_tag("xdr:nvPicPr")
599 def _write_c_nv_pic_pr(self) -> None:
600 # Write the <xdr:cNvPicPr> element.
601 self._xml_start_tag("xdr:cNvPicPr")
603 # Write the a:picLocks element.
604 self._write_a_pic_locks()
606 self._xml_end_tag("xdr:cNvPicPr")
608 def _write_a_pic_locks(self) -> None:
609 # Write the <a:picLocks> element.
610 attributes = [("noChangeAspect", 1)]
612 self._xml_empty_tag("a:picLocks", attributes)
614 def _write_blip_fill(self, index: int) -> None:
615 # Write the <xdr:blipFill> element.
616 self._xml_start_tag("xdr:blipFill")
618 # Write the a:blip element.
619 self._write_a_blip(index)
621 # Write the a:stretch element.
622 self._write_a_stretch()
624 self._xml_end_tag("xdr:blipFill")
626 def _write_a_blip(self, index: int) -> None:
627 # Write the <a:blip> element.
628 schema = "http://schemas.openxmlformats.org/officeDocument/"
629 xmlns_r = schema + "2006/relationships"
630 r_embed = "rId" + str(index)
632 attributes = [("xmlns:r", xmlns_r), ("r:embed", r_embed)]
634 self._xml_empty_tag("a:blip", attributes)
636 def _write_a_stretch(self) -> None:
637 # Write the <a:stretch> element.
638 self._xml_start_tag("a:stretch")
640 # Write the a:fillRect element.
641 self._write_a_fill_rect()
643 self._xml_end_tag("a:stretch")
645 def _write_a_fill_rect(self) -> None:
646 # Write the <a:fillRect> element.
647 self._xml_empty_tag("a:fillRect")
649 def _write_sp_pr(self, col_absolute, row_absolute, drawing: DrawingInfo) -> None:
650 # Write the <xdr:spPr> element, for charts.
652 self._xml_start_tag("xdr:spPr")
654 # Write the a:xfrm element.
655 self._write_a_xfrm(col_absolute, row_absolute, drawing._width, drawing._height)
657 # Write the a:prstGeom element.
658 self._write_a_prst_geom(drawing._shape)
660 self._xml_end_tag("xdr:spPr")
662 def _write_xdr_sp_pr(
663 self, col_absolute: int, row_absolute: int, drawing: DrawingInfo
664 ) -> None:
665 # Write the <xdr:spPr> element for shapes.
666 self._xml_start_tag("xdr:spPr")
668 # Write the a:xfrm element.
669 self._write_a_xfrm(
670 col_absolute, row_absolute, drawing._width, drawing._height, drawing._shape
671 )
673 # Write the a:prstGeom element.
674 shape = drawing._shape
675 self._write_a_prst_geom(shape)
677 if shape.fill:
678 if not shape.fill["defined"]:
679 # Write the a:solidFill element.
680 self._write_a_solid_fill_scheme("lt1")
681 elif "none" in shape.fill:
682 # Write the a:noFill element.
683 self._xml_empty_tag("a:noFill")
684 elif "color" in shape.fill:
685 # Write the a:solidFill element.
686 self._write_a_solid_fill(shape.fill["color"])
688 if shape.gradient:
689 # Write the a:gradFill element.
690 self._write_a_grad_fill(shape.gradient)
692 # Write the a:ln element.
693 self._write_a_ln(shape.line)
695 self._xml_end_tag("xdr:spPr")
697 def _write_a_xfrm(
698 self, col_absolute, row_absolute, width, height, shape=None
699 ) -> None:
700 # Write the <a:xfrm> element.
701 attributes = []
703 if shape:
704 if shape.rotation:
705 rotation = shape.rotation
706 rotation *= 60000
707 attributes.append(("rot", rotation))
709 if shape.flip_h:
710 attributes.append(("flipH", 1))
711 if shape.flip_v:
712 attributes.append(("flipV", 1))
714 self._xml_start_tag("a:xfrm", attributes)
716 # Write the a:off element.
717 self._write_a_off(col_absolute, row_absolute)
719 # Write the a:ext element.
720 self._write_a_ext(width, height)
722 self._xml_end_tag("a:xfrm")
724 def _write_a_off(self, x, y) -> None:
725 # Write the <a:off> element.
726 attributes = [
727 ("x", x),
728 ("y", y),
729 ]
731 self._xml_empty_tag("a:off", attributes)
733 def _write_a_ext(self, cx, cy) -> None:
734 # Write the <a:ext> element.
735 attributes = [
736 ("cx", cx),
737 ("cy", cy),
738 ]
740 self._xml_empty_tag("a:ext", attributes)
742 def _write_a_prst_geom(self, shape=None) -> None:
743 # Write the <a:prstGeom> element.
744 attributes = [("prst", "rect")]
746 self._xml_start_tag("a:prstGeom", attributes)
748 # Write the a:avLst element.
749 self._write_a_av_lst(shape)
751 self._xml_end_tag("a:prstGeom")
753 def _write_a_av_lst(self, shape=None) -> None:
754 # Write the <a:avLst> element.
755 adjustments = []
757 if shape and shape.adjustments:
758 adjustments = shape.adjustments
760 if adjustments:
761 self._xml_start_tag("a:avLst")
763 i = 0
764 for adj in adjustments:
765 i += 1
766 # Only connectors have multiple adjustments.
767 if shape.connect:
768 suffix = i
769 else:
770 suffix = ""
772 # Scale Adjustments: 100,000 = 100%.
773 adj_int = str(int(adj * 1000))
775 attributes = [("name", "adj" + suffix), ("fmla", "val" + adj_int)]
777 self._xml_empty_tag("a:gd", attributes)
779 self._xml_end_tag("a:avLst")
780 else:
781 self._xml_empty_tag("a:avLst")
783 def _write_a_solid_fill(self, color: Color) -> None:
784 # Write the <a:solidFill> element.
785 self._xml_start_tag("a:solidFill")
787 # Write the a:srgbClr element.
788 self._write_a_srgb_clr(color)
790 self._xml_end_tag("a:solidFill")
792 def _write_a_solid_fill_scheme(self, named_color, shade=None) -> None:
793 attributes = [("val", named_color)]
795 self._xml_start_tag("a:solidFill")
797 if shade:
798 self._xml_start_tag("a:schemeClr", attributes)
799 self._write_a_shade(shade)
800 self._xml_end_tag("a:schemeClr")
801 else:
802 self._xml_empty_tag("a:schemeClr", attributes)
804 self._xml_end_tag("a:solidFill")
806 def _write_a_ln(self, line) -> None:
807 # Write the <a:ln> element.
808 width = line.get("width", 0.75)
810 # Round width to nearest 0.25, like Excel.
811 width = int((width + 0.125) * 4) / 4.0
813 # Convert to internal units.
814 width = int(0.5 + (12700 * width))
816 attributes = [("w", width), ("cmpd", "sng")]
818 self._xml_start_tag("a:ln", attributes)
820 if "none" in line:
821 # Write the a:noFill element.
822 self._xml_empty_tag("a:noFill")
824 elif "color" in line:
825 # Write the a:solidFill element.
826 self._write_a_solid_fill(line["color"])
828 else:
829 # Write the a:solidFill element.
830 self._write_a_solid_fill_scheme("lt1", "50000")
832 # Write the line/dash type.
833 line_type = line.get("dash_type")
834 if line_type:
835 # Write the a:prstDash element.
836 self._write_a_prst_dash(line_type)
838 self._xml_end_tag("a:ln")
840 def _write_tx_body(self, shape) -> None:
841 # Write the <xdr:txBody> element.
842 attributes = []
844 if shape.text_rotation != 0:
845 if shape.text_rotation == 90:
846 attributes.append(("vert", "vert270"))
847 if shape.text_rotation == -90:
848 attributes.append(("vert", "vert"))
849 if shape.text_rotation == 270:
850 attributes.append(("vert", "wordArtVert"))
851 if shape.text_rotation == 271:
852 attributes.append(("vert", "eaVert"))
854 attributes.append(("wrap", "square"))
855 attributes.append(("rtlCol", "0"))
857 if not shape.align["defined"]:
858 attributes.append(("anchor", "t"))
859 else:
860 if "vertical" in shape.align:
861 align = shape.align["vertical"]
862 if align == "top":
863 attributes.append(("anchor", "t"))
864 elif align == "middle":
865 attributes.append(("anchor", "ctr"))
866 elif align == "bottom":
867 attributes.append(("anchor", "b"))
868 else:
869 attributes.append(("anchor", "t"))
871 if "horizontal" in shape.align:
872 align = shape.align["horizontal"]
873 if align == "center":
874 attributes.append(("anchorCtr", "1"))
875 else:
876 attributes.append(("anchorCtr", "0"))
878 self._xml_start_tag("xdr:txBody")
879 self._xml_empty_tag("a:bodyPr", attributes)
880 self._xml_empty_tag("a:lstStyle")
882 lines = shape.text.split("\n")
884 # Set the font attributes.
885 font = shape.font
886 # pylint: disable=protected-access
887 style_attrs = Shape._get_font_style_attributes(font)
888 latin_attrs = Shape._get_font_latin_attributes(font)
889 style_attrs.insert(0, ("lang", font["lang"]))
891 if shape.textlink != "":
892 attributes = [
893 ("id", "{B8ADDEFE-BF52-4FD4-8C5D-6B85EF6FF707}"),
894 ("type", "TxLink"),
895 ]
897 self._xml_start_tag("a:p")
898 self._xml_start_tag("a:fld", attributes)
900 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
902 self._xml_data_element("a:t", shape.text)
903 self._xml_end_tag("a:fld")
905 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
907 self._xml_end_tag("a:p")
908 else:
909 for line in lines:
910 self._xml_start_tag("a:p")
912 if line == "":
913 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
914 self._xml_end_tag("a:p")
915 continue
917 if "text" in shape.align:
918 if shape.align["text"] == "left":
919 self._xml_empty_tag("a:pPr", [("algn", "l")])
920 if shape.align["text"] == "center":
921 self._xml_empty_tag("a:pPr", [("algn", "ctr")])
922 if shape.align["text"] == "right":
923 self._xml_empty_tag("a:pPr", [("algn", "r")])
925 self._xml_start_tag("a:r")
927 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
929 self._xml_data_element("a:t", line)
931 self._xml_end_tag("a:r")
932 self._xml_end_tag("a:p")
934 self._xml_end_tag("xdr:txBody")
936 def _write_font_run(self, font, style_attrs, latin_attrs, run_type) -> None:
937 # Write a:rPr or a:endParaRPr.
938 has_color = font.get("color") is not None
940 if latin_attrs or has_color:
941 self._xml_start_tag(run_type, style_attrs)
943 if has_color:
944 self._write_a_solid_fill(font["color"])
946 if latin_attrs:
947 self._write_a_latin(latin_attrs)
948 self._write_a_cs(latin_attrs)
950 self._xml_end_tag(run_type)
951 else:
952 self._xml_empty_tag(run_type, style_attrs)
954 def _write_style(self) -> None:
955 # Write the <xdr:style> element.
956 self._xml_start_tag("xdr:style")
958 # Write the a:lnRef element.
959 self._write_a_ln_ref()
961 # Write the a:fillRef element.
962 self._write_a_fill_ref()
964 # Write the a:effectRef element.
965 self._write_a_effect_ref()
967 # Write the a:fontRef element.
968 self._write_a_font_ref()
970 self._xml_end_tag("xdr:style")
972 def _write_a_ln_ref(self) -> None:
973 # Write the <a:lnRef> element.
974 attributes = [("idx", "0")]
976 self._xml_start_tag("a:lnRef", attributes)
978 # Write the a:scrgbClr element.
979 self._write_a_scrgb_clr()
981 self._xml_end_tag("a:lnRef")
983 def _write_a_fill_ref(self) -> None:
984 # Write the <a:fillRef> element.
985 attributes = [("idx", "0")]
987 self._xml_start_tag("a:fillRef", attributes)
989 # Write the a:scrgbClr element.
990 self._write_a_scrgb_clr()
992 self._xml_end_tag("a:fillRef")
994 def _write_a_effect_ref(self) -> None:
995 # Write the <a:effectRef> element.
996 attributes = [("idx", "0")]
998 self._xml_start_tag("a:effectRef", attributes)
1000 # Write the a:scrgbClr element.
1001 self._write_a_scrgb_clr()
1003 self._xml_end_tag("a:effectRef")
1005 def _write_a_scrgb_clr(self) -> None:
1006 # Write the <a:scrgbClr> element.
1008 attributes = [
1009 ("r", "0"),
1010 ("g", "0"),
1011 ("b", "0"),
1012 ]
1014 self._xml_empty_tag("a:scrgbClr", attributes)
1016 def _write_a_font_ref(self) -> None:
1017 # Write the <a:fontRef> element.
1018 attributes = [("idx", "minor")]
1020 self._xml_start_tag("a:fontRef", attributes)
1022 # Write the a:schemeClr element.
1023 self._write_a_scheme_clr("dk1")
1025 self._xml_end_tag("a:fontRef")
1027 def _write_a_scheme_clr(self, val) -> None:
1028 # Write the <a:schemeClr> element.
1029 attributes = [("val", val)]
1031 self._xml_empty_tag("a:schemeClr", attributes)
1033 def _write_a_shade(self, shade) -> None:
1034 # Write the <a:shade> element.
1035 attributes = [("val", shade)]
1037 self._xml_empty_tag("a:shade", attributes)
1039 def _write_a_prst_dash(self, val) -> None:
1040 # Write the <a:prstDash> element.
1042 attributes = [("val", val)]
1044 self._xml_empty_tag("a:prstDash", attributes)
1046 def _write_a_grad_fill(self, gradient) -> None:
1047 # Write the <a:gradFill> element.
1049 attributes = [("flip", "none"), ("rotWithShape", "1")]
1051 if gradient["type"] == "linear":
1052 attributes = []
1054 self._xml_start_tag("a:gradFill", attributes)
1056 # Write the a:gsLst element.
1057 self._write_a_gs_lst(gradient)
1059 if gradient["type"] == "linear":
1060 # Write the a:lin element.
1061 self._write_a_lin(gradient["angle"])
1062 else:
1063 # Write the a:path element.
1064 self._write_a_path(gradient["type"])
1066 # Write the a:tileRect element.
1067 self._write_a_tile_rect(gradient["type"])
1069 self._xml_end_tag("a:gradFill")
1071 def _write_a_gs_lst(self, gradient) -> None:
1072 # Write the <a:gsLst> element.
1073 positions = gradient["positions"]
1074 colors = gradient["colors"]
1076 self._xml_start_tag("a:gsLst")
1078 for i, color in enumerate(colors):
1079 pos = int(positions[i] * 1000)
1080 attributes = [("pos", pos)]
1081 self._xml_start_tag("a:gs", attributes)
1083 # Write the a:srgbClr element.
1084 self._write_a_srgb_clr(color)
1086 self._xml_end_tag("a:gs")
1088 self._xml_end_tag("a:gsLst")
1090 def _write_a_lin(self, angle) -> None:
1091 # Write the <a:lin> element.
1093 angle = int(60000 * angle)
1095 attributes = [
1096 ("ang", angle),
1097 ("scaled", "0"),
1098 ]
1100 self._xml_empty_tag("a:lin", attributes)
1102 def _write_a_path(self, gradient_type) -> None:
1103 # Write the <a:path> element.
1105 attributes = [("path", gradient_type)]
1107 self._xml_start_tag("a:path", attributes)
1109 # Write the a:fillToRect element.
1110 self._write_a_fill_to_rect(gradient_type)
1112 self._xml_end_tag("a:path")
1114 def _write_a_fill_to_rect(self, gradient_type) -> None:
1115 # Write the <a:fillToRect> element.
1117 if gradient_type == "shape":
1118 attributes = [
1119 ("l", "50000"),
1120 ("t", "50000"),
1121 ("r", "50000"),
1122 ("b", "50000"),
1123 ]
1124 else:
1125 attributes = [
1126 ("l", "100000"),
1127 ("t", "100000"),
1128 ]
1130 self._xml_empty_tag("a:fillToRect", attributes)
1132 def _write_a_tile_rect(self, gradient_type) -> None:
1133 # Write the <a:tileRect> element.
1135 if gradient_type == "shape":
1136 attributes = []
1137 else:
1138 attributes = [
1139 ("r", "-100000"),
1140 ("b", "-100000"),
1141 ]
1143 self._xml_empty_tag("a:tileRect", attributes)
1145 def _write_a_srgb_clr(self, color: Color) -> None:
1146 # Write the <a:srgbClr> element.
1147 attributes = [("val", color._rgb_hex_value())]
1149 self._xml_empty_tag("a:srgbClr", attributes)
1151 def _write_a_latin(self, attributes) -> None:
1152 # Write the <a:latin> element.
1153 self._xml_empty_tag("a:latin", attributes)
1155 def _write_a_cs(self, attributes) -> None:
1156 # Write the <a:latin> element.
1157 self._xml_empty_tag("a:cs", attributes)