/* 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/. */ //! [Length values][length]. //! //! [length]: https://drafts.csswg.org/css-values/#lengths use app_units::Au; use cssparser::{Parser, Token}; use euclid::size::Size2D; use font_metrics::FontMetrics; use parser::{Parse, ParserContext}; use std::{cmp, fmt, mem}; use std::ascii::AsciiExt; use std::ops::Mul; use style_traits::ToCss; use style_traits::values::specified::AllowedNumericType; use super::{Angle, Number, SimplifiedValueNode, SimplifiedSumNode, Time}; use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal}; use values::computed::Context; 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}; const AU_PER_PX: CSSFloat = 60.; const AU_PER_IN: CSSFloat = AU_PER_PX * 96.; const AU_PER_CM: CSSFloat = AU_PER_IN / 2.54; const AU_PER_MM: CSSFloat = AU_PER_IN / 25.4; const AU_PER_Q: CSSFloat = AU_PER_MM / 4.; const AU_PER_PT: CSSFloat = AU_PER_IN / 72.; const AU_PER_PC: CSSFloat = AU_PER_PT * 12.; #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] /// A font relative length. pub enum FontRelativeLength { /// A "em" value: https://drafts.csswg.org/css-values/#em Em(CSSFloat), /// A "ex" value: https://drafts.csswg.org/css-values/#ex Ex(CSSFloat), /// A "ch" value: https://drafts.csswg.org/css-values/#ch Ch(CSSFloat), /// A "rem" value: https://drafts.csswg.org/css-values/#rem Rem(CSSFloat) } impl ToCss for FontRelativeLength { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { FontRelativeLength::Em(length) => write!(dest, "{}em", length), FontRelativeLength::Ex(length) => write!(dest, "{}ex", length), FontRelativeLength::Ch(length) => write!(dest, "{}ch", length), FontRelativeLength::Rem(length) => write!(dest, "{}rem", length) } } } impl FontRelativeLength { /// Gets the first available font metrics from the current context's /// font-family list. pub fn find_first_available_font_metrics(context: &Context) -> Option { use font_metrics::FontMetricsQueryResult::*; if let Some(ref metrics_provider) = context.font_metrics_provider { for family in context.style().get_font().font_family_iter() { if let Available(metrics) = metrics_provider.query(family.atom()) { return metrics; } } } None } /// Computes the font-relative length. We use the use_inherited flag to /// special-case the computation of font-size. pub fn to_computed_value(&self, context: &Context, use_inherited: bool) -> Au { let reference_font_size = if use_inherited { context.inherited_style().get_font().clone_font_size() } else { context.style().get_font().clone_font_size() }; let root_font_size = context.style().root_font_size; match *self { FontRelativeLength::Em(length) => reference_font_size.scale_by(length), FontRelativeLength::Ex(length) => { match Self::find_first_available_font_metrics(context) { Some(metrics) => metrics.x_height, // https://drafts.csswg.org/css-values/#ex // // In the cases where it is impossible or impractical to // determine the x-height, a value of 0.5em must be // assumed. // None => reference_font_size.scale_by(0.5 * length), } }, FontRelativeLength::Ch(length) => { let wm = context.style().writing_mode; // TODO(emilio, #14144): Compute this properly once we support // all the relevant writing-mode related properties, this should // be equivalent to "is the text in the block direction?". let vertical = wm.is_vertical(); match Self::find_first_available_font_metrics(context) { Some(metrics) => { if vertical { metrics.zero_advance_measure.height } else { metrics.zero_advance_measure.width } } // https://drafts.csswg.org/css-values/#ch // // In the cases where it is impossible or impractical to // determine the measure of the “0” glyph, it must be // assumed to be 0.5em wide by 1em tall. Thus, the ch // unit falls back to 0.5em in the general case, and to // 1em when it would be typeset upright (i.e. // writing-mode is vertical-rl or vertical-lr and // text-orientation is upright). // None => { if vertical { reference_font_size.scale_by(length) } else { reference_font_size.scale_by(0.5 * length) } } } } FontRelativeLength::Rem(length) => root_font_size.scale_by(length) } } } #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] /// A viewport-relative length. /// /// https://drafts.csswg.org/css-values/#viewport-relative-lengths pub enum ViewportPercentageLength { /// A vw unit: https://drafts.csswg.org/css-values/#vw Vw(CSSFloat), /// A vh unit: https://drafts.csswg.org/css-values/#vh Vh(CSSFloat), /// https://drafts.csswg.org/css-values/#vmin Vmin(CSSFloat), /// https://drafts.csswg.org/css-values/#vmax Vmax(CSSFloat) } impl HasViewportPercentage for ViewportPercentageLength { fn has_viewport_percentage(&self) -> bool { true } } impl ToCss for ViewportPercentageLength { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { ViewportPercentageLength::Vw(length) => write!(dest, "{}vw", length), ViewportPercentageLength::Vh(length) => write!(dest, "{}vh", length), ViewportPercentageLength::Vmin(length) => write!(dest, "{}vmin", length), ViewportPercentageLength::Vmax(length) => write!(dest, "{}vmax", length) } } } impl ViewportPercentageLength { /// Computes the given viewport-relative length for the given viewport size. pub fn to_computed_value(&self, viewport_size: Size2D) -> Au { macro_rules! to_unit { ($viewport_dimension:expr) => { $viewport_dimension.to_f32_px() / 100.0 } } let value = match *self { ViewportPercentageLength::Vw(length) => length * to_unit!(viewport_size.width), ViewportPercentageLength::Vh(length) => length * to_unit!(viewport_size.height), ViewportPercentageLength::Vmin(length) => length * to_unit!(cmp::min(viewport_size.width, viewport_size.height)), ViewportPercentageLength::Vmax(length) => length * to_unit!(cmp::max(viewport_size.width, viewport_size.height)), }; Au::from_f32_px(value) } } /// HTML5 "character width", as defined in HTML5 § 14.5.4. #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct CharacterWidth(pub i32); impl CharacterWidth { /// Computes the given character width. pub fn to_computed_value(&self, reference_font_size: Au) -> Au { // This applies the *converting a character width to pixels* algorithm as specified // in HTML5 § 14.5.4. // // TODO(pcwalton): Find these from the font. let average_advance = reference_font_size.scale_by(0.5); let max_advance = reference_font_size; average_advance.scale_by(self.0 as CSSFloat - 1.0) + max_advance } } /// A `` without taking `calc` expressions into account /// /// https://drafts.csswg.org/css-values/#lengths #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum NoCalcLength { /// An absolute length: https://drafts.csswg.org/css-values/#absolute-length Absolute(Au), // application units /// A font-relative length: /// /// https://drafts.csswg.org/css-values/#font-relative-lengths FontRelative(FontRelativeLength), /// A viewport-relative length. /// /// https://drafts.csswg.org/css-values/#viewport-relative-lengths ViewportPercentage(ViewportPercentageLength), /// HTML5 "character width", as defined in HTML5 § 14.5.4. /// /// This cannot be specified by the user directly and is only generated by /// `Stylist::synthesize_rules_for_legacy_attributes()`. ServoCharacterWidth(CharacterWidth), } impl HasViewportPercentage for NoCalcLength { fn has_viewport_percentage(&self) -> bool { match *self { NoCalcLength::ViewportPercentage(_) => true, _ => false, } } } impl ToCss for NoCalcLength { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { NoCalcLength::Absolute(length) => write!(dest, "{}px", length.to_f32_px()), NoCalcLength::FontRelative(length) => length.to_css(dest), NoCalcLength::ViewportPercentage(length) => length.to_css(dest), /* This should only be reached from style dumping code */ NoCalcLength::ServoCharacterWidth(CharacterWidth(i)) => write!(dest, "CharWidth({})", i), } } } impl Mul for NoCalcLength { type Output = NoCalcLength; #[inline] fn mul(self, scalar: CSSFloat) -> NoCalcLength { match self { NoCalcLength::Absolute(Au(v)) => NoCalcLength::Absolute(Au(((v as f32) * scalar) as i32)), NoCalcLength::FontRelative(v) => NoCalcLength::FontRelative(v * scalar), NoCalcLength::ViewportPercentage(v) => NoCalcLength::ViewportPercentage(v * scalar), NoCalcLength::ServoCharacterWidth(_) => panic!("Can't multiply ServoCharacterWidth!"), } } } impl NoCalcLength { /// https://drafts.csswg.org/css-fonts-3/#font-size-prop pub fn from_str(s: &str) -> Option { Some(match_ignore_ascii_case! { s, "xx-small" => NoCalcLength::Absolute(Au::from_px(FONT_MEDIUM_PX) * 3 / 5), "x-small" => NoCalcLength::Absolute(Au::from_px(FONT_MEDIUM_PX) * 3 / 4), "small" => NoCalcLength::Absolute(Au::from_px(FONT_MEDIUM_PX) * 8 / 9), "medium" => NoCalcLength::Absolute(Au::from_px(FONT_MEDIUM_PX)), "large" => NoCalcLength::Absolute(Au::from_px(FONT_MEDIUM_PX) * 6 / 5), "x-large" => NoCalcLength::Absolute(Au::from_px(FONT_MEDIUM_PX) * 3 / 2), "xx-large" => NoCalcLength::Absolute(Au::from_px(FONT_MEDIUM_PX) * 2), // https://github.com/servo/servo/issues/3423#issuecomment-56321664 "smaller" => NoCalcLength::FontRelative(FontRelativeLength::Em(0.85)), "larger" => NoCalcLength::FontRelative(FontRelativeLength::Em(1.2)), _ => return None }) } /// https://drafts.csswg.org/css-fonts-3/#font-size-prop pub fn from_font_size_int(i: u8) -> Self { let au = match i { 0 | 1 => Au::from_px(FONT_MEDIUM_PX) * 3 / 4, 2 => Au::from_px(FONT_MEDIUM_PX) * 8 / 9, 3 => Au::from_px(FONT_MEDIUM_PX), 4 => Au::from_px(FONT_MEDIUM_PX) * 6 / 5, 5 => Au::from_px(FONT_MEDIUM_PX) * 3 / 2, 6 => Au::from_px(FONT_MEDIUM_PX) * 2, _ => Au::from_px(FONT_MEDIUM_PX) * 3, }; NoCalcLength::Absolute(au) } /// Parse a given absolute or relative dimension. pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result { match_ignore_ascii_case! { unit, "px" => Ok(NoCalcLength::Absolute(Au((value * AU_PER_PX) as i32))), "in" => Ok(NoCalcLength::Absolute(Au((value * AU_PER_IN) as i32))), "cm" => Ok(NoCalcLength::Absolute(Au((value * AU_PER_CM) as i32))), "mm" => Ok(NoCalcLength::Absolute(Au((value * AU_PER_MM) as i32))), "q" => Ok(NoCalcLength::Absolute(Au((value * AU_PER_Q) as i32))), "pt" => Ok(NoCalcLength::Absolute(Au((value * AU_PER_PT) as i32))), "pc" => Ok(NoCalcLength::Absolute(Au((value * AU_PER_PC) as i32))), // font-relative "em" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Em(value))), "ex" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ex(value))), "ch" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ch(value))), "rem" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Rem(value))), // viewport percentages "vw" => Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value))), "vh" => Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value))), "vmin" => Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value))), "vmax" => Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value))), _ => Err(()) } } #[inline] /// Returns a `zero` length. pub fn zero() -> NoCalcLength { NoCalcLength::Absolute(Au(0)) } #[inline] /// Checks whether the length value is zero. pub fn is_zero(&self) -> bool { *self == NoCalcLength::Absolute(Au(0)) } /// Get an absolute length from a px value. #[inline] pub fn from_px(px_value: CSSFloat) -> NoCalcLength { NoCalcLength::Absolute(Au((px_value * AU_PER_PX) as i32)) } } /// An extension to `NoCalcLength` to parse `calc` expressions. /// This is commonly used for the `` values. /// /// https://drafts.csswg.org/css-values/#lengths #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum Length { /// The internal length type that cannot parse `calc` NoCalc(NoCalcLength), /// A calc expression. /// /// https://drafts.csswg.org/css-values/#calc-notation /// /// TODO(emilio): We have more `Calc` variants around, we should only use /// one. Calc(Box, AllowedNumericType), } impl HasViewportPercentage for Length { fn has_viewport_percentage(&self) -> bool { match *self { Length::NoCalc(ref inner) => inner.has_viewport_percentage(), Length::Calc(ref calc, _) => calc.has_viewport_percentage(), } } } impl ToCss for Length { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { Length::NoCalc(ref inner) => inner.to_css(dest), Length::Calc(ref calc, _) => calc.to_css(dest), } } } impl Mul for Length { type Output = Length; #[inline] fn mul(self, scalar: CSSFloat) -> Length { match self { Length::NoCalc(inner) => Length::NoCalc(inner * scalar), Length::Calc(..) => panic!("Can't multiply Calc!"), } } } impl Mul for FontRelativeLength { type Output = FontRelativeLength; #[inline] fn mul(self, scalar: CSSFloat) -> FontRelativeLength { match self { FontRelativeLength::Em(v) => FontRelativeLength::Em(v * scalar), FontRelativeLength::Ex(v) => FontRelativeLength::Ex(v * scalar), FontRelativeLength::Ch(v) => FontRelativeLength::Ch(v * scalar), FontRelativeLength::Rem(v) => FontRelativeLength::Rem(v * scalar), } } } impl Mul for ViewportPercentageLength { type Output = ViewportPercentageLength; #[inline] fn mul(self, scalar: CSSFloat) -> ViewportPercentageLength { match self { ViewportPercentageLength::Vw(v) => ViewportPercentageLength::Vw(v * scalar), ViewportPercentageLength::Vh(v) => ViewportPercentageLength::Vh(v * scalar), ViewportPercentageLength::Vmin(v) => ViewportPercentageLength::Vmin(v * scalar), ViewportPercentageLength::Vmax(v) => ViewportPercentageLength::Vmax(v * scalar), } } } impl Length { #[inline] /// Returns a `zero` length. pub fn zero() -> Length { Length::NoCalc(NoCalcLength::zero()) } /// https://drafts.csswg.org/css-fonts-3/#font-size-prop pub fn from_str(s: &str) -> Option { NoCalcLength::from_str(s).map(Length::NoCalc) } /// Parse a given absolute or relative dimension. pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result { NoCalcLength::parse_dimension(value, unit).map(Length::NoCalc) } /// https://drafts.csswg.org/css-fonts-3/#font-size-prop pub fn from_font_size_int(i: u8) -> Self { Length::NoCalc(NoCalcLength::from_font_size_int(i)) } #[inline] fn parse_internal(input: &mut Parser, context: AllowedNumericType) -> Result { match try!(input.next()) { Token::Dimension(ref value, ref unit) if context.is_ok(value.value) => Length::parse_dimension(value.value, unit), Token::Number(ref value) if value.value == 0. => Ok(Length::zero()), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => input.parse_nested_block(|input| { CalcLengthOrPercentage::parse_length(input, context) }), _ => Err(()) } } /// Parse a non-negative length #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { Length::parse_internal(input, AllowedNumericType::NonNegative) } /// Get an absolute length from a px value. #[inline] pub fn from_px(px_value: CSSFloat) -> Length { Length::NoCalc(NoCalcLength::from_px(px_value)) } /// Extract inner length without a clone, replacing it with a 0 Au /// /// Use when you need to move out of a length array without cloning #[inline] pub fn take(&mut self) -> Self { mem::replace(self, Length::zero()) } } impl Parse for Length { fn parse(_context: &ParserContext, input: &mut Parser) -> Result { Length::parse_internal(input, AllowedNumericType::All) } } impl Either { #[inline] #[allow(missing_docs)] pub fn parse_non_negative_length(_context: &ParserContext, input: &mut Parser) -> Result { Length::parse_internal(input, AllowedNumericType::NonNegative).map(Either::First) } } /// A calc sum expression node. #[derive(Clone, Debug)] pub struct CalcSumNode { /// The products of this node. pub products: Vec, } /// A calc product expression node. #[derive(Clone, Debug)] pub struct CalcProductNode { /// The values inside this product node. values: Vec } /// A value inside a `Calc` expression. #[derive(Clone, Debug)] #[allow(missing_docs)] pub enum CalcValueNode { Length(NoCalcLength), Angle(Angle), Time(Time), Percentage(CSSFloat), Number(CSSFloat), Sum(Box), } #[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, pub vw: Option, pub vh: Option, pub vmin: Option, pub vmax: Option, pub em: Option, pub ex: Option, pub ch: Option, pub rem: Option, pub percentage: Option, } impl CalcLengthOrPercentage { /// Parse a calc sum node. pub fn parse_sum(input: &mut Parser, expected_unit: CalcUnit) -> Result { let mut products = Vec::new(); products.push(try!(CalcLengthOrPercentage::parse_product(input, expected_unit))); while let Ok(token) = input.next() { match token { Token::Delim('+') => { products.push(try!(CalcLengthOrPercentage::parse_product(input, expected_unit))); } Token::Delim('-') => { let mut right = try!(CalcLengthOrPercentage::parse_product(input, expected_unit)); right.values.push(CalcValueNode::Number(-1.)); products.push(right); } _ => return Err(()) } } Ok(CalcSumNode { products: products }) } fn parse_product(input: &mut Parser, expected_unit: CalcUnit) -> Result { let mut values = Vec::new(); values.push(try!(CalcLengthOrPercentage::parse_value(input, expected_unit))); loop { let position = input.position(); match input.next() { Ok(Token::Delim('*')) => { values.push(try!(CalcLengthOrPercentage::parse_value(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(input: &mut Parser, expected_unit: CalcUnit) -> Result { 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(value.value, unit).map(CalcValueNode::Length) } (Token::Dimension(ref value, ref unit), CalcUnit::Angle) => { Angle::parse_dimension(value.value, unit).map(CalcValueNode::Angle) } (Token::Dimension(ref value, ref unit), CalcUnit::Time) => { Time::parse_dimension(value.value, unit).map(CalcValueNode::Time) } (Token::Percentage(ref value), CalcUnit::LengthOrPercentage) => Ok(CalcValueNode::Percentage(value.unit_value)), (Token::ParenthesisBlock, _) => { input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(i, expected_unit)) .map(|result| CalcValueNode::Sum(Box::new(result))) }, _ => Err(()) } } fn simplify_value_to_number(node: &CalcValueNode) -> Option { 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 { 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 { 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 { 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 { 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(input: &mut Parser, context: AllowedNumericType) -> Result { CalcLengthOrPercentage::parse(input, CalcUnit::Length).map(|calc| { Length::Calc(Box::new(calc), context) }) } fn parse_length_or_percentage(input: &mut Parser) -> Result { CalcLengthOrPercentage::parse(input, CalcUnit::LengthOrPercentage) } #[allow(missing_docs)] pub fn parse(input: &mut Parser, expected_unit: CalcUnit) -> Result { let ast = try!(CalcLengthOrPercentage::parse_sum(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(Au(au))) => absolute = Some(absolute.unwrap_or(0) + au), 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), 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(input: &mut Parser) -> Result { let ast = try!(CalcLengthOrPercentage::parse_sum(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(Time(val)) => time = Some(time.unwrap_or(0.) + val), _ => return Err(()), } } match time { Some(time) => Ok(Time(time)), _ => Err(()) } } #[allow(missing_docs)] pub fn parse_angle(input: &mut Parser) -> Result { let ast = try!(CalcLengthOrPercentage::parse_sum(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(Angle(val)) => angle = Some(angle.unwrap_or(0.) + val), SimplifiedValueNode::Number(val) => number = Some(number.unwrap_or(0.) + val), _ => unreachable!() } } match (angle, number) { (Some(angle), None) => Ok(Angle(angle)), (None, Some(value)) if value == 0. => Ok(Angle(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(&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] #[derive(Clone, PartialEq, Copy, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct Percentage(pub CSSFloat); impl ToCss for Percentage { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { write!(dest, "{}%", self.0 * 100.) } } impl Parse for Percentage { #[inline] fn parse(_context: &ParserContext, input: &mut Parser) -> Result { let context = AllowedNumericType::All; match try!(input.next()) { Token::Percentage(ref value) if context.is_ok(value.unit_value) => Ok(Percentage(value.unit_value)), _ => Err(()) } } } /// 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)] pub enum LengthOrPercentage { Length(NoCalcLength), Percentage(Percentage), Calc(Box), } impl From for LengthOrPercentage { fn from(len: Length) -> LengthOrPercentage { match len { Length::NoCalc(l) => LengthOrPercentage::Length(l), Length::Calc(l, _) => LengthOrPercentage::Calc(l), } } } impl HasViewportPercentage for LengthOrPercentage { fn has_viewport_percentage(&self) -> bool { match *self { LengthOrPercentage::Length(ref length) => length.has_viewport_percentage(), LengthOrPercentage::Calc(ref calc) => calc.has_viewport_percentage(), _ => false } } } impl ToCss for LengthOrPercentage { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentage::Length(ref length) => length.to_css(dest), LengthOrPercentage::Percentage(percentage) => percentage.to_css(dest), LengthOrPercentage::Calc(ref calc) => calc.to_css(dest), } } } impl LengthOrPercentage { /// Returns a `zero` length. pub fn zero() -> LengthOrPercentage { LengthOrPercentage::Length(NoCalcLength::zero()) } fn parse_internal(input: &mut Parser, context: AllowedNumericType) -> Result { match try!(input.next()) { Token::Dimension(ref value, ref unit) if context.is_ok(value.value) => NoCalcLength::parse_dimension(value.value, unit).map(LengthOrPercentage::Length), Token::Percentage(ref value) if context.is_ok(value.unit_value) => Ok(LengthOrPercentage::Percentage(Percentage(value.unit_value))), Token::Number(ref value) if value.value == 0. => Ok(LengthOrPercentage::zero()), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { let calc = try!(input.parse_nested_block(CalcLengthOrPercentage::parse_length_or_percentage)); Ok(LengthOrPercentage::Calc(Box::new(calc))) }, _ => Err(()) } } /// Parse a non-negative length. #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { LengthOrPercentage::parse_internal(input, AllowedNumericType::NonNegative) } /// Extract value from ref without a clone, replacing it with a 0 Au /// /// Use when you need to move out of a length array without cloning #[inline] pub fn take(&mut self) -> Self { mem::replace(self, LengthOrPercentage::zero()) } } impl Parse for LengthOrPercentage { #[inline] fn parse(_context: &ParserContext, input: &mut Parser) -> Result { LengthOrPercentage::parse_internal(input, AllowedNumericType::All) } } /// TODO(emilio): Do the Length and Percentage variants make any sense with /// CalcLengthOrPercentage? #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[allow(missing_docs)] pub enum LengthOrPercentageOrAuto { Length(NoCalcLength), Percentage(Percentage), Auto, Calc(Box), } impl HasViewportPercentage for LengthOrPercentageOrAuto { fn has_viewport_percentage(&self) -> bool { match *self { LengthOrPercentageOrAuto::Length(ref length) => length.has_viewport_percentage(), LengthOrPercentageOrAuto::Calc(ref calc) => calc.has_viewport_percentage(), _ => false } } } impl ToCss for LengthOrPercentageOrAuto { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrAuto::Length(ref length) => length.to_css(dest), LengthOrPercentageOrAuto::Percentage(percentage) => percentage.to_css(dest), LengthOrPercentageOrAuto::Auto => dest.write_str("auto"), LengthOrPercentageOrAuto::Calc(ref calc) => calc.to_css(dest), } } } impl LengthOrPercentageOrAuto { fn parse_internal(input: &mut Parser, context: AllowedNumericType) -> Result { match try!(input.next()) { Token::Dimension(ref value, ref unit) if context.is_ok(value.value) => NoCalcLength::parse_dimension(value.value, unit).map(LengthOrPercentageOrAuto::Length), Token::Percentage(ref value) if context.is_ok(value.unit_value) => Ok(LengthOrPercentageOrAuto::Percentage(Percentage(value.unit_value))), Token::Number(ref value) if value.value == 0. => Ok(LengthOrPercentageOrAuto::Length(NoCalcLength::zero())), Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") => Ok(LengthOrPercentageOrAuto::Auto), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { let calc = try!(input.parse_nested_block(CalcLengthOrPercentage::parse_length_or_percentage)); Ok(LengthOrPercentageOrAuto::Calc(Box::new(calc))) }, _ => Err(()) } } /// Parse a non-negative length, percentage, or auto. #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { LengthOrPercentageOrAuto::parse_internal(input, AllowedNumericType::NonNegative) } } impl Parse for LengthOrPercentageOrAuto { #[inline] fn parse(_context: &ParserContext, input: &mut Parser) -> Result { LengthOrPercentageOrAuto::parse_internal(input, AllowedNumericType::All) } } /// TODO(emilio): Do the Length and Percentage variants make any sense with /// CalcLengthOrPercentage? #[derive(Clone, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[allow(missing_docs)] pub enum LengthOrPercentageOrNone { Length(NoCalcLength), Percentage(Percentage), Calc(Box), None, } impl HasViewportPercentage for LengthOrPercentageOrNone { fn has_viewport_percentage(&self) -> bool { match *self { LengthOrPercentageOrNone::Length(ref length) => length.has_viewport_percentage(), LengthOrPercentageOrNone::Calc(ref calc) => calc.has_viewport_percentage(), _ => false } } } impl ToCss for LengthOrPercentageOrNone { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrNone::Length(ref length) => length.to_css(dest), LengthOrPercentageOrNone::Percentage(ref percentage) => percentage.to_css(dest), LengthOrPercentageOrNone::Calc(ref calc) => calc.to_css(dest), LengthOrPercentageOrNone::None => dest.write_str("none"), } } } impl LengthOrPercentageOrNone { fn parse_internal(input: &mut Parser, context: AllowedNumericType) -> Result { match try!(input.next()) { Token::Dimension(ref value, ref unit) if context.is_ok(value.value) => NoCalcLength::parse_dimension(value.value, unit).map(LengthOrPercentageOrNone::Length), Token::Percentage(ref value) if context.is_ok(value.unit_value) => Ok(LengthOrPercentageOrNone::Percentage(Percentage(value.unit_value))), Token::Number(ref value) if value.value == 0. => Ok(LengthOrPercentageOrNone::Length(NoCalcLength::zero())), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { let calc = try!(input.parse_nested_block(CalcLengthOrPercentage::parse_length_or_percentage)); Ok(LengthOrPercentageOrNone::Calc(Box::new(calc))) }, Token::Ident(ref value) if value.eq_ignore_ascii_case("none") => Ok(LengthOrPercentageOrNone::None), _ => Err(()) } } /// Parse a non-negative LengthOrPercentageOrNone. #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { LengthOrPercentageOrNone::parse_internal(input, AllowedNumericType::NonNegative) } } impl Parse for LengthOrPercentageOrNone { #[inline] fn parse(_context: &ParserContext, input: &mut Parser) -> Result { LengthOrPercentageOrNone::parse_internal(input, AllowedNumericType::All) } } /// Either a `` or the `none` keyword. pub type LengthOrNone = Either; /// Either a `` or the `normal` keyword. pub type LengthOrNormal = Either; /// Either a `` or the `auto` keyword. pub type LengthOrAuto = Either; /// Either a `` or a `` 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 { /// A ``. Length(NoCalcLength), /// A percentage. Percentage(Percentage), /// A `calc` node. Calc(Box), /// The `auto` keyword. Auto, /// The `content` keyword. Content } impl HasViewportPercentage for LengthOrPercentageOrAutoOrContent { fn has_viewport_percentage(&self) -> bool { match *self { LengthOrPercentageOrAutoOrContent::Length(ref length) => length.has_viewport_percentage(), LengthOrPercentageOrAutoOrContent::Calc(ref calc) => calc.has_viewport_percentage(), _ => false } } } impl ToCss for LengthOrPercentageOrAutoOrContent { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrAutoOrContent::Length(ref len) => len.to_css(dest), LengthOrPercentageOrAutoOrContent::Percentage(perc) => perc.to_css(dest), LengthOrPercentageOrAutoOrContent::Auto => dest.write_str("auto"), LengthOrPercentageOrAutoOrContent::Content => dest.write_str("content"), LengthOrPercentageOrAutoOrContent::Calc(ref calc) => calc.to_css(dest), } } } impl Parse for LengthOrPercentageOrAutoOrContent { fn parse(_context: &ParserContext, input: &mut Parser) -> Result { let context = AllowedNumericType::NonNegative; match try!(input.next()) { Token::Dimension(ref value, ref unit) if context.is_ok(value.value) => NoCalcLength::parse_dimension(value.value, unit).map(LengthOrPercentageOrAutoOrContent::Length), Token::Percentage(ref value) if context.is_ok(value.unit_value) => Ok(LengthOrPercentageOrAutoOrContent::Percentage(Percentage(value.unit_value))), Token::Number(ref value) if value.value == 0. => Ok(LengthOrPercentageOrAutoOrContent::Length(NoCalcLength::zero())), Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") => Ok(LengthOrPercentageOrAutoOrContent::Auto), Token::Ident(ref value) if value.eq_ignore_ascii_case("content") => Ok(LengthOrPercentageOrAutoOrContent::Content), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { let calc = try!(input.parse_nested_block(CalcLengthOrPercentage::parse_length_or_percentage)); Ok(LengthOrPercentageOrAutoOrContent::Calc(Box::new(calc))) }, _ => Err(()) } } } /// Either a `` or a ``. pub type LengthOrNumber = Either; impl LengthOrNumber { /// Parse a non-negative LengthOrNumber. pub fn parse_non_negative(_context: &ParserContext, input: &mut Parser) -> Result { // We try to parse as a Number first because, for cases like LengthOrNumber, // we want "0" to be parsed as a plain Number rather than a Length (0px); this // matches the behaviour of all major browsers if let Ok(v) = input.try(Number::parse_non_negative) { Ok(Either::Second(v)) } else { Length::parse_non_negative(input).map(Either::First) } } }