initial commit
This commit is contained in:
49
deps/inkex/colors/__init__.py
vendored
Normal file
49
deps/inkex/colors/__init__.py
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# coding=utf-8
|
||||
"""
|
||||
The color module allows for the parsing and printing of CSS colors in an SVG document.
|
||||
|
||||
Support formats are currently:
|
||||
|
||||
1. #RGB #RRGGBB #RGBA #RRGGBBAA formats
|
||||
2. Named colors such as 'red'
|
||||
3. icc-color(...) which is specific to SVG 1.1
|
||||
4. rgb(...) and rgba(...) from CSS Color Module 3
|
||||
5. hsl(...) and hsla(...) from CSS Color Module 3
|
||||
6. hwb(...) from CSS Color Module 4, but encoded internally as hsv
|
||||
7. device-cmyk(...) from CSS Color Module 4
|
||||
|
||||
Each color space has it's own class, such as ColorRGB. Each space will parse multiple
|
||||
formats, for example ColorRGB supports hex and rgb CSS module formats.
|
||||
|
||||
Each color object is a list of numbers, each number is a channel in that color space
|
||||
with alpha channel being held in it's own property which may be a unit number or None.
|
||||
|
||||
The numbers a color stores are typically in the range defined in the CSS module
|
||||
specification so for example RGB, all the numbers are between 0-255 while for hsl
|
||||
the hue channel is between 0-360 and the saturation and lightness are between 0-100.
|
||||
|
||||
To get normalised numbers you can use to the `to_units` function to get everything 0-1
|
||||
|
||||
Each Color space type has a name value which can be used to identify the color space,
|
||||
if this is more useful than checking the class type. Either can be used when converting
|
||||
the color values between spaces.
|
||||
|
||||
A color object may be converted into a different space by using the
|
||||
`color.to(other_space)` function, which will return a new color object in the requested
|
||||
space.
|
||||
|
||||
There are three special cases.
|
||||
|
||||
1. ColorNamed is a type of ColorRGB which will preferentially print the name instead
|
||||
of the hex value if one is available.
|
||||
2. ColorNone is a special value which indicates the keyword `none` and does not
|
||||
allow any values or alpha.
|
||||
3. ColorCMS can not be converted to other color spaces and contains a `fallback_color`
|
||||
to access the RGB fallback if it was provided.
|
||||
|
||||
"""
|
||||
|
||||
from .color import Color, ColorError, ColorIdError
|
||||
from .utils import is_color
|
||||
|
||||
from .spaces import *
|
||||
BIN
deps/inkex/colors/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/__pycache__/__init__.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/__init__.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/__pycache__/color.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/color.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/__pycache__/color.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/color.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/__pycache__/converters.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/converters.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/__pycache__/converters.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/converters.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/__pycache__/utils.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/utils.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/__pycache__/utils.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/__pycache__/utils.cpython-313.pyc
vendored
Normal file
Binary file not shown.
295
deps/inkex/colors/color.py
vendored
Normal file
295
deps/inkex/colors/color.py
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2020 Martin Owens
|
||||
# 2021 Jonathan Neuhauser
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
"""
|
||||
Basic color controls
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional, Tuple, Union
|
||||
|
||||
from .converters import Converters
|
||||
|
||||
Number = Union[int, float]
|
||||
|
||||
|
||||
def round_by_type(kind, number):
|
||||
"""Round a number to zero or five decimal places depending on it's type"""
|
||||
return kind(round(number, kind == float and 5 or 0))
|
||||
|
||||
|
||||
class ColorError(KeyError):
|
||||
"""Specific color parsing error"""
|
||||
|
||||
|
||||
class ColorIdError(ColorError):
|
||||
"""Special color error for gradient and color stop ids"""
|
||||
|
||||
|
||||
class Color(list):
|
||||
"""A parsed color object which could be in many color spaces, the default is sRGB
|
||||
|
||||
Can be constructed from valid CSS color attributes, as well as
|
||||
tuple/list + color space. Percentage values are supported.
|
||||
"""
|
||||
|
||||
_spaces: Dict[str, type] = {}
|
||||
|
||||
name: Optional[str] = None
|
||||
|
||||
# A list of known channels
|
||||
channels: Tuple[str, ...] = ()
|
||||
|
||||
# A list of scales for converting css color values to known qantities
|
||||
scales: Tuple[
|
||||
Union[Tuple[Number, Number, bool], Tuple[Number, Number]], ...
|
||||
] = () # Min (int/float), Max (int/float), [wrap around (bool:False)]
|
||||
|
||||
# If alpha is not specified, this is the default for most color types.
|
||||
default_alpha = 1.0
|
||||
|
||||
def __init_subclass__(cls):
|
||||
if not cls.name:
|
||||
return # It is a base class
|
||||
|
||||
# Add space to a dictionary of available color spaces
|
||||
cls._spaces[cls.name] = cls
|
||||
|
||||
Converters.add_space(cls)
|
||||
|
||||
def __new__(cls, value=None, alpha=None, arg=None):
|
||||
if not cls.name:
|
||||
if value is None:
|
||||
return super().__new__(cls._spaces["none"])
|
||||
|
||||
if isinstance(value, int):
|
||||
return super().__new__(cls._spaces["rgb"])
|
||||
|
||||
if isinstance(value, str):
|
||||
# String from xml or css attributes
|
||||
for space in cls._spaces.values():
|
||||
if space.can_parse(value.lower()):
|
||||
return super().__new__(space, value)
|
||||
|
||||
if isinstance(value, Color):
|
||||
return super().__new__(type(value), value)
|
||||
|
||||
if isinstance(value, (list, tuple)):
|
||||
from ..deprecated.main import _deprecated
|
||||
|
||||
_deprecated(
|
||||
"Anonymous lists of numbers for colors no longer default to rgb"
|
||||
)
|
||||
return super().__new__(cls._spaces["rgb"], value)
|
||||
|
||||
return super().__new__(cls, value, alpha=alpha, arg=arg)
|
||||
|
||||
def __init__(self, values, alpha=None, arg=None):
|
||||
super().__init__()
|
||||
|
||||
if not self.name:
|
||||
raise ColorError(f"Not a known color value: '{values}' {arg}")
|
||||
|
||||
if not isinstance(values, (list, tuple)):
|
||||
raise ColorError(
|
||||
f"Colors must be constructed with a list of values: '{values}'"
|
||||
)
|
||||
|
||||
if alpha is not None and not isinstance(alpha, float):
|
||||
raise ColorError("Color alpha property must be a float number")
|
||||
|
||||
if alpha is None and self.channels and len(values) == len(self.channels) + 1:
|
||||
alpha = values.pop()
|
||||
|
||||
if isinstance(values, Color):
|
||||
alpha = values.alpha
|
||||
|
||||
if self.channels and len(values) != len(self.channels):
|
||||
raise ColorError(
|
||||
f"You must have {len(self.channels)} channels for a {self.name} color"
|
||||
)
|
||||
|
||||
self[:] = values
|
||||
self.alpha = alpha
|
||||
|
||||
def __hash__(self):
|
||||
"""Allow colors to be hashable"""
|
||||
return tuple(self + [self.alpha, self.name]).__hash__()
|
||||
|
||||
def __str__(self):
|
||||
raise NotImplementedError(
|
||||
f"Color space {self.name} can not be printed to a string."
|
||||
)
|
||||
|
||||
def __int__(self):
|
||||
raise NotImplementedError(
|
||||
f"Color space {self.name} can not be converted to a number."
|
||||
)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Get the color value"""
|
||||
space = self.name
|
||||
|
||||
if (
|
||||
isinstance(index, slice)
|
||||
and index.start is not None
|
||||
and not isinstance(index.start, int)
|
||||
):
|
||||
# We support the format `value = color["space_name":index]` here
|
||||
space = self._spaces[index.start]
|
||||
index = int(index.stop)
|
||||
|
||||
# Allow regular slicing to fall through more freely than setitem
|
||||
if space == self.name:
|
||||
return super().__getitem__(index)
|
||||
|
||||
if not isinstance(index, int):
|
||||
raise ColorError(f"Unknown color getter definition: '{index}'")
|
||||
|
||||
return self.to(space)[
|
||||
index
|
||||
] # Note: this calls Color.__getitem__ function again
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"""Set the color value in place, limits setter to specific color space"""
|
||||
space = self.name
|
||||
|
||||
if isinstance(index, slice):
|
||||
# Support the format color[:] = [list of numbers] here
|
||||
if index.start is None and index.stop is None:
|
||||
super().__setitem__(
|
||||
index, (self.constrain(ind, val) for ind, val in enumerate(value))
|
||||
)
|
||||
return
|
||||
|
||||
# We support the format `color["space_name":index] = value` here
|
||||
space = self._spaces[index.start]
|
||||
index = int(index.stop)
|
||||
|
||||
if not isinstance(index, int):
|
||||
raise ColorError(f"Unknown color setter definition: '{index}'")
|
||||
|
||||
# Setting a channel in the existing space
|
||||
if space == self.name:
|
||||
super().__setitem__(index, self.constrain(index, value))
|
||||
else:
|
||||
# Set channel is another space, convert back and forth
|
||||
values = self.to(space)
|
||||
values[index] = value # Note: this calls Color.__setitem__ function again
|
||||
self[:] = values.to(self.name)
|
||||
|
||||
def to(self, space): # pylint: disable=invalid-name
|
||||
"""Get this color but in a specific color space"""
|
||||
if space in self._spaces.values():
|
||||
space = space.name
|
||||
if space not in self._spaces:
|
||||
raise AttributeError(
|
||||
f"Unknown color space {space} when converting from {self.name}"
|
||||
)
|
||||
if not hasattr(type(self), f"to_{space}"):
|
||||
setattr(
|
||||
type(self),
|
||||
f"to_{space}",
|
||||
Converters.find_converter(type(self), self._spaces[space]),
|
||||
)
|
||||
return getattr(self, f"to_{space}")()
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name.startswith("to_") and name.count("_") == 1:
|
||||
return lambda: self.to(name.split("_")[-1])
|
||||
raise AttributeError(f"Can not find attribute {type(self).__name__}.{name}")
|
||||
|
||||
@property
|
||||
def effective_alpha(self):
|
||||
"""Get the alpha as set, or tell me what it would be by default"""
|
||||
if self.alpha is None:
|
||||
return self.default_alpha
|
||||
return self.alpha
|
||||
|
||||
def get_values(self, alpha=True):
|
||||
"""Returns all values, including alpha as a list"""
|
||||
if alpha:
|
||||
return list(self + [self.effective_alpha])
|
||||
return list(self)
|
||||
|
||||
@classmethod
|
||||
def to_units(cls, *values):
|
||||
"""Convert the color values into floats scales from 0.0 to 1.0"""
|
||||
return [cls.scale_down(ind, val) for ind, val in enumerate(values)]
|
||||
|
||||
@classmethod
|
||||
def from_units(cls, *values):
|
||||
"""Convert float values to the scales expected and return a new instance"""
|
||||
return [cls.scale_up(ind, val) for ind, val in enumerate(values)]
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, string): # pylint: disable=unused-argument
|
||||
"""Returns true if this string can be parsed for this color type"""
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def scale_up(cls, index, value):
|
||||
"""Convert from float 0.0 to 1.0 to an int used in css"""
|
||||
(min_value, max_value) = cls.scales[index][:2]
|
||||
return cls.constrain(
|
||||
index, (value * (max_value - min_value)) + min_value
|
||||
) # See inkscape/src/colors/spaces/base.h:SCALE_UP
|
||||
|
||||
@classmethod
|
||||
def scale_down(cls, index, value):
|
||||
"""Convert from int, often 0 to 255 to a float 0.0 to 1.0"""
|
||||
(min_value, max_value) = cls.scales[index][:2]
|
||||
return (cls.constrain(index, value) - min_value) / (
|
||||
max_value - min_value
|
||||
) # See inkscape/src/colors/spaces/base.h:SCALE_DOWN
|
||||
|
||||
@classmethod
|
||||
def constrain(cls, index, value):
|
||||
"""Constrains the value to the css scale"""
|
||||
scale = cls.scales[index]
|
||||
if len(scale) == 3 and scale[2] is True:
|
||||
if value == scale[1]:
|
||||
return value
|
||||
return round_by_type(
|
||||
type(scale[0]), value % scale[1]
|
||||
) # Wrap around value (i.e. hue)
|
||||
return min(max(round_by_type(type(scale[0]), value), scale[0]), scale[1])
|
||||
|
||||
def interpolate(self, other, fraction):
|
||||
"""Interpolate two colours by the given fraction
|
||||
|
||||
.. versionadded:: 1.1"""
|
||||
from ..tween import ColorInterpolator # pylint: disable=import-outside-toplevel
|
||||
|
||||
try:
|
||||
other = other.to(type(self))
|
||||
except ColorError:
|
||||
raise ColorError("Can not convert color in interpolation.")
|
||||
return ColorInterpolator(self, other).interpolate(fraction)
|
||||
|
||||
|
||||
class AlphaNotAllowed:
|
||||
"""Mixin class to indicate that alpha values are not permitted on this color space"""
|
||||
|
||||
alpha = property(
|
||||
lambda self: None,
|
||||
lambda self, value: None,
|
||||
)
|
||||
|
||||
def get_values(self, alpha=False):
|
||||
return super().get_values(False)
|
||||
122
deps/inkex/colors/converters.py
vendored
Normal file
122
deps/inkex/colors/converters.py
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2018-2024 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
"""
|
||||
Basic color errors and common functions
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from typing import Dict, List, Callable
|
||||
|
||||
ConverterFunc = Callable[[float], List[float]]
|
||||
|
||||
|
||||
class Converters:
|
||||
"""
|
||||
Record how colors can be converted between different spaces and provides
|
||||
a way to path-find between multiple step conversions.
|
||||
"""
|
||||
|
||||
links: Dict[str, Dict[str, ConverterFunc]] = defaultdict(dict)
|
||||
chains: Dict[str, List[List[str]]] = {}
|
||||
|
||||
@classmethod
|
||||
def add_space(cls, color_cls):
|
||||
"""
|
||||
Records the stated links between this class and other color spaces
|
||||
"""
|
||||
for name, func in color_cls.__dict__.items():
|
||||
if not name.startswith("convert_"):
|
||||
continue
|
||||
_, direction, space = name.split("_", 2)
|
||||
from_name = color_cls.name if direction == "to" else space
|
||||
to_name = color_cls.name if direction == "from" else space
|
||||
|
||||
if from_name != to_name:
|
||||
if not isinstance(func, staticmethod):
|
||||
raise TypeError(f"Method '{name}' must be a static method.")
|
||||
cls.links[from_name][to_name] = func.__func__
|
||||
|
||||
@classmethod
|
||||
def get_chain(cls, source, target):
|
||||
"""
|
||||
Get a chain of conversions between two color spaces, if possible.
|
||||
"""
|
||||
|
||||
def build_chains(chains, space):
|
||||
new_chains = []
|
||||
for chain in chains:
|
||||
for hop in cls.links[space]:
|
||||
if hop not in chain:
|
||||
new_chains += build_chains([chain + [hop]], hop)
|
||||
return chains + new_chains
|
||||
|
||||
if source not in cls.chains:
|
||||
cls.chains[source] = build_chains([[source]], source)
|
||||
|
||||
chosen = None
|
||||
for chain in cls.chains[source] or ():
|
||||
if chain[-1] == target and (not chosen or len(chain) < len(chosen)):
|
||||
chosen = chain
|
||||
return chosen
|
||||
|
||||
@classmethod
|
||||
def find_converter(cls, source, target):
|
||||
"""
|
||||
Find a way to convert from source to target using any conversion functions.
|
||||
|
||||
Will hop from one space to another if needed.
|
||||
"""
|
||||
func = None
|
||||
|
||||
# Passthough
|
||||
if source == target:
|
||||
return lambda self: self
|
||||
|
||||
if func is None:
|
||||
chain = cls.get_chain(source.name, target.name)
|
||||
if chain:
|
||||
return cls.generate_converter(chain, source, target)
|
||||
|
||||
# Returning a function means we only run this function once, even when not found
|
||||
def _error(self):
|
||||
raise NotImplementedError(
|
||||
f"Color space {source} can not be converted to {target}."
|
||||
)
|
||||
|
||||
return _error
|
||||
|
||||
@classmethod
|
||||
def generate_converter(cls, chain, source_cls, target_cls):
|
||||
"""
|
||||
Put together a function that can do every step of the chain of conversions
|
||||
"""
|
||||
# Build a list of functions to run
|
||||
funcs = [cls.links[a][b] for a, b in zip(chain, chain[1:])]
|
||||
funcs.insert(0, source_cls.to_units)
|
||||
funcs.append(target_cls.from_units)
|
||||
|
||||
def _inner(values):
|
||||
if hasattr(values, "alpha") and values.alpha is not None:
|
||||
values = list(values) + [values.alpha]
|
||||
for func in funcs:
|
||||
values = func(*values)
|
||||
return target_cls(values)
|
||||
|
||||
return _inner
|
||||
11
deps/inkex/colors/spaces/__init__.py
vendored
Normal file
11
deps/inkex/colors/spaces/__init__.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Each color space that this module supports such have one file in this module.
|
||||
"""
|
||||
|
||||
from .cmyk import ColorDeviceCMYK
|
||||
from .cms import ColorCMS
|
||||
from .hsl import ColorHSL
|
||||
from .hsv import ColorHSV
|
||||
from .named import ColorNamed
|
||||
from .none import ColorNone
|
||||
from .rgb import ColorRGB
|
||||
BIN
deps/inkex/colors/spaces/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/__init__.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/__init__.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/cms.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/cms.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/cms.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/cms.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/cmyk.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/cmyk.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/cmyk.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/cmyk.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/css.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/css.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/css.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/css.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/hsl.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/hsl.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/hsl.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/hsl.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/hsv.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/hsv.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/hsv.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/hsv.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/named.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/named.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/named.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/named.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/none.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/none.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/none.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/none.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/rgb.cpython-312.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/rgb.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
deps/inkex/colors/spaces/__pycache__/rgb.cpython-313.pyc
vendored
Normal file
BIN
deps/inkex/colors/spaces/__pycache__/rgb.cpython-313.pyc
vendored
Normal file
Binary file not shown.
95
deps/inkex/colors/spaces/cms.py
vendored
Normal file
95
deps/inkex/colors/spaces/cms.py
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2024 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
"""
|
||||
SVG icc-color parser
|
||||
"""
|
||||
|
||||
from ..color import Color, AlphaNotAllowed, ColorError, round_by_type
|
||||
from .css import CssColor
|
||||
from .rgb import ColorRGB
|
||||
|
||||
|
||||
class ColorCMS(CssColor, AlphaNotAllowed):
|
||||
"""
|
||||
Parse and print SVG icc-color objects into their values and the fallback RGB
|
||||
"""
|
||||
|
||||
name = "cms"
|
||||
css_func = "icc-color"
|
||||
channels = ()
|
||||
scales = ()
|
||||
|
||||
def __init__(self, values, icc_profile=None, fallback=None):
|
||||
if isinstance(values, str):
|
||||
if values.strip().startswith("#") and " " in values:
|
||||
fallback, values = values.split(" ", 1)
|
||||
fallback = Color(fallback)
|
||||
icc_profile, values = self.parse_css_color(values)
|
||||
|
||||
if icc_profile is None:
|
||||
raise ColorError("CMS Color requires an icc color profile name.")
|
||||
|
||||
self.icc_profile = icc_profile
|
||||
self.fallback_rgb = fallback
|
||||
super().__init__(values)
|
||||
|
||||
def __str__(self) -> str:
|
||||
values = self.css_join.join([f"{v:g}" for v in self.get_css_values()])
|
||||
fallback = str(ColorRGB(self.fallback_rgb)) + " " if self.fallback_rgb else ""
|
||||
return f"{fallback}{self.css_func}({self.icc_profile}, {values})"
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, string: str) -> bool:
|
||||
# Custom detection because of RGB fallback prefix
|
||||
return "icc-color" in string.replace("(", " ").split()
|
||||
|
||||
@classmethod
|
||||
def constrain(cls, index, value):
|
||||
return min(max(round_by_type(float, value), 0.0), 1.0)
|
||||
|
||||
@classmethod
|
||||
def scale_up(cls, index, value):
|
||||
return value # All cms values are already 0.0 to 1.0
|
||||
|
||||
@classmethod
|
||||
def scale_down(cls, index, value):
|
||||
return value # All cms values are already 0.0 to 1.0
|
||||
|
||||
@staticmethod
|
||||
def convert_to_rgb(*data):
|
||||
"""Catch attempted conversions to rgb"""
|
||||
raise NotImplementedError("Can not convert to RGB from icc color")
|
||||
|
||||
@staticmethod
|
||||
def convert_from_rgb(*data):
|
||||
"""Catch attempted conversions from rgb"""
|
||||
raise NotImplementedError("Can not convert from RGB to icc color")
|
||||
|
||||
|
||||
# This is research code for a future developer to use. We already use PIL and this will
|
||||
# allow icc colors to be converted in python. This isn't needed right now, so this work
|
||||
# will be left undone.
|
||||
# @staticmethod
|
||||
# def convert_to_rgb():
|
||||
# from PIL import Image, ImageCms
|
||||
# pixel = Image.fromarray([[int(r * 255), int(g * 255), int(b * 255)]], 'RGB')
|
||||
# transform = ImageCms.buildTransform(sRGB_profile, self.this_profile, "RGB",
|
||||
# self.this_profile_mode, self.this_rendering_intent, 0)
|
||||
# transform.apply_in_place(pixel)
|
||||
# return [p / 255 for p in pixel[0]]
|
||||
81
deps/inkex/colors/spaces/cmyk.py
vendored
Normal file
81
deps/inkex/colors/spaces/cmyk.py
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2024 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# pylint: disable=W0223
|
||||
"""
|
||||
DeviceCMYK Color Space
|
||||
"""
|
||||
|
||||
from .css import CssColorModule4
|
||||
|
||||
|
||||
class ColorDeviceCMYK(CssColorModule4):
|
||||
"""
|
||||
Parse the device-cmyk CSS Color Module 4 format.
|
||||
|
||||
Note that this format is NOT true CMYK as you might expect in a printer and
|
||||
is instead is an aproximation of the intended ink levels if this was converted
|
||||
into a real CMYK color profile using a color management system.
|
||||
"""
|
||||
|
||||
name = "cmyk"
|
||||
|
||||
channels = ("cyan", "magenta", "yellow", "black")
|
||||
scales = ((0, 100), (0, 100), (0, 100), (0, 100), (0.0, 1.0))
|
||||
css_either_prefix = "device-cmyk"
|
||||
|
||||
cyan = property(
|
||||
lambda self: self[0], lambda self, value: self.__setitem__(0, value)
|
||||
)
|
||||
magenta = property(
|
||||
lambda self: self[1], lambda self, value: self.__setitem__(1, value)
|
||||
)
|
||||
yellow = property(
|
||||
lambda self: self[2], lambda self, value: self.__setitem__(2, value)
|
||||
)
|
||||
black = property(
|
||||
lambda self: self[3], lambda self, value: self.__setitem__(3, value)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def convert_to_rgb(cyan, magenta, yellow, black, *alpha):
|
||||
"""
|
||||
Convert a set of Device-CMYK identities into RGB
|
||||
"""
|
||||
white = 1.0 - black
|
||||
return [
|
||||
1.0 - min((1.0, cyan * white + black)),
|
||||
1.0 - min((1.0, magenta * white + black)),
|
||||
1.0 - min((1.0, yellow * white + black)),
|
||||
] + list(alpha)
|
||||
|
||||
@staticmethod
|
||||
def convert_from_rgb(red, green, blue, *alpha):
|
||||
"""
|
||||
Convert RGB into Device-CMYK
|
||||
"""
|
||||
white = max((red, green, blue))
|
||||
black = 1.0 - white
|
||||
return [
|
||||
# Each channel is it's color chart oposite (cyan->red)
|
||||
# with a bit of white removed.
|
||||
(white and (1.0 - red - black) / white or 0.0),
|
||||
(white and (1.0 - green - black) / white or 0.0),
|
||||
(white and (1.0 - blue - black) / white or 0.0),
|
||||
black,
|
||||
] + list(alpha)
|
||||
139
deps/inkex/colors/spaces/css.py
vendored
Normal file
139
deps/inkex/colors/spaces/css.py
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2018-2024 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# pylint: disable=W0223
|
||||
"""
|
||||
Parsing CSS elements from colors
|
||||
"""
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from ..color import Color, ColorError, ColorIdError
|
||||
|
||||
|
||||
class CssColor(Color):
|
||||
"""
|
||||
A Color which is always parsed and printed from a css format.
|
||||
"""
|
||||
|
||||
# A list of css prefixes which ar valid for this space
|
||||
css_noalpha_prefix: Optional[str] = None
|
||||
css_alpha_prefix: Optional[str] = None
|
||||
css_either_prefix: Optional[str] = None
|
||||
|
||||
# Some CSS formats require commas, others do not
|
||||
css_join: str = ", "
|
||||
css_join_alpha: str = ", "
|
||||
css_func = "color"
|
||||
|
||||
def __str__(self):
|
||||
values = self.css_join.join([f"{v:g}" for v in self.get_css_values()])
|
||||
prefix = self.css_noalpha_prefix or self.css_either_prefix
|
||||
if self.alpha is not None:
|
||||
# Alpha is stored as a percent for clarity
|
||||
alpha = int(self.alpha * 100)
|
||||
values += self.css_join_alpha + f"{alpha}%"
|
||||
if not self.css_either_prefix:
|
||||
prefix = self.css_alpha_prefix
|
||||
if prefix is None:
|
||||
raise ColorError(f"Can't encode color {self.name} into CSS color format.")
|
||||
return f"{prefix}({values})"
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, string: str):
|
||||
string = string.replace(" ", "")
|
||||
if "(" not in string or ")" not in string:
|
||||
return False
|
||||
for prefix in (
|
||||
cls.css_noalpha_prefix,
|
||||
cls.css_alpha_prefix,
|
||||
cls.css_either_prefix,
|
||||
):
|
||||
if prefix and (prefix + "(" in string or "color(" + prefix in string):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __init__(self, value, alpha=None):
|
||||
if isinstance(value, str):
|
||||
prefix, values = self.parse_css_color(value)
|
||||
has_alpha = (
|
||||
self.channels is not None and len(values) == len(self.channels) + 1
|
||||
)
|
||||
|
||||
if prefix == self.css_noalpha_prefix or (
|
||||
prefix == self.css_either_prefix and not has_alpha
|
||||
):
|
||||
super().__init__(values)
|
||||
|
||||
elif prefix == self.css_alpha_prefix or (
|
||||
prefix == self.css_either_prefix and has_alpha
|
||||
):
|
||||
super().__init__(values, values.pop())
|
||||
else:
|
||||
raise ColorError(f"Could not parse {self.name} css color: '{value}'")
|
||||
else:
|
||||
super().__init__(value, alpha=alpha)
|
||||
|
||||
@classmethod
|
||||
def parse_css_color(cls, value):
|
||||
"""Parse a css string into a list of values and it's color space prefix"""
|
||||
prefix, values = value.lower().strip().strip(")").split("(")
|
||||
# Some css formats use commas, others do not
|
||||
if "," in cls.css_join:
|
||||
values = values.replace(",", " ")
|
||||
if "/" in cls.css_join_alpha:
|
||||
values = values.replace("/", " ")
|
||||
|
||||
# Split values by spaces
|
||||
values = values.split()
|
||||
prefix = prefix.strip()
|
||||
if prefix == cls.css_func:
|
||||
prefix = values.pop(0)
|
||||
if prefix == "url":
|
||||
raise ColorIdError("Can not parse url as if it was a color.")
|
||||
return prefix, [cls.parse_css_value(i, v) for i, v in enumerate(values)]
|
||||
|
||||
def get_css_values(self):
|
||||
"""Return a list of values used for css string output"""
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def parse_css_value(cls, index, value) -> Union[int, float]:
|
||||
"""Parse a CSS value such as 100%, 360 or 0.4"""
|
||||
if cls.scales and index >= len(cls.scales):
|
||||
raise ValueError("Can't add any more values to color.")
|
||||
|
||||
if isinstance(value, str):
|
||||
value = value.strip()
|
||||
if value.endswith("%"):
|
||||
value = float(value.strip("%")) / 100
|
||||
elif "." in value:
|
||||
value = float(value)
|
||||
else:
|
||||
value = int(value)
|
||||
|
||||
if isinstance(value, float) and value <= 1.0:
|
||||
value = cls.scale_up(index, value)
|
||||
return cls.constrain(index, value)
|
||||
|
||||
|
||||
class CssColorModule4(CssColor):
|
||||
"""Tweak the css parser for CSS Module Four formating"""
|
||||
|
||||
css_join = " "
|
||||
css_join_alpha = " / "
|
||||
107
deps/inkex/colors/spaces/hsl.py
vendored
Normal file
107
deps/inkex/colors/spaces/hsl.py
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2024 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# pylint: disable=W0223
|
||||
"""
|
||||
HSL Color Space
|
||||
"""
|
||||
|
||||
from .css import CssColor
|
||||
|
||||
|
||||
class ColorHSL(CssColor):
|
||||
"""
|
||||
Parse the HSL CSS Module Module 3 format.
|
||||
"""
|
||||
|
||||
name = "hsl"
|
||||
channels = ("hue", "saturation", "lightness")
|
||||
scales = ((0, 360, True), (0, 100), (0, 100), (0.0, 1.0))
|
||||
|
||||
css_noalpha_prefix = "hsl"
|
||||
css_alpha_prefix = "hsla"
|
||||
|
||||
hue = property(lambda self: self[0], lambda self, value: self.__setitem__(0, value))
|
||||
saturation = property(
|
||||
lambda self: self[1], lambda self, value: self.__setitem__(1, value)
|
||||
)
|
||||
lightness = property(
|
||||
lambda self: self[2], lambda self, value: self.__setitem__(2, value)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def convert_from_rgb(red, green, blue, alpha=None):
|
||||
"""RGB to HSL colour conversion"""
|
||||
rgb_max = max(red, green, blue)
|
||||
rgb_min = min(red, green, blue)
|
||||
delta = rgb_max - rgb_min
|
||||
hsl = [0.0, 0.0, (rgb_max + rgb_min) / 2.0]
|
||||
|
||||
if delta != 0:
|
||||
if hsl[2] <= 0.5:
|
||||
hsl[1] = delta / (rgb_max + rgb_min)
|
||||
else:
|
||||
hsl[1] = delta / (2 - rgb_max - rgb_min)
|
||||
|
||||
if red == rgb_max:
|
||||
hsl[0] = (green - blue) / delta
|
||||
elif green == rgb_max:
|
||||
hsl[0] = 2.0 + (blue - red) / delta
|
||||
elif blue == rgb_max:
|
||||
hsl[0] = 4.0 + (red - green) / delta
|
||||
|
||||
hsl[0] /= 6.0
|
||||
if hsl[0] < 0:
|
||||
hsl[0] += 1
|
||||
if hsl[0] > 1:
|
||||
hsl[0] -= 1
|
||||
if alpha is not None:
|
||||
hsl.append(alpha)
|
||||
return hsl
|
||||
|
||||
@staticmethod
|
||||
def convert_to_rgb(hue, sat, light, *alpha):
|
||||
"""HSL to RGB Color Conversion"""
|
||||
if sat == 0:
|
||||
return [light, light, light] # Gray
|
||||
|
||||
if light < 0.5:
|
||||
val2 = light * (1 + sat)
|
||||
else:
|
||||
val2 = light + sat - light * sat
|
||||
val1 = 2 * light - val2
|
||||
ret = [
|
||||
_hue_to_rgb(val1, val2, hue * 6 + 2.0),
|
||||
_hue_to_rgb(val1, val2, hue * 6),
|
||||
_hue_to_rgb(val1, val2, hue * 6 - 2.0),
|
||||
]
|
||||
return ret + list(alpha)
|
||||
|
||||
|
||||
def _hue_to_rgb(val1, val2, hue):
|
||||
if hue < 0:
|
||||
hue += 6.0
|
||||
if hue > 6:
|
||||
hue -= 6.0
|
||||
if hue < 1:
|
||||
return val1 + (val2 - val1) * hue
|
||||
if hue < 3:
|
||||
return val2
|
||||
if hue < 4:
|
||||
return val1 + (val2 - val1) * (4 - hue)
|
||||
return val1
|
||||
88
deps/inkex/colors/spaces/hsv.py
vendored
Normal file
88
deps/inkex/colors/spaces/hsv.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2024 Jonathan Neuhauser
|
||||
# 2024 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# pylint: disable=W0223
|
||||
"""
|
||||
HSV Color Space
|
||||
"""
|
||||
|
||||
from .css import CssColorModule4
|
||||
|
||||
|
||||
class ColorHSV(CssColorModule4):
|
||||
"""
|
||||
Parse the HWB CSS Color Module 4 format and retain as HSV values.
|
||||
"""
|
||||
|
||||
name = "hsv"
|
||||
channels = ("hue", "saturation", "value")
|
||||
scales = ((0, 360, True), (0, 100), (0, 100), (0.0, 1.0))
|
||||
|
||||
# We use HWB to store HSV as this makes the most sense to Inkscape
|
||||
css_either_prefix = "hwb"
|
||||
|
||||
hue = property(lambda self: self[0], lambda self, value: self.__setitem__(0, value))
|
||||
saturation = property(
|
||||
lambda self: self[1], lambda self, value: self.__setitem__(1, value)
|
||||
)
|
||||
value = property(
|
||||
lambda self: self[2], lambda self, value: self.__setitem__(2, value)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def parse_css_color(cls, value):
|
||||
"""Parsing HWB as if it was HSV for css input"""
|
||||
prefix, values = super().parse_css_color(value)
|
||||
# See https://en.wikipedia.org/wiki/HWB_color_model#Converting_to_and_from_HSV
|
||||
values[1] /= 100
|
||||
values[2] /= 100
|
||||
scale = values[1] + values[2]
|
||||
if scale > 1.0:
|
||||
values[1] /= scale
|
||||
values[2] /= scale
|
||||
values[1] = int(
|
||||
(values[2] == 1.0 and 0.0 or (1.0 - (values[1] / (1.0 - values[2])))) * 100
|
||||
)
|
||||
values[2] = int((1.0 - values[2]) * 100)
|
||||
return prefix, values
|
||||
|
||||
def get_css_values(self):
|
||||
"""Convert our HSV values into HWB for css output"""
|
||||
values = list(self)
|
||||
values[1] = (100 - values[1]) * (values[2] / 100)
|
||||
values[2] = 100 - values[2]
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def convert_to_hsl(hue, saturation, value, *alpha):
|
||||
"""Conversion according to
|
||||
https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL
|
||||
|
||||
.. versionadded:: 1.5"""
|
||||
lum = value * (1 - saturation / 2)
|
||||
sat = 0 if lum in (0, 1) else (value - lum) / min(lum, 1 - lum)
|
||||
return [hue, sat, lum] + list(alpha)
|
||||
|
||||
@staticmethod
|
||||
def convert_from_hsl(hue, saturation, lightness, *alpha):
|
||||
"""Convertion according to Inkscape C++ codebase
|
||||
.. versionadded:: 1.5"""
|
||||
val = lightness + saturation * min(lightness, 1 - lightness)
|
||||
sat = 0 if val == 0 else 2 * (1 - lightness / val)
|
||||
return [hue, sat, val] + list(alpha)
|
||||
236
deps/inkex/colors/spaces/named.py
vendored
Normal file
236
deps/inkex/colors/spaces/named.py
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2024, Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
"""
|
||||
CSS Named colors
|
||||
"""
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from ..color import Color
|
||||
from .rgb import ColorRGB
|
||||
|
||||
_COLORS = {
|
||||
"aliceblue": "#f0f8ff",
|
||||
"antiquewhite": "#faebd7",
|
||||
"aqua": "#00ffff",
|
||||
"aquamarine": "#7fffd4",
|
||||
"azure": "#f0ffff",
|
||||
"beige": "#f5f5dc",
|
||||
"bisque": "#ffe4c4",
|
||||
"black": "#000000",
|
||||
"blanchedalmond": "#ffebcd",
|
||||
"blue": "#0000ff",
|
||||
"blueviolet": "#8a2be2",
|
||||
"brown": "#a52a2a",
|
||||
"burlywood": "#deb887",
|
||||
"cadetblue": "#5f9ea0",
|
||||
"chartreuse": "#7fff00",
|
||||
"chocolate": "#d2691e",
|
||||
"coral": "#ff7f50",
|
||||
"cornflowerblue": "#6495ed",
|
||||
"cornsilk": "#fff8dc",
|
||||
"crimson": "#dc143c",
|
||||
"cyan": "#00ffff",
|
||||
"darkblue": "#00008b",
|
||||
"darkcyan": "#008b8b",
|
||||
"darkgoldenrod": "#b8860b",
|
||||
"darkgray": "#a9a9a9",
|
||||
"darkgreen": "#006400",
|
||||
"darkgrey": "#a9a9a9",
|
||||
"darkkhaki": "#bdb76b",
|
||||
"darkmagenta": "#8b008b",
|
||||
"darkolivegreen": "#556b2f",
|
||||
"darkorange": "#ff8c00",
|
||||
"darkorchid": "#9932cc",
|
||||
"darkred": "#8b0000",
|
||||
"darksalmon": "#e9967a",
|
||||
"darkseagreen": "#8fbc8f",
|
||||
"darkslateblue": "#483d8b",
|
||||
"darkslategray": "#2f4f4f",
|
||||
"darkslategrey": "#2f4f4f",
|
||||
"darkturquoise": "#00ced1",
|
||||
"darkviolet": "#9400d3",
|
||||
"deeppink": "#ff1493",
|
||||
"deepskyblue": "#00bfff",
|
||||
"dimgray": "#696969",
|
||||
"dimgrey": "#696969",
|
||||
"dodgerblue": "#1e90ff",
|
||||
"firebrick": "#b22222",
|
||||
"floralwhite": "#fffaf0",
|
||||
"forestgreen": "#228b22",
|
||||
"fuchsia": "#ff00ff",
|
||||
"gainsboro": "#dcdcdc",
|
||||
"ghostwhite": "#f8f8ff",
|
||||
"gold": "#ffd700",
|
||||
"goldenrod": "#daa520",
|
||||
"gray": "#808080",
|
||||
"grey": "#808080",
|
||||
"green": "#008000",
|
||||
"greenyellow": "#adff2f",
|
||||
"honeydew": "#f0fff0",
|
||||
"hotpink": "#ff69b4",
|
||||
"indianred": "#cd5c5c",
|
||||
"indigo": "#4b0082",
|
||||
"ivory": "#fffff0",
|
||||
"khaki": "#f0e68c",
|
||||
"lavender": "#e6e6fa",
|
||||
"lavenderblush": "#fff0f5",
|
||||
"lawngreen": "#7cfc00",
|
||||
"lemonchiffon": "#fffacd",
|
||||
"lightblue": "#add8e6",
|
||||
"lightcoral": "#f08080",
|
||||
"lightcyan": "#e0ffff",
|
||||
"lightgoldenrodyellow": "#fafad2",
|
||||
"lightgray": "#d3d3d3",
|
||||
"lightgreen": "#90ee90",
|
||||
"lightgrey": "#d3d3d3",
|
||||
"lightpink": "#ffb6c1",
|
||||
"lightsalmon": "#ffa07a",
|
||||
"lightseagreen": "#20b2aa",
|
||||
"lightskyblue": "#87cefa",
|
||||
"lightslategray": "#778899",
|
||||
"lightslategrey": "#778899",
|
||||
"lightsteelblue": "#b0c4de",
|
||||
"lightyellow": "#ffffe0",
|
||||
"lime": "#00ff00",
|
||||
"limegreen": "#32cd32",
|
||||
"linen": "#faf0e6",
|
||||
"magenta": "#ff00ff",
|
||||
"maroon": "#800000",
|
||||
"mediumaquamarine": "#66cdaa",
|
||||
"mediumblue": "#0000cd",
|
||||
"mediumorchid": "#ba55d3",
|
||||
"mediumpurple": "#9370db",
|
||||
"mediumseagreen": "#3cb371",
|
||||
"mediumslateblue": "#7b68ee",
|
||||
"mediumspringgreen": "#00fa9a",
|
||||
"mediumturquoise": "#48d1cc",
|
||||
"mediumvioletred": "#c71585",
|
||||
"midnightblue": "#191970",
|
||||
"mintcream": "#f5fffa",
|
||||
"mistyrose": "#ffe4e1",
|
||||
"moccasin": "#ffe4b5",
|
||||
"navajowhite": "#ffdead",
|
||||
"navy": "#000080",
|
||||
"oldlace": "#fdf5e6",
|
||||
"olive": "#808000",
|
||||
"olivedrab": "#6b8e23",
|
||||
"orange": "#ffa500",
|
||||
"orangered": "#ff4500",
|
||||
"orchid": "#da70d6",
|
||||
"palegoldenrod": "#eee8aa",
|
||||
"palegreen": "#98fb98",
|
||||
"paleturquoise": "#afeeee",
|
||||
"palevioletred": "#db7093",
|
||||
"papayawhip": "#ffefd5",
|
||||
"peachpuff": "#ffdab9",
|
||||
"peru": "#cd853f",
|
||||
"pink": "#ffc0cb",
|
||||
"plum": "#dda0dd",
|
||||
"powderblue": "#b0e0e6",
|
||||
"purple": "#800080",
|
||||
"rebeccapurple": "#663399",
|
||||
"red": "#ff0000",
|
||||
"rosybrown": "#bc8f8f",
|
||||
"royalblue": "#4169e1",
|
||||
"saddlebrown": "#8b4513",
|
||||
"salmon": "#fa8072",
|
||||
"sandybrown": "#f4a460",
|
||||
"seagreen": "#2e8b57",
|
||||
"seashell": "#fff5ee",
|
||||
"sienna": "#a0522d",
|
||||
"silver": "#c0c0c0",
|
||||
"skyblue": "#87ceeb",
|
||||
"slateblue": "#6a5acd",
|
||||
"slategray": "#708090",
|
||||
"slategrey": "#708090",
|
||||
"snow": "#fffafa",
|
||||
"springgreen": "#00ff7f",
|
||||
"steelblue": "#4682b4",
|
||||
"tan": "#d2b48c",
|
||||
"teal": "#008080",
|
||||
"thistle": "#d8bfd8",
|
||||
"tomato": "#ff6347",
|
||||
"turquoise": "#40e0d0",
|
||||
"violet": "#ee82ee",
|
||||
"wheat": "#f5deb3",
|
||||
"white": "#ffffff",
|
||||
"whitesmoke": "#f5f5f5",
|
||||
"yellow": "#ffff00",
|
||||
"yellowgreen": "#9acd32",
|
||||
}
|
||||
|
||||
|
||||
class ColorNamed(ColorRGB):
|
||||
"""
|
||||
Parse specific named colors, fall back to RGB parsing if it fails.
|
||||
"""
|
||||
|
||||
_color_names: Dict[ColorRGB, str] = {}
|
||||
_name_colors: Dict[str, ColorRGB] = {}
|
||||
|
||||
name = "named"
|
||||
|
||||
def __init__(self, name, alpha=None):
|
||||
if isinstance(name, str):
|
||||
super().__init__(self.name_colors()[name.lower().strip()])
|
||||
else:
|
||||
super().__init__(name, alpha=alpha)
|
||||
|
||||
@classmethod
|
||||
def color_names(cls):
|
||||
"""Cache a list of color names"""
|
||||
if not cls._color_names:
|
||||
cls._color_names = {
|
||||
value: name for name, value in cls.name_colors().items()
|
||||
}
|
||||
return cls._color_names
|
||||
|
||||
@classmethod
|
||||
def name_colors(cls):
|
||||
"""Cache a list of color objects"""
|
||||
if not cls._name_colors:
|
||||
cls._name_colors = {name: Color(value) for name, value in _COLORS.items()}
|
||||
return cls._name_colors
|
||||
|
||||
def __str__(self):
|
||||
return self.color_names().get(self, super().__str__())
|
||||
|
||||
def __hash__(self):
|
||||
"""Allow named colors to match rgb colors"""
|
||||
return tuple(self + [self.alpha, super().name]).__hash__()
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, string: str):
|
||||
"""If the string is one of the color names, we can parse it"""
|
||||
return string in cls.name_colors()
|
||||
|
||||
@staticmethod
|
||||
def convert_to_rgb(*data):
|
||||
"""Converting to RGB is transparent, already in RGB"""
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def convert_from_rgb(*data):
|
||||
"""Converting from RGB is transparent, the store is RGB"""
|
||||
return data
|
||||
|
||||
def to_rgb(self):
|
||||
"""Prevent masking by ColorRGB of to_rgb method"""
|
||||
return ColorRGB(list(self), alpha=self.alpha)
|
||||
55
deps/inkex/colors/spaces/none.py
vendored
Normal file
55
deps/inkex/colors/spaces/none.py
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2021 Jonathan Neuhauser
|
||||
# 2020 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# pylint: disable=W0223
|
||||
"""
|
||||
An empty color for 'none'
|
||||
"""
|
||||
|
||||
from ..color import Color, AlphaNotAllowed
|
||||
|
||||
|
||||
class ColorNone(Color, AlphaNotAllowed):
|
||||
"""A special color for 'none' colors"""
|
||||
|
||||
name = "none"
|
||||
|
||||
# Override opacity since none can not have opacity
|
||||
default_alpha = 0.0
|
||||
|
||||
def __init__(self, value=None):
|
||||
pass
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "none"
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, string: str) -> bool:
|
||||
"""Returns true if this is the word 'none'"""
|
||||
return string == "none"
|
||||
|
||||
@staticmethod
|
||||
def convert_to_rgb(*_):
|
||||
"""Converting to RGB means transparent black"""
|
||||
return [0, 0, 0, 0]
|
||||
|
||||
@staticmethod
|
||||
def convert_from_rgb(*_):
|
||||
"""Converting from RGB means throwing out all data"""
|
||||
return []
|
||||
105
deps/inkex/colors/spaces/rgb.py
vendored
Normal file
105
deps/inkex/colors/spaces/rgb.py
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2024, Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
"""
|
||||
RGB Colors
|
||||
"""
|
||||
|
||||
from ..color import ColorError
|
||||
from .css import CssColor
|
||||
|
||||
|
||||
class ColorRGB(CssColor):
|
||||
"""
|
||||
Parse multiple versions of RGB from CSS module and standard hex formats.
|
||||
"""
|
||||
|
||||
name = "rgb"
|
||||
channels = ("red", "green", "blue")
|
||||
scales = ((0, 255), (0, 255), (0, 255), (0.0, 1.0))
|
||||
|
||||
css_noalpha_prefix = "rgb"
|
||||
css_alpha_prefix = "rgba"
|
||||
|
||||
red = property(lambda self: self[0], lambda self, value: self.__setitem__(0, value))
|
||||
green = property(
|
||||
lambda self: self[1], lambda self, value: self.__setitem__(1, value)
|
||||
)
|
||||
blue = property(
|
||||
lambda self: self[2], lambda self, value: self.__setitem__(2, value)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, string: str) -> bool:
|
||||
return "icc" not in string and (
|
||||
string.startswith("#")
|
||||
or string.lstrip("-").isdigit()
|
||||
or super().can_parse(string)
|
||||
)
|
||||
|
||||
def __init__(self, value, alpha=None):
|
||||
# Not CSS, but inkscape, some old color values stores as 32bit int strings
|
||||
if isinstance(value, str) and value.lstrip("-").isdigit():
|
||||
value = int(value)
|
||||
|
||||
if isinstance(value, int):
|
||||
super().__init__(
|
||||
[
|
||||
((value >> 24) & 255), # red
|
||||
((value >> 16) & 255), # green
|
||||
((value >> 8) & 255), # blue
|
||||
((value & 255) / 255.0),
|
||||
]
|
||||
) # opacity
|
||||
|
||||
elif isinstance(value, str) and value.startswith("#") and " " not in value:
|
||||
if len(value) == 4: # (css: #rgb -> #rrggbb)
|
||||
# pylint: disable=consider-using-f-string
|
||||
value = "#{1}{1}{2}{2}{3}{3}".format(*value)
|
||||
elif len(value) == 5: # (css: #rgba -> #rrggbbaa)
|
||||
# pylint: disable=consider-using-f-string
|
||||
value = "#{1}{1}{2}{2}{3}{3}{4}{4}".format(*value)
|
||||
|
||||
# Convert hex to integers
|
||||
try:
|
||||
values = [int(value[i : i + 2], 16) for i in range(1, len(value), 2)]
|
||||
if len(values) == 4:
|
||||
values[3] /= 255
|
||||
super().__init__(values)
|
||||
except ValueError as error:
|
||||
raise ColorError(f"Bad RGB hex color value '{value}'") from error
|
||||
else:
|
||||
super().__init__(value, alpha=alpha)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.alpha is not None:
|
||||
return super().__str__()
|
||||
if len(self) < len(self.channels):
|
||||
raise ColorError(
|
||||
f"Incorrect number of channels for Color Space {self.name}"
|
||||
)
|
||||
# Always hex values when outputting color
|
||||
return "#{0:02x}{1:02x}{2:02x}".format(*(int(v) for v in self)) # pylint: disable=consider-using-f-string
|
||||
|
||||
def __int__(self) -> int:
|
||||
return (
|
||||
(self[0] << 24)
|
||||
+ (self[1] << 16)
|
||||
+ (self[2] << 8)
|
||||
+ int((self.alpha or 1.0) * 255)
|
||||
)
|
||||
31
deps/inkex/colors/utils.py
vendored
Normal file
31
deps/inkex/colors/utils.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# coding=utf-8
|
||||
#
|
||||
# Copyright (C) 2018-2024 Martin Owens
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
"""
|
||||
Utilities for color support
|
||||
"""
|
||||
|
||||
from .color import Color, ColorError
|
||||
|
||||
|
||||
def is_color(color):
|
||||
"""Determine if it is a color that we can use. If not, leave it unchanged."""
|
||||
try:
|
||||
return bool(Color(color))
|
||||
except ColorError:
|
||||
return False
|
||||
Reference in New Issue
Block a user