mirror of
https://github.com/servo/servo.git
synced 2025-10-04 02:29:12 +01:00
style: Use ColorMix for interpolated colors in the computed style rather than ComplexColorRatios
This among other things preserves the right color-space when interpolating currentColor. Differential Revision: https://phabricator.services.mozilla.com/D147512
This commit is contained in:
parent
0ac6aaa357
commit
b6e8088e8e
10 changed files with 460 additions and 537 deletions
|
@ -7,7 +7,7 @@
|
|||
use super::AllowQuirks;
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
|
||||
use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto};
|
||||
use crate::values::generics::color::{ColorInterpolationMethod, GenericColorMix, GenericCaretColor, GenericColorOrAuto};
|
||||
use crate::values::specified::calc::CalcNode;
|
||||
use crate::values::specified::Percentage;
|
||||
use crate::values::CustomIdent;
|
||||
|
@ -19,119 +19,8 @@ use std::io::Write as IoWrite;
|
|||
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
|
||||
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
|
||||
|
||||
/// A color space as defined in [1].
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-color-space
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
|
||||
pub enum ColorSpace {
|
||||
/// The sRGB color space.
|
||||
Srgb,
|
||||
/// The linear-sRGB color space.
|
||||
LinearSrgb,
|
||||
/// The CIEXYZ color space.
|
||||
#[parse(aliases = "xyz-d65")]
|
||||
Xyz,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-color-xyz
|
||||
XyzD50,
|
||||
/// The CIELAB color space.
|
||||
Lab,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-hsl-hsl
|
||||
Hsl,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-hwb-hwb
|
||||
Hwb,
|
||||
/// The CIELAB color space, expressed in cylindrical coordinates.
|
||||
Lch,
|
||||
// TODO: Oklab, Lch
|
||||
}
|
||||
|
||||
impl ColorSpace {
|
||||
/// Returns whether this is a `<polar-color-space>`.
|
||||
pub fn is_polar(self) -> bool {
|
||||
match self {
|
||||
Self::Srgb | Self::LinearSrgb | Self::Xyz | Self::XyzD50 | Self::Lab => false,
|
||||
Self::Hsl | Self::Hwb | Self::Lch => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A hue-interpolation-method as defined in [1].
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
|
||||
pub enum HueInterpolationMethod {
|
||||
/// https://drafts.csswg.org/css-color-4/#shorter
|
||||
Shorter,
|
||||
/// https://drafts.csswg.org/css-color-4/#longer
|
||||
Longer,
|
||||
/// https://drafts.csswg.org/css-color-4/#increasing
|
||||
Increasing,
|
||||
/// https://drafts.csswg.org/css-color-4/#decreasing
|
||||
Decreasing,
|
||||
/// https://drafts.csswg.org/css-color-4/#specified
|
||||
Specified,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-color-4/#color-interpolation-method
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub struct ColorInterpolationMethod {
|
||||
/// The color-space the interpolation should be done in.
|
||||
pub space: ColorSpace,
|
||||
/// The hue interpolation method.
|
||||
pub hue: HueInterpolationMethod,
|
||||
}
|
||||
|
||||
impl Parse for ColorInterpolationMethod {
|
||||
fn parse<'i, 't>(
|
||||
_: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_ident_matching("in")?;
|
||||
let space = ColorSpace::parse(input)?;
|
||||
// https://drafts.csswg.org/css-color-4/#hue-interpolation
|
||||
// Unless otherwise specified, if no specific hue interpolation
|
||||
// algorithm is selected by the host syntax, the default is shorter.
|
||||
let hue = if space.is_polar() {
|
||||
input.try_parse(|input| -> Result<_, ParseError<'i>> {
|
||||
let hue = HueInterpolationMethod::parse(input)?;
|
||||
input.expect_ident_matching("hue")?;
|
||||
Ok(hue)
|
||||
}).unwrap_or(HueInterpolationMethod::Shorter)
|
||||
} else {
|
||||
HueInterpolationMethod::Shorter
|
||||
};
|
||||
Ok(Self { space, hue })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for ColorInterpolationMethod {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
dest.write_str("in ")?;
|
||||
self.space.to_css(dest)?;
|
||||
if self.hue != HueInterpolationMethod::Shorter {
|
||||
dest.write_char(' ')?;
|
||||
self.hue.to_css(dest)?;
|
||||
dest.write_str(" hue")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A restricted version of the css `color-mix()` function, which only supports
|
||||
/// percentages.
|
||||
///
|
||||
/// https://drafts.csswg.org/css-color-5/#color-mix
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct ColorMix {
|
||||
pub interpolation: ColorInterpolationMethod,
|
||||
pub left: Color,
|
||||
pub left_percentage: Percentage,
|
||||
pub right: Color,
|
||||
pub right_percentage: Percentage,
|
||||
}
|
||||
/// A specified color-mix().
|
||||
pub type ColorMix = GenericColorMix<Color, Percentage>;
|
||||
|
||||
#[inline]
|
||||
fn allow_color_mix() -> bool {
|
||||
|
@ -160,7 +49,9 @@ impl Parse for ColorMix {
|
|||
input.expect_comma()?;
|
||||
|
||||
let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
|
||||
input.try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input)).ok()
|
||||
input
|
||||
.try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
|
||||
.ok()
|
||||
};
|
||||
|
||||
let mut left_percentage = try_parse_percentage(input);
|
||||
|
@ -180,9 +71,8 @@ impl Parse for ColorMix {
|
|||
right_percentage = try_parse_percentage(input);
|
||||
}
|
||||
|
||||
let right_percentage = right_percentage.unwrap_or_else(|| {
|
||||
Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))
|
||||
});
|
||||
let right_percentage = right_percentage
|
||||
.unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
|
||||
|
||||
let left_percentage =
|
||||
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
|
||||
|
@ -198,47 +88,12 @@ impl Parse for ColorMix {
|
|||
left_percentage,
|
||||
right,
|
||||
right_percentage,
|
||||
normalize_weights: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for ColorMix {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
|
||||
if percent.is_calc() {
|
||||
return false;
|
||||
}
|
||||
if percent.get() == 0.5 {
|
||||
return other.get() == 0.5;
|
||||
}
|
||||
if is_left {
|
||||
return false;
|
||||
}
|
||||
(1.0 - percent.get() - other.get()).abs() <= f32::EPSILON
|
||||
}
|
||||
|
||||
dest.write_str("color-mix(")?;
|
||||
self.interpolation.to_css(dest)?;
|
||||
dest.write_str(", ")?;
|
||||
self.left.to_css(dest)?;
|
||||
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
|
||||
dest.write_str(" ")?;
|
||||
self.left_percentage.to_css(dest)?;
|
||||
}
|
||||
dest.write_str(", ")?;
|
||||
self.right.to_css(dest)?;
|
||||
if !can_omit(&self.right_percentage, &self.left_percentage, false) {
|
||||
dest.write_str(" ")?;
|
||||
self.right_percentage.to_css(dest)?;
|
||||
}
|
||||
dest.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
/// Specified color value
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
pub enum Color {
|
||||
|
@ -251,8 +106,6 @@ pub enum Color {
|
|||
/// Authored representation
|
||||
authored: Option<Box<str>>,
|
||||
},
|
||||
/// A complex color value from computed value
|
||||
Complex(ComputedColor),
|
||||
/// A system color.
|
||||
#[cfg(feature = "gecko")]
|
||||
System(SystemColor),
|
||||
|
@ -640,8 +493,6 @@ impl ToCss for Color {
|
|||
Color::Numeric {
|
||||
parsed: ref rgba, ..
|
||||
} => rgba.to_css(dest),
|
||||
// TODO: Could represent this as a color-mix() instead.
|
||||
Color::Complex(_) => Ok(()),
|
||||
Color::ColorMix(ref mix) => mix.to_css(dest),
|
||||
#[cfg(feature = "gecko")]
|
||||
Color::System(system) => system.to_css(dest),
|
||||
|
@ -787,22 +638,23 @@ impl Color {
|
|||
/// the context to resolve, then `None` is returned.
|
||||
pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
|
||||
Some(match *self {
|
||||
Color::CurrentColor => ComputedColor::currentcolor(),
|
||||
Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed),
|
||||
Color::Complex(ref complex) => *complex,
|
||||
Color::CurrentColor => ComputedColor::CurrentColor,
|
||||
Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed),
|
||||
Color::ColorMix(ref mix) => {
|
||||
use crate::values::animated::color::Color as AnimatedColor;
|
||||
use crate::values::animated::ToAnimatedValue;
|
||||
use crate::values::computed::percentage::Percentage;
|
||||
|
||||
let left = mix.left.to_computed_color(context)?.to_animated_value();
|
||||
let right = mix.right.to_computed_color(context)?.to_animated_value();
|
||||
ToAnimatedValue::from_animated_value(AnimatedColor::mix(
|
||||
&mix.interpolation,
|
||||
&left,
|
||||
mix.left_percentage.get(),
|
||||
&right,
|
||||
mix.right_percentage.get(),
|
||||
))
|
||||
let left = mix.left.to_computed_color(context)?;
|
||||
let right = mix.right.to_computed_color(context)?;
|
||||
let mut color = ComputedColor::ColorMix(Box::new(GenericColorMix {
|
||||
interpolation: mix.interpolation,
|
||||
left,
|
||||
left_percentage: Percentage(mix.left_percentage.get()),
|
||||
right,
|
||||
right_percentage: Percentage(mix.right_percentage.get()),
|
||||
normalize_weights: mix.normalize_weights,
|
||||
}));
|
||||
color.simplify(None);
|
||||
color
|
||||
},
|
||||
#[cfg(feature = "gecko")]
|
||||
Color::System(system) => system.compute(context?),
|
||||
|
@ -820,13 +672,13 @@ impl ToComputedValue for Color {
|
|||
}
|
||||
|
||||
fn from_computed_value(computed: &ComputedColor) -> Self {
|
||||
if computed.is_numeric() {
|
||||
return Color::rgba(computed.color);
|
||||
match *computed {
|
||||
ComputedColor::Numeric(ref color) => Color::rgba(*color),
|
||||
ComputedColor::CurrentColor => Color::CurrentColor,
|
||||
ComputedColor::ColorMix(ref mix) => {
|
||||
Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
|
||||
},
|
||||
}
|
||||
if computed.is_currentcolor() {
|
||||
return Color::currentcolor();
|
||||
}
|
||||
Color::Complex(*computed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -854,7 +706,7 @@ impl ToComputedValue for MozFontSmoothingBackgroundColor {
|
|||
fn to_computed_value(&self, context: &Context) -> RGBA {
|
||||
self.0
|
||||
.to_computed_value(context)
|
||||
.to_rgba(RGBA::transparent())
|
||||
.into_rgba(RGBA::transparent())
|
||||
}
|
||||
|
||||
fn from_computed_value(computed: &RGBA) -> Self {
|
||||
|
@ -871,7 +723,15 @@ impl SpecifiedValueInfo for Color {
|
|||
// should probably be handled that way as well.
|
||||
// XXX `currentColor` should really be `currentcolor`. But let's
|
||||
// keep it consistent with the old system for now.
|
||||
f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]);
|
||||
f(&[
|
||||
"rgb",
|
||||
"rgba",
|
||||
"hsl",
|
||||
"hsla",
|
||||
"hwb",
|
||||
"currentColor",
|
||||
"transparent",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -888,7 +748,7 @@ impl ToComputedValue for ColorPropertyValue {
|
|||
fn to_computed_value(&self, context: &Context) -> RGBA {
|
||||
self.0
|
||||
.to_computed_value(context)
|
||||
.to_rgba(context.builder.get_parent_inherited_text().clone_color())
|
||||
.into_rgba(context.builder.get_parent_inherited_text().clone_color())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -1050,7 +910,19 @@ impl ToCss for ColorScheme {
|
|||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
MallocSizeOf,
|
||||
Parse,
|
||||
PartialEq,
|
||||
SpecifiedValueInfo,
|
||||
ToCss,
|
||||
ToComputedValue,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum PrintColorAdjust {
|
||||
/// Ignore backgrounds and darken text.
|
||||
|
|
|
@ -92,11 +92,6 @@ impl Percentage {
|
|||
Number::new_with_clamping_mode(self.value, self.calc_clamping_mode)
|
||||
}
|
||||
|
||||
/// Returns whether this percentage is a `calc()` value.
|
||||
pub fn is_calc(&self) -> bool {
|
||||
self.calc_clamping_mode.is_some()
|
||||
}
|
||||
|
||||
/// Returns the calc() clamping mode for this percentage.
|
||||
pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
|
||||
self.calc_clamping_mode
|
||||
|
@ -188,6 +183,24 @@ impl ToComputedValue for Percentage {
|
|||
|
||||
impl SpecifiedValueInfo for Percentage {}
|
||||
|
||||
/// Turns the percentage into a plain float.
|
||||
pub trait ToPercentage {
|
||||
/// Returns whether this percentage used to be a calc().
|
||||
fn is_calc(&self) -> bool { false }
|
||||
/// Turns the percentage into a plain float.
|
||||
fn to_percentage(&self) -> CSSFloat;
|
||||
}
|
||||
|
||||
impl ToPercentage for Percentage {
|
||||
fn is_calc(&self) -> bool {
|
||||
self.calc_clamping_mode.is_some()
|
||||
}
|
||||
|
||||
fn to_percentage(&self) -> CSSFloat {
|
||||
self.get()
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper of Percentage, whose value must be >= 0.
|
||||
pub type NonNegativePercentage = NonNegative<Percentage>;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue