fills.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. from __future__ import absolute_import, division
  2. # Copyright (c) 2010-2019 openpyxl
  3. from openpyxl.descriptors import (
  4. Float,
  5. Set,
  6. Alias,
  7. NoneSet,
  8. Sequence,
  9. Integer,
  10. MinMax,
  11. )
  12. from openpyxl.descriptors.serialisable import Serialisable
  13. from openpyxl.compat import safe_string
  14. from .colors import ColorDescriptor, Color
  15. from openpyxl.xml.functions import Element, localname
  16. from openpyxl.xml.constants import SHEET_MAIN_NS
  17. FILL_NONE = 'none'
  18. FILL_SOLID = 'solid'
  19. FILL_PATTERN_DARKDOWN = 'darkDown'
  20. FILL_PATTERN_DARKGRAY = 'darkGray'
  21. FILL_PATTERN_DARKGRID = 'darkGrid'
  22. FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal'
  23. FILL_PATTERN_DARKTRELLIS = 'darkTrellis'
  24. FILL_PATTERN_DARKUP = 'darkUp'
  25. FILL_PATTERN_DARKVERTICAL = 'darkVertical'
  26. FILL_PATTERN_GRAY0625 = 'gray0625'
  27. FILL_PATTERN_GRAY125 = 'gray125'
  28. FILL_PATTERN_LIGHTDOWN = 'lightDown'
  29. FILL_PATTERN_LIGHTGRAY = 'lightGray'
  30. FILL_PATTERN_LIGHTGRID = 'lightGrid'
  31. FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal'
  32. FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis'
  33. FILL_PATTERN_LIGHTUP = 'lightUp'
  34. FILL_PATTERN_LIGHTVERTICAL = 'lightVertical'
  35. FILL_PATTERN_MEDIUMGRAY = 'mediumGray'
  36. fills = (FILL_SOLID, FILL_PATTERN_DARKDOWN, FILL_PATTERN_DARKGRAY,
  37. FILL_PATTERN_DARKGRID, FILL_PATTERN_DARKHORIZONTAL, FILL_PATTERN_DARKTRELLIS,
  38. FILL_PATTERN_DARKUP, FILL_PATTERN_DARKVERTICAL, FILL_PATTERN_GRAY0625,
  39. FILL_PATTERN_GRAY125, FILL_PATTERN_LIGHTDOWN, FILL_PATTERN_LIGHTGRAY,
  40. FILL_PATTERN_LIGHTGRID, FILL_PATTERN_LIGHTHORIZONTAL,
  41. FILL_PATTERN_LIGHTTRELLIS, FILL_PATTERN_LIGHTUP, FILL_PATTERN_LIGHTVERTICAL,
  42. FILL_PATTERN_MEDIUMGRAY)
  43. class Fill(Serialisable):
  44. """Base class"""
  45. tagname = "fill"
  46. @classmethod
  47. def from_tree(cls, el):
  48. children = [c for c in el]
  49. if not children:
  50. return
  51. child = children[0]
  52. if "patternFill" in child.tag:
  53. return PatternFill._from_tree(child)
  54. return super(Fill, GradientFill).from_tree(child)
  55. class PatternFill(Fill):
  56. """Area fill patterns for use in styles.
  57. Caution: if you do not specify a fill_type, other attributes will have
  58. no effect !"""
  59. tagname = "patternFill"
  60. __elements__ = ('fgColor', 'bgColor')
  61. patternType = NoneSet(values=fills)
  62. fill_type = Alias("patternType")
  63. fgColor = ColorDescriptor()
  64. start_color = Alias("fgColor")
  65. bgColor = ColorDescriptor()
  66. end_color = Alias("bgColor")
  67. def __init__(self, patternType=None, fgColor=Color(), bgColor=Color(),
  68. fill_type=None, start_color=None, end_color=None):
  69. if fill_type is not None:
  70. patternType = fill_type
  71. self.patternType = patternType
  72. if start_color is not None:
  73. fgColor = start_color
  74. self.fgColor = fgColor
  75. if end_color is not None:
  76. bgColor = end_color
  77. self.bgColor = bgColor
  78. @classmethod
  79. def _from_tree(cls, el):
  80. attrib = dict(el.attrib)
  81. for child in el:
  82. desc = localname(child)
  83. attrib[desc] = Color.from_tree(child)
  84. return cls(**attrib)
  85. def to_tree(self, tagname=None, idx=None):
  86. parent = Element("fill")
  87. el = Element(self.tagname)
  88. if self.patternType is not None:
  89. el.set('patternType', self.patternType)
  90. for c in self.__elements__:
  91. value = getattr(self, c)
  92. if value != Color():
  93. el.append(value.to_tree(c))
  94. parent.append(el)
  95. return parent
  96. DEFAULT_EMPTY_FILL = PatternFill()
  97. DEFAULT_GRAY_FILL = PatternFill(patternType='gray125')
  98. class Stop(Serialisable):
  99. tagname = "stop"
  100. position = MinMax(min=0, max=1)
  101. color = ColorDescriptor()
  102. def __init__(self, color, position):
  103. self.position = position
  104. self.color = color
  105. def _assign_position(values):
  106. """
  107. Automatically assign positions if a list of colours is provided.
  108. It is not permitted to mix colours and stops
  109. """
  110. n_values = len(values)
  111. n_stops = sum(isinstance(value, Stop) for value in values)
  112. if n_stops == 0:
  113. interval = 1
  114. if n_values > 2:
  115. interval = 1 / (n_values - 1)
  116. values = [Stop(value, i * interval)
  117. for i, value in enumerate(values)]
  118. elif n_stops < n_values:
  119. raise ValueError('Cannot interpret mix of Stops and Colors in GradientFill')
  120. pos = set()
  121. for stop in values:
  122. if stop.position in pos:
  123. raise ValueError("Duplicate position {0}".format(stop.position))
  124. pos.add(stop.position)
  125. return values
  126. class StopList(Sequence):
  127. expected_type = Stop
  128. def __set__(self, obj, values):
  129. values = _assign_position(values)
  130. super(StopList, self).__set__(obj, values)
  131. class GradientFill(Fill):
  132. """Fill areas with gradient
  133. Two types of gradient fill are supported:
  134. - A type='linear' gradient interpolates colours between
  135. a set of specified Stops, across the length of an area.
  136. The gradient is left-to-right by default, but this
  137. orientation can be modified with the degree
  138. attribute. A list of Colors can be provided instead
  139. and they will be positioned with equal distance between them.
  140. - A type='path' gradient applies a linear gradient from each
  141. edge of the area. Attributes top, right, bottom, left specify
  142. the extent of fill from the respective borders. Thus top="0.2"
  143. will fill the top 20% of the cell.
  144. """
  145. tagname = "gradientFill"
  146. type = Set(values=('linear', 'path'))
  147. fill_type = Alias("type")
  148. degree = Float()
  149. left = Float()
  150. right = Float()
  151. top = Float()
  152. bottom = Float()
  153. stop = StopList()
  154. def __init__(self, type="linear", degree=0, left=0, right=0, top=0,
  155. bottom=0, stop=()):
  156. self.degree = degree
  157. self.left = left
  158. self.right = right
  159. self.top = top
  160. self.bottom = bottom
  161. self.stop = stop
  162. self.type = type
  163. def __iter__(self):
  164. for attr in self.__attrs__:
  165. value = getattr(self, attr)
  166. if value:
  167. yield attr, safe_string(value)
  168. def to_tree(self, tagname=None, namespace=None, idx=None):
  169. parent = Element("fill")
  170. el = super(GradientFill, self).to_tree()
  171. parent.append(el)
  172. return parent