mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
style: Rewrite calc to be cleaner and support arbitrary expressions.
This improves Servo's calc support compliant with[1], and makes it cleaner and more straight-forward. [1]: https://github.com/w3c/csswg-drafts/issues/1241
This commit is contained in:
parent
36f26148e6
commit
3608dc8088
7 changed files with 638 additions and 596 deletions
547
components/style/values/specified/calc.rs
Normal file
547
components/style/values/specified/calc.rs
Normal file
|
@ -0,0 +1,547 @@
|
||||||
|
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! [Calc expressions][calc].
|
||||||
|
//!
|
||||||
|
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
|
||||||
|
|
||||||
|
use app_units::Au;
|
||||||
|
use cssparser::{Parser, Token};
|
||||||
|
use parser::ParserContext;
|
||||||
|
use std::ascii::AsciiExt;
|
||||||
|
use std::fmt;
|
||||||
|
use style_traits::ToCss;
|
||||||
|
use values::{CSSInteger, CSSFloat, HasViewportPercentage};
|
||||||
|
use values::specified::{Angle, Time};
|
||||||
|
use values::specified::length::{FontRelativeLength, NoCalcLength, ViewportPercentageLength};
|
||||||
|
|
||||||
|
/// A node inside a `Calc` expression's AST.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum CalcNode {
|
||||||
|
/// `<length>`
|
||||||
|
Length(NoCalcLength),
|
||||||
|
/// `<angle>`
|
||||||
|
Angle(Angle),
|
||||||
|
/// `<time>`
|
||||||
|
Time(Time),
|
||||||
|
/// `<percentage>`
|
||||||
|
Percentage(CSSFloat),
|
||||||
|
/// `<number>`
|
||||||
|
Number(CSSFloat),
|
||||||
|
/// An expression of the form `x + y`
|
||||||
|
Sum(Box<CalcNode>, Box<CalcNode>),
|
||||||
|
/// An expression of the form `x - y`
|
||||||
|
Sub(Box<CalcNode>, Box<CalcNode>),
|
||||||
|
/// An expression of the form `x * y`
|
||||||
|
Mul(Box<CalcNode>, Box<CalcNode>),
|
||||||
|
/// An expression of the form `x / y`
|
||||||
|
Div(Box<CalcNode>, Box<CalcNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum CalcUnit {
|
||||||
|
/// `<number>`
|
||||||
|
Number,
|
||||||
|
/// `<integer>`
|
||||||
|
Integer,
|
||||||
|
/// `<length>`
|
||||||
|
Length,
|
||||||
|
/// `<length> | <percentage>`
|
||||||
|
LengthOrPercentage,
|
||||||
|
/// `<angle>`
|
||||||
|
Angle,
|
||||||
|
/// `<time>`
|
||||||
|
Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct to hold a simplified `<length>` or `<percentage>` expression.
|
||||||
|
#[derive(Clone, PartialEq, Copy, Debug, Default)]
|
||||||
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct CalcLengthOrPercentage {
|
||||||
|
pub absolute: Option<Au>,
|
||||||
|
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<CSSFloat>,
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
pub mozmm: Option<CSSFloat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasViewportPercentage for CalcLengthOrPercentage {
|
||||||
|
fn has_viewport_percentage(&self) -> bool {
|
||||||
|
self.vw.is_some() || self.vh.is_some() ||
|
||||||
|
self.vmin.is_some() || self.vmax.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToCss for CalcLengthOrPercentage {
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
||||||
|
let mut first_value = true;
|
||||||
|
macro_rules! first_value_check {
|
||||||
|
() => {
|
||||||
|
if !first_value {
|
||||||
|
try!(dest.write_str(" + "));
|
||||||
|
} else {
|
||||||
|
first_value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! serialize {
|
||||||
|
( $( $val:ident ),* ) => {
|
||||||
|
$(
|
||||||
|
if let Some(val) = self.$val {
|
||||||
|
first_value_check!();
|
||||||
|
try!(val.to_css(dest));
|
||||||
|
try!(dest.write_str(stringify!($val)));
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(dest.write_str("calc("));
|
||||||
|
|
||||||
|
serialize!(ch, em, ex, rem, vh, vmax, vmin, vw);
|
||||||
|
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
{
|
||||||
|
serialize!(mozmm);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(val) = self.absolute {
|
||||||
|
first_value_check!();
|
||||||
|
try!(val.to_css(dest));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(val) = self.percentage {
|
||||||
|
first_value_check!();
|
||||||
|
try!(write!(dest, "{}%", val * 100.));
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(dest, ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CalcNode {
|
||||||
|
/// Tries to parse a single element in the expression, that is, a
|
||||||
|
/// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
|
||||||
|
/// `expected_unit`.
|
||||||
|
///
|
||||||
|
/// May return a "complex" `CalcNode`, in the presence of a parenthesized
|
||||||
|
/// expression, for example.
|
||||||
|
fn parse_one(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser,
|
||||||
|
expected_unit: CalcUnit)
|
||||||
|
-> Result<Self, ()>
|
||||||
|
{
|
||||||
|
match (try!(input.next()), expected_unit) {
|
||||||
|
(Token::Number(ref value), _) => Ok(CalcNode::Number(value.value)),
|
||||||
|
(Token::Dimension(ref value, ref unit), CalcUnit::Length) |
|
||||||
|
(Token::Dimension(ref value, ref unit), CalcUnit::LengthOrPercentage) => {
|
||||||
|
NoCalcLength::parse_dimension(context, value.value, unit)
|
||||||
|
.map(CalcNode::Length)
|
||||||
|
}
|
||||||
|
(Token::Dimension(ref value, ref unit), CalcUnit::Angle) => {
|
||||||
|
Angle::parse_dimension(value.value,
|
||||||
|
unit,
|
||||||
|
/* from_calc = */ true)
|
||||||
|
.map(CalcNode::Angle)
|
||||||
|
}
|
||||||
|
(Token::Dimension(ref value, ref unit), CalcUnit::Time) => {
|
||||||
|
Time::parse_dimension(value.value,
|
||||||
|
unit,
|
||||||
|
/* from_calc = */ true)
|
||||||
|
.map(CalcNode::Time)
|
||||||
|
}
|
||||||
|
(Token::Percentage(ref value), CalcUnit::LengthOrPercentage) => {
|
||||||
|
Ok(CalcNode::Percentage(value.unit_value))
|
||||||
|
}
|
||||||
|
(Token::ParenthesisBlock, _) => {
|
||||||
|
input.parse_nested_block(|i| {
|
||||||
|
CalcNode::parse(context, i, expected_unit)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => {
|
||||||
|
input.parse_nested_block(|i| {
|
||||||
|
CalcNode::parse(context, i, expected_unit)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a top-level `calc` expression, with all nested sub-expressions.
|
||||||
|
///
|
||||||
|
/// This is in charge of parsing, for example, `2 + 3 * 100%`.
|
||||||
|
fn parse(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser,
|
||||||
|
expected_unit: CalcUnit)
|
||||||
|
-> Result<Self, ()>
|
||||||
|
{
|
||||||
|
let mut root = Self::parse_product(context, input, expected_unit)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let position = input.position();
|
||||||
|
match input.next_including_whitespace() {
|
||||||
|
Ok(Token::WhiteSpace(_)) => {
|
||||||
|
if input.is_exhausted() {
|
||||||
|
break; // allow trailing whitespace
|
||||||
|
}
|
||||||
|
match input.next()? {
|
||||||
|
Token::Delim('+') => {
|
||||||
|
let rhs =
|
||||||
|
Self::parse_product(context, input, expected_unit)?;
|
||||||
|
let new_root =
|
||||||
|
CalcNode::Sum(Box::new(root), Box::new(rhs));
|
||||||
|
root = new_root;
|
||||||
|
}
|
||||||
|
Token::Delim('-') => {
|
||||||
|
let rhs =
|
||||||
|
Self::parse_product(context, input, expected_unit)?;
|
||||||
|
let new_root =
|
||||||
|
CalcNode::Sub(Box::new(root), Box::new(rhs));
|
||||||
|
root = new_root;
|
||||||
|
}
|
||||||
|
_ => return Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
input.reset(position);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a top-level `calc` expression, and all the products that may
|
||||||
|
/// follow, and stop as soon as a non-product expression is found.
|
||||||
|
///
|
||||||
|
/// This should parse correctly:
|
||||||
|
///
|
||||||
|
/// * `2`
|
||||||
|
/// * `2 * 2`
|
||||||
|
/// * `2 * 2 + 2` (but will leave the `+ 2` unparsed).
|
||||||
|
///
|
||||||
|
fn parse_product(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser,
|
||||||
|
expected_unit: CalcUnit)
|
||||||
|
-> Result<Self, ()>
|
||||||
|
{
|
||||||
|
let mut root = Self::parse_one(context, input, expected_unit)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let position = input.position();
|
||||||
|
match input.next() {
|
||||||
|
Ok(Token::Delim('*')) => {
|
||||||
|
let rhs = Self::parse_one(context, input, expected_unit)?;
|
||||||
|
let new_root = CalcNode::Mul(Box::new(root), Box::new(rhs));
|
||||||
|
root = new_root;
|
||||||
|
}
|
||||||
|
// TODO(emilio): Figure out why the `Integer` check.
|
||||||
|
Ok(Token::Delim('/')) if expected_unit != CalcUnit::Integer => {
|
||||||
|
let rhs = Self::parse_one(context, input, expected_unit)?;
|
||||||
|
let new_root = CalcNode::Div(Box::new(root), Box::new(rhs));
|
||||||
|
root = new_root;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
input.reset(position);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to simplify this expression into a `<length>` or `<percentage`>
|
||||||
|
/// value.
|
||||||
|
fn to_length_or_percentage(&self) -> Result<CalcLengthOrPercentage, ()> {
|
||||||
|
let mut ret = CalcLengthOrPercentage::default();
|
||||||
|
self.add_length_or_percentage_to(&mut ret, 1.0)?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 CalcLengthOrPercentage,
|
||||||
|
factor: CSSFloat)
|
||||||
|
-> Result<(), ()>
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
CalcNode::Percentage(pct) => {
|
||||||
|
ret.percentage = Some(ret.percentage.unwrap_or(0.) + pct * factor)
|
||||||
|
}
|
||||||
|
CalcNode::Length(ref l) => {
|
||||||
|
match *l {
|
||||||
|
NoCalcLength::Absolute(abs) => {
|
||||||
|
ret.absolute = Some(
|
||||||
|
ret.absolute.unwrap_or(Au(0)) +
|
||||||
|
Au::from(abs).scale_by(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.em.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!(),
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
NoCalcLength::Physical(physical) => {
|
||||||
|
ret.mozmm = Some(ret.mozmm.unwrap_or(0.) + physical.0 * factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CalcNode::Sub(ref a, ref b) => {
|
||||||
|
a.add_length_or_percentage_to(ret, factor)?;
|
||||||
|
b.add_length_or_percentage_to(ret, factor * -1.0)?;
|
||||||
|
}
|
||||||
|
CalcNode::Sum(ref a, ref b) => {
|
||||||
|
a.add_length_or_percentage_to(ret, factor)?;
|
||||||
|
b.add_length_or_percentage_to(ret, factor)?;
|
||||||
|
}
|
||||||
|
CalcNode::Mul(ref a, ref b) => {
|
||||||
|
match b.to_number() {
|
||||||
|
Ok(rhs) => {
|
||||||
|
a.add_length_or_percentage_to(ret, factor * rhs)?;
|
||||||
|
}
|
||||||
|
Err(..) => {
|
||||||
|
let lhs = a.to_number()?;
|
||||||
|
b.add_length_or_percentage_to(ret, factor * lhs)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CalcNode::Div(ref a, ref b) => {
|
||||||
|
let new_factor = b.to_number()?;
|
||||||
|
if new_factor == 0. {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
a.add_length_or_percentage_to(ret, factor / new_factor)?;
|
||||||
|
}
|
||||||
|
CalcNode::Angle(..) |
|
||||||
|
CalcNode::Time(..) |
|
||||||
|
CalcNode::Number(..) => return Err(()),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to simplify this expression into a `<time>` value.
|
||||||
|
fn to_time(&self) -> Result<Time, ()> {
|
||||||
|
Ok(match *self {
|
||||||
|
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.
|
||||||
|
fn to_angle(&self) -> Result<Angle, ()> {
|
||||||
|
Ok(match *self {
|
||||||
|
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.radians() - rhs.radians())
|
||||||
|
}
|
||||||
|
CalcNode::Sum(ref a, ref b) => {
|
||||||
|
let lhs = a.to_angle()?;
|
||||||
|
let rhs = b.to_angle()?;
|
||||||
|
Angle::from_calc(lhs.radians() + rhs.radians())
|
||||||
|
}
|
||||||
|
CalcNode::Mul(ref a, ref b) => {
|
||||||
|
match a.to_angle() {
|
||||||
|
Ok(lhs) => {
|
||||||
|
let rhs = b.to_number()?;
|
||||||
|
Angle::from_calc(lhs.radians() * rhs)
|
||||||
|
}
|
||||||
|
Err(..) => {
|
||||||
|
let lhs = a.to_number()?;
|
||||||
|
let rhs = b.to_angle()?;
|
||||||
|
Angle::from_calc(lhs * rhs.radians())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.radians() / rhs)
|
||||||
|
}
|
||||||
|
CalcNode::Number(..) |
|
||||||
|
CalcNode::Length(..) |
|
||||||
|
CalcNode::Percentage(..) |
|
||||||
|
CalcNode::Time(..) => return Err(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to simplify this expression into a `<number>` value.
|
||||||
|
fn to_number(&self) -> Result<CSSFloat, ()> {
|
||||||
|
Ok(match *self {
|
||||||
|
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()?
|
||||||
|
}
|
||||||
|
CalcNode::Mul(ref a, ref b) => {
|
||||||
|
a.to_number()? * b.to_number()?
|
||||||
|
}
|
||||||
|
CalcNode::Div(ref a, ref b) => {
|
||||||
|
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(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience parsing function for integers.
|
||||||
|
pub fn parse_integer(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser)
|
||||||
|
-> Result<CSSInteger, ()>
|
||||||
|
{
|
||||||
|
Self::parse(context, input, CalcUnit::Integer)?
|
||||||
|
.to_number()
|
||||||
|
.map(|n| n as CSSInteger)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience parsing function for `<length> | <percentage>`.
|
||||||
|
pub fn parse_length_or_percentage(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser)
|
||||||
|
-> Result<CalcLengthOrPercentage, ()>
|
||||||
|
{
|
||||||
|
Self::parse(context, input, CalcUnit::LengthOrPercentage)?
|
||||||
|
.to_length_or_percentage()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience parsing function for `<length>`.
|
||||||
|
pub fn parse_length(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser)
|
||||||
|
-> Result<CalcLengthOrPercentage, ()>
|
||||||
|
{
|
||||||
|
Self::parse(context, input, CalcUnit::Length)?
|
||||||
|
.to_length_or_percentage()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience parsing function for `<number>`.
|
||||||
|
pub fn parse_number(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser)
|
||||||
|
-> Result<CSSFloat, ()>
|
||||||
|
{
|
||||||
|
Self::parse(context, input, CalcUnit::Number)?
|
||||||
|
.to_number()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience parsing function for `<angle>`.
|
||||||
|
pub fn parse_angle(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser)
|
||||||
|
-> Result<Angle, ()>
|
||||||
|
{
|
||||||
|
Self::parse(context, input, CalcUnit::Angle)?
|
||||||
|
.to_angle()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience parsing function for `<time>`.
|
||||||
|
pub fn parse_time(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser)
|
||||||
|
-> Result<Time, ()>
|
||||||
|
{
|
||||||
|
Self::parse(context, input, CalcUnit::Time)?
|
||||||
|
.to_time()
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,11 +17,13 @@ use std::ops::Mul;
|
||||||
use style_traits::ToCss;
|
use style_traits::ToCss;
|
||||||
use style_traits::values::specified::AllowedLengthType;
|
use style_traits::values::specified::AllowedLengthType;
|
||||||
use stylesheets::CssRuleType;
|
use stylesheets::CssRuleType;
|
||||||
use super::{AllowQuirks, Angle, Number, SimplifiedValueNode, SimplifiedSumNode, Time, ToComputedValue};
|
use super::{AllowQuirks, Number, ToComputedValue};
|
||||||
use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal};
|
use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal};
|
||||||
use values::ExtremumLength;
|
use values::ExtremumLength;
|
||||||
use values::computed::{ComputedValueAsSpecified, Context};
|
use values::computed::{ComputedValueAsSpecified, Context};
|
||||||
|
use values::specified::calc::CalcNode;
|
||||||
|
|
||||||
|
pub use values::specified::calc::CalcLengthOrPercentage;
|
||||||
pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
|
pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
|
||||||
pub use super::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
|
pub use super::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
|
||||||
pub use super::image::{SizeKeyword, VerticalDirection};
|
pub use super::image::{SizeKeyword, VerticalDirection};
|
||||||
|
@ -637,7 +639,10 @@ impl Length {
|
||||||
},
|
},
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") =>
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") =>
|
||||||
input.parse_nested_block(|input| {
|
input.parse_nested_block(|input| {
|
||||||
CalcLengthOrPercentage::parse_length(context, input, num_context)
|
CalcNode::parse_length(context, input)
|
||||||
|
.map(|calc| {
|
||||||
|
Length::Calc(num_context, Box::new(calc))
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
|
@ -700,432 +705,6 @@ impl<T: Parse> Either<Length, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A calc sum expression node.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CalcSumNode {
|
|
||||||
/// The products of this node.
|
|
||||||
pub products: Vec<CalcProductNode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A calc product expression node.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CalcProductNode {
|
|
||||||
/// The values inside this product node.
|
|
||||||
values: Vec<CalcValueNode>
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A value inside a `Calc` expression.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub enum CalcValueNode {
|
|
||||||
Length(NoCalcLength),
|
|
||||||
Angle(CSSFloat),
|
|
||||||
Time(CSSFloat),
|
|
||||||
Percentage(CSSFloat),
|
|
||||||
Number(CSSFloat),
|
|
||||||
Sum(Box<CalcSumNode>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub enum CalcUnit {
|
|
||||||
Number,
|
|
||||||
Integer,
|
|
||||||
Length,
|
|
||||||
LengthOrPercentage,
|
|
||||||
Angle,
|
|
||||||
Time,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Copy, Debug, Default)]
|
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub struct CalcLengthOrPercentage {
|
|
||||||
pub absolute: Option<Au>,
|
|
||||||
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<CSSFloat>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CalcLengthOrPercentage {
|
|
||||||
/// Parse a calc sum node.
|
|
||||||
pub fn parse_sum(context: &ParserContext, input: &mut Parser, expected_unit: CalcUnit) -> Result<CalcSumNode, ()> {
|
|
||||||
let mut products = Vec::new();
|
|
||||||
products.push(try!(CalcLengthOrPercentage::parse_product(context, input, expected_unit)));
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let position = input.position();
|
|
||||||
match input.next_including_whitespace() {
|
|
||||||
Ok(Token::WhiteSpace(_)) => {
|
|
||||||
if input.is_exhausted() {
|
|
||||||
break; // allow trailing whitespace
|
|
||||||
}
|
|
||||||
match input.next() {
|
|
||||||
Ok(Token::Delim('+')) => {
|
|
||||||
products.push(try!(CalcLengthOrPercentage::parse_product(context, input, expected_unit)));
|
|
||||||
}
|
|
||||||
Ok(Token::Delim('-')) => {
|
|
||||||
let mut right = try!(CalcLengthOrPercentage::parse_product(context, input, expected_unit));
|
|
||||||
right.values.push(CalcValueNode::Number(-1.));
|
|
||||||
products.push(right);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
input.reset(position);
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(CalcSumNode { products: products })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_product(context: &ParserContext, input: &mut Parser, expected_unit: CalcUnit)
|
|
||||||
-> Result<CalcProductNode, ()> {
|
|
||||||
let mut values = Vec::new();
|
|
||||||
values.push(try!(CalcLengthOrPercentage::parse_value(context, input, expected_unit)));
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let position = input.position();
|
|
||||||
match input.next() {
|
|
||||||
Ok(Token::Delim('*')) => {
|
|
||||||
values.push(try!(CalcLengthOrPercentage::parse_value(context, input, expected_unit)));
|
|
||||||
}
|
|
||||||
Ok(Token::Delim('/')) if expected_unit != CalcUnit::Integer => {
|
|
||||||
if let Ok(Token::Number(ref value)) = input.next() {
|
|
||||||
if value.value == 0. {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
values.push(CalcValueNode::Number(1. / value.value));
|
|
||||||
} else {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
input.reset(position);
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CalcProductNode { values: values })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_value(context: &ParserContext, input: &mut Parser, expected_unit: CalcUnit) -> Result<CalcValueNode, ()> {
|
|
||||||
match (try!(input.next()), expected_unit) {
|
|
||||||
(Token::Number(ref value), _) => Ok(CalcValueNode::Number(value.value)),
|
|
||||||
(Token::Dimension(ref value, ref unit), CalcUnit::Length) |
|
|
||||||
(Token::Dimension(ref value, ref unit), CalcUnit::LengthOrPercentage) => {
|
|
||||||
NoCalcLength::parse_dimension(context, value.value, unit).map(CalcValueNode::Length)
|
|
||||||
}
|
|
||||||
(Token::Dimension(ref value, ref unit), CalcUnit::Angle) => {
|
|
||||||
Angle::parse_dimension(value.value, unit).map(|angle| {
|
|
||||||
CalcValueNode::Angle(angle.radians())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
(Token::Dimension(ref value, ref unit), CalcUnit::Time) => {
|
|
||||||
Time::parse_dimension(value.value, unit).map(|time| {
|
|
||||||
CalcValueNode::Time(time.seconds())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
(Token::Percentage(ref value), CalcUnit::LengthOrPercentage) =>
|
|
||||||
Ok(CalcValueNode::Percentage(value.unit_value)),
|
|
||||||
(Token::ParenthesisBlock, _) => {
|
|
||||||
input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(context, i, expected_unit))
|
|
||||||
.map(|result| CalcValueNode::Sum(Box::new(result)))
|
|
||||||
},
|
|
||||||
(Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => {
|
|
||||||
input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(context, i, expected_unit))
|
|
||||||
.map(|result| CalcValueNode::Sum(Box::new(result)))
|
|
||||||
}
|
|
||||||
_ => Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn simplify_value_to_number(node: &CalcValueNode) -> Option<CSSFloat> {
|
|
||||||
match *node {
|
|
||||||
CalcValueNode::Number(number) => Some(number),
|
|
||||||
CalcValueNode::Sum(ref sum) => CalcLengthOrPercentage::simplify_sum_to_number(sum),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn simplify_sum_to_number(node: &CalcSumNode) -> Option<CSSFloat> {
|
|
||||||
let mut sum = 0.;
|
|
||||||
for ref product in &node.products {
|
|
||||||
match CalcLengthOrPercentage::simplify_product_to_number(product) {
|
|
||||||
Some(number) => sum += number,
|
|
||||||
_ => return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(sum)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn simplify_product_to_number(node: &CalcProductNode) -> Option<CSSFloat> {
|
|
||||||
let mut product = 1.;
|
|
||||||
for ref value in &node.values {
|
|
||||||
match CalcLengthOrPercentage::simplify_value_to_number(value) {
|
|
||||||
Some(number) => product *= number,
|
|
||||||
_ => return None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn simplify_products_in_sum(node: &CalcSumNode) -> Result<SimplifiedValueNode, ()> {
|
|
||||||
let mut simplified = Vec::new();
|
|
||||||
for product in &node.products {
|
|
||||||
match try!(CalcLengthOrPercentage::simplify_product(product)) {
|
|
||||||
SimplifiedValueNode::Sum(ref sum) => simplified.extend_from_slice(&sum.values),
|
|
||||||
val => simplified.push(val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if simplified.len() == 1 {
|
|
||||||
Ok(simplified[0].clone())
|
|
||||||
} else {
|
|
||||||
Ok(SimplifiedValueNode::Sum(Box::new(SimplifiedSumNode { values: simplified })))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub fn simplify_product(node: &CalcProductNode) -> Result<SimplifiedValueNode, ()> {
|
|
||||||
let mut multiplier = 1.;
|
|
||||||
let mut node_with_unit = None;
|
|
||||||
for node in &node.values {
|
|
||||||
match CalcLengthOrPercentage::simplify_value_to_number(&node) {
|
|
||||||
Some(number) => multiplier *= number,
|
|
||||||
_ if node_with_unit.is_none() => {
|
|
||||||
node_with_unit = Some(match *node {
|
|
||||||
CalcValueNode::Sum(ref sum) =>
|
|
||||||
try!(CalcLengthOrPercentage::simplify_products_in_sum(sum)),
|
|
||||||
CalcValueNode::Length(ref l) => SimplifiedValueNode::Length(l.clone()),
|
|
||||||
CalcValueNode::Angle(a) => SimplifiedValueNode::Angle(a),
|
|
||||||
CalcValueNode::Time(t) => SimplifiedValueNode::Time(t),
|
|
||||||
CalcValueNode::Percentage(p) => SimplifiedValueNode::Percentage(p),
|
|
||||||
_ => unreachable!("Numbers should have been handled by simplify_value_to_nubmer")
|
|
||||||
})
|
|
||||||
},
|
|
||||||
_ => return Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match node_with_unit {
|
|
||||||
None => Ok(SimplifiedValueNode::Number(multiplier)),
|
|
||||||
Some(ref value) => Ok(value * multiplier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_length(context: &ParserContext,
|
|
||||||
input: &mut Parser,
|
|
||||||
num_context: AllowedLengthType) -> Result<Length, ()> {
|
|
||||||
CalcLengthOrPercentage::parse(context, input, CalcUnit::Length).map(|calc| {
|
|
||||||
Length::Calc(num_context, Box::new(calc))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_length_or_percentage(context: &ParserContext, input: &mut Parser) -> Result<CalcLengthOrPercentage, ()> {
|
|
||||||
CalcLengthOrPercentage::parse(context, input, CalcUnit::LengthOrPercentage)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub fn parse(context: &ParserContext,
|
|
||||||
input: &mut Parser,
|
|
||||||
expected_unit: CalcUnit) -> Result<CalcLengthOrPercentage, ()> {
|
|
||||||
let ast = try!(CalcLengthOrPercentage::parse_sum(context, input, expected_unit));
|
|
||||||
|
|
||||||
let mut simplified = Vec::new();
|
|
||||||
for ref node in ast.products {
|
|
||||||
match try!(CalcLengthOrPercentage::simplify_product(node)) {
|
|
||||||
SimplifiedValueNode::Sum(sum) => simplified.extend_from_slice(&sum.values),
|
|
||||||
value => simplified.push(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut absolute = None;
|
|
||||||
let mut vw = None;
|
|
||||||
let mut vh = None;
|
|
||||||
let mut vmax = None;
|
|
||||||
let mut vmin = None;
|
|
||||||
let mut em = None;
|
|
||||||
let mut ex = None;
|
|
||||||
let mut ch = None;
|
|
||||||
let mut rem = None;
|
|
||||||
let mut percentage = None;
|
|
||||||
|
|
||||||
for value in simplified {
|
|
||||||
match value {
|
|
||||||
SimplifiedValueNode::Percentage(p) =>
|
|
||||||
percentage = Some(percentage.unwrap_or(0.) + p),
|
|
||||||
SimplifiedValueNode::Length(NoCalcLength::Absolute(length)) =>
|
|
||||||
absolute = Some(absolute.unwrap_or(0.) + Au::from(length).to_f32_px()),
|
|
||||||
SimplifiedValueNode::Length(NoCalcLength::ViewportPercentage(v)) =>
|
|
||||||
match v {
|
|
||||||
ViewportPercentageLength::Vw(val) =>
|
|
||||||
vw = Some(vw.unwrap_or(0.) + val),
|
|
||||||
ViewportPercentageLength::Vh(val) =>
|
|
||||||
vh = Some(vh.unwrap_or(0.) + val),
|
|
||||||
ViewportPercentageLength::Vmin(val) =>
|
|
||||||
vmin = Some(vmin.unwrap_or(0.) + val),
|
|
||||||
ViewportPercentageLength::Vmax(val) =>
|
|
||||||
vmax = Some(vmax.unwrap_or(0.) + val),
|
|
||||||
},
|
|
||||||
SimplifiedValueNode::Length(NoCalcLength::FontRelative(f)) =>
|
|
||||||
match f {
|
|
||||||
FontRelativeLength::Em(val) =>
|
|
||||||
em = Some(em.unwrap_or(0.) + val),
|
|
||||||
FontRelativeLength::Ex(val) =>
|
|
||||||
ex = Some(ex.unwrap_or(0.) + val),
|
|
||||||
FontRelativeLength::Ch(val) =>
|
|
||||||
ch = Some(ch.unwrap_or(0.) + val),
|
|
||||||
FontRelativeLength::Rem(val) =>
|
|
||||||
rem = Some(rem.unwrap_or(0.) + val),
|
|
||||||
},
|
|
||||||
// TODO Add support for top level number in calc(). See servo/servo#14421.
|
|
||||||
_ => return Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CalcLengthOrPercentage {
|
|
||||||
absolute: absolute.map(Au::from_f32_px),
|
|
||||||
vw: vw,
|
|
||||||
vh: vh,
|
|
||||||
vmax: vmax,
|
|
||||||
vmin: vmin,
|
|
||||||
em: em,
|
|
||||||
ex: ex,
|
|
||||||
ch: ch,
|
|
||||||
rem: rem,
|
|
||||||
percentage: percentage,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub fn parse_time(context: &ParserContext, input: &mut Parser) -> Result<Time, ()> {
|
|
||||||
let ast = try!(CalcLengthOrPercentage::parse_sum(context, input, CalcUnit::Time));
|
|
||||||
|
|
||||||
let mut simplified = Vec::new();
|
|
||||||
for ref node in ast.products {
|
|
||||||
match try!(CalcLengthOrPercentage::simplify_product(node)) {
|
|
||||||
SimplifiedValueNode::Sum(sum) => simplified.extend_from_slice(&sum.values),
|
|
||||||
value => simplified.push(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut time = None;
|
|
||||||
|
|
||||||
for value in simplified {
|
|
||||||
match value {
|
|
||||||
SimplifiedValueNode::Time(val) =>
|
|
||||||
time = Some(time.unwrap_or(0.) + val),
|
|
||||||
_ => return Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match time {
|
|
||||||
Some(time) => Ok(Time::from_calc(time)),
|
|
||||||
_ => Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub fn parse_angle(context: &ParserContext, input: &mut Parser) -> Result<Angle, ()> {
|
|
||||||
let ast = try!(CalcLengthOrPercentage::parse_sum(context, input, CalcUnit::Angle));
|
|
||||||
|
|
||||||
let mut simplified = Vec::new();
|
|
||||||
for ref node in ast.products {
|
|
||||||
match try!(CalcLengthOrPercentage::simplify_product(node)) {
|
|
||||||
SimplifiedValueNode::Sum(sum) => simplified.extend_from_slice(&sum.values),
|
|
||||||
value => simplified.push(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut angle = None;
|
|
||||||
let mut number = None;
|
|
||||||
|
|
||||||
for value in simplified {
|
|
||||||
match value {
|
|
||||||
SimplifiedValueNode::Angle(val) => {
|
|
||||||
angle = Some(angle.unwrap_or(0.) + val)
|
|
||||||
}
|
|
||||||
// TODO(emilio): This `Number` logic looks fishy.
|
|
||||||
//
|
|
||||||
// In particular, this allows calc(2 - 2) to parse as an
|
|
||||||
// `Angle`, which doesn't seem desired to me.
|
|
||||||
SimplifiedValueNode::Number(val) => {
|
|
||||||
number = Some(number.unwrap_or(0.) + val)
|
|
||||||
}
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match (angle, number) {
|
|
||||||
(Some(angle), None) => Ok(Angle::from_calc(angle)),
|
|
||||||
(None, Some(value)) if value == 0. => Ok(Angle::from_calc(0.)),
|
|
||||||
_ => Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasViewportPercentage for CalcLengthOrPercentage {
|
|
||||||
fn has_viewport_percentage(&self) -> bool {
|
|
||||||
self.vw.is_some() || self.vh.is_some() ||
|
|
||||||
self.vmin.is_some() || self.vmax.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToCss for CalcLengthOrPercentage {
|
|
||||||
#[allow(unused_assignments)]
|
|
||||||
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
|
|
||||||
let mut first_value = true;
|
|
||||||
macro_rules! first_value_check {
|
|
||||||
() => {
|
|
||||||
if !first_value {
|
|
||||||
try!(dest.write_str(" + "));
|
|
||||||
} else {
|
|
||||||
first_value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! serialize {
|
|
||||||
( $( $val:ident ),* ) => {
|
|
||||||
$(
|
|
||||||
if let Some(val) = self.$val {
|
|
||||||
first_value_check!();
|
|
||||||
try!(val.to_css(dest));
|
|
||||||
try!(dest.write_str(stringify!($val)));
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try!(write!(dest, "calc("));
|
|
||||||
|
|
||||||
serialize!(ch, em, ex, rem, vh, vmax, vmin, vw);
|
|
||||||
if let Some(val) = self.absolute {
|
|
||||||
first_value_check!();
|
|
||||||
try!(val.to_css(dest));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(val) = self.percentage {
|
|
||||||
first_value_check!();
|
|
||||||
try!(write!(dest, "{}%", val * 100.));
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(dest, ")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A percentage value.
|
/// A percentage value.
|
||||||
///
|
///
|
||||||
/// [0 .. 100%] maps to [0.0 .. 1.0]
|
/// [0 .. 100%] maps to [0.0 .. 1.0]
|
||||||
|
@ -1177,8 +756,6 @@ impl Parse for Percentage {
|
||||||
impl ComputedValueAsSpecified for Percentage {}
|
impl ComputedValueAsSpecified for Percentage {}
|
||||||
|
|
||||||
/// A length or a percentage value.
|
/// A length or a percentage value.
|
||||||
///
|
|
||||||
/// TODO(emilio): Does this make any sense vs. CalcLengthOrPercentage?
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -1254,7 +831,7 @@ impl LengthOrPercentage {
|
||||||
Ok(LengthOrPercentage::Length(NoCalcLength::from_px(value.value))),
|
Ok(LengthOrPercentage::Length(NoCalcLength::from_px(value.value))),
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
let calc = try!(input.parse_nested_block(|i| {
|
let calc = try!(input.parse_nested_block(|i| {
|
||||||
CalcLengthOrPercentage::parse_length_or_percentage(context, i)
|
CalcNode::parse_length_or_percentage(context, i)
|
||||||
}));
|
}));
|
||||||
Ok(LengthOrPercentage::Calc(Box::new(calc)))
|
Ok(LengthOrPercentage::Calc(Box::new(calc)))
|
||||||
},
|
},
|
||||||
|
@ -1338,8 +915,7 @@ impl LengthOrPercentage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO(emilio): Do the Length and Percentage variants make any sense with
|
/// Either a `<length>`, a `<percentage>`, or the `auto` keyword.
|
||||||
/// CalcLengthOrPercentage?
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -1350,7 +926,6 @@ pub enum LengthOrPercentageOrAuto {
|
||||||
Calc(Box<CalcLengthOrPercentage>),
|
Calc(Box<CalcLengthOrPercentage>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From<NoCalcLength> for LengthOrPercentageOrAuto {
|
impl From<NoCalcLength> for LengthOrPercentageOrAuto {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(len: NoCalcLength) -> Self {
|
fn from(len: NoCalcLength) -> Self {
|
||||||
|
@ -1410,7 +985,7 @@ impl LengthOrPercentageOrAuto {
|
||||||
Ok(LengthOrPercentageOrAuto::Auto),
|
Ok(LengthOrPercentageOrAuto::Auto),
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
let calc = try!(input.parse_nested_block(|i| {
|
let calc = try!(input.parse_nested_block(|i| {
|
||||||
CalcLengthOrPercentage::parse_length_or_percentage(context, i)
|
CalcNode::parse_length_or_percentage(context, i)
|
||||||
}));
|
}));
|
||||||
Ok(LengthOrPercentageOrAuto::Calc(Box::new(calc)))
|
Ok(LengthOrPercentageOrAuto::Calc(Box::new(calc)))
|
||||||
},
|
},
|
||||||
|
@ -1462,8 +1037,7 @@ impl LengthOrPercentageOrAuto {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO(emilio): Do the Length and Percentage variants make any sense with
|
/// Either a `<length>`, a `<percentage>`, or the `none` keyword.
|
||||||
/// CalcLengthOrPercentage?
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -1517,7 +1091,7 @@ impl LengthOrPercentageOrNone {
|
||||||
}
|
}
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
let calc = try!(input.parse_nested_block(|i| {
|
let calc = try!(input.parse_nested_block(|i| {
|
||||||
CalcLengthOrPercentage::parse_length_or_percentage(context, i)
|
CalcNode::parse_length_or_percentage(context, i)
|
||||||
}));
|
}));
|
||||||
Ok(LengthOrPercentageOrNone::Calc(Box::new(calc)))
|
Ok(LengthOrPercentageOrNone::Calc(Box::new(calc)))
|
||||||
},
|
},
|
||||||
|
@ -1561,8 +1135,6 @@ pub type LengthOrAuto = Either<Length, Auto>;
|
||||||
|
|
||||||
/// Either a `<length>` or a `<percentage>` or the `auto` keyword or the
|
/// Either a `<length>` or a `<percentage>` or the `auto` keyword or the
|
||||||
/// `content` keyword.
|
/// `content` keyword.
|
||||||
///
|
|
||||||
/// TODO(emilio): Do the Length and Percentage variants make any sense with
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
pub enum LengthOrPercentageOrAutoOrContent {
|
pub enum LengthOrPercentageOrAutoOrContent {
|
||||||
|
@ -1596,7 +1168,7 @@ impl LengthOrPercentageOrAutoOrContent {
|
||||||
Ok(LengthOrPercentageOrAutoOrContent::Content),
|
Ok(LengthOrPercentageOrAutoOrContent::Content),
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
let calc = try!(input.parse_nested_block(|i| {
|
let calc = try!(input.parse_nested_block(|i| {
|
||||||
CalcLengthOrPercentage::parse_length_or_percentage(context, i)
|
CalcNode::parse_length_or_percentage(context, i)
|
||||||
}));
|
}));
|
||||||
Ok(LengthOrPercentageOrAutoOrContent::Calc(Box::new(calc)))
|
Ok(LengthOrPercentageOrAutoOrContent::Calc(Box::new(calc)))
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,13 +16,13 @@ use self::url::SpecifiedUrl;
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::f32;
|
use std::f32;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Mul;
|
|
||||||
use style_traits::ToCss;
|
use style_traits::ToCss;
|
||||||
use style_traits::values::specified::AllowedNumericType;
|
use style_traits::values::specified::AllowedNumericType;
|
||||||
use super::{Auto, CSSFloat, CSSInteger, HasViewportPercentage, Either, None_};
|
use super::{Auto, CSSFloat, CSSInteger, HasViewportPercentage, Either, None_};
|
||||||
use super::computed::{self, Context};
|
use super::computed::{self, Context};
|
||||||
use super::computed::{Shadow as ComputedShadow, ToComputedValue};
|
use super::computed::{Shadow as ComputedShadow, ToComputedValue};
|
||||||
use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
|
use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
|
||||||
|
use values::specified::calc::CalcNode;
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
|
pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
|
||||||
|
@ -34,13 +34,14 @@ pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword,
|
||||||
pub use self::length::AbsoluteLength;
|
pub use self::length::AbsoluteLength;
|
||||||
pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
|
pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
|
||||||
pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
|
pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
|
||||||
pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength, CalcUnit};
|
pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
|
||||||
pub use self::length::{MaxLength, MinLength};
|
pub use self::length::{MaxLength, MinLength};
|
||||||
pub use self::position::{HorizontalPosition, Position, VerticalPosition};
|
pub use self::position::{HorizontalPosition, Position, VerticalPosition};
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub mod align;
|
pub mod align;
|
||||||
pub mod basic_shape;
|
pub mod basic_shape;
|
||||||
|
pub mod calc;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
@ -153,96 +154,28 @@ impl ToCss for CSSRGBA {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// Parse an `<integer>` value, handling `calc()` correctly.
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub struct SimplifiedSumNode {
|
|
||||||
values: Vec<SimplifiedValueNode>,
|
|
||||||
}
|
|
||||||
impl<'a> Mul<CSSFloat> for &'a SimplifiedSumNode {
|
|
||||||
type Output = SimplifiedSumNode;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mul(self, scalar: CSSFloat) -> SimplifiedSumNode {
|
|
||||||
SimplifiedSumNode {
|
|
||||||
values: self.values.iter().map(|p| p * scalar).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub enum SimplifiedValueNode {
|
|
||||||
Length(NoCalcLength),
|
|
||||||
Angle(CSSFloat),
|
|
||||||
Time(CSSFloat),
|
|
||||||
Percentage(CSSFloat),
|
|
||||||
Number(CSSFloat),
|
|
||||||
Sum(Box<SimplifiedSumNode>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Mul<CSSFloat> for &'a SimplifiedValueNode {
|
|
||||||
type Output = SimplifiedValueNode;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn mul(self, scalar: CSSFloat) -> SimplifiedValueNode {
|
|
||||||
match *self {
|
|
||||||
SimplifiedValueNode::Length(ref l) => {
|
|
||||||
SimplifiedValueNode::Length(l.clone() * scalar)
|
|
||||||
},
|
|
||||||
SimplifiedValueNode::Percentage(p) => {
|
|
||||||
SimplifiedValueNode::Percentage(p * scalar)
|
|
||||||
},
|
|
||||||
SimplifiedValueNode::Angle(a) => {
|
|
||||||
SimplifiedValueNode::Angle(a * scalar)
|
|
||||||
},
|
|
||||||
SimplifiedValueNode::Time(t) => {
|
|
||||||
SimplifiedValueNode::Time(t * scalar)
|
|
||||||
},
|
|
||||||
SimplifiedValueNode::Number(n) => {
|
|
||||||
SimplifiedValueNode::Number(n * scalar)
|
|
||||||
},
|
|
||||||
SimplifiedValueNode::Sum(ref s) => {
|
|
||||||
let sum = &**s * scalar;
|
|
||||||
SimplifiedValueNode::Sum(Box::new(sum))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub fn parse_integer(context: &ParserContext, input: &mut Parser) -> Result<Integer, ()> {
|
pub fn parse_integer(context: &ParserContext, input: &mut Parser) -> Result<Integer, ()> {
|
||||||
match try!(input.next()) {
|
match try!(input.next()) {
|
||||||
Token::Number(ref value) => value.int_value.ok_or(()).map(Integer::new),
|
Token::Number(ref value) => value.int_value.ok_or(()).map(Integer::new),
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
let ast = try!(input.parse_nested_block(|i| {
|
let result = try!(input.parse_nested_block(|i| {
|
||||||
CalcLengthOrPercentage::parse_sum(context, i, CalcUnit::Integer)
|
CalcNode::parse_integer(context, i)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut result = None;
|
Ok(Integer::from_calc(result))
|
||||||
|
|
||||||
for ref node in ast.products {
|
|
||||||
match try!(CalcLengthOrPercentage::simplify_product(node)) {
|
|
||||||
SimplifiedValueNode::Number(val) =>
|
|
||||||
result = Some(result.unwrap_or(0) + val as CSSInteger),
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Some(result) => Ok(Integer::from_calc(result)),
|
|
||||||
_ => Err(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
/// Parse a `<number>` value, handling `calc()` correctly, and without length
|
||||||
|
/// limitations.
|
||||||
pub fn parse_number(context: &ParserContext, input: &mut Parser) -> Result<Number, ()> {
|
pub fn parse_number(context: &ParserContext, input: &mut Parser) -> Result<Number, ()> {
|
||||||
parse_number_with_clamping_mode(context, input, AllowedNumericType::All)
|
parse_number_with_clamping_mode(context, input, AllowedNumericType::All)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
/// Parse a `<number>` value, with a given clamping mode.
|
||||||
pub fn parse_number_with_clamping_mode(context: &ParserContext,
|
pub fn parse_number_with_clamping_mode(context: &ParserContext,
|
||||||
input: &mut Parser,
|
input: &mut Parser,
|
||||||
clamping_mode: AllowedNumericType)
|
clamping_mode: AllowedNumericType)
|
||||||
|
@ -255,29 +188,14 @@ pub fn parse_number_with_clamping_mode(context: &ParserContext,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
let ast = try!(input.parse_nested_block(|i| {
|
let result = try!(input.parse_nested_block(|i| {
|
||||||
CalcLengthOrPercentage::parse_sum(context, i, CalcUnit::Number)
|
CalcNode::parse_number(context, i)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut result = None;
|
Ok(Number {
|
||||||
|
value: result.min(f32::MAX).max(f32::MIN),
|
||||||
for ref node in ast.products {
|
calc_clamping_mode: Some(clamping_mode),
|
||||||
match try!(CalcLengthOrPercentage::simplify_product(node)) {
|
})
|
||||||
SimplifiedValueNode::Number(val) =>
|
|
||||||
result = Some(result.unwrap_or(0.) + val),
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Some(result) => {
|
|
||||||
Ok(Number {
|
|
||||||
value: result.min(f32::MAX).max(f32::MIN),
|
|
||||||
calc_clamping_mode: Some(clamping_mode),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
_ => Err(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
|
@ -338,20 +256,23 @@ impl ToComputedValue for Angle {
|
||||||
|
|
||||||
impl Angle {
|
impl Angle {
|
||||||
/// Returns an angle with the given value in degrees.
|
/// Returns an angle with the given value in degrees.
|
||||||
pub fn from_degrees(value: CSSFloat) -> Self {
|
pub fn from_degrees(value: CSSFloat, was_calc: bool) -> Self {
|
||||||
Angle { value: computed::Angle::Degree(value), was_calc: false }
|
Angle { value: computed::Angle::Degree(value), was_calc: was_calc }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an angle with the given value in gradians.
|
/// Returns an angle with the given value in gradians.
|
||||||
pub fn from_gradians(value: CSSFloat) -> Self {
|
pub fn from_gradians(value: CSSFloat, was_calc: bool) -> Self {
|
||||||
Angle { value: computed::Angle::Gradian(value), was_calc: false }
|
Angle { value: computed::Angle::Gradian(value), was_calc: was_calc }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an angle with the given value in turns.
|
/// Returns an angle with the given value in turns.
|
||||||
pub fn from_turns(value: CSSFloat) -> Self {
|
pub fn from_turns(value: CSSFloat, was_calc: bool) -> Self {
|
||||||
Angle { value: computed::Angle::Turn(value), was_calc: false }
|
Angle { value: computed::Angle::Turn(value), was_calc: was_calc }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an angle with the given value in radians.
|
/// Returns an angle with the given value in radians.
|
||||||
pub fn from_radians(value: CSSFloat) -> Self {
|
pub fn from_radians(value: CSSFloat, was_calc: bool) -> Self {
|
||||||
Angle { value: computed::Angle::Radian(value), was_calc: false }
|
Angle { value: computed::Angle::Radian(value), was_calc: was_calc }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -362,7 +283,7 @@ impl Angle {
|
||||||
|
|
||||||
/// Returns an angle value that represents zero.
|
/// Returns an angle value that represents zero.
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self::from_degrees(0.0)
|
Self::from_degrees(0.0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an `Angle` parsed from a `calc()` expression.
|
/// Returns an `Angle` parsed from a `calc()` expression.
|
||||||
|
@ -378,40 +299,55 @@ impl Parse for Angle {
|
||||||
/// Parses an angle according to CSS-VALUES § 6.1.
|
/// Parses an angle according to CSS-VALUES § 6.1.
|
||||||
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||||
match try!(input.next()) {
|
match try!(input.next()) {
|
||||||
Token::Dimension(ref value, ref unit) => Angle::parse_dimension(value.value, unit),
|
Token::Dimension(ref value, ref unit) => {
|
||||||
|
Angle::parse_dimension(value.value,
|
||||||
|
unit,
|
||||||
|
/* from_calc = */ false)
|
||||||
|
}
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
input.parse_nested_block(|i| CalcLengthOrPercentage::parse_angle(context, i))
|
input.parse_nested_block(|i| CalcNode::parse_angle(context, i))
|
||||||
},
|
}
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Angle {
|
impl Angle {
|
||||||
#[allow(missing_docs)]
|
/// Parse an `<angle>` value given a value and an unit.
|
||||||
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Angle, ()> {
|
pub fn parse_dimension(
|
||||||
|
value: CSSFloat,
|
||||||
|
unit: &str,
|
||||||
|
from_calc: bool)
|
||||||
|
-> Result<Angle, ()>
|
||||||
|
{
|
||||||
let angle = match_ignore_ascii_case! { unit,
|
let angle = match_ignore_ascii_case! { unit,
|
||||||
"deg" => Angle::from_degrees(value),
|
"deg" => Angle::from_degrees(value, from_calc),
|
||||||
"grad" => Angle::from_gradians(value),
|
"grad" => Angle::from_gradians(value, from_calc),
|
||||||
"turn" => Angle::from_turns(value),
|
"turn" => Angle::from_turns(value, from_calc),
|
||||||
"rad" => Angle::from_radians(value),
|
"rad" => Angle::from_radians(value, from_calc),
|
||||||
_ => return Err(())
|
_ => return Err(())
|
||||||
};
|
};
|
||||||
Ok(angle)
|
Ok(angle)
|
||||||
}
|
}
|
||||||
/// Parse an angle, including unitless 0 degree.
|
/// Parse an angle, including unitless 0 degree.
|
||||||
/// Note that numbers without any AngleUnit, including unitless 0
|
///
|
||||||
/// angle, should be invalid. However, some properties still accept
|
/// Note that numbers without any AngleUnit, including unitless 0 angle,
|
||||||
/// unitless 0 angle and stores it as '0deg'. We can remove this and
|
/// should be invalid. However, some properties still accept unitless 0
|
||||||
/// get back to the unified version Angle::parse once
|
/// angle and stores it as '0deg'.
|
||||||
|
///
|
||||||
|
/// We can remove this and get back to the unified version Angle::parse once
|
||||||
/// https://github.com/w3c/csswg-drafts/issues/1162 is resolved.
|
/// https://github.com/w3c/csswg-drafts/issues/1162 is resolved.
|
||||||
pub fn parse_with_unitless(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
pub fn parse_with_unitless(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||||
match try!(input.next()) {
|
match try!(input.next()) {
|
||||||
Token::Dimension(ref value, ref unit) => Angle::parse_dimension(value.value, unit),
|
Token::Dimension(ref value, ref unit) => {
|
||||||
|
Angle::parse_dimension(value.value,
|
||||||
|
unit,
|
||||||
|
/* from_calc = */ false)
|
||||||
|
}
|
||||||
Token::Number(ref value) if value.value == 0. => Ok(Angle::zero()),
|
Token::Number(ref value) if value.value == 0. => Ok(Angle::zero()),
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||||
input.parse_nested_block(|i| CalcLengthOrPercentage::parse_angle(context, i))
|
input.parse_nested_block(|i| CalcNode::parse_angle(context, i))
|
||||||
},
|
}
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,7 +515,12 @@ impl Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a time according to CSS-VALUES § 6.2.
|
/// Parses a time according to CSS-VALUES § 6.2.
|
||||||
fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Time, ()> {
|
pub fn parse_dimension(
|
||||||
|
value: CSSFloat,
|
||||||
|
unit: &str,
|
||||||
|
from_calc: bool)
|
||||||
|
-> Result<Time, ()>
|
||||||
|
{
|
||||||
let seconds = match_ignore_ascii_case! { unit,
|
let seconds = match_ignore_ascii_case! { unit,
|
||||||
"s" => value,
|
"s" => value,
|
||||||
"ms" => value / 1000.0,
|
"ms" => value / 1000.0,
|
||||||
|
@ -588,7 +529,7 @@ impl Time {
|
||||||
|
|
||||||
Ok(Time {
|
Ok(Time {
|
||||||
seconds: seconds,
|
seconds: seconds,
|
||||||
was_calc: false,
|
was_calc: from_calc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,10 +561,10 @@ impl Parse for Time {
|
||||||
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||||
match input.next() {
|
match input.next() {
|
||||||
Ok(Token::Dimension(ref value, ref unit)) => {
|
Ok(Token::Dimension(ref value, ref unit)) => {
|
||||||
Time::parse_dimension(value.value, &unit)
|
Time::parse_dimension(value.value, &unit, /* from_calc = */ false)
|
||||||
}
|
}
|
||||||
Ok(Token::Function(ref name)) if name.eq_ignore_ascii_case("calc") => {
|
Ok(Token::Function(ref name)) if name.eq_ignore_ascii_case("calc") => {
|
||||||
input.parse_nested_block(|i| CalcLengthOrPercentage::parse_time(context, i))
|
input.parse_nested_block(|i| CalcNode::parse_time(context, i))
|
||||||
}
|
}
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
|
@ -804,7 +745,7 @@ impl Integer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trivially constructs a new integer value from a `calc()` expression.
|
/// Trivially constructs a new integer value from a `calc()` expression.
|
||||||
pub fn from_calc(val: CSSInteger) -> Self {
|
fn from_calc(val: CSSInteger) -> Self {
|
||||||
Integer {
|
Integer {
|
||||||
value: val,
|
value: val,
|
||||||
was_calc: true,
|
was_calc: true,
|
||||||
|
|
|
@ -14,14 +14,14 @@ fn image_orientation_longhand_should_parse_properly() {
|
||||||
assert_eq!(from_image, SpecifiedValue { angle: None, flipped: false });
|
assert_eq!(from_image, SpecifiedValue { angle: None, flipped: false });
|
||||||
|
|
||||||
let flip = parse_longhand!(image_orientation, "flip");
|
let flip = parse_longhand!(image_orientation, "flip");
|
||||||
assert_eq!(flip, SpecifiedValue { angle: Some(Angle::from_degrees(0.0)), flipped: true });
|
assert_eq!(flip, SpecifiedValue { angle: Some(Angle::zero()), flipped: true });
|
||||||
|
|
||||||
let zero = parse_longhand!(image_orientation, "0deg");
|
let zero = parse_longhand!(image_orientation, "0deg");
|
||||||
assert_eq!(zero, SpecifiedValue { angle: Some(Angle::from_degrees(0.0)), flipped: false });
|
assert_eq!(zero, SpecifiedValue { angle: Some(Angle::zero()), flipped: false });
|
||||||
|
|
||||||
let negative_rad = parse_longhand!(image_orientation, "-1rad");
|
let negative_rad = parse_longhand!(image_orientation, "-1rad");
|
||||||
assert_eq!(negative_rad, SpecifiedValue { angle: Some(Angle::from_radians(-1.0)), flipped: false });
|
assert_eq!(negative_rad, SpecifiedValue { angle: Some(Angle::from_radians(-1.0, false)), flipped: false });
|
||||||
|
|
||||||
let flip_with_180 = parse_longhand!(image_orientation, "180deg flip");
|
let flip_with_180 = parse_longhand!(image_orientation, "180deg flip");
|
||||||
assert_eq!(flip_with_180, SpecifiedValue { angle: Some(Angle::from_degrees(180.0)), flipped: true });
|
assert_eq!(flip_with_180, SpecifiedValue { angle: Some(Angle::from_degrees(180.0, false)), flipped: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ fn test_calc() {
|
||||||
assert!(parse(Length::parse, "calc( 1px + 2px )").is_ok());
|
assert!(parse(Length::parse, "calc( 1px + 2px )").is_ok());
|
||||||
assert!(parse(Length::parse, "calc(1px + 2px )").is_ok());
|
assert!(parse(Length::parse, "calc(1px + 2px )").is_ok());
|
||||||
assert!(parse(Length::parse, "calc( 1px + 2px)").is_ok());
|
assert!(parse(Length::parse, "calc( 1px + 2px)").is_ok());
|
||||||
|
assert!(parse(Length::parse, "calc( 1px + 2px / ( 1 + 2 - 1))").is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -6,7 +6,6 @@ use app_units::Au;
|
||||||
use parsing::parse;
|
use parsing::parse;
|
||||||
use style::values::HasViewportPercentage;
|
use style::values::HasViewportPercentage;
|
||||||
use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength};
|
use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength};
|
||||||
use style::values::specified::length::{CalcLengthOrPercentage, CalcUnit};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn length_has_viewport_percentage() {
|
fn length_has_viewport_percentage() {
|
||||||
|
@ -15,21 +14,3 @@ fn length_has_viewport_percentage() {
|
||||||
let l = NoCalcLength::Absolute(AbsoluteLength::Px(Au(100).to_f32_px()));
|
let l = NoCalcLength::Absolute(AbsoluteLength::Px(Au(100).to_f32_px()));
|
||||||
assert!(!l.has_viewport_percentage());
|
assert!(!l.has_viewport_percentage());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn calc_top_level_number_with_unit() {
|
|
||||||
fn parse_value(text: &str, unit: CalcUnit) -> Result<CalcLengthOrPercentage, ()> {
|
|
||||||
parse(|context, input| CalcLengthOrPercentage::parse(context, input, unit), text)
|
|
||||||
}
|
|
||||||
assert_eq!(parse_value("1", CalcUnit::Length), Err(()));
|
|
||||||
assert_eq!(parse_value("1", CalcUnit::LengthOrPercentage), Err(()));
|
|
||||||
assert_eq!(parse_value("1", CalcUnit::Angle), Err(()));
|
|
||||||
assert_eq!(parse_value("1", CalcUnit::Time), Err(()));
|
|
||||||
assert_eq!(parse_value("1px + 1", CalcUnit::Length), Err(()));
|
|
||||||
assert_eq!(parse_value("1em + 1", CalcUnit::Length), Err(()));
|
|
||||||
assert_eq!(parse_value("1px + 1", CalcUnit::LengthOrPercentage), Err(()));
|
|
||||||
assert_eq!(parse_value("1% + 1", CalcUnit::LengthOrPercentage), Err(()));
|
|
||||||
assert_eq!(parse_value("1rad + 1", CalcUnit::Angle), Err(()));
|
|
||||||
assert_eq!(parse_value("1deg + 1", CalcUnit::Angle), Err(()));
|
|
||||||
assert_eq!(parse_value("1s + 1", CalcUnit::Time), Err(()));
|
|
||||||
}
|
|
||||||
|
|
|
@ -1043,19 +1043,19 @@ mod shorthand_serialization {
|
||||||
#[test]
|
#[test]
|
||||||
fn transform_skew() {
|
fn transform_skew() {
|
||||||
validate_serialization(
|
validate_serialization(
|
||||||
&SpecifiedOperation::Skew(Angle::from_degrees(42.3), None),
|
&SpecifiedOperation::Skew(Angle::from_degrees(42.3, false), None),
|
||||||
"skew(42.3deg)");
|
"skew(42.3deg)");
|
||||||
validate_serialization(
|
validate_serialization(
|
||||||
&SpecifiedOperation::Skew(Angle::from_gradians(-50.0), Some(Angle::from_turns(0.73))),
|
&SpecifiedOperation::Skew(Angle::from_gradians(-50.0, false), Some(Angle::from_turns(0.73, false))),
|
||||||
"skew(-50grad, 0.73turn)");
|
"skew(-50grad, 0.73turn)");
|
||||||
validate_serialization(
|
validate_serialization(
|
||||||
&SpecifiedOperation::SkewX(Angle::from_radians(0.31)), "skewX(0.31rad)");
|
&SpecifiedOperation::SkewX(Angle::from_radians(0.31, false)), "skewX(0.31rad)");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transform_rotate() {
|
fn transform_rotate() {
|
||||||
validate_serialization(
|
validate_serialization(
|
||||||
&SpecifiedOperation::Rotate(Angle::from_turns(35.0)),
|
&SpecifiedOperation::Rotate(Angle::from_turns(35.0, false)),
|
||||||
"rotate(35turn)"
|
"rotate(35turn)"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue