mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
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:
parent
6ce64abe7e
commit
4559546fbb
12 changed files with 512 additions and 90 deletions
|
@ -21,7 +21,7 @@ shmem = ["dep:to_shmem", "dep:to_shmem_derive"]
|
|||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
||||
cssparser = "0.29"
|
||||
cssparser = "0.30"
|
||||
derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] }
|
||||
fxhash = "0.2"
|
||||
log = "0.4"
|
||||
|
|
|
@ -41,7 +41,7 @@ arrayvec = "0.7"
|
|||
atomic_refcell = "0.1"
|
||||
bitflags = "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"] }
|
||||
encoding_rs = { version = "0.8", optional = true }
|
||||
euclid = "0.22"
|
||||
|
|
|
@ -18,20 +18,18 @@ use std::cmp::max;
|
|||
|
||||
/// Convert a given RGBA value to `nscolor`.
|
||||
pub fn convert_rgba_to_nscolor(rgba: &RGBA) -> u32 {
|
||||
((rgba.alpha as u32) << 24) |
|
||||
((rgba.blue as u32) << 16) |
|
||||
((rgba.green as u32) << 8) |
|
||||
(rgba.red as u32)
|
||||
u32::from_le_bytes([
|
||||
rgba.red,
|
||||
rgba.green,
|
||||
rgba.blue,
|
||||
(rgba.alpha * 255.0).round() as u8,
|
||||
])
|
||||
}
|
||||
|
||||
/// Convert a given `nscolor` to a Servo RGBA value.
|
||||
pub fn convert_nscolor_to_rgba(color: u32) -> RGBA {
|
||||
RGBA::new(
|
||||
(color & 0xff) as u8,
|
||||
(color >> 8 & 0xff) as u8,
|
||||
(color >> 16 & 0xff) as u8,
|
||||
(color >> 24 & 0xff) as u8,
|
||||
)
|
||||
let [r, g, b, a] = color.to_le_bytes();
|
||||
RGBA::new(r, g, b, a as f32 / 255.0)
|
||||
}
|
||||
|
||||
/// Round `width` down to the nearest device pixel, but any non-zero value that
|
||||
|
|
|
@ -433,9 +433,11 @@ fn tweak_when_ignoring_colors(
|
|||
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.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -458,7 +460,7 @@ fn tweak_when_ignoring_colors(
|
|||
// otherwise, this is needed to preserve semi-transparent
|
||||
// backgrounds.
|
||||
let alpha = alpha_channel(color, context);
|
||||
if alpha == 0 {
|
||||
if alpha == 0.0 {
|
||||
return;
|
||||
}
|
||||
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
|
||||
// override this with a non-transparent color, then override it with
|
||||
// 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();
|
||||
declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color(
|
||||
specified::ColorPropertyValue(color.into()),
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
${helpers.predefined_type(
|
||||
"color",
|
||||
"ColorPropertyValue",
|
||||
"::cssparser::RGBA::new(0, 0, 0, 255)",
|
||||
"::cssparser::RGBA::new(0, 0, 0, 1.0)",
|
||||
engines="gecko servo",
|
||||
animation_value_type="AnimatedRGBA",
|
||||
ignored_when_colors_disabled="True",
|
||||
|
|
|
@ -78,7 +78,6 @@ ${helpers.predefined_type(
|
|||
engines="gecko",
|
||||
spec="https://drafts.csswg.org/css-ui/#caret-color",
|
||||
animation_value_type="CaretColor",
|
||||
boxed=True,
|
||||
ignored_when_colors_disabled=True,
|
||||
)}
|
||||
|
||||
|
@ -90,7 +89,6 @@ ${helpers.predefined_type(
|
|||
spec="https://drafts.csswg.org/css-ui-4/#widget-accent",
|
||||
gecko_pref="layout.css.accent-color.enabled",
|
||||
animation_value_type="ColorOrAuto",
|
||||
boxed=True,
|
||||
ignored_when_colors_disabled=True,
|
||||
has_effect_on_gecko_scrollbars=False,
|
||||
)}
|
||||
|
|
|
@ -47,8 +47,8 @@ impl Parse for FontPaletteOverrideColor {
|
|||
let location = input.current_source_location();
|
||||
let color = SpecifiedColor::parse(context, input)?;
|
||||
// Only absolute colors are accepted here.
|
||||
if let SpecifiedColor::Numeric { parsed: _, authored: _ } = color {
|
||||
Ok(FontPaletteOverrideColor{ index, color })
|
||||
if let SpecifiedColor::Absolute { .. } = color {
|
||||
Ok(FontPaletteOverrideColor { index, color })
|
||||
} else {
|
||||
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||
}
|
||||
|
@ -183,11 +183,10 @@ impl FontPaletteValuesRule {
|
|||
}
|
||||
}
|
||||
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 {
|
||||
Gecko_SetFontPaletteOverride(palette_values,
|
||||
c.index.0.value(),
|
||||
*parsed);
|
||||
Gecko_SetFontPaletteOverride(palette_values, c.index.0.value(), rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,6 +168,13 @@ impl Color {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
@ -424,29 +431,56 @@ struct XYZD50A {
|
|||
|
||||
impl_lerp!(XYZD50A, None);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
struct LABA {
|
||||
lightness: f32,
|
||||
a: f32,
|
||||
b: f32,
|
||||
alpha: f32,
|
||||
pub struct LABA {
|
||||
pub lightness: f32,
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
impl_lerp!(LABA, None);
|
||||
|
||||
/// An animated LCHA colour.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
struct LCHA {
|
||||
lightness: f32,
|
||||
chroma: f32,
|
||||
hue: f32,
|
||||
alpha: f32,
|
||||
pub struct LCHA {
|
||||
pub lightness: f32,
|
||||
pub chroma: f32,
|
||||
pub hue: f32,
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
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.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
|
@ -604,6 +638,7 @@ impl From<XYZD65A> for XYZD50A {
|
|||
-0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.,
|
||||
0., 0., 0., 1.,
|
||||
);
|
||||
|
||||
let d50 = BRADFORD.transform_vector3d(Vector3D::new(d65.x, d65.y, d65.z));
|
||||
Self {
|
||||
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 {
|
||||
/// 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 {
|
||||
/// 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 {
|
||||
fn from(d50: XYZD50A) -> Self {
|
||||
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 {
|
||||
fn from(rgba: RGBA) -> Self {
|
||||
Self::from(LABA::from(rgba))
|
||||
|
@ -839,3 +991,15 @@ impl From<LCHA> for RGBA {
|
|||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ impl ToCss for Color {
|
|||
{
|
||||
match *self {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +44,12 @@ impl Color {
|
|||
|
||||
/// Returns opaque black.
|
||||
pub fn black() -> Color {
|
||||
Color::rgba(RGBA::new(0, 0, 0, 255))
|
||||
Color::rgba(RGBA::new(0, 0, 0, 1.0))
|
||||
}
|
||||
|
||||
/// Returns opaque white.
|
||||
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
|
||||
|
|
|
@ -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
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum Color {
|
||||
/// The 'currentColor' keyword
|
||||
CurrentColor,
|
||||
/// A specific RGBA color
|
||||
Numeric {
|
||||
/// Parsed RGBA color
|
||||
parsed: RGBA,
|
||||
/// Authored representation
|
||||
authored: Option<Box<str>>,
|
||||
},
|
||||
/// An absolute color.
|
||||
/// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function
|
||||
Absolute(Box<Absolute>),
|
||||
/// A system color.
|
||||
#[cfg(feature = "gecko")]
|
||||
System(SystemColor),
|
||||
|
@ -477,16 +750,24 @@ impl Color {
|
|||
let authored = input.expect_ident_cloned().ok();
|
||||
input.reset(&start);
|
||||
authored
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let compontent_parser = ColorComponentParser(&*context);
|
||||
match input.try_parse(|i| CSSParserColor::parse_with(&compontent_parser, i)) {
|
||||
Ok(value) => Ok(match value {
|
||||
CSSParserColor::CurrentColor => Color::CurrentColor,
|
||||
CSSParserColor::RGBA(rgba) => Color::Numeric {
|
||||
parsed: rgba,
|
||||
authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()),
|
||||
CSSParserColor::Absolute(absolute) => {
|
||||
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()),
|
||||
}))
|
||||
},
|
||||
}),
|
||||
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)));
|
||||
}
|
||||
|
||||
|
@ -515,7 +797,9 @@ impl Color {
|
|||
|
||||
/// Returns whether a given color is valid for authors.
|
||||
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.
|
||||
|
@ -526,9 +810,8 @@ impl Color {
|
|||
) -> Option<ComputedColor> {
|
||||
use crate::error_reporting::ContextualParseError;
|
||||
let start = input.position();
|
||||
let result = input.parse_entirely(|input| {
|
||||
Self::parse_internal(context, input, PreserveAuthored::No)
|
||||
});
|
||||
let result = input
|
||||
.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
|
||||
|
||||
let specified = match result {
|
||||
Ok(s) => s,
|
||||
|
@ -545,14 +828,11 @@ impl Color {
|
|||
// default and not available on OffscreenCanvas anyways...
|
||||
if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
|
||||
let location = e.location.clone();
|
||||
let error = ContextualParseError::UnsupportedValue(
|
||||
input.slice_from(start),
|
||||
e,
|
||||
);
|
||||
let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
|
||||
context.log_css_error(location, error);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
match device {
|
||||
|
@ -572,14 +852,8 @@ impl ToCss for Color {
|
|||
W: Write,
|
||||
{
|
||||
match *self {
|
||||
Color::CurrentColor => CSSParserColor::CurrentColor.to_css(dest),
|
||||
Color::Numeric {
|
||||
authored: Some(ref authored),
|
||||
..
|
||||
} => dest.write_str(authored),
|
||||
Color::Numeric {
|
||||
parsed: ref rgba, ..
|
||||
} => rgba.to_css(dest),
|
||||
Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
|
||||
Color::Absolute(ref absolute) => absolute.to_css(dest),
|
||||
Color::ColorMix(ref mix) => mix.to_css(dest),
|
||||
#[cfg(feature = "gecko")]
|
||||
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 {
|
||||
/// Returns whether this color is allowed in forced-colors mode.
|
||||
pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
|
||||
|
@ -610,7 +872,7 @@ impl Color {
|
|||
Color::CurrentColor => true,
|
||||
#[cfg(feature = "gecko")]
|
||||
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) => {
|
||||
mix.left.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())
|
||||
}
|
||||
|
||||
/// Returns a numeric RGBA color value.
|
||||
/// Returns an absolute RGBA color value.
|
||||
#[inline]
|
||||
pub fn rgba(rgba: RGBA) -> Self {
|
||||
Color::Numeric {
|
||||
parsed: rgba,
|
||||
Color::Absolute(Box::new(Absolute {
|
||||
color: AbsoluteColor::from_rgba(rgba),
|
||||
authored: None,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Parse a color, with quirks.
|
||||
|
@ -677,7 +939,7 @@ impl Color {
|
|||
if ident.len() != 3 && ident.len() != 6 {
|
||||
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)
|
||||
});
|
||||
},
|
||||
|
@ -722,7 +984,7 @@ impl Color {
|
|||
.unwrap();
|
||||
}
|
||||
debug_assert_eq!(written, 6);
|
||||
parse_hash_color(&serialization)
|
||||
RGBA::parse_hash(&serialization)
|
||||
.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> {
|
||||
Some(match *self {
|
||||
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) => {
|
||||
use crate::values::computed::percentage::Percentage;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ gecko = []
|
|||
[dependencies]
|
||||
app_units = "0.7"
|
||||
bitflags = "1.0"
|
||||
cssparser = "0.29"
|
||||
cssparser = "0.30"
|
||||
euclid = "0.22"
|
||||
lazy_static = "1"
|
||||
malloc_size_of = { path = "../malloc_size_of" }
|
||||
|
|
|
@ -501,9 +501,8 @@ impl_to_css_for_predefined_type!(i8);
|
|||
impl_to_css_for_predefined_type!(i32);
|
||||
impl_to_css_for_predefined_type!(u16);
|
||||
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::Color);
|
||||
impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
|
||||
impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
|
||||
|
||||
/// Define an enum type with unit variants that each correspond to a CSS keyword.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue