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

541 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-2025, 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 index = 0 

95 for drawing in self.drawings: 

96 # Write the xdr:twoCellAnchor element. 

97 index += 1 

98 self._write_two_cell_anchor(index, drawing) 

99 

100 else: 

101 # Write the xdr:absoluteAnchor element. 

102 drawing = DrawingInfo() 

103 drawing._rel_index = 1 

104 self._write_absolute_anchor(1, drawing) 

105 

106 self._xml_end_tag("xdr:wsDr") 

107 

108 # Close the file. 

109 self._xml_close() 

110 

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

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

113 self.drawings.append(drawing_object) 

114 

115 ########################################################################### 

116 # 

117 # XML methods. 

118 # 

119 ########################################################################### 

120 

121 def _write_drawing_workspace(self) -> None: 

122 # Write the <xdr:wsDr> element. 

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

124 xmlns_xdr = schema + "2006/spreadsheetDrawing" 

125 xmlns_a = schema + "2006/main" 

126 

127 attributes = [ 

128 ("xmlns:xdr", xmlns_xdr), 

129 ("xmlns:a", xmlns_a), 

130 ] 

131 

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

133 

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

135 # Write the <xdr:twoCellAnchor> element. 

136 dimensions = drawing._dimensions 

137 col_from = dimensions[0] 

138 row_from = dimensions[1] 

139 col_from_offset = dimensions[2] 

140 row_from_offset = dimensions[3] 

141 col_to = dimensions[4] 

142 row_to = dimensions[5] 

143 col_to_offset = dimensions[6] 

144 row_to_offset = dimensions[7] 

145 col_absolute = dimensions[8] 

146 row_absolute = dimensions[9] 

147 

148 attributes = [] 

149 

150 # Add attribute for positioning. 

151 if drawing._anchor == 2: 

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

153 elif drawing._anchor == 3: 

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

155 

156 # Add editAs attribute for shapes. 

157 if drawing._shape and drawing._shape.edit_as: 

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

159 

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

161 

162 # Write the xdr:from element. 

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

164 

165 # Write the xdr:from element. 

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

167 

168 if drawing._drawing_type == DrawingTypes.CHART: 

169 # Graphic frame. 

170 # Write the xdr:graphicFrame element for charts. 

171 self._write_graphic_frame(index, drawing) 

172 elif drawing._drawing_type == DrawingTypes.IMAGE: 

173 # Write the xdr:pic element. 

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

175 else: 

176 # Write the xdr:sp element for shapes. 

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

178 

179 # Write the xdr:clientData element. 

180 self._write_client_data() 

181 

182 self._xml_end_tag("xdr:twoCellAnchor") 

183 

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

185 self._xml_start_tag("xdr:absoluteAnchor") 

186 # Write the <xdr:absoluteAnchor> element. 

187 

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

189 if self.orientation == 0: 

190 # Write the xdr:pos element. 

191 self._write_pos(0, 0) 

192 

193 # Write the xdr:ext element. 

194 self._write_xdr_ext(9308969, 6078325) 

195 

196 else: 

197 # Write the xdr:pos element. 

198 self._write_pos(0, -47625) 

199 

200 # Write the xdr:ext element. 

201 self._write_xdr_ext(6162675, 6124575) 

202 

203 # Write the xdr:graphicFrame element. 

204 self._write_graphic_frame(index, drawing) 

205 

206 # Write the xdr:clientData element. 

207 self._write_client_data() 

208 

209 self._xml_end_tag("xdr:absoluteAnchor") 

210 

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

212 # Write the <xdr:from> element. 

213 self._xml_start_tag("xdr:from") 

214 

215 # Write the xdr:col element. 

216 self._write_col(col) 

217 

218 # Write the xdr:colOff element. 

219 self._write_col_off(col_offset) 

220 

221 # Write the xdr:row element. 

222 self._write_row(row) 

223 

224 # Write the xdr:rowOff element. 

225 self._write_row_off(row_offset) 

226 

227 self._xml_end_tag("xdr:from") 

228 

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

230 # Write the <xdr:to> element. 

231 self._xml_start_tag("xdr:to") 

232 

233 # Write the xdr:col element. 

234 self._write_col(col) 

235 

236 # Write the xdr:colOff element. 

237 self._write_col_off(col_offset) 

238 

239 # Write the xdr:row element. 

240 self._write_row(row) 

