99from itertools import zip_longest as zipl
1010import functools
1111import math
12- from ColorHelper .ch_util import get_base_color
12+ from ColorHelper .ch_util import get_base_color , COLOR_PARTS
13+
14+ RE_CHAN_VALUE = re .compile (r'(?i)(?:[+\-]?(?:[0-9]*\.)?[0-9]+(?:e[-+]?[0-9]+)?(?:%|deg|rad|turn|grad)?|none)' )
1315
1416BASE = get_base_color ()
1517
2729 \#(?:{hex}{{6}}(?:{hex}{{2}})?|{hex}{{3}}(?:{hex})?) |
2830 [\w][\w\d]*
2931 )
30- """ .format (** parse . COLOR_PARTS )
32+ """ .format (** COLOR_PARTS )
3133 ),
3234 "functions" : re .compile (r'(?i)[\w][\w\d]*\(' ),
33- "separators" : re .compile (r'(?:{comma}|{space}|{slash})' .format (** parse . COLOR_PARTS ))
35+ "separators" : re .compile (r'(?:{comma}|{space}|{slash})' .format (** COLOR_PARTS ))
3436}
3537
3638RE_ADJUSTERS = {
4143 (?:(\+\s+|\-\s+)?({strict_percent}|{strict_float})|(\*)?\s*({strict_percent}|{strict_float}))
4244 \s*\)
4345 """ .format (
44- ** parse . COLOR_PARTS
46+ ** COLOR_PARTS
4547 )
4648 ),
4749 "saturation" : re .compile (
48- r'(?i)\s+s(?:aturation)?\((\+\s|\-\s|\*)?\s*({strict_percent})\s*\)' .format (** parse . COLOR_PARTS )
50+ r'(?i)\s+s(?:aturation)?\((\+\s|\-\s|\*)?\s*({strict_percent})\s*\)' .format (** COLOR_PARTS )
4951 ),
5052 "lightness" : re .compile (
51- r'(?i)\s+l(?:ightness)?\((\+\s|\-\s|\*)?\s*({strict_percent})\s*\)' .format (** parse . COLOR_PARTS )
53+ r'(?i)\s+l(?:ightness)?\((\+\s|\-\s|\*)?\s*({strict_percent})\s*\)' .format (** COLOR_PARTS )
5254 ),
5355 "min-contrast_start" : re .compile (r'(?i)\s+min-contrast\(\s*' ),
5456 "blend_start" : re .compile (r'(?i)\s+blenda?\(\s*' ),
5557 "end" : re .compile (r'(?i)\s*\)' )
5658}
5759
58- RE_HUE = re .compile (r'(?i){angle}' .format (** parse . COLOR_PARTS ))
60+ RE_HUE = re .compile (r'(?i){angle}' .format (** COLOR_PARTS ))
5961RE_COLOR_START = re .compile (r'(?i)color\(\s*' )
60- RE_BLEND_END = re .compile (r'(?i)\s+({strict_percent})(?:\s+(rgb|hsl|hwb))?\s*\)' .format (** parse . COLOR_PARTS ))
62+ RE_BLEND_END = re .compile (r'(?i)\s+({strict_percent})(?:\s+(rgb|hsl|hwb))?\s*\)' .format (** COLOR_PARTS ))
6163RE_BRACKETS = re .compile (r'(?:(\()|(\))|[^()]+)' )
62- RE_MIN_CONTRAST_END = re .compile (r'(?i)\s+({strict_float})\s*\)' .format (** parse . COLOR_PARTS ))
64+ RE_MIN_CONTRAST_END = re .compile (r'(?i)\s+({strict_float})\s*\)' .format (** COLOR_PARTS ))
6365RE_VARS = re .compile (r'(?i)(?:(?<=^)|(?<=[\s\t\(,/]))(var\(\s*([-\w][-\w\d]*)\s*\))(?!\()(?=[\s\t\),/]|$)' )
6466
6567HWB_MATCH = re .compile (
6668 r"""(?xi)
6769 \b(hwb)\(\s*
6870 (?:
6971 # Space separated format
70- {angle}{space }{percent}{space }{percent}(?:{slash}(?:{percent}|{float}))? |
72+ {angle}{loose_space }{percent}{loose_space }{percent}(?:{slash}(?:{percent}|{float}))? |
7173 # comma separated format
7274 {angle}{comma}{percent}{comma}{percent}(?:{comma}(?:{percent}|{float}))?
7375 )
7476 \s*\)
75- """ .format (** parse . COLOR_PARTS )
77+ """ .format (** COLOR_PARTS )
7678)
7779
7880
@@ -188,7 +190,10 @@ def match(cls, string, start=0, fullmatch=True):
188190
189191 m = HWB_MATCH .match (string , start )
190192 if m is not None and (not fullmatch or m .end (0 ) == len (string )):
191- return parse .parse_channels (string [m .end (1 ) + 1 :m .end (0 ) - 1 ], cls .BOUNDS ), m .end (0 )
193+ return parse .parse_channels (
194+ list (RE_CHAN_VALUE .findall (string [m .end (1 ) + 1 :m .end (0 ) - 1 ])),
195+ cls .CHANNELS , scaled = True
196+ ), m .end (0 )
192197 return None
193198
194199 @classmethod
@@ -198,8 +203,10 @@ def to_string(
198203 * ,
199204 alpha = None ,
200205 precision = None ,
206+ percent : bool = True ,
201207 fit = True ,
202208 none = False ,
209+ comma : bool = False ,
203210 ** kwargs
204211 ) -> str :
205212 """Convert to CSS."""
@@ -212,7 +219,9 @@ def to_string(
212219 fit = fit ,
213220 none = none ,
214221 color = kwargs .get ('color' , False ),
215- legacy = kwargs .get ('comma' , False )
222+ percent = True if comma else percent ,
223+ scale = 100 ,
224+ legacy = comma
216225 )
217226
218227
@@ -349,7 +358,7 @@ def adjust_base(self, base, string):
349358
350359 self ._color = base
351360 pattern = "color({} {})" .format (self ._color .clone ().clip ().to_string (precision = - 1 ), string )
352- color , start = self ._adjust (pattern )
361+ color , _ = self ._adjust (pattern )
353362 if color is not None :
354363 self ._color .update (color )
355364 else :
@@ -486,12 +495,12 @@ def min_contrast(self, color1, color2, target):
486495 if is_dark :
487496 primary = "whiteness"
488497 secondary = "blackness"
489- min_mix = orig . whiteness * 100
498+ min_mix = orig [ primary ] * 100
490499 max_mix = 100
491500 else :
492501 primary = "blackness"
493502 secondary = "whiteness"
494- min_mix = orig . blackness * 100
503+ min_mix = orig [ primary ] * 100
495504 max_mix = 100
496505 orig_ratio = ratio
497506 last_ratio = 0
@@ -620,13 +629,15 @@ def _parse(
620629 s = color
621630 space_class = cls .CS_MAP .get (s )
622631 if not space_class :
623- raise ValueError ("'{}' is not a registered color space" )
632+ raise ValueError ("'{}' is not a registered color space" . format ( s ) )
624633 num_channels = len (space_class .CHANNELS )
625- if len (data ) < num_channels :
626- data = list (data ) + [alg .NaN ] * (num_channels - len (data ))
634+ num_data = len (data )
635+ if num_data < num_channels :
636+ data = list (data ) + [alg .NaN ] * (num_channels - num_data )
627637 coords = [alg .clamp (float (v ), * c .limit ) for c , v in zipl (space_class .CHANNELS , data )]
628638 coords .append (alg .clamp (float (alpha ), * space_class .channels [- 1 ].limit ))
629639 obj = space_class , coords
640+
630641 # Parse a CSS string
631642 else :
632643 m = cls ._match (color , fullmatch = True , variables = variables )
@@ -635,18 +646,18 @@ def _parse(
635646 coords = [alg .clamp (float (v ), * c .limit ) for c , v in zipl (m [0 ].CHANNELS , m [1 ])]
636647 coords .append (alg .clamp (float (m [2 ]), * m [0 ].channels [- 1 ].limit ))
637648 obj = m [0 ], coords
649+
650+ # Handle a color instance
638651 elif isinstance (color , BASE ):
639- # Handle a color instance
640652 space_class = cls .CS_MAP .get (color .space ())
641653 if not space_class :
642- raise ValueError ("'{}' is not a registered color space" )
654+ raise ValueError ("'{}' is not a registered color space" . format ( color . space ()) )
643655 obj = space_class , color [:]
656+
657+ # Handle a color dictionary
644658 elif isinstance (color , Mapping ):
645- # Handle a color dictionary
646- space = color ['space' ]
647- coords = color ['coords' ]
648- alpha = color .get ('alpha' , 1.0 )
649- obj = cls ._parse (space , coords , alpha )
659+ obj = cls ._parse (color ['space' ], color ['coords' ], color .get ('alpha' , 1.0 ))
660+
650661 else :
651662 raise TypeError ("'{}' is an unrecognized type" .format (type (color )))
652663
@@ -710,15 +721,15 @@ def new(self, color, data=None, alpha=util.DEF_ALPHA, *, variables=None, **kwarg
710721
711722 return type (self )(color , data , alpha , variables = variables , ** kwargs )
712723
713- def update (self , color , data = None , alpha = util .DEF_ALPHA , * , variables = None , ** kwargs ):
724+ def update (self , color , data = None , alpha = util .DEF_ALPHA , * , norm = True , variables = None , ** kwargs ):
714725 """Update the existing color space with the provided color."""
715726
716727 space = self .space ()
717728 self ._space , self ._coords = self ._parse (
718729 color , data = data , alpha = alpha , variables = variables , ** kwargs
719730 )
720731 if self ._space .NAME != space :
721- self .convert (space , in_place = True )
732+ self .convert (space , in_place = True , norm = norm )
722733 return self
723734
724735 def mutate (self , color , data = None , alpha = util .DEF_ALPHA , * , variables = None , ** kwargs ):
0 commit comments