style: Make CalcNode the specified representation of <length> and <length-percentage> values.

This is the meat of the patch. There are a couple improvements done in a couple
later patches which should hopefully be straight-forward.

Differential Revision: https://phabricator.services.mozilla.com/D63397
This commit is contained in:
Emilio Cobos Álvarez 2020-02-21 00:46:41 +00:00
parent 426edbd991
commit 7e8dbd0896
9 changed files with 320 additions and 385 deletions

View file

@ -1,39 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Animation implementation for various length-related types.
use super::{Animate, Procedure};
use crate::values::computed::length::LengthPercentage;
use crate::values::computed::Percentage;
use style_traits::values::specified::AllowedNumericType;
/// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
impl Animate for LengthPercentage {
#[inline]
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
let animate_percentage_half = |this: Option<Percentage>, other: Option<Percentage>| {
if this.is_none() && other.is_none() {
return Ok(None);
}
let this = this.unwrap_or_default();
let other = other.unwrap_or_default();
Ok(Some(this.animate(&other, procedure)?))
};
let length = self
.unclamped_length()
.animate(&other.unclamped_length(), procedure)?;
let percentage =
animate_percentage_half(self.specified_percentage(), other.specified_percentage())?;
// Gets clamped as needed after the animation if needed, so no need to
// specify any particular AllowedNumericType.
Ok(LengthPercentage::new_calc(
length,
percentage,
AllowedNumericType::All,
))
}
}

View file

@ -23,7 +23,6 @@ pub mod color;
pub mod effects; pub mod effects;
mod font; mod font;
mod grid; mod grid;
mod length;
mod svg; mod svg;
pub mod transform; pub mod transform;
@ -454,6 +453,16 @@ where
} }
} }
impl<T> ToAnimatedZero for Box<[T]>
where
T: ToAnimatedZero,
{
#[inline]
fn to_animated_zero(&self) -> Result<Self, ()> {
self.iter().map(|v| v.to_animated_zero()).collect()
}
}
impl<T> ToAnimatedZero for crate::OwnedSlice<T> impl<T> ToAnimatedZero for crate::OwnedSlice<T>
where where
T: ToAnimatedZero, T: ToAnimatedZero,

View file

@ -17,7 +17,7 @@ use crate::values::{specified, CSSFloat};
use crate::Zero; use crate::Zero;
use app_units::Au; use app_units::Au;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub}; use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub};
use style_traits::{CSSPixel, CssWriter, ToCss}; use style_traits::{CSSPixel, CssWriter, ToCss};
pub use super::image::Image; pub use super::image::Image;
@ -331,6 +331,13 @@ impl Div<CSSFloat> for CSSPixelLength {
} }
} }
impl MulAssign<CSSFloat> for CSSPixelLength {
#[inline]
fn mul_assign(&mut self, other: CSSFloat) {
self.0 *= other;
}
}
impl Mul<CSSFloat> for CSSPixelLength { impl Mul<CSSFloat> for CSSPixelLength {
type Output = Self; type Output = Self;

View file

@ -25,9 +25,9 @@
//! our expectations. //! our expectations.
use super::{Context, Length, Percentage, ToComputedValue}; use super::{Context, Length, Percentage, ToComputedValue};
use crate::values::animated::{ToAnimatedValue, ToAnimatedZero}; use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
use crate::values::generics::NonNegative; use crate::values::generics::{calc, NonNegative};
use crate::values::specified::length::FontBaseSize; use crate::values::specified::length::FontBaseSize;
use crate::values::{specified, CSSFloat}; use crate::values::{specified, CSSFloat};
use crate::Zero; use crate::Zero;
@ -35,6 +35,7 @@ use app_units::Au;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::borrow::Cow;
use style_traits::values::specified::AllowedNumericType; use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ToCss}; use style_traits::{CssWriter, ToCss};
@ -162,7 +163,7 @@ impl MallocSizeOf for LengthPercentage {
} }
/// An unpacked `<length-percentage>` that borrows the `calc()` variant. /// An unpacked `<length-percentage>` that borrows the `calc()` variant.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq, ToCss)]
enum Unpacked<'a> { enum Unpacked<'a> {
Calc(&'a CalcLengthPercentage), Calc(&'a CalcLengthPercentage),
Length(Length), Length(Length),
@ -185,6 +186,14 @@ impl LengthPercentage {
Self::new_length(Length::new(1.)) Self::new_length(Length::new(1.))
} }
fn to_calc_node(&self) -> Cow<CalcNode> {
match self.unpack() {
Unpacked::Length(l) => Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l))),
Unpacked::Percentage(p) => Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p))),
Unpacked::Calc(p) => Cow::Borrowed(&p.node),
}
}
/// Constructs a length value. /// Constructs a length value.
#[inline] #[inline]
pub fn new_length(length: Length) -> Self { pub fn new_length(length: Length) -> Self {
@ -211,25 +220,48 @@ impl LengthPercentage {
percent percent
} }
/// Given a `LengthPercentage` value `v`, construct the value representing
/// `calc(100% - v)`.
pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
// TODO: This could in theory take ownership of the calc node in `v` if
// possible instead of cloning.
let mut node = v.to_calc_node().into_owned();
node.negate();
let new_node = CalcNode::Sum(vec![
CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())),
node,
].into());
Self::new_calc(new_node, clamping_mode)
}
/// Constructs a `calc()` value. /// Constructs a `calc()` value.
#[inline] #[inline]
pub fn new_calc( pub fn new_calc(
length: Length, mut node: CalcNode,
percentage: Option<Percentage>,
clamping_mode: AllowedNumericType, clamping_mode: AllowedNumericType,
) -> Self { ) -> Self {
let percentage = match percentage { node.simplify_and_sort_children();
Some(p) => p,
None => return Self::new_length(Length::new(clamping_mode.clamp(length.px()))), match node {
}; CalcNode::Leaf(l) => {
if length.is_zero() { return match l {
return Self::new_percent(Percentage(clamping_mode.clamp(percentage.0))); CalcLengthPercentageLeaf::Length(l) => {
Self::new_length(Length::new(clamping_mode.clamp(l.px())))
},
CalcLengthPercentageLeaf::Percentage(p) => {
Self::new_percent(Percentage(clamping_mode.clamp(p.0)))
},
}
}
_ => {
Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
clamping_mode,
node,
}))
}
} }
Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
length,
percentage,
clamping_mode,
}))
} }
/// Private version of new_calc() that constructs a calc() variant without /// Private version of new_calc() that constructs a calc() variant without
@ -313,57 +345,7 @@ impl LengthPercentage {
match self.unpack() { match self.unpack() {
Unpacked::Length(l) => l.px() == 0.0, Unpacked::Length(l) => l.px() == 0.0,
Unpacked::Percentage(p) => p.0 == 0.0, Unpacked::Percentage(p) => p.0 == 0.0,
Unpacked::Calc(ref c) => { Unpacked::Calc(..) => false,
debug_assert_ne!(
c.length.px(),
0.0,
"Should've been simplified to a percentage"
);
false
},
}
}
/// Returns the `<length>` component of this `calc()`, unclamped.
#[inline]
pub fn unclamped_length(&self) -> Length {
match self.unpack() {
Unpacked::Length(l) => l,
Unpacked::Percentage(..) => Zero::zero(),
Unpacked::Calc(c) => c.unclamped_length(),
}
}
/// Returns this `calc()` as a `<length>`.
///
/// Panics in debug mode if a percentage is present in the expression.
#[inline]
fn length(&self) -> Length {
debug_assert!(!self.has_percentage());
self.length_component()
}
/// Returns the `<length>` component of this `calc()`, clamped.
#[inline]
pub fn length_component(&self) -> Length {
match self.unpack() {
Unpacked::Length(l) => l,
Unpacked::Percentage(..) => Zero::zero(),
Unpacked::Calc(c) => c.length_component(),
}
}
/// Returns the `<percentage>` component of this `calc()`, unclamped, as a
/// float.
///
/// FIXME: This are very different semantics from length(), we should
/// probably rename this.
#[inline]
pub fn percentage(&self) -> CSSFloat {
match self.unpack() {
Unpacked::Length(..) => 0.,
Unpacked::Percentage(p) => p.0,
Unpacked::Calc(c) => c.percentage.0,
} }
} }
@ -407,25 +389,8 @@ impl LengthPercentage {
#[inline] #[inline]
pub fn to_percentage(&self) -> Option<Percentage> { pub fn to_percentage(&self) -> Option<Percentage> {
match self.unpack() { match self.unpack() {
Unpacked::Length(..) => None,
Unpacked::Percentage(p) => Some(p), Unpacked::Percentage(p) => Some(p),
Unpacked::Calc(ref c) => { Unpacked::Length(..) | Unpacked::Calc(..) => None,
debug_assert!(!c.length.is_zero());
None
},
}
}
/// Return the specified percentage if any.
#[inline]
pub fn specified_percentage(&self) -> Option<Percentage> {
match self.unpack() {
Unpacked::Length(..) => None,
Unpacked::Percentage(p) => Some(p),
Unpacked::Calc(ref c) => {
debug_assert!(self.has_percentage());
Some(c.percentage)
},
} }
} }
@ -452,10 +417,10 @@ impl LengthPercentage {
/// the height property), they apply whenever a calc() expression contains /// the height property), they apply whenever a calc() expression contains
/// percentages. /// percentages.
pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> { pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
if self.has_percentage() { if let Unpacked::Length(l) = self.unpack() {
return Some(self.resolve(container_len?)); return Some(l);
} }
Some(self.length()) Some(self.resolve(container_len?))
} }
/// Returns the clamped non-negative values. /// Returns the clamped non-negative values.
@ -549,7 +514,7 @@ impl ToCss for LengthPercentage {
where where
W: Write, W: Write,
{ {
specified::LengthPercentage::from_computed_value(self).to_css(dest) self.unpack().to_css(dest)
} }
} }
@ -584,46 +549,135 @@ impl<'de> Deserialize<'de> for LengthPercentage {
} }
} }
/// The leaves of a `<length-percentage>` calc expression.
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, ToAnimatedZero, ToCss, ToResolvedValue)]
#[allow(missing_docs)]
pub enum CalcLengthPercentageLeaf {
Length(Length),
Percentage(Percentage),
}
impl CalcLengthPercentageLeaf {
fn is_zero_length(&self) -> bool {
match *self {
Self::Length(ref l) => l.is_zero(),
Self::Percentage(..) => false,
}
}
}
impl PartialOrd for CalcLengthPercentageLeaf {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use self::CalcLengthPercentageLeaf::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return None;
}
match (self, other) {
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
(&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
_ => {
match *self {
Length(..) | Percentage(..) => {},
}
unsafe { debug_unreachable!("Forgot a branch?"); }
}
}
}
}
impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
fn is_negative(&self) -> bool {
match *self {
Self::Length(ref l) => l.px() < 0.,
Self::Percentage(ref p) => p.0 < 0.,
}
}
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
use self::CalcLengthPercentageLeaf::*;
// 0px plus anything else is equal to the right hand side.
if self.is_zero_length() {
*self = other.clone();
return Ok(());
}
if other.is_zero_length() {
return Ok(());
}
match (self, other) {
(&mut Length(ref mut one), &Length(ref other)) => {
*one += *other;
},
(&mut Percentage(ref mut one), &Percentage(ref other)) => {
one.0 += other.0;
},
_ => return Err(()),
}
Ok(())
}
fn mul_by(&mut self, scalar: f32) {
match *self {
Self::Length(ref mut l) => *l = *l * scalar,
Self::Percentage(ref mut p) => p.0 *= scalar,
}
}
fn simplify(&mut self) {}
fn sort_key(&self) -> calc::SortKey {
match *self {
Self::Length(..) => calc::SortKey::Px,
Self::Percentage(..) => calc::SortKey::Percentage,
}
}
}
/// The computed version of a calc() node for `<length-percentage>` values.
pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>;
/// The representation of a calc() function with mixed lengths and percentages. /// The representation of a calc() function with mixed lengths and percentages.
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue)] #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, ToCss)]
#[repr(C)] #[repr(C)]
pub struct CalcLengthPercentage { pub struct CalcLengthPercentage {
length: Length,
percentage: Percentage,
#[animation(constant)] #[animation(constant)]
#[css(skip)]
clamping_mode: AllowedNumericType, clamping_mode: AllowedNumericType,
node: CalcNode,
} }
impl CalcLengthPercentage { impl CalcLengthPercentage {
/// Returns the length component of this `calc()`, clamped.
#[inline]
fn length_component(&self) -> Length {
Length::new(self.clamping_mode.clamp(self.length.px()))
}
/// Resolves the percentage. /// Resolves the percentage.
#[inline] #[inline]
pub fn resolve(&self, basis: Length) -> Length { fn resolve(&self, basis: Length) -> Length {
let length = self.length.px() + basis.px() * self.percentage.0; // TODO: This could be faster (without the extra allocations),
Length::new(self.clamping_mode.clamp(length)) // potentially.
let mut resolved = self.node.map_leafs(|l| {
match l {
CalcLengthPercentageLeaf::Length(..) => l.clone(),
CalcLengthPercentageLeaf::Percentage(ref p) => {
CalcLengthPercentageLeaf::Length(Length::new(basis.px() * p.0))
},
}
});
resolved.simplify_and_sort_children();
match resolved {
CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l)) => l,
other => unreachable!("Didn't manage to resolve <length-percentage>: {:?}", other),
}
} }
/// Returns the length, without clamping.
#[inline]
fn unclamped_length(&self) -> Length {
self.length
}
/// Returns the clamped non-negative values.
#[inline]
fn clamp_to_non_negative(&self) -> LengthPercentage { fn clamp_to_non_negative(&self) -> LengthPercentage {
LengthPercentage::new_calc( let mut new = self.clone();
self.length, new.clamping_mode = AllowedNumericType::NonNegative;
Some(self.percentage), LengthPercentage::new_calc_unchecked(Box::new(new))
AllowedNumericType::NonNegative,
)
} }
} }
@ -641,7 +695,7 @@ impl CalcLengthPercentage {
// maybe. // maybe.
impl PartialEq for CalcLengthPercentage { impl PartialEq for CalcLengthPercentage {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.length == other.length && self.percentage == other.percentage self.node == other.node
} }
} }
@ -656,43 +710,30 @@ impl specified::CalcLengthPercentage {
where where
F: Fn(Length) -> Length, F: Fn(Length) -> Length,
{ {
use crate::values::specified::length::{FontRelativeLength, ViewportPercentageLength}; use crate::values::specified::calc::Leaf;
use std::f32; use crate::values::specified::length::NoCalcLength;
let mut length = 0.; let node = self.node.map_leaves(|leaf| {
match *leaf {
if let Some(absolute) = self.absolute { Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)),
length += zoom_fn(absolute.to_computed_value(context)).px(); Leaf::Length(l) => {
} CalcLengthPercentageLeaf::Length(match l {
NoCalcLength::Absolute(ref abs) => {
for val in &[ zoom_fn(abs.to_computed_value(context))
self.vw.map(ViewportPercentageLength::Vw), },
self.vh.map(ViewportPercentageLength::Vh), NoCalcLength::FontRelative(ref fr) => {
self.vmin.map(ViewportPercentageLength::Vmin), fr.to_computed_value(context, base_size)
self.vmax.map(ViewportPercentageLength::Vmax), },
] { other => other.to_computed_value(context),
if let Some(val) = *val { })
let viewport_size = context.viewport_size_for_viewport_unit_resolution(); },
length += val.to_computed_value(viewport_size).px(); Leaf::Number(..) |
Leaf::Angle(..) |
Leaf::Time(..) => unreachable!("Shouldn't have parsed"),
} }
} });
for val in &[ LengthPercentage::new_calc(node, self.clamping_mode)
self.ch.map(FontRelativeLength::Ch),
self.em.map(FontRelativeLength::Em),
self.ex.map(FontRelativeLength::Ex),
self.rem.map(FontRelativeLength::Rem),
] {
if let Some(val) = *val {
length += val.to_computed_value(context, base_size).px();
}
}
LengthPercentage::new_calc(
Length::new(length.min(f32::MAX).max(f32::MIN)),
self.percentage,
self.clamping_mode,
)
} }
/// Compute font-size or line-height taking into account text-zoom if necessary. /// Compute font-size or line-height taking into account text-zoom if necessary.
@ -711,25 +752,14 @@ impl specified::CalcLengthPercentage {
/// Compute the value into pixel length as CSSFloat without context, /// Compute the value into pixel length as CSSFloat without context,
/// so it returns Err(()) if there is any non-absolute unit. /// so it returns Err(()) if there is any non-absolute unit.
pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
if self.vw.is_some() || use crate::values::specified::calc::Leaf;
self.vh.is_some() || use crate::values::specified::length::NoCalcLength;
self.vmin.is_some() ||
self.vmax.is_some() ||
self.em.is_some() ||
self.ex.is_some() ||
self.ch.is_some() ||
self.rem.is_some() ||
self.percentage.is_some()
{
return Err(());
}
match self.absolute { // Simplification should've turned this into an absolute length,
Some(abs) => Ok(abs.to_px()), // otherwise it wouldn't have been able to.
None => { match self.node {
debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
Err(()) _ => Err(()),
},
} }
} }
@ -740,17 +770,48 @@ impl specified::CalcLengthPercentage {
#[inline] #[inline]
fn from_computed_value(computed: &CalcLengthPercentage) -> Self { fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
use crate::values::specified::length::AbsoluteLength; use crate::values::specified::calc::Leaf;
use crate::values::specified::length::NoCalcLength;
specified::CalcLengthPercentage { specified::CalcLengthPercentage {
clamping_mode: computed.clamping_mode, clamping_mode: computed.clamping_mode,
absolute: Some(AbsoluteLength::from_computed_value(&computed.length)), node: computed.node.map_leaves(|l| {
percentage: Some(computed.percentage), match l {
..Default::default() CalcLengthPercentageLeaf::Length(ref l) => Leaf::Length(NoCalcLength::from_px(l.px())),
CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0),
}
})
} }
} }
} }
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
/// https://drafts.csswg.org/css-values-4/#combine-math
/// https://drafts.csswg.org/css-values-4/#combine-mixed
impl Animate for LengthPercentage {
#[inline]
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
Ok(match (self.unpack(), other.unpack()) {
(Unpacked::Length(one), Unpacked::Length(other)) => {
Self::new_length(one.animate(&other, procedure)?)
},
(Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
Self::new_percent(one.animate(&other, procedure)?)
},
_ => {
let mut one = self.to_calc_node().into_owned();
let mut other = other.to_calc_node().into_owned();
let (l, r) = procedure.weights();
one.mul_by(l as f32);
other.mul_by(r as f32);
Self::new_calc(CalcNode::Sum(vec![one, other].into()), AllowedNumericType::All)
},
})
}
}
/// A wrapper of LengthPercentage, whose value must be >= 0. /// A wrapper of LengthPercentage, whose value must be >= 0.
pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;

View file

@ -12,7 +12,7 @@ use std::{cmp, mem};
use smallvec::SmallVec; use smallvec::SmallVec;
/// Whether we're a `min` or `max` function. /// Whether we're a `min` or `max` function.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)] #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, ToAnimatedZero, ToResolvedValue, ToShmem)]
#[repr(u8)] #[repr(u8)]
pub enum MinMaxOp { pub enum MinMaxOp {
/// `min()` /// `min()`
@ -44,24 +44,27 @@ pub enum SortKey {
} }
/// A generic node in a calc expression. /// A generic node in a calc expression.
///
/// FIXME: This would be much more elegant if we used `Self` in the types below,
/// but we can't because of https://github.com/serde-rs/serde/issues/1565.
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, ToAnimatedZero, ToResolvedValue, ToShmem)]
pub enum GenericCalcNode<L> { pub enum GenericCalcNode<L> {
/// A leaf node. /// A leaf node.
Leaf(L), Leaf(L),
/// A sum node, representing `a + b + c` where a, b, and c are the /// A sum node, representing `a + b + c` where a, b, and c are the
/// arguments. /// arguments.
Sum(Box<[Self]>), Sum(Box<[GenericCalcNode<L>]>),
/// A `min` or `max` function. /// A `min` or `max` function.
MinMax(Box<[Self]>, MinMaxOp), MinMax(Box<[GenericCalcNode<L>]>, MinMaxOp),
/// A `clamp()` function. /// A `clamp()` function.
Clamp { Clamp {
/// The minimum value. /// The minimum value.
min: Box<Self>, min: Box<GenericCalcNode<L>>,
/// The central value. /// The central value.
center: Box<Self>, center: Box<GenericCalcNode<L>>,
/// The maximum value. /// The maximum value.
max: Box<Self>, max: Box<GenericCalcNode<L>>,
}, },
} }
@ -111,6 +114,46 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
} }
} }
/// Convert this `CalcNode` into a `CalcNode` with a different leaf kind.
pub fn map_leaves<O, F>(&self, mut map: F) -> CalcNode<O>
where
O: CalcNodeLeaf,
F: FnMut(&L) -> O,
{
self.map_leaves_internal(&mut map)
}
fn map_leaves_internal<O, F>(&self, map: &mut F) -> CalcNode<O>
where
O: CalcNodeLeaf,
F: FnMut(&L) -> O,
{
fn map_children<L, O, F>(
children: &[CalcNode<L>],
map: &mut F,
) -> Box<[CalcNode<O>]>
where
L: CalcNodeLeaf,
O: CalcNodeLeaf,
F: FnMut(&L) -> O,
{
children.iter().map(|c| c.map_leaves_internal(map)).collect()
}
match *self {
Self::Leaf(ref l) => CalcNode::Leaf(map(l)),
Self::Sum(ref c) => CalcNode::Sum(map_children(c, map)),
Self::MinMax(ref c, op) => CalcNode::MinMax(map_children(c, map), op),
Self::Clamp { ref min, ref center, ref max } => {
let min = Box::new(min.map_leaves_internal(map));
let center = Box::new(center.map_leaves_internal(map));
let max = Box::new(max.map_leaves_internal(map));
CalcNode::Clamp { min, center, max }
}
}
}
fn is_negative_leaf(&self) -> bool { fn is_negative_leaf(&self) -> bool {
match *self { match *self {
Self::Leaf(ref l) => l.is_negative(), Self::Leaf(ref l) => l.is_negative(),

View file

@ -354,7 +354,7 @@ impl ToAbsoluteLength for SpecifiedLengthPercentage {
match *self { match *self {
Length(len) => len.to_computed_pixel_length_without_context(), Length(len) => len.to_computed_pixel_length_without_context(),
Calc(ref calc) => calc.to_computed_pixel_length_without_context(), Calc(ref calc) => calc.to_computed_pixel_length_without_context(),
_ => Err(()), Percentage(..) => Err(()),
} }
} }
} }

View file

@ -7,7 +7,6 @@
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation //! [calc]: https://drafts.csswg.org/css-values/#calc-notation
use crate::parser::ParserContext; use crate::parser::ParserContext;
use crate::values::computed;
use crate::values::generics::calc as generic; use crate::values::generics::calc as generic;
use crate::values::generics::calc::{MinMaxOp, SortKey}; use crate::values::generics::calc::{MinMaxOp, SortKey};
use crate::values::specified::length::ViewportPercentageLength; use crate::values::specified::length::ViewportPercentageLength;
@ -35,7 +34,7 @@ pub enum MathFunction {
} }
/// A leaf node inside a `Calc` expression's AST. /// A leaf node inside a `Calc` expression's AST.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Leaf { pub enum Leaf {
/// `<length>` /// `<length>`
Length(NoCalcLength), Length(NoCalcLength),
@ -89,87 +88,12 @@ enum CalcUnit {
/// relative lengths, and to_computed_pixel_length_without_context() handles /// relative lengths, and to_computed_pixel_length_without_context() handles
/// this case. Therefore, if you want to add a new field, please make sure this /// this case. Therefore, if you want to add a new field, please make sure this
/// function work properly. /// function work properly.
#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)] #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct CalcLengthPercentage { pub struct CalcLengthPercentage {
#[css(skip)]
pub clamping_mode: AllowedNumericType, pub clamping_mode: AllowedNumericType,
pub absolute: Option<AbsoluteLength>, pub node: CalcNode,
pub vw: Option<CSSFloat>,
pub vh: Option<CSSFloat>,
pub vmin: Option<CSSFloat>,
pub vmax: Option<CSSFloat>,
pub em: Option<CSSFloat>,
pub ex: Option<CSSFloat>,
pub ch: Option<CSSFloat>,
pub rem: Option<CSSFloat>,
pub percentage: Option<computed::Percentage>,
}
impl ToCss for CalcLengthPercentage {
/// <https://drafts.csswg.org/css-values/#calc-serialize>
///
/// FIXME(emilio): Should this simplify away zeros?
#[allow(unused_assignments)]
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
use num_traits::Zero;
let mut first_value = true;
macro_rules! first_value_check {
($val:expr) => {
if !first_value {
dest.write_str(if $val < Zero::zero() { " - " } else { " + " })?;
} else if $val < Zero::zero() {
dest.write_str("-")?;
}
first_value = false;
};
}
macro_rules! serialize {
( $( $val:ident ),* ) => {
$(
if let Some(val) = self.$val {
first_value_check!(val);
val.abs().to_css(dest)?;
dest.write_str(stringify!($val))?;
}
)*
};
}
macro_rules! serialize_abs {
( $( $val:ident ),+ ) => {
$(
if let Some(AbsoluteLength::$val(v)) = self.absolute {
first_value_check!(v);
AbsoluteLength::$val(v.abs()).to_css(dest)?;
}
)+
};
}
dest.write_str("calc(")?;
// NOTE(emilio): Percentages first because of web-compat problems, see:
// https://github.com/w3c/csswg-drafts/issues/1731
if let Some(val) = self.percentage {
first_value_check!(val.0);
val.abs().to_css(dest)?;
}
// NOTE(emilio): The order here it's very intentional, and alphabetic
// per the spec linked above.
serialize!(ch);
serialize_abs!(Cm);
serialize!(em, ex);
serialize_abs!(In, Mm, Pc, Pt, Px, Q);
serialize!(rem, vh, vmax, vmin, vw);
dest.write_str(")")
}
} }
impl SpecifiedValueInfo for CalcLengthPercentage {} impl SpecifiedValueInfo for CalcLengthPercentage {}
@ -591,84 +515,15 @@ impl CalcNode {
/// Tries to simplify this expression into a `<length>` or `<percentage`> /// Tries to simplify this expression into a `<length>` or `<percentage`>
/// value. /// value.
fn to_length_or_percentage( fn into_length_or_percentage(
&mut self, mut self,
clamping_mode: AllowedNumericType, clamping_mode: AllowedNumericType,
) -> Result<CalcLengthPercentage, ()> { ) -> Result<CalcLengthPercentage, ()> {
let mut ret = CalcLengthPercentage {
clamping_mode,
..Default::default()
};
self.simplify_and_sort_children(); self.simplify_and_sort_children();
self.add_length_or_percentage_to(&mut ret, 1.0)?; Ok(CalcLengthPercentage {
Ok(ret) clamping_mode,
} node: self,
})
/// Puts this `<length>` or `<percentage>` into `ret`, or error.
///
/// `factor` is the sign or multiplicative factor to account for the sign
/// (this allows adding and substracting into the return value).
fn add_length_or_percentage_to(
&self,
ret: &mut CalcLengthPercentage,
factor: CSSFloat,
) -> Result<(), ()> {
match *self {
CalcNode::Leaf(Leaf::Percentage(pct)) => {
ret.percentage = Some(computed::Percentage(
ret.percentage.map_or(0., |p| p.0) + pct * factor,
));
},
CalcNode::Leaf(Leaf::Length(ref l)) => match *l {
NoCalcLength::Absolute(abs) => {
ret.absolute = Some(match ret.absolute {
Some(value) => value + abs * factor,
None => abs * factor,
});
},
NoCalcLength::FontRelative(rel) => match rel {
FontRelativeLength::Em(em) => {
ret.em = Some(ret.em.unwrap_or(0.) + em * factor);
},
FontRelativeLength::Ex(ex) => {
ret.ex = Some(ret.ex.unwrap_or(0.) + ex * factor);
},
FontRelativeLength::Ch(ch) => {
ret.ch = Some(ret.ch.unwrap_or(0.) + ch * factor);
},
FontRelativeLength::Rem(rem) => {
ret.rem = Some(ret.rem.unwrap_or(0.) + rem * factor);
},
},
NoCalcLength::ViewportPercentage(rel) => match rel {
ViewportPercentageLength::Vh(vh) => {
ret.vh = Some(ret.vh.unwrap_or(0.) + vh * factor)
},
ViewportPercentageLength::Vw(vw) => {
ret.vw = Some(ret.vw.unwrap_or(0.) + vw * factor)
},
ViewportPercentageLength::Vmax(vmax) => {
ret.vmax = Some(ret.vmax.unwrap_or(0.) + vmax * factor)
},
ViewportPercentageLength::Vmin(vmin) => {
ret.vmin = Some(ret.vmin.unwrap_or(0.) + vmin * factor)
},
},
NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
},
CalcNode::Sum(ref children) => {
for child in &**children {
child.add_length_or_percentage_to(ret, factor)?;
}
},
CalcNode::MinMax(..) | CalcNode::Clamp { .. } => {
// FIXME(emilio): Implement min/max/clamp for length-percentage.
return Err(());
},
CalcNode::Leaf(..) => return Err(()),
}
Ok(())
} }
/// Tries to simplify this expression into a `<time>` value. /// Tries to simplify this expression into a `<time>` value.
@ -742,7 +597,7 @@ impl CalcNode {
function: MathFunction, function: MathFunction,
) -> Result<CalcLengthPercentage, ParseError<'i>> { ) -> Result<CalcLengthPercentage, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::LengthPercentage)? Self::parse(context, input, function, CalcUnit::LengthPercentage)?
.to_length_or_percentage(clamping_mode) .into_length_or_percentage(clamping_mode)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }
@ -765,7 +620,7 @@ impl CalcNode {
function: MathFunction, function: MathFunction,
) -> Result<CalcLengthPercentage, ParseError<'i>> { ) -> Result<CalcLengthPercentage, ParseError<'i>> {
Self::parse(context, input, function, CalcUnit::Length)? Self::parse(context, input, function, CalcUnit::Length)?
.to_length_or_percentage(clamping_mode) .into_length_or_percentage(clamping_mode)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }

View file

@ -16,7 +16,7 @@ use crate::values::generics::length::{
GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize, GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize,
}; };
use crate::values::generics::NonNegative; use crate::values::generics::NonNegative;
use crate::values::specified::calc::CalcNode; use crate::values::specified::calc::{self, CalcNode};
use crate::values::specified::NonNegativeNumber; use crate::values::specified::NonNegativeNumber;
use crate::values::CSSFloat; use crate::values::CSSFloat;
use crate::Zero; use crate::Zero;
@ -952,9 +952,10 @@ impl From<Percentage> for LengthPercentage {
#[inline] #[inline]
fn from(pc: Percentage) -> Self { fn from(pc: Percentage) -> Self {
if pc.is_calc() { if pc.is_calc() {
// FIXME(emilio): Hard-coding the clamping mode is suspect.
LengthPercentage::Calc(Box::new(CalcLengthPercentage { LengthPercentage::Calc(Box::new(CalcLengthPercentage {
percentage: Some(computed::Percentage(pc.get())), clamping_mode: AllowedNumericType::All,
..Default::default() node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())),
})) }))
} else { } else {
LengthPercentage::Percentage(computed::Percentage(pc.get())) LengthPercentage::Percentage(computed::Percentage(pc.get()))

View file

@ -295,10 +295,8 @@ impl<S: Side> ToComputedValue for PositionComponent<S> {
}, },
PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => { PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
let length = length.to_computed_value(context); let length = length.to_computed_value(context);
let p = Percentage(1. - length.percentage());
let l = -length.unclamped_length();
// We represent `<end-side> <length>` as `calc(100% - <length>)`. // We represent `<end-side> <length>` as `calc(100% - <length>)`.
ComputedLengthPercentage::new_calc(l, Some(p), AllowedNumericType::All) ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
}, },
PositionComponent::Side(_, Some(ref length)) | PositionComponent::Side(_, Some(ref length)) |
PositionComponent::Length(ref length) => length.to_computed_value(context), PositionComponent::Length(ref length) => length.to_computed_value(context),