mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +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::values::specified::AllowedLengthType;
|
||||
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::ExtremumLength;
|
||||
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::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
|
||||
pub use super::image::{SizeKeyword, VerticalDirection};
|
||||
|
@ -637,7 +639,10 @@ impl Length {
|
|||
},
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") =>
|
||||
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(())
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
/// [0 .. 100%] maps to [0.0 .. 1.0]
|
||||
|
@ -1177,8 +756,6 @@ impl Parse for Percentage {
|
|||
impl ComputedValueAsSpecified for Percentage {}
|
||||
|
||||
/// A length or a percentage value.
|
||||
///
|
||||
/// TODO(emilio): Does this make any sense vs. CalcLengthOrPercentage?
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[allow(missing_docs)]
|
||||
|
@ -1254,7 +831,7 @@ impl LengthOrPercentage {
|
|||
Ok(LengthOrPercentage::Length(NoCalcLength::from_px(value.value))),
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||
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)))
|
||||
},
|
||||
|
@ -1338,8 +915,7 @@ impl LengthOrPercentage {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO(emilio): Do the Length and Percentage variants make any sense with
|
||||
/// CalcLengthOrPercentage?
|
||||
/// Either a `<length>`, a `<percentage>`, or the `auto` keyword.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[allow(missing_docs)]
|
||||
|
@ -1350,7 +926,6 @@ pub enum LengthOrPercentageOrAuto {
|
|||
Calc(Box<CalcLengthOrPercentage>),
|
||||
}
|
||||
|
||||
|
||||
impl From<NoCalcLength> for LengthOrPercentageOrAuto {
|
||||
#[inline]
|
||||
fn from(len: NoCalcLength) -> Self {
|
||||
|
@ -1410,7 +985,7 @@ impl LengthOrPercentageOrAuto {
|
|||
Ok(LengthOrPercentageOrAuto::Auto),
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||
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)))
|
||||
},
|
||||
|
@ -1462,8 +1037,7 @@ impl LengthOrPercentageOrAuto {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO(emilio): Do the Length and Percentage variants make any sense with
|
||||
/// CalcLengthOrPercentage?
|
||||
/// Either a `<length>`, a `<percentage>`, or the `none` keyword.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
#[allow(missing_docs)]
|
||||
|
@ -1517,7 +1091,7 @@ impl LengthOrPercentageOrNone {
|
|||
}
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||
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)))
|
||||
},
|
||||
|
@ -1561,8 +1135,6 @@ pub type LengthOrAuto = Either<Length, Auto>;
|
|||
|
||||
/// Either a `<length>` or a `<percentage>` or the `auto` keyword or the
|
||||
/// `content` keyword.
|
||||
///
|
||||
/// TODO(emilio): Do the Length and Percentage variants make any sense with
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||
pub enum LengthOrPercentageOrAutoOrContent {
|
||||
|
@ -1596,7 +1168,7 @@ impl LengthOrPercentageOrAutoOrContent {
|
|||
Ok(LengthOrPercentageOrAutoOrContent::Content),
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||
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)))
|
||||
},
|
||||
|
|
|
@ -16,13 +16,13 @@ use self::url::SpecifiedUrl;
|
|||
use std::ascii::AsciiExt;
|
||||
use std::f32;
|
||||
use std::fmt;
|
||||
use std::ops::Mul;
|
||||
use style_traits::ToCss;
|
||||
use style_traits::values::specified::AllowedNumericType;
|
||||
use super::{Auto, CSSFloat, CSSInteger, HasViewportPercentage, Either, None_};
|
||||
use super::computed::{self, Context};
|
||||
use super::computed::{Shadow as ComputedShadow, ToComputedValue};
|
||||
use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
|
||||
use values::specified::calc::CalcNode;
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
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::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
|
||||
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::position::{HorizontalPosition, Position, VerticalPosition};
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
pub mod align;
|
||||
pub mod basic_shape;
|
||||
pub mod calc;
|
||||
pub mod color;
|
||||
pub mod grid;
|
||||
pub mod image;
|
||||
|
@ -153,96 +154,28 @@ impl ToCss for CSSRGBA {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[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)]
|
||||
/// Parse an `<integer>` value, handling `calc()` correctly.
|
||||
pub fn parse_integer(context: &ParserContext, input: &mut Parser) -> Result<Integer, ()> {
|
||||
match try!(input.next()) {
|
||||
Token::Number(ref value) => value.int_value.ok_or(()).map(Integer::new),
|
||||
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
|
||||
let ast = try!(input.parse_nested_block(|i| {
|
||||
CalcLengthOrPercentage::parse_sum(context, i, CalcUnit::Integer)
|
||||
let result = try!(input.parse_nested_block(|i| {
|
||||
CalcNode::parse_integer(context, i)
|
||||
}));
|
||||
|
||||
let mut result = None;
|
||||
|
||||
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(())
|
||||
}
|
||||
Ok(Integer::from_calc(result))
|
||||
}
|
||||
_ => 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, ()> {
|
||||
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,
|
||||
input: &mut Parser,
|
||||
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") => {
|
||||
let ast = try!(input.parse_nested_block(|i| {
|
||||
CalcLengthOrPercentage::parse_sum(context, i, CalcUnit::Number)
|
||||
let result = try!(input.parse_nested_block(|i| {
|
||||
CalcNode::parse_number(context, i)
|
||||
}));
|
||||
|
||||
let mut result = None;
|
||||
|
||||
for ref node in ast.products {
|
||||
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(())
|
||||
}
|
||||
Ok(Number {
|
||||
value: result.min(f32::MAX).max(f32::MIN),
|
||||
calc_clamping_mode: Some(clamping_mode),
|
||||
})
|
||||
}
|
||||
_ => Err(())
|
||||
}
|
||||
|
@ -338,20 +256,23 @@ impl ToComputedValue for Angle {
|
|||
|
||||
impl Angle {
|
||||
/// Returns an angle with the given value in degrees.
|
||||
pub fn from_degrees(value: CSSFloat) -> Self {
|
||||
Angle { value: computed::Angle::Degree(value), was_calc: false }
|
||||
pub fn from_degrees(value: CSSFloat, was_calc: bool) -> Self {
|
||||
Angle { value: computed::Angle::Degree(value), was_calc: was_calc }
|
||||
}
|
||||
|
||||
/// Returns an angle with the given value in gradians.
|
||||
pub fn from_gradians(value: CSSFloat) -> Self {
|
||||
Angle { value: computed::Angle::Gradian(value), was_calc: false }
|
||||
pub fn from_gradians(value: CSSFloat, was_calc: bool) -> Self {
|
||||
Angle { value: computed::Angle::Gradian(value), was_calc: was_calc }
|
||||
}
|
||||
|
||||
/// Returns an angle with the given value in turns.
|
||||
pub fn from_turns(value: CSSFloat) -> Self {
|
||||
Angle { value: computed::Angle::Turn(value), was_calc: false }
|
||||
pub fn from_turns(value: CSSFloat, was_calc: bool) -> Self {
|
||||
Angle { value: computed::Angle::Turn(value), was_calc: was_calc }
|
||||
}
|
||||
|
||||
/// Returns an angle with the given value in radians.
|
||||
pub fn from_radians(value: CSSFloat) -> Self {
|
||||
Angle { value: computed::Angle::Radian(value), was_calc: false }
|
||||
pub fn from_radians(value: CSSFloat, was_calc: bool) -> Self {
|
||||
Angle { value: computed::Angle::Radian(value), was_calc: was_calc }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -362,7 +283,7 @@ impl Angle {
|
|||
|
||||
/// Returns an angle value that represents zero.
|
||||
pub fn zero() -> Self {
|
||||
Self::from_degrees(0.0)
|
||||
Self::from_degrees(0.0, false)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||
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") => {
|
||||
input.parse_nested_block(|i| CalcLengthOrPercentage::parse_angle(context, i))
|
||||
},
|
||||
input.parse_nested_block(|i| CalcNode::parse_angle(context, i))
|
||||
}
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Angle {
|
||||
#[allow(missing_docs)]
|
||||
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Angle, ()> {
|
||||
/// Parse an `<angle>` value given a value and an unit.
|
||||
pub fn parse_dimension(
|
||||
value: CSSFloat,
|
||||
unit: &str,
|
||||
from_calc: bool)
|
||||
-> Result<Angle, ()>
|
||||
{
|
||||
let angle = match_ignore_ascii_case! { unit,
|
||||
"deg" => Angle::from_degrees(value),
|
||||
"grad" => Angle::from_gradians(value),
|
||||
"turn" => Angle::from_turns(value),
|
||||
"rad" => Angle::from_radians(value),
|
||||
"deg" => Angle::from_degrees(value, from_calc),
|
||||
"grad" => Angle::from_gradians(value, from_calc),
|
||||
"turn" => Angle::from_turns(value, from_calc),
|
||||
"rad" => Angle::from_radians(value, from_calc),
|
||||
_ => return Err(())
|
||||
};
|
||||
Ok(angle)
|
||||
}
|
||||
/// 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
|
||||
/// unitless 0 angle and stores it as '0deg'. We can remove this and
|
||||
/// get back to the unified version Angle::parse once
|
||||
///
|
||||
/// Note that numbers without any AngleUnit, including unitless 0 angle,
|
||||
/// should be invalid. However, some properties still accept unitless 0
|
||||
/// 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.
|
||||
pub fn parse_with_unitless(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
|
||||
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::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(())
|
||||
}
|
||||
}
|
||||
|
@ -579,7 +515,12 @@ impl Time {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
"s" => value,
|
||||
"ms" => value / 1000.0,
|
||||
|
@ -588,7 +529,7 @@ impl Time {
|
|||
|
||||
Ok(Time {
|
||||
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, ()> {
|
||||
match input.next() {
|
||||
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") => {
|
||||
input.parse_nested_block(|i| CalcLengthOrPercentage::parse_time(context, i))
|
||||
input.parse_nested_block(|i| CalcNode::parse_time(context, i))
|
||||
}
|
||||
_ => Err(())
|
||||
}
|
||||
|
@ -804,7 +745,7 @@ impl Integer {
|
|||
}
|
||||
|
||||
/// Trivially constructs a new integer value from a `calc()` expression.
|
||||
pub fn from_calc(val: CSSInteger) -> Self {
|
||||
fn from_calc(val: CSSInteger) -> Self {
|
||||
Integer {
|
||||
value: val,
|
||||
was_calc: true,
|
||||
|
|
|
@ -14,14 +14,14 @@ fn image_orientation_longhand_should_parse_properly() {
|
|||
assert_eq!(from_image, SpecifiedValue { angle: None, flipped: false });
|
||||
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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 / ( 1 + 2 - 1))").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -6,7 +6,6 @@ use app_units::Au;
|
|||
use parsing::parse;
|
||||
use style::values::HasViewportPercentage;
|
||||
use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength};
|
||||
use style::values::specified::length::{CalcLengthOrPercentage, CalcUnit};
|
||||
|
||||
#[test]
|
||||
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()));
|
||||
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]
|
||||
fn transform_skew() {
|
||||
validate_serialization(
|
||||
&SpecifiedOperation::Skew(Angle::from_degrees(42.3), None),
|
||||
&SpecifiedOperation::Skew(Angle::from_degrees(42.3, false), None),
|
||||
"skew(42.3deg)");
|
||||
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)");
|
||||
validate_serialization(
|
||||
&SpecifiedOperation::SkewX(Angle::from_radians(0.31)), "skewX(0.31rad)");
|
||||
&SpecifiedOperation::SkewX(Angle::from_radians(0.31, false)), "skewX(0.31rad)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rotate() {
|
||||
validate_serialization(
|
||||
&SpecifiedOperation::Rotate(Angle::from_turns(35.0)),
|
||||
&SpecifiedOperation::Rotate(Angle::from_turns(35.0, false)),
|
||||
"rotate(35turn)"
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue