initial commit

This commit is contained in:
2026-01-17 23:40:16 -05:00
commit 513afcb35a
220 changed files with 31164 additions and 0 deletions

122
deps/inkex/colors/converters.py vendored Normal file
View 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