Skip to content

Commit 1943191

Browse files
color: Support CSS Color Level 4 rgb & hsl syntax (#113)
1 parent 4a84ac1 commit 1943191

File tree

4 files changed

+11927
-73
lines changed

4 files changed

+11927
-73
lines changed

src/color.rs

+135-58
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use std::cmp;
66
use std::fmt;
7+
use std::f32::consts::PI;
78

89
use super::{Token, Parser, ToCss};
910
use tokenizer::NumericValue;
@@ -398,75 +399,151 @@ fn clamp_f32(val: f32) -> u8 {
398399

399400
#[inline]
400401
fn parse_color_function(name: &str, arguments: &mut Parser) -> Result<Color, ()> {
401-
let (is_rgb, has_alpha) = match_ignore_ascii_case! { name,
402-
"rgba" => (true, true),
403-
"rgb" => (true, false),
404-
"hsl" => (false, false),
405-
"hsla" => (false, true),
402+
let is_rgb = match_ignore_ascii_case! { name,
403+
"rgb" | "rgba" => true,
404+
"hsl" | "hsla" => false,
406405
_ => return Err(())
407406
};
408407

408+
let (red, green, blue, uses_commas) = if is_rgb {
409+
parse_rgb_components_rgb(arguments)?
410+
} else {
411+
parse_rgb_components_hsl(arguments)?
412+
};
413+
414+
let alpha = if !arguments.is_exhausted() {
415+
if uses_commas {
416+
try!(arguments.expect_comma());
417+
} else {
418+
match try!(arguments.next()) {
419+
Token::Delim('/') => {},
420+
_ => return Err(())
421+
};
422+
};
423+
let token = try!(arguments.next());
424+
match token {
425+
Token::Number(NumericValue { value: v, .. }) => {
426+
clamp_f32(v)
427+
}
428+
Token::Percentage(ref v) => {
429+
clamp_f32(v.unit_value)
430+
}
431+
_ => {
432+
return Err(())
433+
}
434+
}
435+
} else {
436+
255
437+
};
438+
439+
try!(arguments.expect_exhausted());
440+
rgba(red, green, blue, alpha)
441+
}
442+
443+
444+
#[inline]
445+
fn parse_rgb_components_rgb(arguments: &mut Parser) -> Result<(u8, u8, u8, bool), ()> {
409446
let red: u8;
410447
let green: u8;
411448
let blue: u8;
412-
if is_rgb {
413-
// Either integers or percentages, but all the same type.
414-
// https://drafts.csswg.org/css-color/#rgb-functions
415-
match try!(arguments.next()) {
416-
Token::Number(NumericValue { int_value: Some(v), .. }) => {
417-
red = clamp_i32(v);
449+
let mut uses_commas = false;
450+
451+
// Either integers or percentages, but all the same type.
452+
// https://drafts.csswg.org/css-color/#rgb-functions
453+
match try!(arguments.next()) {
454+
Token::Number(NumericValue { value: v, .. }) => {
455+
red = clamp_i32(v as i32);
456+
green = clamp_i32(match try!(arguments.next()) {
457+
Token::Number(NumericValue { value: v, .. }) => v,
458+
Token::Comma => {
459+
uses_commas = true;
460+
try!(arguments.expect_number())
461+
}
462+
_ => return Err(())
463+
} as i32);
464+
if uses_commas {
418465
try!(arguments.expect_comma());
419-
green = clamp_i32(try!(arguments.expect_integer()));
420-
try!(arguments.expect_comma());
421-
blue = clamp_i32(try!(arguments.expect_integer()));
422466
}
423-
Token::Percentage(ref v) => {
424-
red = clamp_f32(v.unit_value);
425-
try!(arguments.expect_comma());
426-
green = clamp_f32(try!(arguments.expect_percentage()));
467+
blue = clamp_i32(try!(arguments.expect_number()) as i32);
468+
}
469+
Token::Percentage(ref v) => {
470+
red = clamp_f32(v.unit_value);
471+
green = clamp_f32(match try!(arguments.next()) {
472+
Token::Percentage(ref v) => v.unit_value,
473+
Token::Comma => {
474+
uses_commas = true;
475+
try!(arguments.expect_percentage())
476+
}
477+
_ => return Err(())
478+
});
479+
if uses_commas {
427480
try!(arguments.expect_comma());
428-
blue = clamp_f32(try!(arguments.expect_percentage()));
429481
}
430-
_ => return Err(())
431-
};
432-
} else {
433-
let hue_degrees = try!(arguments.expect_number());
434-
// Subtract an integer before rounding, to avoid some rounding errors:
435-
let hue_normalized_degrees = hue_degrees - 360. * (hue_degrees / 360.).floor();
436-
let hue = hue_normalized_degrees / 360.;
437-
// Saturation and lightness are clamped to 0% ... 100%
438-
// https://drafts.csswg.org/css-color/#the-hsl-notation
439-
try!(arguments.expect_comma());
440-
let saturation = try!(arguments.expect_percentage()).max(0.).min(1.);
441-
try!(arguments.expect_comma());
442-
let lightness = try!(arguments.expect_percentage()).max(0.).min(1.);
443-
444-
// https://drafts.csswg.org/css-color/#hsl-color
445-
// except with h pre-multiplied by 3, to avoid some rounding errors.
446-
fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
447-
if h3 < 0. { h3 += 3. }
448-
if h3 > 3. { h3 -= 3. }
449-
450-
if h3 * 2. < 1. { m1 + (m2 - m1) * h3 * 2. }
451-
else if h3 * 2. < 3. { m2 }
452-
else if h3 < 2. { m1 + (m2 - m1) * (2. - h3) * 2. }
453-
else { m1 }
482+
blue = clamp_f32(try!(arguments.expect_percentage()));
454483
}
455-
let m2 = if lightness <= 0.5 { lightness * (saturation + 1.) }
456-
else { lightness + saturation - lightness * saturation };
457-
let m1 = lightness * 2. - m2;
458-
let hue_times_3 = hue * 3.;
459-
red = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 + 1.));
460-
green = clamp_f32(hue_to_rgb(m1, m2, hue_times_3));
461-
blue = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 - 1.));
462-
}
484+
_ => return Err(())
485+
};
486+
return Ok((red, green, blue, uses_commas));
487+
}
463488

464-
let alpha = if has_alpha {
465-
try!(arguments.expect_comma());
466-
clamp_f32(try!(arguments.expect_number()))
467-
} else {
468-
255
489+
#[inline]
490+
fn parse_rgb_components_hsl(arguments: &mut Parser) -> Result<(u8, u8, u8, bool), ()> {
491+
let mut uses_commas = false;
492+
// Hue given as an angle
493+
// https://drafts.csswg.org/css-values/#angles
494+
let hue_degrees = match try!(arguments.next()) {
495+
Token::Number(NumericValue { value: v, .. }) => v,
496+
Token::Dimension(NumericValue { value: v, .. }, unit) => {
497+
match_ignore_ascii_case! { &*unit,
498+
"deg" => v,
499+
"grad" => v * 360. / 400.,
500+
"rad" => v * 360. / (2. * PI),
501+
"turn" => v * 360.,
502+
_ => return Err(())
503+
}
504+
}
505+
_ => return Err(())
469506
};
470-
try!(arguments.expect_exhausted());
471-
rgba(red, green, blue, alpha)
507+
// Subtract an integer before rounding, to avoid some rounding errors:
508+
let hue_normalized_degrees = hue_degrees - 360. * (hue_degrees / 360.).floor();
509+
let hue = hue_normalized_degrees / 360.;
510+
511+
// Saturation and lightness are clamped to 0% ... 100%
512+
// https://drafts.csswg.org/css-color/#the-hsl-notation
513+
let saturation = match try!(arguments.next()) {
514+
Token::Percentage(ref v) => v.unit_value,
515+
Token::Comma => {
516+
uses_commas = true;
517+
try!(arguments.expect_percentage())
518+
}
519+
_ => return Err(())
520+
};
521+
let saturation = saturation.max(0.).min(1.);
522+
523+
if uses_commas {
524+
try!(arguments.expect_comma());
525+
}
526+
527+
let lightness = try!(arguments.expect_percentage());
528+
let lightness = lightness.max(0.).min(1.);
529+
530+
// https://drafts.csswg.org/css-color/#hsl-color
531+
// except with h pre-multiplied by 3, to avoid some rounding errors.
532+
fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
533+
if h3 < 0. { h3 += 3. }
534+
if h3 > 3. { h3 -= 3. }
535+
536+
if h3 * 2. < 1. { m1 + (m2 - m1) * h3 * 2. }
537+
else if h3 * 2. < 3. { m2 }
538+
else if h3 < 2. { m1 + (m2 - m1) * (2. - h3) * 2. }
539+
else { m1 }
540+
}
541+
let m2 = if lightness <= 0.5 { lightness * (saturation + 1.) }
542+
else { lightness + saturation - lightness * saturation };
543+
let m1 = lightness * 2. - m2;
544+
let hue_times_3 = hue * 3.;
545+
let red = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 + 1.));
546+
let green = clamp_f32(hue_to_rgb(m1, m2, hue_times_3));
547+
let blue = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 - 1.));
548+
return Ok((red, green, blue, uses_commas));
472549
}

0 commit comments

Comments
 (0)