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

327 statements  

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# 

9 

10# Package imports. 

11from xlsxwriter.comments import CommentType 

12from xlsxwriter.image import Image 

13 

14from . import xmlwriter 

15 

16 

17########################################################################### 

18# 

19# A button type class. 

20# 

21########################################################################### 

22class ButtonType: 

23 """ 

24 A class to represent a button in an Excel worksheet. 

25 

26 """ 

27 

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. 

39 

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 

52 

53 self.macro = f"[0]!Button{button_number}_Click" 

54 self.caption = f"Button {button_number}" 

55 self.description = None 

56 

57 self.x_scale = 1 

58 self.y_scale = 1 

59 self.x_offset = 0 

60 self.y_offset = 0 

61 

62 self.vertices = [] 

63 

64 # Set any user supplied options. 

65 self._set_user_options(options) 

66 

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 

74 

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) 

83 

84 # Set the macro name. 

85 if options.get("macro"): 

86 self.macro = "[0]!" + options["macro"] 

87 

88 # Scale the size of the button box if required. 

89 if options.get("x_scale"): 

90 self.width = self.width * options["x_scale"] 

91 

92 if options.get("y_scale"): 

93 self.height = self.height * options["y_scale"] 

94 

95 # Round the dimensions to the nearest pixel. 

96 self.width = int(0.5 + self.width) 

97 self.height = int(0.5 + self.height) 

98 

99 

100########################################################################### 

101# 

102# The file writer class for the Excel XLSX VML file. 

103# 

104########################################################################### 

105 

106 

107class Vml(xmlwriter.XMLwriter): 

108 """ 

109 A class for writing the Excel XLSX Vml file. 

110 

111 

112 """ 

113 

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 

129 

130 self._write_xml_namespace() 

131 

132 # Write the o:shapelayout element. 

133 self._write_shapelayout(data_id) 

134 

135 if buttons_data: 

136 # Write the v:shapetype element. 

137 self._write_button_shapetype() 

138 

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 

144 

145 if comments_data: 

146 # Write the v:shapetype element. 

147 self._write_comment_shapetype() 

148 

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 

154 

155 if header_images: 

156 # Write the v:shapetype element. 

157 self._write_image_shapetype() 

158 

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 

165 

166 self._xml_end_tag("xml") 

167 

168 # Close the XML writer filehandle. 

169 self._xml_close() 

170 

171 def _pixels_to_points(self, vertices): 

172 # Convert comment vertices from pixels to points. 

173 

174 left, top, width, height = vertices[8:12] 

175 

176 # Scale to pixels. 

177 left *= 0.75 

178 top *= 0.75 

179 width *= 0.75 

180 height *= 0.75 

181 

182 return left, top, width, height 

183 

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" 

195 

196 attributes = [ 

197 ("xmlns:v", xmlns), 

198 ("xmlns:o", xmlns_o), 

199 ("xmlns:x", xmlns_x), 

200 ] 

201 

202 self._xml_start_tag("xml", attributes) 

203 

204 def _write_shapelayout(self, data_id): 

205 # Write the <o:shapelayout> element. 

206 attributes = [("v:ext", "edit")] 

207 

208 self._xml_start_tag("o:shapelayout", attributes) 

209 

210 # Write the o:idmap element. 

211 self._write_idmap(data_id) 

212 

213 self._xml_end_tag("o:shapelayout") 

214 

215 def _write_idmap(self, data_id): 

216 # Write the <o:idmap> element. 

217 attributes = [ 

218 ("v:ext", "edit"), 

219 ("data", data_id), 

220 ] 

221 

222 self._xml_empty_tag("o:idmap", attributes) 

223 

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" 

230 

231 attributes = [ 

232 ("id", shape_id), 

233 ("coordsize", coordsize), 

234 ("o:spt", spt), 

235 ("path", path), 

236 ] 

237 

238 self._xml_start_tag("v:shapetype", attributes) 

239 

240 # Write the v:stroke element. 

241 self._write_stroke() 

242 

243 # Write the v:path element. 

244 self._write_comment_path("t", "rect") 

245 

246 self._xml_end_tag("v:shapetype") 

247 

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" 

254 

255 attributes = [ 

256 ("id", shape_id), 

257 ("coordsize", coordsize), 

258 ("o:spt", spt), 

259 ("path", path), 

260 ] 

261 

262 self._xml_start_tag("v:shapetype", attributes) 

263 

264 # Write the v:stroke element. 

265 self._write_stroke() 

266 

267 # Write the v:path element. 

268 self._write_button_path() 

269 

270 # Write the o:lock element. 

271 self._write_shapetype_lock() 

272 

273 self._xml_end_tag("v:shapetype") 

274 

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" 

284 

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 ] 

294 

295 self._xml_start_tag("v:shapetype", attributes) 

296 

297 # Write the v:stroke element. 

298 self._write_stroke() 

299 

300 # Write the v:formulas element. 

301 self._write_formulas() 

302 

303 # Write the v:path element. 

304 self._write_image_path() 

305 

306 # Write the o:lock element. 

307 self._write_aspect_ratio_lock() 

308 

309 self._xml_end_tag("v:shapetype") 

310 

311 def _write_stroke(self): 

312 # Write the <v:stroke> element. 

313 joinstyle = "miter" 

314 

315 attributes = [("joinstyle", joinstyle)] 

316 

317 self._xml_empty_tag("v:stroke", attributes) 

318 

319 def _write_comment_path(self, gradientshapeok, connecttype): 

320 # Write the <v:path> element. 

321 attributes = [] 

322 

323 if gradientshapeok: 

324 attributes.append(("gradientshapeok", "t")) 

325 

326 attributes.append(("o:connecttype", connecttype)) 

327 

328 self._xml_empty_tag("v:path", attributes) 

329 

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" 

337 

338 attributes = [ 

339 ("shadowok", shadowok), 

340 ("o:extrusionok", extrusionok), 

341 ("strokeok", strokeok), 

342 ("fillok", fillok), 

343 ("o:connecttype", connecttype), 

344 ] 

345 

346 self._xml_empty_tag("v:path", attributes) 

347 

348 def _write_image_path(self): 

349 # Write the <v:path> element. 

350 extrusionok = "f" 

351 gradientshapeok = "t" 

352 connecttype = "rect" 

353 

354 attributes = [ 

355 ("o:extrusionok", extrusionok), 

356 ("gradientshapeok", gradientshapeok), 

357 ("o:connecttype", connecttype), 

358 ] 

359 

360 self._xml_empty_tag("v:path", attributes) 

361 

362 def _write_shapetype_lock(self): 

363 # Write the <o:lock> element. 

364 ext = "edit" 

365 shapetype = "t" 

366 

367 attributes = [ 

368 ("v:ext", ext), 

369 ("shapetype", shapetype), 

370 ] 

371 

372 self._xml_empty_tag("o:lock", attributes) 

373 

374 def _write_rotation_lock(self): 

375 # Write the <o:lock> element. 

376 ext = "edit" 

377 rotation = "t" 

378 

379 attributes = [ 

380 ("v:ext", ext), 

381 ("rotation", rotation), 

382 ] 

383 

384 self._xml_empty_tag("o:lock", attributes) 

385 

386 def _write_aspect_ratio_lock(self): 

387 # Write the <o:lock> element. 

388 ext = "edit" 

389 aspectratio = "t" 

390 

391 attributes = [ 

392 ("v:ext", ext), 

393 ("aspectratio", aspectratio), 

394 ] 

395 

396 self._xml_empty_tag("o:lock", attributes) 

397 

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" 

403 

404 # Set the shape index. 

405 shape_id = "_x0000_s" + str(shape_id) 

406 

407 (left, top, width, height) = self._pixels_to_points(comment.vertices) 

408 

409 # Set the visibility. 

410 if comment.is_visible: 

411 visibility = "visible" 

412 

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 ) 

422 

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 ] 

430 

431 self._xml_start_tag("v:shape", attributes) 

432 

433 # Write the v:fill element. 

434 self._write_comment_fill() 

435 

436 # Write the v:shadow element. 

437 self._write_shadow() 

438 

439 # Write the v:path element. 

440 self._write_comment_path(None, "none") 

441 

442 # Write the v:textbox element. 

443 self._write_comment_textbox() 

444 

445 # Write the x:ClientData element. 

446 self._write_comment_client_data(comment) 

447 

448 self._xml_end_tag("v:shape") 

449 

450 def _write_button_shape(self, shape_id, z_index, button: ButtonType): 

451 # Write the <v:shape> element. 

452 shape_type = "#_x0000_t201" 

453 

454 # Set the shape index. 

455 shape_id = "_x0000_s" + str(shape_id) 

456 

457 (left, top, width, height) = self._pixels_to_points(button.vertices) 

458 

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 ) 

468 

469 attributes = [ 

470 ("id", shape_id), 

471 ("type", shape_type), 

472 ] 

473 

474 if button.description is not None: 

475 attributes.append(("alt", button.description)) 

476 

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

482 

483 self._xml_start_tag("v:shape", attributes) 

484 

485 # Write the v:fill element. 

486 self._write_button_fill() 

487 

488 # Write the o:lock element. 

489 self._write_rotation_lock() 

490 

491 # Write the v:textbox element. 

492 self._write_button_textbox(button) 

493 

494 # Write the x:ClientData element. 

495 self._write_button_client_data(button) 

496 

497 self._xml_end_tag("v:shape") 

498 

499 def _write_image_shape(self, shape_id, z_index, image: Image): 

500 # Write the <v:shape> element. 

501 shape_type = "#_x0000_t75" 

502 

503 # Set the shape index. 

504 shape_id = "_x0000_s" + str(shape_id) 

505 

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 

514 

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 

518 

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) 

522 

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 ) 

531 

532 attributes = [ 

533 ("id", position), 

534 ("o:spid", shape_id), 

535 ("type", shape_type), 

536 ("style", style), 

537 ] 

538 

539 self._xml_start_tag("v:shape", attributes) 

540 

541 # Write the v:imagedata element. 

542 self._write_imagedata(ref_id, name) 

543 

544 # Write the o:lock element. 

545 self._write_rotation_lock() 

546 

547 self._xml_end_tag("v:shape") 

548 

549 def _write_comment_fill(self): 

550 # Write the <v:fill> element. 

551 color_2 = "#ffffe1" 

552 

553 attributes = [("color2", color_2)] 

554 

555 self._xml_empty_tag("v:fill", attributes) 

556 

557 def _write_button_fill(self): 

558 # Write the <v:fill> element. 

559 color_2 = "buttonFace [67]" 

560 detectmouseclick = "t" 

561 

562 attributes = [ 

563 ("color2", color_2), 

564 ("o:detectmouseclick", detectmouseclick), 

565 ] 

566 

567 self._xml_empty_tag("v:fill", attributes) 

568 

569 def _write_shadow(self): 

570 # Write the <v:shadow> element. 

571 on = "t" 

572 color = "black" 

573 obscured = "t" 

574 

575 attributes = [ 

576 ("on", on), 

577 ("color", color), 

578 ("obscured", obscured), 

579 ] 

580 

581 self._xml_empty_tag("v:shadow", attributes) 

582 

583 def _write_comment_textbox(self): 

584 # Write the <v:textbox> element. 

585 style = "mso-direction-alt:auto" 

586 

587 attributes = [("style", style)] 

588 

589 self._xml_start_tag("v:textbox", attributes) 

590 

591 # Write the div element. 

592 self._write_div("left") 

593 

594 self._xml_end_tag("v:textbox") 

595 

596 def _write_button_textbox(self, button: ButtonType): 

597 # Write the <v:textbox> element. 

598 style = "mso-direction-alt:auto" 

599 

600 attributes = [("style", style), ("o:singleclick", "f")] 

601 

602 self._xml_start_tag("v:textbox", attributes) 

603 

604 # Write the div element. 

605 self._write_div("center", button.caption) 

606 

607 self._xml_end_tag("v:textbox") 

608 

609 def _write_div(self, align: str, caption: str = None): 

610 # Write the <div> element. 

611 

612 style = "text-align:" + align 

613 

614 attributes = [("style", style)] 

615 

616 self._xml_start_tag("div", attributes) 

617 

618 if caption: 

619 self._write_button_font(caption) 

620 

621 self._xml_end_tag("div") 

622 

623 def _write_button_font(self, caption: str): 

624 # Write the <font> element. 

625 face = "Calibri" 

626 size = 220 

627 color = "#000000" 

628 

629 attributes = [ 

630 ("face", face), 

631 ("size", size), 

632 ("color", color), 

633 ] 

