Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/xlsxwriter/drawing.py: 16%

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

537 statements  

1############################################################################### 

2# 

3# Drawing - A class for writing the Excel XLSX Drawing file. 

4# 

5# SPDX-License-Identifier: BSD-2-Clause 

6# 

7# Copyright (c) 2013-2026, John McNamara, jmcnamara@cpan.org 

8# 

9 

10from enum import Enum 

11 

12from xlsxwriter import xmlwriter 

13from xlsxwriter.color import Color 

14from xlsxwriter.shape import Shape 

15from xlsxwriter.url import Url 

16 

17 

18class DrawingTypes(Enum): 

19 """ 

20 Enum to represent different types of drawings in a worksheet. 

21 """ 

22 

23 NONE = 0 

24 CHART = 1 

25 IMAGE = 2 

26 SHAPE = 3 

27 

28 

29class DrawingInfo: 

30 """ 

31 An internal class to represent a drawing object in an Excel worksheet. 

32 

33 """ 

34 

35 def __init__(self) -> None: 

36 """ 

37 Initialize a DrawingType instance with default values. 

38 """ 

39 self._drawing_type = DrawingTypes.NONE 

40 self._anchor_type = None 

41 self._dimensions = [] 

42 self._width = 0 

43 self._height = 0 

44 self._shape = None 

45 self._anchor = None 

46 self._url = None 

47 self._rel_index = 0 

48 self._name = None 

49 self._description = None 

50 self._decorative = False 

51 

52 

53class Drawing(xmlwriter.XMLwriter): 

54 """ 

55 A class for writing the Excel XLSX Drawing file. 

56 

57 

58 """ 

59 

60 ########################################################################### 

61 # 

62 # Public API. 

63 # 

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

65 

66 def __init__(self) -> None: 

67 """ 

68 Constructor. 

69 

70 """ 

71 

72 super().__init__() 

73 

74 self.drawings = [] 

75 self.embedded = 0 

76 self.orientation = 0 

77 

78 ########################################################################### 

79 # 

80 # Private API. 

81 # 

82 ########################################################################### 

83 

84 def _assemble_xml_file(self) -> None: 

85 # Assemble and write the XML file. 

86 

87 # Write the XML declaration. 

88 self._xml_declaration() 

89 

90 # Write the xdr:wsDr element. 

91 self._write_drawing_workspace() 

92 

93 if self.embedded: 

94 for index, drawing in enumerate(self.drawings, 1): 

95 # Write the xdr:twoCellAnchor element. 

96 self._write_two_cell_anchor(index, drawing) 

97 

98 else: 

99 # Write the xdr:absoluteAnchor element. 

100 drawing = DrawingInfo() 

101 drawing._rel_index = 1 

102 self._write_absolute_anchor(1, drawing) 

103 

104 self._xml_end_tag("xdr:wsDr") 

105 

106 # Close the file. 

107 self._xml_close() 

108 

109 def _add_drawing_object(self, drawing_object: DrawingInfo) -> None: 

110 # Add a chart, image or shape sub object to the drawing. 

111 self.drawings.append(drawing_object) 

112 

113 ########################################################################### 

114 # 

115 # XML methods. 

116 # 

117 ########################################################################### 

118 

119 def _write_drawing_workspace(self) -> None: 

120 # Write the <xdr:wsDr> element. 

121 schema = "http://schemas.openxmlformats.org/drawingml/" 

122 xmlns_xdr = schema + "2006/spreadsheetDrawing" 

123 xmlns_a = schema + "2006/main" 

124 

125 attributes = [ 

126 ("xmlns:xdr", xmlns_xdr), 

127 ("xmlns:a", xmlns_a), 

128 ] 

129 

130 self._xml_start_tag("xdr:wsDr", attributes) 

131 

132 def _write_two_cell_anchor(self, index: int, drawing: DrawingInfo) -> None: 

133 # Write the <xdr:twoCellAnchor> element. 

134 dimensions = drawing._dimensions 

135 col_from = dimensions[0] 

136 row_from = dimensions[1] 

137 col_from_offset = dimensions[2] 

138 row_from_offset = dimensions[3] 

139 col_to = dimensions[4] 

140 row_to = dimensions[5] 

141 col_to_offset = dimensions[6] 

142 row_to_offset = dimensions[7] 

143 col_absolute = dimensions[8] 

144 row_absolute = dimensions[9] 

145 

146 attributes = [] 

147 

148 # Add attribute for positioning. 

149 if drawing._anchor == 2: 

150 attributes.append(("editAs", "oneCell")) 

151 elif drawing._anchor == 3: 

152 attributes.append(("editAs", "absolute")) 

153 

154 # Add editAs attribute for shapes. 

155 if drawing._shape and drawing._shape.edit_as: 

156 attributes.append(("editAs", drawing._shape.edit_as)) 

157 

158 self._xml_start_tag("xdr:twoCellAnchor", attributes) 

159 

160 # Write the xdr:from element. 

161 self._write_from(col_from, row_from, col_from_offset, row_from_offset) 

162 

163 # Write the xdr:from element. 

164 self._write_to(col_to, row_to, col_to_offset, row_to_offset) 

