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.color import Color 

13from xlsxwriter.url import Url 

14 

15from . import xmlwriter 

16from .shape import Shape 

17 

18 

19class DrawingTypes(Enum): 

20 """ 

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

22 """ 

23 

24 NONE = 0 

25 CHART = 1 

26 IMAGE = 2 

27 SHAPE = 3 

28 

29 

30class DrawingInfo: 

31 """ 

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

33 

34 """ 

35 

36 def __init__(self) -> None: 

37 """ 

38 Initialize a DrawingType instance with default values. 

39 """ 

40 self._drawing_type = DrawingTypes.NONE 

41 self._anchor_type = None 

42 self._dimensions = [] 

43 self._width = 0 

44 self._height = 0 

45 self._shape = None 

46 self._anchor = None 

47 self._url = None 

48 self._rel_index = 0 

49 self._name = None 

50 self._description = None 

51 self._decorative = False 

52 

53 

54class Drawing(xmlwriter.XMLwriter): 

55 """ 

56 A class for writing the Excel XLSX Drawing file. 

57 

58 

59 """ 

60 

61 ########################################################################### 

62 # 

63 # Public API. 

64 # 

65 ########################################################################### 

66 

67 def __init__(self) -> None: 

68 """ 

69 Constructor. 

70 

71 """ 

72 

73 super().__init__() 

74 

75 self.drawings = [] 

76 self.embedded = 0 

77 self.orientation = 0 

78 

79 ########################################################################### 

80 # 

81 # Private API. 

82 # 

83 ########################################################################### 

84 

85 def _assemble_xml_file(self) -> None: 

86 # Assemble and write the XML file. 

87 

88 # Write the XML declaration. 

89 self._xml_declaration() 

90 

91 # Write the xdr:wsDr element. 

92 self._write_drawing_workspace() 

93 

94 if self.embedded: 

95 index = 0 

96 for drawing in self.drawings: 

97 # Write the xdr:twoCellAnchor element. 

98 index += 1 

99 self._write_two_cell_anchor(index, drawing) 

100 

101 else: 

102 # Write the xdr:absoluteAnchor element. 

103 drawing = DrawingInfo() 

104 drawing._rel_index = 1 

105 self._write_absolute_anchor(1, drawing) 

106 

107 self._xml_end_tag("xdr:wsDr") 

108 

109 # Close the file. 

110 self._xml_close() 

111 

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

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

114 self.drawings.append(drawing_object) 

115 

116 ########################################################################### 

117 # 

118 # XML methods. 

119 # 

120 ########################################################################### 

121 

122 def _write_drawing_workspace(self) -> None: 

123 # Write the <xdr:wsDr> element. 

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

125 xmlns_xdr = schema + "2006/spreadsheetDrawing" 

126 xmlns_a = schema + "2006/main" 

127 

128 attributes = [ 

129 ("xmlns:xdr", xmlns_xdr), 

130 ("xmlns:a", xmlns_a), 

131 ] 

132 

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

134 

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

136 # Write the <xdr:twoCellAnchor> element. 

137 dimensions = drawing._dimensions 

138 col_from = dimensions[0] 

139 row_from = dimensions[1] 

140 col_from_offset = dimensions[2] 

141 row_from_offset = dimensions[3] 

142 col_to = dimensions[4] 

143 row_to = dimensions[5] 

144 col_to_offset = dimensions[6] 

145 row_to_offset = dimensions[7] 

146 col_absolute = dimensions[8] 

147 row_absolute = dimensions[9] 

148 

149 attributes = [] 

150 

151 # Add attribute for positioning. 

152 if drawing._anchor == 2: 

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

154 elif drawing._anchor == 3: 

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

156 

157 # Add editAs attribute for shapes. 

158 if drawing._shape and drawing._shape.edit_as: 

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

160 

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

162 

163 # Write the xdr:from element. 

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

165 

166 # Write the xdr:from element. 

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

168 

169 if drawing._drawing_type == DrawingTypes.CHART: 

170 # Graphic frame. 

171 # Write the xdr:graphicFrame element for charts. 

172 self._write_graphic_frame(index, drawing) 

173 elif drawing._drawing_type == DrawingTypes.IMAGE: 

174 # Write the xdr:pic element. 

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

176 else: 

177 # Write the xdr:sp element for shapes. 

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

179 

180 # Write the xdr:clientData element. 

181 self._write_client_data() 

182 

183 self._xml_end_tag("xdr:twoCellAnchor") 

184 

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

186 self._xml_start_tag("xdr:absoluteAnchor") 

187 # Write the <xdr:absoluteAnchor> element. 

188 

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

190 if self.orientation == 0: 

191 # Write the xdr:pos element. 

192 self._write_pos(0, 0) 

193 

194 # Write the xdr:ext element. 

195 self._write_xdr_ext(9308969, 6078325) 

196 

197 else: 

198 # Write the xdr:pos element. 

199 self._write_pos(0, -47625) 

200 

201 # Write the xdr:ext element. 

202 self._write_xdr_ext(6162675, 6124575) 

203 

204 # Write the xdr:graphicFrame element. 

205 self._write_graphic_frame(index, drawing) 

206 

207 # Write the xdr:clientData element. 

208 self._write_client_data() 

209 

210 self._xml_end_tag("xdr:absoluteAnchor") 

211 

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

213 # Write the <xdr:from> element. 

214 self._xml_start_tag("xdr:from") 

215 

216 # Write the xdr:col element. 

217 self._write_col(col) 

218 

219 # Write the xdr:colOff element. 

220 self._write_col_off(col_offset) 

221 

222 # Write the xdr:row element. 

223 self._write_row(row) 

224 

225 # Write the xdr:rowOff element. 

226 self._write_row_off(row_offset) 

227 

228 self._xml_end_tag("xdr:from") 

229 

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

231 # Write the <xdr:to> element. 

232 self._xml_start_tag("xdr:to") 

233 

234 # Write the xdr:col element. 

235 self._write_col(col) 

236 

237 # Write the xdr:colOff element. 

238 self._write_col_off(col_offset) 

239 

240 # Write the xdr:row element. 

241 self._write_row(row) 

242 

243 # Write the xdr:rowOff element. 

244 self._write_row_off(row_offset) 

245 

246 self._xml_end_tag("xdr:to") 

247 

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

249 # Write the <xdr:col> element. 

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

251 

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

253 # Write the <xdr:colOff> element. 

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

255 

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

257 # Write the <xdr:row> element. 

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

259 

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

261 # Write the <xdr:rowOff> element. 

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

263 

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

265 # Write the <xdr:pos> element. 

266 

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

268 

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

270 

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

272 # Write the <xdr:ext> element. 

273 

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

275 

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

277 

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

279 # Write the <xdr:graphicFrame> element. 

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

281 

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

283 

284 # Write the xdr:nvGraphicFramePr element. 

285 self._write_nv_graphic_frame_pr(index, drawing) 

286 

287 # Write the xdr:xfrm element. 

288 self._write_xfrm() 

289 

290 # Write the a:graphic element. 

291 self._write_atag_graphic(drawing._rel_index) 

292 

293 self._xml_end_tag("xdr:graphicFrame") 

294 

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

296 # Write the <xdr:nvGraphicFramePr> element. 

297 

298 name = drawing._name 

299 if not name: 

300 name = "Chart " + str(index) 

301 

302 self._xml_start_tag("xdr:nvGraphicFramePr") 

303 

304 # Write the xdr:cNvPr element. 

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

306 

307 # Write the xdr:cNvGraphicFramePr element. 

308 self._write_c_nv_graphic_frame_pr() 

309 

310 self._xml_end_tag("xdr:nvGraphicFramePr") 

311 

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

313 # Write the <xdr:cNvPr> element. 

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

315 

316 # Add description attribute for images. 

317 if drawing._description and not drawing._decorative: 

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

319 

320 if drawing._url or drawing._decorative: 

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

322 

323 if drawing._url: 

324 self._write_a_hlink_click(drawing._url) 

325 

326 if drawing._decorative: 

327 self._write_decorative() 

328 

329 self._xml_end_tag("xdr:cNvPr") 

330 else: 

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

332 

333 def _write_decorative(self) -> None: 

334 self._xml_start_tag("a:extLst") 

335 

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

337 self._write_a16_creation_id() 

338 self._xml_end_tag("a:ext") 

339 

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

341 self._write_adec_decorative() 

342 self._xml_end_tag("a:ext") 

343 

344 self._xml_end_tag("a:extLst") 

345 

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

347 # Write the <a:ext> element. 

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

349 

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

351 

352 def _write_adec_decorative(self) -> None: 

353 # Write the <adec:decorative> element. 

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

355 val = "1" 

356 

357 attributes = [ 

358 ("xmlns:adec", xmlns), 

359 ("val", val), 

360 ] 

361 

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

363 

364 def _write_a16_creation_id(self) -> None: 

365 # Write the <a16:creationId> element. 

366 

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

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

369 

370 attributes = [ 

371 ("xmlns:a16", xmlns_a_16), 

372 ("id", creation_id), 

373 ] 

374 

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

376 

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

378 # Write the <a:hlinkClick> element. 

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

380 xmlns_r = schema + "2006/relationships" 

381 

382 attributes = [ 

383 ("xmlns:r", xmlns_r), 

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

385 ] 

386 

387 if url._tip: 

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

389 

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

391 

392 def _write_c_nv_graphic_frame_pr(self) -> None: 

393 # Write the <xdr:cNvGraphicFramePr> element. 

394 if self.embedded: 

395 self._xml_empty_tag("xdr:cNvGraphicFramePr") 

396 else: 

397 self._xml_start_tag("xdr:cNvGraphicFramePr") 

398 

399 # Write the a:graphicFrameLocks element. 

400 self._write_a_graphic_frame_locks() 

401 

402 self._xml_end_tag("xdr:cNvGraphicFramePr") 

403 

404 def _write_a_graphic_frame_locks(self) -> None: 

405 # Write the <a:graphicFrameLocks> element. 

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

407 

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

409 

410 def _write_xfrm(self) -> None: 

411 # Write the <xdr:xfrm> element. 

412 self._xml_start_tag("xdr:xfrm") 

413 

414 # Write the xfrmOffset element. 

415 self._write_xfrm_offset() 

416 

417 # Write the xfrmOffset element. 

418 self._write_xfrm_extension() 

419 

420 self._xml_end_tag("xdr:xfrm") 

421 

422 def _write_xfrm_offset(self) -> None: 

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

424 

425 attributes = [ 

426 ("x", 0), 

427 ("y", 0), 

428 ] 

429 

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

431 

432 def _write_xfrm_extension(self) -> None: 

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

434 

435 attributes = [ 

436 ("cx", 0), 

437 ("cy", 0), 

438 ] 

439 

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

441 

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

443 # Write the <a:graphic> element. 

444 self._xml_start_tag("a:graphic") 

445 

446 # Write the a:graphicData element. 

447 self._write_atag_graphic_data(index) 

448 

449 self._xml_end_tag("a:graphic") 

450 

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

452 # Write the <a:graphicData> element. 

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

454 

455 attributes = [ 

456 ( 

457 "uri", 

458 uri, 

459 ) 

460 ] 

461 

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

463 

464 # Write the c:chart element. 

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

466 

467 self._xml_end_tag("a:graphicData") 

468 

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

470 # Write the <c:chart> element. 

471 

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

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

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

475 

476 attributes = [ 

477 ("xmlns:c", xmlns_c), 

478 ("xmlns:r", xmlns_r), 

479 ("r:id", r_id), 

480 ] 

481 

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

483 

484 def _write_client_data(self) -> None: 

485 # Write the <xdr:clientData> element. 

486 self._xml_empty_tag("xdr:clientData") 

487 

488 def _write_sp( 

489 self, 

490 index, 

491 col_absolute, 

492 row_absolute, 

493 drawing: DrawingInfo, 

494 ) -> None: 

495 # Write the <xdr:sp> element. 

496 

497 if drawing._shape and drawing._shape.connect: 

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

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

500 

501 # Write the xdr:nvCxnSpPr element. 

502 self._write_nv_cxn_sp_pr(drawing._shape) 

503 

504 # Write the xdr:spPr element. 

505 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing) 

506 

507 self._xml_end_tag("xdr:cxnSp") 

508 else: 

509 # Add attribute for shapes. 

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

511 

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

513 

514 # Write the xdr:nvSpPr element. 

515 self._write_nv_sp_pr(index, drawing) 

516 

517 # Write the xdr:spPr element. 

518 self._write_xdr_sp_pr(col_absolute, row_absolute, drawing) 

519 

520 # Write the xdr:style element. 

521 self._write_style() 

522 

523 # Write the xdr:txBody element. 

524 if drawing._shape.text is not None: 

525 self._write_tx_body(drawing._shape) 

526 

527 self._xml_end_tag("xdr:sp") 

528 

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

530 # Write the <xdr:nvCxnSpPr> element. 

531 self._xml_start_tag("xdr:nvCxnSpPr") 

532 

533 self._xml_start_tag("xdr:cNvCxnSpPr") 

534 

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

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

537 

538 if shape.start: 

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

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

541 

542 if shape.end: 

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

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

545 

546 self._xml_end_tag("xdr:cNvCxnSpPr") 

547 self._xml_end_tag("xdr:nvCxnSpPr") 

548 

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

550 # Write the <xdr:NvSpPr> element. 

551 attributes = [] 

552 

553 self._xml_start_tag("xdr:nvSpPr") 

554 

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

556 

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

558 

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

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

561 

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

563 

564 self._xml_end_tag("xdr:nvSpPr") 

565 

566 def _write_pic( 

567 self, 

568 index: int, 

569 col_absolute: int, 

570 row_absolute: int, 

571 drawing: DrawingInfo, 

572 ) -> None: 

573 # Write the <xdr:pic> element. 

574 self._xml_start_tag("xdr:pic") 

575 

576 # Write the xdr:nvPicPr element. 

577 self._write_nv_pic_pr(index, drawing) 

578 # Write the xdr:blipFill element. 

579 self._write_blip_fill(drawing._rel_index) 

580 

581 # Write the xdr:spPr element. 

582 self._write_sp_pr(col_absolute, row_absolute, drawing) 

583 

584 self._xml_end_tag("xdr:pic") 

585 

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

587 # Write the <xdr:nvPicPr> element. 

588 self._xml_start_tag("xdr:nvPicPr") 

589 

590 name = "Picture " + str(index) 

591 

592 # Write the xdr:cNvPr element. 

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

594 

595 # Write the xdr:cNvPicPr element. 

596 self._write_c_nv_pic_pr() 

597 

598 self._xml_end_tag("xdr:nvPicPr") 

599 

600 def _write_c_nv_pic_pr(self) -> None: 

601 # Write the <xdr:cNvPicPr> element. 

602 self._xml_start_tag("xdr:cNvPicPr") 

603 

604 # Write the a:picLocks element. 

605 self._write_a_pic_locks() 

606 

607 self._xml_end_tag("xdr:cNvPicPr") 

608 

609 def _write_a_pic_locks(self) -> None: 

610 # Write the <a:picLocks> element. 

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

612 

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

614 

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

616 # Write the <xdr:blipFill> element. 

617 self._xml_start_tag("xdr:blipFill") 

618 

619 # Write the a:blip element. 

620 self._write_a_blip(index) 

621 

622 # Write the a:stretch element. 

623 self._write_a_stretch() 

624 

625 self._xml_end_tag("xdr:blipFill") 

626 

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

628 # Write the <a:blip> element. 

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

630 xmlns_r = schema + "2006/relationships" 

631 r_embed = "rId" + str(index) 

632 

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

634 

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

636 

