# Copyright (c) 2010-2024 openpyxl
|
|
import re
|
from openpyxl.compat import safe_string
|
from openpyxl.descriptors import (
|
String,
|
Bool,
|
MinMax,
|
Integer,
|
Typed,
|
)
|
from openpyxl.descriptors.sequence import NestedSequence
|
from openpyxl.descriptors.serialisable import Serialisable
|
|
# Default Color Index as per 18.8.27 of ECMA Part 4
|
COLOR_INDEX = (
|
'00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', #0-4
|
'00FFFF00', '00FF00FF', '0000FFFF', '00000000', '00FFFFFF', #5-9
|
'00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', #10-14
|
'0000FFFF', '00800000', '00008000', '00000080', '00808000', #15-19
|
'00800080', '00008080', '00C0C0C0', '00808080', '009999FF', #20-24
|
'00993366', '00FFFFCC', '00CCFFFF', '00660066', '00FF8080', #25-29
|
'000066CC', '00CCCCFF', '00000080', '00FF00FF', '00FFFF00', #30-34
|
'0000FFFF', '00800080', '00800000', '00008080', '000000FF', #35-39
|
'0000CCFF', '00CCFFFF', '00CCFFCC', '00FFFF99', '0099CCFF', #40-44
|
'00FF99CC', '00CC99FF', '00FFCC99', '003366FF', '0033CCCC', #45-49
|
'0099CC00', '00FFCC00', '00FF9900', '00FF6600', '00666699', #50-54
|
'00969696', '00003366', '00339966', '00003300', '00333300', #55-59
|
'00993300', '00993366', '00333399', '00333333', #60-63
|
)
|
# indices 64 and 65 are reserved for the system foreground and background colours respectively
|
|
# Will remove these definitions in a future release
|
BLACK = COLOR_INDEX[0]
|
WHITE = COLOR_INDEX[1]
|
#RED = COLOR_INDEX[2]
|
#DARKRED = COLOR_INDEX[8]
|
BLUE = COLOR_INDEX[4]
|
#DARKBLUE = COLOR_INDEX[12]
|
#GREEN = COLOR_INDEX[3]
|
#DARKGREEN = COLOR_INDEX[9]
|
#YELLOW = COLOR_INDEX[5]
|
#DARKYELLOW = COLOR_INDEX[19]
|
|
|
aRGB_REGEX = re.compile("^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$")
|
|
|
class RGB(Typed):
|
"""
|
Descriptor for aRGB values
|
If not supplied alpha is 00
|
"""
|
|
expected_type = str
|
|
def __set__(self, instance, value):
|
if not self.allow_none:
|
m = aRGB_REGEX.match(value)
|
if m is None:
|
raise ValueError("Colors must be aRGB hex values")
|
if len(value) == 6:
|
value = "00" + value
|
super().__set__(instance, value)
|
|
|
class Color(Serialisable):
|
"""Named colors for use in styles."""
|
|
tagname = "color"
|
|
rgb = RGB()
|
indexed = Integer()
|
auto = Bool()
|
theme = Integer()
|
tint = MinMax(min=-1, max=1, expected_type=float)
|
type = String()
|
|
|
def __init__(self, rgb=BLACK, indexed=None, auto=None, theme=None, tint=0.0, index=None, type='rgb'):
|
if index is not None:
|
indexed = index
|
if indexed is not None:
|
self.type = 'indexed'
|
self.indexed = indexed
|
elif theme is not None:
|
self.type = 'theme'
|
self.theme = theme
|
elif auto is not None:
|
self.type = 'auto'
|
self.auto = auto
|
else:
|
self.rgb = rgb
|
self.type = 'rgb'
|
self.tint = tint
|
|
@property
|
def value(self):
|
return getattr(self, self.type)
|
|
@value.setter
|
def value(self, value):
|
setattr(self, self.type, value)
|
|
def __iter__(self):
|
attrs = [(self.type, self.value)]
|
if self.tint != 0:
|
attrs.append(('tint', self.tint))
|
for k, v in attrs:
|
yield k, safe_string(v)
|
|
@property
|
def index(self):
|
# legacy
|
return self.value
|
|
|
def __add__(self, other):
|
"""
|
Adding colours is undefined behaviour best do nothing
|
"""
|
if not isinstance(other, Color):
|
return super().__add__(other)
|
return self
|
|
|
class ColorDescriptor(Typed):
|
|
expected_type = Color
|
|
def __set__(self, instance, value):
|
if isinstance(value, str):
|
value = Color(rgb=value)
|
super().__set__(instance, value)
|
|
|
class RgbColor(Serialisable):
|
|
tagname = "rgbColor"
|
|
rgb = RGB()
|
|
def __init__(self,
|
rgb=None,
|
):
|
self.rgb = rgb
|
|
|
class ColorList(Serialisable):
|
|
tagname = "colors"
|
|
indexedColors = NestedSequence(expected_type=RgbColor)
|
mruColors = NestedSequence(expected_type=Color)
|
|
__elements__ = ('indexedColors', 'mruColors')
|
|
def __init__(self,
|
indexedColors=(),
|
mruColors=(),
|
):
|
self.indexedColors = indexedColors
|
self.mruColors = mruColors
|
|
|
def __bool__(self):
|
return bool(self.indexedColors) or bool(self.mruColors)
|
|
|
@property
|
def index(self):
|
return [val.rgb for val in self.indexedColors]
|