style: Add lab(), lch(), oklab(), oklch() to specified colors

Use new changes from cssparser and use the new lab/lch/oklab/oklch color
formats.

Introduced a new color type AbsoluteColor.  It represents any kind of
color that has absolute numerical values.  It is also tied to a color
space and therefore can be trivially converted to another color space.

Differential Revision: https://phabricator.services.mozilla.com/D163579
This commit is contained in:
Tiaan Louw 2023-01-20 10:55:51 +00:00 committed by Martin Robinson
parent 6ce64abe7e
commit 4559546fbb
12 changed files with 512 additions and 90 deletions

View file

@ -21,7 +21,7 @@ shmem = ["dep:to_shmem", "dep:to_shmem_derive"]
[dependencies] [dependencies]
bitflags = "1.0" bitflags = "1.0"
cssparser = "0.29" cssparser = "0.30"
derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] } derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] }
fxhash = "0.2" fxhash = "0.2"
log = "0.4" log = "0.4"

View file

@ -41,7 +41,7 @@ arrayvec = "0.7"
atomic_refcell = "0.1" atomic_refcell = "0.1"
bitflags = "1.0" bitflags = "1.0"
byteorder = "1.0" byteorder = "1.0"
cssparser = "0.29" cssparser = "0.30"
derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "from"] } derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "from"] }
encoding_rs = { version = "0.8", optional = true } encoding_rs = { version = "0.8", optional = true }
euclid = "0.22" euclid = "0.22"

View file

@ -18,20 +18,18 @@ use std::cmp::max;
/// Convert a given RGBA value to `nscolor`. /// Convert a given RGBA value to `nscolor`.
pub fn convert_rgba_to_nscolor(rgba: &RGBA) -> u32 { pub fn convert_rgba_to_nscolor(rgba: &RGBA) -> u32 {
((rgba.alpha as u32) << 24) | u32::from_le_bytes([
((rgba.blue as u32) << 16) | rgba.red,
((rgba.green as u32) << 8) | rgba.green,
(rgba.red as u32) rgba.blue,
(rgba.alpha * 255.0).round() as u8,
])
} }
/// Convert a given `nscolor` to a Servo RGBA value. /// Convert a given `nscolor` to a Servo RGBA value.
pub fn convert_nscolor_to_rgba(color: u32) -> RGBA { pub fn convert_nscolor_to_rgba(color: u32) -> RGBA {
RGBA::new( let [r, g, b, a] = color.to_le_bytes();
(color & 0xff) as u8, RGBA::new(r, g, b, a as f32 / 255.0)
(color >> 8 & 0xff) as u8,
(color >> 16 & 0xff) as u8,
(color >> 24 & 0xff) as u8,
)
} }
/// Round `width` down to the nearest device pixel, but any non-zero value that /// Round `width` down to the nearest device pixel, but any non-zero value that

View file