637 def _write_a_stretch(self) -> None: 

638 # Write the <a:stretch> element. 

639 self._xml_start_tag("a:stretch") 

640 

641 # Write the a:fillRect element. 

642 self._write_a_fill_rect() 

643 

644 self._xml_end_tag("a:stretch") 

645 

646 def _write_a_fill_rect(self) -> None: 

647 # Write the <a:fillRect> element. 

648 self._xml_empty_tag("a:fillRect") 

649 

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

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

652 

653 self._xml_start_tag("xdr:spPr") 

654 

655 # Write the a:xfrm element. 

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

657 

658 # Write the a:prstGeom element. 

659 self._write_a_prst_geom(drawing._shape) 

660 

661 self._xml_end_tag("xdr:spPr") 

662 

663 def _write_xdr_sp_pr( 

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

665 ) -> None: 

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

667 self._xml_start_tag("xdr:spPr") 

668 

669 # Write the a:xfrm element. 

670 self._write_a_xfrm( 

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

672 ) 

673 

674 # Write the a:prstGeom element. 

675 shape = drawing._shape 

676 self._write_a_prst_geom(shape) 

677 

678 if shape.fill: 

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

680 # Write the a:solidFill element. 

681 self._write_a_solid_fill_scheme("lt1") 

682 elif "none" in shape.fill: 

683 # Write the a:noFill element. 

684 self._xml_empty_tag("a:noFill") 

685 elif "color" in shape.fill: 

686 # Write the a:solidFill element. 

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

688 

689 if shape.gradient: 

690 # Write the a:gradFill element. 

691 self._write_a_grad_fill(shape.gradient) 

692 

693 # Write the a:ln element. 

694 self._write_a_ln(shape.line) 

695 

696 self._xml_end_tag("xdr:spPr") 

697 

698 def _write_a_xfrm( 

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

700 ) -> None: 

701 # Write the <a:xfrm> element. 

702 attributes = [] 

703 

704 if shape: 

705 if shape.rotation: 

706 rotation = shape.rotation 

707 rotation *= 60000 

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

709 

710 if shape.flip_h: 

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

712 if shape.flip_v: 

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

714 

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

716 

717 # Write the a:off element. 

718 self._write_a_off(col_absolute, row_absolute) 

719 

720 # Write the a:ext element. 

721 self._write_a_ext(width, height) 

722 

723 self._xml_end_tag("a:xfrm") 

724 

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

726 # Write the <a:off> element. 

727 attributes = [ 

728 ("x", x), 

729 ("y", y), 

730 ] 

731 

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

733 

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

735 # Write the <a:ext> element. 

736 attributes = [ 

737 ("cx", cx), 

738 ("cy", cy), 

739 ] 

740 

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

742 

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

744 # Write the <a:prstGeom> element. 

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

746 

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

748 

749 # Write the a:avLst element. 

750 self._write_a_av_lst(shape) 

751 

752 self._xml_end_tag("a:prstGeom") 

753 

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

755 # Write the <a:avLst> element. 

756 adjustments = [] 

757 

758 if shape and shape.adjustments: 

759 adjustments = shape.adjustments 

760 

761 if adjustments: 

762 self._xml_start_tag("a:avLst") 

763 

764 i = 0 

765 for adj in adjustments: 

766 i += 1 

767 # Only connectors have multiple adjustments. 

768 if shape.connect: 

769 suffix = i 

770 else: 

771 suffix = "" 

772 

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

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

775 

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

777 

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

779 

780 self._xml_end_tag("a:avLst") 

781 else: 

782 self._xml_empty_tag("a:avLst") 

783 

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

785 # Write the <a:solidFill> element. 

786 self._xml_start_tag("a:solidFill") 

787 

788 # Write the a:srgbClr element. 

789 self._write_a_srgb_clr(color) 

790 

791 self._xml_end_tag("a:solidFill") 

792 

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

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

795 

796 self._xml_start_tag("a:solidFill") 

797 

798 if shade: 

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

800 self._write_a_shade(shade) 

