# Copyright (c) 2010-2024 fastpyxl
from fastpyxl.descriptors import (
Typed,
)
from fastpyxl.styles.colors import aRGB_REGEX
from fastpyxl.xml.constants import DRAWING_NS
from fastpyxl.xml.functions import Element
from fastpyxl.typed_serialisable.render import namespaced_tag
from fastpyxl.typed_serialisable.base import Serialisable
from fastpyxl.typed_serialisable.errors import FieldValidationError
from fastpyxl.typed_serialisable.fields import AliasField, Field
from fastpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
PRESET_COLORS = [
'aliceBlue', 'antiqueWhite', 'aqua', 'aquamarine',
'azure', 'beige', 'bisque', 'black', 'blanchedAlmond', 'blue',
'blueViolet', 'brown', 'burlyWood', 'cadetBlue', 'chartreuse',
'chocolate', 'coral', 'cornflowerBlue', 'cornsilk', 'crimson', 'cyan',
'darkBlue', 'darkCyan', 'darkGoldenrod', 'darkGray', 'darkGrey',
'darkGreen', 'darkKhaki', 'darkMagenta', 'darkOliveGreen', 'darkOrange',
'darkOrchid', 'darkRed', 'darkSalmon', 'darkSeaGreen', 'darkSlateBlue',
'darkSlateGray', 'darkSlateGrey', 'darkTurquoise', 'darkViolet',
'dkBlue', 'dkCyan', 'dkGoldenrod', 'dkGray', 'dkGrey', 'dkGreen',
'dkKhaki', 'dkMagenta', 'dkOliveGreen', 'dkOrange', 'dkOrchid', 'dkRed',
'dkSalmon', 'dkSeaGreen', 'dkSlateBlue', 'dkSlateGray', 'dkSlateGrey',
'dkTurquoise', 'dkViolet', 'deepPink', 'deepSkyBlue', 'dimGray',
'dimGrey', 'dodgerBlue', 'firebrick', 'floralWhite', 'forestGreen',
'fuchsia', 'gainsboro', 'ghostWhite', 'gold', 'goldenrod', 'gray',
'grey', 'green', 'greenYellow', 'honeydew', 'hotPink', 'indianRed',
'indigo', 'ivory', 'khaki', 'lavender', 'lavenderBlush', 'lawnGreen',
'lemonChiffon', 'lightBlue', 'lightCoral', 'lightCyan',
'lightGoldenrodYellow', 'lightGray', 'lightGrey', 'lightGreen',
'lightPink', 'lightSalmon', 'lightSeaGreen', 'lightSkyBlue',
'lightSlateGray', 'lightSlateGrey', 'lightSteelBlue', 'lightYellow',
'ltBlue', 'ltCoral', 'ltCyan', 'ltGoldenrodYellow', 'ltGray', 'ltGrey',
'ltGreen', 'ltPink', 'ltSalmon', 'ltSeaGreen', 'ltSkyBlue',
'ltSlateGray', 'ltSlateGrey', 'ltSteelBlue', 'ltYellow', 'lime',
'limeGreen', 'linen', 'magenta', 'maroon', 'medAquamarine', 'medBlue',
'medOrchid', 'medPurple', 'medSeaGreen', 'medSlateBlue',
'medSpringGreen', 'medTurquoise', 'medVioletRed', 'mediumAquamarine',
'mediumBlue', 'mediumOrchid', 'mediumPurple', 'mediumSeaGreen',
'mediumSlateBlue', 'mediumSpringGreen', 'mediumTurquoise',
'mediumVioletRed', 'midnightBlue', 'mintCream', 'mistyRose', 'moccasin',
'navajoWhite', 'navy', 'oldLace', 'olive', 'oliveDrab', 'orange',
'orangeRed', 'orchid', 'paleGoldenrod', 'paleGreen', 'paleTurquoise',
'paleVioletRed', 'papayaWhip', 'peachPuff', 'peru', 'pink', 'plum',
'powderBlue', 'purple', 'red', 'rosyBrown', 'royalBlue', 'saddleBrown',
'salmon', 'sandyBrown', 'seaGreen', 'seaShell', 'sienna', 'silver',
'skyBlue', 'slateBlue', 'slateGray', 'slateGrey', 'snow', 'springGreen',
'steelBlue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet',
'wheat', 'white', 'whiteSmoke', 'yellow', 'yellowGreen'
]
SCHEME_COLORS= ['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', 'accent3',
'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', 'dk1', 'lt1',
'dk2', 'lt2'
]
SYSTEM_COLOR_VALS = frozenset(
(
"scrollBar",
"background",
"activeCaption",
"inactiveCaption",
"menu",
"window",
"windowFrame",
"menuText",
"windowText",
"captionText",
"activeBorder",
"inactiveBorder",
"appWorkspace",
"highlight",
"highlightText",
"btnFace",
"btnShadow",
"grayText",
"btnText",
"inactiveCaptionText",
"btnHighlight",
"3dDkShadow",
"3dLight",
"infoText",
"infoBk",
"hotLight",
"gradientActiveCaption",
"gradientInactiveCaption",
"menuHighlight",
"menuBar",
)
)
SCHEME_COLOR_VALS = frozenset(SCHEME_COLORS)
def _system_color_val(v):
if v is None:
return None
if v not in SYSTEM_COLOR_VALS:
raise FieldValidationError(f"val rejected value {v!r}")
return v
def _scheme_color_val(v):
if v is None:
return None
if v not in SCHEME_COLOR_VALS:
raise FieldValidationError(f"val rejected value {v!r}")
return v
def _system_last_clr(v):
if v is None:
return None
if not isinstance(v, str) or aRGB_REGEX.match(v) is None:
raise FieldValidationError(f"lastClr rejected value {v!r}")
if len(v) == 6:
return "00" + v
return v
def _drawml_empty_element(tag, value, ns=None):
if not value:
return None
return Element(namespaced_tag(tag, ns or DRAWING_NS))
[docs]
class SystemColor(Serialisable):
tagname = "sysClr"
namespace = DRAWING_NS
tint: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
shade: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
comp: Transform | None = Field.element(expected_type=Transform, allow_none=True, default=None)
inv: Transform | None = Field.element(expected_type=Transform, allow_none=True, default=None)
gray: Transform | None = Field.element(expected_type=Transform, allow_none=True, default=None)
alpha: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
alphaOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
alphaMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
hue: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
hueOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
hueMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
sat: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
satOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
satMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
lum: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
lumOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
lumMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
red: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
redOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
redMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
green: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
greenOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
greenMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
blue: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
blueOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
blueMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
gamma: Transform | None = Field.element(expected_type=Transform, allow_none=True, default=None)
invGamma: Transform | None = Field.element(expected_type=Transform, allow_none=True, default=None)
val: str | None = Field.attribute(
expected_type=str,
allow_none=True,
default="windowText",
converter=_system_color_val,
)
lastClr: str | None = Field.attribute(
expected_type=str,
allow_none=True,
converter=_system_last_clr, default=None,
)
xml_order = (
"tint",
"shade",
"comp",
"inv",
"gray",
"alpha",
"alphaOff",
"alphaMod",
"hue",
"hueOff",
"hueMod",
"sat",
"satOff",
"satMod",
"lum",
"lumOff",
"lumMod",
"red",
"redOff",
"redMod",
"green",
"greenOff",
"greenMod",
"blue",
"blueOff",
"blueMod",
"gamma",
"invGamma",
)
def __init__(self,
val="windowText",
lastClr=None,
tint=None,
shade=None,
comp=None,
inv=None,
gray=None,
alpha=None,
alphaOff=None,
alphaMod=None,
hue=None,
hueOff=None,
hueMod=None,
sat=None,
satOff=None,
satMod=None,
lum=None,
lumOff=None,
lumMod=None,
red=None,
redOff=None,
redMod=None,
green=None,
greenOff=None,
greenMod=None,
blue=None,
blueOff=None,
blueMod=None,
gamma=None,
invGamma=None
):
self.val = val
self.lastClr = lastClr
self.tint = tint
self.shade = shade
self.comp = comp
self.inv = inv
self.gray = gray
self.alpha = alpha
self.alphaOff = alphaOff
self.alphaMod = alphaMod
self.hue = hue
self.hueOff = hueOff
self.hueMod = hueMod
self.sat = sat
self.satOff = satOff
self.satMod = satMod
self.lum = lum
self.lumOff = lumOff
self.lumMod = lumMod
self.red = red
self.redOff = redOff
self.redMod = redMod
self.green = green
self.greenOff = greenOff
self.greenMod = greenMod
self.blue = blue
self.blueOff = blueOff
self.blueMod = blueMod
self.gamma = gamma
self.invGamma = invGamma
[docs]
class HSLColor(Serialisable):
tagname = "hslClr"
hue: int | None = Field.attribute(expected_type=int, allow_none=True, default=None)
sat: float | int | None = Field.attribute(
expected_type=float,
allow_none=True,
converter=lambda v: _range_converter(v, field_name="sat", min_value=0, max_value=100), default=None,
)
lum: float | int | None = Field.attribute(
expected_type=float,
allow_none=True,
converter=lambda v: _range_converter(v, field_name="lum", min_value=0, max_value=100), default=None,
)
#TODO add color transform options
def __init__(self,
hue=None,
sat=None,
lum=None,
):
self.hue = hue
self.sat = sat
self.lum = lum
[docs]
class RGBPercent(Serialisable):
tagname = "rgbClr"
r: float | int | None = Field.attribute(
expected_type=float,
allow_none=True,
converter=lambda v: _range_converter(v, field_name="r", min_value=0, max_value=100), default=None,
)
g: float | int | None = Field.attribute(
expected_type=float,
allow_none=True,
converter=lambda v: _range_converter(v, field_name="g", min_value=0, max_value=100), default=None,
)
b: float | int | None = Field.attribute(
expected_type=float,
allow_none=True,
converter=lambda v: _range_converter(v, field_name="b", min_value=0, max_value=100), default=None,
)
#TODO add color transform options
def __init__(self,
r=None,
g=None,
b=None,
):
self.r = r
self.g = g
self.b = b
[docs]
class SchemeColor(Serialisable):
tagname = "schemeClr"
namespace = DRAWING_NS
tint: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
shade: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
comp: bool | None = Field.nested_bool(
allow_none=True,
renderer=_drawml_empty_element, default=None,
)
inv: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
gray: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
alpha: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
alphaOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
alphaMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
hue: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
hueOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
hueMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
sat: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
satOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
satMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
lum: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
lumOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
lumMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
red: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
redOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
redMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
green: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
greenOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
greenMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
blue: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
blueOff: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
blueMod: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None)
gamma: bool | None = Field.nested_bool(
allow_none=True,
renderer=_drawml_empty_element, default=None,
)
invGamma: bool | None = Field.nested_bool(
allow_none=True,
renderer=_drawml_empty_element, default=None,
)
val: str | None = Field.attribute(
expected_type=str,
allow_none=True,
converter=_scheme_color_val, default=None,
)
xml_order = (
"tint",
"shade",
"comp",
"inv",
"gray",
"alpha",
"alphaOff",
"alphaMod",
"hue",
"hueOff",
"hueMod",
"sat",
"satOff",
"satMod",
"lum",
"lumMod",
"lumOff",
"red",
"redOff",
"redMod",
"green",
"greenOff",
"greenMod",
"blue",
"blueOff",
"blueMod",
"gamma",
"invGamma",
)
def __init__(self,
tint=None,
shade=None,
comp=None,
inv=None,
gray=None,
alpha=None,
alphaOff=None,
alphaMod=None,
hue=None,
hueOff=None,
hueMod=None,
sat=None,
satOff=None,
satMod=None,
lum=None,
lumOff=None,
lumMod=None,
red=None,
redOff=None,
redMod=None,
green=None,
greenOff=None,
greenMod=None,
blue=None,
blueOff=None,
blueMod=None,
gamma=None,
invGamma=None,
val=None,
):
self.tint = tint
self.shade = shade
self.comp = comp
self.inv = inv
self.gray = gray
self.alpha = alpha
self.alphaOff = alphaOff
self.alphaMod = alphaMod
self.hue = hue
self.hueOff = hueOff
self.hueMod = hueMod
self.sat = sat
self.satOff = satOff
self.satMod = satMod
self.lum = lum
self.lumOff = lumOff
self.lumMod = lumMod
self.red = red
self.redOff = redOff
self.redMod = redMod
self.green = green
self.greenOff = greenOff
self.greenMod = greenMod
self.blue = blue
self.blueOff = blueOff
self.blueMod = blueMod
self.gamma = gamma
self.invGamma = invGamma
self.val = val
[docs]
class ColorChoice(Serialisable):
tagname = "colorChoice"
namespace = DRAWING_NS
scrgbClr: RGBPercent | None = Field.element(expected_type=RGBPercent, allow_none=True, default=None)
RGBPercent = AliasField("scrgbClr", default=None)
srgbClr: str | None = Field.nested_value(expected_type=str, allow_none=True, default=None)
RGB = AliasField("srgbClr", default=None)
hslClr: HSLColor | None = Field.element(expected_type=HSLColor, allow_none=True, default=None)
sysClr: SystemColor | None = Field.element(expected_type=SystemColor, allow_none=True, default=None)
schemeClr: SchemeColor | None = Field.element(expected_type=SchemeColor, allow_none=True, default=None)
prstClr: str | None = Field.nested_value(
expected_type=str,
allow_none=True,
converter=lambda v: _enum_converter(v, PRESET_COLORS, "prstClr"), default=None,
)
xml_order = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
def __init__(self,
scrgbClr=None,
srgbClr=None,
hslClr=None,
sysClr=None,
schemeClr=None,
prstClr=None,
):
self.scrgbClr = scrgbClr
self.srgbClr = srgbClr
self.hslClr = hslClr
self.sysClr = sysClr
self.schemeClr = schemeClr
self.prstClr = prstClr
_COLOR_SET = ('dk1', 'lt1', 'dk2', 'lt2', 'accent1', 'accent2', 'accent3',
'accent4', 'accent5', 'accent6', 'hlink', 'folHlink')
def _color_set_converter(v):
return _enum_converter(v, _COLOR_SET, "color")
[docs]
class ColorMapping(Serialisable):
tagname = "clrMapOvr"
bg1: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
tx1: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
bg2: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
tx2: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
accent1: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
accent2: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
accent3: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
accent4: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
accent5: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
accent6: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
hlink: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
folHlink: str | None = Field.attribute(expected_type=str, allow_none=True, converter=_color_set_converter, default=None)
extLst: OfficeArtExtensionList | None = Field.element(expected_type=OfficeArtExtensionList, allow_none=True, default=None)
def __init__(self,
bg1="lt1",
tx1="dk1",
bg2="lt2",
tx2="dk2",
accent1="accent1",
accent2="accent2",
accent3="accent3",
accent4="accent4",
accent5="accent5",
accent6="accent6",
hlink="hlink",
folHlink="folHlink",
extLst=None,
):
self.bg1 = bg1
self.tx1 = tx1
self.bg2 = bg2
self.tx2 = tx2
self.accent1 = accent1
self.accent2 = accent2
self.accent3 = accent3
self.accent4 = accent4
self.accent5 = accent5
self.accent6 = accent6
self.hlink = hlink
self.folHlink = folHlink
self.extLst = extLst
[docs]
class ColorChoiceDescriptor(Typed):
"""
Objects can choose from 7 different kinds of color system.
Assume RGBHex if a string is passed in.
"""
expected_type = ColorChoice
allow_none = True
def __set__(self, instance, value):
if isinstance(value, str):
value = ColorChoice(srgbClr=value)
else:
if self.namespace is not None and value is not None:
value.namespace = self.namespace
super().__set__(instance, value)
def _range_converter(value, *, field_name: str, min_value: float, max_value: float):
if value is None:
return None
try:
numeric = float(value)
except Exception as exc: # pragma: no cover
raise FieldValidationError(f"{field_name} rejected value {value!r}") from exc
if numeric < min_value or numeric > max_value:
raise FieldValidationError(f"{field_name} rejected value {value!r}")
return numeric
def _enum_converter(value, allowed_values, field_name: str):
if value is None:
return None
if value not in allowed_values:
raise FieldValidationError(f"{field_name} rejected value {value!r}")
return value