spreadsheet_drawing.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. from openpyxl.descriptors.serialisable import Serialisable
  4. from openpyxl.descriptors import (
  5. Typed,
  6. Bool,
  7. NoneSet,
  8. Integer,
  9. Sequence,
  10. Alias,
  11. )
  12. from openpyxl.descriptors.nested import (
  13. NestedText,
  14. NestedNoneSet,
  15. )
  16. from openpyxl.descriptors.excel import Relation
  17. from openpyxl.packaging.relationship import (
  18. Relationship,
  19. RelationshipList,
  20. )
  21. from openpyxl.utils import coordinate_to_tuple
  22. from openpyxl.utils.units import (
  23. cm_to_EMU,
  24. pixels_to_EMU,
  25. )
  26. from openpyxl.drawing.image import Image
  27. from openpyxl.xml.constants import SHEET_DRAWING_NS
  28. from openpyxl.chart._chart import ChartBase
  29. from .xdr import (
  30. XDRPoint2D,
  31. XDRPositiveSize2D,
  32. )
  33. from .fill import Blip
  34. from .connector import Shape
  35. from .graphic import (
  36. GroupShape,
  37. GraphicFrame,
  38. )
  39. from .geometry import PresetGeometry2D
  40. from .picture import PictureFrame
  41. from .relation import ChartRelation
  42. class AnchorClientData(Serialisable):
  43. fLocksWithSheet = Bool(allow_none=True)
  44. fPrintsWithSheet = Bool(allow_none=True)
  45. def __init__(self,
  46. fLocksWithSheet=None,
  47. fPrintsWithSheet=None,
  48. ):
  49. self.fLocksWithSheet = fLocksWithSheet
  50. self.fPrintsWithSheet = fPrintsWithSheet
  51. class AnchorMarker(Serialisable):
  52. tagname = "marker"
  53. col = NestedText(expected_type=int)
  54. colOff = NestedText(expected_type=int)
  55. row = NestedText(expected_type=int)
  56. rowOff = NestedText(expected_type=int)
  57. def __init__(self,
  58. col=0,
  59. colOff=0,
  60. row=0,
  61. rowOff=0,
  62. ):
  63. self.col = col
  64. self.colOff = colOff
  65. self.row = row
  66. self.rowOff = rowOff
  67. class _AnchorBase(Serialisable):
  68. #one of
  69. sp = Typed(expected_type=Shape, allow_none=True)
  70. shape = Alias("sp")
  71. grpSp = Typed(expected_type=GroupShape, allow_none=True)
  72. groupShape = Alias("grpSp")
  73. graphicFrame = Typed(expected_type=GraphicFrame, allow_none=True)
  74. cxnSp = Typed(expected_type=Shape, allow_none=True)
  75. connectionShape = Alias("cxnSp")
  76. pic = Typed(expected_type=PictureFrame, allow_none=True)
  77. contentPart = Relation()
  78. clientData = Typed(expected_type=AnchorClientData)
  79. __elements__ = ('sp', 'grpSp', 'graphicFrame',
  80. 'cxnSp', 'pic', 'contentPart', 'clientData')
  81. def __init__(self,
  82. clientData=None,
  83. sp=None,
  84. grpSp=None,
  85. graphicFrame=None,
  86. cxnSp=None,
  87. pic=None,
  88. contentPart=None
  89. ):
  90. if clientData is None:
  91. clientData = AnchorClientData()
  92. self.clientData = clientData
  93. self.sp = sp
  94. self.grpSp = grpSp
  95. self.graphicFrame = graphicFrame
  96. self.cxnSp = cxnSp
  97. self.pic = pic
  98. self.contentPart = contentPart
  99. class AbsoluteAnchor(_AnchorBase):
  100. tagname = "absoluteAnchor"
  101. pos = Typed(expected_type=XDRPoint2D)
  102. ext = Typed(expected_type=XDRPositiveSize2D)
  103. sp = _AnchorBase.sp
  104. grpSp = _AnchorBase.grpSp
  105. graphicFrame = _AnchorBase.graphicFrame
  106. cxnSp = _AnchorBase.cxnSp
  107. pic = _AnchorBase.pic
  108. contentPart = _AnchorBase.contentPart
  109. clientData = _AnchorBase.clientData
  110. __elements__ = ('pos', 'ext') + _AnchorBase.__elements__
  111. def __init__(self,
  112. pos=None,
  113. ext=None,
  114. **kw
  115. ):
  116. if pos is None:
  117. pos = XDRPoint2D(0, 0)
  118. self.pos = pos
  119. if ext is None:
  120. ext = XDRPositiveSize2D(0, 0)
  121. self.ext = ext
  122. super(AbsoluteAnchor, self).__init__(**kw)
  123. class OneCellAnchor(_AnchorBase):
  124. tagname = "oneCellAnchor"
  125. _from = Typed(expected_type=AnchorMarker)
  126. ext = Typed(expected_type=XDRPositiveSize2D)
  127. sp = _AnchorBase.sp
  128. grpSp = _AnchorBase.grpSp
  129. graphicFrame = _AnchorBase.graphicFrame
  130. cxnSp = _AnchorBase.cxnSp
  131. pic = _AnchorBase.pic
  132. contentPart = _AnchorBase.contentPart
  133. clientData = _AnchorBase.clientData
  134. __elements__ = ('_from', 'ext') + _AnchorBase.__elements__
  135. def __init__(self,
  136. _from=None,
  137. ext=None,
  138. **kw
  139. ):
  140. if _from is None:
  141. _from = AnchorMarker()
  142. self._from = _from
  143. if ext is None:
  144. ext = XDRPositiveSize2D(0, 0)
  145. self.ext = ext
  146. super(OneCellAnchor, self).__init__(**kw)
  147. class TwoCellAnchor(_AnchorBase):
  148. tagname = "twoCellAnchor"
  149. editAs = NoneSet(values=(['twoCell', 'oneCell', 'absolute']))
  150. _from = Typed(expected_type=AnchorMarker)
  151. to = Typed(expected_type=AnchorMarker)
  152. sp = _AnchorBase.sp
  153. grpSp = _AnchorBase.grpSp
  154. graphicFrame = _AnchorBase.graphicFrame
  155. cxnSp = _AnchorBase.cxnSp
  156. pic = _AnchorBase.pic
  157. contentPart = _AnchorBase.contentPart
  158. clientData = _AnchorBase.clientData
  159. __elements__ = ('_from', 'to') + _AnchorBase.__elements__
  160. def __init__(self,
  161. editAs=None,
  162. _from=None,
  163. to=None,
  164. **kw
  165. ):
  166. self.editAs = editAs
  167. if _from is None:
  168. _from = AnchorMarker()
  169. self._from = _from
  170. if to is None:
  171. to = AnchorMarker()
  172. self.to = to
  173. super(TwoCellAnchor, self).__init__(**kw)
  174. def _check_anchor(obj):
  175. """
  176. Check whether an object has an existing Anchor object
  177. If not create a OneCellAnchor using the provided coordinate
  178. """
  179. anchor = obj.anchor
  180. if not isinstance(anchor, _AnchorBase):
  181. row, col = coordinate_to_tuple(anchor.upper())
  182. anchor = OneCellAnchor()
  183. anchor._from.row = row -1
  184. anchor._from.col = col -1
  185. if isinstance(obj, ChartBase):
  186. anchor.ext.width = cm_to_EMU(obj.width)
  187. anchor.ext.height = cm_to_EMU(obj.height)
  188. elif isinstance(obj, Image):
  189. anchor.ext.width = pixels_to_EMU(obj.width)
  190. anchor.ext.height = pixels_to_EMU(obj.height)
  191. return anchor
  192. class SpreadsheetDrawing(Serialisable):
  193. tagname = "wsDr"
  194. mime_type = "application/vnd.openxmlformats-officedocument.drawing+xml"
  195. _rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
  196. _path = PartName="/xl/drawings/drawing{0}.xml"
  197. _id = None
  198. twoCellAnchor = Sequence(expected_type=TwoCellAnchor, allow_none=True)
  199. oneCellAnchor = Sequence(expected_type=OneCellAnchor, allow_none=True)
  200. absoluteAnchor = Sequence(expected_type=AbsoluteAnchor, allow_none=True)
  201. __elements__ = ("twoCellAnchor", "oneCellAnchor", "absoluteAnchor")
  202. def __init__(self,
  203. twoCellAnchor=(),
  204. oneCellAnchor=(),
  205. absoluteAnchor=(),
  206. ):
  207. self.twoCellAnchor = twoCellAnchor
  208. self.oneCellAnchor = oneCellAnchor
  209. self.absoluteAnchor = absoluteAnchor
  210. self.charts = []
  211. self.images = []
  212. self._rels = []
  213. def __hash__(self):
  214. """
  215. Just need to check for identity
  216. """
  217. return id(self)
  218. def __bool__(self):
  219. return bool(self.charts) or bool(self.images)
  220. __nonzero__ = __bool__
  221. def _write(self):
  222. """
  223. create required structure and the serialise
  224. """
  225. anchors = []
  226. for idx, obj in enumerate(self.charts + self.images, 1):
  227. anchor = _check_anchor(obj)
  228. if isinstance(obj, ChartBase):
  229. rel = Relationship(type="chart", Target=obj.path)
  230. anchor.graphicFrame = self._chart_frame(idx)
  231. elif isinstance(obj, Image):
  232. rel = Relationship(type="image", Target=obj.path)
  233. child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
  234. if not child:
  235. anchor.pic = self._picture_frame(idx)
  236. else:
  237. child.blipFill.blip.embed = "rId{0}".format(idx)
  238. anchors.append(anchor)
  239. self._rels.append(rel)
  240. for a in anchors:
  241. if isinstance(a, OneCellAnchor):
  242. self.oneCellAnchor.append(a)
  243. elif isinstance(a, TwoCellAnchor):
  244. self.twoCellAnchor.append(a)
  245. else:
  246. self.absoluteAnchor.append(a)
  247. tree = self.to_tree()
  248. tree.set('xmlns', SHEET_DRAWING_NS)
  249. return tree
  250. def _chart_frame(self, idx):
  251. chart_rel = ChartRelation("rId%s" % idx)
  252. frame = GraphicFrame()
  253. nv = frame.nvGraphicFramePr.cNvPr
  254. nv.id = idx
  255. nv.name = "Chart {0}".format(idx)
  256. frame.graphic.graphicData.chart = chart_rel
  257. return frame
  258. def _picture_frame(self, idx):
  259. pic = PictureFrame()
  260. pic.nvPicPr.cNvPr.descr = "Picture"
  261. pic.nvPicPr.cNvPr.id = idx
  262. pic.nvPicPr.cNvPr.name = "Image {0}".format(idx)
  263. pic.blipFill.blip = Blip()
  264. pic.blipFill.blip.embed = "rId{0}".format(idx)
  265. pic.blipFill.blip.cstate = "print"
  266. pic.spPr.prstGeom = PresetGeometry2D(prst="rect")
  267. pic.spPr.ln = None
  268. return pic
  269. def _write_rels(self):
  270. rels = RelationshipList()
  271. rels.Relationship = self._rels
  272. return rels.to_tree()
  273. @property
  274. def path(self):
  275. return self._path.format(self._id)
  276. @property
  277. def _chart_rels(self):
  278. """
  279. Get relationship information for each chart and bind anchor to it
  280. """
  281. rels = []
  282. anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
  283. for anchor in anchors:
  284. if anchor.graphicFrame is not None:
  285. graphic = anchor.graphicFrame.graphic
  286. rel = graphic.graphicData.chart
  287. if rel is not None:
  288. rel.anchor = anchor
  289. rel.anchor.graphicFrame = None
  290. rels.append(rel)
  291. return rels
  292. @property
  293. def _blip_rels(self):
  294. """
  295. Get relationship information for each blip and bind anchor to it
  296. """
  297. rels = []
  298. anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
  299. for anchor in anchors:
  300. child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
  301. if child and child.blipFill:
  302. rel = child.blipFill.blip
  303. if rel is not None:
  304. rel.anchor = anchor
  305. rels.append(rel)
  306. return rels