801 self._xml_end_tag("a:schemeClr") 

802 else: 

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

804 

805 self._xml_end_tag("a:solidFill") 

806 

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

808 # Write the <a:ln> element. 

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

810 

811 # Round width to nearest 0.25, like Excel. 

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

813 

814 # Convert to internal units. 

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

816 

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

818 

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

820 

821 if "none" in line: 

822 # Write the a:noFill element. 

823 self._xml_empty_tag("a:noFill") 

824 

825 elif "color" in line: 

826 # Write the a:solidFill element. 

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

828 

829 else: 

830 # Write the a:solidFill element. 

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

832 

833 # Write the line/dash type. 

834 line_type = line.get("dash_type") 

835 if line_type: 

836 # Write the a:prstDash element. 

837 self._write_a_prst_dash(line_type) 

838 

839 self._xml_end_tag("a:ln") 

840 

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

842 # Write the <xdr:txBody> element. 

843 attributes = [] 

844 

845 if shape.text_rotation != 0: 

846 if shape.text_rotation == 90: 

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

848 if shape.text_rotation == -90: 

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

850 if shape.text_rotation == 270: 

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

852 if shape.text_rotation == 271: 

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

854 

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

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

857 

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

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

860 else: 

861 if "vertical" in shape.align: 

862 align = shape.align["vertical"] 

863 if align == "top": 

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

865 elif align == "middle": 

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

867 elif align == "bottom": 

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

869 else: 

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

871 

872 if "horizontal" in shape.align: 

873 align = shape.align["horizontal"] 

874 if align == "center": 

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

876 else: 

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

878 

879 self._xml_start_tag("xdr:txBody") 

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

881 self._xml_empty_tag("a:lstStyle") 

882 

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

884 

885 # Set the font attributes. 

886 font = shape.font 

887 # pylint: disable=protected-access 

888 style_attrs = Shape._get_font_style_attributes(font) 

889 latin_attrs = Shape._get_font_latin_attributes(font) 

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

891 

892 if shape.textlink != "": 

893 attributes = [ 

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

895 ("type", "TxLink"), 

896 ] 

897 

898 self._xml_start_tag("a:p") 

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

900 

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

902 

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

904 self._xml_end_tag("a:fld") 

905 

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

907 

908 self._xml_end_tag("a:p") 

909 else: 

910 for line in lines: 

911 self._xml_start_tag("a:p") 

912 

913 if line == "": 

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

915 self._xml_end_tag("a:p") 

916 continue 

917 

918 if "text" in shape.align: 

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

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

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

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

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

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

925 

926 self._xml_start_tag("a:r") 

927 

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

929 

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

931 

932 self._xml_end_tag("a:r") 

933 self._xml_end_tag("a:p") 

934 

935 self._xml_end_tag("xdr:txBody") 

936 

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

938 # Write a:rPr or a:endParaRPr. 

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

940 

941 if latin_attrs or has_color: 

942 self._xml_start_tag(run_type, style_attrs) 

943 

944 if has_color: 

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

946 

947 if latin_attrs: 

948 self._write_a_latin(latin_attrs) 

949 self._write_a_cs(latin_attrs) 

950 

951 self._xml_end_tag(run_type) 

952 else: 

953 self._xml_empty_tag(run_type, style_attrs) 

954 

955 def _write_style(self) -> None: 

956 # Write the <xdr:style> element. 

957 self._xml_start_tag("xdr:style") 

958 

959 # Write the a:lnRef element. 

960 self._write_a_ln_ref() 

961 

962 # Write the a:fillRef element. 

963 self._write_a_fill_ref() 

964 

965 # Write the a:effectRef element. 

966 self._write_a_effect_ref() 

967 

968 # Write the a:fontRef element. 

969 self._write_a_font_ref() 

970 

971 self._xml_end_tag("xdr:style") 

972 

973 def _write_a_ln_ref(self) -> None: 

974 # Write the <a:lnRef> element. 

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

976 

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

978 

979 # Write the a:scrgbClr element. 

