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

187 statements  

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 

10 

11 

12class Shape(object): 

13 """ 

14 A class for to represent Excel XLSX shape objects. 

15 

16 

17 """ 

18 

19 ########################################################################### 

20 # 

21 # Public API. 

22 # 

23 ########################################################################### 

24 

25 def __init__(self, shape_type, name, options): 

26 """ 

27 Constructor. 

28 

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 

53 

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 

61 

62 self._set_options(options) 

63 

64 ########################################################################### 

65 # 

66 # Private API. 

67 # 

68 ########################################################################### 

69 

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")) 

76 

77 self.text_rotation = options.get("text_rotation", 0) 

78 

79 self.textlink = options.get("textlink", "") 

80 if self.textlink.startswith("="): 

81 self.textlink = self.textlink.lstrip("=") 

82 

83 if options.get("border"): 

84 self.line = self._get_line_properties(options["border"]) 

85 

86 # Gradient fill overrides solid fill. 

87 if self.gradient: 

88 self.fill = None 

89 

90 ########################################################################### 

91 # 

92 # Static methods for processing chart/shape style properties. 

93 # 

94 ########################################################################### 

95 

96 @staticmethod 

97 def _get_line_properties(line): 

98 # Convert user line properties to the structure required internally. 

99 

100 if not line: 

101 return {"defined": False} 

102 

103 # Copy the user defined properties since they will be modified. 

104 line = copy.deepcopy(line) 

105 

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 } 

119 

120 # Check the dash type. 

121 dash_type = line.get("dash_type") 

122 

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 

129 

130 line["defined"] = True 

131 

132 return line 

133 

134 @staticmethod 

135 def _get_fill_properties(fill): 

136 # Convert user fill properties to the structure required internally. 

137 

138 if not fill: 

139 return {"defined": False} 

140 

141 # Copy the user defined properties since they will be modified. 

142 fill = copy.deepcopy(fill) 

143 

144 fill["defined"] = True 

145 

146 return fill 

147 

148 @staticmethod 

149 def _get_pattern_properties(pattern): 

150 # Convert user defined pattern to the structure required internally. 

151 

152 if not pattern: 

153 return 

154 

155 # Copy the user defined properties since they will be modified. 

156 pattern = copy.deepcopy(pattern) 

157 

158 if not pattern.get("pattern"): 

159 warn("Pattern must include 'pattern'") 

160 return 

161 

162 if not pattern.get("fg_color"): 

163 warn("Pattern must include 'fg_color'") 

164 return 

165 

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 } 

216 

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"]] 

223 

224 # Specify a default background color. 

225 pattern["bg_color"] = pattern.get("bg_color", "#FFFFFF") 

226 

227 return pattern 

228 

229 @staticmethod 

230 def _get_gradient_properties(gradient): 

231 # Convert user defined gradient to the structure required internally. 

232 

233 if not gradient: 

234 return 

235 

236 # Copy the user defined properties since they will be modified. 

237 gradient = copy.deepcopy(gradient) 

238 

239 types = { 

240 "linear": "linear", 

241 "radial": "circle", 

242 "rectangular": "rect", 

243 "path": "shape", 

244 } 

245 

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 

250 

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 

255 

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 

261 

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] 

271 

272 elif len(gradient["colors"]) == 3: 

273 gradient["positions"] = [0, 50, 100] 

274 

275 elif len(gradient["colors"]) == 4: 

276 gradient["positions"] = [0, 33, 66, 100] 

277 

278 else: 

279 warn("Must specify gradient positions") 

280 return 

281 

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 

289 

290 # Check for valid types. 

291 gradient_type = gradient.get("type") 

292 

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" 

301 

302 return gradient 

303 

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 = {} 

309 

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 } 

322 

323 # Convert font size units. 

324 if font["size"]: 

325 font["size"] = int(font["size"] * 100) 

326 

327 return font 

328 

329 @staticmethod 

330 def _get_font_style_attributes(font): 

331 # _get_font_style_attributes. 

332 attributes = [] 

333 

334 if not font: 

335 return attributes 

336 

337 if font.get("size"): 

338 attributes.append(("sz", font["size"])) 

339 

340 if font.get("bold") is not None: 

341 attributes.append(("b", 0 + font["bold"])) 

342 

343 if font.get("italic") is not None: 

344 attributes.append(("i", 0 + font["italic"])) 

345 

346 if font.get("underline") is not None: 

347 attributes.append(("u", "sng")) 

348 

349 if font.get("baseline") != -1: 

350 attributes.append(("baseline", font["baseline"])) 

351 

352 return attributes 

353 

354 @staticmethod 

355 def _get_font_latin_attributes(font): 

356 # _get_font_latin_attributes. 

357 attributes = [] 

358 

359 if not font: 

360 return attributes 

361 

362 if font["name"] is not None: 

363 attributes.append(("typeface", font["name"])) 

364 

365 if font["pitch_family"] is not None: 

366 attributes.append(("pitchFamily", font["pitch_family"])) 

367 

368 if font["charset"] is not None: 

369 attributes.append(("charset", font["charset"])) 

370 

371 return attributes 

372 

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} 

378 

379 # Copy the user defined properties since they will be modified. 

380 align = copy.deepcopy(align) 

381 

382 if "vertical" in align: 

383 align_type = align["vertical"] 

384 

385 align_types = { 

386 "top": "top", 

387 "middle": "middle", 

388 "bottom": "bottom", 

389 } 

390 

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} 

396 

397 if "horizontal" in align: 

398 align_type = align["horizontal"] 

399 

400 align_types = { 

401 "left": "left", 

402 "center": "center", 

403 "right": "right", 

404 } 

405 

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} 

411 

412 align["defined"] = True 

413 

414 return align