165 

166 if drawing._drawing_type == DrawingTypes.CHART: 

167 # Graphic frame. 

168 # Write the xdr:graphicFrame element for charts. 

169 self._write_graphic_frame(index, drawing) 

170 elif drawing._drawing_type == DrawingTypes.IMAGE: 

171 # Write the xdr:pic element. 

172 self._write_pic(index, col_absolute, row_absolute, drawing) 

173 else: 

174 # Write the xdr:sp element for shapes. 

175 self._write_sp(index, col_absolute, row_absolute, drawing) 

176 

177 # Write the xdr:clientData element. 

178 self._write_client_data() 

179 

180 self._xml_end_tag("xdr:twoCellAnchor") 

181 

182 def _write_absolute_anchor(self, index: int, drawing: DrawingInfo) -> None: 

183 self._xml_start_tag("xdr:absoluteAnchor") 

184 # Write the <xdr:absoluteAnchor> element. 

185 

186 # Different coordinates for horizontal (= 0) and vertical (= 1). 

187 if self.orientation == 0: 

188 # Write the xdr:pos element. 

189 self._write_pos(0, 0) 

190 

191 # Write the xdr:ext element. 

192 self._write_xdr_ext(9308969, 6078325) 

193 

194 else: 

195 # Write the xdr:pos element. 

196 self._write_pos(0, -47625) 

197 

198 # Write the xdr:ext element. 

199 self._write_xdr_ext(6162675, 6124575) 

200 

201 # Write the xdr:graphicFrame element. 

202 self._write_graphic_frame(index, drawing) 

203 

204 # Write the xdr:clientData element. 

205 self._write_client_data() 

206 

207 self._xml_end_tag("xdr:absoluteAnchor") 

208 

209 def _write_from(self, col: int, row: int, col_offset, row_offset) -> None: 

210 # Write the <xdr:from> element. 

211 self._xml_start_tag("xdr:from") 

212 

213 # Write the xdr:col element. 

214 self._write_col(col) 

215 

216 # Write the xdr:colOff element. 

217 self._write_col_off(col_offset) 

218 

219 # Write the xdr:row element. 

220 self._write_row(row) 

221 

222 # Write the xdr:rowOff element. 

223 self._write_row_off(row_offset) 

224 

225 self._xml_end_tag("xdr:from") 

226 

227 def _write_to(self, col: int, row: int, col_offset, row_offset) -> None: 

228 # Write the <xdr:to> element. 

229 self._xml_start_tag("xdr:to") 

230 

231 # Write the xdr:col element. 

232 self._write_col(col) 

233 

234 # Write the xdr:colOff element. 

235 self._write_col_off(col_offset) 

236 

237 # Write the xdr:row element. 

238 self._write_row(row) 

239 

240 # Write the xdr:rowOff element. 

241 self._write_row_off(row_offset) 

242 

243 self._xml_end_tag("xdr:to") 

244 

245 def _write_col(self, data) -> None: 

246 # Write the <xdr:col> element. 

247 self._xml_data_element("xdr:col", data) 

248 

249 def _write_col_off(self, data) -> None: 

250 # Write the <xdr:colOff> element. 

251 self._xml_data_element("xdr:colOff", data) 

252 

253 def _write_row(self, data) -> None: 

254 # Write the <xdr:row> element. 

255 self._xml_data_element("xdr:row", data) 

256 

257 def _write_row_off(self, data) -> None: 

258 # Write the <xdr:rowOff> element. 

259 self._xml_data_element("xdr:rowOff", data) 

260 

261 def _write_pos(self, x, y) -> None: 

262 # Write the <xdr:pos> element. 

263 

264 attributes = [("x", x), ("y", y)] 

265 

266 self._xml_empty_tag("xdr:pos", attributes) 

267 

268 def _write_xdr_ext(self, cx, cy) -> None: 

269 # Write the <xdr:ext> element. 

270 

271 attributes = [("cx", cx), ("cy", cy)] 

272 

273 self._xml_empty_tag("xdr:ext", attributes) 

274 

275 def _write_graphic_frame(self, index: int, drawing: DrawingInfo) -> None: 

276 # Write the <xdr:graphicFrame> element. 

277 attributes = [("macro", "")] 

278 

279 self._xml_start_tag("xdr:graphicFrame", attributes) 

280 

281 # Write the xdr:nvGraphicFramePr element. 

282 self._write_nv_graphic_frame_pr(index, drawing) 

283 

284 # Write the xdr:xfrm element. 

285 self._write_xfrm() 

286 

287 # Write the a:graphic element. 

288 self._write_atag_graphic(drawing._rel_index) 

289 

290 self._xml_end_tag("xdr:graphicFrame") 

291 

292 def _write_nv_graphic_frame_pr(self, index: int, drawing: DrawingInfo) -> None: 

293 # Write the <xdr:nvGraphicFramePr> element. 

294 

295 name = drawing._name 

296 if not name: 

297 name = "Chart " + str(index) 

298 

299 self._xml_start_tag("xdr:nvGraphicFramePr") 

300 

301 # Write the xdr:cNvPr element. 