980 self._write_a_scrgb_clr() 

981 

982 self._xml_end_tag("a:lnRef") 

983 

984 def _write_a_fill_ref(self) -> None: 

985 # Write the <a:fillRef> element. 

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

987 

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

989 

990 # Write the a:scrgbClr element. 

991 self._write_a_scrgb_clr() 

992 

993 self._xml_end_tag("a:fillRef") 

994 

995 def _write_a_effect_ref(self) -> None: 

996 # Write the <a:effectRef> element. 

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

998 

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

1000 

1001 # Write the a:scrgbClr element. 

1002 self._write_a_scrgb_clr() 

1003 

1004 self._xml_end_tag("a:effectRef") 

1005 

1006 def _write_a_scrgb_clr(self) -> None: 

1007 # Write the <a:scrgbClr> element. 

1008 

1009 attributes = [ 

1010 ("r", "0"), 

1011 ("g", "0"), 

1012 ("b", "0"), 

1013 ] 

1014 

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

1016 

1017 def _write_a_font_ref(self) -> None: 

1018 # Write the <a:fontRef> element. 

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

1020 

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

1022 

1023 # Write the a:schemeClr element. 

1024 self._write_a_scheme_clr("dk1") 

1025 

1026 self._xml_end_tag("a:fontRef") 

1027 

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

1029 # Write the <a:schemeClr> element. 

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

1031 

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

1033 

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

1035 # Write the <a:shade> element. 

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

1037 

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

1039 

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

1041 # Write the <a:prstDash> element. 

1042 

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

1044 

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

1046 

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

1048 # Write the <a:gradFill> element. 

1049 

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

1051 

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

1053 attributes = [] 

1054 

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

1056 

1057 # Write the a:gsLst element. 

1058 self._write_a_gs_lst(gradient) 

1059 

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

1061 # Write the a:lin element. 

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

1063 else: 

1064 # Write the a:path element. 

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

1066 

1067 # Write the a:tileRect element. 

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

1069 

1070 self._xml_end_tag("a:gradFill") 

1071 

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

1073 # Write the <a:gsLst> element. 

1074 positions = gradient["positions"] 

1075 colors = gradient["colors"] 

1076 

1077 self._xml_start_tag("a:gsLst") 

1078 

1079 for i, color in enumerate(colors): 

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

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

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

1083 

1084 # Write the a:srgbClr element. 

1085 self._write_a_srgb_clr(color) 

1086 

1087 self._xml_end_tag("a:gs") 

1088 

1089 self._xml_end_tag("a:gsLst") 

1090 

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

1092 # Write the <a:lin> element. 

1093 

1094 angle = int(60000 * angle) 

1095 

1096 attributes = [ 

1097 ("ang", angle), 

1098 ("scaled", "0"), 

1099 ] 

1100 

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

1102 

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

1104 # Write the <a:path> element. 

1105 

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

1107 

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

1109 

1110 # Write the a:fillToRect element. 

1111 self._write_a_fill_to_rect(gradient_type) 

1112 

1113 self._xml_end_tag("a:path") 

1114 

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

1116 # Write the <a:fillToRect> element. 

1117 

1118 if gradient_type == "shape": 

1119 attributes = [ 

1120 ("l", "50000"), 

1121 ("t", "50000"), 

1122 ("r", "50000"), 

1123 ("b", "50000"), 

1124 ] 

1125 else: 

1126 attributes = [ 

1127 ("l", "100000"), 

1128 ("t", "100000"), 

1129 ] 

1130 

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

1132 

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

1134 # Write the <a:tileRect> element. 

1135 

1136 if gradient_type == "shape": 

1137 attributes = [] 

1138 else: 

1139 attributes = [ 

1140 ("r", "-100000"), 

1141 ("b", "-100000"), 

1142 ] 

1143 

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

1145 

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

1147 # Write the <a:srgbClr> element. 

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

1149 

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

1151 

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

1153 # Write the <a:latin> element. 

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

1155 

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

1157 # Write the <a:latin> element. 

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