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-2025, John McNamara, jmcnamara@cpan.org
8#
10# Package imports.
11from xlsxwriter.comments import CommentType
12from xlsxwriter.image import Image
14from . import xmlwriter
17###########################################################################
18#
19# A button type class.
20#
21###########################################################################
22class ButtonType:
23 """
24 A class to represent a button in an Excel worksheet.
26 """
28 def __init__(
29 self,
30 row: int,
31 col: int,
32 height: int,
33 width: int,
34 button_number: int,
35 options: dict = None,
36 ):
37 """
38 Initialize a ButtonType instance.
40 Args:
41 row (int): The row number of the button.
42 col (int): The column number of the button.
43 height (int): The height of the button.
44 width (int): The width of the button.
45 button_number (int): The button number.
46 options (dict): Additional options for the button.
47 """
48 self.row = row
49 self.col = col
50 self.width = width
51 self.height = height
53 self.macro = f"[0]!Button{button_number}_Click"
54 self.caption = f"Button {button_number}"
55 self.description = None
57 self.x_scale = 1
58 self.y_scale = 1
59 self.x_offset = 0
60 self.y_offset = 0
62 self.vertices = []
64 # Set any user supplied options.
65 self._set_user_options(options)
67 def _set_user_options(self, options=None):
68 """
69 This method handles the additional optional parameters to
70 ``insert_button()``.
71 """
72 if options is None:
73 return
75 # Overwrite the defaults with any user supplied values. Incorrect or
76 # misspelled parameters are silently ignored.
77 self.width = options.get("width", self.width)
78 self.height = options.get("height", self.height)
79 self.caption = options.get("caption", self.caption)
80 self.x_offset = options.get("x_offset", self.x_offset)
81 self.y_offset = options.get("y_offset", self.y_offset)
82 self.description = options.get("description", self.description)
84 # Set the macro name.
85 if options.get("macro"):
86 self.macro = "[0]!" + options["macro"]
88 # Scale the size of the button box if required.
89 if options.get("x_scale"):
90 self.width = self.width * options["x_scale"]
92 if options.get("y_scale"):
93 self.height = self.height * options["y_scale"]
95 # Round the dimensions to the nearest pixel.
96 self.width = int(0.5 + self.width)
97 self.height = int(0.5 + self.height)
100###########################################################################
101#
102# The file writer class for the Excel XLSX VML file.
103#
104###########################################################################
107class Vml(xmlwriter.XMLwriter):
108 """
109 A class for writing the Excel XLSX Vml file.
112 """
114 ###########################################################################
115 #
116 # Private API.
117 #
118 ###########################################################################
119 def _assemble_xml_file(
120 self,
121 data_id,
122 vml_shape_id,
123 comments_data=None,
124 buttons_data=None,
125 header_images=None,
126 ):
127 # Assemble and write the XML file.
128 z_index = 1
130 self._write_xml_namespace()
132 # Write the o:shapelayout element.
133 self._write_shapelayout(data_id)
135 if buttons_data:
136 # Write the v:shapetype element.
137 self._write_button_shapetype()
139 for button in buttons_data:
140 # Write the v:shape element.
141 vml_shape_id += 1
142 self._write_button_shape(vml_shape_id, z_index, button)
143 z_index += 1
145 if comments_data:
146 # Write the v:shapetype element.
147 self._write_comment_shapetype()
149 for comment in comments_data:
150 # Write the v:shape element.
151 vml_shape_id += 1
152 self._write_comment_shape(vml_shape_id, z_index, comment)
153 z_index += 1
155 if header_images:
156 # Write the v:shapetype element.
157 self._write_image_shapetype()
159 index = 1
160 for image in header_images:
161 # Write the v:shape element.
162 vml_shape_id += 1
163 self._write_image_shape(vml_shape_id, index, image)
164 index += 1
166 self._xml_end_tag("xml")
168 # Close the XML writer filehandle.
169 self._xml_close()
171 def _pixels_to_points(self, vertices):
172 # Convert comment vertices from pixels to points.
174 left, top, width, height = vertices[8:12]
176 # Scale to pixels.
177 left *= 0.75
178 top *= 0.75
179 width *= 0.75
180 height *= 0.75
182 return left, top, width, height
184 ###########################################################################
185 #
186 # XML methods.
187 #
188 ###########################################################################
189 def _write_xml_namespace(self):
190 # Write the <xml> element. This is the root element of VML.
191 schema = "urn:schemas-microsoft-com:"
192 xmlns = schema + "vml"
193 xmlns_o = schema + "office:office"
194 xmlns_x = schema + "office:excel"
196 attributes = [
197 ("xmlns:v", xmlns),
198 ("xmlns:o", xmlns_o),
199 ("xmlns:x", xmlns_x),
200 ]
202 self._xml_start_tag("xml", attributes)
204 def _write_shapelayout(self, data_id):
205 # Write the <o:shapelayout> element.
206 attributes = [("v:ext", "edit")]
208 self._xml_start_tag("o:shapelayout", attributes)
210 # Write the o:idmap element.
211 self._write_idmap(data_id)
213 self._xml_end_tag("o:shapelayout")
215 def _write_idmap(self, data_id):
216 # Write the <o:idmap> element.
217 attributes = [
218 ("v:ext", "edit"),
219 ("data", data_id),
220 ]
222 self._xml_empty_tag("o:idmap", attributes)
224 def _write_comment_shapetype(self):
225 # Write the <v:shapetype> element.
226 shape_id = "_x0000_t202"
227 coordsize = "21600,21600"
228 spt = 202
229 path = "m,l,21600r21600,l21600,xe"
231 attributes = [
232 ("id", shape_id),
233 ("coordsize", coordsize),
234 ("o:spt", spt),
235 ("path", path),
236 ]
238 self._xml_start_tag("v:shapetype", attributes)
240 # Write the v:stroke element.
241 self._write_stroke()
243 # Write the v:path element.
244 self._write_comment_path("t", "rect")
246 self._xml_end_tag("v:shapetype")
248 def _write_button_shapetype(self):
249 # Write the <v:shapetype> element.
250 shape_id = "_x0000_t201"
251 coordsize = "21600,21600"
252 spt = 201
253 path = "m,l,21600r21600,l21600,xe"
255 attributes = [
256 ("id", shape_id),
257 ("coordsize", coordsize),
258 ("o:spt", spt),
259 ("path", path),
260 ]
262 self._xml_start_tag("v:shapetype", attributes)
264 # Write the v:stroke element.
265 self._write_stroke()
267 # Write the v:path element.
268 self._write_button_path()
270 # Write the o:lock element.
271 self._write_shapetype_lock()
273 self._xml_end_tag("v:shapetype")
275 def _write_image_shapetype(self):
276 # Write the <v:shapetype> element.
277 shape_id = "_x0000_t75"
278 coordsize = "21600,21600"
279 spt = 75
280 o_preferrelative = "t"
281 path = "m@4@5l@4@11@9@11@9@5xe"
282 filled = "f"
283 stroked = "f"
285 attributes = [
286 ("id", shape_id),
287 ("coordsize", coordsize),
288 ("o:spt", spt),
289 ("o:preferrelative", o_preferrelative),
290 ("path", path),
291 ("filled", filled),
292 ("stroked", stroked),
293 ]
295 self._xml_start_tag("v:shapetype", attributes)
297 # Write the v:stroke element.
298 self._write_stroke()
300 # Write the v:formulas element.
301 self._write_formulas()
303 # Write the v:path element.
304 self._write_image_path()
306 # Write the o:lock element.
307 self._write_aspect_ratio_lock()
309 self._xml_end_tag("v:shapetype")
311 def _write_stroke(self):
312 # Write the <v:stroke> element.
313 joinstyle = "miter"
315 attributes = [("joinstyle", joinstyle)]
317 self._xml_empty_tag("v:stroke", attributes)
319 def _write_comment_path(self, gradientshapeok, connecttype):
320 # Write the <v:path> element.
321 attributes = []
323 if gradientshapeok:
324 attributes.append(("gradientshapeok", "t"))
326 attributes.append(("o:connecttype", connecttype))
328 self._xml_empty_tag("v:path", attributes)
330 def _write_button_path(self):
331 # Write the <v:path> element.
332 shadowok = "f"
333 extrusionok = "f"
334 strokeok = "f"
335 fillok = "f"
336 connecttype = "rect"
338 attributes = [
339 ("shadowok", shadowok),
340 ("o:extrusionok", extrusionok),
341 ("strokeok", strokeok),
342 ("fillok", fillok),
343 ("o:connecttype", connecttype),
344 ]
346 self._xml_empty_tag("v:path", attributes)
348 def _write_image_path(self):
349 # Write the <v:path> element.
350 extrusionok = "f"
351 gradientshapeok = "t"
352 connecttype = "rect"
354 attributes = [
355 ("o:extrusionok", extrusionok),
356 ("gradientshapeok", gradientshapeok),
357 ("o:connecttype", connecttype),
358 ]
360 self._xml_empty_tag("v:path", attributes)
362 def _write_shapetype_lock(self):
363 # Write the <o:lock> element.
364 ext = "edit"
365 shapetype = "t"
367 attributes = [
368 ("v:ext", ext),
369 ("shapetype", shapetype),
370 ]
372 self._xml_empty_tag("o:lock", attributes)
374 def _write_rotation_lock(self):
375 # Write the <o:lock> element.
376 ext = "edit"
377 rotation = "t"
379 attributes = [
380 ("v:ext", ext),
381 ("rotation", rotation),
382 ]
384 self._xml_empty_tag("o:lock", attributes)
386 def _write_aspect_ratio_lock(self):
387 # Write the <o:lock> element.
388 ext = "edit"
389 aspectratio = "t"
391 attributes = [
392 ("v:ext", ext),
393 ("aspectratio", aspectratio),
394 ]
396 self._xml_empty_tag("o:lock", attributes)
398 def _write_comment_shape(self, shape_id, z_index, comment: CommentType):
399 # Write the <v:shape> element.
400 shape_type = "#_x0000_t202"
401 insetmode = "auto"
402 visibility = "hidden"
404 # Set the shape index.
405 shape_id = "_x0000_s" + str(shape_id)
407 (left, top, width, height) = self._pixels_to_points(comment.vertices)
409 # Set the visibility.
410 if comment.is_visible:
411 visibility = "visible"
413 style = (
414 f"position:absolute;"
415 f"margin-left:{left:.15g}pt;"
416 f"margin-top:{top:.15g}pt;"
417 f"width:{width:.15g}pt;"
418 f"height:{height:.15g}pt;"
419 f"z-index:{z_index};"
420 f"visibility:{visibility}"
421 )
423 attributes = [
424 ("id", shape_id),
425 ("type", shape_type),
426 ("style", style),
427 ("fillcolor", comment.color._vml_rgb_hex_value()),
428 ("o:insetmode", insetmode),
429 ]
431 self._xml_start_tag("v:shape", attributes)
433 # Write the v:fill element.
434 self._write_comment_fill()
436 # Write the v:shadow element.
437 self._write_shadow()
439 # Write the v:path element.
440 self._write_comment_path(None, "none")
442 # Write the v:textbox element.
443 self._write_comment_textbox()
445 # Write the x:ClientData element.
446 self._write_comment_client_data(comment)
448 self._xml_end_tag("v:shape")
450 def _write_button_shape(self, shape_id, z_index, button: ButtonType):
451 # Write the <v:shape> element.
452 shape_type = "#_x0000_t201"
454 # Set the shape index.
455 shape_id = "_x0000_s" + str(shape_id)
457 (left, top, width, height) = self._pixels_to_points(button.vertices)
459 style = (
460 f"position:absolute;"
461 f"margin-left:{left:.15g}pt;"
462 f"margin-top:{top:.15g}pt;"
463 f"width:{width:.15g}pt;"
464 f"height:{height:.15g}pt;"
465 f"z-index:{z_index};"
466 f"mso-wrap-style:tight"
467 )
469 attributes = [
470 ("id", shape_id),
471 ("type", shape_type),
472 ]
474 if button.description is not None:
475 attributes.append(("alt", button.description))
477 attributes.append(("style", style))
478 attributes.append(("o:button", "t"))
479 attributes.append(("fillcolor", "buttonFace [67]"))
480 attributes.append(("strokecolor", "windowText [64]"))
481 attributes.append(("o:insetmode", "auto"))
483 self._xml_start_tag("v:shape", attributes)
485 # Write the v:fill element.
486 self._write_button_fill()
488 # Write the o:lock element.
489 self._write_rotation_lock()
491 # Write the v:textbox element.
492 self._write_button_textbox(button)
494 # Write the x:ClientData element.
495 self._write_button_client_data(button)
497 self._xml_end_tag("v:shape")
499 def _write_image_shape(self, shape_id, z_index, image: Image):
500 # Write the <v:shape> element.
501 shape_type = "#_x0000_t75"
503 # Set the shape index.
504 shape_id = "_x0000_s" + str(shape_id)
506 # Get the image parameters
507 name = image.image_name
508 width = image._width
509 x_dpi = image._x_dpi
510 y_dpi = image._y_dpi
511 height = image._height
512 ref_id = image._ref_id
513 position = image._header_position
515 # Scale the height/width by the resolution, relative to 72dpi.
516 width = width * 72.0 / x_dpi
517 height = height * 72.0 / y_dpi
519 # Excel uses a rounding based around 72 and 96 dpi.
520 width = 72.0 / 96 * int(width * 96.0 / 72 + 0.25)
521 height = 72.0 / 96 * int(height * 96.0 / 72 + 0.25)
523 style = (
524 f"position:absolute;"
525 f"margin-left:0;"
526 f"margin-top:0;"
527 f"width:{width:.15g}pt;"
528 f"height:{height:.15g}pt;"
529 f"z-index:{z_index}"
530 )
532 attributes = [
533 ("id", position),
534 ("o:spid", shape_id),
535 ("type", shape_type),
536 ("style", style),
537 ]
539 self._xml_start_tag("v:shape", attributes)
541 # Write the v:imagedata element.
542 self._write_imagedata(ref_id, name)
544 # Write the o:lock element.
545 self._write_rotation_lock()
547 self._xml_end_tag("v:shape")
549 def _write_comment_fill(self):
550 # Write the <v:fill> element.
551 color_2 = "#ffffe1"
553 attributes = [("color2", color_2)]
555 self._xml_empty_tag("v:fill", attributes)
557 def _write_button_fill(self):
558 # Write the <v:fill> element.
559 color_2 = "buttonFace [67]"
560 detectmouseclick = "t"
562 attributes = [
563 ("color2", color_2),
564 ("o:detectmouseclick", detectmouseclick),
565 ]
567 self._xml_empty_tag("v:fill", attributes)
569 def _write_shadow(self):
570 # Write the <v:shadow> element.
571 on = "t"
572 color = "black"
573 obscured = "t"
575 attributes = [
576 ("on", on),
577 ("color", color),
578 ("obscured", obscured),
579 ]
581 self._xml_empty_tag("v:shadow", attributes)
583 def _write_comment_textbox(self):
584 # Write the <v:textbox> element.
585 style = "mso-direction-alt:auto"
587 attributes = [("style", style)]
589 self._xml_start_tag("v:textbox", attributes)
591 # Write the div element.
592 self._write_div("left")
594 self._xml_end_tag("v:textbox")
596 def _write_button_textbox(self, button: ButtonType):
597 # Write the <v:textbox> element.
598 style = "mso-direction-alt:auto"
600 attributes = [("style", style), ("o:singleclick", "f")]
602 self._xml_start_tag("v:textbox", attributes)
604 # Write the div element.
605 self._write_div("center", button.caption)
607 self._xml_end_tag("v:textbox")
609 def _write_div(self, align: str, caption: str = None):
610 # Write the <div> element.
612 style = "text-align:" + align
614 attributes = [("style", style)]
616 self._xml_start_tag("div", attributes)
618 if caption:
619 self._write_button_font(caption)
621 self._xml_end_tag("div")
623 def _write_button_font(self, caption: str):
624 # Write the <font> element.
625 face = "Calibri"
626 size = 220
627 color = "#000000"
629 attributes = [
630 ("face", face),
631 ("size", size),
632 ("color", color),
633 ]
635 self._xml_data_element("font", caption, attributes)
637 def _write_comment_client_data(self, comment: CommentType):
638 # Write the <x:ClientData> element.
639 object_type = "Note"
641 attributes = [("ObjectType", object_type)]
643 self._xml_start_tag("x:ClientData", attributes)
645 # Write the x:MoveWithCells element.
646 self._write_move_with_cells()
648 # Write the x:SizeWithCells element.
649 self._write_size_with_cells()
651 # Write the x:Anchor element.
652 self._write_anchor(comment.vertices)
654 # Write the x:AutoFill element.
655 self._write_auto_fill()
657 # Write the x:Row element.
658 self._write_row(comment.row)
660 # Write the x:Column element.
661 self._write_column(comment.col)
663 # Write the x:Visible element.
664 if comment.is_visible:
665 self._write_visible()
667 self._xml_end_tag("x:ClientData")
669 def _write_button_client_data(self, button):
670 # Write the <x:ClientData> element.
671 object_type = "Button"
673 attributes = [("ObjectType", object_type)]
675 self._xml_start_tag("x:ClientData", attributes)
677 # Write the x:Anchor element.
678 self._write_anchor(button.vertices)
680 # Write the x:PrintObject element.
681 self._write_print_object()
683 # Write the x:AutoFill element.
684 self._write_auto_fill()
686 # Write the x:FmlaMacro element.
687 self._write_fmla_macro(button.macro)
689 # Write the x:TextHAlign element.
690 self._write_text_halign()
692 # Write the x:TextVAlign element.
693 self._write_text_valign()
695 self._xml_end_tag("x:ClientData")
697 def _write_move_with_cells(self):
698 # Write the <x:MoveWithCells> element.
699 self._xml_empty_tag("x:MoveWithCells")
701 def _write_size_with_cells(self):
702 # Write the <x:SizeWithCells> element.
703 self._xml_empty_tag("x:SizeWithCells")
705 def _write_visible(self):
706 # Write the <x:Visible> element.
707 self._xml_empty_tag("x:Visible")
709 def _write_anchor(self, vertices):
710 # Write the <x:Anchor> element.
711 (col_start, row_start, x1, y1, col_end, row_end, x2, y2) = vertices[:8]
713 strings = [col_start, x1, row_start, y1, col_end, x2, row_end, y2]
714 strings = [str(i) for i in strings]
716 data = ", ".join(strings)
718 self._xml_data_element("x:Anchor", data)
720 def _write_auto_fill(self):
721 # Write the <x:AutoFill> element.
722 data = "False"
724 self._xml_data_element("x:AutoFill", data)
726 def _write_row(self, data):
727 # Write the <x:Row> element.
728 self._xml_data_element("x:Row", data)
730 def _write_column(self, data):
731 # Write the <x:Column> element.
732 self._xml_data_element("x:Column", data)
734 def _write_print_object(self):
735 # Write the <x:PrintObject> element.
736 self._xml_data_element("x:PrintObject", "False")
738 def _write_text_halign(self):
739 # Write the <x:TextHAlign> element.
740 self._xml_data_element("x:TextHAlign", "Center")
742 def _write_text_valign(self):
743 # Write the <x:TextVAlign> element.
744 self._xml_data_element("x:TextVAlign", "Center")
746 def _write_fmla_macro(self, data):
747 # Write the <x:FmlaMacro> element.
748 self._xml_data_element("x:FmlaMacro", data)
750 def _write_imagedata(self, ref_id, o_title):
751 # Write the <v:imagedata> element.
752 attributes = [
753 ("o:relid", "rId" + str(ref_id)),
754 ("o:title", o_title),
755 ]
757 self._xml_empty_tag("v:imagedata", attributes)
759 def _write_formulas(self):
760 # Write the <v:formulas> element.
761 self._xml_start_tag("v:formulas")
763 # Write the v:f elements.
764 self._write_formula("if lineDrawn pixelLineWidth 0")
765 self._write_formula("sum @0 1 0")
766 self._write_formula("sum 0 0 @1")
767 self._write_formula("prod @2 1 2")
768 self._write_formula("prod @3 21600 pixelWidth")
769 self._write_formula("prod @3 21600 pixelHeight")
770 self._write_formula("sum @0 0 1")
771 self._write_formula("prod @6 1 2")
772 self._write_formula("prod @7 21600 pixelWidth")
773 self._write_formula("sum @8 21600 0")
774 self._write_formula("prod @7 21600 pixelHeight")
775 self._write_formula("sum @10 21600 0")
777 self._xml_end_tag("v:formulas")
779 def _write_formula(self, eqn):
780 # Write the <v:f> element.
781 attributes = [("eqn", eqn)]
783 self._xml_empty_tag("v:f", attributes)