From 16fdfd5906e8982ecaf099e3990cbbc056b2e74c Mon Sep 17 00:00:00 2001 From: Kenta Moriuchi Date: Sun, 11 May 2025 08:26:31 +0900 Subject: [PATCH 01/53] fix: remove grid feature (#972) --- Cargo.toml | 3 +-- src/lib.rs | 4 ---- src/properties/mod.rs | 32 -------------------------------- 3 files changed, 1 insertion(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f4eef4e..29d24d8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,11 +34,10 @@ path = "src/lib.rs" crate-type = ["rlib"] [features] -default = ["bundler", "grid", "nodejs", "sourcemap"] +default = ["bundler", "nodejs", "sourcemap"] browserslist = ["browserslist-rs"] bundler = ["dashmap", "sourcemap", "rayon"] cli = ["atty", "clap", "serde_json", "browserslist", "jemallocator"] -grid = [] jsonschema = ["schemars", "serde", "parcel_selectors/jsonschema"] nodejs = ["dep:serde"] serde = [ diff --git a/src/lib.rs b/src/lib.rs index 3def1948..43581946 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21690,7 +21690,6 @@ mod tests { } } - #[cfg(feature = "grid")] #[test] fn test_grid() { minify_test( @@ -24982,7 +24981,6 @@ mod tests { false, ); - #[cfg(feature = "grid")] css_modules_test( r#" body { @@ -25027,7 +25025,6 @@ mod tests { false, ); - #[cfg(feature = "grid")] css_modules_test( r#" .grid { @@ -25066,7 +25063,6 @@ mod tests { false, ); - #[cfg(feature = "grid")] css_modules_test( r#" .grid { diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 00667a30..fb552773 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -104,7 +104,6 @@ pub mod display; pub mod effects; pub mod flex; pub mod font; -#[cfg(feature = "grid")] pub mod grid; pub mod list; pub(crate) mod margin_padding; @@ -154,7 +153,6 @@ use display::*; use effects::*; use flex::*; use font::*; -#[cfg(feature = "grid")] use grid::*; use list::*; use margin_padding::*; @@ -1372,50 +1370,20 @@ define_properties! { "flex-negative": FlexNegative(CSSNumber, VendorPrefix) / Ms unprefixed: false, "flex-preferred-size": FlexPreferredSize(LengthPercentageOrAuto, VendorPrefix) / Ms unprefixed: false, - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-template-columns": GridTemplateColumns(TrackSizing<'i>), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-template-rows": GridTemplateRows(TrackSizing<'i>), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-auto-columns": GridAutoColumns(TrackSizeList), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-auto-rows": GridAutoRows(TrackSizeList), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-auto-flow": GridAutoFlow(GridAutoFlow), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-template-areas": GridTemplateAreas(GridTemplateAreas), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-template": GridTemplate(GridTemplate<'i>) shorthand: true, - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid": Grid(Grid<'i>) shorthand: true, - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-row-start": GridRowStart(GridLine<'i>), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-row-end": GridRowEnd(GridLine<'i>), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-column-start": GridColumnStart(GridLine<'i>), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-column-end": GridColumnEnd(GridLine<'i>), - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-row": GridRow(GridRow<'i>) shorthand: true, - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-column": GridColumn(GridColumn<'i>) shorthand: true, - #[cfg(feature = "grid")] - #[cfg_attr(docsrs, doc(cfg(feature = "grid")))] "grid-area": GridArea(GridArea<'i>) shorthand: true, "margin-top": MarginTop(LengthPercentageOrAuto) [logical_group: Margin, category: Physical], From 0f064abacbe0076d162d3f18ce685baf680117f2 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sat, 10 May 2025 19:42:52 -0400 Subject: [PATCH 02/53] Prevent new lines written by `write_str` from breaking source maps (#971) --- src/lib.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/printer.rs | 19 +++++++++++++++++++ src/stylesheet.rs | 4 ++-- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 43581946..96b640f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28738,6 +28738,48 @@ mod tests { ); } + #[test] + #[cfg(feature = "sourcemap")] + fn test_source_maps_with_license_comments() { + let source = r#"/*! a single line comment */ + /*! + a comment + containing + multiple + lines + */ + .a { + display: flex; + } + + .b { + display: hidden; + } + "#; + + let mut sm = parcel_sourcemap::SourceMap::new("/"); + let source_index = sm.add_source("input.css"); + sm.set_source_content(source_index as usize, source).unwrap(); + + let mut stylesheet = StyleSheet::parse(&source, ParserOptions { + source_index, + ..Default::default() + }).unwrap(); + stylesheet.minify(MinifyOptions::default()).unwrap(); + stylesheet + .to_css(PrinterOptions { + source_map: Some(&mut sm), + minify: true, + ..PrinterOptions::default() + }) + .unwrap(); + let map = sm.to_json(None).unwrap(); + assert_eq!( + map, + r#"{"version":3,"sourceRoot":null,"mappings":";;;;;;;AAOI,gBAIA","sources":["input.css"],"sourcesContent":["/*! a single line comment */\n /*!\n a comment\n containing\n multiple\n lines\n */\n .a {\n display: flex;\n }\n\n .b {\n display: hidden;\n }\n "],"names":[]}"# + ); + } + #[test] fn test_error_recovery() { use std::sync::{Arc, RwLock}; diff --git a/src/printer.rs b/src/printer.rs index 7061485e..b231fbec 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -146,6 +146,25 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> { Ok(()) } + /// Writes a raw string which may contain newlines to the underlying destination. + pub fn write_str_with_newlines(&mut self, s: &str) -> Result<(), PrinterError> { + let mut last_line_start: usize = 0; + + for (idx, n) in s.char_indices() { + if n == '\n' { + self.line += 1; + self.col = 0; + + // Keep track of where the *next* line starts + last_line_start = idx + 1; + } + } + + self.col += (s.len() - last_line_start) as u32; + self.dest.write_str(s)?; + Ok(()) + } + /// Write a single character to the underlying destination. pub fn write_char(&mut self, c: char) -> Result<(), PrinterError> { if c == '\n' { diff --git a/src/stylesheet.rs b/src/stylesheet.rs index dcf87f1c..990a09be 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -287,8 +287,8 @@ where for comment in &self.license_comments { printer.write_str("/*")?; - printer.write_str(comment)?; - printer.write_str("*/\n")?; + printer.write_str_with_newlines(comment)?; + printer.write_str_with_newlines("*/\n")?; } if let Some(config) = &self.options.css_modules { From 4ebcb45600ce4a15db0d7c4b3861013eb0adf077 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 10 May 2025 22:43:35 -0700 Subject: [PATCH 03/53] Update relative color parsing to latest spec (#465) --- src/lib.rs | 158 +++++++++---- src/properties/custom.rs | 29 +-- src/values/angle.rs | 17 +- src/values/calc.rs | 35 +-- src/values/color.rs | 396 ++++++++++++++++++++------------- src/values/length.rs | 2 +- src/values/percentage.rs | 24 +- src/values/time.rs | 10 +- website/pages/transpilation.md | 4 +- 9 files changed, 423 insertions(+), 252 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 96b640f2..04cb0e49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17514,6 +17514,7 @@ mod tests { minify_test(".foo { color: hsl(100deg, 100%, 50%) }", ".foo{color:#5f0}"); minify_test(".foo { color: hsl(100, 100%, 50%) }", ".foo{color:#5f0}"); minify_test(".foo { color: hsl(100 100% 50%) }", ".foo{color:#5f0}"); + minify_test(".foo { color: hsl(100 100 50) }", ".foo{color:#5f0}"); minify_test(".foo { color: hsl(100, 100%, 50%, .8) }", ".foo{color:#5f0c}"); minify_test(".foo { color: hsl(100 100% 50% / .8) }", ".foo{color:#5f0c}"); minify_test(".foo { color: hsla(100, 100%, 50%, .8) }", ".foo{color:#5f0c}"); @@ -17525,12 +17526,21 @@ mod tests { minify_test(".foo { color: hwb(194 0% 0% / 50%) }", ".foo{color:#00c4ff80}"); minify_test(".foo { color: hwb(194 0% 50%) }", ".foo{color:#006280}"); minify_test(".foo { color: hwb(194 50% 0%) }", ".foo{color:#80e1ff}"); + minify_test(".foo { color: hwb(194 50 0) }", ".foo{color:#80e1ff}"); minify_test(".foo { color: hwb(194 50% 50%) }", ".foo{color:gray}"); // minify_test(".foo { color: ActiveText }", ".foo{color:ActiveTet}"); minify_test( ".foo { color: lab(29.2345% 39.3825 20.0664); }", ".foo{color:lab(29.2345% 39.3825 20.0664)}", ); + minify_test( + ".foo { color: lab(29.2345 39.3825 20.0664); }", + ".foo{color:lab(29.2345% 39.3825 20.0664)}", + ); + minify_test( + ".foo { color: lab(29.2345% 39.3825% 20.0664%); }", + ".foo{color:lab(29.2345% 49.2281 25.083)}", + ); minify_test( ".foo { color: lab(29.2345% 39.3825 20.0664 / 100%); }", ".foo{color:lab(29.2345% 39.3825 20.0664)}", @@ -17543,6 +17553,14 @@ mod tests { ".foo { color: lch(29.2345% 44.2 27); }", ".foo{color:lch(29.2345% 44.2 27)}", ); + minify_test( + ".foo { color: lch(29.2345 44.2 27); }", + ".foo{color:lch(29.2345% 44.2 27)}", + ); + minify_test( + ".foo { color: lch(29.2345% 44.2% 27deg); }", + ".foo{color:lch(29.2345% 66.3 27)}", + ); minify_test( ".foo { color: lch(29.2345% 44.2 45deg); }", ".foo{color:lch(29.2345% 44.2 45)}", @@ -17563,10 +17581,26 @@ mod tests { ".foo { color: oklab(40.101% 0.1147 0.0453); }", ".foo{color:oklab(40.101% .1147 .0453)}", ); + minify_test( + ".foo { color: oklab(.40101 0.1147 0.0453); }", + ".foo{color:oklab(40.101% .1147 .0453)}", + ); + minify_test( + ".foo { color: oklab(40.101% 0.1147% 0.0453%); }", + ".foo{color:oklab(40.101% .0004588 .0001812)}", + ); minify_test( ".foo { color: oklch(40.101% 0.12332 21.555); }", ".foo{color:oklch(40.101% .12332 21.555)}", ); + minify_test( + ".foo { color: oklch(.40101 0.12332 21.555); }", + ".foo{color:oklch(40.101% .12332 21.555)}", + ); + minify_test( + ".foo { color: oklch(40.101% 0.12332% 21.555); }", + ".foo{color:oklch(40.101% .00049328 21.555)}", + ); minify_test( ".foo { color: oklch(40.101% 0.12332 .5turn); }", ".foo{color:oklch(40.101% .12332 180)}", @@ -18431,7 +18465,7 @@ mod tests { } test("lab(from indianred calc(l * .8) a b)", "lab(43.1402% 45.7516 23.1557)"); - test("lch(from indianred calc(l + 10%) c h)", "lch(63.9252% 51.2776 26.8448)"); + test("lch(from indianred calc(l + 10) c h)", "lch(63.9252% 51.2776 26.8448)"); test("lch(from indianred l calc(c - 50) h)", "lch(53.9252% 1.27763 26.8448)"); test( "lch(from indianred l c calc(h + 180deg))", @@ -18447,12 +18481,23 @@ mod tests { "rgba(205, 92, 92, .7)", ); test( - "rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + 20%))", + "rgb(from rgba(205, 92, 92, .5) r g b / calc(alpha + .2))", "rgba(205, 92, 92, .7)", ); test("lch(from indianred l sin(c) h)", "lch(53.9252% .84797 26.8448)"); test("lch(from indianred l sqrt(c) h)", "lch(53.9252% 7.16084 26.8448)"); - test("lch(from indianred l c sin(h))", "lch(53.9252% 51.2776 .990043)"); + test("lch(from indianred l c sin(h))", "lch(53.9252% 51.2776 .451575)"); + test("lch(from indianred calc(10% + 20%) c h)", "lch(30% 51.2776 26.8448)"); + test("lch(from indianred calc(10 + 20) c h)", "lch(30% 51.2776 26.8448)"); + test("lch(from indianred l c calc(10 + 20))", "lch(53.9252% 51.2776 30)"); + test( + "lch(from indianred l c calc(10deg + 20deg))", + "lch(53.9252% 51.2776 30)", + ); + test( + "lch(from indianred l c calc(10deg + 0.35rad))", + "lch(53.9252% 51.2776 30.0535)", + ); minify_test( ".foo{color:lch(from currentColor l c sin(h))}", ".foo{color:lch(from currentColor l c sin(h))}", @@ -18566,21 +18611,15 @@ mod tests { // Testing permutation. test("rgb(from rebeccapurple g b r)", "rgb(51, 153, 102)"); - test("rgb(from rebeccapurple b alpha r / g)", "rgba(153, 255, 102, 0.2)"); - test("rgb(from rebeccapurple r r r / r)", "rgba(102, 102, 102, 0.4)"); - test( - "rgb(from rebeccapurple alpha alpha alpha / alpha)", - "rgb(255, 255, 255)", - ); + test("rgb(from rebeccapurple b alpha r / g)", "rgba(153, 1, 102, 1)"); + test("rgb(from rebeccapurple r r r / r)", "rgba(102, 102, 102, 1)"); + test("rgb(from rebeccapurple alpha alpha alpha / alpha)", "rgb(1, 1, 1)"); test("rgb(from rgb(20%, 40%, 60%, 80%) g b r)", "rgb(102, 153, 51)"); - test( - "rgb(from rgb(20%, 40%, 60%, 80%) b alpha r / g)", - "rgba(153, 204, 51, 0.4)", - ); - test("rgb(from rgb(20%, 40%, 60%, 80%) r r r / r)", "rgba(51, 51, 51, 0.2)"); + test("rgb(from rgb(20%, 40%, 60%, 80%) b alpha r / g)", "rgba(153, 1, 51, 1)"); + test("rgb(from rgb(20%, 40%, 60%, 80%) r r r / r)", "rgba(51, 51, 51, 1)"); test( "rgb(from rgb(20%, 40%, 60%, 80%) alpha alpha alpha / alpha)", - "rgba(204, 204, 204, 0.8)", + "rgba(1, 1, 1, 0.8)", ); // Testing mixes of number and percentage. (These would not be allowed in the non-relative syntax). @@ -18704,17 +18743,29 @@ mod tests { // Testing valid permutation (types match). test("hsl(from rebeccapurple h l s)", "rgb(128, 77, 179)"); - test("hsl(from rebeccapurple h alpha l / s)", "rgba(102, 0, 204, 0.5)"); - test("hsl(from rebeccapurple h l l / l)", "rgba(102, 61, 143, 0.4)"); - test("hsl(from rebeccapurple h alpha alpha / alpha)", "rgb(255, 255, 255)"); + test( + "hsl(from rebeccapurple h calc(alpha * 100) l / calc(s / 100))", + "rgba(102, 0, 204, 0.5)", + ); + test( + "hsl(from rebeccapurple h l l / calc(l / 100))", + "rgba(102, 61, 143, 0.4)", + ); + test( + "hsl(from rebeccapurple h calc(alpha * 100) calc(alpha * 100) / calc(alpha * 100))", + "rgb(255, 255, 255)", + ); test("hsl(from rgb(20%, 40%, 60%, 80%) h l s)", "rgb(77, 128, 179)"); test( - "hsl(from rgb(20%, 40%, 60%, 80%) h alpha l / s)", + "hsl(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) l / calc(s / 100))", "rgba(20, 102, 184, 0.5)", ); - test("hsl(from rgb(20%, 40%, 60%, 80%) h l l / l)", "rgba(61, 102, 143, 0.4)"); test( - "hsl(from rgb(20%, 40%, 60%, 80%) h alpha alpha / alpha)", + "hsl(from rgb(20%, 40%, 60%, 80%) h l l / calc(l / 100))", + "rgba(61, 102, 143, 0.4)", + ); + test( + "hsl(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) calc(alpha * 100) / alpha)", "rgba(163, 204, 245, 0.8)", ); @@ -18842,17 +18893,29 @@ mod tests { // Testing valid permutation (types match). test("hwb(from rebeccapurple h b w)", "rgb(153, 102, 204)"); - test("hwb(from rebeccapurple h alpha w / b)", "rgba(213, 213, 213, 0.4)"); - test("hwb(from rebeccapurple h w w / w)", "rgba(128, 51, 204, 0.2)"); - test("hwb(from rebeccapurple h alpha alpha / alpha)", "rgb(128, 128, 128)"); + test( + "hwb(from rebeccapurple h calc(alpha * 100) w / calc(b / 100))", + "rgba(213, 213, 213, 0.4)", + ); + test( + "hwb(from rebeccapurple h w w / calc(w / 100))", + "rgba(128, 51, 204, 0.2)", + ); + test( + "hwb(from rebeccapurple h calc(alpha * 100) calc(alpha * 100) / alpha)", + "rgb(128, 128, 128)", + ); test("hwb(from rgb(20%, 40%, 60%, 80%) h b w)", "rgb(102, 153, 204)"); test( - "hwb(from rgb(20%, 40%, 60%, 80%) h alpha w / b)", + "hwb(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) w / calc(b / 100))", "rgba(204, 204, 204, 0.4)", ); - test("hwb(from rgb(20%, 40%, 60%, 80%) h w w / w)", "rgba(51, 128, 204, 0.2)"); test( - "hwb(from rgb(20%, 40%, 60%, 80%) h alpha alpha / alpha)", + "hwb(from rgb(20%, 40%, 60%, 80%) h w w / calc(w / 100))", + "rgba(51, 128, 204, 0.2)", + ); + test( + "hwb(from rgb(20%, 40%, 60%, 80%) h calc(alpha * 100) calc(alpha * 100) / alpha)", "rgba(128, 128, 128, 0.8)", ); @@ -19305,7 +19368,11 @@ mod tests { // NOTE: 'c' is a valid hue, as hue is |. test( &format!("{}(from {}(70% 45 30) alpha c h / l)", color_space, color_space), - &format!("{}(100% 45 30 / 0.7)", color_space), + &format!( + "{}(1 45 30 / {})", + color_space, + if *color_space == "lch" { "1" } else { ".7" } + ), ); test( &format!("{}(from {}(70% 45 30) l c c / alpha)", color_space, color_space), @@ -19313,15 +19380,19 @@ mod tests { ); test( &format!("{}(from {}(70% 45 30) alpha c h / alpha)", color_space, color_space), - &format!("{}(100% 45 30)", color_space), + &format!("{}(1 45 30)", color_space), ); test( &format!("{}(from {}(70% 45 30) alpha c c / alpha)", color_space, color_space), - &format!("{}(100% 45 45)", color_space), + &format!("{}(1 45 45)", color_space), ); test( &format!("{}(from {}(70% 45 30 / 40%) alpha c h / l)", color_space, color_space), - &format!("{}(40% 45 30 / 0.7)", color_space), + &format!( + "{}(.4 45 30 / {})", + color_space, + if *color_space == "lch" { "1" } else { ".7" } + ), ); test( &format!("{}(from {}(70% 45 30 / 40%) l c c / alpha)", color_space, color_space), @@ -19332,14 +19403,14 @@ mod tests { "{}(from {}(70% 45 30 / 40%) alpha c h / alpha)", color_space, color_space ), - &format!("{}(40% 45 30 / 0.4)", color_space), + &format!("{}(.4 45 30 / 0.4)", color_space), ); test( &format!( "{}(from {}(70% 45 30 / 40%) alpha c c / alpha)", color_space, color_space ), - &format!("{}(40% 45 45 / 0.4)", color_space), + &format!("{}(.4 45 45 / 0.4)", color_space), ); // Testing with calc(). @@ -20201,13 +20272,10 @@ mod tests { ".foo{color:hsl(from rebeccapurple s h l)}", ".foo{color:hsl(from rebeccapurple s h l)}", ); + minify_test(".foo{color:hsl(from rebeccapurple s s s / s)}", ".foo{color:#bfaa40}"); minify_test( - ".foo{color:hsl(from rebeccapurple s s s / s)}", - ".foo{color:hsl(from rebeccapurple s s s/s)}", - ); - minify_test( - ".foo{color:hsl(from rebeccapurple alpha alpha alpha / alpha)}", - ".foo{color:hsl(from rebeccapurple alpha alpha alpha/alpha)}", + ".foo{color:hsl(from rebeccapurple calc(alpha * 100) calc(alpha * 100) calc(alpha * 100) / alpha)}", + ".foo{color:#fff}", ); } } @@ -28761,10 +28829,14 @@ mod tests { let source_index = sm.add_source("input.css"); sm.set_source_content(source_index as usize, source).unwrap(); - let mut stylesheet = StyleSheet::parse(&source, ParserOptions { - source_index, - ..Default::default() - }).unwrap(); + let mut stylesheet = StyleSheet::parse( + &source, + ParserOptions { + source_index, + ..Default::default() + }, + ) + .unwrap(); stylesheet.minify(MinifyOptions::default()).unwrap(); stylesheet .to_css(PrinterOptions { diff --git a/src/properties/custom.rs b/src/properties/custom.rs index 824bc5b7..83a29273 100644 --- a/src/properties/custom.rs +++ b/src/properties/custom.rs @@ -12,7 +12,7 @@ use crate::traits::{Parse, ParseWithOptions, ToCss}; use crate::values::angle::Angle; use crate::values::color::{ parse_hsl_hwb_components, parse_rgb_components, ColorFallbackKind, ComponentParser, CssColor, LightDarkColor, - HSL, RGBA, SRGB, + HSL, RGB, RGBA, }; use crate::values::ident::{CustomIdent, DashedIdent, DashedIdentReference, Ident}; use crate::values::length::{serialize_dimension, LengthValue}; @@ -1594,7 +1594,7 @@ impl<'i> UnresolvedColor<'i> { match_ignore_ascii_case! { &*f, "rgb" => { input.parse_nested_block(|input| { - parser.parse_relative::(input, |input, parser| { + parser.parse_relative::(input, |input, parser| { let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?; if is_legacy { return Err(input.new_custom_error(ParserError::InvalidValue)) @@ -1636,20 +1636,15 @@ impl<'i> UnresolvedColor<'i> { where W: std::fmt::Write, { - #[inline] - fn c(c: &f32) -> i32 { - (c * 255.0).round().clamp(0.0, 255.0) as i32 - } - match self { UnresolvedColor::RGB { r, g, b, alpha } => { if should_compile!(dest.targets.current, SpaceSeparatedColorNotation) { dest.write_str("rgba(")?; - c(r).to_css(dest)?; + r.to_css(dest)?; dest.delim(',', false)?; - c(g).to_css(dest)?; + g.to_css(dest)?; dest.delim(',', false)?; - c(b).to_css(dest)?; + b.to_css(dest)?; dest.delim(',', false)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(')')?; @@ -1657,11 +1652,11 @@ impl<'i> UnresolvedColor<'i> { } dest.write_str("rgb(")?; - c(r).to_css(dest)?; + r.to_css(dest)?; dest.write_char(' ')?; - c(g).to_css(dest)?; + g.to_css(dest)?; dest.write_char(' ')?; - c(b).to_css(dest)?; + b.to_css(dest)?; dest.delim('/', true)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(')') @@ -1671,9 +1666,9 @@ impl<'i> UnresolvedColor<'i> { dest.write_str("hsla(")?; h.to_css(dest)?; dest.delim(',', false)?; - Percentage(*s).to_css(dest)?; + Percentage(*s / 100.0).to_css(dest)?; dest.delim(',', false)?; - Percentage(*l).to_css(dest)?; + Percentage(*l / 100.0).to_css(dest)?; dest.delim(',', false)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(')')?; @@ -1683,9 +1678,9 @@ impl<'i> UnresolvedColor<'i> { dest.write_str("hsl(")?; h.to_css(dest)?; dest.write_char(' ')?; - Percentage(*s).to_css(dest)?; + Percentage(*s / 100.0).to_css(dest)?; dest.write_char(' ')?; - Percentage(*l).to_css(dest)?; + Percentage(*l / 100.0).to_css(dest)?; dest.delim('/', true)?; alpha.to_css(dest, is_custom_property)?; dest.write_char(')') diff --git a/src/values/angle.rs b/src/values/angle.rs index a1bc6a00..dff23a28 100644 --- a/src/values/angle.rs +++ b/src/values/angle.rs @@ -183,11 +183,13 @@ impl Into> for Angle { } } -impl From> for Angle { - fn from(calc: Calc) -> Angle { +impl TryFrom> for Angle { + type Error = (); + + fn try_from(calc: Calc) -> Result { match calc { - Calc::Value(v) => *v, - _ => unreachable!(), + Calc::Value(v) => Ok(*v), + _ => Err(()), } } } @@ -285,6 +287,13 @@ macro_rules! impl_try_from_angle { Err(()) } } + + impl TryInto for $t { + type Error = (); + fn try_into(self) -> Result { + Err(()) + } + } }; } diff --git a/src/values/calc.rs b/src/values/calc.rs index 22303967..6022cf2f 100644 --- a/src/values/calc.rs +++ b/src/values/calc.rs @@ -314,8 +314,9 @@ impl< + TrySign + std::cmp::PartialOrd + Into> - + From> + + TryFrom> + TryFrom + + TryInto + Clone + std::fmt::Debug, > Parse<'i> for Calc @@ -335,8 +336,9 @@ impl< + TrySign + std::cmp::PartialOrd + Into> - + From> + + TryFrom> + TryFrom + + TryInto + Clone + std::fmt::Debug, > Calc @@ -550,12 +552,12 @@ impl< match *input.next()? { Token::Delim('+') => { let next = Calc::parse_product(input, parse_ident)?; - cur = cur.add(next); + cur = cur.add(next).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?; } Token::Delim('-') => { let mut rhs = Calc::parse_product(input, parse_ident)?; rhs = rhs * -1.0; - cur = cur.add(rhs); + cur = cur.add(rhs).map_err(|_| input.new_custom_error(ParserError::InvalidValue))?; } ref t => { let t = t.clone(); @@ -744,9 +746,12 @@ impl< ) -> Result>> { input.parse_nested_block(|input| { let v: Calc = Calc::parse_sum(input, |v| { - parse_ident(v).and_then(|v| match v { - Calc::Number(v) => Some(Calc::Number(v)), - _ => None, + parse_ident(v).and_then(|v| -> Option> { + match v { + Calc::Number(v) => Some(Calc::Number(v)), + Calc::Value(v) => (*v).try_into().ok().map(|v| Calc::Value(Box::new(v))), + _ => None, + } }) })?; let rad = match v { @@ -903,11 +908,9 @@ impl> std::ops::Mul for Calc { } } -impl> + std::convert::From> + std::fmt::Debug> AddInternal - for Calc -{ - fn add(self, other: Calc) -> Calc { - match (self, other) { +impl> + std::convert::TryFrom> + std::fmt::Debug> Calc { + pub(crate) fn add(self, other: Calc) -> Result, >>::Error> { + Ok(match (self, other) { (Calc::Value(a), Calc::Value(b)) => (a.add(*b)).into(), (Calc::Number(a), Calc::Number(b)) => Calc::Number(a + b), (Calc::Sum(a, b), Calc::Number(c)) => { @@ -934,10 +937,10 @@ impl> + std::convert::From> | (a, b @ Calc::Product(..)) => Calc::Sum(Box::new(a), Box::new(b)), (Calc::Function(a), b) => Calc::Sum(Box::new(Calc::Function(a)), Box::new(b)), (a, Calc::Function(b)) => Calc::Sum(Box::new(a), Box::new(Calc::Function(b))), - (Calc::Value(a), b) => (a.add(V::from(b))).into(), - (a, Calc::Value(b)) => (V::from(a).add(*b)).into(), - (a @ Calc::Sum(..), b @ Calc::Sum(..)) => V::from(a).add(V::from(b)).into(), - } + (Calc::Value(a), b) => (a.add(V::try_from(b)?)).into(), + (a, Calc::Value(b)) => (V::try_from(a)?.add(*b)).into(), + (a @ Calc::Sum(..), b @ Calc::Sum(..)) => V::try_from(a)?.add(V::try_from(b)?).into(), + }) } } diff --git a/src/values/color.rs b/src/values/color.rs index 3edf8447..b21eb196 100644 --- a/src/values/color.rs +++ b/src/values/color.rs @@ -101,7 +101,7 @@ impl CurrentColor { #[serde(tag = "type", rename_all = "lowercase")] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] enum RGBColor { - RGB(SRGB), + RGB(RGB), } #[cfg(feature = "serde")] @@ -222,7 +222,7 @@ pub enum PredefinedColor { #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum FloatColor { /// An RGB color. - RGB(SRGB), + RGB(RGB), /// An HSL color. HSL(HSL), /// An HWB color. @@ -617,16 +617,16 @@ impl ToCss for CssColor { Ok(()) } CssColor::LAB(lab) => match &**lab { - LABColor::LAB(lab) => write_components("lab", lab.l, lab.a, lab.b, lab.alpha, dest), - LABColor::LCH(lch) => write_components("lch", lch.l, lch.c, lch.h, lch.alpha, dest), + LABColor::LAB(lab) => write_components("lab", lab.l / 100.0, lab.a, lab.b, lab.alpha, dest), + LABColor::LCH(lch) => write_components("lch", lch.l / 100.0, lch.c, lch.h, lch.alpha, dest), LABColor::OKLAB(lab) => write_components("oklab", lab.l, lab.a, lab.b, lab.alpha, dest), LABColor::OKLCH(lch) => write_components("oklch", lch.l, lch.c, lch.h, lch.alpha, dest), }, CssColor::Predefined(predefined) => write_predefined(predefined, dest), CssColor::Float(float) => { // Serialize as hex. - let srgb = SRGB::from(**float); - CssColor::from(srgb).to_css(dest) + let rgb = RGB::from(**float); + CssColor::from(rgb).to_css(dest) } CssColor::LightDark(light, dark) => { if should_compile!(dest.targets.current, LightDark) { @@ -718,21 +718,23 @@ impl RelativeComponentParser { } } - fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option { + fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option<(f32, ChannelType)> { if ident.eq_ignore_ascii_case(self.names.0) && allowed_types.intersects(self.types.0) { - return Some(self.components.0); + return Some((self.components.0, self.types.0)); } if ident.eq_ignore_ascii_case(self.names.1) && allowed_types.intersects(self.types.1) { - return Some(self.components.1); + return Some((self.components.1, self.types.1)); } if ident.eq_ignore_ascii_case(self.names.2) && allowed_types.intersects(self.types.2) { - return Some(self.components.2); + return Some((self.components.2, self.types.2)); } - if ident.eq_ignore_ascii_case("alpha") && allowed_types.intersects(ChannelType::Percentage) { - return Some(self.components.3); + if ident.eq_ignore_ascii_case("alpha") + && allowed_types.intersects(ChannelType::Number | ChannelType::Percentage) + { + return Some((self.components.3, ChannelType::Number)); } None @@ -742,24 +744,12 @@ impl RelativeComponentParser { &self, input: &mut Parser<'i, 't>, allowed_types: ChannelType, - ) -> Result>> { + ) -> Result<(f32, ChannelType), ParseError<'i, ParserError<'i>>> { match self.get_ident(input.expect_ident()?.as_ref(), allowed_types) { Some(v) => Ok(v), None => Err(input.new_error_for_next_token()), } } - - fn parse_calc<'i, 't>( - &self, - input: &mut Parser<'i, 't>, - allowed_types: ChannelType, - ) -> Result>> { - match Calc::parse_with(input, |ident| self.get_ident(ident, allowed_types).map(Calc::Number)) { - Ok(Calc::Value(v)) => Ok(*v), - Ok(Calc::Number(n)) => Ok(n), - _ => Err(input.new_custom_error(ParserError::InvalidValue)), - } - } } impl<'i> ColorParser<'i> for RelativeComponentParser { @@ -770,46 +760,55 @@ impl<'i> ColorParser<'i> for RelativeComponentParser { &self, input: &mut Parser<'i, 't>, ) -> Result> { - if let Ok(value) = input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number)) { - return Ok(AngleOrNumber::Number { value }); - } - - if let Ok(value) = input.try_parse(|input| self.parse_calc(input, ChannelType::Angle | ChannelType::Number)) { - return Ok(AngleOrNumber::Number { value }); + if let Ok((value, ty)) = + input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number)) + { + return Ok(match ty { + ChannelType::Angle => AngleOrNumber::Angle { degrees: value }, + ChannelType::Number => AngleOrNumber::Number { value }, + _ => unreachable!(), + }); } - if let Ok(value) = input.try_parse(|input| -> Result>> { + if let Ok(value) = input.try_parse(|input| -> Result>> { match Calc::parse_with(input, |ident| { self .get_ident(ident, ChannelType::Angle | ChannelType::Number) - .map(|v| Calc::Value(Box::new(Angle::Deg(v)))) + .map(|(value, ty)| match ty { + ChannelType::Angle => Calc::Value(Box::new(Angle::Deg(value))), + ChannelType::Number => Calc::Number(value), + _ => unreachable!(), + }) }) { - Ok(Calc::Value(v)) => Ok(*v), + Ok(Calc::Value(v)) => Ok(AngleOrNumber::Angle { + degrees: v.to_degrees(), + }), + Ok(Calc::Number(v)) => Ok(AngleOrNumber::Number { value: v }), _ => Err(input.new_custom_error(ParserError::InvalidValue)), } }) { - return Ok(AngleOrNumber::Angle { - degrees: value.to_degrees(), - }); + return Ok(value); } Err(input.new_error_for_next_token()) } fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result> { - if let Ok(value) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) { + if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) { return Ok(value); } - if let Ok(value) = input.try_parse(|input| self.parse_calc(input, ChannelType::Number)) { - return Ok(value); + match Calc::parse_with(input, |ident| { + self.get_ident(ident, ChannelType::Number).map(|(v, _)| Calc::Number(v)) + }) { + Ok(Calc::Value(v)) => Ok(*v), + Ok(Calc::Number(n)) => Ok(n), + _ => Err(input.new_error_for_next_token()), } - - Err(input.new_error_for_next_token()) } fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result> { - if let Ok(value) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) { + if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) { return Ok(value); } @@ -817,7 +816,7 @@ impl<'i> ColorParser<'i> for RelativeComponentParser { match Calc::parse_with(input, |ident| { self .get_ident(ident, ChannelType::Percentage) - .map(|v| Calc::Value(Box::new(Percentage(v)))) + .map(|(v, _)| Calc::Value(Box::new(Percentage(v)))) }) { Ok(Calc::Value(v)) => Ok(*v), _ => Err(input.new_custom_error(ParserError::InvalidValue)), @@ -833,29 +832,32 @@ impl<'i> ColorParser<'i> for RelativeComponentParser { &self, input: &mut Parser<'i, 't>, ) -> Result> { - if let Ok(value) = + if let Ok((value, ty)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage | ChannelType::Number)) { - return Ok(NumberOrPercentage::Percentage { unit_value: value }); - } - - if let Ok(value) = - input.try_parse(|input| self.parse_calc(input, ChannelType::Percentage | ChannelType::Number)) - { - return Ok(NumberOrPercentage::Percentage { unit_value: value }); + return Ok(match ty { + ChannelType::Percentage => NumberOrPercentage::Percentage { unit_value: value }, + ChannelType::Number => NumberOrPercentage::Number { value }, + _ => unreachable!(), + }); } - if let Ok(value) = input.try_parse(|input| -> Result>> { + if let Ok(value) = input.try_parse(|input| -> Result>> { match Calc::parse_with(input, |ident| { self .get_ident(ident, ChannelType::Percentage | ChannelType::Number) - .map(|v| Calc::Value(Box::new(Percentage(v)))) + .map(|(value, ty)| match ty { + ChannelType::Percentage => Calc::Value(Box::new(Percentage(value))), + ChannelType::Number => Calc::Number(value), + _ => unreachable!(), + }) }) { - Ok(Calc::Value(v)) => Ok(*v), + Ok(Calc::Value(v)) => Ok(NumberOrPercentage::Percentage { unit_value: v.0 }), + Ok(Calc::Number(v)) => Ok(NumberOrPercentage::Number { value: v }), _ => Err(input.new_custom_error(ParserError::InvalidValue)), } }) { - return Ok(NumberOrPercentage::Percentage { unit_value: value.0 }); + return Ok(value); } Err(input.new_error_for_next_token()) @@ -1026,22 +1028,22 @@ fn parse_color_function<'i, 't>( match_ignore_ascii_case! {&*function, "lab" => { - parse_lab::(input, &mut parser, |l, a, b, alpha| { + parse_lab::(input, &mut parser, 100.0, 125.0, |l, a, b, alpha| { LABColor::LAB(LAB { l, a, b, alpha }) }) }, "oklab" => { - parse_lab::(input, &mut parser, |l, a, b, alpha| { + parse_lab::(input, &mut parser, 1.0, 0.4, |l, a, b, alpha| { LABColor::OKLAB(OKLAB { l, a, b, alpha }) }) }, "lch" => { - parse_lch::(input, &mut parser, |l, c, h, alpha| { + parse_lch::(input, &mut parser, 100.0, 150.0, |l, c, h, alpha| { LABColor::LCH(LCH { l, c, h, alpha }) }) }, "oklch" => { - parse_lch::(input, &mut parser, |l, c, h, alpha| { + parse_lch::(input, &mut parser, 1.0, 0.4, |l, c, h, alpha| { LABColor::OKLCH(OKLCH { l, c, h, alpha }) }) }, @@ -1100,15 +1102,17 @@ fn parse_color_function<'i, 't>( fn parse_lab<'i, 't, T: TryFrom + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>( input: &mut Parser<'i, 't>, parser: &mut ComponentParser, + l_basis: f32, + ab_basis: f32, f: F, ) -> Result>> { // https://www.w3.org/TR/css-color-4/#funcdef-lab input.parse_nested_block(|input| { parser.parse_relative::(input, |input, parser| { // f32::max() does not propagate NaN, so use clamp for now until f32::maximum() is stable. - let l = parser.parse_percentage(input)?.clamp(0.0, f32::MAX); - let a = parser.parse_number(input)?; - let b = parser.parse_number(input)?; + let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX); + let a = parse_number_or_percentage(input, parser, ab_basis)?; + let b = parse_number_or_percentage(input, parser, ab_basis)?; let alpha = parse_alpha(input, parser)?; let lab = f(l, a, b, alpha); @@ -1122,6 +1126,8 @@ fn parse_lab<'i, 't, T: TryFrom + ColorSpace, F: Fn(f32, f32, f32, f32 fn parse_lch<'i, 't, T: TryFrom + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>( input: &mut Parser<'i, 't>, parser: &mut ComponentParser, + l_basis: f32, + c_basis: f32, f: F, ) -> Result>> { // https://www.w3.org/TR/css-color-4/#funcdef-lch @@ -1136,8 +1142,8 @@ fn parse_lch<'i, 't, T: TryFrom + ColorSpace, F: Fn(f32, f32, f32, f32 } } - let l = parser.parse_percentage(input)?.clamp(0.0, f32::MAX); - let c = parser.parse_number(input)?.clamp(0.0, f32::MAX); + let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX); + let c = parse_number_or_percentage(input, parser, c_basis)?.clamp(0.0, f32::MAX); let h = parse_angle_or_number(input, parser)?; let alpha = parse_alpha(input, parser)?; let lab = f(l, c, h, alpha); @@ -1203,9 +1209,9 @@ fn parse_predefined_relative<'i, 't>( // Out of gamut values should not be clamped, i.e. values < 0 or > 1 should be preserved. // The browser will gamut-map the color for the target device that it is rendered on. - let a = input.try_parse(|input| parse_number_or_percentage(input, parser))?; - let b = input.try_parse(|input| parse_number_or_percentage(input, parser))?; - let c = input.try_parse(|input| parse_number_or_percentage(input, parser))?; + let a = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?; + let b = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?; + let c = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?; let alpha = parse_alpha(input, parser)?; let res = match_ignore_ascii_case! { &*&colorspace, @@ -1258,11 +1264,11 @@ pub(crate) fn parse_hsl_hwb_components<'i, 't, T: TryFrom + ColorSpace let h = parse_angle_or_number(input, parser)?; let is_legacy_syntax = allows_legacy && parser.from.is_none() && !h.is_nan() && input.try_parse(|p| p.expect_comma()).is_ok(); - let a = parser.parse_percentage(input)?.clamp(0.0, 1.0); + let a = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0); if is_legacy_syntax { input.expect_comma()?; } - let b = parser.parse_percentage(input)?.clamp(0.0, 1.0); + let b = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0); if is_legacy_syntax && (a.is_nan() || b.is_nan()) { return Err(input.new_custom_error(ParserError::InvalidValue)); } @@ -1276,7 +1282,7 @@ fn parse_rgb<'i, 't>( ) -> Result>> { // https://drafts.csswg.org/css-color-4/#rgb-functions input.parse_nested_block(|input| { - parser.parse_relative::(input, |input, parser| { + parser.parse_relative::(input, |input, parser| { let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?; let alpha = if is_legacy { parse_legacy_alpha(input, parser)? @@ -1288,10 +1294,15 @@ fn parse_rgb<'i, 't>( if is_legacy { Ok(CssColor::RGBA(RGBA::new(r as u8, g as u8, b as u8, alpha))) } else { - Ok(CssColor::RGBA(RGBA::from_floats(r, g, b, alpha))) + Ok(CssColor::RGBA(RGBA::from_floats( + r / 255.0, + g / 255.0, + b / 255.0, + alpha, + ))) } } else { - Ok(CssColor::Float(Box::new(FloatColor::RGB(SRGB { r, g, b, alpha })))) + Ok(CssColor::Float(Box::new(FloatColor::RGB(RGB { r, g, b, alpha })))) } }) }) @@ -1327,8 +1338,8 @@ pub(crate) fn parse_rgb_components<'i, 't>( fn get_component<'i, 't>(value: NumberOrPercentage) -> f32 { match value { NumberOrPercentage::Number { value } if value.is_nan() => value, - NumberOrPercentage::Number { value } => value.round().clamp(0.0, 255.0) / 255.0, - NumberOrPercentage::Percentage { unit_value } => unit_value.clamp(0.0, 1.0), + NumberOrPercentage::Number { value } => value.round().clamp(0.0, 255.0), + NumberOrPercentage::Percentage { unit_value } => (unit_value * 255.0).round().clamp(0.0, 255.0), } } @@ -1359,10 +1370,11 @@ fn parse_angle_or_number<'i, 't>( fn parse_number_or_percentage<'i, 't>( input: &mut Parser<'i, 't>, parser: &ComponentParser, + percent_basis: f32, ) -> Result>> { Ok(match parser.parse_number_or_percentage(input)? { NumberOrPercentage::Number { value } => value, - NumberOrPercentage::Percentage { unit_value } => unit_value, + NumberOrPercentage::Percentage { unit_value } => unit_value * percent_basis, }) } @@ -1372,7 +1384,7 @@ fn parse_alpha<'i, 't>( parser: &ComponentParser, ) -> Result>> { let res = if input.try_parse(|input| input.expect_delim('/')).is_ok() { - parse_number_or_percentage(input, parser)?.clamp(0.0, 1.0) + parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0) } else { 1.0 }; @@ -1386,7 +1398,7 @@ fn parse_legacy_alpha<'i, 't>( ) -> Result>> { Ok(if !input.is_exhausted() { input.expect_comma()?; - parse_number_or_percentage(input, parser)?.clamp(0.0, 1.0) + parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0) } else { 1.0 }) @@ -1566,41 +1578,14 @@ define_colorspace! { /// A color in the [`sRGB`](https://www.w3.org/TR/css-color-4/#predefined-sRGB) color space. pub struct SRGB { /// The red component. - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))] - r: Percentage, + r: Number, /// The green component. - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))] - g: Percentage, + g: Number, /// The blue component. - #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))] - b: Percentage + b: Number } } -// serialize RGB components in the 0-255 range as it is more common. -#[cfg(feature = "serde")] -fn serialize_rgb_component(v: &f32, serializer: S) -> Result -where - S: serde::Serializer, -{ - let v = if !v.is_nan() { - (v * 255.0).round().max(0.0).min(255.0) - } else { - *v - }; - - serializer.serialize_f32(v) -} - -#[cfg(feature = "serde")] -fn deserialize_rgb_component<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let v: f32 = serde::Deserialize::deserialize(deserializer)?; - Ok(v / 255.0) -} - // Copied from an older version of cssparser. /// A color with red, green, blue, and alpha components, in a byte each. #[derive(Clone, Copy, PartialEq, Debug)] @@ -1688,15 +1673,28 @@ fn clamp_floor_256_f32(val: f32) -> u8 { val.round().max(0.).min(255.) as u8 } +define_colorspace! { + /// A color in the [`RGB`](https://w3c.github.io/csswg-drafts/css-color-4/#rgb-functions) color space. + /// Components are in the 0-255 range. + pub struct RGB { + /// The red component. + r: Number, + /// The green component. + g: Number, + /// The blue component. + b: Number + } +} + define_colorspace! { /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space. pub struct SRGBLinear { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1704,11 +1702,11 @@ define_colorspace! { /// A color in the [`display-p3`](https://www.w3.org/TR/css-color-4/#predefined-display-p3) color space. pub struct P3 { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1716,11 +1714,11 @@ define_colorspace! { /// A color in the [`a98-rgb`](https://www.w3.org/TR/css-color-4/#predefined-a98-rgb) color space. pub struct A98 { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1728,11 +1726,11 @@ define_colorspace! { /// A color in the [`prophoto-rgb`](https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb) color space. pub struct ProPhoto { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1740,11 +1738,11 @@ define_colorspace! { /// A color in the [`rec2020`](https://www.w3.org/TR/css-color-4/#predefined-rec2020) color space. pub struct Rec2020 { /// The red component. - r: Percentage, + r: Number, /// The green component. - g: Percentage, + g: Number, /// The blue component. - b: Percentage + b: Number } } @@ -1752,7 +1750,7 @@ define_colorspace! { /// A color in the [CIE Lab](https://www.w3.org/TR/css-color-4/#cie-lab) color space. pub struct LAB { /// The lightness component. - l: Percentage, + l: Number, /// The a component. a: Number, /// The b component. @@ -1764,7 +1762,7 @@ define_colorspace! { /// A color in the [CIE LCH](https://www.w3.org/TR/css-color-4/#cie-lab) color space. pub struct LCH { /// The lightness component. - l: Percentage, + l: Number, /// The chroma component. c: Number, /// The hue component. @@ -1776,7 +1774,7 @@ define_colorspace! { /// A color in the [OKLab](https://www.w3.org/TR/css-color-4/#ok-lab) color space. pub struct OKLAB { /// The lightness component. - l: Percentage, + l: Number, /// The a component. a: Number, /// The b component. @@ -1788,7 +1786,7 @@ define_colorspace! { /// A color in the [OKLCH](https://www.w3.org/TR/css-color-4/#ok-lab) color space. pub struct OKLCH { /// The lightness component. - l: Percentage, + l: Number, /// The chroma component. c: Number, /// The hue component. @@ -1800,11 +1798,11 @@ define_colorspace! { /// A color in the [`xyz-d50`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space. pub struct XYZd50 { /// The x component. - x: Percentage, + x: Number, /// The y component. - y: Percentage, + y: Number, /// The z component. - z: Percentage + z: Number } } @@ -1812,11 +1810,11 @@ define_colorspace! { /// A color in the [`xyz-d65`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space. pub struct XYZd65 { /// The x component. - x: Percentage, + x: Number, /// The y component. - y: Percentage, + y: Number, /// The z component. - z: Percentage + z: Number } } @@ -1826,9 +1824,9 @@ define_colorspace! { /// The hue component. h: Angle, /// The saturation component. - s: Percentage, + s: Number, /// The lightness component. - l: Percentage + l: Number } } @@ -1838,9 +1836,9 @@ define_colorspace! { /// The hue component. h: Angle, /// The whiteness component. - w: Percentage, + w: Number, /// The blackness component. - b: Percentage + b: Number } } @@ -1945,7 +1943,7 @@ impl From for XYZd50 { const E: f32 = 216.0 / 24389.0; // 6^3/29^3 let lab = lab.resolve_missing(); - let l = lab.l * 100.0; + let l = lab.l; let a = lab.a; let b = lab.b; @@ -2204,7 +2202,7 @@ impl From for LAB { let f2 = if z > E { z.cbrt() } else { (K * z + 16.0) / 116.0 }; - let l = ((116.0 * f1) - 16.0) / 100.0; + let l = (116.0 * f1) - 16.0; let a = 500.0 * (f0 - f1); let b = 200.0 * (f1 - f2); LAB { @@ -2657,8 +2655,8 @@ impl From for HSL { HSL { h, - s, - l, + s: s * 100.0, + l: l * 100.0, alpha: rgb.alpha, } } @@ -2669,7 +2667,7 @@ impl From for SRGB { // https://drafts.csswg.org/css-color/#hsl-to-rgb let hsl = hsl.resolve_missing(); let h = (hsl.h - 360.0 * (hsl.h / 360.0).floor()) / 360.0; - let (r, g, b) = hsl_to_rgb(h, hsl.s, hsl.l); + let (r, g, b) = hsl_to_rgb(h, hsl.s / 100.0, hsl.l / 100.0); SRGB { r, g, @@ -2690,8 +2688,8 @@ impl From for HWB { let b = 1.0 - r.max(g).max(b); HWB { h: hsl.h, - w, - b, + w: w * 100.0, + b: b * 100.0, alpha: rgb.alpha, } } @@ -2702,8 +2700,8 @@ impl From for SRGB { // https://drafts.csswg.org/css-color/#hwb-to-rgb let hwb = hwb.resolve_missing(); let h = hwb.h; - let w = hwb.w; - let b = hwb.b; + let w = hwb.w / 100.0; + let b = hwb.b / 100.0; if w + b >= 1.0 { let gray = w / (w + b); @@ -2717,8 +2715,8 @@ impl From for SRGB { let mut rgba = SRGB::from(HSL { h, - s: 1.0, - l: 0.5, + s: 100.0, + l: 50.0, alpha: hwb.alpha, }); let x = 1.0 - w - b; @@ -2730,13 +2728,7 @@ impl From for SRGB { } impl From for SRGB { - fn from(rgb: RGBA) -> Self { - Self::from(&rgb) - } -} - -impl From<&RGBA> for SRGB { - fn from(rgb: &RGBA) -> SRGB { + fn from(rgb: RGBA) -> SRGB { SRGB { r: rgb.red_f32(), g: rgb.green_f32(), @@ -2753,6 +2745,57 @@ impl From for RGBA { } } +impl From for RGB { + fn from(rgb: SRGB) -> Self { + RGB { + r: rgb.r * 255.0, + g: rgb.g * 255.0, + b: rgb.b * 255.0, + alpha: rgb.alpha, + } + } +} + +impl From for SRGB { + fn from(rgb: RGB) -> Self { + SRGB { + r: rgb.r / 255.0, + g: rgb.g / 255.0, + b: rgb.b / 255.0, + alpha: rgb.alpha, + } + } +} + +impl From for RGB { + fn from(rgb: RGBA) -> Self { + RGB::from(&rgb) + } +} + +impl From<&RGBA> for RGB { + fn from(rgb: &RGBA) -> Self { + RGB { + r: rgb.red as f32, + g: rgb.green as f32, + b: rgb.blue as f32, + alpha: rgb.alpha_f32(), + } + } +} + +impl From for RGBA { + fn from(rgb: RGB) -> Self { + let rgb = rgb.resolve(); + RGBA::new( + clamp_floor_256_f32(rgb.r), + clamp_floor_256_f32(rgb.g), + clamp_floor_256_f32(rgb.b), + rgb.alpha, + ) + } +} + // Once Rust specialization is stable, this could be simplified. via!(LAB -> XYZd50 -> XYZd65); via!(ProPhoto -> XYZd50 -> XYZd65); @@ -2844,6 +2887,20 @@ via!(HWB -> SRGB -> XYZd65); via!(HWB -> XYZd65 -> OKLAB); via!(HWB -> XYZd65 -> OKLCH); +via!(RGB -> SRGB -> LAB); +via!(RGB -> SRGB -> LCH); +via!(RGB -> SRGB -> OKLAB); +via!(RGB -> SRGB -> OKLCH); +via!(RGB -> SRGB -> P3); +via!(RGB -> SRGB -> SRGBLinear); +via!(RGB -> SRGB -> A98); +via!(RGB -> SRGB -> ProPhoto); +via!(RGB -> SRGB -> XYZd50); +via!(RGB -> SRGB -> XYZd65); +via!(RGB -> SRGB -> Rec2020); +via!(RGB -> SRGB -> HSL); +via!(RGB -> SRGB -> HWB); + // RGBA is an 8-bit version. Convert to SRGB, which is a // more accurate floating point representation for all operations. via!(RGBA -> SRGB -> LAB); @@ -2950,6 +3007,7 @@ color_space!(ProPhoto); color_space!(Rec2020); color_space!(HSL); color_space!(HWB); +color_space!(RGB); color_space!(RGBA); macro_rules! predefined { @@ -3012,6 +3070,7 @@ macro_rules! rgb { rgb!(SRGB); rgb!(HSL); rgb!(HWB); +rgb!(RGB); impl From for CssColor { fn from(color: RGBA) -> CssColor { @@ -3069,15 +3128,15 @@ macro_rules! hsl_hwb_color_gamut { impl ColorGamut for $t { #[inline] fn in_gamut(&self) -> bool { - self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0 + self.$a >= 0.0 && self.$a <= 100.0 && self.$b >= 0.0 && self.$b <= 100.0 } #[inline] fn clip(&self) -> Self { Self { h: self.h % 360.0, - $a: self.$a.clamp(0.0, 1.0), - $b: self.$b.clamp(0.0, 1.0), + $a: self.$a.clamp(0.0, 100.0), + $b: self.$b.clamp(0.0, 100.0), alpha: self.alpha.clamp(0.0, 1.0), } } @@ -3100,6 +3159,23 @@ unbounded_color_gamut!(OKLCH, l, c, h); hsl_hwb_color_gamut!(HSL, s, l); hsl_hwb_color_gamut!(HWB, w, b); +impl ColorGamut for RGB { + #[inline] + fn in_gamut(&self) -> bool { + self.r >= 0.0 && self.r <= 255.0 && self.g >= 0.0 && self.g <= 255.0 && self.b >= 0.0 && self.b <= 255.0 + } + + #[inline] + fn clip(&self) -> Self { + Self { + r: self.r.clamp(0.0, 255.0), + g: self.g.clamp(0.0, 255.0), + b: self.b.clamp(0.0, 255.0), + alpha: self.alpha.clamp(0.0, 1.0), + } + } +} + fn delta_eok>(a: T, b: OKLCH) -> f32 { // https://www.w3.org/TR/css-color-4/#color-difference-OK let a: OKLAB = a.into(); @@ -3532,7 +3608,7 @@ impl Interpolate for HSL { self.h = f32::NAN; } - if self.l.abs() < f32::EPSILON || (self.l - 1.0).abs() < f32::EPSILON { + if self.l.abs() < f32::EPSILON || (self.l - 100.0).abs() < f32::EPSILON { self.h = f32::NAN; self.s = f32::NAN; } @@ -3551,7 +3627,7 @@ impl Interpolate for HWB { fn adjust_powerless_components(&mut self) { // If white+black is equal to 100% (after normalization), it defines an achromatic color, // i.e. some shade of gray, without any hint of the chosen hue. In this case, the hue component is powerless. - if (self.w + self.b - 1.0).abs() < f32::EPSILON { + if (self.w + self.b - 100.0).abs() < f32::EPSILON { self.h = f32::NAN; } } diff --git a/src/values/length.rs b/src/values/length.rs index 149f1dd1..2ce01b06 100644 --- a/src/values/length.rs +++ b/src/values/length.rs @@ -656,7 +656,7 @@ impl Length { } match (a, b) { - (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b))), + (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b).unwrap())), (Length::Calc(calc), b) => { if let Calc::Value(a) = *calc { a.add(b) diff --git a/src/values/percentage.rs b/src/values/percentage.rs index 54352750..5e059457 100644 --- a/src/values/percentage.rs +++ b/src/values/percentage.rs @@ -74,11 +74,13 @@ impl std::convert::Into> for Percentage { } } -impl std::convert::From> for Percentage { - fn from(calc: Calc) -> Percentage { +impl std::convert::TryFrom> for Percentage { + type Error = (); + + fn try_from(calc: Calc) -> Result { match calc { - Calc::Value(v) => *v, - _ => unreachable!(), + Calc::Value(v) => Ok(*v), + _ => Err(()), } } } @@ -203,6 +205,7 @@ impl< + Zero + TrySign + TryFrom + + TryInto + PartialOrd + std::fmt::Debug, > Parse<'i> for DimensionPercentage @@ -349,7 +352,7 @@ impl + Clone + Zero + TrySign + std::fmt::Debug> DimensionPercentag match (a, b) { (DimensionPercentage::Calc(a), DimensionPercentage::Calc(b)) => { - DimensionPercentage::Calc(Box::new(a.add(*b))) + DimensionPercentage::Calc(Box::new(a.add(*b).unwrap())) } (DimensionPercentage::Calc(calc), b) => { if let Calc::Value(a) = *calc { @@ -434,6 +437,17 @@ impl> TryFrom for DimensionPercentage } } +impl> TryInto for DimensionPercentage { + type Error = (); + + fn try_into(self) -> Result { + match self { + DimensionPercentage::Dimension(d) => d.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + impl Zero for DimensionPercentage { fn zero() -> Self { DimensionPercentage::Dimension(D::zero()) diff --git a/src/values/time.rs b/src/values/time.rs index 20408e8a..11e67b31 100644 --- a/src/values/time.rs +++ b/src/values/time.rs @@ -130,11 +130,13 @@ impl std::convert::Into> for Time { } } -impl std::convert::From> for Time { - fn from(calc: Calc