/* 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/. */ pub use cssparser::RGBA; use app_units::Au; use cssparser::CssStringWriter; use std::fmt::{self, Write}; use url::Url; // This is a re-implementation of the ToCss trait in cssparser. // It's done here because the app_units crate shouldn't depend // on cssparser, and it's not possible to implement a trait when // both the trait and the type are defined in different crates. pub trait AuExtensionMethods { /// Serialize `self` in CSS syntax, writing to `dest`. fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write; /// Serialize `self` in CSS syntax and return a string. /// /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) #[inline] fn to_css_string(&self) -> String { let mut s = String::new(); self.to_css(&mut s).unwrap(); s } } impl AuExtensionMethods for Au { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { write!(dest, "{}px", self.to_f64_px()) } } macro_rules! define_numbered_css_keyword_enum { ($name: ident: $( $css: expr => $variant: ident = $value: expr ),+,) => { define_numbered_css_keyword_enum!($name: $( $css => $variant = $value ),+); }; ($name: ident: $( $css: expr => $variant: ident = $value: expr ),+) => { #[allow(non_camel_case_types)] #[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Copy, RustcEncodable, Debug, HeapSizeOf)] #[derive(Deserialize, Serialize)] pub enum $name { $( $variant = $value ),+ } impl $name { pub fn parse(input: &mut ::cssparser::Parser) -> Result<$name, ()> { match_ignore_ascii_case! { try!(input.expect_ident()), $( $css => Ok($name::$variant), )+ _ => Err(()) } } } impl ::cssparser::ToCss for $name { fn to_css(&self, dest: &mut W) -> ::std::fmt::Result where W: ::std::fmt::Write { match *self { $( $name::$variant => dest.write_str($css) ),+ } } } } } /// The real ToCss trait can’t be implemented for Url /// since neither rust-url or rust-cssparser depend on the other. pub trait LocalToCss { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write; } impl LocalToCss for Url { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(dest.write_str("url(\"")); try!(write!(CssStringWriter::new(dest), "{}", self)); try!(dest.write_str("\")")); Ok(()) } } pub type CSSFloat = f32; pub const FONT_MEDIUM_PX: i32 = 16; pub mod specified { use app_units::Au; use cssparser::{self, Parser, ToCss, Token}; use euclid::size::Size2D; use parser::ParserContext; use std::ascii::AsciiExt; use std::cmp; use std::f32::consts::PI; use std::fmt; use std::ops::Mul; use style_traits::values::specified::AllowedNumericType; use super::AuExtensionMethods; use super::computed::{TContext, ToComputedValue}; use super::{CSSFloat, FONT_MEDIUM_PX}; use url::Url; #[derive(Clone, PartialEq, Debug, HeapSizeOf)] pub struct CSSColor { pub parsed: cssparser::Color, pub authored: Option, } impl CSSColor { pub fn parse(input: &mut Parser) -> Result { let start_position = input.position(); let authored = match input.next() { Ok(Token::Ident(s)) => Some(s.into_owned()), _ => None, }; input.reset(start_position); Ok(CSSColor { parsed: try!(cssparser::Color::parse(input)), authored: authored, }) } } impl ToCss for CSSColor { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match self.authored { Some(ref s) => dest.write_str(s), None => self.parsed.to_css(dest), } } } #[derive(Clone, PartialEq, Debug, HeapSizeOf)] pub struct CSSRGBA { pub parsed: cssparser::RGBA, pub authored: Option, } impl ToCss for CSSRGBA { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match self.authored { Some(ref s) => dest.write_str(s), None => self.parsed.to_css(dest), } } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum FontRelativeLength { Em(CSSFloat), Ex(CSSFloat), Ch(CSSFloat), 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 { pub fn to_computed_value(&self, reference_font_size: Au, root_font_size: Au) -> Au { match *self { FontRelativeLength::Em(length) => reference_font_size.scale_by(length), FontRelativeLength::Ex(length) | FontRelativeLength::Ch(length) => { // https://github.com/servo/servo/issues/7462 let em_factor = 0.5; reference_font_size.scale_by(length * em_factor) }, FontRelativeLength::Rem(length) => root_font_size.scale_by(length) } } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum ViewportPercentageLength { Vw(CSSFloat), Vh(CSSFloat), Vmin(CSSFloat), Vmax(CSSFloat) } 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 { 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) } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub struct CharacterWidth(pub i32); impl CharacterWidth { 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 } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum Length { Absolute(Au), // application units FontRelative(FontRelativeLength), 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), Calc(CalcLengthOrPercentage), } impl ToCss for Length { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { Length::Absolute(length) => write!(dest, "{}px", length.to_f32_px()), Length::FontRelative(length) => length.to_css(dest), Length::ViewportPercentage(length) => length.to_css(dest), Length::Calc(calc) => calc.to_css(dest), Length::ServoCharacterWidth(_) => panic!("internal CSS values should never be serialized"), } } } impl Mul for Length { type Output = Length; #[inline] fn mul(self, scalar: CSSFloat) -> Length { match self { Length::Absolute(Au(v)) => Length::Absolute(Au(((v as f32) * scalar) as i32)), Length::FontRelative(v) => Length::FontRelative(v * scalar), Length::ViewportPercentage(v) => Length::ViewportPercentage(v * scalar), Length::Calc(_) => panic!("Can't multiply Calc!"), Length::ServoCharacterWidth(_) => panic!("Can't multiply ServoCharacterWidth!"), } } } 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), } } } 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.; impl Length { // 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" => Length::Absolute(Au::from_px(FONT_MEDIUM_PX) * 3 / 5), "x-small" => Length::Absolute(Au::from_px(FONT_MEDIUM_PX) * 3 / 4), "small" => Length::Absolute(Au::from_px(FONT_MEDIUM_PX) * 8 / 9), "medium" => Length::Absolute(Au::from_px(FONT_MEDIUM_PX)), "large" => Length::Absolute(Au::from_px(FONT_MEDIUM_PX) * 6 / 5), "x-large" => Length::Absolute(Au::from_px(FONT_MEDIUM_PX) * 3 / 2), "xx-large" => Length::Absolute(Au::from_px(FONT_MEDIUM_PX) * 2), // https://github.com/servo/servo/issues/3423#issuecomment-56321664 "smaller" => Length::FontRelative(FontRelativeLength::Em(0.85)), "larger" => Length::FontRelative(FontRelativeLength::Em(1.2)), _ => return None }) } #[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::Absolute(Au(0))), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => input.parse_nested_block(CalcLengthOrPercentage::parse_length), _ => Err(()) } } pub fn parse(input: &mut Parser) -> Result { Length::parse_internal(input, &AllowedNumericType::All) } pub fn parse_non_negative(input: &mut Parser) -> Result { Length::parse_internal(input, &AllowedNumericType::NonNegative) } pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result { match_ignore_ascii_case! { unit, "px" => Ok(Length::from_px(value)), "in" => Ok(Length::Absolute(Au((value * AU_PER_IN) as i32))), "cm" => Ok(Length::Absolute(Au((value * AU_PER_CM) as i32))), "mm" => Ok(Length::Absolute(Au((value * AU_PER_MM) as i32))), "q" => Ok(Length::Absolute(Au((value * AU_PER_Q) as i32))), "pt" => Ok(Length::Absolute(Au((value * AU_PER_PT) as i32))), "pc" => Ok(Length::Absolute(Au((value * AU_PER_PC) as i32))), // font-relative "em" => Ok(Length::FontRelative(FontRelativeLength::Em(value))), "ex" => Ok(Length::FontRelative(FontRelativeLength::Ex(value))), "ch" => Ok(Length::FontRelative(FontRelativeLength::Ch(value))), "rem" => Ok(Length::FontRelative(FontRelativeLength::Rem(value))), // viewport percentages "vw" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vw(value))), "vh" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vh(value))), "vmin" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vmin(value))), "vmax" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vmax(value))), _ => Err(()) } } #[inline] pub fn from_px(px_value: CSSFloat) -> Length { Length::Absolute(Au((px_value * AU_PER_PX) as i32)) } } #[derive(Clone, Debug)] struct CalcSumNode { products: Vec, } #[derive(Clone, Debug)] struct CalcProductNode { values: Vec } #[derive(Clone, Debug)] enum CalcValueNode { Length(Length), Angle(Angle), Time(Time), Percentage(CSSFloat), Number(CSSFloat), Sum(Box), } #[derive(Clone, Debug)] struct SimplifiedSumNode { values: Vec, } impl<'a> Mul 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)] enum SimplifiedValueNode { Length(Length), Angle(Angle), Time(Time), Percentage(CSSFloat), Number(CSSFloat), Sum(Box), } impl<'a> Mul for &'a SimplifiedValueNode { type Output = SimplifiedValueNode; #[inline] fn mul(self, scalar: CSSFloat) -> SimplifiedValueNode { match *self { SimplifiedValueNode::Length(l) => SimplifiedValueNode::Length(l * scalar), SimplifiedValueNode::Percentage(p) => SimplifiedValueNode::Percentage(p * scalar), SimplifiedValueNode::Angle(Angle(a)) => SimplifiedValueNode::Angle(Angle(a * scalar)), SimplifiedValueNode::Time(Time(t)) => SimplifiedValueNode::Time(Time(t * scalar)), SimplifiedValueNode::Number(n) => SimplifiedValueNode::Number(n * scalar), SimplifiedValueNode::Sum(box ref s) => { let sum = s * scalar; SimplifiedValueNode::Sum(box sum) } } } } pub fn parse_integer(input: &mut Parser) -> Result { match try!(input.next()) { Token::Number(ref value) => value.int_value.ok_or(()), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { let ast = try!(input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(i, CalcUnit::Integer))); 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 i32), _ => unreachable!() } } match result { Some(result) => Ok(result), _ => Err(()) } } _ => Err(()) } } pub fn parse_number(input: &mut Parser) -> Result { match try!(input.next()) { Token::Number(ref value) => Ok(value.value), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { let ast = try!(input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(i, CalcUnit::Number))); 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(result), _ => Err(()) } } _ => Err(()) } } #[derive(Clone, Copy, PartialEq)] enum CalcUnit { Number, Integer, Length, LengthOrPercentage, Angle, Time, } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] 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 { 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) => { Length::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 result)) }, _ => Err(()) } } fn simplify_value_to_number(node: &CalcValueNode) -> Option { match *node { CalcValueNode::Number(number) => Some(number), CalcValueNode::Sum(box 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(box sum) => simplified.extend_from_slice(&sum.values), val => simplified.push(val), } } if simplified.len() == 1 { Ok(simplified[0].clone()) } else { Ok(SimplifiedValueNode::Sum(box SimplifiedSumNode { values: simplified } )) } } 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(box ref sum) => try!(CalcLengthOrPercentage::simplify_products_in_sum(sum)), CalcValueNode::Length(l) => SimplifiedValueNode::Length(l), 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) -> Result { CalcLengthOrPercentage::parse(input, CalcUnit::Length).map(Length::Calc) } fn parse_length_or_percentage(input: &mut Parser) -> Result { CalcLengthOrPercentage::parse(input, CalcUnit::LengthOrPercentage) } 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; let mut number = None; for value in simplified { match value { SimplifiedValueNode::Percentage(p) => percentage = Some(percentage.unwrap_or(0.) + p), SimplifiedValueNode::Length(Length::Absolute(Au(au))) => absolute = Some(absolute.unwrap_or(0) + au), SimplifiedValueNode::Length(Length::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(Length::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), }, SimplifiedValueNode::Number(val) => number = Some(number.unwrap_or(0.) + val), _ => return Err(()), } } Ok(CalcLengthOrPercentage { absolute: absolute.map(Au), vw: vw.map(ViewportPercentageLength::Vw), vh: vh.map(ViewportPercentageLength::Vh), vmax: vmax.map(ViewportPercentageLength::Vmax), vmin: vmin.map(ViewportPercentageLength::Vmin), em: em.map(FontRelativeLength::Em), ex: ex.map(FontRelativeLength::Ex), ch: ch.map(FontRelativeLength::Ch), rem: rem.map(FontRelativeLength::Rem), percentage: percentage.map(Percentage), }) } 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(()) } } 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 ToCss for CalcLengthOrPercentage { #[allow(unused_assignments)] fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { macro_rules! count { ( $( $val:ident ),* ) => { { let mut count = 0; $( if let Some(_) = self.$val { count += 1; } )* count } }; } macro_rules! serialize { ( $( $val:ident ),* ) => { { let mut first_value = true; $( if let Some(val) = self.$val { if !first_value { try!(write!(dest, " + ")); } else { first_value = false; } try!(val.to_css(dest)); } )* } }; } let count = count!(ch, em, ex, absolute, rem, vh, vmax, vmin, vw, percentage); assert!(count > 0); if count > 1 { try!(write!(dest, "calc(")); } serialize!(ch, em, ex, absolute, rem, vh, vmax, vmin, vw, percentage); if count > 1 { try!(write!(dest, ")")); } Ok(()) } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub struct Percentage(pub CSSFloat); // [0 .. 100%] maps to [0.0 .. 1.0] impl ToCss for Percentage { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { write!(dest, "{}%", self.0 * 100.) } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum LengthOrPercentage { Length(Length), Percentage(Percentage), Calc(CalcLengthOrPercentage), } impl ToCss for LengthOrPercentage { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentage::Length(length) => length.to_css(dest), LengthOrPercentage::Percentage(percentage) => percentage.to_css(dest), LengthOrPercentage::Calc(calc) => calc.to_css(dest), } } } impl LengthOrPercentage { pub fn zero() -> LengthOrPercentage { LengthOrPercentage::Length(Length::Absolute(Au(0))) } 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).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::Length(Length::Absolute(Au(0)))), 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(calc)) }, _ => Err(()) } } #[inline] pub fn parse(input: &mut Parser) -> Result { LengthOrPercentage::parse_internal(input, &AllowedNumericType::All) } #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { LengthOrPercentage::parse_internal(input, &AllowedNumericType::NonNegative) } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum LengthOrPercentageOrAuto { Length(Length), Percentage(Percentage), Auto, Calc(CalcLengthOrPercentage), } impl ToCss for LengthOrPercentageOrAuto { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrAuto::Length(length) => length.to_css(dest), LengthOrPercentageOrAuto::Percentage(percentage) => percentage.to_css(dest), LengthOrPercentageOrAuto::Auto => dest.write_str("auto"), LengthOrPercentageOrAuto::Calc(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) => Length::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(Length::Absolute(Au(0)))), 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(calc)) }, _ => Err(()) } } #[inline] pub fn parse(input: &mut Parser) -> Result { LengthOrPercentageOrAuto::parse_internal(input, &AllowedNumericType::All) } #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { LengthOrPercentageOrAuto::parse_internal(input, &AllowedNumericType::NonNegative) } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum LengthOrPercentageOrNone { Length(Length), Percentage(Percentage), Calc(CalcLengthOrPercentage), None, } impl ToCss for LengthOrPercentageOrNone { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrNone::Length(length) => length.to_css(dest), LengthOrPercentageOrNone::Percentage(percentage) => percentage.to_css(dest), LengthOrPercentageOrNone::Calc(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) => Length::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(Length::Absolute(Au(0)))), 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(calc)) }, Token::Ident(ref value) if value.eq_ignore_ascii_case("none") => Ok(LengthOrPercentageOrNone::None), _ => Err(()) } } #[inline] pub fn parse(input: &mut Parser) -> Result { LengthOrPercentageOrNone::parse_internal(input, &AllowedNumericType::All) } #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { LengthOrPercentageOrNone::parse_internal(input, &AllowedNumericType::NonNegative) } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum LengthOrNone { Length(Length), None, } impl ToCss for LengthOrNone { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrNone::Length(length) => length.to_css(dest), LengthOrNone::None => dest.write_str("none"), } } } impl LengthOrNone { 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).map(LengthOrNone::Length), Token::Number(ref value) if value.value == 0. => Ok(LengthOrNone::Length(Length::Absolute(Au(0)))), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => input.parse_nested_block(CalcLengthOrPercentage::parse_length).map(LengthOrNone::Length), Token::Ident(ref value) if value.eq_ignore_ascii_case("none") => Ok(LengthOrNone::None), _ => Err(()) } } #[inline] pub fn parse(input: &mut Parser) -> Result { LengthOrNone::parse_internal(input, &AllowedNumericType::All) } #[inline] pub fn parse_non_negative(input: &mut Parser) -> Result { LengthOrNone::parse_internal(input, &AllowedNumericType::NonNegative) } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum LengthOrPercentageOrAutoOrContent { Length(Length), Percentage(Percentage), Calc(CalcLengthOrPercentage), Auto, Content } impl ToCss for LengthOrPercentageOrAutoOrContent { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrAutoOrContent::Length(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(calc) => calc.to_css(dest), } } } impl LengthOrPercentageOrAutoOrContent { pub fn parse(input: &mut Parser) -> Result { let context = AllowedNumericType::NonNegative; match try!(input.next()) { Token::Dimension(ref value, ref unit) if context.is_ok(value.value) => Length::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(Length::Absolute(Au(0)))), 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(calc)) }, _ => Err(()) } } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub struct BorderRadiusSize(pub Size2D); impl BorderRadiusSize { pub fn zero() -> BorderRadiusSize { let zero = LengthOrPercentage::Length(Length::Absolute(Au(0))); BorderRadiusSize(Size2D::new(zero, zero)) } pub fn new(width: LengthOrPercentage, height: LengthOrPercentage) -> BorderRadiusSize { BorderRadiusSize(Size2D::new(width, height)) } pub fn circle(radius: LengthOrPercentage) -> BorderRadiusSize { BorderRadiusSize(Size2D::new(radius, radius)) } #[inline] pub fn parse(input: &mut Parser) -> Result { let first = try!(LengthOrPercentage::parse_non_negative(input)); let second = input.try(LengthOrPercentage::parse_non_negative).unwrap_or(first); Ok(BorderRadiusSize(Size2D::new(first, second))) } } impl ToCss for BorderRadiusSize { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(self.0.width.to_css(dest)); try!(dest.write_str(" ")); self.0.height.to_css(dest) } } // http://dev.w3.org/csswg/css2/colors.html#propdef-background-position #[derive(Clone, PartialEq, Copy)] pub enum PositionComponent { LengthOrPercentage(LengthOrPercentage), Center, Left, Right, Top, Bottom, } impl PositionComponent { pub fn parse(input: &mut Parser) -> Result { input.try(LengthOrPercentage::parse) .map(PositionComponent::LengthOrPercentage) .or_else(|()| { match try!(input.next()) { Token::Ident(value) => { match_ignore_ascii_case! { value, "center" => Ok(PositionComponent::Center), "left" => Ok(PositionComponent::Left), "right" => Ok(PositionComponent::Right), "top" => Ok(PositionComponent::Top), "bottom" => Ok(PositionComponent::Bottom), _ => Err(()) } }, _ => Err(()) } }) } #[inline] pub fn to_length_or_percentage(self) -> LengthOrPercentage { match self { PositionComponent::LengthOrPercentage(value) => value, PositionComponent::Center => LengthOrPercentage::Percentage(Percentage(0.5)), PositionComponent::Left | PositionComponent::Top => LengthOrPercentage::Percentage(Percentage(0.0)), PositionComponent::Right | PositionComponent::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)), } } } #[derive(Clone, PartialEq, PartialOrd, Copy, Debug, HeapSizeOf, Deserialize, Serialize)] pub struct Angle(pub CSSFloat); impl ToCss for Angle { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { write!(dest, "{}rad", self.0) } } impl Angle { pub fn radians(self) -> f32 { let Angle(radians) = self; radians } } const RAD_PER_DEG: CSSFloat = PI / 180.0; const RAD_PER_GRAD: CSSFloat = PI / 200.0; const RAD_PER_TURN: CSSFloat = PI * 2.0; impl Angle { /// Parses an angle according to CSS-VALUES § 6.1. pub fn parse(input: &mut Parser) -> Result { match try!(input.next()) { Token::Dimension(ref value, ref unit) => Angle::parse_dimension(value.value, unit), Token::Number(ref value) if value.value == 0. => Ok(Angle(0.)), Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { input.parse_nested_block(CalcLengthOrPercentage::parse_angle) }, _ => Err(()) } } pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result { match_ignore_ascii_case! { unit, "deg" => Ok(Angle(value * RAD_PER_DEG)), "grad" => Ok(Angle(value * RAD_PER_GRAD)), "turn" => Ok(Angle(value * RAD_PER_TURN)), "rad" => Ok(Angle(value)), _ => Err(()) } } } /// Specified values for an image according to CSS-IMAGES. #[derive(Clone, PartialEq, Debug, HeapSizeOf)] pub enum Image { Url(Url), LinearGradient(LinearGradient), } impl ToCss for Image { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use values::LocalToCss; match *self { Image::Url(ref url) => { url.to_css(dest) } Image::LinearGradient(ref gradient) => gradient.to_css(dest) } } } impl Image { pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { if let Ok(url) = input.try(|input| input.expect_url()) { Ok(Image::Url(context.parse_url(&url))) } else { match_ignore_ascii_case! { try!(input.expect_function()), "linear-gradient" => { Ok(Image::LinearGradient(try!( input.parse_nested_block(LinearGradient::parse_function)))) }, _ => Err(()) } } } } /// Specified values for a CSS linear gradient. #[derive(Clone, PartialEq, Debug, HeapSizeOf)] pub struct LinearGradient { /// The angle or corner of the gradient. pub angle_or_corner: AngleOrCorner, /// The color stops. pub stops: Vec, } impl ToCss for LinearGradient { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(dest.write_str("linear-gradient(")); try!(self.angle_or_corner.to_css(dest)); for stop in &self.stops { try!(dest.write_str(", ")); try!(stop.to_css(dest)); } try!(dest.write_str(")")); Ok(()) } } /// Specified values for an angle or a corner in a linear gradient. #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub enum AngleOrCorner { Angle(Angle), Corner(HorizontalDirection, VerticalDirection), } impl ToCss for AngleOrCorner { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { AngleOrCorner::Angle(angle) => angle.to_css(dest), AngleOrCorner::Corner(horizontal, vertical) => { try!(dest.write_str("to ")); try!(horizontal.to_css(dest)); try!(dest.write_str(" ")); try!(vertical.to_css(dest)); Ok(()) } } } } /// Specified values for one color stop in a linear gradient. #[derive(Clone, PartialEq, Debug, HeapSizeOf)] pub struct ColorStop { /// The color of this stop. pub color: CSSColor, /// The position of this stop. If not specified, this stop is placed halfway between the /// point that precedes it and the point that follows it. pub position: Option, } impl ToCss for ColorStop { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(self.color.to_css(dest)); if let Some(position) = self.position { try!(dest.write_str(" ")); try!(position.to_css(dest)); } Ok(()) } } define_css_keyword_enum!(HorizontalDirection: "left" => Left, "right" => Right); define_css_keyword_enum!(VerticalDirection: "top" => Top, "bottom" => Bottom); fn parse_one_color_stop(input: &mut Parser) -> Result { Ok(ColorStop { color: try!(CSSColor::parse(input)), position: input.try(LengthOrPercentage::parse).ok(), }) } impl LinearGradient { /// Parses a linear gradient from the given arguments. pub fn parse_function(input: &mut Parser) -> Result { let angle_or_corner = if input.try(|input| input.expect_ident_matching("to")).is_ok() { let (horizontal, vertical) = if let Ok(value) = input.try(HorizontalDirection::parse) { (Some(value), input.try(VerticalDirection::parse).ok()) } else { let value = try!(VerticalDirection::parse(input)); (input.try(HorizontalDirection::parse).ok(), Some(value)) }; try!(input.expect_comma()); match (horizontal, vertical) { (None, Some(VerticalDirection::Top)) => { AngleOrCorner::Angle(Angle(0.0)) }, (Some(HorizontalDirection::Right), None) => { AngleOrCorner::Angle(Angle(PI * 0.5)) }, (None, Some(VerticalDirection::Bottom)) => { AngleOrCorner::Angle(Angle(PI)) }, (Some(HorizontalDirection::Left), None) => { AngleOrCorner::Angle(Angle(PI * 1.5)) }, (Some(horizontal), Some(vertical)) => { AngleOrCorner::Corner(horizontal, vertical) } (None, None) => unreachable!(), } } else if let Ok(angle) = input.try(Angle::parse) { try!(input.expect_comma()); AngleOrCorner::Angle(angle) } else { AngleOrCorner::Angle(Angle(PI)) }; // Parse the color stops. let stops = try!(input.parse_comma_separated(parse_one_color_stop)); if stops.len() < 2 { return Err(()) } Ok(LinearGradient { angle_or_corner: angle_or_corner, stops: stops, }) } } pub fn parse_border_radius(input: &mut Parser) -> Result { input.try(BorderRadiusSize::parse).or_else(|()| { match_ignore_ascii_case! { try!(input.expect_ident()), "thin" => Ok(BorderRadiusSize::circle( LengthOrPercentage::Length(Length::from_px(1.)))), "medium" => Ok(BorderRadiusSize::circle( LengthOrPercentage::Length(Length::from_px(3.)))), "thick" => Ok(BorderRadiusSize::circle( LengthOrPercentage::Length(Length::from_px(5.)))), _ => Err(()) } }) } pub fn parse_border_width(input: &mut Parser) -> Result { input.try(Length::parse_non_negative).or_else(|()| { match_ignore_ascii_case! { try!(input.expect_ident()), "thin" => Ok(Length::from_px(1.)), "medium" => Ok(Length::from_px(3.)), "thick" => Ok(Length::from_px(5.)), _ => Err(()) } }) } // The integer values here correspond to the border conflict resolution rules in CSS 2.1 § // 17.6.2.1. Higher values override lower values. define_numbered_css_keyword_enum! { BorderStyle: "none" => none = -1, "solid" => solid = 6, "double" => double = 7, "dotted" => dotted = 4, "dashed" => dashed = 5, "hidden" => hidden = -2, "groove" => groove = 1, "ridge" => ridge = 3, "inset" => inset = 0, "outset" => outset = 2, } impl BorderStyle { pub fn none_or_hidden(&self) -> bool { matches!(*self, BorderStyle::none | BorderStyle::hidden) } } /// A time in seconds according to CSS-VALUES § 6.2. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, HeapSizeOf)] pub struct Time(pub CSSFloat); impl Time { /// Returns the time in fractional seconds. pub fn seconds(self) -> f32 { let Time(seconds) = self; seconds } /// Parses a time according to CSS-VALUES § 6.2. fn parse_dimension(value: CSSFloat, unit: &str) -> Result { if unit.eq_ignore_ascii_case("s") { Ok(Time(value)) } else if unit.eq_ignore_ascii_case("ms") { Ok(Time(value / 1000.0)) } else { Err(()) } } pub fn parse(input: &mut Parser) -> Result { match input.next() { Ok(Token::Dimension(ref value, ref unit)) => { Time::parse_dimension(value.value, &unit) } Ok(Token::Function(ref name)) if name.eq_ignore_ascii_case("calc") => { input.parse_nested_block(CalcLengthOrPercentage::parse_time) } _ => Err(()) } } } impl ToComputedValue for Time { type ComputedValue = Time; #[inline] fn to_computed_value(&self, _: &Cx) -> Time { *self } } impl ToCss for Time { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { write!(dest, "{}s", self.0) } } #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, HeapSizeOf)] pub struct Number(pub CSSFloat); impl Number { pub fn parse(input: &mut Parser) -> Result { parse_number(input).map(Number) } fn parse_with_minimum(input: &mut Parser, min: CSSFloat) -> Result { match parse_number(input) { Ok(value) if value < min => Err(()), value => value.map(Number), } } pub fn parse_non_negative(input: &mut Parser) -> Result { Number::parse_with_minimum(input, 0.0) } pub fn parse_at_least_one(input: &mut Parser) -> Result { Number::parse_with_minimum(input, 1.0) } } impl ToComputedValue for Number { type ComputedValue = CSSFloat; #[inline] fn to_computed_value(&self, _: &Cx) -> CSSFloat { self.0 } } impl ToCss for Number { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { self.0.to_css(dest) } } #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, HeapSizeOf)] pub struct Opacity(pub CSSFloat); impl Opacity { pub fn parse(input: &mut Parser) -> Result { parse_number(input).map(Opacity) } } impl ToComputedValue for Opacity { type ComputedValue = CSSFloat; #[inline] fn to_computed_value(&self, _: &Cx) -> CSSFloat { if self.0 < 0.0 { 0.0 } else if self.0 > 1.0 { 1.0 } else { self.0 } } } impl ToCss for Opacity { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { self.0.to_css(dest) } } } pub mod computed { use app_units::Au; use euclid::size::Size2D; use properties::ComputedValues; use properties::style_struct_traits::Font; use std::fmt; use super::AuExtensionMethods; use super::specified::AngleOrCorner; use super::{CSSFloat, specified}; use url::Url; pub use cssparser::Color as CSSColor; pub use super::specified::{Angle, BorderStyle, Time}; pub trait TContext { type ConcreteComputedValues: ComputedValues; fn is_root_element(&self) -> bool; fn viewport_size(&self) -> Size2D; fn inherited_style(&self) -> &Self::ConcreteComputedValues; fn style(&self) -> &Self::ConcreteComputedValues; fn mutate_style(&mut self) -> &mut Self::ConcreteComputedValues; } pub struct Context<'a, C: ComputedValues> { pub is_root_element: bool, pub viewport_size: Size2D, pub inherited_style: &'a C, /// Values access through this need to be in the properties "computed early": /// color, text-decoration, font-size, display, position, float, border-*-style, outline-style pub style: C, } impl<'a, C: ComputedValues> TContext for Context<'a, C> { type ConcreteComputedValues = C; fn is_root_element(&self) -> bool { self.is_root_element } fn viewport_size(&self) -> Size2D { self.viewport_size } fn inherited_style(&self) -> &C { &self.inherited_style } fn style(&self) -> &C { &self.style } fn mutate_style(&mut self) -> &mut C { &mut self.style } } pub trait ToComputedValue { type ComputedValue; #[inline] fn to_computed_value(&self, _context: &Cx) -> Self::ComputedValue; } pub trait ComputedValueAsSpecified {} impl ToComputedValue for T where T: ComputedValueAsSpecified + Clone { type ComputedValue = T; #[inline] fn to_computed_value(&self, _context: &Cx) -> T { self.clone() } } impl ToComputedValue for specified::CSSColor { type ComputedValue = CSSColor; #[inline] fn to_computed_value(&self, _context: &Cx) -> CSSColor { self.parsed } } impl ComputedValueAsSpecified for specified::BorderStyle {} impl ToComputedValue for specified::Length { type ComputedValue = Au; #[inline] fn to_computed_value(&self, context: &Cx) -> Au { match *self { specified::Length::Absolute(length) => length, specified::Length::Calc(calc) => calc.to_computed_value(context).length(), specified::Length::FontRelative(length) => length.to_computed_value(context.style().get_font().clone_font_size(), context.style().root_font_size()), specified::Length::ViewportPercentage(length) => length.to_computed_value(context.viewport_size()), specified::Length::ServoCharacterWidth(length) => length.to_computed_value(context.style().get_font().clone_font_size()) } } } #[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)] pub struct CalcLengthOrPercentage { pub length: Option, pub percentage: Option, } impl CalcLengthOrPercentage { #[inline] pub fn length(&self) -> Au { self.length.unwrap_or(Au(0)) } #[inline] pub fn percentage(&self) -> CSSFloat { self.percentage.unwrap_or(0.) } } impl From for CalcLengthOrPercentage { fn from(len: LengthOrPercentage) -> CalcLengthOrPercentage { match len { LengthOrPercentage::Percentage(this) => { CalcLengthOrPercentage { length: None, percentage: Some(this), } } LengthOrPercentage::Length(this) => { CalcLengthOrPercentage { length: Some(this), percentage: None, } } LengthOrPercentage::Calc(this) => { this } } } } impl From for Option { fn from(len: LengthOrPercentageOrAuto) -> Option { match len { LengthOrPercentageOrAuto::Percentage(this) => { Some(CalcLengthOrPercentage { length: None, percentage: Some(this), }) } LengthOrPercentageOrAuto::Length(this) => { Some(CalcLengthOrPercentage { length: Some(this), percentage: None, }) } LengthOrPercentageOrAuto::Calc(this) => { Some(this) } LengthOrPercentageOrAuto::Auto => { None } } } } impl ::cssparser::ToCss for CalcLengthOrPercentage { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match (self.length, self.percentage) { (None, Some(p)) => write!(dest, "{}%", p * 100.), (Some(l), None) => write!(dest, "{}px", Au::to_px(l)), (Some(l), Some(p)) => write!(dest, "calc({}px + {}%)", Au::to_px(l), p * 100.), _ => unreachable!() } } } impl ToComputedValue for specified::CalcLengthOrPercentage { type ComputedValue = CalcLengthOrPercentage; fn to_computed_value(&self, context: &Cx) -> CalcLengthOrPercentage { let mut length = None; if let Some(absolute) = self.absolute { length = Some(length.unwrap_or(Au(0)) + absolute); } for val in &[self.vw, self.vh, self.vmin, self.vmax] { if let Some(val) = *val { length = Some(length.unwrap_or(Au(0)) + val.to_computed_value(context.viewport_size())); } } for val in &[self.ch, self.em, self.ex, self.rem] { if let Some(val) = *val { length = Some(length.unwrap_or(Au(0)) + val.to_computed_value( context.style().get_font().clone_font_size(), context.style().root_font_size())); } } CalcLengthOrPercentage { length: length, percentage: self.percentage.map(|p| p.0) } } } #[derive(PartialEq, Clone, Copy, HeapSizeOf)] pub struct BorderRadiusSize(pub Size2D); impl BorderRadiusSize { pub fn zero() -> BorderRadiusSize { BorderRadiusSize(Size2D::new(LengthOrPercentage::Length(Au(0)), LengthOrPercentage::Length(Au(0)))) } } impl ToComputedValue for specified::BorderRadiusSize { type ComputedValue = BorderRadiusSize; #[inline] fn to_computed_value(&self, context: &Cx) -> BorderRadiusSize { let w = self.0.width.to_computed_value(context); let h = self.0.height.to_computed_value(context); BorderRadiusSize(Size2D::new(w, h)) } } impl ::cssparser::ToCss for BorderRadiusSize { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(self.0.width.to_css(dest)); try!(dest.write_str("/")); self.0.height.to_css(dest) } } #[derive(PartialEq, Clone, Copy, HeapSizeOf)] pub enum LengthOrPercentage { Length(Au), Percentage(CSSFloat), Calc(CalcLengthOrPercentage), } impl LengthOrPercentage { #[inline] pub fn zero() -> LengthOrPercentage { LengthOrPercentage::Length(Au(0)) } /// Returns true if the computed value is absolute 0 or 0%. /// /// (Returns false for calc() values, even if ones that may resolve to zero.) #[inline] pub fn is_definitely_zero(&self) -> bool { use self::LengthOrPercentage::*; match *self { Length(Au(0)) | Percentage(0.0) => true, Length(_) | Percentage(_) | Calc(_) => false } } } impl fmt::Debug for LengthOrPercentage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LengthOrPercentage::Length(length) => write!(f, "{:?}", length), LengthOrPercentage::Percentage(percentage) => write!(f, "{}%", percentage * 100.), LengthOrPercentage::Calc(calc) => write!(f, "{:?}", calc), } } } impl ToComputedValue for specified::LengthOrPercentage { type ComputedValue = LengthOrPercentage; fn to_computed_value(&self, context: &Cx) -> LengthOrPercentage { match *self { specified::LengthOrPercentage::Length(value) => { LengthOrPercentage::Length(value.to_computed_value(context)) } specified::LengthOrPercentage::Percentage(value) => { LengthOrPercentage::Percentage(value.0) } specified::LengthOrPercentage::Calc(calc) => { LengthOrPercentage::Calc(calc.to_computed_value(context)) } } } } impl ::cssparser::ToCss for LengthOrPercentage { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentage::Length(length) => length.to_css(dest), LengthOrPercentage::Percentage(percentage) => write!(dest, "{}%", percentage * 100.), LengthOrPercentage::Calc(calc) => calc.to_css(dest), } } } #[derive(PartialEq, Clone, Copy, HeapSizeOf)] pub enum LengthOrPercentageOrAuto { Length(Au), Percentage(CSSFloat), Auto, Calc(CalcLengthOrPercentage), } impl LengthOrPercentageOrAuto { /// Returns true if the computed value is absolute 0 or 0%. /// /// (Returns false for calc() values, even if ones that may resolve to zero.) #[inline] pub fn is_definitely_zero(&self) -> bool { use self::LengthOrPercentageOrAuto::*; match *self { Length(Au(0)) | Percentage(0.0) => true, Length(_) | Percentage(_) | Calc(_) | Auto => false } } } impl fmt::Debug for LengthOrPercentageOrAuto { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LengthOrPercentageOrAuto::Length(length) => write!(f, "{:?}", length), LengthOrPercentageOrAuto::Percentage(percentage) => write!(f, "{}%", percentage * 100.), LengthOrPercentageOrAuto::Auto => write!(f, "auto"), LengthOrPercentageOrAuto::Calc(calc) => write!(f, "{:?}", calc), } } } impl ToComputedValue for specified::LengthOrPercentageOrAuto { type ComputedValue = LengthOrPercentageOrAuto; #[inline] fn to_computed_value(&self, context: &Cx) -> LengthOrPercentageOrAuto { match *self { specified::LengthOrPercentageOrAuto::Length(value) => { LengthOrPercentageOrAuto::Length(value.to_computed_value(context)) } specified::LengthOrPercentageOrAuto::Percentage(value) => { LengthOrPercentageOrAuto::Percentage(value.0) } specified::LengthOrPercentageOrAuto::Auto => { LengthOrPercentageOrAuto::Auto } specified::LengthOrPercentageOrAuto::Calc(calc) => { LengthOrPercentageOrAuto::Calc(calc.to_computed_value(context)) } } } } impl ::cssparser::ToCss for LengthOrPercentageOrAuto { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrAuto::Length(length) => length.to_css(dest), LengthOrPercentageOrAuto::Percentage(percentage) => write!(dest, "{}%", percentage * 100.), LengthOrPercentageOrAuto::Auto => dest.write_str("auto"), LengthOrPercentageOrAuto::Calc(calc) => calc.to_css(dest), } } } #[derive(PartialEq, Clone, Copy, HeapSizeOf)] pub enum LengthOrPercentageOrAutoOrContent { Length(Au), Percentage(CSSFloat), Calc(CalcLengthOrPercentage), Auto, Content } impl fmt::Debug for LengthOrPercentageOrAutoOrContent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LengthOrPercentageOrAutoOrContent::Length(length) => write!(f, "{:?}", length), LengthOrPercentageOrAutoOrContent::Percentage(percentage) => write!(f, "{}%", percentage * 100.), LengthOrPercentageOrAutoOrContent::Calc(calc) => write!(f, "{:?}", calc), LengthOrPercentageOrAutoOrContent::Auto => write!(f, "auto"), LengthOrPercentageOrAutoOrContent::Content => write!(f, "content") } } } impl ToComputedValue for specified::LengthOrPercentageOrAutoOrContent { type ComputedValue = LengthOrPercentageOrAutoOrContent; #[inline] fn to_computed_value(&self, context: &Cx) -> LengthOrPercentageOrAutoOrContent { match *self { specified::LengthOrPercentageOrAutoOrContent::Length(value) => { LengthOrPercentageOrAutoOrContent::Length(value.to_computed_value(context)) }, specified::LengthOrPercentageOrAutoOrContent::Percentage(value) => { LengthOrPercentageOrAutoOrContent::Percentage(value.0) }, specified::LengthOrPercentageOrAutoOrContent::Calc(calc) => { LengthOrPercentageOrAutoOrContent::Calc(calc.to_computed_value(context)) }, specified::LengthOrPercentageOrAutoOrContent::Auto => { LengthOrPercentageOrAutoOrContent::Auto }, specified::LengthOrPercentageOrAutoOrContent::Content => { LengthOrPercentageOrAutoOrContent::Content } } } } impl ::cssparser::ToCss for LengthOrPercentageOrAutoOrContent { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrAutoOrContent::Length(length) => length.to_css(dest), LengthOrPercentageOrAutoOrContent::Percentage(percentage) => write!(dest, "{}%", percentage * 100.), LengthOrPercentageOrAutoOrContent::Calc(calc) => calc.to_css(dest), LengthOrPercentageOrAutoOrContent::Auto => dest.write_str("auto"), LengthOrPercentageOrAutoOrContent::Content => dest.write_str("content") } } } #[derive(PartialEq, Clone, Copy, HeapSizeOf)] pub enum LengthOrPercentageOrNone { Length(Au), Percentage(CSSFloat), Calc(CalcLengthOrPercentage), None, } impl fmt::Debug for LengthOrPercentageOrNone { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LengthOrPercentageOrNone::Length(length) => write!(f, "{:?}", length), LengthOrPercentageOrNone::Percentage(percentage) => write!(f, "{}%", percentage * 100.), LengthOrPercentageOrNone::Calc(calc) => write!(f, "{:?}", calc), LengthOrPercentageOrNone::None => write!(f, "none"), } } } impl ToComputedValue for specified::LengthOrPercentageOrNone { type ComputedValue = LengthOrPercentageOrNone; #[inline] fn to_computed_value(&self, context: &Cx) -> LengthOrPercentageOrNone { match *self { specified::LengthOrPercentageOrNone::Length(value) => { LengthOrPercentageOrNone::Length(value.to_computed_value(context)) } specified::LengthOrPercentageOrNone::Percentage(value) => { LengthOrPercentageOrNone::Percentage(value.0) } specified::LengthOrPercentageOrNone::Calc(calc) => { LengthOrPercentageOrNone::Calc(calc.to_computed_value(context)) } specified::LengthOrPercentageOrNone::None => { LengthOrPercentageOrNone::None } } } } impl ::cssparser::ToCss for LengthOrPercentageOrNone { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrPercentageOrNone::Length(length) => length.to_css(dest), LengthOrPercentageOrNone::Percentage(percentage) => write!(dest, "{}%", percentage * 100.), LengthOrPercentageOrNone::Calc(calc) => calc.to_css(dest), LengthOrPercentageOrNone::None => dest.write_str("none"), } } } #[derive(PartialEq, Clone, Copy, HeapSizeOf)] pub enum LengthOrNone { Length(Au), None, } impl fmt::Debug for LengthOrNone { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LengthOrNone::Length(length) => write!(f, "{:?}", length), LengthOrNone::None => write!(f, "none"), } } } impl ToComputedValue for specified::LengthOrNone { type ComputedValue = LengthOrNone; #[inline] fn to_computed_value(&self, context: &Cx) -> LengthOrNone { match *self { specified::LengthOrNone::Length(specified::Length::Calc(calc)) => { LengthOrNone::Length(calc.to_computed_value(context).length()) } specified::LengthOrNone::Length(value) => { LengthOrNone::Length(value.to_computed_value(context)) } specified::LengthOrNone::None => { LengthOrNone::None } } } } impl ::cssparser::ToCss for LengthOrNone { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { LengthOrNone::Length(length) => length.to_css(dest), LengthOrNone::None => dest.write_str("none"), } } } impl ToComputedValue for specified::Image { type ComputedValue = Image; #[inline] fn to_computed_value(&self, context: &Cx) -> Image { match *self { specified::Image::Url(ref url) => Image::Url(url.clone()), specified::Image::LinearGradient(ref linear_gradient) => { Image::LinearGradient(linear_gradient.to_computed_value(context)) } } } } /// Computed values for an image according to CSS-IMAGES. #[derive(Clone, PartialEq, HeapSizeOf)] pub enum Image { Url(Url), LinearGradient(LinearGradient), } impl fmt::Debug for Image { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Image::Url(ref url) => write!(f, "url(\"{}\")", url), Image::LinearGradient(ref grad) => write!(f, "linear-gradient({:?})", grad), } } } /// Computed values for a CSS linear gradient. #[derive(Clone, PartialEq, HeapSizeOf)] pub struct LinearGradient { /// The angle or corner of the gradient. pub angle_or_corner: AngleOrCorner, /// The color stops. pub stops: Vec, } impl ::cssparser::ToCss for LinearGradient { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(dest.write_str("linear-gradient(")); try!(self.angle_or_corner.to_css(dest)); for stop in &self.stops { try!(dest.write_str(", ")); try!(stop.to_css(dest)); } try!(dest.write_str(")")); Ok(()) } } impl fmt::Debug for LinearGradient { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let _ = write!(f, "{:?}", self.angle_or_corner); for stop in &self.stops { let _ = write!(f, ", {:?}", stop); } Ok(()) } } /// Computed values for one color stop in a linear gradient. #[derive(Clone, PartialEq, Copy, HeapSizeOf)] pub struct ColorStop { /// The color of this stop. pub color: CSSColor, /// The position of this stop. If not specified, this stop is placed halfway between the /// point that precedes it and the point that follows it per CSS-IMAGES § 3.4. pub position: Option, } impl ::cssparser::ToCss for ColorStop { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(self.color.to_css(dest)); if let Some(position) = self.position { try!(dest.write_str(" ")); try!(position.to_css(dest)); } Ok(()) } } impl fmt::Debug for ColorStop { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let _ = write!(f, "{:?}", self.color); self.position.map(|pos| { let _ = write!(f, " {:?}", pos); }); Ok(()) } } impl ToComputedValue for specified::LinearGradient { type ComputedValue = LinearGradient; #[inline] fn to_computed_value(&self, context: &Cx) -> LinearGradient { let specified::LinearGradient { angle_or_corner, ref stops } = *self; LinearGradient { angle_or_corner: angle_or_corner, stops: stops.iter().map(|stop| { ColorStop { color: stop.color.parsed, position: match stop.position { None => None, Some(value) => Some(value.to_computed_value(context)), }, } }).collect() } } } pub type Length = Au; pub type Number = CSSFloat; pub type Opacity = CSSFloat; }