@ -433,9 +433,11 @@ fn tweak_when_ignoring_colors(
return; return;
} }
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 { fn alpha_channel(color: &Color, context: &computed::Context) -> f32 {
// We assume here currentColor is opaque. // We assume here currentColor is opaque.
let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255)); let color = color
.to_computed_value(context)
.into_rgba(RGBA::new(0, 0, 0, 1.0));
color.alpha color.alpha
} }
@ -458,7 +460,7 @@ fn tweak_when_ignoring_colors(
// otherwise, this is needed to preserve semi-transparent // otherwise, this is needed to preserve semi-transparent
// backgrounds. // backgrounds.
let alpha = alpha_channel(color, context); let alpha = alpha_channel(color, context);
if alpha == 0 { if alpha == 0.0 {
return; return;
} }
let mut color = context.builder.device.default_background_color(); let mut color = context.builder.device.default_background_color();
@ -474,7 +476,7 @@ fn tweak_when_ignoring_colors(
// If the inherited color would be transparent, but we would // If the inherited color would be transparent, but we would
// override this with a non-transparent color, then override it with // override this with a non-transparent color, then override it with
// the default color. Otherwise just let it inherit through. // the default color. Otherwise just let it inherit through.
if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { if context.builder.get_parent_inherited_text().clone_color().alpha == 0.0 {
let color = context.builder.device.default_color(); let color = context.builder.device.default_color();
declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color(
specified::ColorPropertyValue(color.into()), specified::ColorPropertyValue(color.into()),

View file

@ -9,7 +9,7 @@
${helpers.predefined_type( ${helpers.predefined_type(
"color", "color",
"ColorPropertyValue", "ColorPropertyValue",
"::cssparser::RGBA::new(0, 0, 0, 255)", "::cssparser::RGBA::new(0, 0, 0, 1.0)",
engines="gecko servo", engines="gecko servo",
animation_value_type="AnimatedRGBA", animation_value_type="AnimatedRGBA",
ignored_when_colors_disabled="True", ignored_when_colors_disabled="True",

View file

@ -78,7 +78,6 @@ ${helpers.predefined_type(
engines="gecko", engines="gecko",
spec="https://drafts.csswg.org/css-ui/#caret-color", spec="https://drafts.csswg.org/css-ui/#caret-color",
animation_value_type="CaretColor", animation_value_type="CaretColor",
boxed=True,
ignored_when_colors_disabled=True, ignored_when_colors_disabled=True,
)} )}
@ -90,7 +89,6 @@ ${helpers.predefined_type(
spec="https://drafts.csswg.org/css-ui-4/#widget-accent", spec="https://drafts.csswg.org/css-ui-4/#widget-accent",
gecko_pref="layout.css.accent-color.enabled", gecko_pref="layout.css.accent-color.enabled",
animation_value_type="ColorOrAuto", animation_value_type="ColorOrAuto",
boxed=True,
ignored_when_colors_disabled=True, ignored_when_colors_disabled=True,
has_effect_on_gecko_scrollbars=False, has_effect_on_gecko_scrollbars=False,
)} )}

View file

@ -47,7 +47,7 @@ impl Parse for FontPaletteOverrideColor {
let location = input.current_source_location(); let location = input.current_source_location();
let color = SpecifiedColor::parse(context, input)?; let color = SpecifiedColor::parse(context, input)?;
// Only absolute colors are accepted here. // Only absolute colors are accepted here.
if let SpecifiedColor::Numeric { parsed: _, authored: _ } = color { if let SpecifiedColor::Absolute { .. } = color {
Ok(FontPaletteOverrideColor { index, color }) Ok(FontPaletteOverrideColor { index, color })
} else { } else {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
@ -183,11 +183,10 @@ impl FontPaletteValuesRule {
} }
} }
for c in &self.override_colors { for c in &self.override_colors {
if let SpecifiedColor::Numeric { parsed, authored: _ } = &c.color { if let SpecifiedColor::Absolute(ref absolute) = c.color {
let rgba = absolute.color.to_rgba();
unsafe { unsafe {
Gecko_SetFontPaletteOverride(palette_values, Gecko_SetFontPaletteOverride(palette_values, c.index.0.value(), rgba);
c.index.0.value(),
*parsed);
} }
} }
} }

View file

@ -168,6 +168,13 @@ impl Color {
rgba.alpha *= alpha_multiplier; rgba.alpha *= alpha_multiplier;
} }
// FIXME: In rare cases we end up with 0.999995 in the alpha channel,
// so we reduce the precision to avoid serializing to
// rgba(?, ?, ?, 1). This is not ideal, so we should look into
// ways to avoid it. Maybe pre-multiply all color components and
// then divide after calculations?
rgba.alpha = (rgba.alpha * 1000.0).round() / 1000.0;
rgba rgba
} }
} }
@ -424,29 +431,56 @@ struct XYZD50A {
impl_lerp!(XYZD50A, None); impl_lerp!(XYZD50A, None);
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
#[repr(C)] #[repr(C)]
struct LABA { pub struct LABA {
lightness: f32, pub lightness: f32,
a: f32, pub a: f32,
b: f32, pub b: f32,
alpha: f32, pub alpha: f32,
} }
impl_lerp!(LABA, None); impl_lerp!(LABA, None);
/// An animated LCHA colour. /// An animated LCHA colour.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
#[repr(C)] #[repr(C)]
struct LCHA { pub struct LCHA {
lightness: f32, pub lightness: f32,
chroma: f32, pub chroma: f32,
hue: f32, pub hue: f32,
alpha: f32, pub alpha: f32,
} }
impl_lerp!(LCHA, Some(2)); impl_lerp!(LCHA, Some(2));
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct OKLABA {
pub lightness: f32,
pub a: f32,
pub b: f32,
pub alpha: f32,
}
impl_lerp!(OKLABA, None);
/// An animated OKLCHA colour.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct OKLCHA {
pub lightness: f32,
pub chroma: f32,
pub hue: f32,
pub alpha: f32,
}
impl_lerp!(OKLCHA, Some(2));
/// An animated hwb() color. /// An animated hwb() color.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
#[repr(C)] #[repr(C)]
@ -604,6 +638,7 @@ impl From<XYZD65A> for XYZD50A {
-0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0., -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.,
0., 0., 0., 1., 0., 0., 0., 1.,
); );
let d50 = BRADFORD.transform_vector3d(Vector3D::new(d65.x, d65.y, d65.z)); let d50 = BRADFORD.transform_vector3d(Vector3D::new(d65.x, d65.y, d65.z));
Self { Self {
x: d50.x, x: d50.x,
@ -747,6 +782,22 @@ impl From<LABA> for LCHA {
} }
} }
impl From<OKLABA> for OKLCHA {
/// Convert an OKLAB color to OKLCH as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(oklaba: OKLABA) -> Self {
let hue = oklaba.b.atan2(oklaba.a) * DEG_PER_RAD;
let chroma = (oklaba.a * oklaba.a + oklaba.b * oklaba.b).sqrt();
OKLCHA {
lightness: oklaba.lightness,
chroma,
hue,
alpha: oklaba.alpha,
}
}
}
impl From<LCHA> for LABA { impl From<LCHA> for LABA {
/// Convert a LCH color to LAB as specified in [1]. /// Convert a LCH color to LAB as specified in [1].
/// ///
@ -764,6 +815,23 @@ impl From<LCHA> for LABA {
} }
} }
impl From<OKLCHA> for OKLABA {
/// Convert a OKLCH color to OKLAB as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(oklcha: OKLCHA) -> Self {
let hue_radians = oklcha.hue * RAD_PER_DEG;
let a = oklcha.chroma * hue_radians.cos();
let b = oklcha.chroma * hue_radians.sin();
OKLABA {
lightness: oklcha.lightness,
a,
b,
alpha: oklcha.alpha,
}
}
}
impl From<LABA> for XYZD50A { impl From<LABA> for XYZD50A {
/// Convert a CIELAB color to XYZ as specified in [1] and [2]. /// Convert a CIELAB color to XYZ as specified in [1] and [2].
/// ///
@ -804,6 +872,78 @@ impl From<LABA> for XYZD50A {
} }
} }
impl From<XYZD65A> for OKLABA {
fn from(xyza: XYZD65A) -> Self {
// https://drafts.csswg.org/css-color-4/#color-conversion-code
#[cfg_attr(rustfmt, rustfmt_skip)]
const XYZ_TO_LMS: Transform3D<f32> = Transform3D::new(
0.8190224432164319, 0.0329836671980271, 0.048177199566046255, 0.,
0.3619062562801221, 0.9292868468965546, 0.26423952494422764, 0.,
-0.12887378261216414, 0.03614466816999844, 0.6335478258136937, 0.,
0., 0., 0., 1.,
);
#[cfg_attr(rustfmt, rustfmt_skip)]
const LMS_TO_OKLAB: Transform3D<f32> = Transform3D::new(
0.2104542553, 1.9779984951, 0.0259040371, 0.,
0.7936177850, -2.4285922050, 0.7827717662, 0.,
-0.0040720468, 0.4505937099, -0.8086757660, 0.,
0., 0., 0., 1.,
);
let lms = XYZ_TO_LMS.transform_vector3d(Vector3D::new(xyza.x, xyza.y, xyza.z));
let lab = LMS_TO_OKLAB.transform_vector3d(Vector3D::new(
lms.x.cbrt(),
lms.y.cbrt(),
lms.z.cbrt(),
));
Self {
lightness: lab.x,
a: lab.y,
b: lab.z,
alpha: xyza.alpha,
}
}
}
impl From<OKLABA> for XYZD65A {
fn from(oklaba: OKLABA) -> Self {
// https://drafts.csswg.org/css-color-4/#color-conversion-code
// Given OKLab, convert to XYZ relative to D65
#[cfg_attr(rustfmt, rustfmt_skip)]
const LMS_TO_XYZ: Transform3D<f32> = Transform3D::new(
1.2268798733741557, -0.04057576262431372, -0.07637294974672142, 0.,
-0.5578149965554813, 1.1122868293970594, -0.4214933239627914, 0.,
0.28139105017721583, -0.07171106666151701, 1.5869240244272418, 0.,
0., 0., 0., 1.,
);
#[cfg_attr(rustfmt, rustfmt_skip)]
const OKLAB_TO_LMS: Transform3D<f32> = Transform3D::new(
0.99999999845051981432, 1.0000000088817607767, 1.0000000546724109177, 0.,
0.39633779217376785678, -0.1055613423236563494, -0.089484182094965759684, 0.,
0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399, 0.,
0., 0., 0., 1.,
);
let lms =
OKLAB_TO_LMS.transform_vector3d(Vector3D::new(oklaba.lightness, oklaba.a, oklaba.b));
let xyz = LMS_TO_XYZ.transform_vector3d(Vector3D::new(
lms.x.powf(3.0),
lms.y.powf(3.0),
lms.z.powf(3.0),
));
Self {
x: xyz.x,
y: xyz.y,
z: xyz.z,
alpha: oklaba.alpha,
}
}
}
impl From<XYZD50A> for RGBA { impl From<XYZD50A> for RGBA {
fn from(d50: XYZD50A) -> Self { fn from(d50: XYZD50A) -> Self {
Self::from(XYZD65A::from(d50)) Self::from(XYZD65A::from(d50))
@ -828,6 +968,18 @@ impl From<LABA> for RGBA {
} }
} }
impl From<OKLABA> for RGBA {
fn from(oklaba: OKLABA) -> Self {
Self::from(XYZD65A::from(oklaba))
}
}
impl From<RGBA> for OKLABA {
fn from(rgba: RGBA) -> Self {
Self::from(XYZD65A::from(rgba))
}
}
impl From<RGBA> for LCHA { impl From<RGBA> for LCHA {
fn from(rgba: RGBA) -> Self { fn from(rgba: RGBA) -> Self {
Self::from(LABA::from(rgba)) Self::from(LABA::from(rgba))
@ -839,3 +991,15 @@ impl From<LCHA> for RGBA {
Self::from(LABA::from(lcha)) Self::from(LABA::from(lcha))
} }
} }
impl From<OKLCHA> for RGBA {
fn from(oklcha: OKLCHA) -> Self {
Self::from(OKLABA::from(oklcha))
}
}
impl From<RGBA> for OKLCHA {
fn from(rgba: RGBA) -> Self {
Self::from(OKLABA::from(rgba))
}
}

View file

@ -30,7 +30,7 @@ impl ToCss for Color {
{ {
match *self { match *self {
Self::Numeric(ref c) => c.to_css(dest), Self::Numeric(ref c) => c.to_css(dest),
Self::CurrentColor => CSSParserColor::CurrentColor.to_css(dest), Self::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
Self::ColorMix(ref m) => m.to_css(dest), Self::ColorMix(ref m) => m.to_css(dest),
} }
} }
@ -44,12 +44,12 @@ impl Color {
/// Returns opaque black. /// Returns opaque black.
pub fn black() -> Color { pub fn black() -> Color {
Color::rgba(RGBA::new(0, 0, 0, 255)) Color::rgba(RGBA::new(0, 0, 0, 1.0))
} }
/// Returns opaque white. /// Returns opaque white.
pub fn white() -> Color { pub fn white() -> Color {
Color::rgba(RGBA::new(255, 255, 255, 255)) Color::rgba(RGBA::new(255, 255, 255, 1.0))
} }
/// Combine this complex color with the given foreground color into /// Combine this complex color with the given foreground color into

View file

@ -98,18 +98,291 @@ impl ColorMix {
} }
} }
/// A color space representation in the CSS specification.
///
/// https://w3c.github.io/csswg-drafts/css-color-4/#color-type
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[repr(u8)]
pub enum ColorSpace {
/// A color specified in the Lab color format, e.g.
/// "lab(29.2345% 39.3825 20.0664)".
/// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors
Lab,
/// A color specified in the Lch color format, e.g.
/// "lch(29.2345% 44.2 27)".
/// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors
Lch,
/// A color specified in the Oklab color format, e.g.
/// "oklab(40.101% 0.1147 0.0453)".
/// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors
Oklab,
/// A color specified in the Oklch color format, e.g.
/// "oklch(40.101% 0.12332 21.555)".
/// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors
Oklch,
/// A color specified with the color(..) function and the "srgb" color
/// space, e.g. "color(srgb 0.691 0.139 0.259)".
Srgb,
/// A color specified with the color(..) function and the "srgb-linear"
/// color space, e.g. "color(srgb-linear 0.435 0.017 0.055)".
SrgbLinear,
/// A color specified with the color(..) function and the "display-p3"
/// color space, e.g. "color(display-p3 0.84 0.19 0.72)".
DisplayP3,
/// A color specified with the color(..) function and the "a98-rgb" color
/// space, e.g. "color(a98-rgb 0.44091 0.49971 0.37408)".
A98Rgb,
/// A color specified with the color(..) function and the "prophoto-rgb"
/// color space, e.g. "color(prophoto-rgb 0.36589 0.41717 0.31333)".
ProphotoRgb,
/// A color specified with the color(..) function and the "rec2020" color
/// space, e.g. "color(rec2020 0.42210 0.47580 0.35605)".
Rec2020,
/// A color specified with the color(..) function and the "xyz-d50" color
/// space, e.g. "color(xyz-d50 0.2005 0.14089 0.4472)".
XyzD50,
/// A color specified with the color(..) function and the "xyz-d65" or "xyz"
/// color space, e.g. "color(xyz-d65 0.21661 0.14602 0.59452)".
XyzD65,
}
bitflags! {
#[derive(Default, MallocSizeOf, ToShmem)]
#[repr(C)]
struct SerializationFlags : u8 {
const AS_COLOR_FUNCTION = 0x01;
}
}
/// The 3 components that make up a color. (Does not include the alpha component)
#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
struct ColorComponents(f32, f32, f32);
/// An absolutely specified color, using either rgb(), rgba(), lab(), lch(),
/// oklab(), oklch() or color().
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[repr(C)]
pub struct AbsoluteColor {
components: ColorComponents,
alpha: f32,
color_space: ColorSpace,
flags: SerializationFlags,
}
macro_rules! color_components_as {
($c:expr, $t:ty) => {{
// This macro is not an inline function, because we can't use the
// generic type ($t) in a constant expression as per:
// https://github.com/rust-lang/rust/issues/76560
const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
const_assert_eq!(
std::mem::align_of::<AbsoluteColor>(),
std::mem::align_of::<$t>()
);
std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
}};
}
impl AbsoluteColor {
/// Create a new [AbsoluteColor] with the given [ColorSpace] and components.
pub fn new(color_space: ColorSpace, c1: f32, c2: f32, c3: f32, alpha: f32) -> Self {
Self {
components: ColorComponents(c1, c2, c3),
alpha,
color_space,
flags: SerializationFlags::empty(),
}
}
/// Convenience function to create a color in the sRGB color space.
pub fn from_rgba(rgba: RGBA) -> Self {
let red = rgba.red as f32 / 255.0;
let green = rgba.green as f32 / 255.0;
let blue = rgba.blue as f32 / 255.0;
Self::new(ColorSpace::Srgb, red, green, blue, rgba.alpha)
}
/// Return the alpha component.
#[inline]
pub fn alpha(&self) -> f32 {
self.alpha
}
/// Convert the color to sRGB color space and return it in the RGBA struct.
pub fn to_rgba(&self) -> RGBA {
let rgba = self.to_color_space(ColorSpace::Srgb);
let red = (rgba.components.0 * 255.0).round() as u8;
let green = (rgba.components.1 * 255.0).round() as u8;
let blue = (rgba.components.2 * 255.0).round() as u8;
RGBA::new(red, green, blue, rgba.alpha)
}
/// Convert this color to the specified color space.
pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
use crate::values::animated::color::{AnimatedRGBA, LABA, LCHA, OKLABA, OKLCHA};
use ColorSpace::*;
if self.color_space == color_space {
return self.clone();
}
match (self.color_space, color_space) {
(Lab, Srgb) => {
let laba = unsafe { color_components_as!(self, LABA) };
let rgba = AnimatedRGBA::from(*laba);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
(Oklab, Srgb) => {
let oklaba: &OKLABA = unsafe { color_components_as!(self, OKLABA) };
let rgba = AnimatedRGBA::from(*oklaba);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
(Lch, Srgb) => {
let lcha: &LCHA = unsafe { color_components_as!(self, LCHA) };
let rgba = AnimatedRGBA::from(*lcha);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
(Oklch, Srgb) => {
let oklcha: &OKLCHA = unsafe { color_components_as!(self, OKLCHA) };
let rgba = AnimatedRGBA::from(*oklcha);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
_ => {
// Conversion to other color spaces is not implemented yet.
log::warn!(
"Can not convert from {:?} to {:?}!",
self.color_space,
color_space
);
Self::from_rgba(RGBA::new(0, 0, 0, 1.0))
},
}
}
}
impl From<cssparser::AbsoluteColor> for AbsoluteColor {
fn from(f: cssparser::AbsoluteColor) -> Self {
match f {
cssparser::AbsoluteColor::Rgba(rgba) => Self::from_rgba(rgba),
cssparser::AbsoluteColor::Lab(lab) => {
Self::new(ColorSpace::Lab, lab.lightness, lab.a, lab.b, lab.alpha)
},
cssparser::AbsoluteColor::Lch(lch) => Self::new(
ColorSpace::Lch,
lch.lightness,
lch.chroma,
lch.hue,
lch.alpha,
),
cssparser::AbsoluteColor::Oklab(oklab) => Self::new(
ColorSpace::Oklab,
oklab.lightness,
oklab.a,
oklab.b,
oklab.alpha,
),
cssparser::AbsoluteColor::Oklch(oklch) => Self::new(
ColorSpace::Oklch,
oklch.lightness,
oklch.chroma,
oklch.hue,
oklch.alpha,
),
}
}
}
impl ToCss for AbsoluteColor {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match self.color_space {
ColorSpace::Srgb if !self.flags.contains(SerializationFlags::AS_COLOR_FUNCTION) => {
cssparser::ToCss::to_css(
&cssparser::RGBA::from_floats(
self.components.0,
self.components.1,
self.components.2,
self.alpha(),
),
dest,
)
},
ColorSpace::Lab => cssparser::ToCss::to_css(
unsafe { color_components_as!(self, cssparser::Lab) },
dest,
),
ColorSpace::Lch => cssparser::ToCss::to_css(
unsafe { color_components_as!(self, cssparser::Lch) },
dest,
),
ColorSpace::Oklab => cssparser::ToCss::to_css(
unsafe { color_components_as!(self, cssparser::Oklab) },
dest,
),
ColorSpace::Oklch => cssparser::ToCss::to_css(
unsafe { color_components_as!(self, cssparser::Oklch) },
dest,
),
// Other color spaces are not implemented yet. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1128204
ColorSpace::Srgb |
ColorSpace::SrgbLinear |
ColorSpace::DisplayP3 |
ColorSpace::A98Rgb |
ColorSpace::ProphotoRgb |
ColorSpace::Rec2020 |
ColorSpace::XyzD50 |
ColorSpace::XyzD65 => todo!(),
}
}
}
/// Container holding an absolute color and the text specified by an author.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub struct Absolute {
/// The specified color.
pub color: AbsoluteColor,
/// Authored representation.
pub authored: Option<Box<str>>,
}
impl ToCss for Absolute {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if let Some(ref authored) = self.authored {
dest.write_str(authored)
} else {
self.color.to_css(dest)
}
}
}
/// Specified color value /// Specified color value
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Color { pub enum Color {
/// The 'currentColor' keyword /// The 'currentColor' keyword
CurrentColor, CurrentColor,
/// A specific RGBA color /// An absolute color.
Numeric { /// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function
/// Parsed RGBA color Absolute(Box<Absolute>),
parsed: RGBA,
/// Authored representation
authored: Option<Box<str>>,
},
/// A system color. /// A system color.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
System(SystemColor), System(SystemColor),
@ -477,16 +750,24 @@ impl Color {
let authored = input.expect_ident_cloned().ok(); let authored = input.expect_ident_cloned().ok();
input.reset(&start); input.reset(&start);
authored authored
} },
}; };
let compontent_parser = ColorComponentParser(&*context); let compontent_parser = ColorComponentParser(&*context);
match input.try_parse(|i| CSSParserColor::parse_with(&compontent_parser, i)) { match input.try_parse(|i| CSSParserColor::parse_with(&compontent_parser, i)) {
Ok(value) => Ok(match value { Ok(value) => Ok(match value {
CSSParserColor::CurrentColor => Color::CurrentColor, CSSParserColor::CurrentColor => Color::CurrentColor,
CSSParserColor::RGBA(rgba) => Color::Numeric { CSSParserColor::Absolute(absolute) => {
parsed: rgba, let enabled = matches!(absolute, cssparser::AbsoluteColor::Rgba(_)) ||
static_prefs::pref!("layout.css.more_color_4.enabled");
if !enabled {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Color::Absolute(Box::new(Absolute {
color: absolute.into(),
authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()), authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()),
}))
}, },
}), }),
Err(e) => { Err(e) => {
@ -497,7 +778,8 @@ impl Color {
} }
} }
if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored)) { if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
{
return Ok(Color::ColorMix(Box::new(mix))); return Ok(Color::ColorMix(Box::new(mix)));
} }
@ -515,7 +797,9 @@ impl Color {
/// Returns whether a given color is valid for authors. /// Returns whether a given color is valid for authors.
pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool { pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
input.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)).is_ok() input
.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
.is_ok()
} }
/// Tries to parse a color and compute it with a given device. /// Tries to parse a color and compute it with a given device.
@ -526,9 +810,8 @@ impl Color {
) -> Option<ComputedColor> { ) -> Option<ComputedColor> {
use crate::error_reporting::ContextualParseError; use crate::error_reporting::ContextualParseError;
let start = input.position(); let start = input.position();
let result = input.parse_entirely(|input| { let result = input
Self::parse_internal(context, input, PreserveAuthored::No) .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
});
let specified = match result { let specified = match result {
Ok(s) => s, Ok(s) => s,
@ -545,14 +828,11 @@ impl Color {
// default and not available on OffscreenCanvas anyways... // default and not available on OffscreenCanvas anyways...
if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind { if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
let location = e.location.clone(); let location = e.location.clone();
let error = ContextualParseError::UnsupportedValue( let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
input.slice_from(start),
e,
);
context.log_css_error(location, error); context.log_css_error(location, error);
} }
return None; return None;
} },
}; };
match device { match device {
@ -572,14 +852,8 @@ impl ToCss for Color {
W: Write, W: Write,
{ {
match *self { match *self {
Color::CurrentColor => CSSParserColor::CurrentColor.to_css(dest), Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
Color::Numeric { Color::Absolute(ref absolute) => absolute.to_css(dest),
authored: Some(ref authored),
..
} => dest.write_str(authored),
Color::Numeric {
parsed: ref rgba, ..
} => rgba.to_css(dest),
Color::ColorMix(ref mix) => mix.to_css(dest), Color::ColorMix(ref mix) => mix.to_css(dest),
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
Color::System(system) => system.to_css(dest), Color::System(system) => system.to_css(dest),
@ -589,18 +863,6 @@ impl ToCss for Color {
} }
} }
/// A wrapper of cssparser::Color::parse_hash.
///
/// That function should never return CurrentColor, so it makes no sense to
/// handle a cssparser::Color here. This should really be done in cssparser
/// directly rather than here.
fn parse_hash_color(value: &[u8]) -> Result<RGBA, ()> {
CSSParserColor::parse_hash(value).map(|color| match color {
CSSParserColor::RGBA(rgba) => rgba,
CSSParserColor::CurrentColor => unreachable!("parse_hash should never return currentcolor"),
})
}
impl Color { impl Color {
/// Returns whether this color is allowed in forced-colors mode. /// Returns whether this color is allowed in forced-colors mode.
pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool { pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
@ -610,7 +872,7 @@ impl Color {
Color::CurrentColor => true, Color::CurrentColor => true,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
Color::System(..) => true, Color::System(..) => true,
Color::Numeric { ref parsed, .. } => allow_transparent && parsed.alpha == 0, Color::Absolute(ref absolute) => allow_transparent && absolute.color.alpha() == 0.0,
Color::ColorMix(ref mix) => { Color::ColorMix(ref mix) => {
mix.left.honored_in_forced_colors_mode(allow_transparent) && mix.left.honored_in_forced_colors_mode(allow_transparent) &&
mix.right.honored_in_forced_colors_mode(allow_transparent) mix.right.honored_in_forced_colors_mode(allow_transparent)
@ -631,13 +893,13 @@ impl Color {
Color::rgba(RGBA::transparent()) Color::rgba(RGBA::transparent())
} }
/// Returns a numeric RGBA color value. /// Returns an absolute RGBA color value.
#[inline] #[inline]
pub fn rgba(rgba: RGBA) -> Self { pub fn rgba(rgba: RGBA) -> Self {
Color::Numeric { Color::Absolute(Box::new(Absolute {
parsed: rgba, color: AbsoluteColor::from_rgba(rgba),
authored: None, authored: None,
} }))
} }
/// Parse a color, with quirks. /// Parse a color, with quirks.
@ -677,7 +939,7 @@ impl Color {
if ident.len() != 3 && ident.len() != 6 { if ident.len() != 3 && ident.len() != 6 {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
} }
return parse_hash_color(ident.as_bytes()).map_err(|()| { return RGBA::parse_hash(ident.as_bytes()).map_err(|()| {
location.new_custom_error(StyleParseErrorKind::UnspecifiedError) location.new_custom_error(StyleParseErrorKind::UnspecifiedError)
}); });
}, },
@ -722,7 +984,7 @@ impl Color {
.unwrap(); .unwrap();
} }
debug_assert_eq!(written, 6); debug_assert_eq!(written, 6);
parse_hash_color(&serialization) RGBA::parse_hash(&serialization)
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }
} }
@ -735,7 +997,7 @@ impl Color {
pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> { pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
Some(match *self { Some(match *self {
Color::CurrentColor => ComputedColor::CurrentColor, Color::CurrentColor => ComputedColor::CurrentColor,
Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed), Color::Absolute(ref absolute) => ComputedColor::Numeric(absolute.color.to_rgba()),
Color::ColorMix(ref mix) => { Color::ColorMix(ref mix) => {
use crate::values::computed::percentage::Percentage; use crate::values::computed::percentage::Percentage;

View file

@ -17,7 +17,7 @@ gecko = []
[dependencies] [dependencies]
app_units = "0.7" app_units = "0.7"
bitflags = "1.0" bitflags = "1.0"
cssparser = "0.29" cssparser = "0.30"
euclid = "0.22" euclid = "0.22"
lazy_static = "1" lazy_static = "1"
malloc_size_of = { path = "../malloc_size_of" } malloc_size_of = { path = "../malloc_size_of" }

View file

@ -501,9 +501,8 @@ impl_to_css_for_predefined_type!(i8);
impl_to_css_for_predefined_type!(i32); impl_to_css_for_predefined_type!(i32);
impl_to_css_for_predefined_type!(u16); impl_to_css_for_predefined_type!(u16);
impl_to_css_for_predefined_type!(u32); impl_to_css_for_predefined_type!(u32);
impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
impl_to_css_for_predefined_type!(::cssparser::RGBA); impl_to_css_for_predefined_type!(::cssparser::RGBA);
impl_to_css_for_predefined_type!(::cssparser::Color); impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
impl_to_css_for_predefined_type!(::cssparser::UnicodeRange); impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
/// Define an enum type with unit variants that each correspond to a CSS keyword. /// Define an enum type with unit variants that each correspond to a CSS keyword.