Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/xlsxwriter/vml.py: 15%
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# Vml - A class for writing the Excel XLSX Vml file.
4#
5# SPDX-License-Identifier: BSD-2-Clause
6#
7# Copyright (c) 2013-2026, John McNamara, jmcnamara@cpan.org
8#
10from typing import Optional
12from xlsxwriter import xmlwriter
14# Package imports.
15from xlsxwriter.comments import CommentType
16from xlsxwriter.image import Image
19###########################################################################
20#
21# A button type class.
22#
23###########################################################################
24class ButtonType:
25 """
26 A class to represent a button in an Excel worksheet.
28 """
30 def __init__(
31 self,
32 row: int,
33 col: int,
34 height: int,
35 width: int,
36 button_number: int,
37 options: Optional[dict] = None,
38 ) -> None:
39 """
40 Initialize a ButtonType instance.
42 Args:
43 row (int): The row number of the button.
44 col (int): The column number of the button.
45 height (int): The height of the button.
46 width (int): The width of the button.
47 button_number (int): The button number.
48 options (dict): Additional options for the button.
49 """
50 self.row = row
51 self.col = col
52 self.width = width
53 self.height = height
55 self.macro = f"[0]!Button{button_number}_Click"
56 self.caption = f"Button {button_number}"
57 self.description = None
59 self.x_scale = 1
60 self.y_scale = 1
61 self.x_offset = 0
62 self.y_offset = 0
64 self.vertices = []
66 # Set any user supplied options.
67 self._set_user_options(options)
69 def _set_user_options(self, options=None) -> None:
70 """
71 This method handles the additional optional parameters to
72 ``insert_button()``.
73 """
74 if options is None:
75 return
77 # Overwrite the defaults with any user supplied values. Incorrect or
78 # misspelled parameters are silently ignored.
79 self.width = options.get("width", self.width)
80 self.height = options.get("height", self.height)
81 self.caption = options.get("caption", self.caption)
82 self.x_offset = options.get("x_offset", self.x_offset)
83 self.y_offset = options.get("y_offset", self.y_offset)
84 self.description = options.get("description", self.description)
86 # Set the macro name.
87 if options.get("macro"):
88 self.macro = "[0]!" + options["macro"]
90 # Scale the size of the button box if required.
91 if options.get("x_scale"):
92 self.width = self.width * options["x_scale"]
94 if options.get("y_scale"):
95 self.height = self.height * options["y_scale"]
97 # Round the dimensions to the nearest pixel.
98 self.width = int(0.5 + self.width)
99 self.height = int(0.5 + self.height)
102###########################################################################
103#
104# The file writer class for the Excel XLSX VML file.
105#
106###########################################################################
109class Vml(xmlwriter.XMLwriter):
110 """
111 A class for writing the Excel XLSX Vml file.
114 """
116 ###########################################################################
117 #
118 # Private API.
119 #
120 ###########################################################################
121 def _assemble_xml_file(
122 self,
123 data_id,
124 vml_shape_id,
125 comments_data=None,
126 buttons_data=None,
127 header_images=None,
128 ) -> None:
129 # Assemble and write the XML file.
130 z_index = 1
132 self._write_xml_namespace()
134 # Write the o:shapelayout element.
135 self._write_shapelayout(data_id)
137 if buttons_data:
138 # Write the v:shapetype element.
139 self._write_button_shapetype()
141 for button in buttons_data:
142 # Write the v:shape element.
143 vml_shape_id += 1
144 self._write_button_shape(vml_shape_id, z_index, button)
145 z_index += 1
147 if comments_data:
148 # Write the v:shapetype element.
149 self._write_comment_shapetype()
151 for comment in comments_data:
152 # Write the v:shape element.
153 vml_shape_id += 1
154 self._write_comment_shape(vml_shape_id, z_index, comment)
155 z_index += 1
157 if header_images:
158 # Write the v:shapetype element.
159 self._write_image_shapetype()
161 index = 1
162 for image in header_images:
163 # Write the v:shape element.
164 vml_shape_id += 1
165 self._write_image_shape(vml_shape_id, index, image)
166 index += 1
168 self._xml_end_tag("xml")
170 # Close the XML writer filehandle.
171 self._xml_close()
173 def _pixels_to_points(self, vertices):
174 # Convert comment vertices from pixels to points.
176 left, top, width, height = vertices[8:12]
178 # Scale to pixels.
179 left *= 0.75
180 top *= 0.75
181 width *= 0.75
182 height *= 0.75
184 return left, top, width, height
186 ###########################################################################
187 #
188 # XML methods.
189 #
190 ###########################################################################
191 def _write_xml_namespace(self) -> None:
192 # Write the <xml> element. This is the root element of VML.
193 schema = "urn:schemas-microsoft-com:"
194 xmlns = schema + "vml"
195 xmlns_o = schema + "office:office"
196 xmlns_x = schema + "office:excel"
198 attributes = [
199 ("xmlns:v", xmlns),
200 ("xmlns:o", xmlns_o),
201 ("xmlns:x", xmlns_x),
202 ]
204 self._xml_start_tag("xml", attributes)
206 def _write_shapelayout(self, data_id) -> None:
207 # Write the <o:shapelayout> element.
208 attributes = [("v:ext", "edit")]
210 self._xml_start_tag("o:shapelayout", attributes)
212 # Write the o:idmap element.
213 self._write_idmap(data_id)
215 self._xml_end_tag("o:shapelayout")
217 def _write_idmap(self, data_id) -> None:
218 # Write the <o:idmap> element.
219 attributes = [
220 ("v:ext", "edit"),
221 ("data", data_id),
222 ]
224 self._xml_empty_tag("o:idmap", attributes)
226 def _write_comment_shapetype(self) -> None:
227 # Write the <v:shapetype> element.
228 shape_id = "_x0000_t202"
229 coordsize = "21600,21600"
230 spt = 202
231 path = "m,l,21600r21600,l21600,xe"
233 attributes = [
234 ("id", shape_id),
235 ("coordsize", coordsize),
236 ("o:spt", spt),
237 ("path", path),
238 ]
240 self._xml_start_tag("v:shapetype", attributes)
242 # Write the v:stroke element.
243 self._write_stroke()
245 # Write the v:path element.
246 self._write_comment_path("t", "rect")
248 self._xml_end_tag("v:shapetype")
250 def _write_button_shapetype(self) -> None:
251 # Write the <v:shapetype> element.
252 shape_id = "_x0000_t201"
253 coordsize = "21600,21600"
254 spt = 201
255 path = "m,l,21600r21600,l21600,xe"
257 attributes = [
258 ("id", shape_id),
259 ("coordsize", coordsize),
260 ("o:spt", spt),
261 ("path", path),
262 ]
264 self._xml_start_tag("v:shapetype", attributes)
266 # Write the v:stroke element.
267 self._write_stroke()
269 # Write the v:path element.
270 self._write_button_path()
272 # Write the o:lock element.
273 self._write_shapetype_lock()
275 self._xml_end_tag("v:shapetype")
277 def _write_image_shapetype(self) -> None:
278 # Write the <v:shapetype> element.
279 shape_id = "_x0000_t75"
280 coordsize = "21600,21600"
281 spt = 75
282 o_preferrelative = "t"
283 path = "m@4@5l@4@11@9@11@9@5xe"
284 filled = "f"
285 stroked = "f"
287 attributes = [
288 ("id", shape_id),
289 ("coordsize", coordsize),
290 ("o:spt", spt),
291 ("o:preferrelative", o_preferrelative),
292 ("path", path),
293 ("filled", filled),
294 ("stroked", stroked),
295 ]
297 self._xml_start_tag("v:shapetype", attributes)
299 # Write the v:stroke element.
300 self._write_stroke()
302 # Write the v:formulas element.
303 self._write_formulas()
305 # Write the v:path element.
306 self._write_image_path()
308 # Write the o:lock element.
309 self._write_aspect_ratio_lock()
311 self._xml_end_tag("v:shapetype")
313 def _write_stroke(self) -> None:
314 # Write the <v:stroke> element.
315 joinstyle = "miter"
317 attributes = [("joinstyle", joinstyle)]
319 self._xml_empty_tag("v:stroke", attributes)
321 def _write_comment_path(self, gradientshapeok, connecttype) -> None:
322 # Write the <v:path> element.
323 attributes = []
325 if gradientshapeok:
326 attributes.append(("gradientshapeok", "t"))
328 attributes.append(("o:connecttype", connecttype))
330 self._xml_empty_tag("v:path", attributes)
332 def _write_button_path(self) -> None:
333 # Write the <v:path> element.
334 shadowok = "f"
335 extrusionok = "f"
336 strokeok = "f"
337 fillok = "f"
338 connecttype = "rect"
340 attributes = [
341 ("shadowok", shadowok),
342 ("o:extrusionok", extrusionok),
343 ("strokeok", strokeok),
344 ("fillok", fillok),
345 ("o:connecttype", connecttype),
346 ]
348 self._xml_empty_tag("v:path", attributes)
350 def _write_image_path(self) -> None:
351 # Write the <v:path> element.
352 extrusionok = "f"
353 gradientshapeok = "t"
354 connecttype = "rect"
356 attributes = [
357 ("o:extrusionok", extrusionok),
358 ("gradientshapeok", gradientshapeok),
359 ("o:connecttype", connecttype),
360 ]
362 self._xml_empty_tag("v:path", attributes)
364 def _write_shapetype_lock(self) -> None:
365 # Write the <o:lock> element.
366 ext = "edit"
367 shapetype = "t"
369 attributes = [
370 ("v:ext", ext),
371 ("shapetype", shapetype),
372 ]
374 self._xml_empty_tag("o:lock", attributes)
376 def _write_rotation_lock(self) -> None:
377 # Write the <o:lock> element.
378 ext = "edit"
379 rotation = "t"
381 attributes = [
382 ("v:ext", ext),
383 ("rotation", rotation),
384 ]
386 self._xml_empty_tag("o:lock", attributes)
388 def _write_aspect_ratio_lock(self) -> None:
389 # Write the <o:lock> element.
390 ext = "edit"
391 aspectratio = "t"
393 attributes = [
394 ("v:ext", ext),
395 ("aspectratio", aspectratio),
396 ]
398 self._xml_empty_tag("o:lock", attributes)
400 def _write_comment_shape(self, shape_id, z_index, comment: CommentType) -> None:
401 # Write the <v:shape> element.
402 shape_type = "#_x0000_t202"
403 insetmode = "auto"
404 visibility = "hidden"
406 # Set the shape index.
407 shape_id = "_x0000_s" + str(shape_id)
409 left, top, width, height = self._pixels_to_points(comment.vertices)
411 # Set the visibility.
412 if comment.is_visible:
413 visibility = "visible"
415 style = (
416 f"position:absolute;"
417 f"margin-left:{left:.15g}pt;"
418 f"margin-top:{top:.15g}pt;"
419 f"width:{width:.15g}pt;"
420 f"height:{height:.15g}pt;"
421 f"z-index:{z_index};"
422 f"visibility:{visibility}"
423 )
425 attributes = [
426 ("id", shape_id),
427 ("type", shape_type),
428 ("style", style),
429 ("fillcolor", comment.color._vml_rgb_hex_value()),
430 ("o:insetmode", insetmode),
431 ]
433 self._xml_start_tag("v:shape", attributes)
435 # Write the v:fill element.
436 self._write_comment_fill()
438 # Write the v:shadow element.
439 self._write_shadow()
441 # Write the v:path element.
442 self._write_comment_path(None, "none")
444 # Write the v:textbox element.
445 self._write_comment_textbox()
447 # Write the x:ClientData element.
448 self._write_comment_client_data(comment)
450 self._xml_end_tag("v:shape")
452 def _write_button_shape(self, shape_id, z_index, button: ButtonType) -> None:
453 # Write the <v:shape> element.
454 shape_type = "#_x0000_t201"
456 # Set the shape index.
457 shape_id = "_x0000_s" + str(shape_id)
459 left, top, width, height = self._pixels_to_points(button.vertices)
461 style = (
462 f"position:absolute;"
463 f"margin-left:{left:.15g}pt;"
464 f"margin-top:{top:.15g}pt;"
465 f"width:{width:.15g}pt;"
466 f"height:{height:.15g}pt;"
467 f"z-index:{z_index};"
468 f"mso-wrap-style:tight"
469 )
471 attributes = [
472 ("id", shape_id),
473 ("type", shape_type),
474 ]
476 if button.description is not None:
477 attributes.append(("alt", button.description))
479 attributes.append(("style", style))
480 attributes.append(("o:button", "t"))
481 attributes.append(("fillcolor", "buttonFace [67]"))
482 attributes.append(("strokecolor", "windowText [64]"))
483 attributes.append(("o:insetmode", "auto"))
485 self._xml_start_tag("v:shape", attributes)
487 # Write the v:fill element.
488 self._write_button_fill()
490 # Write the o:lock element.
491 self._write_rotation_lock()
493 # Write the v:textbox element.
494 self._write_button_textbox(button)
496 # Write the x:ClientData element.
497 self._write_button_client_data(button)
499 self._xml_end_tag("v:shape")
501 def _write_image_shape(self, shape_id, z_index, image: Image) -> None:
502 # Write the <v:shape> element.
503 shape_type = "#_x0000_t75"
505 # Set the shape index.
506 shape_id = "_x0000_s" + str(shape_id)
508 # Get the image parameters
509 name = image.image_name
510 width = image._width
511 x_dpi = image._x_dpi
512 y_dpi = image._y_dpi
513 height = image._height
514 ref_id = image._ref_id
515 position = image._header_position
517 # Scale the height/width by the resolution, relative to 72dpi.
518 width = width * 72.0 / x_dpi
519 height = height * 72.0 / y_dpi
521 # Excel uses a rounding based around 72 and 96 dpi.
522 width = 72.0 / 96 * int(width * 96.0 / 72 + 0.25)
523 height = 72.0 / 96 * int(height * 96.0 / 72 + 0.25)
525 style = (
526 f"position:absolute;"
527 f"margin-left:0;"
528 f"margin-top:0;"
529 f"width:{width:.15g}pt;"
530 f"height:{height:.15g}pt;"
531 f"z-index:{z_index}"
532 )
534 attributes = [
535 ("id", position),
536 ("o:spid", shape_id),
537 ("type", shape_type),
538 ("style", style),
539 ]
541 self._xml_start_tag("v:shape", attributes)
543 # Write the v:imagedata element.
544 self._write_imagedata(ref_id, name)
546 # Write the o:lock element.
547 self._write_rotation_lock()
549 self._xml_end_tag("v:shape")
551 def _write_comment_fill(self) -> None:
552 # Write the <v:fill> element.
553 color_2 = "#ffffe1"
555 attributes = [("color2", color_2)]
557 self._xml_empty_tag("v:fill", attributes)
559 def _write_button_fill(self) -> None:
560 # Write the <v:fill> element.
561 color_2 = "buttonFace [67]"
562 detectmouseclick = "t"
564 attributes = [
565 ("color2", color_2),
566 ("o:detectmouseclick", detectmouseclick),
567 ]
569 self._xml_empty_tag("v:fill", attributes)
571 def _write_shadow(self) -> None:
572 # Write the <v:shadow> element.
573 on = "t"
574 color = "black"
575 obscured = "t"
577 attributes = [
578 ("on", on),
579 ("color", color),
580 ("obscured", obscured),
581 ]
583 self._xml_empty_tag("v:shadow", attributes)
585 def _write_comment_textbox(self) -> None:
586 # Write the <v:textbox> element.
587 style = "mso-direction-alt:auto"
589 attributes = [("style", style)]
591 self._xml_start_tag("v:textbox", attributes)
593 # Write the div element.
594 self._write_div("left")
596 self._xml_end_tag("v:textbox")
598 def _write_button_textbox(self, button: ButtonType) -> None:
599 # Write the <v:textbox> element.
600 style = "mso-direction-alt:auto"
602 attributes = [("style", style), ("o:singleclick", "f")]
604 self._xml_start_tag("v:textbox", attributes)
606 # Write the div element.
607 self._write_div("center", button.caption)
609 self._xml_end_tag("v:textbox")
611 def _write_div(self, align: str, caption: Optional[str] = None) -> None:
612 # Write the <div> element.
614 style = "text-align:" + align
616 attributes = [("style", style)]
618 self._xml_start_tag("div", attributes)
620 if caption:
621 self._write_button_font(caption)
623 self._xml_end_tag("div")
625 def _write_button_font(self, caption: str) -> None:
626 # Write the <font> element.
627 face = "Calibri"
628 size = 220
629 color = "#000000"
631 attributes = [
632 ("face", face),
633 ("size", size),
634 ("color", color),
635 ]
637 self._xml_data_element("font", caption, attributes)
639 def _write_comment_client_data(self, comment: CommentType) -> None:
640 # Write the <x:ClientData> element.
641 object_type = "Note"
643 attributes = [("ObjectType", object_type)]
645 self._xml_start_tag("x:ClientData", attributes)
647 # Write the x:MoveWithCells element.
648 self._write_move_with_cells()
650 # Write the x:SizeWithCells element.
651 self._write_size_with_cells()
653 # Write the x:Anchor element.
654 self._write_anchor(comment.vertices)
656 # Write the x:AutoFill element.
657 self._write_auto_fill()
659 # Write the x:Row element.
660 self._write_row(comment.row)
662 # Write the x:Column element.
663 self._write_column(comment.col)
665 # Write the x:Visible element.
666 if comment.is_visible:
667 self._write_visible()
669 self._xml_end_tag("x:ClientData")
671 def _write_button_client_data(self, button) -> None:
672 # Write the <x:ClientData> element.
673 object_type = "Button"
675 attributes = [("ObjectType", object_type)]
677 self._xml_start_tag("x:ClientData", attributes)
679 # Write the x:Anchor element.
680 self._write_anchor(button.vertices)
682 # Write the x:PrintObject element.
683 self._write_print_object()
685 # Write the x:AutoFill element.
686 self._write_auto_fill()
688 # Write the x:FmlaMacro element.
689 self._write_fmla_macro(button.macro)
691 # Write the x:TextHAlign element.
692 self._write_text_halign()
694 # Write the x:TextVAlign element.
695 self._write_text_valign()
697 self._xml_end_tag("x:ClientData")
699 def _write_move_with_cells(self) -> None:
700 # Write the <x:MoveWithCells> element.
701 self._xml_empty_tag("x:MoveWithCells")
703 def _write_size_with_cells(self) -> None:
704 # Write the <x:SizeWithCells> element.
705 self._xml_empty_tag("x:SizeWithCells")
707 def _write_visible(self) -> None:
708 # Write the <x:Visible> element.
709 self._xml_empty_tag("x:Visible")
711 def _write_anchor(self, vertices) -> None:
712 # Write the <x:Anchor> element.
713 col_start, row_start, x1, y1, col_end, row_end, x2, y2 = vertices[:8]
715 strings = [col_start, x1, row_start, y1, col_end, x2, row_end, y2]
716 strings = [str(i) for i in strings]
718 data = ", ".join(strings)
720 self._xml_data_element("x:Anchor", data)
722 def _write_auto_fill(self) -> None:
723 # Write the <x:AutoFill> element.
724 data = "False"
726 self._xml_data_element("x:AutoFill", data)
728 def _write_row(self, data) -> None:
729 # Write the <x:Row> element.
730 self._xml_data_element("x:Row", data)
732 def _write_column(self, data) -> None:
733 # Write the <x:Column> element.
734 self._xml_data_element("x:Column", data)
736 def _write_print_object(self) -> None:
737 # Write the <x:PrintObject> element.
738 self._xml_data_element("x:PrintObject", "False")
740 def _write_text_halign(self) -> None:
741 # Write the <x:TextHAlign> element.
742 self._xml_data_element("x:TextHAlign", "Center")
744 def _write_text_valign(self) -> None:
745 # Write the <x:TextVAlign> element.
746 self._xml_data_element("x:TextVAlign", "Center")
748 def _write_fmla_macro(self, data) -> None:
749 # Write the <x:FmlaMacro> element.
750 self._xml_data_element("x:FmlaMacro", data)
752 def _write_imagedata(self, ref_id, o_title) -> None:
753 # Write the <v:imagedata> element.
754 attributes = [
755 ("o:relid", "rId" + str(ref_id)),
756 ("o:title", o_title),
757 ]
759 self._xml_empty_tag("v:imagedata", attributes)
761 def _write_formulas(self) -> None:
762 # Write the <v:formulas> element.
763 self._xml_start_tag("v:formulas")
765 # Write the v:f elements.
766 self._write_formula("if lineDrawn pixelLineWidth 0")
767 self._write_formula("sum @0 1 0")
768 self._write_formula("sum 0 0 @1")
769 self._write_formula("prod @2 1 2")
770 self._write_formula("prod @3 21600 pixelWidth")
771 self._write_formula("prod @3 21600 pixelHeight")
772 self._write_formula("sum @0 0 1")
773 self._write_formula("prod @6 1 2")
774 self._write_formula("prod @7 21600 pixelWidth")
775 self._write_formula("sum @8 21600 0")
776 self._write_formula("prod @7 21600 pixelHeight")
777 self._write_formula("sum @10 21600 0")
779 self._xml_end_tag("v:formulas")
781 def _write_formula(self, eqn) -> None:
782 # Write the <v:f> element.
783 attributes = [("eqn", eqn)]
785 self._xml_empty_tag("v:f", attributes)