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-2026, 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 for index, drawing in enumerate(self.drawings, 1):
95 # Write the xdr:twoCellAnchor element.
96 self._write_two_cell_anchor(index, drawing)
98 else:
99 # Write the xdr:absoluteAnchor element.
100 drawing = DrawingInfo()
101 drawing._rel_index = 1
102 self._write_absolute_anchor(1, drawing)
104 self._xml_end_tag("xdr:wsDr")
106 # Close the file.
107 self._xml_close()
109 def _add_drawing_object(self, drawing_object: DrawingInfo) -> None:
110 # Add a chart, image or shape sub object to the drawing.
111 self.drawings.append(drawing_object)
113 ###########################################################################
114 #
115 # XML methods.
116 #
117 ###########################################################################
119 def _write_drawing_workspace(self) -> None:
120 # Write the <xdr:wsDr> element.
121 schema = "http://schemas.openxmlformats.org/drawingml/"
122 xmlns_xdr = schema + "2006/spreadsheetDrawing"
123 xmlns_a = schema + "2006/main"
125 attributes = [
126 ("xmlns:xdr", xmlns_xdr),
127 ("xmlns:a", xmlns_a),
128 ]
130 self._xml_start_tag("xdr:wsDr", attributes)
132 def _write_two_cell_anchor(self, index: int, drawing: DrawingInfo) -> None:
133 # Write the <xdr:twoCellAnchor> element.
134 dimensions = drawing._dimensions
135 col_from = dimensions[0]
136 row_from = dimensions[1]
137 col_from_offset = dimensions[2]
138 row_from_offset = dimensions[3]
139 col_to = dimensions[4]
140 row_to = dimensions[5]
141 col_to_offset = dimensions[6]
142 row_to_offset = dimensions[7]
143 col_absolute = dimensions[8]
144 row_absolute = dimensions[9]
146 attributes = []
148 # Add attribute for positioning.
149 if drawing._anchor == 2:
150 attributes.append(("editAs", "oneCell"))
151 elif drawing._anchor == 3:
152 attributes.append(("editAs", "absolute"))
154 # Add editAs attribute for shapes.
155 if drawing._shape and drawing._shape.edit_as:
156 attributes.append(("editAs", drawing._shape.edit_as))
158 self._xml_start_tag("xdr:twoCellAnchor", attributes)
160 # Write the xdr:from element.
161 self._write_from(col_from, row_from, col_from_offset, row_from_offset)
163 # Write the xdr:from element.
164 self._write_to(col_to, row_to, col_to_offset, row_to_offset)
166 if drawing._drawing_type == DrawingTypes.CHART:
167 # Graphic frame.
168 # Write the xdr:graphicFrame element for charts.
169 self._write_graphic_frame(index, drawing)
170 elif drawing._drawing_type == DrawingTypes.IMAGE:
171 # Write the xdr:pic element.
172 self._write_pic(index, col_absolute, row_absolute, drawing)
173 else:
174 # Write the xdr:sp element for shapes.
175 self._write_sp(index, col_absolute, row_absolute, drawing)
177 # Write the xdr:clientData element.
178 self._write_client_data()
180 self._xml_end_tag("xdr:twoCellAnchor")
182 def _write_absolute_anchor(self, index: int, drawing: DrawingInfo) -> None:
183 self._xml_start_tag("xdr:absoluteAnchor")
184 # Write the <xdr:absoluteAnchor> element.
186 # Different coordinates for horizontal (= 0) and vertical (= 1).
187 if self.orientation == 0:
188 # Write the xdr:pos element.
189 self._write_pos(0, 0)
191 # Write the xdr:ext element.
192 self._write_xdr_ext(9308969, 6078325)
194 else:
195 # Write the xdr:pos element.
196 self._write_pos(0, -47625)
198 # Write the xdr:ext element.
199 self._write_xdr_ext(6162675, 6124575)
201 # Write the xdr:graphicFrame element.
202 self._write_graphic_frame(index, drawing)
204 # Write the xdr:clientData element.
205 self._write_client_data()
207 self._xml_end_tag("xdr:absoluteAnchor")
209 def _write_from(self, col: int, row: int, col_offset, row_offset) -> None:
210 # Write the <xdr:from> element.
211 self._xml_start_tag("xdr:from")
213 # Write the xdr:col element.
214 self._write_col(col)
216 # Write the xdr:colOff element.
217 self._write_col_off(col_offset)
219 # Write the xdr:row element.
220 self._write_row(row)
222 # Write the xdr:rowOff element.
223 self._write_row_off(row_offset)
225 self._xml_end_tag("xdr:from")
227 def _write_to(self, col: int, row: int, col_offset, row_offset) -> None:
228 # Write the <xdr:to> element.
229 self._xml_start_tag("xdr:to")
231 # Write the xdr:col element.
232 self._write_col(col)
234 # Write the xdr:colOff element.
235 self._write_col_off(col_offset)
237 # Write the xdr:row element.
238 self._write_row(row)
240 # Write the xdr:rowOff element.
241 self._write_row_off(row_offset)
243 self._xml_end_tag("xdr:to")
245 def _write_col(self, data) -> None:
246 # Write the <xdr:col> element.
247 self._xml_data_element("xdr:col", data)
249 def _write_col_off(self, data) -> None:
250 # Write the <xdr:colOff> element.
251 self._xml_data_element("xdr:colOff", data)
253 def _write_row(self, data) -> None:
254 # Write the <xdr:row> element.
255 self._xml_data_element("xdr:row", data)
257 def _write_row_off(self, data) -> None:
258 # Write the <xdr:rowOff> element.
259 self._xml_data_element("xdr:rowOff", data)
261 def _write_pos(self, x, y) -> None:
262 # Write the <xdr:pos> element.
264 attributes = [("x", x), ("y", y)]
266 self._xml_empty_tag("xdr:pos", attributes)
268 def _write_xdr_ext(self, cx, cy) -> None:
269 # Write the <xdr:ext> element.
271 attributes = [("cx", cx), ("cy", cy)]
273 self._xml_empty_tag("xdr:ext", attributes)
275 def _write_graphic_frame(self, index: int, drawing: DrawingInfo) -> None:
276 # Write the <xdr:graphicFrame> element.
277 attributes = [("macro", "")]
279 self._xml_start_tag("xdr:graphicFrame", attributes)
281 # Write the xdr:nvGraphicFramePr element.
282 self._write_nv_graphic_frame_pr(index, drawing)
284 # Write the xdr:xfrm element.
285 self._write_xfrm()
287 # Write the a:graphic element.
288 self._write_atag_graphic(drawing._rel_index)
290 self._xml_end_tag("xdr:graphicFrame")
292 def _write_nv_graphic_frame_pr(self, index: int, drawing: DrawingInfo) -> None:
293 # Write the <xdr:nvGraphicFramePr> element.
295 name = drawing._name
296 if not name:
297 name = "Chart " + str(index)
299 self._xml_start_tag("xdr:nvGraphicFramePr")
301 # Write the xdr:cNvPr element.
302 self._write_c_nv_pr(index + 1, drawing, name)
304 # Write the xdr:cNvGraphicFramePr element.
305 self._write_c_nv_graphic_frame_pr()
307 self._xml_end_tag("xdr:nvGraphicFramePr")
309 def _write_c_nv_pr(self, index: int, drawing: DrawingInfo, name: str) -> None:
310 # Write the <xdr:cNvPr> element.
311 attributes = [("id", index), ("name", name)]
313 # Add description attribute for images.
314 if drawing._description and not drawing._decorative:
315 attributes.append(("descr", drawing._description))
317 if drawing._url or drawing._decorative:
318 self._xml_start_tag("xdr:cNvPr", attributes)
320 if drawing._url:
321 self._write_a_hlink_click(drawing._url)
323 if drawing._decorative:
324 self._write_decorative()
326 self._xml_end_tag("xdr:cNvPr")
327 else:
328 self._xml_empty_tag("xdr:cNvPr", attributes)
330 def _write_decorative(self) -> None:
331 self._xml_start_tag("a:extLst")
333 self._write_uri_ext("{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}")
334 self._write_a16_creation_id()
335 self._xml_end_tag("a:ext")
337 self._write_uri_ext("{C183D7F6-B498-43B3-948B-1728B52AA6E4}")
338 self._write_adec_decorative()
339 self._xml_end_tag("a:ext")
341 self._xml_end_tag("a:extLst")
343 def _write_uri_ext(self, uri) -> None:
344 # Write the <a:ext> element.
345 attributes = [("uri", uri)]
347 self._xml_start_tag("a:ext", attributes)
349 def _write_adec_decorative(self) -> None:
350 # Write the <adec:decorative> element.
351 xmlns = "http://schemas.microsoft.com/office/drawing/2017/decorative"
352 val = "1"
354 attributes = [
355 ("xmlns:adec", xmlns),
356 ("val", val),
357 ]
359 self._xml_empty_tag("adec:decorative", attributes)
361 def _write_a16_creation_id(self) -> None:
362 # Write the <a16:creationId> element.
364 xmlns_a_16 = "http://schemas.microsoft.com/office/drawing/2014/main"
365 creation_id = "{00000000-0008-0000-0000-000002000000}"
367 attributes = [
368 ("xmlns:a16", xmlns_a_16),
369 ("id", creation_id),
370 ]
372 self._xml_empty_tag("a16:creationId", attributes)
374 def _write_a_hlink_click(self, url: Url) -> None:
375 # Write the <a:hlinkClick> element.
376 schema = "http://schemas.openxmlformats.org/officeDocument/"
377 xmlns_r = schema + "2006/relationships"
379 attributes = [
380 ("xmlns:r", xmlns_r),
381 ("r:id", "rId" + str(url._rel_index)),
382 ]
384 if url._tip:
385 attributes.append(("tooltip", url._tip))
387 self._xml_empty_tag("a:hlinkClick", attributes)
389 def _write_c_nv_graphic_frame_pr(self) -> None:
390 # Write the <xdr:cNvGraphicFramePr> element.
391 if self.embedded:
392 self._xml_empty_tag("xdr:cNvGraphicFramePr")
393 else:
394 self._xml_start_tag("xdr:cNvGraphicFramePr")
396 # Write the a:graphicFrameLocks element.
397 self._write_a_graphic_frame_locks()
399 self._xml_end_tag("xdr:cNvGraphicFramePr")
401 def _write_a_graphic_frame_locks(self) -> None:
402 # Write the <a:graphicFrameLocks> element.
403 attributes = [("noGrp", 1)]
405 self._xml_empty_tag("a:graphicFrameLocks", attributes)
407 def _write_xfrm(self) -> None:
408 # Write the <xdr:xfrm> element.
409 self._xml_start_tag("xdr:xfrm")
411 # Write the xfrmOffset element.
412 self._write_xfrm_offset()
414 # Write the xfrmOffset element.
415 self._write_xfrm_extension()
417 self._xml_end_tag("xdr:xfrm")
419 def _write_xfrm_offset(self) -> None:
420 # Write the <a:off> xfrm sub-element.
422 attributes = [
423 ("x", 0),
424 ("y", 0),
425 ]
427 self._xml_empty_tag("a:off", attributes)
429 def _write_xfrm_extension(self) -> None:
430 # Write the <a:ext> xfrm sub-element.
432 attributes = [
433 ("cx", 0),
434 ("cy", 0),
435 ]
437 self._xml_empty_tag("a:ext", attributes)
439 def _write_atag_graphic(self, index: int) -> None:
440 # Write the <a:graphic> element.
441 self._xml_start_tag("a:graphic")
443 # Write the a:graphicData element.
444 self._write_atag_graphic_data(index)
446 self._xml_end_tag("a:graphic")
448 def _write_atag_graphic_data(self, index: int) -> None:
449 # Write the <a:graphicData> element.
450 uri = "http://schemas.openxmlformats.org/drawingml/2006/chart"
452 attributes = [
453 (
454 "uri",
455 uri,
456 )
457 ]
459 self._xml_start_tag("a:graphicData", attributes)
461 # Write the c:chart element.
462 self._write_c_chart("rId" + str(index))
464 self._xml_end_tag("a:graphicData")
466 def _write_c_chart(self, r_id) -> None:
467 # Write the <c:chart> element.
469 schema = "http://schemas.openxmlformats.org/"
470 xmlns_c = schema + "drawingml/2006/chart"
471 xmlns_r = schema + "officeDocument/2006/relationships"
473 attributes = [
474 ("xmlns:c", xmlns_c),
475 ("xmlns:r", xmlns_r),
476 ("r:id", r_id),
477 ]
479 self._xml_empty_tag("c:chart", attributes)
481 def _write_client_data(self) -> None:
482 # Write the <xdr:clientData> element.
483 self._xml_empty_tag("xdr:clientData")
485 def _write_sp(
486 self,
487 index,
488 col_absolute,
489 row_absolute,
490 drawing: DrawingInfo,
491 ) -> None:
492 # Write the <xdr:sp> element.
494 if drawing._shape and drawing._shape.connect:
495 attributes = [("macro", "")]
496 self._xml_start_tag("xdr:cxnSp", attributes)
498 # Write the xdr:nvCxnSpPr element.
499 self._write_nv_cxn_sp_pr(drawing._shape)
501 # Write the xdr:spPr element.
502 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing)
504 self._xml_end_tag("xdr:cxnSp")
505 else:
506 # Add attribute for shapes.
507 attributes = [("macro", ""), ("textlink", drawing._shape.textlink)]
509 self._xml_start_tag("xdr:sp", attributes)
511 # Write the xdr:nvSpPr element.
512 self._write_nv_sp_pr(index, drawing)
514 # Write the xdr:spPr element.
515 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing)
517 # Write the xdr:style element.
518 self._write_style()
520 # Write the xdr:txBody element.
521 if drawing._shape.text is not None:
522 self._write_tx_body(drawing._shape)
524 self._xml_end_tag("xdr:sp")
526 def _write_nv_cxn_sp_pr(self, shape) -> None:
527 # Write the <xdr:nvCxnSpPr> element.
528 self._xml_start_tag("xdr:nvCxnSpPr")
530 self._xml_start_tag("xdr:cNvCxnSpPr")
532 attributes = [("noChangeShapeType", "1")]
533 self._xml_empty_tag("a:cxnSpLocks", attributes)
535 if shape.start:
536 attributes = [("id", shape.start), ("idx", shape.start_index)]
537 self._xml_empty_tag("a:stCxn", attributes)
539 if shape.end:
540 attributes = [("id", shape.end), ("idx", shape.end_index)]
541 self._xml_empty_tag("a:endCxn", attributes)
543 self._xml_end_tag("xdr:cNvCxnSpPr")
544 self._xml_end_tag("xdr:nvCxnSpPr")
546 def _write_nv_sp_pr(self, index: int, drawing: DrawingInfo) -> None:
547 # Write the <xdr:NvSpPr> element.
548 attributes = []
550 self._xml_start_tag("xdr:nvSpPr")
552 name = drawing._shape.name + " " + str(index)
554 self._write_c_nv_pr(index + 1, drawing, name)
556 if drawing._shape.name == "TextBox":
557 attributes = [("txBox", 1)]
559 self._xml_empty_tag("xdr:cNvSpPr", attributes)
561 self._xml_end_tag("xdr:nvSpPr")
563 def _write_pic(
564 self,
565 index: int,
566 col_absolute: int,
567 row_absolute: int,
568 drawing: DrawingInfo,
569 ) -> None:
570 # Write the <xdr:pic> element.
571 self._xml_start_tag("xdr:pic")
573 # Write the xdr:nvPicPr element.
574 self._write_nv_pic_pr(index, drawing)
575 # Write the xdr:blipFill element.
576 self._write_blip_fill(drawing._rel_index)
578 # Write the xdr:spPr element.
579 self._write_sp_pr(col_absolute, row_absolute, drawing)
581 self._xml_end_tag("xdr:pic")
583 def _write_nv_pic_pr(self, index: int, drawing: DrawingInfo) -> None:
584 # Write the <xdr:nvPicPr> element.
585 self._xml_start_tag("xdr:nvPicPr")
587 name = "Picture " + str(index)
589 # Write the xdr:cNvPr element.
590 self._write_c_nv_pr(index + 1, drawing, name)
592 # Write the xdr:cNvPicPr element.
593 self._write_c_nv_pic_pr()
595 self._xml_end_tag("xdr:nvPicPr")
597 def _write_c_nv_pic_pr(self) -> None:
598 # Write the <xdr:cNvPicPr> element.
599 self._xml_start_tag("xdr:cNvPicPr")
601 # Write the a:picLocks element.
602 self._write_a_pic_locks()
604 self._xml_end_tag("xdr:cNvPicPr")
606 def _write_a_pic_locks(self) -> None:
607 # Write the <a:picLocks> element.
608 attributes = [("noChangeAspect", 1)]
610 self._xml_empty_tag("a:picLocks", attributes)
612 def _write_blip_fill(self, index: int) -> None:
613 # Write the <xdr:blipFill> element.
614 self._xml_start_tag("xdr:blipFill")
616 # Write the a:blip element.
617 self._write_a_blip(index)
619 # Write the a:stretch element.
620 self._write_a_stretch()
622 self._xml_end_tag("xdr:blipFill")
624 def _write_a_blip(self, index: int) -> None:
625 # Write the <a:blip> element.
626 schema = "http://schemas.openxmlformats.org/officeDocument/"
627 xmlns_r = schema + "2006/relationships"
628 r_embed = "rId" + str(index)
630 attributes = [("xmlns:r", xmlns_r), ("r:embed", r_embed)]
632 self._xml_empty_tag("a:blip", attributes)
634 def _write_a_stretch(self) -> None:
635 # Write the <a:stretch> element.
636 self._xml_start_tag("a:stretch")
638 # Write the a:fillRect element.
639 self._write_a_fill_rect()
641 self._xml_end_tag("a:stretch")
643 def _write_a_fill_rect(self) -> None:
644 # Write the <a:fillRect> element.
645 self._xml_empty_tag("a:fillRect")
647 def _write_sp_pr(self, col_absolute, row_absolute, drawing: DrawingInfo) -> None:
648 # Write the <xdr:spPr> element, for charts.
650 self._xml_start_tag("xdr:spPr")
652 # Write the a:xfrm element.
653 self._write_a_xfrm(col_absolute, row_absolute, drawing._width, drawing._height)
655 # Write the a:prstGeom element.
656 self._write_a_prst_geom(drawing._shape)
658 self._xml_end_tag("xdr:spPr")
660 def _write_xdr_sp_pr(
661 self, col_absolute: int, row_absolute: int, drawing: DrawingInfo
662 ) -> None:
663 # Write the <xdr:spPr> element for shapes.
664 self._xml_start_tag("xdr:spPr")
666 # Write the a:xfrm element.
667 self._write_a_xfrm(
668 col_absolute, row_absolute, drawing._width, drawing._height, drawing._shape
669 )
671 # Write the a:prstGeom element.
672 shape = drawing._shape
673 self._write_a_prst_geom(shape)
675 if shape.fill:
676 if not shape.fill["defined"]:
677 # Write the a:solidFill element.
678 self._write_a_solid_fill_scheme("lt1")
679 elif "none" in shape.fill:
680 # Write the a:noFill element.
681 self._xml_empty_tag("a:noFill")
682 elif "color" in shape.fill:
683 # Write the a:solidFill element.
684 self._write_a_solid_fill(shape.fill["color"])
686 if shape.gradient:
687 # Write the a:gradFill element.
688 self._write_a_grad_fill(shape.gradient)
690 # Write the a:ln element.
691 self._write_a_ln(shape.line)
693 self._xml_end_tag("xdr:spPr")
695 def _write_a_xfrm(
696 self, col_absolute, row_absolute, width, height, shape=None
697 ) -> None:
698 # Write the <a:xfrm> element.
699 attributes = []
701 if shape:
702 if shape.rotation:
703 rotation = shape.rotation
704 rotation *= 60000
705 attributes.append(("rot", rotation))
707 if shape.flip_h:
708 attributes.append(("flipH", 1))
709 if shape.flip_v:
710 attributes.append(("flipV", 1))
712 self._xml_start_tag("a:xfrm", attributes)
714 # Write the a:off element.
715 self._write_a_off(col_absolute, row_absolute)
717 # Write the a:ext element.
718 self._write_a_ext(width, height)
720 self._xml_end_tag("a:xfrm")
722 def _write_a_off(self, x, y) -> None:
723 # Write the <a:off> element.
724 attributes = [
725 ("x", x),
726 ("y", y),
727 ]
729 self._xml_empty_tag("a:off", attributes)
731 def _write_a_ext(self, cx, cy) -> None:
732 # Write the <a:ext> element.
733 attributes = [
734 ("cx", cx),
735 ("cy", cy),
736 ]
738 self._xml_empty_tag("a:ext", attributes)
740 def _write_a_prst_geom(self, shape=None) -> None:
741 # Write the <a:prstGeom> element.
742 attributes = [("prst", "rect")]
744 self._xml_start_tag("a:prstGeom", attributes)
746 # Write the a:avLst element.
747 self._write_a_av_lst(shape)
749 self._xml_end_tag("a:prstGeom")
751 def _write_a_av_lst(self, shape=None) -> None:
752 # Write the <a:avLst> element.
753 adjustments = []
755 if shape and shape.adjustments:
756 adjustments = shape.adjustments
758 if adjustments:
759 self._xml_start_tag("a:avLst")
761 for i, adj in enumerate(adjustments, 1):
762 # Only connectors have multiple adjustments.
763 if shape.connect:
764 suffix = i
765 else:
766 suffix = ""
768 # Scale Adjustments: 100,000 = 100%.
769 adj_int = str(int(adj * 1000))
771 attributes = [("name", "adj" + suffix), ("fmla", "val" + adj_int)]
773 self._xml_empty_tag("a:gd", attributes)
775 self._xml_end_tag("a:avLst")
776 else:
777 self._xml_empty_tag("a:avLst")
779 def _write_a_solid_fill(self, color: Color) -> None:
780 # Write the <a:solidFill> element.
781 self._xml_start_tag("a:solidFill")
783 # Write the a:srgbClr element.
784 self._write_a_srgb_clr(color)
786 self._xml_end_tag("a:solidFill")
788 def _write_a_solid_fill_scheme(self, named_color, shade=None) -> None:
789 attributes = [("val", named_color)]
791 self._xml_start_tag("a:solidFill")
793 if shade:
794 self._xml_start_tag("a:schemeClr", attributes)
795 self._write_a_shade(shade)
796 self._xml_end_tag("a:schemeClr")
797 else:
798 self._xml_empty_tag("a:schemeClr", attributes)
800 self._xml_end_tag("a:solidFill")
802 def _write_a_ln(self, line) -> None:
803 # Write the <a:ln> element.
804 width = line.get("width", 0.75)
806 # Round width to nearest 0.25, like Excel.
807 width = int((width + 0.125) * 4) / 4.0
809 # Convert to internal units.
810 width = int(0.5 + (12700 * width))
812 attributes = [("w", width), ("cmpd", "sng")]
814 self._xml_start_tag("a:ln", attributes)
816 if "none" in line:
817 # Write the a:noFill element.
818 self._xml_empty_tag("a:noFill")
820 elif "color" in line:
821 # Write the a:solidFill element.
822 self._write_a_solid_fill(line["color"])
824 else:
825 # Write the a:solidFill element.
826 self._write_a_solid_fill_scheme("lt1", "50000")
828 # Write the line/dash type.
829 line_type = line.get("dash_type")
830 if line_type:
831 # Write the a:prstDash element.
832 self._write_a_prst_dash(line_type)
834 self._xml_end_tag("a:ln")
836 def _write_tx_body(self, shape) -> None:
837 # Write the <xdr:txBody> element.
838 attributes = []
840 if shape.text_rotation != 0:
841 if shape.text_rotation == 90:
842 attributes.append(("vert", "vert270"))
843 if shape.text_rotation == -90:
844 attributes.append(("vert", "vert"))
845 if shape.text_rotation == 270:
846 attributes.append(("vert", "wordArtVert"))
847 if shape.text_rotation == 271:
848 attributes.append(("vert", "eaVert"))
850 attributes.append(("wrap", "square"))
851 attributes.append(("rtlCol", "0"))
853 if not shape.align["defined"]:
854 attributes.append(("anchor", "t"))
855 else:
856 if "vertical" in shape.align:
857 align = shape.align["vertical"]
858 if align == "top":
859 attributes.append(("anchor", "t"))
860 elif align == "middle":
861 attributes.append(("anchor", "ctr"))
862 elif align == "bottom":
863 attributes.append(("anchor", "b"))
864 else:
865 attributes.append(("anchor", "t"))
867 if "horizontal" in shape.align:
868 align = shape.align["horizontal"]
869 if align == "center":
870 attributes.append(("anchorCtr", "1"))
871 else:
872 attributes.append(("anchorCtr", "0"))
874 self._xml_start_tag("xdr:txBody")
875 self._xml_empty_tag("a:bodyPr", attributes)
876 self._xml_empty_tag("a:lstStyle")
878 lines = shape.text.split("\n")
880 # Set the font attributes.
881 font = shape.font
882 # pylint: disable=protected-access
883 style_attrs = Shape._get_font_style_attributes(font)
884 latin_attrs = Shape._get_font_latin_attributes(font)
885 style_attrs.insert(0, ("lang", font["lang"]))
887 if shape.textlink != "":
888 attributes = [
889 ("id", "{B8ADDEFE-BF52-4FD4-8C5D-6B85EF6FF707}"),
890 ("type", "TxLink"),
891 ]
893 self._xml_start_tag("a:p")
894 self._xml_start_tag("a:fld", attributes)
896 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
898 self._xml_data_element("a:t", shape.text)
899 self._xml_end_tag("a:fld")
901 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
903 self._xml_end_tag("a:p")
904 else:
905 for line in lines:
906 self._xml_start_tag("a:p")
908 if line == "":
909 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr")
910 self._xml_end_tag("a:p")
911 continue
913 if "text" in shape.align:
914 if shape.align["text"] == "left":
915 self._xml_empty_tag("a:pPr", [("algn", "l")])
916 if shape.align["text"] == "center":
917 self._xml_empty_tag("a:pPr", [("algn", "ctr")])
918 if shape.align["text"] == "right":
919 self._xml_empty_tag("a:pPr", [("algn", "r")])
921 self._xml_start_tag("a:r")
923 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr")
925 self._xml_data_element("a:t", line)
927 self._xml_end_tag("a:r")
928 self._xml_end_tag("a:p")
930 self._xml_end_tag("xdr:txBody")
932 def _write_font_run(self, font, style_attrs, latin_attrs, run_type) -> None:
933 # Write a:rPr or a:endParaRPr.
934 has_color = font.get("color") is not None
936 if latin_attrs or has_color:
937 self._xml_start_tag(run_type, style_attrs)
939 if has_color:
940 self._write_a_solid_fill(font["color"])
942 if latin_attrs:
943 self._write_a_latin(latin_attrs)
944 self._write_a_cs(latin_attrs)
946 self._xml_end_tag(run_type)
947 else:
948 self._xml_empty_tag(run_type, style_attrs)
950 def _write_style(self) -> None:
951 # Write the <xdr:style> element.
952 self._xml_start_tag("xdr:style")
954 # Write the a:lnRef element.
955 self._write_a_ln_ref()
957 # Write the a:fillRef element.
958 self._write_a_fill_ref()
960 # Write the a:effectRef element.
961 self._write_a_effect_ref()
963 # Write the a:fontRef element.
964 self._write_a_font_ref()
966 self._xml_end_tag("xdr:style")
968 def _write_a_ln_ref(self) -> None:
969 # Write the <a:lnRef> element.
970 attributes = [("idx", "0")]
972 self._xml_start_tag("a:lnRef", attributes)
974 # Write the a:scrgbClr element.
975 self._write_a_scrgb_clr()
977 self._xml_end_tag("a:lnRef")
979 def _write_a_fill_ref(self) -> None:
980 # Write the <a:fillRef> element.
981 attributes = [("idx", "0")]
983 self._xml_start_tag("a:fillRef", attributes)
985 # Write the a:scrgbClr element.
986 self._write_a_scrgb_clr()
988 self._xml_end_tag("a:fillRef")
990 def _write_a_effect_ref(self) -> None:
991 # Write the <a:effectRef> element.
992 attributes = [("idx", "0")]
994 self._xml_start_tag("a:effectRef", attributes)
996 # Write the a:scrgbClr element.
997 self._write_a_scrgb_clr()
999 self._xml_end_tag("a:effectRef")
1001 def _write_a_scrgb_clr(self) -> None:
1002 # Write the <a:scrgbClr> element.
1004 attributes = [
1005 ("r", "0"),
1006 ("g", "0"),
1007 ("b", "0"),
1008 ]
1010 self._xml_empty_tag("a:scrgbClr", attributes)
1012 def _write_a_font_ref(self) -> None:
1013 # Write the <a:fontRef> element.
1014 attributes = [("idx", "minor")]
1016 self._xml_start_tag("a:fontRef", attributes)
1018 # Write the a:schemeClr element.
1019 self._write_a_scheme_clr("dk1")
1021 self._xml_end_tag("a:fontRef")
1023 def _write_a_scheme_clr(self, val) -> None:
1024 # Write the <a:schemeClr> element.
1025 attributes = [("val", val)]
1027 self._xml_empty_tag("a:schemeClr", attributes)
1029 def _write_a_shade(self, shade) -> None:
1030 # Write the <a:shade> element.
1031 attributes = [("val", shade)]
1033 self._xml_empty_tag("a:shade", attributes)
1035 def _write_a_prst_dash(self, val) -> None:
1036 # Write the <a:prstDash> element.
1038 attributes = [("val", val)]
1040 self._xml_empty_tag("a:prstDash", attributes)
1042 def _write_a_grad_fill(self, gradient) -> None:
1043 # Write the <a:gradFill> element.
1045 attributes = [("flip", "none"), ("rotWithShape", "1")]
1047 if gradient["type"] == "linear":
1048 attributes = []
1050 self._xml_start_tag("a:gradFill", attributes)
1052 # Write the a:gsLst element.
1053 self._write_a_gs_lst(gradient)
1055 if gradient["type"] == "linear":
1056 # Write the a:lin element.
1057 self._write_a_lin(gradient["angle"])
1058 else:
1059 # Write the a:path element.
1060 self._write_a_path(gradient["type"])
1062 # Write the a:tileRect element.
1063 self._write_a_tile_rect(gradient["type"])
1065 self._xml_end_tag("a:gradFill")
1067 def _write_a_gs_lst(self, gradient) -> None:
1068 # Write the <a:gsLst> element.
1069 positions = gradient["positions"]
1070 colors = gradient["colors"]
1072 self._xml_start_tag("a:gsLst")
1074 for i, color in enumerate(colors):
1075 pos = int(positions[i] * 1000)
1076 attributes = [("pos", pos)]
1077 self._xml_start_tag("a:gs", attributes)
1079 # Write the a:srgbClr element.
1080 self._write_a_srgb_clr(color)
1082 self._xml_end_tag("a:gs")
1084 self._xml_end_tag("a:gsLst")
1086 def _write_a_lin(self, angle) -> None:
1087 # Write the <a:lin> element.
1089 angle = int(60000 * angle)
1091 attributes = [
1092 ("ang", angle),
1093 ("scaled", "0"),
1094 ]
1096 self._xml_empty_tag("a:lin", attributes)
1098 def _write_a_path(self, gradient_type) -> None:
1099 # Write the <a:path> element.
1101 attributes = [("path", gradient_type)]
1103 self._xml_start_tag("a:path", attributes)
1105 # Write the a:fillToRect element.
1106 self._write_a_fill_to_rect(gradient_type)
1108 self._xml_end_tag("a:path")
1110 def _write_a_fill_to_rect(self, gradient_type) -> None:
1111 # Write the <a:fillToRect> element.
1113 if gradient_type == "shape":
1114 attributes = [
1115 ("l", "50000"),
1116 ("t", "50000"),
1117 ("r", "50000"),
1118 ("b", "50000"),
1119 ]
1120 else:
1121 attributes = [
1122 ("l", "100000"),
1123 ("t", "100000"),
1124 ]
1126 self._xml_empty_tag("a:fillToRect", attributes)
1128 def _write_a_tile_rect(self, gradient_type) -> None:
1129 # Write the <a:tileRect> element.
1131 if gradient_type == "shape":
1132 attributes = []
1133 else:
1134 attributes = [
1135 ("r", "-100000"),
1136 ("b", "-100000"),
1137 ]
1139 self._xml_empty_tag("a:tileRect", attributes)
1141 def _write_a_srgb_clr(self, color: Color) -> None:
1142 # Write the <a:srgbClr> element.
1143 attributes = [("val", color._rgb_hex_value())]
1145 self._xml_empty_tag("a:srgbClr", attributes)
1147 def _write_a_latin(self, attributes) -> None:
1148 # Write the <a:latin> element.
1149 self._xml_empty_tag("a:latin", attributes)
1151 def _write_a_cs(self, attributes) -> None:
1152 # Write the <a:latin> element.
1153 self._xml_empty_tag("a:cs", attributes)