634 

635 self._xml_data_element("font", caption, attributes) 

636 

637 def _write_comment_client_data(self, comment: CommentType): 

638 # Write the <x:ClientData> element. 

639 object_type = "Note" 

640 

641 attributes = [("ObjectType", object_type)] 

642 

643 self._xml_start_tag("x:ClientData", attributes) 

644 

645 # Write the x:MoveWithCells element. 

646 self._write_move_with_cells() 

647 

648 # Write the x:SizeWithCells element. 

649 self._write_size_with_cells() 

650 

651 # Write the x:Anchor element. 

652 self._write_anchor(comment.vertices) 

653 

654 # Write the x:AutoFill element. 

655 self._write_auto_fill() 

656 

657 # Write the x:Row element. 

658 self._write_row(comment.row) 

659 

660 # Write the x:Column element. 

661 self._write_column(comment.col) 

662 

663 # Write the x:Visible element. 

664 if comment.is_visible: 

665 self._write_visible() 

666 

667 self._xml_end_tag("x:ClientData") 

668 

669 def _write_button_client_data(self, button): 

670 # Write the <x:ClientData> element. 

671 object_type = "Button" 

672 

673 attributes = [("ObjectType", object_type)] 

674 

675 self._xml_start_tag("x:ClientData", attributes) 

676 

677 # Write the x:Anchor element. 

678 self._write_anchor(button.vertices) 

679 

680 # Write the x:PrintObject element. 

681 self._write_print_object() 

682 

683 # Write the x:AutoFill element. 

684 self._write_auto_fill() 

685 

686 # Write the x:FmlaMacro element. 

687 self._write_fmla_macro(button.macro) 

688 

689 # Write the x:TextHAlign element. 

690 self._write_text_halign() 

691 

692 # Write the x:TextVAlign element. 

693 self._write_text_valign() 

694 

695 self._xml_end_tag("x:ClientData") 

696 

697 def _write_move_with_cells(self): 

698 # Write the <x:MoveWithCells> element. 

699 self._xml_empty_tag("x:MoveWithCells") 

700 

701 def _write_size_with_cells(self): 

702 # Write the <x:SizeWithCells> element. 

703 self._xml_empty_tag("x:SizeWithCells") 

704 

705 def _write_visible(self): 

706 # Write the <x:Visible> element. 

707 self._xml_empty_tag("x:Visible") 

708 

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] 

712 

713 strings = [col_start, x1, row_start, y1, col_end, x2, row_end, y2] 

714 strings = [str(i) for i in strings] 

715 

716 data = ", ".join(strings) 

717 

718 self._xml_data_element("x:Anchor", data) 

719 

720 def _write_auto_fill(self): 

721 # Write the <x:AutoFill> element. 

722 data = "False" 

723 

724 self._xml_data_element("x:AutoFill", data) 

725 

726 def _write_row(self, data): 

727 # Write the <x:Row> element. 

728 self._xml_data_element("x:Row", data) 

729 

730 def _write_column(self, data): 

731 # Write the <x:Column> element. 

732 self._xml_data_element("x:Column", data) 

733 

734 def _write_print_object(self): 

735 # Write the <x:PrintObject> element. 

736 self._xml_data_element("x:PrintObject", "False") 

737 

738 def _write_text_halign(self): 

739 # Write the <x:TextHAlign> element. 

740 self._xml_data_element("x:TextHAlign", "Center") 

741 

742 def _write_text_valign(self): 

743 # Write the <x:TextVAlign> element. 

744 self._xml_data_element("x:TextVAlign", "Center") 

745 

746 def _write_fmla_macro(self, data): 

747 # Write the <x:FmlaMacro> element. 

748 self._xml_data_element("x:FmlaMacro", data) 

749 

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 ] 

756 

757 self._xml_empty_tag("v:imagedata", attributes) 

758 

759 def _write_formulas(self): 

760 # Write the <v:formulas> element. 

761 self._xml_start_tag("v:formulas") 

762 

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

776 

777 self._xml_end_tag("v:formulas") 

778 

779 def _write_formula(self, eqn): 

780 # Write the <v:f> element. 

781 attributes = [("eqn", eqn)] 

782 

783 self._xml_empty_tag("v:f", attributes)