241 

242 # Write the xdr:rowOff element. 

243 self._write_row_off(row_offset) 

244 

245 self._xml_end_tag("xdr:to") 

246 

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

248 # Write the <xdr:col> element. 

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

250 

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

252 # Write the <xdr:colOff> element. 

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

254 

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

256 # Write the <xdr:row> element. 

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

258 

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

260 # Write the <xdr:rowOff> element. 

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

262 

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

264 # Write the <xdr:pos> element. 

265 

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

267 

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

269 

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

271 # Write the <xdr:ext> element. 

272 

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

274 

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

276 

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

278 # Write the <xdr:graphicFrame> element. 

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

280 

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

282 

283 # Write the xdr:nvGraphicFramePr element. 

284 self._write_nv_graphic_frame_pr(index, drawing) 

285 

286 # Write the xdr:xfrm element. 

287 self._write_xfrm() 

288 

289 # Write the a:graphic element. 

290 self._write_atag_graphic(drawing._rel_index) 

291 

292 self._xml_end_tag("xdr:graphicFrame") 

293 

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

295 # Write the <xdr:nvGraphicFramePr> element. 

296 

297 name = drawing._name 

298 if not name: 

299 name = "Chart " + str(index) 

300 

301 self._xml_start_tag("xdr:nvGraphicFramePr") 

302 

303 # Write the xdr:cNvPr element. 

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

305 

306 # Write the xdr:cNvGraphicFramePr element. 

307 self._write_c_nv_graphic_frame_pr() 

308 

309 self._xml_end_tag("xdr:nvGraphicFramePr") 

310 

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

312 # Write the <xdr:cNvPr> element. 

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

314 

315 # Add description attribute for images. 

316 if drawing._description and not drawing._decorative: 

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

318 

319 if drawing._url or drawing._decorative: 

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

321 

322 if drawing._url: 

323 self._write_a_hlink_click(drawing._url) 

324 

325 if drawing._decorative: 

326 self._write_decorative() 

327 

328 self._xml_end_tag("xdr:cNvPr") 

329 else: 

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

331 

332 def _write_decorative(self) -> None: 

333 self._xml_start_tag("a:extLst") 

334 

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

336 self._write_a16_creation_id() 

337 self._xml_end_tag("a:ext") 

338 

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

340 self._write_adec_decorative() 

341 self._xml_end_tag("a:ext") 

342 

343 self._xml_end_tag("a:extLst") 

344 

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

346 # Write the <a:ext> element. 

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

348 

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

350 

351 def _write_adec_decorative(self) -> None: 

352 # Write the <adec:decorative> element. 

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

354 val = "1" 

355 

356 attributes = [ 

357 ("xmlns:adec", xmlns), 

358 ("val", val), 

359 ] 

360 

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

362 

363 def _write_a16_creation_id(self) -> None: 

364 # Write the <a16:creationId> element. 

365 

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

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

368 

369 attributes = [ 

370 ("xmlns:a16", xmlns_a_16), 

371 ("id", creation_id), 

372 ] 

373 

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

375 

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

377 # Write the <a:hlinkClick> element. 

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

379 xmlns_r = schema + "2006/relationships" 

380 

381 attributes = [ 

382 ("xmlns:r", xmlns_r), 

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

384 ] 

385 

386 if url._tip: 

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

388 

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

390 

391 def _write_c_nv_graphic_frame_pr(self) -> None: 

392 # Write the <xdr:cNvGraphicFramePr> element. 

393 if self.embedded: 

394 self._xml_empty_tag("xdr:cNvGraphicFramePr") 

395 else: 

396 self._xml_start_tag("xdr:cNvGraphicFramePr") 

397 

398 # Write the a:graphicFrameLocks element. 

399 self._write_a_graphic_frame_locks() 

400 

401 self._xml_end_tag("xdr:cNvGraphicFramePr") 

402 

403 def _write_a_graphic_frame_locks(self) -> None: 

404 # Write the <a:graphicFrameLocks> element. 

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

406 

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

408 

409 def _write_xfrm(self) -> None: 

410 # Write the <xdr:xfrm> element. 

411 self._xml_start_tag("xdr:xfrm") 

412 

413 # Write the xfrmOffset element. 

414 self._write_xfrm_offset() 

415 

416 # Write the xfrmOffset element. 

417 self._write_xfrm_extension() 

418 

419 self._xml_end_tag("xdr:xfrm") 

420 

421 def _write_xfrm_offset(self) -> None: 

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

423 

424 attributes = [ 

425 ("x", 0), 

426 ("y", 0), 

427 ] 

428 

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

430 

431 def _write_xfrm_extension(self) -> None: 

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

433 

434 attributes = [ 

435 ("cx", 0), 

436 ("cy", 0), 

437 ] 

438 

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

440 

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

442 # Write the <a:graphic> element. 

443 self._xml_start_tag("a:graphic") 

444 

445 # Write the a:graphicData element. 

446 self._write_atag_graphic_data(index) 

447 

448 self._xml_end_tag("a:graphic") 

449 

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

451 # Write the <a:graphicData> element. 

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

453 

454 attributes = [ 

455 ( 

456 "uri", 

457 uri, 

458 ) 

459 ] 

460 

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

462 

463 # Write the c:chart element. 

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

465 

466 self._xml_end_tag("a:graphicData") 

467 

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

469 # Write the <c:chart> element. 

470 

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

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

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

474 

475 attributes = [ 

476 ("xmlns:c", xmlns_c), 

477 ("xmlns:r", xmlns_r), 

478 ("r:id", r_id), 

479 ] 

480 

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

482 

483 def _write_client_data(self) -> None: 

484 # Write the <xdr:clientData> element. 

485 self._xml_empty_tag("xdr:clientData") 

486 

487 def _write_sp( 

488 self, 

489 index, 

490 col_absolute, 

491 row_absolute, 

492 drawing: DrawingInfo, 

493 ) -> None: 

494 # Write the <xdr:sp> element. 

495 

496 if drawing._shape and drawing._shape.connect: 

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

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

499 

500 # Write the xdr:nvCxnSpPr element. 

501 self._write_nv_cxn_sp_pr(drawing._shape) 

502 

503 # Write the xdr:spPr element. 

504 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing) 

505 

506 self._xml_end_tag("xdr:cxnSp") 

507 else: 

508 # Add attribute for shapes. 

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

510 

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

512 

513 # Write the xdr:nvSpPr element. 

514 self._write_nv_sp_pr(index, drawing) 

515 

516 # Write the xdr:spPr element. 

517 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing) 

518 

519 # Write the xdr:style element. 

520 self._write_style() 

521 

522 # Write the xdr:txBody element. 

523 if drawing._shape.text is not None: 

524 self._write_tx_body(drawing._shape) 

525 

526 self._xml_end_tag("xdr:sp") 

527 

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

529 # Write the <xdr:nvCxnSpPr> element. 

530 self._xml_start_tag("xdr:nvCxnSpPr") 

531 

532 self._xml_start_tag("xdr:cNvCxnSpPr") 

533 

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

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

536 

537 if shape.start: 

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

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

540 

541 if shape.end: 

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

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

544 

545 self._xml_end_tag("xdr:cNvCxnSpPr") 

546 self._xml_end_tag("xdr:nvCxnSpPr") 

547 

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

549 # Write the <xdr:NvSpPr> element. 

550 attributes = [] 

551 

552 self._xml_start_tag("xdr:nvSpPr") 

553 

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

555 

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

557 

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

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

560 

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

562 

563 self._xml_end_tag("xdr:nvSpPr") 

564 

565 def _write_pic( 

566 self, 

567 index: int, 

568 col_absolute: int, 

569 row_absolute: int, 

570 drawing: DrawingInfo, 

571 ) -> None: 

572 # Write the <xdr:pic> element. 

573 self._xml_start_tag("xdr:pic") 

574 

575 # Write the xdr:nvPicPr element. 

576 self._write_nv_pic_pr(index, drawing) 

577 # Write the xdr:blipFill element. 

578 self._write_blip_fill(drawing._rel_index) 

579 

580 # Write the xdr:spPr element. 

581 self._write_sp_pr(col_absolute, row_absolute, drawing) 

582 

583 self._xml_end_tag("xdr:pic") 

584 

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

586 # Write the <xdr:nvPicPr> element. 

587 self._xml_start_tag("xdr:nvPicPr") 

588 

589 name = "Picture " + str(index) 

590 

591 # Write the xdr:cNvPr element. 

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

593 

594 # Write the xdr:cNvPicPr element. 

595 self._write_c_nv_pic_pr() 

596 

