@@ -177,13 +177,35 @@ fn parse_alpha_component(num: &str) -> Result<u8, ColorParseError> {
177177 }
178178}
179179
180- /// Parse a hex color from a string.
181- ///
182- /// The allowed formats are:
183- /// * #RGB
184- /// * #RGBA
185- /// * #RRGGBB
186- /// * #RRGGBBAA
180+ fn parse_hue_component ( num : & str ) -> Result < ColorFloat , ColorParseError > {
181+ use ColorParseError :: InvalidToken ;
182+
183+ if num. ends_with ( '%' ) {
184+ return Err ( InvalidToken ) ;
185+ }
186+
187+ num. parse :: < ColorFloat > ( ) . map_err ( |_| InvalidToken )
188+ }
189+
190+ fn parse_hsl_percentage ( num : & str ) -> Result < ColorFloat , ColorParseError > {
191+ use ColorParseError :: { InvalidToken , OutOfRange } ;
192+
193+ let core = num. strip_suffix ( '%' ) . ok_or ( InvalidToken ) ?;
194+ let v: ColorFloat = core. parse ( ) . map_err ( |_| InvalidToken ) ?;
195+ if !( 0.0 ..=100.0 ) . contains ( & v) {
196+ return Err ( OutOfRange ) ;
197+ }
198+ Ok ( v)
199+ }
200+
201+ // Parse a hex color from a string.
202+ //
203+ // The allowed formats are:
204+ // * #RGB
205+ // * #RGBA
206+ // * #RRGGBB
207+ // * #RRGGBBAA
208+
187209fn parse_hex ( hex : & str ) -> Result < Color , ColorParseError > {
188210 use ColorParseError :: * ;
189211
@@ -253,13 +275,14 @@ fn parse_hex(hex: &str) -> Result<Color, ColorParseError> {
253275 Ok ( Color :: from_rgba ( [ r, g, b, a] ) )
254276}
255277
256- /// Parse a CSS rgb function.
257- ///
258- /// The allowed styles are:
259- /// rgb(r,g,b)
260- /// rgb(r g b)
261- /// rgb(r% g% b%)
262- /// rgb(r g b / a)
278+ // Parse a CSS rgb function.
279+ //
280+ // The allowed styles are:
281+ // rgb(r,g,b)
282+ // rgb(r g b)
283+ // rgb(r% g% b%)
284+ // rgb(r g b / a)
285+
263286fn parse_css_rgb ( args : & str ) -> Result < Color , ColorParseError > {
264287 use ColorParseError :: * ;
265288
@@ -342,6 +365,86 @@ fn parse_css_rgb(args: &str) -> Result<Color, ColorParseError> {
342365 Ok ( Color :: from_rgba ( [ r, g, b, a] ) )
343366}
344367
368+ // Parse a CSS hsl/hsla function (modern space or legacy comma syntax).
369+ fn parse_css_hsl ( args : & str ) -> Result < Color , ColorParseError > {
370+ use ColorParseError :: * ;
371+
372+ let tokens = tokenize ( args) ?;
373+ if tokens. is_empty ( ) {
374+ return Err ( InvalidFunc ) ;
375+ }
376+
377+ let has_comma = tokens. iter ( ) . any ( |t| matches ! ( t, CssColorToken :: Comma ) ) ;
378+
379+ let mut it = tokens. iter ( ) . peekable ( ) ;
380+ let mut comps: Vec < & str > = Vec :: new ( ) ;
381+ let mut alpha: Option < & str > = None ;
382+
383+ if has_comma {
384+ // legacy hsl(h, s%, l%) or with trailing alpha
385+ loop {
386+ let num = match it. next ( ) {
387+ Some ( CssColorToken :: Number ( s) ) => * s,
388+ Some ( _) => return Err ( InvalidFunc ) ,
389+ None => break ,
390+ } ;
391+ comps. push ( num) ;
392+
393+ match it. next ( ) {
394+ Some ( CssColorToken :: Comma ) => continue ,
395+ Some ( CssColorToken :: Slash ) => {
396+ let next = match it. next ( ) {
397+ Some ( CssColorToken :: Number ( s) ) => * s,
398+ _ => return Err ( InvalidToken ) ,
399+ } ;
400+ alpha = Some ( next) ;
401+ if it. next ( ) . is_some ( ) {
402+ return Err ( InvalidFunc ) ;
403+ }
404+ break ;
405+ }
406+ None => break ,
407+ Some ( _) => return Err ( InvalidToken ) ,
408+ }
409+ }
410+ } else {
411+ // modern: hsl(h s% l% / a?)
412+ while let Some ( token) = it. next ( ) {
413+ match token {
414+ CssColorToken :: Number ( s) => {
415+ if alpha. is_some ( ) {
416+ return Err ( InvalidFunc ) ; // numbers after alpha not allowed
417+ }
418+ comps. push ( * s) ;
419+ }
420+ CssColorToken :: Slash => {
421+ if alpha. is_some ( ) {
422+ return Err ( InvalidFunc ) ; // double slash
423+ }
424+ let next = match it. next ( ) {
425+ Some ( CssColorToken :: Number ( s) ) => * s,
426+ _ => return Err ( InvalidToken ) ,
427+ } ;
428+ alpha = Some ( next) ;
429+ }
430+ CssColorToken :: Comma => return Err ( InvalidToken ) , // commas not allowed in this form
431+ }
432+ }
433+ }
434+
435+ if comps. len ( ) != 3 {
436+ return Err ( InvalidFunc ) ;
437+ }
438+
439+ let h = parse_hue_component ( comps[ 0 ] ) ?;
440+ let s = parse_hsl_percentage ( comps[ 1 ] ) ?;
441+ let l = parse_hsl_percentage ( comps[ 2 ] ) ?;
442+ let a = alpha. map ( parse_alpha_component) . transpose ( ) ?. unwrap_or ( 255 ) ;
443+
444+ let color = Color :: from_hsl ( [ h, s, l] ) ;
445+ Ok ( color. with_alpha ( a) )
446+ }
447+
345448pub fn parse_color ( mut s : & str ) -> Result < Color , ColorParseError > {
346449 use ColorParseError :: * ;
347450
@@ -365,13 +468,21 @@ pub fn parse_color(mut s: &str) -> Result<Color, ColorParseError> {
365468 if let Some ( args) = lower. strip_prefix ( "rgb(" ) . and_then ( |x| x. strip_suffix ( ')' ) ) {
366469 return parse_css_rgb ( args) ;
367470 }
368- // rgba() in CSS is just rgb() with the same arg grammar in modern CSS
369471 if let Some ( args) = lower
370472 . strip_prefix ( "rgba(" )
371473 . and_then ( |x| x. strip_suffix ( ')' ) )
372474 {
373475 return parse_css_rgb ( args) ;
374476 }
477+ if let Some ( args) = lower. strip_prefix ( "hsl(" ) . and_then ( |x| x. strip_suffix ( ')' ) ) {
478+ return parse_css_hsl ( args) ;
479+ }
480+ if let Some ( args) = lower
481+ . strip_prefix ( "hsla(" )
482+ . and_then ( |x| x. strip_suffix ( ')' ) )
483+ {
484+ return parse_css_hsl ( args) ;
485+ }
375486
376487 // Hex-like
377488 if let Some ( rest) = s. strip_prefix ( '#' ) {
0 commit comments