302 self._write_c_nv_pr(index + 1, drawing, name) 

303 

304 # Write the xdr:cNvGraphicFramePr element. 

305 self._write_c_nv_graphic_frame_pr() 

306 

307 self._xml_end_tag("xdr:nvGraphicFramePr") 

308 

309 def _write_c_nv_pr(self, index: int, drawing: DrawingInfo, name: str) -> None: 

310 # Write the <xdr:cNvPr> element. 

311 attributes = [("id", index), ("name", name)] 

312 

313 # Add description attribute for images. 

314 if drawing._description and not drawing._decorative: 

315 attributes.append(("descr", drawing._description)) 

316 

317 if drawing._url or drawing._decorative: 

318 self._xml_start_tag("xdr:cNvPr", attributes) 

319 

320 if drawing._url: 

321 self._write_a_hlink_click(drawing._url) 

322 

323 if drawing._decorative: 

324 self._write_decorative() 

325 

326 self._xml_end_tag("xdr:cNvPr") 

327 else: 

328 self._xml_empty_tag("xdr:cNvPr", attributes) 

329 

330 def _write_decorative(self) -> None: 

331 self._xml_start_tag("a:extLst") 

332 

333 self._write_uri_ext("{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}") 

334 self._write_a16_creation_id() 

335 self._xml_end_tag("a:ext") 

336 

337 self._write_uri_ext("{C183D7F6-B498-43B3-948B-1728B52AA6E4}") 

338 self._write_adec_decorative() 

339 self._xml_end_tag("a:ext") 

340 

341 self._xml_end_tag("a:extLst") 

342 

343 def _write_uri_ext(self, uri) -> None: 

344 # Write the <a:ext> element. 

345 attributes = [("uri", uri)] 

346 

347 self._xml_start_tag("a:ext", attributes) 

348 

349 def _write_adec_decorative(self) -> None: 

350 # Write the <adec:decorative> element. 

351 xmlns = "http://schemas.microsoft.com/office/drawing/2017/decorative" 

352 val = "1" 

353 

354 attributes = [ 

355 ("xmlns:adec", xmlns), 

356 ("val", val), 

357 ] 

358 

359 self._xml_empty_tag("adec:decorative", attributes) 

360 

361 def _write_a16_creation_id(self) -> None: 

362 # Write the <a16:creationId> element. 

363 

364 xmlns_a_16 = "http://schemas.microsoft.com/office/drawing/2014/main" 

365 creation_id = "{00000000-0008-0000-0000-000002000000}" 

366 

367 attributes = [ 

368 ("xmlns:a16", xmlns_a_16), 

369 ("id", creation_id), 

370 ] 

371 

372 self._xml_empty_tag("a16:creationId", attributes) 

373 

374 def _write_a_hlink_click(self, url: Url) -> None: 

375 # Write the <a:hlinkClick> element. 

376 schema = "http://schemas.openxmlformats.org/officeDocument/" 

377 xmlns_r = schema + "2006/relationships" 

378 

379 attributes = [ 

380 ("xmlns:r", xmlns_r), 

381 ("r:id", "rId" + str(url._rel_index)), 

382 ] 

383 

384 if url._tip: 

385 attributes.append(("tooltip", url._tip)) 

386 

387 self._xml_empty_tag("a:hlinkClick", attributes) 

388 

389 def _write_c_nv_graphic_frame_pr(self) -> None: 

390 # Write the <xdr:cNvGraphicFramePr> element. 

391 if self.embedded: 

392 self._xml_empty_tag("xdr:cNvGraphicFramePr") 

393 else: 

394 self._xml_start_tag("xdr:cNvGraphicFramePr") 

395 

396 # Write the a:graphicFrameLocks element. 

397 self._write_a_graphic_frame_locks() 

398 

399 self._xml_end_tag("xdr:cNvGraphicFramePr") 

400 

401 def _write_a_graphic_frame_locks(self) -> None: 

402 # Write the <a:graphicFrameLocks> element. 

403 attributes = [("noGrp", 1)] 

404 

405 self._xml_empty_tag("a:graphicFrameLocks", attributes) 

406 

407 def _write_xfrm(self) -> None: 

408 # Write the <xdr:xfrm> element. 

409 self._xml_start_tag("xdr:xfrm") 

410 

411 # Write the xfrmOffset element. 

412 self._write_xfrm_offset() 

413 

414 # Write the xfrmOffset element. 

415 self._write_xfrm_extension() 

416 

417 self._xml_end_tag("xdr:xfrm") 

418 

419 def _write_xfrm_offset(self) -> None: 

420 # Write the <a:off> xfrm sub-element. 

421 

422 attributes = [ 

423 ("x", 0), 

424 ("y", 0), 

425 ] 

426 

427 self._xml_empty_tag("a:off", attributes) 

428 

429 def _write_xfrm_extension(self) -> None: 

430 # Write the <a:ext> xfrm sub-element. 

431 

432 attributes = [ 

433 ("cx", 0), 

434 ("cy", 0), 

435 ] 

436 

437 self._xml_empty_tag("a:ext", attributes) 

438 

439 def _write_atag_graphic(self, index: int) -> None: 

440 # Write the <a:graphic> element. 

441 self._xml_start_tag("a:graphic") 

442 

443 # Write the a:graphicData element. 

444 self._write_atag_graphic_data(index) 

445 

446 self._xml_end_tag("a:graphic") 

447 

448 def _write_atag_graphic_data(self, index: int) -> None: 

449 # Write the <a:graphicData> element. 

450 uri = "http://schemas.openxmlformats.org/drawingml/2006/chart" 

451 

452 attributes = [ 

453 ( 

454 "uri", 

455 uri, 

456 ) 

457 ] 

458 

459 self._xml_start_tag("a:graphicData", attributes) 

460 

461 # Write the c:chart element. 

462 self._write_c_chart("rId" + str(index)) 

463 

464 self._xml_end_tag("a:graphicData") 

465 

466 def _write_c_chart(self, r_id) -> None: 

467 # Write the <c:chart> element. 

468 

469 schema = "http://schemas.openxmlformats.org/" 

470 xmlns_c = schema + "drawingml/2006/chart" 

471 xmlns_r = schema + "officeDocument/2006/relationships" 

472 

473 attributes = [ 

474 ("xmlns:c", xmlns_c), 

475 ("xmlns:r", xmlns_r), 

476 ("r:id", r_id), 

477 ] 

478 

479 self._xml_empty_tag("c:chart", attributes) 

480 

481 def _write_client_data(self) -> None: 

482 # Write the <xdr:clientData> element. 

483 self._xml_empty_tag("xdr:clientData") 

484 

485 def _write_sp( 

486 self, 

487 index, 

488 col_absolute, 

489 row_absolute, 

490 drawing: DrawingInfo, 

491 ) -> None: 

492 # Write the <xdr:sp> element. 

493 

494 if drawing._shape and drawing._shape.connect: 

495 attributes = [("macro", "")] 

496 self._xml_start_tag("xdr:cxnSp", attributes) 

497 

498 # Write the xdr:nvCxnSpPr element. 

499 self._write_nv_cxn_sp_pr(drawing._shape) 

500 

501 # Write the xdr:spPr element. 

502 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing) 

503 

504 self._xml_end_tag("xdr:cxnSp") 

505 else: 

506 # Add attribute for shapes. 

507 attributes = [("macro", ""), ("textlink", drawing._shape.textlink)] 

508 

509 self._xml_start_tag("xdr:sp", attributes) 

510 

511 # Write the xdr:nvSpPr element. 

512 self._write_nv_sp_pr(index, drawing) 

513 

514 # Write the xdr:spPr element. 

515 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing) 

516 

517 # Write the xdr:style element. 

518 self._write_style() 

519 

520 # Write the xdr:txBody element. 

521 if drawing._shape.text is not None: 

522 self._write_tx_body(drawing._shape) 

523 

524 self._xml_end_tag("xdr:sp") 

525 

526 def _write_nv_cxn_sp_pr(self, shape) -> None: 

527 # Write the <xdr:nvCxnSpPr> element. 

528 self._xml_start_tag("xdr:nvCxnSpPr") 

529 

530 self._xml_start_tag("xdr:cNvCxnSpPr") 

531 

532 attributes = [("noChangeShapeType", "1")] 

533 self._xml_empty_tag("a:cxnSpLocks", attributes) 

534 

535 if shape.start: 

536 attributes = [("id", shape.start), ("idx", shape.start_index)] 

537 self._xml_empty_tag("a:stCxn", attributes) 

538 

539 if shape.end: 

540 attributes = [("id", shape.end), ("idx", shape.end_index)] 

541 self._xml_empty_tag("a:endCxn", attributes) 

542 

543 self._xml_end_tag("xdr:cNvCxnSpPr") 

544 self._xml_end_tag("xdr:nvCxnSpPr") 

545 

546 def _write_nv_sp_pr(self, index: int, drawing: DrawingInfo) -> None: 

547 # Write the <xdr:NvSpPr> element. 

548 attributes = [] 

549 

550 self._xml_start_tag("xdr:nvSpPr") 

551 

552 name = drawing._shape.name + " " + str(index) 

553 

554 self._write_c_nv_pr(index + 1, drawing, name) 

555 

556 if drawing._shape.name == "TextBox": 

557 attributes = [("txBox", 1)] 

558 

559 self._xml_empty_tag("xdr:cNvSpPr", attributes) 

560 

561 self._xml_end_tag("xdr:nvSpPr") 

562 

563 def _write_pic( 

564 self, 

565 index: int, 

566 col_absolute: int, 

567 row_absolute: int, 

568 drawing: DrawingInfo, 

569 ) -> None: 

570 # Write the <xdr:pic> element. 

571 self._xml_start_tag("xdr:pic") 

572 

573 # Write the xdr:nvPicPr element. 

574 self._write_nv_pic_pr(index, drawing) 

575 # Write the xdr:blipFill element. 

576 self._write_blip_fill(drawing._rel_index) 

577 

578 # Write the xdr:spPr element. 

579 self._write_sp_pr(col_absolute, row_absolute, drawing) 

580 

581 self._xml_end_tag("xdr:pic") 

582 

583 def _write_nv_pic_pr(self, index: int, drawing: DrawingInfo) -> None: 

584 # Write the <xdr:nvPicPr> element. 

585 self._xml_start_tag("xdr:nvPicPr") 

586 

587 name = "Picture " + str(index) 

588 

589 # Write the xdr:cNvPr element. 

590 self._write_c_nv_pr(index + 1, drawing, name) 

591 

592 # Write the xdr:cNvPicPr element. 

593 self._write_c_nv_pic_pr() 

594 

595 self._xml_end_tag("xdr:nvPicPr") 

596 

597 def _write_c_nv_pic_pr(self) -> None: 

598 # Write the <xdr:cNvPicPr> element. 

599 self._xml_start_tag("xdr:cNvPicPr") 

600 

601 # Write the a:picLocks element. 

602 self._write_a_pic_locks() 

603 

604 self._xml_end_tag("xdr:cNvPicPr") 

605 

606 def _write_a_pic_locks(self) -> None: 

607 # Write the <a:picLocks> element. 

608 attributes = [("noChangeAspect", 1)] 

609 

610 self._xml_empty_tag("a:picLocks", attributes) 

611 

612 def _write_blip_fill(self, index: int) -> None: 

613 # Write the <xdr:blipFill> element. 

614 self._xml_start_tag("xdr:blipFill") 

615 

616 # Write the a:blip element. 

617 self._write_a_blip(index) 

618 

619 # Write the a:stretch element. 

620 self._write_a_stretch() 

621 

622 self._xml_end_tag("xdr:blipFill") 

623 

624 def _write_a_blip(self, index: int) -> None: 

625 # Write the <a:blip> element. 

626 schema = "http://schemas.openxmlformats.org/officeDocument/" 

627 xmlns_r = schema + "2006/relationships" 

628 r_embed = "rId" + str(index) 

629 

630 attributes = [("xmlns:r", xmlns_r), ("r:embed", r_embed)] 

631 

632 self._xml_empty_tag("a:blip", attributes) 

633 

634 def _write_a_stretch(self) -> None: 

635 # Write the <a:stretch> element. 

636 self._xml_start_tag("a:stretch") 

637 

638 # Write the a:fillRect element. 

639 self._write_a_fill_rect() 

640 

641 self._xml_end_tag("a:stretch") 

642 

643 def _write_a_fill_rect(self) -> None: 

644 # Write the <a:fillRect> element. 

645 self._xml_empty_tag("a:fillRect") 

646 

647 def _write_sp_pr(self, col_absolute, row_absolute, drawing: DrawingInfo) -> None: 

648 # Write the <xdr:spPr> element, for charts. 

649 

650 self._xml_start_tag("xdr:spPr") 

651 

652 # Write the a:xfrm element. 

653 self._write_a_xfrm(col_absolute, row_absolute, drawing._width, drawing._height) 

654 

655 # Write the a:prstGeom element. 

656 self._write_a_prst_geom(drawing._shape) 

657 

658 self._xml_end_tag("xdr:spPr") 

659 

660 def _write_xdr_sp_pr( 

661 self, col_absolute: int, row_absolute: int, drawing: DrawingInfo 

662 ) -> None: 

663 # Write the <xdr:spPr> element for shapes. 

664 self._xml_start_tag("xdr:spPr") 

665 

666 # Write the a:xfrm element. 

667 self._write_a_xfrm( 

668 col_absolute, row_absolute, drawing._width, drawing._height, drawing._shape 

669 ) 

670 

671 # Write the a:prstGeom element. 

672 shape = drawing._shape 

673 self._write_a_prst_geom(shape) 

674 

675 if shape.fill: 

676 if not shape.fill["defined"]: 

677 # Write the a:solidFill element. 

678 self._write_a_solid_fill_scheme("lt1") 

679 elif "none" in shape.fill: 

680 # Write the a:noFill element. 

681 self._xml_empty_tag("a:noFill") 

682 elif "color" in shape.fill: 

683 # Write the a:solidFill element. 

684 self._write_a_solid_fill(shape.fill["color"]) 

685 

686 if shape.gradient: 

687 # Write the a:gradFill element. 

688 self._write_a_grad_fill(shape.gradient) 

689 

690 # Write the a:ln element. 

691 self._write_a_ln(shape.line) 

692 

693 self._xml_end_tag("xdr:spPr") 

694 

695 def _write_a_xfrm( 

696 self, col_absolute, row_absolute, width, height, shape=None 

697 ) -> None: 

698 # Write the <a:xfrm> element. 

699 attributes = [] 

700 

701 if shape: 

702 if shape.rotation: 

703 rotation = shape.rotation 

704 rotation *= 60000 

705 attributes.append(("rot", rotation)) 

706 

707 if shape.flip_h: 

708 attributes.append(("flipH", 1)) 

709 if shape.flip_v: 

710 attributes.append(("flipV", 1)) 

711 

712 self._xml_start_tag("a:xfrm", attributes) 

713 

714 # Write the a:off element. 

715 self._write_a_off(col_absolute, row_absolute) 

716 

717 # Write the a:ext element. 

718 self._write_a_ext(width, height) 

719 

720 self._xml_end_tag("a:xfrm") 

721 

722 def _write_a_off(self, x, y) -> None: 

723 # Write the <a:off> element. 

724 attributes = [ 

725 ("x", x), 

726 ("y", y), 

727 ] 

728 

729 self._xml_empty_tag("a:off", attributes) 

730 

731 def _write_a_ext(self, cx, cy) -> None: 

732 # Write the <a:ext> element. 

733 attributes = [ 

734 ("cx", cx), 

735 ("cy", cy), 

736 ] 

737 

738 self._xml_empty_tag("a:ext", attributes) 

739 

740 def _write_a_prst_geom(self, shape=None) -> None: 

741 # Write the <a:prstGeom> element. 

742 attributes = [("prst", "rect")] 

743 

744 self._xml_start_tag("a:prstGeom", attributes) 

745 

746 # Write the a:avLst element. 

747 self._write_a_av_lst(shape) 

748 

749 self._xml_end_tag("a:prstGeom") 

750 

751 def _write_a_av_lst(self, shape=None) -> None: 

752 # Write the <a:avLst> element. 

753 adjustments = [] 

754 

755 if shape and shape.adjustments: 

756 adjustments = shape.adjustments 

757 

758 if adjustments: 

759 self._xml_start_tag("a:avLst") 

760 

761 for i, adj in enumerate(adjustments, 1): 

762 # Only connectors have multiple adjustments. 

763 if shape.connect: 

764 suffix = i 

765 else: 

766 suffix = "" 

767 

768 # Scale Adjustments: 100,000 = 100%. 

769 adj_int = str(int(adj * 1000)) 

770 

771 attributes = [("name", "adj" + suffix), ("fmla", "val" + adj_int)] 

772 

773 self._xml_empty_tag("a:gd", attributes) 

774 

775 self._xml_end_tag("a:avLst") 

776 else: 

777 self._xml_empty_tag("a:avLst") 

778 

779 def _write_a_solid_fill(self, color: Color) -> None: 

780 # Write the <a:solidFill> element. 

781 self._xml_start_tag("a:solidFill") 

782 

783 # Write the a:srgbClr element. 

784 self._write_a_srgb_clr(color) 

785 

786 self._xml_end_tag("a:solidFill") 

787 

788 def _write_a_solid_fill_scheme(self, named_color, shade=None) -> None: 

789 attributes = [("val", named_color)] 

790 

791 self._xml_start_tag("a:solidFill") 

792 

793 if shade: 

794 self._xml_start_tag("a:schemeClr", attributes) 

795 self._write_a_shade(shade) 

796 self._xml_end_tag("a:schemeClr") 

797 else: 

798 self._xml_empty_tag("a:schemeClr", attributes) 

799 

800 self._xml_end_tag("a:solidFill") 

801 

802 def _write_a_ln(self, line) -> None: 

803 # Write the <a:ln> element. 

804 width = line.get("width", 0.75) 

805 

806 # Round width to nearest 0.25, like Excel. 

807 width = int((width + 0.125) * 4) / 4.0 

808 

809 # Convert to internal units. 

810 width = int(0.5 + (12700 * width)) 

811 

812 attributes = [("w", width), ("cmpd", "sng")] 

813 

814 self._xml_start_tag("a:ln", attributes) 

815 

816 if "none" in line: 

817 # Write the a:noFill element. 

818 self._xml_empty_tag("a:noFill") 

819 

820 elif "color" in line: 

821 # Write the a:solidFill element. 

822 self._write_a_solid_fill(line["color"]) 

823 

824 else: 

825 # Write the a:solidFill element. 

826 self._write_a_solid_fill_scheme("lt1", "50000") 

827 

828 # Write the line/dash type. 

829 line_type = line.get("dash_type") 

830 if line_type: 

831 # Write the a:prstDash element. 

832 self._write_a_prst_dash(line_type) 

833 

834 self._xml_end_tag("a:ln") 

835 

836 def _write_tx_body(self, shape) -> None: 

837 # Write the <xdr:txBody> element. 

838 attributes = [] 

839 

840 if shape.text_rotation != 0: 

841 if shape.text_rotation == 90: 

842 attributes.append(("vert", "vert270")) 

843 if shape.text_rotation == -90: 

844 attributes.append(("vert", "vert")) 

845 if shape.text_rotation == 270: 

846 attributes.append(("vert", "wordArtVert")) 

847 if shape.text_rotation == 271: 

848 attributes.append(("vert", "eaVert")) 

849 

850 attributes.append(("wrap", "square")) 

851 attributes.append(("rtlCol", "0")) 

852 

853 if not shape.align["defined"]: 

854 attributes.append(("anchor", "t")) 

855 else: 

856 if "vertical" in shape.align: 

857 align = shape.align["vertical"] 

858 if align == "top": 

859 attributes.append(("anchor", "t")) 

860 elif align == "middle": 

861 attributes.append(("anchor", "ctr")) 

862 elif align == "bottom": 

863 attributes.append(("anchor", "b")) 

864 else: 

865 attributes.append(("anchor", "t")) 

866 

867 if "horizontal" in shape.align: 

868 align = shape.align["horizontal"] 

869 if align == "center": 

870 attributes.append(("anchorCtr", "1")) 

871 else: 

872 attributes.append(("anchorCtr", "0")) 

873 

874 self._xml_start_tag("xdr:txBody") 

875 self._xml_empty_tag("a:bodyPr", attributes) 

876 self._xml_empty_tag("a:lstStyle") 

877 

878 lines = shape.text.split("\n") 

879 

880 # Set the font attributes. 

881 font = shape.font 

882 # pylint: disable=protected-access 

883 style_attrs = Shape._get_font_style_attributes(font) 

884 latin_attrs = Shape._get_font_latin_attributes(font) 

885 style_attrs.insert(0, ("lang", font["lang"])) 

886 

887 if shape.textlink != "": 

888 attributes = [ 

889 ("id", "{B8ADDEFE-BF52-4FD4-8C5D-6B85EF6FF707}"), 

890 ("type", "TxLink"), 

891 ] 

892 

893 self._xml_start_tag("a:p") 

894 self._xml_start_tag("a:fld", attributes) 

895 

896 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr") 

897 

898 self._xml_data_element("a:t", shape.text) 

899 self._xml_end_tag("a:fld") 

900 

901 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr") 

902 

903 self._xml_end_tag("a:p") 

904 else: 

905 for line in lines: 

906 self._xml_start_tag("a:p") 

907 

908 if line == "": 

909 self._write_font_run(font, style_attrs, latin_attrs, "a:endParaRPr") 

910 self._xml_end_tag("a:p") 

911 continue 

912 

913 if "text" in shape.align: 

914 if shape.align["text"] == "left": 

915 self._xml_empty_tag("a:pPr", [("algn", "l")]) 

916 if shape.align["text"] == "center": 

917 self._xml_empty_tag("a:pPr", [("algn", "ctr")]) 

918 if shape.align["text"] == "right": 

919 self._xml_empty_tag("a:pPr", [("algn", "r")]) 

920 

921 self._xml_start_tag("a:r") 

922 

923 self._write_font_run(font, style_attrs, latin_attrs, "a:rPr") 

924 

925 self._xml_data_element("a:t", line) 

926 

927 self._xml_end_tag("a:r") 

928 self._xml_end_tag("a:p") 

929 

930 self._xml_end_tag("xdr:txBody") 

931 

932 def _write_font_run(self, font, style_attrs, latin_attrs, run_type) -> None: 

933 # Write a:rPr or a:endParaRPr. 

934 has_color = font.get("color") is not None 

935 

936 if latin_attrs or has_color: 

937 self._xml_start_tag(run_type, style_attrs) 

938 

939 if has_color: 

940 self._write_a_solid_fill(font["color"]) 

941 

942 if latin_attrs: 

943 self._write_a_latin(latin_attrs) 

944 self._write_a_cs(latin_attrs) 

945 

946 self._xml_end_tag(run_type) 

947 else: 

948 self._xml_empty_tag(run_type, style_attrs) 

949 

950 def _write_style(self) -> None: 

951 # Write the <xdr:style> element. 

952 self._xml_start_tag("xdr:style") 

953 

954 # Write the a:lnRef element. 

955 self._write_a_ln_ref() 

956 

957 # Write the a:fillRef element. 

958 self._write_a_fill_ref() 

959 

960 # Write the a:effectRef element. 

961 self._write_a_effect_ref() 

962 

963 # Write the a:fontRef element. 

964 self._write_a_font_ref() 

965 

966 self._xml_end_tag("xdr:style") 

967 

968 def _write_a_ln_ref(self) -> None: 

969 # Write the <a:lnRef> element. 

970 attributes = [("idx", "0")] 

971 

972 self._xml_start_tag("a:lnRef", attributes) 

973 

974 # Write the a:scrgbClr element. 

975 self._write_a_scrgb_clr() 

976 

977 self._xml_end_tag("a:lnRef") 

978 

979 def _write_a_fill_ref(self) -> None: 

980 # Write the <a:fillRef> element. 

981 attributes = [("idx", "0")] 

982 

983 self._xml_start_tag("a:fillRef", attributes) 

984 

985 # Write the a:scrgbClr element. 

986 self._write_a_scrgb_clr() 

987 

988 self._xml_end_tag("a:fillRef") 

989 

990 def _write_a_effect_ref(self) -> None: 

991 # Write the <a:effectRef> element. 

992 attributes = [("idx", "0")] 

993 

994 self._xml_start_tag("a:effectRef", attributes) 

995 

996 # Write the a:scrgbClr element. 

997 self._write_a_scrgb_clr() 

998 

999 self._xml_end_tag("a:effectRef") 

1000 

1001 def _write_a_scrgb_clr(self) -> None: 

1002 # Write the <a:scrgbClr> element. 

1003 

1004 attributes = [ 

1005 ("r", "0"), 

1006 ("g", "0"), 

1007 ("b", "0"), 

1008 ] 

1009 

1010 self._xml_empty_tag("a:scrgbClr", attributes) 

1011 

1012 def _write_a_font_ref(self) -> None: 

1013 # Write the <a:fontRef> element. 

1014 attributes = [("idx", "minor")] 

1015 

1016 self._xml_start_tag("a:fontRef", attributes) 

1017 

1018 # Write the a:schemeClr element. 

1019 self._write_a_scheme_clr("dk1") 

1020 

1021 self._xml_end_tag("a:fontRef") 

1022 

1023 def _write_a_scheme_clr(self, val) -> None: 

1024 # Write the <a:schemeClr> element. 

1025 attributes = [("val", val)] 

1026 

1027 self._xml_empty_tag("a:schemeClr", attributes) 

1028 

1029 def _write_a_shade(self, shade) -> None: 

1030 # Write the <a:shade> element. 

1031 attributes = [("val", shade)] 

1032 

1033 self._xml_empty_tag("a:shade", attributes) 

1034 

1035 def _write_a_prst_dash(self, val) -> None: 

1036 # Write the <a:prstDash> element. 

1037 

1038 attributes = [("val", val)] 

1039 

1040 self._xml_empty_tag("a:prstDash", attributes) 

1041 

1042 def _write_a_grad_fill(self, gradient) -> None: 

1043 # Write the <a:gradFill> element. 

1044 

1045 attributes = [("flip", "none"), ("rotWithShape", "1")] 

1046 

1047 if gradient["type"] == "linear": 

1048 attributes = [] 

1049 

1050 self._xml_start_tag("a:gradFill", attributes) 

1051 

1052 # Write the a:gsLst element. 

1053 self._write_a_gs_lst(gradient) 

1054 

1055 if gradient["type"] == "linear": 

1056 # Write the a:lin element. 

1057 self._write_a_lin(gradient["angle"]) 

1058 else: 

1059 # Write the a:path element. 

1060 self._write_a_path(gradient["type"]) 

1061 

1062 # Write the a:tileRect element. 

1063 self._write_a_tile_rect(gradient["type"]) 

1064 

1065 self._xml_end_tag("a:gradFill") 

1066 

1067 def _write_a_gs_lst(self, gradient) -> None: 

1068 # Write the <a:gsLst> element. 

1069 positions = gradient["positions"] 

1070 colors = gradient["colors"] 

1071 

1072 self._xml_start_tag("a:gsLst") 

1073 

1074 for i, color in enumerate(colors): 

1075 pos = int(positions[i] * 1000) 

1076 attributes = [("pos", pos)] 

1077 self._xml_start_tag("a:gs", attributes) 

1078 

1079 # Write the a:srgbClr element. 

1080 self._write_a_srgb_clr(color) 

1081 

1082 self._xml_end_tag("a:gs") 

1083 

1084 self._xml_end_tag("a:gsLst") 

1085 

1086 def _write_a_lin(self, angle) -> None: 

1087 # Write the <a:lin> element. 

1088 

1089 angle = int(60000 * angle) 

1090 

1091 attributes = [ 

1092 ("ang", angle), 

1093 ("scaled", "0"), 

1094 ] 

1095 

1096 self._xml_empty_tag("a:lin", attributes) 

1097 

1098 def _write_a_path(self, gradient_type) -> None: 

1099 # Write the <a:path> element. 

1100 

1101 attributes = [("path", gradient_type)] 

1102 

1103 self._xml_start_tag("a:path", attributes) 

1104 

1105 # Write the a:fillToRect element. 

1106 self._write_a_fill_to_rect(gradient_type) 

1107 

1108 self._xml_end_tag("a:path") 

1109 

1110 def _write_a_fill_to_rect(self, gradient_type) -> None: 

1111 # Write the <a:fillToRect> element. 

1112 

1113 if gradient_type == "shape": 

1114 attributes = [ 

1115 ("l", "50000"), 

1116 ("t", "50000"), 

1117 ("r", "50000"), 

1118 ("b", "50000"), 

1119 ] 

1120 else: 

1121 attributes = [ 

1122 ("l", "100000"), 

1123 ("t", "100000"), 

1124 ] 

1125 

1126 self._xml_empty_tag("a:fillToRect", attributes) 

1127 

1128 def _write_a_tile_rect(self, gradient_type) -> None: 

1129 # Write the <a:tileRect> element. 

1130 

1131 if gradient_type == "shape": 

1132 attributes = [] 

1133 else: 

1134 attributes = [ 

1135 ("r", "-100000"), 

1136 ("b", "-100000"), 

1137 ] 

1138 

1139 self._xml_empty_tag("a:tileRect", attributes) 

1140 

1141 def _write_a_srgb_clr(self, color: Color) -> None: 

1142 # Write the <a:srgbClr> element. 

1143 attributes = [("val", color._rgb_hex_value())] 

1144 

1145 self._xml_empty_tag("a:srgbClr", attributes) 

1146 

1147 def _write_a_latin(self, attributes) -> None: 

1148 # Write the <a:latin> element. 

1149 self._xml_empty_tag("a:latin", attributes) 

1150 

1151 def _write_a_cs(self, attributes) -> None: 

1152 # Write the <a:latin> element. 

1153 self._xml_empty_tag("a:cs", attributes)