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:
Emilio Cobos Álvarez 2017-05-04 18:51:18 +02:00
parent 36f26148e6
commit 3608dc8088
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
7 changed files with 638 additions and 596 deletions

View 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()
}
}

View file

@ -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)))
},

View file

@ -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,

View file

@ -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 });
}

View file

@ -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]

View file

@ -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(()));
}

View file

@ -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)"
)
}