fills.py 6.2 KB

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