597 self._xml_end_tag("xdr:nvPicPr") 

598 

599 def _write_c_nv_pic_pr(self) -> None: 

600 # Write the <xdr:cNvPicPr> element. 

601 self._xml_start_tag("xdr:cNvPicPr") 

602 

603 # Write the a:picLocks element. 

604 self._write_a_pic_locks() 

605 

606 self._xml_end_tag("xdr:cNvPicPr") 

607 

608 def _write_a_pic_locks(self) -> None: 

609 # Write the <a:picLocks> element. 

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

611 

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

613 

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

615 # Write the <xdr:blipFill> element. 

616 self._xml_start_tag("xdr:blipFill") 

617 

618 # Write the a:blip element. 

619 self._write_a_blip(index) 

620 

621 # Write the a:stretch element. 

622 self._write_a_stretch() 

623 

624 self._xml_end_tag("xdr:blipFill") 

625 

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

627 # Write the <a:blip> element. 

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

629 xmlns_r = schema + "2006/relationships" 

630 r_embed = "rId" + str(index) 

631 

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

633 

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

635 

636 def _write_a_stretch(self) -> None: 

637 # Write the <a:stretch> element. 

638 self._xml_start_tag("a:stretch") 

639 

640 # Write the a:fillRect element. 

641 self._write_a_fill_rect() 

642 

643 self._xml_end_tag("a:stretch") 

644 

645 def _write_a_fill_rect(self) -> None: 

646 # Write the <a:fillRect> element. 

647 self._xml_empty_tag("a:fillRect") 

648 

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

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

651 

652 self._xml_start_tag("xdr:spPr") 

653 

654 # Write the a:xfrm element. 

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

656 

657 # Write the a:prstGeom element. 

658 self._write_a_prst_geom(drawing._shape) 

659 

660 self._xml_end_tag("xdr:spPr") 

661 

662 def _write_xdr_sp_pr( 

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

664 ) -> None: 

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

666 self._xml_start_tag("xdr:spPr") 

667 

668 # Write the a:xfrm element. 

669 self._write_a_xfrm( 

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

671 ) 

672 

673 # Write the a:prstGeom element. 

674 shape = drawing._shape 

675 self._write_a_prst_geom(shape) 

676 

677 if shape.fill: 

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

679 # Write the a:solidFill element. 

680 self._write_a_solid_fill_scheme("lt1") 

681 elif "none" in shape.fill: 

682 # Write the a:noFill element. 

683 self._xml_empty_tag("a:noFill") 

684 elif "color" in shape.fill: 

685 # Write the a:solidFill element. 

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

687 

688 if shape.gradient: 

689 # Write the a:gradFill element. 

690 self._write_a_grad_fill(shape.gradient) 

691 

692 # Write the a:ln element. 

693 self._write_a_ln(shape.line) 

694 

695 self._xml_end_tag("xdr:spPr") 

696 

697 def _write_a_xfrm( 

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

699 ) -> None: 

700 # Write the <a:xfrm> element. 

701 attributes = [] 

702 

703 if shape: 

704 if shape.rotation: 

705 rotation = shape.rotation 

706 rotation *= 60000 

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

708 

709 if shape.flip_h: 

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

711 if shape.flip_v: 

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

713 

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

715 

716 # Write the a:off element. 

717 self._write_a_off(col_absolute, row_absolute) 

718 

719 # Write the a:ext element. 

720 self._write_a_ext(width, height) 

721 

722 self._xml_end_tag("a:xfrm") 

723 

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

725 # Write the <a:off> element. 

726 attributes = [ 

727 ("x", x), 

728 ("y", y), 

729 ] 

730 

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

732 

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

734 # Write the <a:ext> element. 

735 attributes = [ 

736 ("cx", cx), 

737 ("cy", cy), 

738 ] 

739 

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

741 

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

743 # Write the <a:prstGeom> element. 

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

745 

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

747 

748 # Write the a:avLst element. 

749 self._write_a_av_lst(shape) 

750 

751 self._xml_end_tag("a:prstGeom") 

752 

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

754 # Write the <a:avLst> element. 

755 adjustments = [] 

756 

757 if shape and shape.adjustments: 

758 adjustments = shape.adjustments 

759 

760 if adjustments: 

761 self._xml_start_tag("a:avLst") 

762 

763 i = 0 

764 for adj in adjustments: 

765 i += 1 

766 # Only connectors have multiple adjustments. 

767 if shape.connect: 

768 suffix = i 

769 else: 

770 suffix = "" 

771 

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

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

774 

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

776 

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

778 

779 self._xml_end_tag("a:avLst") 

780 else: 

781 self._xml_empty_tag("a:avLst") 

782 

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

784 # Write the <a:solidFill> element. 

785 self._xml_start_tag("a:solidFill") 

786 

787 # Write the a:srgbClr element. 

788 self._write_a_srgb_clr(color) 

789 

790 self._xml_end_tag("a:solidFill") 

791 

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

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

794 

795 self._xml_start_tag("a:solidFill") 

796 

797 if shade: 

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

799 self._write_a_shade(shade) 

800 self._xml_end_tag("a:schemeClr") 

801 else: 

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

803 

804 self._xml_end_tag("a:solidFill") 

805 

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

807 # Write the <a:ln> element. 

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

809 

810 # Round width to nearest 0.25, like Excel. 

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

812 

813 # Convert to internal units. 

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

815 

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

817 

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

819 

820 if "none" in line: 

821 # Write the a:noFill element. 

822 self._xml_empty_tag("a:noFill") 

823 

824 elif "color" in line: 

825 # Write the a:solidFill element. 

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

827 

828 else: 

829 # Write the a:solidFill element. 

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

831 

832 # Write the line/dash type. 

833 line_type = line.get("dash_type") 

834 if line_type: 

835 # Write the a:prstDash element. 

836 self._write_a_prst_dash(line_type) 

837 

838 self._xml_end_tag("a:ln") 

839 

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

841 # Write the <xdr:txBody> element. 

842 attributes = [] 

843 

844 if shape.text_rotation != 0: 

845 if shape.text_rotation == 90: 

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

847 if shape.text_rotation == -90: 

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

849 if shape.text_rotation == 270: 

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

851 if shape.text_rotation == 271: 

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

853 

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

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

856 

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

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

859 else: 

860 if "vertical" in shape.align: 

861 align = shape.align["vertical"] 

862 if align == "top": 

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

864 elif align == "middle": 

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

866 elif align == "bottom": 

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

868 else: 

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

870 

871 if "horizontal" in shape.align: 

872 align = shape.align["horizontal"] 

873 if align == "center": 

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

875 else: 

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

877 

878 self._xml_start_tag("xdr:txBody") 

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

880 self._xml_empty_tag("a:lstStyle") 

881 

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

883 

884 # Set the font attributes. 

885 font = shape.font 

886 # pylint: disable=protected-access 

887 style_attrs = Shape._get_font_style_attributes(font) 

888 latin_attrs = Shape._get_font_latin_attributes(font) 

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

890 

891 if shape.textlink != "": 

892 attributes = [ 

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

894 ("type", "TxLink"), 

895 ] 

896 

897 self._xml_start_tag("a:p") 

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

899 

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

901 

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

903 self._xml_end_tag("a:fld") 

904 

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

906 

907 self._xml_end_tag("a:p") 

908 else: 

909 for line in lines: 

910 self._xml_start_tag("a:p") 

911 

912 if line == "": 

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

914 self._xml_end_tag("a:p") 

915 continue 

916 

917 if "text" in shape.align: 

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

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

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

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

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

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

924 

925 self._xml_start_tag("a:r") 

926 

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

928 

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

930 

931 self._xml_end_tag("a:r") 

932 self._xml_end_tag("a:p") 

933 

934 self._xml_end_tag("xdr:txBody") 

935 

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

937 # Write a:rPr or a:endParaRPr. 

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

939 

940 if latin_attrs or has_color: 

941 self._xml_start_tag(run_type, style_attrs) 

942 

943 if has_color: 

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

945 

946 if latin_attrs: 

947 self._write_a_latin(latin_attrs) 

948 self._write_a_cs(latin_attrs) 

949 

950 self._xml_end_tag(run_type) 

951 else: 

952 self._xml_empty_tag(run_type, style_attrs) 

953 

954 def _write_style(self) -> None: 

955 # Write the <xdr:style> element. 

956 self._xml_start_tag("xdr:style") 

957 

958 # Write the a:lnRef element. 

959 self._write_a_ln_ref() 

960 

961 # Write the a:fillRef element. 

962 self._write_a_fill_ref() 

963 

964 # Write the a:effectRef element. 

965 self._write_a_effect_ref() 

966 

967 # Write the a:fontRef element. 

968 self._write_a_font_ref() 

969 

970 self._xml_end_tag("xdr:style") 

971 

972 def _write_a_ln_ref(self) -> None: 

973 # Write the <a:lnRef> element. 

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

975 

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

977 

978 # Write the a:scrgbClr element. 

979 self._write_a_scrgb_clr() 

980 

981 self._xml_end_tag("a:lnRef") 

982 

983 def _write_a_fill_ref(self) -> None: 

984 # Write the <a:fillRef> element. 

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

986 

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

988 

989 # Write the a:scrgbClr element. 

990 self._write_a_scrgb_clr() 

991 

992 self._xml_end_tag("a:fillRef") 

993 

994 def _write_a_effect_ref(self) -> None: 

995 # Write the <a:effectRef> element. 

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

997 

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

999 

1000 # Write the a:scrgbClr element. 

1001 self._write_a_scrgb_clr() 

1002 

1003 self._xml_end_tag("a:effectRef") 

1004 

1005 def _write_a_scrgb_clr(self) -> None: 

1006 # Write the <a:scrgbClr> element. 

1007 

1008 attributes = [ 

1009 ("r", "0"), 

1010 ("g", "0"), 

1011 ("b", "0"), 

1012 ] 

1013 

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

1015 

1016 def _write_a_font_ref(self) -> None: 

1017 # Write the <a:fontRef> element. 

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

1019 

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

1021 

1022 # Write the a:schemeClr element. 

1023 self._write_a_scheme_clr("dk1") 

1024 

1025 self._xml_end_tag("a:fontRef") 

1026 

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

1028 # Write the <a:schemeClr> element. 

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

1030 

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

1032 

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

1034 # Write the <a:shade> element. 

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

1036 

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

1038 

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

1040 # Write the <a:prstDash> element. 

1041 

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

1043 

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

1045 

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

1047 # Write the <a:gradFill> element. 

1048 

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

1050 

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

1052 attributes = [] 

1053 

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

1055 

1056 # Write the a:gsLst element. 

1057 self._write_a_gs_lst(gradient) 

1058 

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

1060 # Write the a:lin element. 

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

1062 else: 

1063 # Write the a:path element. 

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

1065 

1066 # Write the a:tileRect element. 

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

1068 

1069 self._xml_end_tag("a:gradFill") 

1070 

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

1072 # Write the <a:gsLst> element. 

1073 positions = gradient["positions"] 

1074 colors = gradient["colors"] 

1075 

1076 self._xml_start_tag("a:gsLst") 

1077 

1078 for i, color in enumerate(colors): 

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

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

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

1082 

1083 # Write the a:srgbClr element. 

1084 self._write_a_srgb_clr(color) 

1085 

1086 self._xml_end_tag("a:gs") 

1087 

1088 self._xml_end_tag("a:gsLst") 

1089 

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

1091 # Write the <a:lin> element. 

1092 

1093 angle = int(60000 * angle) 

1094 

1095 attributes = [ 

1096 ("ang", angle), 

1097 ("scaled", "0"), 

1098 ] 

1099 

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

1101 

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

1103 # Write the <a:path> element. 

1104 

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

1106 

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

1108 

1109 # Write the a:fillToRect element. 

1110 self._write_a_fill_to_rect(gradient_type) 

1111 

1112 self._xml_end_tag("a:path") 

1113 

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

1115 # Write the <a:fillToRect> element. 

1116 

1117 if gradient_type == "shape": 

1118 attributes = [ 

1119 ("l", "50000"), 

1120 ("t", "50000"), 

1121 ("r", "50000"), 

1122 ("b", "50000"), 

1123 ] 

1124 else: 

1125 attributes = [ 

1126 ("l", "100000"), 

1127 ("t", "100000"), 

1128 ] 

1129 

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

1131 

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

1133 # Write the <a:tileRect> element. 

1134 

1135 if gradient_type == "shape": 

1136 attributes = [] 

1137 else: 

1138 attributes = [ 

1139 ("r", "-100000"), 

1140 ("b", "-100000"), 

1141 ] 

1142 

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

1144 

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

1146 # Write the <a:srgbClr> element. 

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

1148 

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

1150 

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

1152 # Write the <a:latin> element. 

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

1154 

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

1156 # Write the <a:latin> element. 

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