mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
style: Implement min() / max() / clamp() for simple css types behind a pref.
So for everything but <length> and <length-percentage>, which have more complex mechanics. The pref is off for now of course. Differential Revision: https://phabricator.services.mozilla.com/D60012
This commit is contained in:
parent
d74f90e3a7
commit
6fa03e1d04
1 changed files with 155 additions and 129 deletions
|
@ -20,9 +20,14 @@ use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKin
|
||||||
/// The name of the mathematical function that we're parsing.
|
/// The name of the mathematical function that we're parsing.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum MathFunction {
|
pub enum MathFunction {
|
||||||
/// `calc()`
|
/// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
|
||||||
Calc,
|
Calc,
|
||||||
// FIXME: min() / max() / clamp
|
/// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min
|
||||||
|
Min,
|
||||||
|
/// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max
|
||||||
|
Max,
|
||||||
|
/// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
|
||||||
|
Clamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node inside a `Calc` expression's AST.
|
/// A node inside a `Calc` expression's AST.
|
||||||
|
@ -46,6 +51,19 @@ pub enum CalcNode {
|
||||||
Mul(Box<CalcNode>, Box<CalcNode>),
|
Mul(Box<CalcNode>, Box<CalcNode>),
|
||||||
/// An expression of the form `x / y`
|
/// An expression of the form `x / y`
|
||||||
Div(Box<CalcNode>, Box<CalcNode>),
|
Div(Box<CalcNode>, Box<CalcNode>),
|
||||||
|
/// A `min()` function.
|
||||||
|
Min(Box<[CalcNode]>),
|
||||||
|
/// A `max()` function.
|
||||||
|
Max(Box<[CalcNode]>),
|
||||||
|
/// A `clamp()` function.
|
||||||
|
Clamp {
|
||||||
|
/// The minimum value.
|
||||||
|
min: Box<CalcNode>,
|
||||||
|
/// The central value.
|
||||||
|
center: Box<CalcNode>,
|
||||||
|
/// The maximum value.
|
||||||
|
max: Box<CalcNode>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An expected unit we intend to parse within a `calc()` expression.
|
/// An expected unit we intend to parse within a `calc()` expression.
|
||||||
|
@ -158,6 +176,80 @@ impl ToCss for CalcLengthPercentage {
|
||||||
|
|
||||||
impl SpecifiedValueInfo for CalcLengthPercentage {}
|
impl SpecifiedValueInfo for CalcLengthPercentage {}
|
||||||
|
|
||||||
|
macro_rules! impl_generic_to_type {
|
||||||
|
($self:ident, $self_variant:ident, $to_self:ident, $to_float:ident, $from_float:path) => {{
|
||||||
|
if let Self::$self_variant(ref v) = *$self {
|
||||||
|
return Ok(v.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match *$self {
|
||||||
|
Self::Sub(ref a, ref b) => $from_float(a.$to_self()?.$to_float() - b.$to_self()?.$to_float()),
|
||||||
|
Self::Sum(ref a, ref b) => $from_float(a.$to_self()?.$to_float() + b.$to_self()?.$to_float()),
|
||||||
|
Self::Mul(ref a, ref b) => match a.$to_self() {
|
||||||
|
Ok(lhs) => {
|
||||||
|
let rhs = b.to_number()?;
|
||||||
|
$from_float(lhs.$to_float() * rhs)
|
||||||
|
},
|
||||||
|
Err(..) => {
|
||||||
|
let lhs = a.to_number()?;
|
||||||
|
let rhs = b.$to_self()?;
|
||||||
|
$from_float(lhs * rhs.$to_float())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Self::Div(ref a, ref b) => {
|
||||||
|
let lhs = a.$to_self()?;
|
||||||
|
let rhs = b.to_number()?;
|
||||||
|
if rhs == 0. {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
$from_float(lhs.$to_float() / rhs)
|
||||||
|
},
|
||||||
|
Self::Clamp { ref min, ref center, ref max } => {
|
||||||
|
let min = min.$to_self()?;
|
||||||
|
let center = center.$to_self()?;
|
||||||
|
let max = max.$to_self()?;
|
||||||
|
|
||||||
|
// Equivalent to cmp::max(min, cmp::min(center, max))
|
||||||
|
//
|
||||||
|
// But preserving units when appropriate.
|
||||||
|
let mut result = center;
|
||||||
|
if result.$to_float() > max.$to_float() {
|
||||||
|
result = max;
|
||||||
|
}
|
||||||
|
if result.$to_float() < min.$to_float() {
|
||||||
|
result = min;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
},
|
||||||
|
Self::Min(ref nodes) => {
|
||||||
|
let mut min = nodes[0].$to_self()?;
|
||||||
|
for node in nodes.iter().skip(1) {
|
||||||
|
let candidate = node.$to_self()?;
|
||||||
|
if candidate.$to_float() < min.$to_float() {
|
||||||
|
min = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
min
|
||||||
|
},
|
||||||
|
Self::Max(ref nodes) => {
|
||||||
|
let mut max = nodes[0].$to_self()?;
|
||||||
|
for node in nodes.iter().skip(1) {
|
||||||
|
let candidate = node.$to_self()?;
|
||||||
|
if candidate.$to_float() > max.$to_float() {
|
||||||
|
max = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max
|
||||||
|
},
|
||||||
|
Self::Length(..) |
|
||||||
|
Self::Angle(..) |
|
||||||
|
Self::Time(..) |
|
||||||
|
Self::Percentage(..) |
|
||||||
|
Self::Number(..) => return Err(()),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
impl CalcNode {
|
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
|
||||||
|
@ -230,14 +322,45 @@ impl CalcNode {
|
||||||
fn parse<'i, 't>(
|
fn parse<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
_function: MathFunction,
|
function: MathFunction,
|
||||||
expected_unit: CalcUnit,
|
expected_unit: CalcUnit,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
// TODO: Do something different based on the function name. In
|
// TODO: Do something different based on the function name. In
|
||||||
// particular, for non-calc function we need to take a list of
|
// particular, for non-calc function we need to take a list of
|
||||||
// comma-separated arguments and such.
|
// comma-separated arguments and such.
|
||||||
input.parse_nested_block(|input| {
|
input.parse_nested_block(|input| {
|
||||||
Self::parse_argument(context, input, expected_unit)
|
match function {
|
||||||
|
MathFunction::Calc => Self::parse_argument(context, input, expected_unit),
|
||||||
|
MathFunction::Clamp => {
|
||||||
|
let min = Self::parse_argument(context, input, expected_unit)?;
|
||||||
|
input.expect_comma()?;
|
||||||
|
let center = Self::parse_argument(context, input, expected_unit)?;
|
||||||
|
input.expect_comma()?;
|
||||||
|
let max = Self::parse_argument(context, input, expected_unit)?;
|
||||||
|
Ok(Self::Clamp {
|
||||||
|
min: Box::new(min),
|
||||||
|
center: Box::new(center),
|
||||||
|
max: Box::new(max),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
MathFunction::Min |
|
||||||
|
MathFunction::Max => {
|
||||||
|
// TODO(emilio): The common case for parse_comma_separated
|
||||||
|
// is just one element, but for min / max is two, really...
|
||||||
|
//
|
||||||
|
// Consider adding an API to cssparser to specify the
|
||||||
|
// initial vector capacity?
|
||||||
|
let arguments = input.parse_comma_separated(|input| {
|
||||||
|
Self::parse_argument(context, input, expected_unit)
|
||||||
|
})?.into_boxed_slice();
|
||||||
|
|
||||||
|
Ok(match function {
|
||||||
|
MathFunction::Min => Self::Min(arguments),
|
||||||
|
MathFunction::Max => Self::Max(arguments),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,45 +451,13 @@ impl CalcNode {
|
||||||
clamping_mode: AllowedNumericType,
|
clamping_mode: AllowedNumericType,
|
||||||
) -> Result<CalcLengthPercentage, ()> {
|
) -> Result<CalcLengthPercentage, ()> {
|
||||||
let mut ret = CalcLengthPercentage {
|
let mut ret = CalcLengthPercentage {
|
||||||
clamping_mode: clamping_mode,
|
clamping_mode,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
self.add_length_or_percentage_to(&mut ret, 1.0)?;
|
self.add_length_or_percentage_to(&mut ret, 1.0)?;
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to simplify this expression into a `<percentage>` value.
|
|
||||||
fn to_percentage(&self) -> Result<CSSFloat, ()> {
|
|
||||||
Ok(match *self {
|
|
||||||
CalcNode::Percentage(percentage) => percentage,
|
|
||||||
CalcNode::Sub(ref a, ref b) => a.to_percentage()? - b.to_percentage()?,
|
|
||||||
CalcNode::Sum(ref a, ref b) => a.to_percentage()? + b.to_percentage()?,
|
|
||||||
CalcNode::Mul(ref a, ref b) => match a.to_percentage() {
|
|
||||||
Ok(lhs) => {
|
|
||||||
let rhs = b.to_number()?;
|
|
||||||
lhs * rhs
|
|
||||||
},
|
|
||||||
Err(..) => {
|
|
||||||
let lhs = a.to_number()?;
|
|
||||||
let rhs = b.to_percentage()?;
|
|
||||||
lhs * rhs
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CalcNode::Div(ref a, ref b) => {
|
|
||||||
let lhs = a.to_percentage()?;
|
|
||||||
let rhs = b.to_number()?;
|
|
||||||
if rhs == 0. {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
lhs / rhs
|
|
||||||
},
|
|
||||||
CalcNode::Number(..) |
|
|
||||||
CalcNode::Length(..) |
|
|
||||||
CalcNode::Angle(..) |
|
|
||||||
CalcNode::Time(..) => return Err(()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Puts this `<length>` or `<percentage>` into `ret`, or error.
|
/// Puts this `<length>` or `<percentage>` into `ret`, or error.
|
||||||
///
|
///
|
||||||
/// `factor` is the sign or multiplicative factor to account for the sign
|
/// `factor` is the sign or multiplicative factor to account for the sign
|
||||||
|
@ -443,6 +534,12 @@ impl CalcNode {
|
||||||
}
|
}
|
||||||
a.add_length_or_percentage_to(ret, factor / new_factor)?;
|
a.add_length_or_percentage_to(ret, factor / new_factor)?;
|
||||||
},
|
},
|
||||||
|
CalcNode::Max(..) |
|
||||||
|
CalcNode::Min(..) |
|
||||||
|
CalcNode::Clamp { .. } => {
|
||||||
|
// FIXME(emilio): Implement min/max/clamp for length-percentage.
|
||||||
|
return Err(())
|
||||||
|
},
|
||||||
CalcNode::Angle(..) | CalcNode::Time(..) | CalcNode::Number(..) => return Err(()),
|
CalcNode::Angle(..) | CalcNode::Time(..) | CalcNode::Number(..) => return Err(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,104 +548,22 @@ impl CalcNode {
|
||||||
|
|
||||||
/// Tries to simplify this expression into a `<time>` value.
|
/// Tries to simplify this expression into a `<time>` value.
|
||||||
fn to_time(&self) -> Result<Time, ()> {
|
fn to_time(&self) -> Result<Time, ()> {
|
||||||
Ok(match *self {
|
impl_generic_to_type!(self, Time, to_time, seconds, Time::from_calc)
|
||||||
CalcNode::Time(ref time) => time.clone(),
|
|
||||||
CalcNode::Sub(ref a, ref b) => {
|
|
||||||
let lhs = a.to_time()?;
|
|
||||||
let rhs = b.to_time()?;
|
|
||||||
Time::from_calc(lhs.seconds() - rhs.seconds())
|
|
||||||
},
|
|
||||||
CalcNode::Sum(ref a, ref b) => {
|
|
||||||
let lhs = a.to_time()?;
|
|
||||||
let rhs = b.to_time()?;
|
|
||||||
Time::from_calc(lhs.seconds() + rhs.seconds())
|
|
||||||
},
|
|
||||||
CalcNode::Mul(ref a, ref b) => match b.to_number() {
|
|
||||||
Ok(rhs) => {
|
|
||||||
let lhs = a.to_time()?;
|
|
||||||
Time::from_calc(lhs.seconds() * rhs)
|
|
||||||
},
|
|
||||||
Err(()) => {
|
|
||||||
let lhs = a.to_number()?;
|
|
||||||
let rhs = b.to_time()?;
|
|
||||||
Time::from_calc(lhs * rhs.seconds())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CalcNode::Div(ref a, ref b) => {
|
|
||||||
let lhs = a.to_time()?;
|
|
||||||
let rhs = b.to_number()?;
|
|
||||||
if rhs == 0. {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
Time::from_calc(lhs.seconds() / rhs)
|
|
||||||
},
|
|
||||||
CalcNode::Number(..) |
|
|
||||||
CalcNode::Length(..) |
|
|
||||||
CalcNode::Percentage(..) |
|
|
||||||
CalcNode::Angle(..) => return Err(()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to simplify this expression into an `Angle` value.
|
/// Tries to simplify this expression into an `Angle` value.
|
||||||
fn to_angle(&self) -> Result<Angle, ()> {
|
fn to_angle(&self) -> Result<Angle, ()> {
|
||||||
Ok(match *self {
|
impl_generic_to_type!(self, Angle, to_angle, degrees, Angle::from_calc)
|
||||||
CalcNode::Angle(ref angle) => angle.clone(),
|
|
||||||
CalcNode::Sub(ref a, ref b) => {
|
|
||||||
let lhs = a.to_angle()?;
|
|
||||||
let rhs = b.to_angle()?;
|
|
||||||
Angle::from_calc(lhs.degrees() - rhs.degrees())
|
|
||||||
},
|
|
||||||
CalcNode::Sum(ref a, ref b) => {
|
|
||||||
let lhs = a.to_angle()?;
|
|
||||||
let rhs = b.to_angle()?;
|
|
||||||
Angle::from_calc(lhs.degrees() + rhs.degrees())
|
|
||||||
},
|
|
||||||
CalcNode::Mul(ref a, ref b) => match a.to_angle() {
|
|
||||||
Ok(lhs) => {
|
|
||||||
let rhs = b.to_number()?;
|
|
||||||
Angle::from_calc(lhs.degrees() * rhs)
|
|
||||||
},
|
|
||||||
Err(..) => {
|
|
||||||
let lhs = a.to_number()?;
|
|
||||||
let rhs = b.to_angle()?;
|
|
||||||
Angle::from_calc(lhs * rhs.degrees())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CalcNode::Div(ref a, ref b) => {
|
|
||||||
let lhs = a.to_angle()?;
|
|
||||||
let rhs = b.to_number()?;
|
|
||||||
if rhs == 0. {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
Angle::from_calc(lhs.degrees() / rhs)
|
|
||||||
},
|
|
||||||
CalcNode::Number(..) |
|
|
||||||
CalcNode::Length(..) |
|
|
||||||
CalcNode::Percentage(..) |
|
|
||||||
CalcNode::Time(..) => return Err(()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to simplify this expression into a `<number>` value.
|
/// Tries to simplify this expression into a `<number>` value.
|
||||||
fn to_number(&self) -> Result<CSSFloat, ()> {
|
fn to_number(&self) -> Result<CSSFloat, ()> {
|
||||||
Ok(match *self {
|
impl_generic_to_type!(self, Number, to_number, clone, From::from)
|
||||||
CalcNode::Number(n) => n,
|
}
|
||||||
CalcNode::Sum(ref a, ref b) => a.to_number()? + b.to_number()?,
|
|
||||||
CalcNode::Sub(ref a, ref b) => a.to_number()? - b.to_number()?,
|
/// Tries to simplify this expression into a `<percentage>` value.
|
||||||
CalcNode::Mul(ref a, ref b) => a.to_number()? * b.to_number()?,
|
fn to_percentage(&self) -> Result<CSSFloat, ()> {
|
||||||
CalcNode::Div(ref a, ref b) => {
|
impl_generic_to_type!(self, Percentage, to_percentage, clone, From::from)
|
||||||
let lhs = a.to_number()?;
|
|
||||||
let rhs = b.to_number()?;
|
|
||||||
if rhs == 0. {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
lhs / rhs
|
|
||||||
},
|
|
||||||
CalcNode::Length(..) |
|
|
||||||
CalcNode::Percentage(..) |
|
|
||||||
CalcNode::Angle(..) |
|
|
||||||
CalcNode::Time(..) => return Err(()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a function name, and the location from where the token came from,
|
/// Given a function name, and the location from where the token came from,
|
||||||
|
@ -558,10 +573,21 @@ impl CalcNode {
|
||||||
name: &CowRcStr<'i>,
|
name: &CowRcStr<'i>,
|
||||||
location: cssparser::SourceLocation,
|
location: cssparser::SourceLocation,
|
||||||
) -> Result<MathFunction, ParseError<'i>> {
|
) -> Result<MathFunction, ParseError<'i>> {
|
||||||
if !name.eq_ignore_ascii_case("calc") {
|
// TODO(emilio): Unify below when the pref for math functions is gone.
|
||||||
|
if name.eq_ignore_ascii_case("calc") {
|
||||||
|
return Ok(MathFunction::Calc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !static_prefs::pref!("layout.css.comparison-functions.enabled") {
|
||||||
return Err(location.new_unexpected_token_error(Token::Function(name.clone())));
|
return Err(location.new_unexpected_token_error(Token::Function(name.clone())));
|
||||||
}
|
}
|
||||||
Ok(MathFunction::Calc)
|
|
||||||
|
Ok(match_ignore_ascii_case! { &*name,
|
||||||
|
"min" => MathFunction::Min,
|
||||||
|
"max" => MathFunction::Max,
|
||||||
|
"clamp" => MathFunction::Clamp,
|
||||||
|
_ => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience parsing function for integers.
|
/// Convenience parsing function for integers.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue