mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
style: Move the guts of calc nodes into a generic enum.
We'll have different leaf nodes as we progress in the value computation stage. Differential Revision: https://phabricator.services.mozilla.com/D63396
This commit is contained in:
parent
869553357d
commit
426edbd991
3 changed files with 488 additions and 365 deletions
408
components/style/values/generics/calc.rs
Normal file
408
components/style/values/generics/calc.rs
Normal file
|
@ -0,0 +1,408 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! [Calc expressions][calc].
|
||||||
|
//!
|
||||||
|
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
|
||||||
|
|
||||||
|
use style_traits::{CssWriter, ToCss};
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
use std::{cmp, mem};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
/// Whether we're a `min` or `max` function.
|
||||||
|
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum MinMaxOp {
|
||||||
|
/// `min()`
|
||||||
|
Min,
|
||||||
|
/// `max()`
|
||||||
|
Max,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This determines the order in which we serialize members of a calc() sum.
|
||||||
|
///
|
||||||
|
/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum SortKey {
|
||||||
|
Number,
|
||||||
|
Percentage,
|
||||||
|
Ch,
|
||||||
|
Deg,
|
||||||
|
Em,
|
||||||
|
Ex,
|
||||||
|
Px,
|
||||||
|
Rem,
|
||||||
|
Sec,
|
||||||
|
Vh,
|
||||||
|
Vmax,
|
||||||
|
Vmin,
|
||||||
|
Vw,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generic node in a calc expression.
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum GenericCalcNode<L> {
|
||||||
|
/// A leaf node.
|
||||||
|
Leaf(L),
|
||||||
|
/// A sum node, representing `a + b + c` where a, b, and c are the
|
||||||
|
/// arguments.
|
||||||
|
Sum(Box<[Self]>),
|
||||||
|
/// A `min` or `max` function.
|
||||||
|
MinMax(Box<[Self]>, MinMaxOp),
|
||||||
|
/// A `clamp()` function.
|
||||||
|
Clamp {
|
||||||
|
/// The minimum value.
|
||||||
|
min: Box<Self>,
|
||||||
|
/// The central value.
|
||||||
|
center: Box<Self>,
|
||||||
|
/// The maximum value.
|
||||||
|
max: Box<Self>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use self::GenericCalcNode as CalcNode;
|
||||||
|
|
||||||
|
/// A trait that represents all the stuff a valid leaf of a calc expression.
|
||||||
|
pub trait CalcNodeLeaf : Clone + Sized + PartialOrd + PartialEq + ToCss {
|
||||||
|
/// Whether this value is known-negative.
|
||||||
|
fn is_negative(&self) -> bool;
|
||||||
|
|
||||||
|
/// Tries to merge one sum to another, that is, perform `x` + `y`.
|
||||||
|
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()>;
|
||||||
|
|
||||||
|
/// Multiplies the leaf by a given scalar number.
|
||||||
|
fn mul_by(&mut self, scalar: f32);
|
||||||
|
|
||||||
|
/// Negates the leaf.
|
||||||
|
fn negate(&mut self) {
|
||||||
|
self.mul_by(-1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Canonicalizes the expression if necessary.
|
||||||
|
fn simplify(&mut self);
|
||||||
|
|
||||||
|
/// Returns the sort key for simplification.
|
||||||
|
fn sort_key(&self) -> SortKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L: CalcNodeLeaf> CalcNode<L> {
|
||||||
|
/// Negates the node.
|
||||||
|
pub fn negate(&mut self) {
|
||||||
|
self.mul_by(-1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_key(&self) -> SortKey {
|
||||||
|
match *self {
|
||||||
|
Self::Leaf(ref l) => l.sort_key(),
|
||||||
|
_ => SortKey::Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to merge one sum to another, that is, perform `x` + `y`.
|
||||||
|
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
|
||||||
|
match (self, other) {
|
||||||
|
(&mut CalcNode::Leaf(ref mut one), &CalcNode::Leaf(ref other)) => one.try_sum_in_place(other),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_negative_leaf(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Self::Leaf(ref l) => l.is_negative(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Multiplies the node by a scalar.
|
||||||
|
pub fn mul_by(&mut self, scalar: f32) {
|
||||||
|
match *self {
|
||||||
|
Self::Leaf(ref mut l) => l.mul_by(scalar),
|
||||||
|
// Multiplication is distributive across this.
|
||||||
|
Self::Sum(ref mut children) => {
|
||||||
|
for node in &mut **children {
|
||||||
|
node.mul_by(scalar);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// This one is a bit trickier.
|
||||||
|
Self::MinMax(ref mut children, ref mut op) => {
|
||||||
|
for node in &mut **children {
|
||||||
|
node.mul_by(scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For negatives we need to invert the operation.
|
||||||
|
if scalar < 0. {
|
||||||
|
*op = match *op {
|
||||||
|
MinMaxOp::Min => MinMaxOp::Max,
|
||||||
|
MinMaxOp::Max => MinMaxOp::Min,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// This one is slightly tricky too.
|
||||||
|
Self::Clamp {
|
||||||
|
ref mut min,
|
||||||
|
ref mut center,
|
||||||
|
ref mut max,
|
||||||
|
} => {
|
||||||
|
min.mul_by(scalar);
|
||||||
|
center.mul_by(scalar);
|
||||||
|
max.mul_by(scalar);
|
||||||
|
// For negatives we need to swap min / max.
|
||||||
|
if scalar < 0. {
|
||||||
|
mem::swap(min, max);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simplifies and sorts the calculation. This is only needed if it's going
|
||||||
|
/// to be preserved after parsing (so, for `<length-percentage>`). Otherwise
|
||||||
|
/// we can just evaluate it and we'll come up with a simplified value
|
||||||
|
/// anyways.
|
||||||
|
pub fn simplify_and_sort_children(&mut self) {
|
||||||
|
macro_rules! replace_self_with {
|
||||||
|
($slot:expr) => {{
|
||||||
|
let dummy = Self::MinMax(Box::new([]), MinMaxOp::Max);
|
||||||
|
let result = mem::replace($slot, dummy);
|
||||||
|
mem::replace(self, result);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
match *self {
|
||||||
|
Self::Clamp {
|
||||||
|
ref mut min,
|
||||||
|
ref mut center,
|
||||||
|
ref mut max,
|
||||||
|
} => {
|
||||||
|
min.simplify_and_sort_children();
|
||||||
|
center.simplify_and_sort_children();
|
||||||
|
max.simplify_and_sort_children();
|
||||||
|
|
||||||
|
// NOTE: clamp() is max(min, min(center, max))
|
||||||
|
let min_cmp_center = match min.partial_cmp(¢er) {
|
||||||
|
Some(o) => o,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// So if we can prove that min is more than center, then we won,
|
||||||
|
// as that's what we should always return.
|
||||||
|
if matches!(min_cmp_center, cmp::Ordering::Greater) {
|
||||||
|
return replace_self_with!(&mut **min);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise try with max.
|
||||||
|
let max_cmp_center = match max.partial_cmp(¢er) {
|
||||||
|
Some(o) => o,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(max_cmp_center, cmp::Ordering::Less) {
|
||||||
|
// max is less than center, so we need to return effectively
|
||||||
|
// `max(min, max)`.
|
||||||
|
let max_cmp_min = match max.partial_cmp(&min) {
|
||||||
|
Some(o) => o,
|
||||||
|
None => {
|
||||||
|
debug_assert!(
|
||||||
|
false,
|
||||||
|
"We compared center with min and max, how are \
|
||||||
|
min / max not comparable with each other?"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(max_cmp_min, cmp::Ordering::Less) {
|
||||||
|
return replace_self_with!(&mut **min);
|
||||||
|
}
|
||||||
|
|
||||||
|
return replace_self_with!(&mut **max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we're the center node.
|
||||||
|
return replace_self_with!(&mut **center);
|
||||||
|
},
|
||||||
|
Self::MinMax(ref mut children, op) => {
|
||||||
|
for child in &mut **children {
|
||||||
|
child.simplify_and_sort_children();
|
||||||
|
}
|
||||||
|
|
||||||
|
let winning_order = match op {
|
||||||
|
MinMaxOp::Min => cmp::Ordering::Less,
|
||||||
|
MinMaxOp::Max => cmp::Ordering::Greater,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = 0;
|
||||||
|
for i in 1..children.len() {
|
||||||
|
let o = match children[i].partial_cmp(&children[result]) {
|
||||||
|
// We can't compare all the children, so we can't
|
||||||
|
// know which one will actually win. Bail out and
|
||||||
|
// keep ourselves as a min / max function.
|
||||||
|
//
|
||||||
|
// TODO: Maybe we could simplify compatible children,
|
||||||
|
// see https://github.com/w3c/csswg-drafts/issues/4756
|
||||||
|
None => return,
|
||||||
|
Some(o) => o,
|
||||||
|
};
|
||||||
|
|
||||||
|
if o == winning_order {
|
||||||
|
result = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_self_with!(&mut children[result]);
|
||||||
|
},
|
||||||
|
Self::Sum(ref mut children_slot) => {
|
||||||
|
let mut sums_to_merge = SmallVec::<[_; 3]>::new();
|
||||||
|
let mut extra_kids = 0;
|
||||||
|
for (i, child) in children_slot.iter_mut().enumerate() {
|
||||||
|
child.simplify_and_sort_children();
|
||||||
|
if let Self::Sum(ref mut children) = *child {
|
||||||
|
extra_kids += children.len();
|
||||||
|
sums_to_merge.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we only have one kid, we've already simplified it, and it
|
||||||
|
// doesn't really matter whether it's a sum already or not, so
|
||||||
|
// lift it up and continue.
|
||||||
|
if children_slot.len() == 1 {
|
||||||
|
return replace_self_with!(&mut children_slot[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut children = mem::replace(children_slot, Box::new([])).into_vec();
|
||||||
|
|
||||||
|
if !sums_to_merge.is_empty() {
|
||||||
|
children.reserve(extra_kids - sums_to_merge.len());
|
||||||
|
// Merge all our nested sums, in reverse order so that the
|
||||||
|
// list indices are not invalidated.
|
||||||
|
for i in sums_to_merge.drain(..).rev() {
|
||||||
|
let kid_children = match children.swap_remove(i) {
|
||||||
|
Self::Sum(c) => c,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// This would be nicer with
|
||||||
|
// https://github.com/rust-lang/rust/issues/59878 fixed.
|
||||||
|
children.extend(kid_children.into_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(children.len() >= 2, "Should still have multiple kids!");
|
||||||
|
|
||||||
|
// Sort by spec order.
|
||||||
|
children.sort_unstable_by_key(|c| c.sort_key());
|
||||||
|
|
||||||
|
// NOTE: if the function returns true, by the docs of dedup_by,
|
||||||
|
// a is removed.
|
||||||
|
children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok());
|
||||||
|
|
||||||
|
if children.len() == 1 {
|
||||||
|
// If only one children remains, lift it up, and carry on.
|
||||||
|
replace_self_with!(&mut children[0]);
|
||||||
|
} else {
|
||||||
|
// Else put our simplified children back.
|
||||||
|
mem::replace(children_slot, children.into_boxed_slice());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::Leaf(ref mut l) => {
|
||||||
|
l.simplify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_css_impl<W>(&self, dest: &mut CssWriter<W>, is_outermost: bool) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
let write_closing_paren = match *self {
|
||||||
|
Self::MinMax(_, op) => {
|
||||||
|
dest.write_str(match op {
|
||||||
|
MinMaxOp::Max => "max(",
|
||||||
|
MinMaxOp::Min => "min(",
|
||||||
|
})?;
|
||||||
|
true
|
||||||
|
},
|
||||||
|
Self::Clamp { .. } => {
|
||||||
|
dest.write_str("clamp(")?;
|
||||||
|
true
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
if is_outermost {
|
||||||
|
dest.write_str("calc(")?;
|
||||||
|
}
|
||||||
|
is_outermost
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
Self::MinMax(ref children, _) => {
|
||||||
|
let mut first = true;
|
||||||
|
for child in &**children {
|
||||||
|
if !first {
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
child.to_css_impl(dest, false)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::Sum(ref children) => {
|
||||||
|
let mut first = true;
|
||||||
|
for child in &**children {
|
||||||
|
if !first {
|
||||||
|
if child.is_negative_leaf() {
|
||||||
|
dest.write_str(" - ")?;
|
||||||
|
let mut c = child.clone();
|
||||||
|
c.negate();
|
||||||
|
c.to_css(dest)?;
|
||||||
|
} else {
|
||||||
|
dest.write_str(" + ")?;
|
||||||
|
child.to_css(dest)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
child.to_css_impl(dest, false)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::Clamp {
|
||||||
|
ref min,
|
||||||
|
ref center,
|
||||||
|
ref max,
|
||||||
|
} => {
|
||||||
|
min.to_css_impl(dest, false)?;
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
center.to_css_impl(dest, false)?;
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
max.to_css_impl(dest, false)?;
|
||||||
|
},
|
||||||
|
Self::Leaf(ref l) => l.to_css(dest)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
if write_closing_paren {
|
||||||
|
dest.write_str(")")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L: CalcNodeLeaf> PartialOrd for CalcNode<L> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||||
|
match (self, other) {
|
||||||
|
(&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => one.partial_cmp(other),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L: CalcNodeLeaf> ToCss for CalcNode<L> {
|
||||||
|
/// <https://drafts.csswg.org/css-values/#calc-serialize>
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
self.to_css_impl(dest, /* is_outermost = */ true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ pub mod basic_shape;
|
||||||
pub mod border;
|
pub mod border;
|
||||||
#[path = "box.rs"]
|
#[path = "box.rs"]
|
||||||
pub mod box_;
|
pub mod box_;
|
||||||
|
pub mod calc;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod column;
|
pub mod column;
|
||||||
pub mod counters;
|
pub mod counters;
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
use crate::parser::ParserContext;
|
use crate::parser::ParserContext;
|
||||||
use crate::values::computed;
|
use crate::values::computed;
|
||||||
|
use crate::values::generics::calc as generic;
|
||||||
|
use crate::values::generics::calc::{MinMaxOp, SortKey};
|
||||||
use crate::values::specified::length::ViewportPercentageLength;
|
use crate::values::specified::length::ViewportPercentageLength;
|
||||||
use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
|
use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
|
||||||
use crate::values::specified::{self, Angle, Time};
|
use crate::values::specified::{self, Angle, Time};
|
||||||
|
@ -15,7 +17,7 @@ use crate::values::{CSSFloat, CSSInteger};
|
||||||
use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token};
|
use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use std::{cmp, mem};
|
use std::cmp;
|
||||||
use style_traits::values::specified::AllowedNumericType;
|
use style_traits::values::specified::AllowedNumericType;
|
||||||
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
|
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
|
||||||
|
|
||||||
|
@ -32,40 +34,9 @@ pub enum MathFunction {
|
||||||
Clamp,
|
Clamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This determines the order in which we serialize members of a calc()
|
/// A leaf node inside a `Calc` expression's AST.
|
||||||
/// sum.
|
|
||||||
///
|
|
||||||
/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
|
||||||
enum SortKey {
|
|
||||||
Number,
|
|
||||||
Percentage,
|
|
||||||
Ch,
|
|
||||||
Deg,
|
|
||||||
Em,
|
|
||||||
Ex,
|
|
||||||
Px,
|
|
||||||
Rem,
|
|
||||||
Sec,
|
|
||||||
Vh,
|
|
||||||
Vmax,
|
|
||||||
Vmin,
|
|
||||||
Vw,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether we're a `min` or `max` function.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum MinMaxOp {
|
|
||||||
/// `min()`
|
|
||||||
Min,
|
|
||||||
/// `max()`
|
|
||||||
Max,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node inside a `Calc` expression's AST.
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum CalcNode {
|
pub enum Leaf {
|
||||||
/// `<length>`
|
/// `<length>`
|
||||||
Length(NoCalcLength),
|
Length(NoCalcLength),
|
||||||
/// `<angle>`
|
/// `<angle>`
|
||||||
|
@ -76,27 +47,28 @@ pub enum CalcNode {
|
||||||
Percentage(CSSFloat),
|
Percentage(CSSFloat),
|
||||||
/// `<number>`
|
/// `<number>`
|
||||||
Number(CSSFloat),
|
Number(CSSFloat),
|
||||||
/// An expression of the form `x + y + ...`. Subtraction is represented by
|
}
|
||||||
/// the negated expression of the right hand side.
|
|
||||||
Sum(Box<[CalcNode]>),
|
impl ToCss for Leaf {
|
||||||
/// A `min()` / `max()` function.
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
MinMax(Box<[CalcNode]>, MinMaxOp),
|
where
|
||||||
/// A `clamp()` function.
|
W: Write,
|
||||||
Clamp {
|
{
|
||||||
/// The minimum value.
|
match *self {
|
||||||
min: Box<CalcNode>,
|
Self::Length(ref l) => l.to_css(dest),
|
||||||
/// The central value.
|
Self::Number(ref n) => n.to_css(dest),
|
||||||
center: Box<CalcNode>,
|
Self::Percentage(p) => crate::values::serialize_percentage(p, dest),
|
||||||
/// The maximum value.
|
Self::Angle(ref a) => a.to_css(dest),
|
||||||
max: Box<CalcNode>,
|
Self::Time(ref t) => t.to_css(dest),
|
||||||
},
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An expected unit we intend to parse within a `calc()` expression.
|
/// An expected unit we intend to parse within a `calc()` expression.
|
||||||
///
|
///
|
||||||
/// This is used as a hint for the parser to fast-reject invalid expressions.
|
/// This is used as a hint for the parser to fast-reject invalid expressions.
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CalcUnit {
|
enum CalcUnit {
|
||||||
/// `<number>`
|
/// `<number>`
|
||||||
Number,
|
Number,
|
||||||
/// `<length>`
|
/// `<length>`
|
||||||
|
@ -204,7 +176,7 @@ impl SpecifiedValueInfo for CalcLengthPercentage {}
|
||||||
|
|
||||||
macro_rules! impl_generic_to_type {
|
macro_rules! impl_generic_to_type {
|
||||||
($self:ident, $self_variant:ident, $to_self:ident, $to_float:ident, $from_float:path) => {{
|
($self:ident, $self_variant:ident, $to_self:ident, $to_float:ident, $from_float:path) => {{
|
||||||
if let Self::$self_variant(ref v) = *$self {
|
if let Self::Leaf(Leaf::$self_variant(ref v)) = *$self {
|
||||||
return Ok(v.clone());
|
return Ok(v.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,47 +235,46 @@ macro_rules! impl_generic_to_type {
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
},
|
},
|
||||||
Self::Length(..) |
|
Self::Leaf(..) => return Err(()),
|
||||||
Self::Angle(..) |
|
|
||||||
Self::Time(..) |
|
|
||||||
Self::Percentage(..) |
|
|
||||||
Self::Number(..) => return Err(()),
|
|
||||||
})
|
})
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for CalcNode {
|
impl PartialOrd for Leaf {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||||
use self::CalcNode::*;
|
use self::Leaf::*;
|
||||||
|
|
||||||
|
if std::mem::discriminant(self) != std::mem::discriminant(other) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
|
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
|
||||||
(&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
|
(&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
|
||||||
(&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()),
|
(&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()),
|
||||||
(&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
|
(&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
|
||||||
(&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
|
(&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
|
||||||
_ => None,
|
_ => {
|
||||||
|
match *self {
|
||||||
|
Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) => {},
|
||||||
|
}
|
||||||
|
unsafe { debug_unreachable!("Forgot a branch?"); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalcNode {
|
impl generic::CalcNodeLeaf for Leaf {
|
||||||
fn is_simple_negative(&self) -> bool {
|
fn is_negative(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Self::Length(ref l) => l.is_negative(),
|
Self::Length(ref l) => l.is_negative(),
|
||||||
Self::Percentage(n) |
|
Self::Percentage(n) |
|
||||||
Self::Number(n) => n < 0.,
|
Self::Number(n) => n < 0.,
|
||||||
Self::Angle(ref a) => a.degrees() < 0.,
|
Self::Angle(ref a) => a.degrees() < 0.,
|
||||||
Self::Time(ref t) => t.seconds() < 0.,
|
Self::Time(ref t) => t.seconds() < 0.,
|
||||||
Self::MinMax(..) |
|
|
||||||
Self::Sum(..) |
|
|
||||||
Self::Clamp { .. } => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn negate(&mut self) {
|
|
||||||
self.mul_by(-1.);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mul_by(&mut self, scalar: f32) {
|
fn mul_by(&mut self, scalar: f32) {
|
||||||
match *self {
|
match *self {
|
||||||
Self::Length(ref mut l) => {
|
Self::Length(ref mut l) => {
|
||||||
|
@ -323,44 +294,10 @@ impl CalcNode {
|
||||||
Self::Percentage(ref mut p) => {
|
Self::Percentage(ref mut p) => {
|
||||||
*p *= scalar;
|
*p *= scalar;
|
||||||
},
|
},
|
||||||
// Multiplication is distributive across this.
|
|
||||||
Self::Sum(ref mut children) => {
|
|
||||||
for node in &mut **children {
|
|
||||||
node.mul_by(scalar);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// This one is a bit trickier.
|
|
||||||
Self::MinMax(ref mut children, ref mut op) => {
|
|
||||||
for node in &mut **children {
|
|
||||||
node.mul_by(scalar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For negatives we need to invert the operation.
|
|
||||||
if scalar < 0. {
|
|
||||||
*op = match *op {
|
|
||||||
MinMaxOp::Min => MinMaxOp::Max,
|
|
||||||
MinMaxOp::Max => MinMaxOp::Min,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Multiplication is distributive across these.
|
|
||||||
Self::Clamp {
|
|
||||||
ref mut min,
|
|
||||||
ref mut center,
|
|
||||||
ref mut max,
|
|
||||||
} => {
|
|
||||||
min.mul_by(scalar);
|
|
||||||
center.mul_by(scalar);
|
|
||||||
max.mul_by(scalar);
|
|
||||||
// For negatives we need to swap min / max.
|
|
||||||
if scalar < 0. {
|
|
||||||
mem::swap(min, max);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_node_sort_key(&self) -> SortKey {
|
fn sort_key(&self) -> SortKey {
|
||||||
match *self {
|
match *self {
|
||||||
Self::Number(..) => SortKey::Number,
|
Self::Number(..) => SortKey::Number,
|
||||||
Self::Percentage(..) => SortKey::Percentage,
|
Self::Percentage(..) => SortKey::Percentage,
|
||||||
|
@ -382,7 +319,12 @@ impl CalcNode {
|
||||||
},
|
},
|
||||||
NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
|
NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
|
||||||
},
|
},
|
||||||
Self::Sum(..) | Self::MinMax(..) | Self::Clamp { .. } => SortKey::Other,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simplify(&mut self) {
|
||||||
|
if let Self::Length(NoCalcLength::Absolute(ref mut abs)) = *self {
|
||||||
|
*abs = AbsoluteLength::Px(abs.to_px());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +333,11 @@ impl CalcNode {
|
||||||
/// Only handles leaf nodes, it's the caller's responsibility to simplify
|
/// Only handles leaf nodes, it's the caller's responsibility to simplify
|
||||||
/// them before calling this if needed.
|
/// them before calling this if needed.
|
||||||
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
|
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
|
||||||
use self::CalcNode::*;
|
use self::Leaf::*;
|
||||||
|
|
||||||
|
if std::mem::discriminant(self) != std::mem::discriminant(other) {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(&mut Number(ref mut one), &Number(ref other)) |
|
(&mut Number(ref mut one), &Number(ref other)) |
|
||||||
|
@ -407,170 +353,22 @@ impl CalcNode {
|
||||||
(&mut Length(ref mut one), &Length(ref other)) => {
|
(&mut Length(ref mut one), &Length(ref other)) => {
|
||||||
*one = one.try_sum(other)?;
|
*one = one.try_sum(other)?;
|
||||||
},
|
},
|
||||||
_ => return Err(()),
|
_ => {
|
||||||
|
match *other {
|
||||||
|
Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) => {},
|
||||||
|
}
|
||||||
|
unsafe { debug_unreachable!(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Simplifies and sorts the calculation. This is only needed if it's going
|
/// A calc node representation for specified values.
|
||||||
/// to be preserved after parsing (so, for `<length-percentage>`). Otherwise
|
pub type CalcNode = generic::GenericCalcNode<Leaf>;
|
||||||
/// we can just evaluate it and we'll come up with a simplified value
|
|
||||||
/// anyways.
|
|
||||||
fn simplify_and_sort_children(&mut self) {
|
|
||||||
macro_rules! replace_self_with {
|
|
||||||
($slot:expr) => {{
|
|
||||||
let result = mem::replace($slot, Self::Number(0.));
|
|
||||||
mem::replace(self, result);
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
match *self {
|
|
||||||
Self::Clamp {
|
|
||||||
ref mut min,
|
|
||||||
ref mut center,
|
|
||||||
ref mut max,
|
|
||||||
} => {
|
|
||||||
min.simplify_and_sort_children();
|
|
||||||
center.simplify_and_sort_children();
|
|
||||||
max.simplify_and_sort_children();
|
|
||||||
|
|
||||||
// NOTE: clamp() is max(min, min(center, max))
|
|
||||||
let min_cmp_center = match min.partial_cmp(¢er) {
|
|
||||||
Some(o) => o,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// So if we can prove that min is more than center, then we won,
|
|
||||||
// as that's what we should always return.
|
|
||||||
if matches!(min_cmp_center, cmp::Ordering::Greater) {
|
|
||||||
return replace_self_with!(&mut **min);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise try with max.
|
|
||||||
let max_cmp_center = match max.partial_cmp(¢er) {
|
|
||||||
Some(o) => o,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(max_cmp_center, cmp::Ordering::Less) {
|
|
||||||
// max is less than center, so we need to return effectively
|
|
||||||
// `max(min, max)`.
|
|
||||||
let max_cmp_min = match max.partial_cmp(&min) {
|
|
||||||
Some(o) => o,
|
|
||||||
None => {
|
|
||||||
debug_assert!(
|
|
||||||
false,
|
|
||||||
"We compared center with min and max, how are \
|
|
||||||
min / max not comparable with each other?"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(max_cmp_min, cmp::Ordering::Less) {
|
|
||||||
return replace_self_with!(&mut **min);
|
|
||||||
}
|
|
||||||
|
|
||||||
return replace_self_with!(&mut **max);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise we're the center node.
|
|
||||||
return replace_self_with!(&mut **center);
|
|
||||||
},
|
|
||||||
Self::MinMax(ref mut children, op) => {
|
|
||||||
for child in &mut **children {
|
|
||||||
child.simplify_and_sort_children();
|
|
||||||
}
|
|
||||||
|
|
||||||
let winning_order = match op {
|
|
||||||
MinMaxOp::Min => cmp::Ordering::Less,
|
|
||||||
MinMaxOp::Max => cmp::Ordering::Greater,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result = 0;
|
|
||||||
for i in 1..children.len() {
|
|
||||||
let o = match children[i].partial_cmp(&children[result]) {
|
|
||||||
// We can't compare all the children, so we can't
|
|
||||||
// know which one will actually win. Bail out and
|
|
||||||
// keep ourselves as a min / max function.
|
|
||||||
//
|
|
||||||
// TODO: Maybe we could simplify compatible children,
|
|
||||||
// see https://github.com/w3c/csswg-drafts/issues/4756
|
|
||||||
None => return,
|
|
||||||
Some(o) => o,
|
|
||||||
};
|
|
||||||
|
|
||||||
if o == winning_order {
|
|
||||||
result = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
replace_self_with!(&mut children[result]);
|
|
||||||
},
|
|
||||||
Self::Sum(ref mut children_slot) => {
|
|
||||||
let mut sums_to_merge = SmallVec::<[_; 3]>::new();
|
|
||||||
let mut extra_kids = 0;
|
|
||||||
for (i, child) in children_slot.iter_mut().enumerate() {
|
|
||||||
child.simplify_and_sort_children();
|
|
||||||
if let Self::Sum(ref mut children) = *child {
|
|
||||||
extra_kids += children.len();
|
|
||||||
sums_to_merge.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we only have one kid, we've already simplified it, and it
|
|
||||||
// doesn't really matter whether it's a sum already or not, so
|
|
||||||
// lift it up and continue.
|
|
||||||
if children_slot.len() == 1 {
|
|
||||||
return replace_self_with!(&mut children_slot[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut children = mem::replace(children_slot, Box::new([])).into_vec();
|
|
||||||
|
|
||||||
if !sums_to_merge.is_empty() {
|
|
||||||
children.reserve(extra_kids - sums_to_merge.len());
|
|
||||||
// Merge all our nested sums, in reverse order so that the
|
|
||||||
// list indices are not invalidated.
|
|
||||||
for i in sums_to_merge.drain(..).rev() {
|
|
||||||
let kid_children = match children.swap_remove(i) {
|
|
||||||
Self::Sum(c) => c,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// This would be nicer with
|
|
||||||
// https://github.com/rust-lang/rust/issues/59878 fixed.
|
|
||||||
children.extend(kid_children.into_vec());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert!(children.len() >= 2, "Should still have multiple kids!");
|
|
||||||
|
|
||||||
// Sort by spec order.
|
|
||||||
children.sort_unstable_by_key(|c| c.calc_node_sort_key());
|
|
||||||
|
|
||||||
// NOTE: if the function returns true, by the docs of dedup_by,
|
|
||||||
// a is removed.
|
|
||||||
children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok());
|
|
||||||
|
|
||||||
if children.len() == 1 {
|
|
||||||
// If only one children remains, lift it up, and carry on.
|
|
||||||
replace_self_with!(&mut children[0]);
|
|
||||||
} else {
|
|
||||||
// Else put our simplified children back.
|
|
||||||
mem::replace(children_slot, children.into_boxed_slice());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::Length(ref mut len) => {
|
|
||||||
if let NoCalcLength::Absolute(ref mut absolute_length) = *len {
|
|
||||||
*absolute_length = AbsoluteLength::Px(absolute_length.to_px());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::Percentage(..) | Self::Angle(..) | Self::Time(..) | Self::Number(..) => {
|
|
||||||
// These are leaves already, nothing to do.
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl CalcNode {
|
||||||
/// Tries to parse a single element in the expression, that is, a
|
/// Tries to parse a single element in the expression, that is, a
|
||||||
/// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
|
/// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
|
||||||
/// `expected_unit`.
|
/// `expected_unit`.
|
||||||
|
@ -584,7 +382,7 @@ impl CalcNode {
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
let location = input.current_source_location();
|
let location = input.current_source_location();
|
||||||
match (input.next()?, expected_unit) {
|
match (input.next()?, expected_unit) {
|
||||||
(&Token::Number { value, .. }, _) => Ok(CalcNode::Number(value)),
|
(&Token::Number { value, .. }, _) => Ok(CalcNode::Leaf(Leaf::Number(value))),
|
||||||
(
|
(
|
||||||
&Token::Dimension {
|
&Token::Dimension {
|
||||||
value, ref unit, ..
|
value, ref unit, ..
|
||||||
|
@ -596,18 +394,22 @@ impl CalcNode {
|
||||||
value, ref unit, ..
|
value, ref unit, ..
|
||||||
},
|
},
|
||||||
CalcUnit::LengthPercentage,
|
CalcUnit::LengthPercentage,
|
||||||
) => NoCalcLength::parse_dimension(context, value, unit)
|
) => {
|
||||||
.map(CalcNode::Length)
|
match NoCalcLength::parse_dimension(context, value, unit) {
|
||||||
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
Ok(l) => Ok(CalcNode::Leaf(Leaf::Length(l))),
|
||||||
|
Err(()) => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
||||||
|
}
|
||||||
|
},
|
||||||
(
|
(
|
||||||
&Token::Dimension {
|
&Token::Dimension {
|
||||||
value, ref unit, ..
|
value, ref unit, ..
|
||||||
},
|
},
|
||||||
CalcUnit::Angle,
|
CalcUnit::Angle,
|
||||||
) => {
|
) => {
|
||||||
Angle::parse_dimension(value, unit, /* from_calc = */ true)
|
match Angle::parse_dimension(value, unit, /* from_calc = */ true) {
|
||||||
.map(CalcNode::Angle)
|
Ok(a) => Ok(CalcNode::Leaf(Leaf::Angle(a))),
|
||||||
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
Err(()) => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
&Token::Dimension {
|
&Token::Dimension {
|
||||||
|
@ -615,13 +417,14 @@ impl CalcNode {
|
||||||
},
|
},
|
||||||
CalcUnit::Time,
|
CalcUnit::Time,
|
||||||
) => {
|
) => {
|
||||||
Time::parse_dimension(value, unit, /* from_calc = */ true)
|
match Time::parse_dimension(value, unit, /* from_calc = */ true) {
|
||||||
.map(CalcNode::Time)
|
Ok(t) => Ok(CalcNode::Leaf(Leaf::Time(t))),
|
||||||
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
Err(()) => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(&Token::Percentage { unit_value, .. }, CalcUnit::LengthPercentage) |
|
(&Token::Percentage { unit_value, .. }, CalcUnit::LengthPercentage) |
|
||||||
(&Token::Percentage { unit_value, .. }, CalcUnit::Percentage) => {
|
(&Token::Percentage { unit_value, .. }, CalcUnit::Percentage) => {
|
||||||
Ok(CalcNode::Percentage(unit_value))
|
Ok(CalcNode::Leaf(Leaf::Percentage(unit_value)))
|
||||||
},
|
},
|
||||||
(&Token::ParenthesisBlock, _) => input.parse_nested_block(|input| {
|
(&Token::ParenthesisBlock, _) => input.parse_nested_block(|input| {
|
||||||
CalcNode::parse_argument(context, input, expected_unit)
|
CalcNode::parse_argument(context, input, expected_unit)
|
||||||
|
@ -811,12 +614,12 @@ impl CalcNode {
|
||||||
factor: CSSFloat,
|
factor: CSSFloat,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
match *self {
|
match *self {
|
||||||
CalcNode::Percentage(pct) => {
|
CalcNode::Leaf(Leaf::Percentage(pct)) => {
|
||||||
ret.percentage = Some(computed::Percentage(
|
ret.percentage = Some(computed::Percentage(
|
||||||
ret.percentage.map_or(0., |p| p.0) + pct * factor,
|
ret.percentage.map_or(0., |p| p.0) + pct * factor,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
CalcNode::Length(ref l) => match *l {
|
CalcNode::Leaf(Leaf::Length(ref l)) => match *l {
|
||||||
NoCalcLength::Absolute(abs) => {
|
NoCalcLength::Absolute(abs) => {
|
||||||
ret.absolute = Some(match ret.absolute {
|
ret.absolute = Some(match ret.absolute {
|
||||||
Some(value) => value + abs * factor,
|
Some(value) => value + abs * factor,
|
||||||
|
@ -862,7 +665,7 @@ impl CalcNode {
|
||||||
// FIXME(emilio): Implement min/max/clamp for length-percentage.
|
// FIXME(emilio): Implement min/max/clamp for length-percentage.
|
||||||
return Err(());
|
return Err(());
|
||||||
},
|
},
|
||||||
CalcNode::Angle(..) | CalcNode::Time(..) | CalcNode::Number(..) => return Err(()),
|
CalcNode::Leaf(..) => return Err(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1035,93 +838,4 @@ impl CalcNode {
|
||||||
Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_css_impl<W>(&self, dest: &mut CssWriter<W>, is_outermost: bool) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
let write_closing_paren = match *self {
|
|
||||||
Self::MinMax(_, op) => {
|
|
||||||
dest.write_str(match op {
|
|
||||||
MinMaxOp::Max => "max(",
|
|
||||||
MinMaxOp::Min => "min(",
|
|
||||||
})?;
|
|
||||||
true
|
|
||||||
},
|
|
||||||
Self::Clamp { .. } => {
|
|
||||||
dest.write_str("clamp(")?;
|
|
||||||
true
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
if is_outermost {
|
|
||||||
dest.write_str("calc(")?;
|
|
||||||
}
|
|
||||||
is_outermost
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
match *self {
|
|
||||||
Self::MinMax(ref children, _) => {
|
|
||||||
let mut first = true;
|
|
||||||
for child in &**children {
|
|
||||||
if !first {
|
|
||||||
dest.write_str(", ")?;
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
child.to_css_impl(dest, false)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::Sum(ref children) => {
|
|
||||||
let mut first = true;
|
|
||||||
for child in &**children {
|
|
||||||
if !first {
|
|
||||||
if child.is_simple_negative() {
|
|
||||||
dest.write_str(" - ")?;
|
|
||||||
let mut c = child.clone();
|
|
||||||
c.negate();
|
|
||||||
c.to_css(dest)?;
|
|
||||||
} else {
|
|
||||||
dest.write_str(" + ")?;
|
|
||||||
child.to_css(dest)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
first = false;
|
|
||||||
child.to_css_impl(dest, false)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::Clamp {
|
|
||||||
ref min,
|
|
||||||
ref center,
|
|
||||||
ref max,
|
|
||||||
} => {
|
|
||||||
min.to_css_impl(dest, false)?;
|
|
||||||
dest.write_str(", ")?;
|
|
||||||
center.to_css_impl(dest, false)?;
|
|
||||||
dest.write_str(", ")?;
|
|
||||||
max.to_css_impl(dest, false)?;
|
|
||||||
},
|
|
||||||
Self::Length(ref l) => l.to_css(dest)?,
|
|
||||||
Self::Number(ref n) => n.to_css(dest)?,
|
|
||||||
Self::Percentage(p) => crate::values::serialize_percentage(p, dest)?,
|
|
||||||
Self::Angle(ref a) => a.to_css(dest)?,
|
|
||||||
Self::Time(ref t) => t.to_css(dest)?,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if write_closing_paren {
|
|
||||||
dest.write_str(")")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToCss for CalcNode {
|
|
||||||
/// <https://drafts.csswg.org/css-values/#calc-serialize>
|
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
self.to_css_impl(dest, /* is_outermost = */ true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue