Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/xlsxwriter/shape.py: 11%
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# Shape - A class for to represent Excel XLSX shape objects.
4#
5# SPDX-License-Identifier: BSD-2-Clause
6# Copyright 2013-2024, John McNamara, jmcnamara@cpan.org
7#
8import copy
9from warnings import warn
12class Shape(object):
13 """
14 A class for to represent Excel XLSX shape objects.
17 """
19 ###########################################################################
20 #
21 # Public API.
22 #
23 ###########################################################################
25 def __init__(self, shape_type, name, options):
26 """
27 Constructor.
29 """
30 super(Shape, self).__init__()
31 self.name = name
32 self.shape_type = shape_type
33 self.connect = 0
34 self.drawing = 0
35 self.edit_as = ""
36 self.id = 0
37 self.text = ""
38 self.textlink = ""
39 self.stencil = 1
40 self.element = -1
41 self.start = None
42 self.start_index = None
43 self.end = None
44 self.end_index = None
45 self.adjustments = []
46 self.start_side = ""
47 self.end_side = ""
48 self.flip_h = 0
49 self.flip_v = 0
50 self.rotation = 0
51 self.text_rotation = 0
52 self.textbox = False
54 self.align = None
55 self.fill = None
56 self.font = None
57 self.format = None
58 self.line = None
59 self.url_rel_index = None
60 self.tip = None
62 self._set_options(options)
64 ###########################################################################
65 #
66 # Private API.
67 #
68 ###########################################################################
70 def _set_options(self, options):
71 self.align = self._get_align_properties(options.get("align"))
72 self.fill = self._get_fill_properties(options.get("fill"))
73 self.font = self._get_font_properties(options.get("font"))
74 self.gradient = self._get_gradient_properties(options.get("gradient"))
75 self.line = self._get_line_properties(options.get("line"))
77 self.text_rotation = options.get("text_rotation", 0)
79 self.textlink = options.get("textlink", "")
80 if self.textlink.startswith("="):
81 self.textlink = self.textlink.lstrip("=")
83 if options.get("border"):
84 self.line = self._get_line_properties(options["border"])
86 # Gradient fill overrides solid fill.
87 if self.gradient:
88 self.fill = None
90 ###########################################################################
91 #
92 # Static methods for processing chart/shape style properties.
93 #
94 ###########################################################################
96 @staticmethod
97 def _get_line_properties(line):
98 # Convert user line properties to the structure required internally.
100 if not line:
101 return {"defined": False}
103 # Copy the user defined properties since they will be modified.
104 line = copy.deepcopy(line)
106 dash_types = {
107 "solid": "solid",
108 "round_dot": "sysDot",
109 "square_dot": "sysDash",
110 "dash": "dash",
111 "dash_dot": "dashDot",
112 "long_dash": "lgDash",
113 "long_dash_dot": "lgDashDot",
114 "long_dash_dot_dot": "lgDashDotDot",
115 "dot": "dot",
116 "system_dash_dot": "sysDashDot",
117 "system_dash_dot_dot": "sysDashDotDot",
118 }
120 # Check the dash type.
121 dash_type = line.get("dash_type")
123 if dash_type is not None:
124 if dash_type in dash_types:
125 line["dash_type"] = dash_types[dash_type]
126 else:
127 warn("Unknown dash type '%s'" % dash_type)
128 return
130 line["defined"] = True
132 return line
134 @staticmethod
135 def _get_fill_properties(fill):
136 # Convert user fill properties to the structure required internally.
138 if not fill:
139 return {"defined": False}
141 # Copy the user defined properties since they will be modified.
142 fill = copy.deepcopy(fill)
144 fill["defined"] = True
146 return fill
148 @staticmethod
149 def _get_pattern_properties(pattern):
150 # Convert user defined pattern to the structure required internally.
152 if not pattern:
153 return
155 # Copy the user defined properties since they will be modified.
156 pattern = copy.deepcopy(pattern)
158 if not pattern.get("pattern"):
159 warn("Pattern must include 'pattern'")
160 return
162 if not pattern.get("fg_color"):
163 warn("Pattern must include 'fg_color'")
164 return
166 types = {
167 "percent_5": "pct5",
168 "percent_10": "pct10",
169 "percent_20": "pct20",
170 "percent_25": "pct25",
171 "percent_30": "pct30",
172 "percent_40": "pct40",
173 "percent_50": "pct50",
174 "percent_60": "pct60",
175 "percent_70": "pct70",
176 "percent_75": "pct75",
177 "percent_80": "pct80",
178 "percent_90": "pct90",
179 "light_downward_diagonal": "ltDnDiag",
180 "light_upward_diagonal": "ltUpDiag",
181 "dark_downward_diagonal": "dkDnDiag",
182 "dark_upward_diagonal": "dkUpDiag",
183 "wide_downward_diagonal": "wdDnDiag",
184 "wide_upward_diagonal": "wdUpDiag",
185 "light_vertical": "ltVert",
186 "light_horizontal": "ltHorz",
187 "narrow_vertical": "narVert",
188 "narrow_horizontal": "narHorz",
189 "dark_vertical": "dkVert",
190 "dark_horizontal": "dkHorz",
191 "dashed_downward_diagonal": "dashDnDiag",
192 "dashed_upward_diagonal": "dashUpDiag",
193 "dashed_horizontal": "dashHorz",
194 "dashed_vertical": "dashVert",
195 "small_confetti": "smConfetti",
196 "large_confetti": "lgConfetti",
197 "zigzag": "zigZag",
198 "wave": "wave",
199 "diagonal_brick": "diagBrick",
200 "horizontal_brick": "horzBrick",
201 "weave": "weave",
202 "plaid": "plaid",
203 "divot": "divot",
204 "dotted_grid": "dotGrid",
205 "dotted_diamond": "dotDmnd",
206 "shingle": "shingle",
207 "trellis": "trellis",
208 "sphere": "sphere",
209 "small_grid": "smGrid",
210 "large_grid": "lgGrid",
211 "small_check": "smCheck",
212 "large_check": "lgCheck",
213 "outlined_diamond": "openDmnd",
214 "solid_diamond": "solidDmnd",
215 }
217 # Check for valid types.
218 if pattern["pattern"] not in types:
219 warn("unknown pattern type '%s'" % pattern["pattern"])
220 return
221 else:
222 pattern["pattern"] = types[pattern["pattern"]]
224 # Specify a default background color.
225 pattern["bg_color"] = pattern.get("bg_color", "#FFFFFF")
227 return pattern
229 @staticmethod
230 def _get_gradient_properties(gradient):
231 # Convert user defined gradient to the structure required internally.
233 if not gradient:
234 return
236 # Copy the user defined properties since they will be modified.
237 gradient = copy.deepcopy(gradient)
239 types = {
240 "linear": "linear",
241 "radial": "circle",
242 "rectangular": "rect",
243 "path": "shape",
244 }
246 # Check the colors array exists and is valid.
247 if "colors" not in gradient or not isinstance(gradient["colors"], list):
248 warn("Gradient must include colors list")
249 return
251 # Check the colors array has the required number of entries.
252 if not 2 <= len(gradient["colors"]) <= 10:
253 warn("Gradient colors list must at least 2 values and not more than 10")
254 return
256 if "positions" in gradient:
257 # Check the positions array has the right number of entries.
258 if len(gradient["positions"]) != len(gradient["colors"]):
259 warn("Gradient positions not equal to number of colors")
260 return
262 # Check the positions are in the correct range.
263 for pos in gradient["positions"]:
264 if not 0 <= pos <= 100:
265 warn("Gradient position must be in the range 0 <= position <= 100")
266 return
267 else:
268 # Use the default gradient positions.
269 if len(gradient["colors"]) == 2:
270 gradient["positions"] = [0, 100]
272 elif len(gradient["colors"]) == 3:
273 gradient["positions"] = [0, 50, 100]
275 elif len(gradient["colors"]) == 4:
276 gradient["positions"] = [0, 33, 66, 100]
278 else:
279 warn("Must specify gradient positions")
280 return
282 angle = gradient.get("angle")
283 if angle:
284 if not 0 <= angle < 360:
285 warn("Gradient angle must be in the range 0 <= angle < 360")
286 return
287 else:
288 gradient["angle"] = 90
290 # Check for valid types.
291 gradient_type = gradient.get("type")
293 if gradient_type is not None:
294 if gradient_type in types:
295 gradient["type"] = types[gradient_type]
296 else:
297 warn("Unknown gradient type '%s" % gradient_type)
298 return
299 else:
300 gradient["type"] = "linear"
302 return gradient
304 @staticmethod
305 def _get_font_properties(options):
306 # Convert user defined font values into private dict values.
307 if options is None:
308 options = {}
310 font = {
311 "name": options.get("name"),
312 "color": options.get("color"),
313 "size": options.get("size", 11),
314 "bold": options.get("bold"),
315 "italic": options.get("italic"),
316 "underline": options.get("underline"),
317 "pitch_family": options.get("pitch_family"),
318 "charset": options.get("charset"),
319 "baseline": options.get("baseline", -1),
320 "lang": options.get("lang", "en-US"),
321 }
323 # Convert font size units.
324 if font["size"]:
325 font["size"] = int(font["size"] * 100)
327 return font
329 @staticmethod
330 def _get_font_style_attributes(font):
331 # _get_font_style_attributes.
332 attributes = []
334 if not font:
335 return attributes
337 if font.get("size"):
338 attributes.append(("sz", font["size"]))
340 if font.get("bold") is not None:
341 attributes.append(("b", 0 + font["bold"]))
343 if font.get("italic") is not None:
344 attributes.append(("i", 0 + font["italic"]))
346 if font.get("underline") is not None:
347 attributes.append(("u", "sng"))
349 if font.get("baseline") != -1:
350 attributes.append(("baseline", font["baseline"]))
352 return attributes
354 @staticmethod
355 def _get_font_latin_attributes(font):
356 # _get_font_latin_attributes.
357 attributes = []
359 if not font:
360 return attributes
362 if font["name"] is not None:
363 attributes.append(("typeface", font["name"]))
365 if font["pitch_family"] is not None:
366 attributes.append(("pitchFamily", font["pitch_family"]))
368 if font["charset"] is not None:
369 attributes.append(("charset", font["charset"]))
371 return attributes
373 @staticmethod
374 def _get_align_properties(align):
375 # Convert user defined align to the structure required internally.
376 if not align:
377 return {"defined": False}
379 # Copy the user defined properties since they will be modified.
380 align = copy.deepcopy(align)
382 if "vertical" in align:
383 align_type = align["vertical"]
385 align_types = {
386 "top": "top",
387 "middle": "middle",
388 "bottom": "bottom",
389 }
391 if align_type in align_types:
392 align["vertical"] = align_types[align_type]
393 else:
394 warn("Unknown alignment type '%s'" % align_type)
395 return {"defined": False}
397 if "horizontal" in align:
398 align_type = align["horizontal"]
400 align_types = {
401 "left": "left",
402 "center": "center",
403 "right": "right",
404 }
406 if align_type in align_types:
407 align["horizontal"] = align_types[align_type]
408 else:
409 warn("Unknown alignment type '%s'" % align_type)
410 return {"defined": False}
412 align["defined"] = True
414 return align