mirror of
https://github.com/servo/servo.git
synced 2025-08-07 06:25:32 +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
|
@ -418,7 +418,7 @@ fn tweak_when_ignoring_colors(
|
||||||
|
|
||||||
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
|
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
|
||||||
// We assume here currentColor is opaque.
|
// We assume here currentColor is opaque.
|
||||||
let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255));
|
let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255));
|
||||||
color.alpha
|
color.alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ ${helpers.single_keyword(
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"stop-color",
|
"stop-color",
|
||||||
"Color",
|
"Color",
|
||||||
"RGBA::new(0, 0, 0, 255).into()",
|
"computed::Color::black()",
|
||||||
engines="gecko",
|
engines="gecko",
|
||||||
animation_value_type="AnimatedRGBA",
|
animation_value_type="AnimatedRGBA",
|
||||||
spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
|
spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
|
||||||
|
@ -40,7 +40,7 @@ ${helpers.predefined_type(
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"flood-color",
|
"flood-color",
|
||||||
"Color",
|
"Color",
|
||||||
"RGBA::new(0, 0, 0, 255).into()",
|
"computed::Color::black()",
|
||||||
engines="gecko",
|
engines="gecko",
|
||||||
animation_value_type="AnimatedColor",
|
animation_value_type="AnimatedColor",
|
||||||
spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
|
spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
|
||||||
|
@ -58,7 +58,7 @@ ${helpers.predefined_type(
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"lighting-color",
|
"lighting-color",
|
||||||
"Color",
|
"Color",
|
||||||
"RGBA::new(255, 255, 255, 255).into()",
|
"computed::Color::white()",
|
||||||
engines="gecko",
|
engines="gecko",
|
||||||
animation_value_type="AnimatedColor",
|
animation_value_type="AnimatedColor",
|
||||||
spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",
|
spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",
|
||||||
|
|
|
@ -3219,7 +3219,7 @@ impl ComputedValues {
|
||||||
/// style.resolve_color(style.get_border().clone_border_top_color());
|
/// style.resolve_color(style.get_border().clone_border_top_color());
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resolve_color(&self, color: computed::Color) -> RGBA {
|
pub fn resolve_color(&self, color: computed::Color) -> RGBA {
|
||||||
color.to_rgba(self.get_inherited_text().clone_color())
|
color.into_rgba(self.get_inherited_text().clone_color())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns which longhand properties have different values in the two
|
/// Returns which longhand properties have different values in the two
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
//! Animated types for CSS colors.
|
//! Animated types for CSS colors.
|
||||||
|
|
||||||
use crate::values::animated::{Animate, Procedure, ToAnimatedZero};
|
use crate::values::animated::{Animate, Procedure, ToAnimatedZero};
|
||||||
|
use crate::values::computed::Percentage;
|
||||||
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||||
use crate::values::generics::color::{Color as GenericColor, ComplexColorRatios};
|
use crate::values::generics::color::{
|
||||||
use crate::values::specified::color::{ColorInterpolationMethod, ColorSpace, HueInterpolationMethod};
|
GenericColor, GenericColorMix, ColorInterpolationMethod, ColorSpace, HueInterpolationMethod,
|
||||||
|
};
|
||||||
use euclid::default::{Transform3D, Vector3D};
|
use euclid::default::{Transform3D, Vector3D};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
@ -15,7 +17,7 @@ use std::f32::consts::PI;
|
||||||
///
|
///
|
||||||
/// Unlike in computed values, each component value may exceed the
|
/// Unlike in computed values, each component value may exceed the
|
||||||
/// range `[0.0, 1.0]`.
|
/// range `[0.0, 1.0]`.
|
||||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)]
|
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero, ToAnimatedValue)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct RGBA {
|
pub struct RGBA {
|
||||||
/// The red component.
|
/// The red component.
|
||||||
|
@ -48,44 +50,20 @@ impl RGBA {
|
||||||
alpha,
|
alpha,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether or not the colour is in gamut for sRGB.
|
|
||||||
pub fn in_gamut(&self) -> bool {
|
|
||||||
0. <= self.red &&
|
|
||||||
self.red <= 1. &&
|
|
||||||
0. <= self.green &&
|
|
||||||
self.green <= 1. &&
|
|
||||||
0. <= self.blue &&
|
|
||||||
self.blue <= 1.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the colour with coordinates clamped to the sRGB range.
|
|
||||||
pub fn clamp(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
red: self.red.max(0.).min(1.),
|
|
||||||
green: self.green.max(0.).min(1.),
|
|
||||||
blue: self.blue.max(0.).min(1.),
|
|
||||||
alpha: self.alpha,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Animate for RGBA {
|
impl Animate for RGBA {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||||
let mut alpha = self.alpha.animate(&other.alpha, procedure)?;
|
let (left_weight, right_weight) = procedure.weights();
|
||||||
if alpha <= 0. {
|
Ok(Color::mix(
|
||||||
// Ideally we should return color value that only alpha component is
|
&ColorInterpolationMethod::srgb(),
|
||||||
// 0, but this is what current gecko does.
|
self,
|
||||||
return Ok(RGBA::transparent());
|
left_weight as f32,
|
||||||
}
|
other,
|
||||||
|
right_weight as f32,
|
||||||
alpha = alpha.min(1.);
|
/* normalize_weights = */ false,
|
||||||
let red = (self.red * self.alpha).animate(&(other.red * other.alpha), procedure)?;
|
))
|
||||||
let green = (self.green * self.alpha).animate(&(other.green * other.alpha), procedure)?;
|
|
||||||
let blue = (self.blue * self.alpha).animate(&(other.blue * other.alpha), procedure)?;
|
|
||||||
let inv = 1. / alpha;
|
|
||||||
Ok(RGBA::new(red * inv, green * inv, blue * inv, alpha))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,41 +91,38 @@ impl ComputeSquaredDistance for RGBA {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An animated value for `<color>`.
|
/// An animated value for `<color>`.
|
||||||
pub type Color = GenericColor<RGBA>;
|
pub type Color = GenericColor<RGBA, Percentage>;
|
||||||
|
|
||||||
|
/// An animated value for `<color-mix>`.
|
||||||
|
pub type ColorMix = GenericColorMix<Color, Percentage>;
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
fn effective_intermediate_rgba(&self) -> RGBA {
|
fn to_rgba(&self, current_color: RGBA) -> RGBA {
|
||||||
if self.ratios.bg == 0. {
|
let mut clone = self.clone();
|
||||||
return RGBA::transparent();
|
clone.simplify(Some(¤t_color));
|
||||||
}
|
*clone.as_numeric().unwrap()
|
||||||
|
|
||||||
if self.ratios.bg == 1. {
|
|
||||||
return self.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
RGBA {
|
|
||||||
alpha: self.color.alpha * self.ratios.bg,
|
|
||||||
..self.color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mix two colors into one.
|
/// Mix two colors into one.
|
||||||
pub fn mix(
|
pub fn mix(
|
||||||
interpolation: &ColorInterpolationMethod,
|
interpolation: &ColorInterpolationMethod,
|
||||||
left_color: &Color,
|
left_color: &RGBA,
|
||||||
mut left_weight: f32,
|
mut left_weight: f32,
|
||||||
right_color: &Color,
|
right_color: &RGBA,
|
||||||
mut right_weight: f32,
|
mut right_weight: f32,
|
||||||
) -> Self {
|
normalize_weights: bool,
|
||||||
|
) -> RGBA {
|
||||||
// https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
|
// https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
|
||||||
let sum = left_weight + right_weight;
|
|
||||||
let mut alpha_multiplier = 1.0;
|
let mut alpha_multiplier = 1.0;
|
||||||
if sum != 1.0 {
|
if normalize_weights {
|
||||||
let scale = 1.0 / sum;
|
let sum = left_weight + right_weight;
|
||||||
left_weight *= scale;
|
if sum != 1.0 {
|
||||||
right_weight *= scale;
|
let scale = 1.0 / sum;
|
||||||
if sum < 1.0 {
|
left_weight *= scale;
|
||||||
alpha_multiplier = sum;
|
right_weight *= scale;
|
||||||
|
if sum < 1.0 {
|
||||||
|
alpha_multiplier = sum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,202 +136,77 @@ impl Color {
|
||||||
ColorSpace::Hsl => Self::mix_in::<HSLA>,
|
ColorSpace::Hsl => Self::mix_in::<HSLA>,
|
||||||
ColorSpace::Lch => Self::mix_in::<LCHA>,
|
ColorSpace::Lch => Self::mix_in::<LCHA>,
|
||||||
};
|
};
|
||||||
mix_function(left_color, left_weight, right_color, right_weight, interpolation.hue, alpha_multiplier)
|
mix_function(
|
||||||
|
left_color,
|
||||||
|
left_weight,
|
||||||
|
right_color,
|
||||||
|
right_weight,
|
||||||
|
interpolation.hue,
|
||||||
|
alpha_multiplier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mix_in<S>(
|
fn mix_in<S>(
|
||||||
left_color: &Color,
|
left_color: &RGBA,
|
||||||
left_weight: f32,
|
left_weight: f32,
|
||||||
right_color: &Color,
|
right_color: &RGBA,
|
||||||
right_weight: f32,
|
right_weight: f32,
|
||||||
hue_interpolation: HueInterpolationMethod,
|
hue_interpolation: HueInterpolationMethod,
|
||||||
alpha_multiplier: f32,
|
alpha_multiplier: f32,
|
||||||
) -> Self
|
) -> RGBA
|
||||||
where
|
where
|
||||||
S: ModelledColor,
|
S: ModelledColor,
|
||||||
{
|
{
|
||||||
let left_bg = S::from(left_color.scaled_rgba());
|
let left = S::from(*left_color);
|
||||||
let right_bg = S::from(right_color.scaled_rgba());
|
let right = S::from(*right_color);
|
||||||
|
|
||||||
let color = S::lerp(&left_bg, left_weight, &right_bg, right_weight, hue_interpolation);
|
|
||||||
let rgba: RGBA = color.into();
|
|
||||||
let mut rgba = if !rgba.in_gamut() {
|
|
||||||
// TODO: Better gamut mapping.
|
|
||||||
rgba.clamp()
|
|
||||||
} else {
|
|
||||||
rgba
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let color = S::lerp(
|
||||||
|
&left,
|
||||||
|
left_weight,
|
||||||
|
&right,
|
||||||
|
right_weight,
|
||||||
|
hue_interpolation,
|
||||||
|
);
|
||||||
|
let mut rgba = RGBA::from(color.into());
|
||||||
if alpha_multiplier != 1.0 {
|
if alpha_multiplier != 1.0 {
|
||||||
rgba.alpha *= alpha_multiplier;
|
rgba.alpha *= alpha_multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fg = left_color.ratios.fg * left_weight + right_color.ratios.fg * right_weight;
|
rgba
|
||||||
Self::new(rgba, ComplexColorRatios { bg: 1., fg })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scaled_rgba(&self) -> RGBA {
|
|
||||||
if self.ratios.bg == 0. {
|
|
||||||
return RGBA::transparent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.ratios.bg == 1. {
|
|
||||||
return self.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
RGBA {
|
|
||||||
red: self.color.red * self.ratios.bg,
|
|
||||||
green: self.color.green * self.ratios.bg,
|
|
||||||
blue: self.color.blue * self.ratios.bg,
|
|
||||||
alpha: self.color.alpha * self.ratios.bg,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Animate for Color {
|
impl Animate for Color {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||||
let self_numeric = self.is_numeric();
|
let (left_weight, right_weight) = procedure.weights();
|
||||||
let other_numeric = other.is_numeric();
|
let mut color = Color::ColorMix(Box::new(ColorMix {
|
||||||
|
interpolation: ColorInterpolationMethod::srgb(),
|
||||||
if self_numeric && other_numeric {
|
left: self.clone(),
|
||||||
return Ok(Self::rgba(self.color.animate(&other.color, procedure)?));
|
left_percentage: Percentage(left_weight as f32),
|
||||||
}
|
right: other.clone(),
|
||||||
|
right_percentage: Percentage(right_weight as f32),
|
||||||
let self_currentcolor = self.is_currentcolor();
|
// See https://github.com/w3c/csswg-drafts/issues/7324
|
||||||
let other_currentcolor = other.is_currentcolor();
|
normalize_weights: false,
|
||||||
|
}));
|
||||||
if self_currentcolor && other_currentcolor {
|
color.simplify(None);
|
||||||
let (self_weight, other_weight) = procedure.weights();
|
Ok(color)
|
||||||
return Ok(Self::new(
|
|
||||||
RGBA::transparent(),
|
|
||||||
ComplexColorRatios {
|
|
||||||
bg: 0.,
|
|
||||||
fg: (self_weight + other_weight) as f32,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME(emilio): Without these special cases tests fail, looks fairly
|
|
||||||
// sketchy!
|
|
||||||
if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) {
|
|
||||||
let (self_weight, other_weight) = procedure.weights();
|
|
||||||
return Ok(if self_numeric {
|
|
||||||
Self::new(
|
|
||||||
self.color,
|
|
||||||
ComplexColorRatios {
|
|
||||||
bg: self_weight as f32,
|
|
||||||
fg: other_weight as f32,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Self::new(
|
|
||||||
other.color,
|
|
||||||
ComplexColorRatios {
|
|
||||||
bg: other_weight as f32,
|
|
||||||
fg: self_weight as f32,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the "scaled" contribution for `color`.
|
|
||||||
// Each `Color`, represents a complex combination of foreground color and
|
|
||||||
// background color where fg and bg represent the overall
|
|
||||||
// contributions. ie:
|
|
||||||
//
|
|
||||||
// color = { bg * mColor, fg * foreground }
|
|
||||||
// = { bg_color , fg_color }
|
|
||||||
// = bg_color + fg_color
|
|
||||||
//
|
|
||||||
// where `foreground` is `currentcolor`, and `bg_color`,
|
|
||||||
// `fg_color` are the scaled background and foreground
|
|
||||||
// contributions.
|
|
||||||
//
|
|
||||||
// Each operation, lerp, addition, or accumulate, can be
|
|
||||||
// represented as a scaled-addition each complex color. ie:
|
|
||||||
//
|
|
||||||
// p * col1 + q * col2
|
|
||||||
//
|
|
||||||
// where p = (1 - a), q = a for lerp(a), p = 1, q = 1 for
|
|
||||||
// addition, etc.
|
|
||||||
//
|
|
||||||
// Therefore:
|
|
||||||
//
|
|
||||||
// col1 op col2
|
|
||||||
// = p * col1 + q * col2
|
|
||||||
// = p * { bg_color1, fg_color1 } + q * { bg_color2, fg_color2 }
|
|
||||||
// = p * (bg_color1 + fg_color1) + q * (bg_color2 + fg_color2)
|
|
||||||
// = p * bg_color1 + p * fg_color1 + q * bg_color2 + p * fg_color2
|
|
||||||
// = (p * bg_color1 + q * bg_color2) + (p * fg_color1 + q * fg_color2)
|
|
||||||
// = (bg_color1 op bg_color2) + (fg_color1 op fg_color2)
|
|
||||||
//
|
|
||||||
// fg_color1 op fg_color2 is equivalent to (fg1 op fg2) * foreground,
|
|
||||||
// so the final color is:
|
|
||||||
//
|
|
||||||
// = { bg_color, fg_color }
|
|
||||||
// = { 1 * (bg_color1 op bg_color2), (fg1 op fg2) * foreground }
|
|
||||||
//
|
|
||||||
// To perform the operation on two complex colors, we need to
|
|
||||||
// generate the scaled contributions of each background color
|
|
||||||
// component.
|
|
||||||
let bg_color1 = self.scaled_rgba();
|
|
||||||
let bg_color2 = other.scaled_rgba();
|
|
||||||
|
|
||||||
// Perform bg_color1 op bg_color2
|
|
||||||
let bg_color = bg_color1.animate(&bg_color2, procedure)?;
|
|
||||||
|
|
||||||
// Calculate the final foreground color ratios; perform
|
|
||||||
// animation on effective fg ratios.
|
|
||||||
let fg = self.ratios.fg.animate(&other.ratios.fg, procedure)?;
|
|
||||||
|
|
||||||
Ok(Self::new(bg_color, ComplexColorRatios { bg: 1., fg }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputeSquaredDistance for Color {
|
impl ComputeSquaredDistance for Color {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||||
// All comments from the Animate impl also apply here.
|
let current_color = RGBA::transparent();
|
||||||
let self_numeric = self.is_numeric();
|
self.to_rgba(current_color)
|
||||||
let other_numeric = other.is_numeric();
|
.compute_squared_distance(&other.to_rgba(current_color))
|
||||||
|
|
||||||
if self_numeric && other_numeric {
|
|
||||||
return self.color.compute_squared_distance(&other.color);
|
|
||||||
}
|
|
||||||
|
|
||||||
let self_currentcolor = self.is_currentcolor();
|
|
||||||
let other_currentcolor = other.is_currentcolor();
|
|
||||||
if self_currentcolor && other_currentcolor {
|
|
||||||
return Ok(SquaredDistance::from_sqrt(0.));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) {
|
|
||||||
let color = if self_numeric {
|
|
||||||
&self.color
|
|
||||||
} else {
|
|
||||||
&other.color
|
|
||||||
};
|
|
||||||
// `computed_squared_distance` is symmetric.
|
|
||||||
return Ok(color.compute_squared_distance(&RGBA::transparent())? +
|
|
||||||
SquaredDistance::from_sqrt(1.));
|
|
||||||
}
|
|
||||||
|
|
||||||
let self_color = self.effective_intermediate_rgba();
|
|
||||||
let other_color = other.effective_intermediate_rgba();
|
|
||||||
let self_ratios = self.ratios;
|
|
||||||
let other_ratios = other.ratios;
|
|
||||||
|
|
||||||
Ok(self_color.compute_squared_distance(&other_color)? +
|
|
||||||
self_ratios.bg.compute_squared_distance(&other_ratios.bg)? +
|
|
||||||
self_ratios.fg.compute_squared_distance(&other_ratios.fg)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToAnimatedZero for Color {
|
impl ToAnimatedZero for Color {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_animated_zero(&self) -> Result<Self, ()> {
|
fn to_animated_zero(&self) -> Result<Self, ()> {
|
||||||
Ok(RGBA::transparent().into())
|
Ok(Color::rgba(RGBA::transparent()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,9 +338,23 @@ fn interpolate_premultiplied(
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
let is_hue = hue_index == Some(i);
|
let is_hue = hue_index == Some(i);
|
||||||
result[i] = if is_hue {
|
result[i] = if is_hue {
|
||||||
interpolate_hue(left[i], left_weight, right[i], right_weight, hue_interpolation)
|
interpolate_hue(
|
||||||
|
left[i],
|
||||||
|
left_weight,
|
||||||
|
right[i],
|
||||||
|
right_weight,
|
||||||
|
hue_interpolation,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
interpolate_premultiplied_component(left[i], left_weight, left_alpha, right[i], right_weight, right_alpha, inverse_of_result_alpha)
|
interpolate_premultiplied_component(
|
||||||
|
left[i],
|
||||||
|
left_weight,
|
||||||
|
left_alpha,
|
||||||
|
right[i],
|
||||||
|
right_weight,
|
||||||
|
right_alpha,
|
||||||
|
inverse_of_result_alpha,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
result[3] = result_alpha;
|
result[3] = result_alpha;
|
||||||
|
@ -524,7 +388,7 @@ macro_rules! impl_lerp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_lerp!(RGBA, None);
|
impl_lerp!(RGBA, None);
|
||||||
|
@ -614,7 +478,12 @@ impl_lerp!(HSLA, Some(0));
|
||||||
//
|
//
|
||||||
// We also return min/max for the hwb conversion.
|
// We also return min/max for the hwb conversion.
|
||||||
fn rgb_to_hsl(rgba: RGBA) -> (HSLA, f32, f32) {
|
fn rgb_to_hsl(rgba: RGBA) -> (HSLA, f32, f32) {
|
||||||
let RGBA { red, green, blue, alpha } = rgba;
|
let RGBA {
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
blue,
|
||||||
|
alpha,
|
||||||
|
} = rgba;
|
||||||
let max = red.max(green).max(blue);
|
let max = red.max(green).max(blue);
|
||||||
let min = red.min(green).min(blue);
|
let min = red.min(green).min(blue);
|
||||||
let mut hue = std::f32::NAN;
|
let mut hue = std::f32::NAN;
|
||||||
|
@ -640,7 +509,16 @@ fn rgb_to_hsl(rgba: RGBA) -> (HSLA, f32, f32) {
|
||||||
hue *= 60.;
|
hue *= 60.;
|
||||||
}
|
}
|
||||||
|
|
||||||
(HSLA { hue, sat, light, alpha }, min, max)
|
(
|
||||||
|
HSLA {
|
||||||
|
hue,
|
||||||
|
sat,
|
||||||
|
light,
|
||||||
|
alpha,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RGBA> for HSLA {
|
impl From<RGBA> for HSLA {
|
||||||
|
@ -750,7 +628,7 @@ impl From<XYZD50A> for XYZD65A {
|
||||||
0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.,
|
0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.,
|
||||||
0., 0., 0., 1.,
|
0., 0., 0., 1.,
|
||||||
);
|
);
|
||||||
let d65 = BRADFORD_INVERSE.transform_vector3d(Vector3D::new(d50.x, d50.y, d50.z));
|
let d65 = BRADFORD_INVERSE.transform_vector3d(Vector3D::new(d50.x, d50.y, d50.z));
|
||||||
Self {
|
Self {
|
||||||
x: d65.x,
|
x: d65.x,
|
||||||
y: d65.y,
|
y: d65.y,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
use crate::values::animated::color::RGBA as AnimatedRGBA;
|
use crate::values::animated::color::RGBA as AnimatedRGBA;
|
||||||
use crate::values::animated::ToAnimatedValue;
|
use crate::values::animated::ToAnimatedValue;
|
||||||
use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto};
|
use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto};
|
||||||
|
use crate::values::computed::percentage::Percentage;
|
||||||
use cssparser::{Color as CSSParserColor, RGBA};
|
use cssparser::{Color as CSSParserColor, RGBA};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use style_traits::{CssWriter, ToCss};
|
use style_traits::{CssWriter, ToCss};
|
||||||
|
@ -20,7 +21,20 @@ pub type ColorPropertyValue = RGBA;
|
||||||
pub type MozFontSmoothingBackgroundColor = RGBA;
|
pub type MozFontSmoothingBackgroundColor = RGBA;
|
||||||
|
|
||||||
/// A computed value for `<color>`.
|
/// A computed value for `<color>`.
|
||||||
pub type Color = GenericColor<RGBA>;
|
pub type Color = GenericColor<RGBA, Percentage>;
|
||||||
|
|
||||||
|
impl ToCss for Color {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Self::Numeric(ref c) => c.to_css(dest),
|
||||||
|
Self::CurrentColor => CSSParserColor::CurrentColor.to_css(dest),
|
||||||
|
Self::ColorMix(ref m) => m.to_css(dest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
/// Returns a complex color value representing transparent.
|
/// Returns a complex color value representing transparent.
|
||||||
|
@ -28,67 +42,21 @@ impl Color {
|
||||||
Color::rgba(RGBA::transparent())
|
Color::rgba(RGBA::transparent())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combine this complex color with the given foreground color into
|
/// Returns opaque black.
|
||||||
/// a numeric RGBA color. It currently uses linear blending.
|
pub fn black() -> Color {
|
||||||
pub fn to_rgba(&self, fg_color: RGBA) -> RGBA {
|
Color::rgba(RGBA::new(0, 0, 0, 255))
|
||||||
// Common cases that the complex color is either pure numeric color or
|
|
||||||
// pure currentcolor.
|
|
||||||
if self.is_numeric() {
|
|
||||||
return self.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.is_currentcolor() {
|
|
||||||
return fg_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ratios = &self.ratios;
|
|
||||||
let color = &self.color;
|
|
||||||
|
|
||||||
// For the more complicated case that the alpha value differs,
|
|
||||||
// we use the following formula to compute the components:
|
|
||||||
// alpha = self_alpha * bg_ratio + fg_alpha * fg_ratio
|
|
||||||
// color = (self_color * self_alpha * bg_ratio +
|
|
||||||
// fg_color * fg_alpha * fg_ratio) / alpha
|
|
||||||
|
|
||||||
let p1 = ratios.bg;
|
|
||||||
let a1 = color.alpha_f32();
|
|
||||||
let r1 = a1 * color.red_f32();
|
|
||||||
let g1 = a1 * color.green_f32();
|
|
||||||
let b1 = a1 * color.blue_f32();
|
|
||||||
|
|
||||||
let p2 = ratios.fg;
|
|
||||||
let a2 = fg_color.alpha_f32();
|
|
||||||
let r2 = a2 * fg_color.red_f32();
|
|
||||||
let g2 = a2 * fg_color.green_f32();
|
|
||||||
let b2 = a2 * fg_color.blue_f32();
|
|
||||||
|
|
||||||
let a = p1 * a1 + p2 * a2;
|
|
||||||
if a <= 0. {
|
|
||||||
return RGBA::transparent();
|
|
||||||
}
|
|
||||||
let a = a.min(1.);
|
|
||||||
|
|
||||||
let inv = 1. / a;
|
|
||||||
|
|
||||||
let r = (p1 * r1 + p2 * r2) * inv;
|
|
||||||
let g = (p1 * g1 + p2 * g2) * inv;
|
|
||||||
let b = (p1 * b1 + p2 * b2) * inv;
|
|
||||||
RGBA::from_floats(r, g, b, a)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ToCss for Color {
|
/// Returns opaque white.
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
pub fn white() -> Color {
|
||||||
where
|
Color::rgba(RGBA::new(255, 255, 255, 255))
|
||||||
W: fmt::Write,
|
}
|
||||||
{
|
|
||||||
if self.is_currentcolor() {
|
/// Combine this complex color with the given foreground color into
|
||||||
return CSSParserColor::CurrentColor.to_css(dest);
|
/// a numeric RGBA color.
|
||||||
}
|
pub fn into_rgba(mut self, current_color: RGBA) -> RGBA {
|
||||||
if self.is_numeric() {
|
self.simplify(Some(¤t_color));
|
||||||
return self.color.to_css(dest);
|
*self.as_numeric().unwrap()
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
use crate::values::animated::ToAnimatedValue;
|
use crate::values::animated::ToAnimatedValue;
|
||||||
use crate::values::generics::NonNegative;
|
use crate::values::generics::NonNegative;
|
||||||
|
use crate::values::specified::percentage::ToPercentage;
|
||||||
use crate::values::{serialize_percentage, CSSFloat};
|
use crate::values::{serialize_percentage, CSSFloat};
|
||||||
use crate::Zero;
|
use crate::Zero;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -64,6 +65,12 @@ impl Zero for Percentage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToPercentage for Percentage {
|
||||||
|
fn to_percentage(&self) -> CSSFloat {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::AddAssign for Percentage {
|
impl std::ops::AddAssign for Percentage {
|
||||||
fn add_assign(&mut self, other: Self) {
|
fn add_assign(&mut self, other: Self) {
|
||||||
self.0 += other.0
|
self.0 += other.0
|
||||||
|
|
|
@ -8,7 +8,6 @@ use crate::values::computed::color::Color;
|
||||||
use crate::values::computed::url::ComputedUrl;
|
use crate::values::computed::url::ComputedUrl;
|
||||||
use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity};
|
use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity};
|
||||||
use crate::values::generics::svg as generic;
|
use crate::values::generics::svg as generic;
|
||||||
use crate::values::RGBA;
|
|
||||||
use crate::Zero;
|
use crate::Zero;
|
||||||
|
|
||||||
pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder};
|
pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder};
|
||||||
|
@ -22,9 +21,8 @@ pub type SVGPaintKind = generic::GenericSVGPaintKind<Color, ComputedUrl>;
|
||||||
impl SVGPaint {
|
impl SVGPaint {
|
||||||
/// Opaque black color
|
/// Opaque black color
|
||||||
pub fn black() -> Self {
|
pub fn black() -> Self {
|
||||||
let rgba = RGBA::from_floats(0., 0., 0., 1.).into();
|
|
||||||
SVGPaint {
|
SVGPaint {
|
||||||
kind: generic::SVGPaintKind::Color(rgba),
|
kind: generic::SVGPaintKind::Color(Color::black()),
|
||||||
fallback: generic::SVGPaintFallback::Unset,
|
fallback: generic::SVGPaintFallback::Unset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,81 +4,268 @@
|
||||||
|
|
||||||
//! Generic types for color properties.
|
//! Generic types for color properties.
|
||||||
|
|
||||||
/// Ratios representing the contribution of color and currentcolor to
|
use std::fmt::{self, Write};
|
||||||
/// the final color value.
|
use style_traits::{CssWriter, ParseError, ToCss};
|
||||||
///
|
use crate::values::{Parse, ParserContext, Parser};
|
||||||
/// NOTE(emilio): For animated colors, the sum of these two might be more than
|
use crate::values::specified::percentage::ToPercentage;
|
||||||
/// one (because the background color would've been scaled down already). So
|
use crate::values::animated::ToAnimatedValue;
|
||||||
/// beware that it is not generally safe to assume that if bg is 1 then fg is 0,
|
use crate::values::animated::color::RGBA as AnimatedRGBA;
|
||||||
/// for example.
|
|
||||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ComplexColorRatios {
|
|
||||||
/// Numeric color contribution.
|
|
||||||
pub bg: f32,
|
|
||||||
/// currentcolor contribution.
|
|
||||||
pub fg: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComplexColorRatios {
|
|
||||||
/// Ratios representing a `Numeric` color.
|
|
||||||
pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. };
|
|
||||||
/// Ratios representing the `CurrentColor` color.
|
|
||||||
pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This struct represents a combined color from a numeric color and
|
/// This struct represents a combined color from a numeric color and
|
||||||
/// the current foreground color (currentcolor keyword).
|
/// the current foreground color (currentcolor keyword).
|
||||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
|
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct GenericColor<RGBA> {
|
pub enum GenericColor<RGBA, Percentage> {
|
||||||
/// The actual numeric color.
|
/// The actual numeric color.
|
||||||
pub color: RGBA,
|
Numeric(RGBA),
|
||||||
/// The ratios of mixing between numeric and currentcolor.
|
/// The `CurrentColor` keyword.
|
||||||
/// The formula is: `color * ratios.bg + currentcolor * ratios.fg`.
|
CurrentColor,
|
||||||
pub ratios: ComplexColorRatios,
|
/// The color-mix() function.
|
||||||
|
ColorMix(Box<GenericColorMix<Self, Percentage>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
|
||||||
|
#[repr(u8)]
|
||||||
|
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, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
|
||||||
|
#[repr(u8)]
|
||||||
|
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, ToAnimatedValue, ToComputedValue, ToResolvedValue)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ColorInterpolationMethod {
|
||||||
|
/// The color-space the interpolation should be done in.
|
||||||
|
pub space: ColorSpace,
|
||||||
|
/// The hue interpolation method.
|
||||||
|
pub hue: HueInterpolationMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorInterpolationMethod {
|
||||||
|
/// Returns the srgb interpolation method.
|
||||||
|
pub fn srgb() -> Self {
|
||||||
|
Self {
|
||||||
|
space: ColorSpace::Srgb,
|
||||||
|
hue: HueInterpolationMethod::Shorter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ToAnimatedValue, ToComputedValue, ToResolvedValue, ToShmem)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct GenericColorMix<Color, Percentage> {
|
||||||
|
pub interpolation: ColorInterpolationMethod,
|
||||||
|
pub left: Color,
|
||||||
|
pub left_percentage: Percentage,
|
||||||
|
pub right: Color,
|
||||||
|
pub right_percentage: Percentage,
|
||||||
|
pub normalize_weights: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use self::GenericColorMix as ColorMix;
|
||||||
|
|
||||||
|
impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
fn can_omit<Percentage: ToPercentage>(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
|
||||||
|
if percent.is_calc() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if percent.to_percentage() == 0.5 {
|
||||||
|
return other.to_percentage() == 0.5;
|
||||||
|
}
|
||||||
|
if is_left {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
(1.0 - percent.to_percentage() - other.to_percentage()).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(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RGBA, Percentage> ColorMix<GenericColor<RGBA, Percentage>, Percentage> {
|
||||||
|
fn to_rgba(&self) -> Option<RGBA>
|
||||||
|
where
|
||||||
|
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
|
||||||
|
Percentage: ToPercentage,
|
||||||
|
{
|
||||||
|
use crate::values::animated::color::Color as AnimatedColor;
|
||||||
|
let left = self.left.as_numeric()?.clone().to_animated_value();
|
||||||
|
let right = self.right.as_numeric()?.clone().to_animated_value();
|
||||||
|
Some(ToAnimatedValue::from_animated_value(AnimatedColor::mix(
|
||||||
|
&self.interpolation,
|
||||||
|
&left,
|
||||||
|
self.left_percentage.to_percentage(),
|
||||||
|
&right,
|
||||||
|
self.right_percentage.to_percentage(),
|
||||||
|
self.normalize_weights,
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::GenericColor as Color;
|
pub use self::GenericColor as Color;
|
||||||
|
|
||||||
impl Color<cssparser::RGBA> {
|
impl<RGBA, Percentage> Color<RGBA, Percentage> {
|
||||||
/// Returns a color value representing currentcolor.
|
/// Returns the numeric rgba value if this color is numeric, or None
|
||||||
pub fn currentcolor() -> Self {
|
/// otherwise.
|
||||||
Color {
|
pub fn as_numeric(&self) -> Option<&RGBA> {
|
||||||
color: cssparser::RGBA::transparent(),
|
match *self {
|
||||||
ratios: ComplexColorRatios::CURRENT_COLOR,
|
Self::Numeric(ref rgba) => Some(rgba),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<RGBA> Color<RGBA> {
|
/// Simplifies the color-mix()es to the extent possible given a current
|
||||||
/// Create a color based upon the specified ratios.
|
/// color (or not).
|
||||||
pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self {
|
pub fn simplify(&mut self, current_color: Option<&RGBA>)
|
||||||
Self { color, ratios }
|
where
|
||||||
|
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
|
||||||
|
Percentage: ToPercentage,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Self::Numeric(..) => {},
|
||||||
|
Self::CurrentColor => {
|
||||||
|
if let Some(c) = current_color {
|
||||||
|
*self = Self::Numeric(c.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::ColorMix(ref mut mix) => {
|
||||||
|
mix.left.simplify(current_color);
|
||||||
|
mix.right.simplify(current_color);
|
||||||
|
|
||||||
|
if let Some(mix) = mix.to_rgba() {
|
||||||
|
*self = Self::Numeric(mix);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a color value representing currentcolor.
|
||||||
|
pub fn currentcolor() -> Self {
|
||||||
|
Self::CurrentColor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a numeric color representing the given RGBA value.
|
/// Returns a numeric color representing the given RGBA value.
|
||||||
pub fn rgba(color: RGBA) -> Self {
|
pub fn rgba(color: RGBA) -> Self {
|
||||||
Self {
|
Self::Numeric(color)
|
||||||
color,
|
|
||||||
ratios: ComplexColorRatios::NUMERIC,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether it is a numeric color (no currentcolor component).
|
|
||||||
pub fn is_numeric(&self) -> bool {
|
|
||||||
self.ratios == ComplexColorRatios::NUMERIC
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether it is a currentcolor value (no numeric color component).
|
/// Whether it is a currentcolor value (no numeric color component).
|
||||||
pub fn is_currentcolor(&self) -> bool {
|
pub fn is_currentcolor(&self) -> bool {
|
||||||
self.ratios == ComplexColorRatios::CURRENT_COLOR
|
matches!(*self, Self::CurrentColor)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<RGBA> From<RGBA> for Color<RGBA> {
|
/// Whether it is a numeric color (no currentcolor component).
|
||||||
fn from(color: RGBA) -> Self {
|
pub fn is_numeric(&self) -> bool {
|
||||||
Self::rgba(color)
|
matches!(*self, Self::Numeric(..))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
use super::AllowQuirks;
|
use super::AllowQuirks;
|
||||||
use crate::parser::{Parse, ParserContext};
|
use crate::parser::{Parse, ParserContext};
|
||||||
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
|
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::calc::CalcNode;
|
||||||
use crate::values::specified::Percentage;
|
use crate::values::specified::Percentage;
|
||||||
use crate::values::CustomIdent;
|
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::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
|
||||||
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
|
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
|
||||||
|
|
||||||
/// A color space as defined in [1].
|
/// A specified color-mix().
|
||||||
///
|
pub type ColorMix = GenericColorMix<Color, Percentage>;
|
||||||
/// [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,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn allow_color_mix() -> bool {
|
fn allow_color_mix() -> bool {
|
||||||
|
@ -160,7 +49,9 @@ impl Parse for ColorMix {
|
||||||
input.expect_comma()?;
|
input.expect_comma()?;
|
||||||
|
|
||||||
let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
|
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);
|
let mut left_percentage = try_parse_percentage(input);
|
||||||
|
@ -180,9 +71,8 @@ impl Parse for ColorMix {
|
||||||
right_percentage = try_parse_percentage(input);
|
right_percentage = try_parse_percentage(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
let right_percentage = right_percentage.unwrap_or_else(|| {
|
let right_percentage = right_percentage
|
||||||
Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))
|
.unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
|
||||||
});
|
|
||||||
|
|
||||||
let left_percentage =
|
let left_percentage =
|
||||||
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
|
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
|
||||||
|
@ -198,47 +88,12 @@ impl Parse for ColorMix {
|
||||||
left_percentage,
|
left_percentage,
|
||||||
right,
|
right,
|
||||||
right_percentage,
|
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
|
/// Specified color value
|
||||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
@ -251,8 +106,6 @@ pub enum Color {
|
||||||
/// Authored representation
|
/// Authored representation
|
||||||
authored: Option<Box<str>>,
|
authored: Option<Box<str>>,
|
||||||
},
|
},
|
||||||
/// A complex color value from computed value
|
|
||||||
Complex(ComputedColor),
|
|
||||||
/// A system color.
|
/// A system color.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
System(SystemColor),
|
System(SystemColor),
|
||||||
|
@ -640,8 +493,6 @@ impl ToCss for Color {
|
||||||
Color::Numeric {
|
Color::Numeric {
|
||||||
parsed: ref rgba, ..
|
parsed: ref rgba, ..
|
||||||
} => rgba.to_css(dest),
|
} => rgba.to_css(dest),
|
||||||
// TODO: Could represent this as a color-mix() instead.
|
|
||||||
Color::Complex(_) => Ok(()),
|
|
||||||
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),
|
||||||
|
@ -787,22 +638,23 @@ impl Color {
|
||||||
/// the context to resolve, then `None` is returned.
|
/// the context to resolve, then `None` is returned.
|
||||||
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::rgba(*parsed),
|
Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed),
|
||||||
Color::Complex(ref complex) => *complex,
|
|
||||||
Color::ColorMix(ref mix) => {
|
Color::ColorMix(ref mix) => {
|
||||||
use crate::values::animated::color::Color as AnimatedColor;
|
use crate::values::computed::percentage::Percentage;
|
||||||
use crate::values::animated::ToAnimatedValue;
|
|
||||||
|
|
||||||
let left = mix.left.to_computed_color(context)?.to_animated_value();
|
let left = mix.left.to_computed_color(context)?;
|
||||||
let right = mix.right.to_computed_color(context)?.to_animated_value();
|
let right = mix.right.to_computed_color(context)?;
|
||||||
ToAnimatedValue::from_animated_value(AnimatedColor::mix(
|
let mut color = ComputedColor::ColorMix(Box::new(GenericColorMix {
|
||||||
&mix.interpolation,
|
interpolation: mix.interpolation,
|
||||||
&left,
|
left,
|
||||||
mix.left_percentage.get(),
|
left_percentage: Percentage(mix.left_percentage.get()),
|
||||||
&right,
|
right,
|
||||||
mix.right_percentage.get(),
|
right_percentage: Percentage(mix.right_percentage.get()),
|
||||||
))
|
normalize_weights: mix.normalize_weights,
|
||||||
|
}));
|
||||||
|
color.simplify(None);
|
||||||
|
color
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
Color::System(system) => system.compute(context?),
|
Color::System(system) => system.compute(context?),
|
||||||
|
@ -820,13 +672,13 @@ impl ToComputedValue for Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_computed_value(computed: &ComputedColor) -> Self {
|
fn from_computed_value(computed: &ComputedColor) -> Self {
|
||||||
if computed.is_numeric() {
|
match *computed {
|
||||||
return Color::rgba(computed.color);
|
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 {
|
fn to_computed_value(&self, context: &Context) -> RGBA {
|
||||||
self.0
|
self.0
|
||||||
.to_computed_value(context)
|
.to_computed_value(context)
|
||||||
.to_rgba(RGBA::transparent())
|
.into_rgba(RGBA::transparent())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_computed_value(computed: &RGBA) -> Self {
|
fn from_computed_value(computed: &RGBA) -> Self {
|
||||||
|
@ -871,7 +723,15 @@ impl SpecifiedValueInfo for Color {
|
||||||
// should probably be handled that way as well.
|
// should probably be handled that way as well.
|
||||||
// XXX `currentColor` should really be `currentcolor`. But let's
|
// XXX `currentColor` should really be `currentcolor`. But let's
|
||||||
// keep it consistent with the old system for now.
|
// 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 {
|
fn to_computed_value(&self, context: &Context) -> RGBA {
|
||||||
self.0
|
self.0
|
||||||
.to_computed_value(context)
|
.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]
|
#[inline]
|
||||||
|
@ -1050,7 +910,19 @@ impl ToCss for ColorScheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
|
/// 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)]
|
#[repr(u8)]
|
||||||
pub enum PrintColorAdjust {
|
pub enum PrintColorAdjust {
|
||||||
/// Ignore backgrounds and darken text.
|
/// Ignore backgrounds and darken text.
|
||||||
|
|
|
@ -92,11 +92,6 @@ impl Percentage {
|
||||||
Number::new_with_clamping_mode(self.value, self.calc_clamping_mode)
|
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.
|
/// Returns the calc() clamping mode for this percentage.
|
||||||
pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
|
pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
|
||||||
self.calc_clamping_mode
|
self.calc_clamping_mode
|
||||||
|
@ -188,6 +183,24 @@ impl ToComputedValue for Percentage {
|
||||||
|
|
||||||
impl SpecifiedValueInfo 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.
|
/// A wrapper of Percentage, whose value must be >= 0.
|
||||||
pub type NonNegativePercentage = NonNegative<Percentage>;
|
pub type NonNegativePercentage = NonNegative<Percentage>;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue