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