/* 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/. */ // This file is a Mako template: http://www.makotemplates.org/ pub use std::ascii::AsciiExt; use servo_util::logical_geometry::{WritingMode, LogicalMargin}; use sync::Arc; pub use url::Url; pub use cssparser::*; pub use cssparser::ast::*; pub use geom::SideOffsets2D; pub use self::common_types::specified::{Angle, AngleAoc, AngleOrCorner, Bottom, CornerAoc}; pub use self::common_types::specified::{Left, Right, Top}; use errors::{ErrorLoggerIterator, log_css_error}; pub use parsing_utils::*; pub use self::common_types::*; use selector_matching::DeclarationBlock; pub use self::property_bit_field::PropertyBitField; pub mod common_types; <%! import re def to_rust_ident(name): name = name.replace("-", "_") if name in ["static", "super", "box"]: # Rust keywords name += "_" return name class Longhand(object): def __init__(self, name, derived_from=None, experimental=False): self.name = name self.ident = to_rust_ident(name) self.camel_case, _ = re.subn( "_([a-z])", lambda m: m.group(1).upper(), self.ident.strip("_").capitalize()) self.style_struct = THIS_STYLE_STRUCT self.experimental = experimental if derived_from is None: self.derived_from = None else: self.derived_from = [ to_rust_ident(name) for name in derived_from ] class Shorthand(object): def __init__(self, name, sub_properties): self.name = name self.ident = to_rust_ident(name) self.sub_properties = [LONGHANDS_BY_NAME[s] for s in sub_properties] class StyleStruct(object): def __init__(self, name, inherited): self.name = name self.ident = to_rust_ident(name.lower()) self.longhands = [] self.inherited = inherited STYLE_STRUCTS = [] THIS_STYLE_STRUCT = None LONGHANDS = [] LONGHANDS_BY_NAME = {} DERIVED_LONGHANDS = {} SHORTHANDS = [] def new_style_struct(name, is_inherited): global THIS_STYLE_STRUCT style_struct = StyleStruct(name, is_inherited) STYLE_STRUCTS.append(style_struct) THIS_STYLE_STRUCT = style_struct return "" def switch_to_style_struct(name): global THIS_STYLE_STRUCT for style_struct in STYLE_STRUCTS: if style_struct.name == name: THIS_STYLE_STRUCT = style_struct return "" fail() %> pub mod longhands { pub use super::*; pub use std; pub fn computed_as_specified(value: T, _context: &computed::Context) -> T { value } <%def name="raw_longhand(name, no_super=False, derived_from=None, experimental=False)"> <% if derived_from is not None: derived_from = derived_from.split() property = Longhand(name, derived_from=derived_from, experimental=experimental) THIS_STYLE_STRUCT.longhands.append(property) LONGHANDS.append(property) LONGHANDS_BY_NAME[name] = property if derived_from is not None: for name in derived_from: DERIVED_LONGHANDS.setdefault(name, []).append(property) %> pub mod ${property.ident} { % if not no_super: use super::*; % endif pub use self::computed_value::*; ${caller.body()} % if derived_from is None: pub fn parse_declared(input: &[ComponentValue], base_url: &Url) -> Result, ()> { match CSSWideKeyword::parse(input) { Ok(InheritKeyword) => Ok(Inherit), Ok(InitialKeyword) => Ok(Initial), Ok(UnsetKeyword) => Ok(${ "Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"}), Err(()) => parse_specified(input, base_url), } } % endif } <%def name="longhand(name, no_super=False, derived_from=None, experimental=False)"> <%self:raw_longhand name="${name}" derived_from="${derived_from}" experimental="${experimental}" no_super="${no_super}"> ${caller.body()} % if derived_from is None: pub fn parse_specified(_input: &[ComponentValue], _base_url: &Url) -> Result, ()> { parse(_input, _base_url).map(super::SpecifiedValue) } % endif <%def name="single_component_value(name, derived_from=None, experimental=False)"> <%self:longhand name="${name}" derived_from="${derived_from}" experimental="${experimental}"> ${caller.body()} pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result { one_component_value(input).and_then(|c| from_component_value(c, base_url)) } <%def name="single_keyword_computed(name, values, experimental=False)"> <%self:single_component_value name="${name}" experimental="${experimental}"> ${caller.body()} pub mod computed_value { #[allow(non_camel_case_types)] #[deriving(PartialEq, Clone, FromPrimitive, Show)] pub enum T { % for value in values.split(): ${to_rust_ident(value)}, % endfor } } pub type SpecifiedValue = computed_value::T; #[inline] pub fn get_initial_value() -> computed_value::T { ${to_rust_ident(values.split()[0])} } pub fn from_component_value(v: &ComponentValue, _base_url: &Url) -> Result { get_ident_lower(v).and_then(|keyword| { match keyword.as_slice() { % for value in values.split(): "${value}" => Ok(${to_rust_ident(value)}), % endfor _ => Err(()), } }) } <%def name="single_keyword(name, values, experimental=False)"> <%self:single_keyword_computed name="${name}" values="${values}" experimental="${experimental}"> // The computed value is the same as the specified value. pub use super::computed_as_specified as to_computed_value; <%def name="predefined_type(name, type, initial_value, parse_method='parse')"> <%self:single_component_value name="${name}"> pub use super::super::common_types::computed::compute_${type} as to_computed_value; pub type SpecifiedValue = specified::${type}; pub mod computed_value { pub type T = super::super::computed::${type}; } #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} } #[inline] pub fn from_component_value(v: &ComponentValue, _base_url: &Url) -> Result { specified::${type}::${parse_method}(v) } // CSS 2.1, Section 8 - Box model ${new_style_struct("Margin", is_inherited=False)} % for side in ["top", "right", "bottom", "left"]: ${predefined_type("margin-" + side, "LengthOrPercentageOrAuto", "computed::LPA_Length(Au(0))")} % endfor ${new_style_struct("Padding", is_inherited=False)} % for side in ["top", "right", "bottom", "left"]: ${predefined_type("padding-" + side, "LengthOrPercentage", "computed::LP_Length(Au(0))", "parse_non_negative")} % endfor ${new_style_struct("Border", is_inherited=False)} % for side in ["top", "right", "bottom", "left"]: ${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")} % endfor ${single_keyword("border-top-style", values="none solid double dotted dashed hidden groove ridge inset outset")} % for side in ["right", "bottom", "left"]: <%self:longhand name="border-${side}-style"> pub use super::border_top_style::{get_initial_value, parse, to_computed_value}; pub type SpecifiedValue = super::border_top_style::SpecifiedValue; pub mod computed_value { pub type T = super::super::border_top_style::computed_value::T; } % endfor pub fn parse_border_width(component_value: &ComponentValue, _base_url: &Url) -> Result { match component_value { &Ident(ref value) => { match value.as_slice().to_ascii_lower().as_slice() { "thin" => Ok(specified::Length::from_px(1.)), "medium" => Ok(specified::Length::from_px(3.)), "thick" => Ok(specified::Length::from_px(5.)), _ => Err(()) } }, _ => specified::Length::parse_non_negative(component_value) } } % for side in ["top", "right", "bottom", "left"]: <%self:longhand name="border-${side}-width"> pub type SpecifiedValue = specified::Length; pub mod computed_value { use super::super::Au; pub type T = Au; } #[inline] pub fn get_initial_value() -> computed_value::T { Au::from_px(3) // medium } pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result { one_component_value(input).and_then(|c| parse_border_width(c, base_url)) } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { if !context.border_${side}_present { Au(0) } else { computed::compute_Au(value, context) } } % endfor <%self:longhand name="border-top-left-radius"> #[deriving(Clone, Show)] pub struct SpecifiedValue { pub radius: specified::LengthOrPercentage, } pub mod computed_value { use super::super::computed; #[deriving(Clone, PartialEq, Show)] pub struct T { pub radius: computed::LengthOrPercentage, } } #[inline] pub fn get_initial_value() -> computed_value::T { computed_value::T { radius: computed::LP_Length(Au(0)), } } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { computed_value::T { radius: computed::compute_LengthOrPercentage(value.radius, context), } } pub fn parse(input: &[ComponentValue], _: &Url) -> Result { let mut iter = input.skip_whitespace(); let radius = match iter.next() { None => return Err(()), Some(cv) => cv, }; let radius = try!(specified::LengthOrPercentage::parse(radius)); if iter.next().is_some() { return Err(()); } Ok(SpecifiedValue { radius: radius, }) } % for corner in ["top-right", "bottom-right", "bottom-left"]: <%self:longhand name="border-${corner}-radius"> pub type SpecifiedValue = super::border_top_left_radius::SpecifiedValue; pub mod computed_value { pub type T = super::super::border_top_left_radius::computed_value::T; } #[inline] pub fn get_initial_value() -> computed_value::T { super::border_top_left_radius::get_initial_value() } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { super::border_top_left_radius::to_computed_value(value, context) } pub fn parse(input: &[ComponentValue], u: &Url) -> Result { super::border_top_left_radius::parse(input, u) } % endfor ${new_style_struct("Outline", is_inherited=False)} // TODO(pcwalton): `invert` ${predefined_type("outline-color", "CSSColor", "CurrentColor")} <%self:single_component_value name="outline-style"> pub use super::border_top_style::{get_initial_value, to_computed_value}; pub type SpecifiedValue = super::border_top_style::SpecifiedValue; pub mod computed_value { pub type T = super::super::border_top_style::computed_value::T; } pub fn from_component_value(value: &ComponentValue, base_url: &Url) -> Result { match value { &Ident(ref ident) if ident.eq_ignore_ascii_case("hidden") => { // `hidden` is not a valid value. Err(()) } _ => super::border_top_style::from_component_value(value, base_url) } } <%self:longhand name="outline-width"> pub use super::border_top_width::{get_initial_value, parse}; pub use computed::compute_Au as to_computed_value; pub type SpecifiedValue = super::border_top_width::SpecifiedValue; pub mod computed_value { pub type T = super::super::border_top_width::computed_value::T; } ${new_style_struct("PositionOffsets", is_inherited=False)} % for side in ["top", "right", "bottom", "left"]: ${predefined_type(side, "LengthOrPercentageOrAuto", "computed::LPA_Auto")} % endfor // CSS 2.1, Section 9 - Visual formatting model ${new_style_struct("Box", is_inherited=False)} // TODO: don't parse values we don't support <%self:single_keyword_computed name="display" values="inline block inline-block table inline-table table-row-group table-header-group table-footer-group table-row table-column-group table-column table-cell table-caption list-item none"> #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { // if context.is_root_element && value == list_item { // return block // } if context.positioned || context.floated || context.is_root_element { match value { inline_table => table, inline | inline_block | table_row_group | table_column | table_column_group | table_header_group | table_footer_group | table_row | table_cell | table_caption => block, _ => value, } } else { value } } ${single_keyword("position", "static absolute relative fixed")} ${single_keyword("float", "none left right")} ${single_keyword("clear", "none left right both")} <%self:longhand name="-servo-display-for-hypothetical-box" derived_from="display" no_super="True"> pub use super::computed_as_specified as to_computed_value; pub use super::display::{SpecifiedValue, get_initial_value}; pub use super::display::{parse}; use super::computed; use super::display; pub mod computed_value { pub type T = super::SpecifiedValue; } #[inline] pub fn derive_from_display(_: display::computed_value::T, context: &computed::Context) -> computed_value::T { context.display } <%self:single_component_value name="z-index"> pub use super::computed_as_specified as to_computed_value; pub type SpecifiedValue = computed_value::T; pub mod computed_value { #[deriving(PartialEq, Clone)] pub enum T { Auto, Number(i32), } impl T { pub fn number_or_zero(self) -> i32 { match self { Auto => 0, Number(value) => value, } } } } #[inline] pub fn get_initial_value() -> computed_value::T { Auto } fn from_component_value(input: &ComponentValue, _: &Url) -> Result { match *input { Ident(ref keyword) if keyword.as_slice().eq_ignore_ascii_case("auto") => Ok(Auto), ast::Number(ast::NumericValue { int_value: Some(value), .. }) => Ok(Number(value as i32)), _ => Err(()) } } ${new_style_struct("InheritedBox", is_inherited=True)} ${single_keyword("direction", "ltr rtl", experimental=True)} // CSS 2.1, Section 10 - Visual formatting model details ${switch_to_style_struct("Box")} ${predefined_type("width", "LengthOrPercentageOrAuto", "computed::LPA_Auto", "parse_non_negative")} <%self:single_component_value name="height"> pub type SpecifiedValue = specified::LengthOrPercentageOrAuto; pub mod computed_value { pub type T = super::super::computed::LengthOrPercentageOrAuto; } #[inline] pub fn get_initial_value() -> computed_value::T { computed::LPA_Auto } #[inline] pub fn from_component_value(v: &ComponentValue, _base_url: &Url) -> Result { specified::LengthOrPercentageOrAuto::parse_non_negative(v) } pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { match (value, context.inherited_height) { (specified::LPA_Percentage(_), computed::LPA_Auto) if !context.is_root_element && !context.positioned => { computed::LPA_Auto }, _ => computed::compute_LengthOrPercentageOrAuto(value, context) } } ${predefined_type("min-width", "LengthOrPercentage", "computed::LP_Length(Au(0))", "parse_non_negative")} ${predefined_type("max-width", "LengthOrPercentageOrNone", "computed::LPN_None", "parse_non_negative")} ${predefined_type("min-height", "LengthOrPercentage", "computed::LP_Length(Au(0))", "parse_non_negative")} ${predefined_type("max-height", "LengthOrPercentageOrNone", "computed::LPN_None", "parse_non_negative")} ${switch_to_style_struct("InheritedBox")} <%self:single_component_value name="line-height"> #[deriving(Clone)] pub enum SpecifiedValue { SpecifiedNormal, SpecifiedLength(specified::Length), SpecifiedNumber(CSSFloat), // percentage are the same as em. } /// normal | | | pub fn from_component_value(input: &ComponentValue, _base_url: &Url) -> Result { match input { &ast::Number(ref value) if value.value >= 0. => Ok(SpecifiedNumber(value.value)), &ast::Percentage(ref value) if value.value >= 0. => Ok(SpecifiedLength(specified::Em(value.value / 100.))), &Dimension(ref value, ref unit) if value.value >= 0. => specified::Length::parse_dimension(value.value, unit.as_slice()) .map(SpecifiedLength), &Ident(ref value) if value.as_slice().eq_ignore_ascii_case("normal") => Ok(SpecifiedNormal), _ => Err(()), } } pub mod computed_value { use super::super::{Au, CSSFloat}; #[deriving(PartialEq, Clone)] pub enum T { Normal, Length(Au), Number(CSSFloat), } } #[inline] pub fn get_initial_value() -> computed_value::T { Normal } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { match value { SpecifiedNormal => Normal, SpecifiedLength(value) => Length(computed::compute_Au(value, context)), SpecifiedNumber(value) => Number(value), } } ${switch_to_style_struct("Box")} <%self:single_component_value name="vertical-align"> <% vertical_align_keywords = ( "baseline sub super top text-top middle bottom text-bottom".split()) %> #[allow(non_camel_case_types)] #[deriving(Clone)] pub enum SpecifiedValue { % for keyword in vertical_align_keywords: Specified_${to_rust_ident(keyword)}, % endfor SpecifiedLengthOrPercentage(specified::LengthOrPercentage), } /// baseline | sub | super | top | text-top | middle | bottom | text-bottom /// | | pub fn from_component_value(input: &ComponentValue, _base_url: &Url) -> Result { match input { &Ident(ref value) => { match value.as_slice().to_ascii_lower().as_slice() { % for keyword in vertical_align_keywords: "${keyword}" => Ok(Specified_${to_rust_ident(keyword)}), % endfor _ => Err(()), } }, _ => specified::LengthOrPercentage::parse_non_negative(input) .map(SpecifiedLengthOrPercentage) } } pub mod computed_value { use super::super::{Au, CSSFloat}; #[allow(non_camel_case_types)] #[deriving(PartialEq, Clone)] pub enum T { % for keyword in vertical_align_keywords: ${to_rust_ident(keyword)}, % endfor Length(Au), Percentage(CSSFloat), } } #[inline] pub fn get_initial_value() -> computed_value::T { baseline } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { match value { % for keyword in vertical_align_keywords: Specified_${to_rust_ident(keyword)} => ${to_rust_ident(keyword)}, % endfor SpecifiedLengthOrPercentage(value) => match computed::compute_LengthOrPercentage(value, context) { computed::LP_Length(value) => Length(value), computed::LP_Percentage(value) => Percentage(value) } } } // CSS 2.1, Section 11 - Visual effects // FIXME: Implement scrolling for `scroll` and `auto` (#2742). ${single_keyword("overflow", "visible hidden scroll auto")} ${switch_to_style_struct("InheritedBox")} // TODO: collapse. Well, do tables first. ${single_keyword("visibility", "visible hidden")} // CSS 2.1, Section 12 - Generated content, automatic numbering, and lists ${switch_to_style_struct("Box")} <%self:longhand name="content"> pub use super::computed_as_specified as to_computed_value; pub mod computed_value { #[deriving(PartialEq, Clone)] pub enum ContentItem { StringContent(String), } #[allow(non_camel_case_types)] #[deriving(PartialEq, Clone)] pub enum T { normal, none, Content(Vec), } } pub type SpecifiedValue = computed_value::T; #[inline] pub fn get_initial_value() -> computed_value::T { normal } // normal | none | [ ]+ // TODO: , , attr(), open-quote, close-quote, no-open-quote, no-close-quote pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result { match one_component_value(input) { Ok(&Ident(ref keyword)) => { match keyword.as_slice().to_ascii_lower().as_slice() { "normal" => return Ok(normal), "none" => return Ok(none), _ => () } }, _ => () } let mut content = vec!(); for component_value in input.skip_whitespace() { match component_value { &QuotedString(ref value) => content.push(StringContent(value.clone())), _ => return Err(()) // invalid/unsupported value } } Ok(Content(content)) } // CSS 2.1, Section 13 - Paged media // CSS 2.1, Section 14 - Colors and Backgrounds ${new_style_struct("Background", is_inherited=False)} ${predefined_type("background-color", "CSSColor", "RGBAColor(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")} <%self:single_component_value name="background-image"> use super::common_types::specified as common_specified; pub mod computed_value { use super::super::super::common_types::computed; #[deriving(Clone, PartialEq)] pub type T = Option; } #[deriving(Clone)] pub type SpecifiedValue = Option; #[inline] pub fn get_initial_value() -> computed_value::T { None } pub fn from_component_value(component_value: &ComponentValue, base_url: &Url) -> Result { match component_value { &ast::Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") => { Ok(None) } _ => { match common_specified::Image::from_component_value(component_value, base_url) { Err(err) => Err(err), Ok(result) => Ok(Some(result)), } } } } pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { match value { None => None, Some(image) => Some(image.to_computed_value(context)), } } <%self:longhand name="background-position"> pub mod computed_value { use super::super::super::common_types::computed::LengthOrPercentage; #[deriving(PartialEq, Clone)] pub struct T { pub horizontal: LengthOrPercentage, pub vertical: LengthOrPercentage, } } #[deriving(Clone)] pub struct SpecifiedValue { pub horizontal: specified::LengthOrPercentage, pub vertical: specified::LengthOrPercentage, } impl SpecifiedValue { fn new(first: specified::PositionComponent, second: specified::PositionComponent) -> Result { let (horiz, vert) = match (category(first), category(second)) { // Don't allow two vertical keywords or two horizontal keywords. (HorizontalKeyword, HorizontalKeyword) | (VerticalKeyword, VerticalKeyword) => return Err(()), // Swap if both are keywords and vertical precedes horizontal. (VerticalKeyword, HorizontalKeyword) | (VerticalKeyword, OtherKeyword) | (OtherKeyword, HorizontalKeyword) => (second, first), // By default, horizontal is first. _ => (first, second), }; Ok(SpecifiedValue { horizontal: horiz.to_length_or_percentage(), vertical: vert.to_length_or_percentage(), }) } } // Collapse `Position` into a few categories to simplify the above `match` expression. enum PositionCategory { HorizontalKeyword, VerticalKeyword, OtherKeyword, LengthOrPercentage, } fn category(p: specified::PositionComponent) -> PositionCategory { match p { specified::Pos_Left | specified::Pos_Right => HorizontalKeyword, specified::Pos_Top | specified::Pos_Bottom => VerticalKeyword, specified::Pos_Center => OtherKeyword, specified::Pos_Length(_) | specified::Pos_Percentage(_) => LengthOrPercentage, } } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { computed_value::T { horizontal: computed::compute_LengthOrPercentage(value.horizontal, context), vertical: computed::compute_LengthOrPercentage(value.vertical, context), } } #[inline] pub fn get_initial_value() -> computed_value::T { computed_value::T { horizontal: computed::LP_Percentage(0.0), vertical: computed::LP_Percentage(0.0), } } pub fn parse_one(first: &ComponentValue) -> Result { let first = try!(specified::PositionComponent::parse(first)); // If only one value is provided, use `center` for the second. SpecifiedValue::new(first, specified::Pos_Center) } pub fn parse_two(first: &ComponentValue, second: &ComponentValue) -> Result { let first = try!(specified::PositionComponent::parse(first)); let second = try!(specified::PositionComponent::parse(second)); SpecifiedValue::new(first, second) } pub fn parse(input: &[ComponentValue], _: &Url) -> Result { let mut input_iter = input.skip_whitespace(); let first = input_iter.next(); let second = input_iter.next(); if input_iter.next().is_some() { return Err(()) } match (first, second) { (Some(first), Some(second)) => { parse_two(first, second) } (Some(first), None) => { parse_one(first) } _ => Err(()) } } ${single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat")} ${single_keyword("background-attachment", "scroll fixed")} ${new_style_struct("Color", is_inherited=True)} <%self:raw_longhand name="color"> pub use super::computed_as_specified as to_computed_value; pub type SpecifiedValue = RGBA; pub mod computed_value { pub type T = super::SpecifiedValue; } #[inline] pub fn get_initial_value() -> computed_value::T { RGBA { red: 0., green: 0., blue: 0., alpha: 1. } /* black */ } pub fn parse_specified(input: &[ComponentValue], _base_url: &Url) -> Result, ()> { match one_component_value(input).and_then(Color::parse) { Ok(RGBAColor(rgba)) => Ok(SpecifiedValue(rgba)), Ok(CurrentColor) => Ok(Inherit), Err(()) => Err(()), } } // CSS 2.1, Section 15 - Fonts ${new_style_struct("Font", is_inherited=True)} <%self:longhand name="font-family"> pub use super::computed_as_specified as to_computed_value; pub mod computed_value { #[deriving(PartialEq, Clone)] pub enum FontFamily { FamilyName(String), // Generic // Serif, // SansSerif, // Cursive, // Fantasy, // Monospace, } impl FontFamily { pub fn name(&self) -> &str { match *self { FamilyName(ref name) => name.as_slice(), } } } pub type T = Vec; } pub type SpecifiedValue = computed_value::T; #[inline] pub fn get_initial_value() -> computed_value::T { vec![FamilyName("serif".to_string())] } /// # /// = | [ + ] /// TODO: pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result { parse_slice_comma_separated(input, parse_one_family) } pub fn parse_one_family<'a>(iter: ParserIter) -> Result { // TODO: avoid copying strings? let mut idents = match iter.next() { Some(&QuotedString(ref value)) => return Ok(FamilyName(value.clone())), Some(&Ident(ref value)) => { // match value.as_slice().to_ascii_lower().as_slice() { // "serif" => return Ok(Serif), // "sans-serif" => return Ok(SansSerif), // "cursive" => return Ok(Cursive), // "fantasy" => return Ok(Fantasy), // "monospace" => return Ok(Monospace), // _ => { vec![value.as_slice()] // } // } } _ => return Err(()) }; loop { match iter.next() { Some(&Ident(ref value)) => { idents.push(value.as_slice()); iter.next(); } Some(component_value) => { iter.push_back(component_value); break } None => break, } } Ok(FamilyName(idents.connect(" "))) } ${single_keyword("font-style", "normal italic oblique")} ${single_keyword("font-variant", "normal small-caps")} <%self:single_component_value name="font-weight"> #[deriving(Clone)] pub enum SpecifiedValue { Bolder, Lighter, % for weight in range(100, 901, 100): SpecifiedWeight${weight}, % endfor } /// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 pub fn from_component_value(input: &ComponentValue, _base_url: &Url) -> Result { match input { &Ident(ref value) => { match value.as_slice().to_ascii_lower().as_slice() { "bold" => Ok(SpecifiedWeight700), "normal" => Ok(SpecifiedWeight400), "bolder" => Ok(Bolder), "lighter" => Ok(Lighter), _ => Err(()), } }, &Number(ref value) => match value.int_value { Some(100) => Ok(SpecifiedWeight100), Some(200) => Ok(SpecifiedWeight200), Some(300) => Ok(SpecifiedWeight300), Some(400) => Ok(SpecifiedWeight400), Some(500) => Ok(SpecifiedWeight500), Some(600) => Ok(SpecifiedWeight600), Some(700) => Ok(SpecifiedWeight700), Some(800) => Ok(SpecifiedWeight800), Some(900) => Ok(SpecifiedWeight900), _ => Err(()), }, _ => Err(()) } } pub mod computed_value { #[deriving(PartialEq, Clone)] pub enum T { % for weight in range(100, 901, 100): Weight${weight}, % endfor } impl T { pub fn is_bold(self) -> bool { match self { Weight900 | Weight800 | Weight700 | Weight600 => true, _ => false } } } } #[inline] pub fn get_initial_value() -> computed_value::T { Weight400 } // normal #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { match value { % for weight in range(100, 901, 100): SpecifiedWeight${weight} => Weight${weight}, % endfor Bolder => match context.inherited_font_weight { Weight100 => Weight400, Weight200 => Weight400, Weight300 => Weight400, Weight400 => Weight700, Weight500 => Weight700, Weight600 => Weight900, Weight700 => Weight900, Weight800 => Weight900, Weight900 => Weight900, }, Lighter => match context.inherited_font_weight { Weight100 => Weight100, Weight200 => Weight100, Weight300 => Weight100, Weight400 => Weight100, Weight500 => Weight100, Weight600 => Weight400, Weight700 => Weight400, Weight800 => Weight700, Weight900 => Weight700, }, } } <%self:single_component_value name="font-size"> pub type SpecifiedValue = specified::Length; // Percentages are the same as em. pub mod computed_value { use super::super::Au; pub type T = Au; } const MEDIUM_PX: int = 16; #[inline] pub fn get_initial_value() -> computed_value::T { Au::from_px(MEDIUM_PX) } #[inline] pub fn to_computed_value(_value: SpecifiedValue, context: &computed::Context) -> computed_value::T { // We already computed this element's font size; no need to compute it again. return context.font_size } /// | | | pub fn from_component_value(input: &ComponentValue, _base_url: &Url) -> Result { match specified::LengthOrPercentage::parse_non_negative(input) { Ok(specified::LP_Length(value)) => return Ok(value), Ok(specified::LP_Percentage(value)) => return Ok(specified::Em(value)), Err(()) => (), } match try!(get_ident_lower(input)).as_slice() { "xx-small" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 3 / 5)), "x-small" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 3 / 4)), "small" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 8 / 9)), "medium" => Ok(specified::Au_(Au::from_px(MEDIUM_PX))), "large" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 6 / 5)), "x-large" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 3 / 2)), "xx-large" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 2)), // https://github.com/servo/servo/issues/3423#issuecomment-56321664 "smaller" => Ok(specified::Em(0.85)), "larger" => Ok(specified::Em(1.2)), _ => return Err(()) } } // CSS 2.1, Section 16 - Text ${new_style_struct("InheritedText", is_inherited=True)} // TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.) ${single_keyword("text-align", "left right center justify")} <%self:single_component_value name="letter-spacing"> pub type SpecifiedValue = Option; pub mod computed_value { use super::super::Au; pub type T = Option; } #[inline] pub fn get_initial_value() -> computed_value::T { None } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { value.map(|length| computed::compute_Au(length, context)) } pub fn from_component_value(input: &ComponentValue, _: &Url) -> Result { match input { &Ident(ref value) if value.eq_ignore_ascii_case("normal") => Ok(None), _ => specified::Length::parse_non_negative(input).map(|length| Some(length)), } } <%self:single_component_value name="word-spacing"> pub type SpecifiedValue = Option; pub mod computed_value { use super::super::Au; pub type T = Option; } #[inline] pub fn get_initial_value() -> computed_value::T { None } #[inline] pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { value.map(|length| computed::compute_Au(length, context)) } pub fn from_component_value(input: &ComponentValue, _: &Url) -> Result { match input { &Ident(ref value) if value.eq_ignore_ascii_case("normal") => Ok(None), _ => specified::Length::parse_non_negative(input).map(|length| Some(length)), } } ${predefined_type("text-indent", "LengthOrPercentage", "computed::LP_Length(Au(0))")} // Also known as "word-wrap" (which is more popular because of IE), but this is the preferred // name per CSS-TEXT 6.2. ${single_keyword("overflow-wrap", "normal break-word")} ${new_style_struct("Text", is_inherited=False)} <%self:longhand name="text-decoration"> pub use super::computed_as_specified as to_computed_value; #[deriving(PartialEq, Clone)] pub struct SpecifiedValue { pub underline: bool, pub overline: bool, pub line_through: bool, // 'blink' is accepted in the parser but ignored. // Just not blinking the text is a conforming implementation per CSS 2.1. } pub mod computed_value { pub type T = super::SpecifiedValue; #[allow(non_upper_case_globals)] pub const none: T = super::SpecifiedValue { underline: false, overline: false, line_through: false }; } #[inline] pub fn get_initial_value() -> computed_value::T { none } /// none | [ underline || overline || line-through || blink ] pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result { let mut result = SpecifiedValue { underline: false, overline: false, line_through: false, }; match one_component_value(input) { Ok(&Ident(ref value)) if value.as_slice().eq_ignore_ascii_case("none") => return Ok(result), _ => {} } let mut blink = false; let mut empty = true; for component_value in input.skip_whitespace() { match get_ident_lower(component_value) { Err(()) => return Err(()), Ok(keyword) => match keyword.as_slice() { "underline" => if result.underline { return Err(()) } else { empty = false; result.underline = true }, "overline" => if result.overline { return Err(()) } else { empty = false; result.overline = true }, "line-through" => if result.line_through { return Err(()) } else { empty = false; result.line_through = true }, "blink" => if blink { return Err(()) } else { empty = false; blink = true }, _ => return Err(()), } } } if !empty { Ok(result) } else { Err(()) } } ${switch_to_style_struct("InheritedText")} <%self:longhand name="-servo-text-decorations-in-effect" derived_from="display text-decoration"> pub use super::computed_as_specified as to_computed_value; #[deriving(Clone, PartialEq)] pub struct SpecifiedValue { pub underline: Option, pub overline: Option, pub line_through: Option, } pub mod computed_value { pub type T = super::SpecifiedValue; } #[inline] pub fn get_initial_value() -> computed_value::T { SpecifiedValue { underline: None, overline: None, line_through: None, } } fn maybe(flag: bool, context: &computed::Context) -> Option { if flag { Some(context.color) } else { None } } fn derive(context: &computed::Context) -> computed_value::T { // Start with no declarations if this is a block; otherwise, start with the // declarations in effect and add in the text decorations that this inline specifies. let mut result = match context.display { display::computed_value::inline => context.inherited_text_decorations_in_effect, _ => { SpecifiedValue { underline: None, overline: None, line_through: None, } } }; if result.underline.is_none() { result.underline = maybe(context.text_decoration.underline, context) } if result.overline.is_none() { result.overline = maybe(context.text_decoration.overline, context) } if result.line_through.is_none() { result.line_through = maybe(context.text_decoration.line_through, context) } result } #[inline] pub fn derive_from_text_decoration(_: text_decoration::computed_value::T, context: &computed::Context) -> computed_value::T { derive(context) } #[inline] pub fn derive_from_display(_: display::computed_value::T, context: &computed::Context) -> computed_value::T { derive(context) } ${single_keyword("white-space", "normal pre nowrap")} // TODO(pcwalton): `full-width` ${single_keyword("text-transform", "none capitalize uppercase lowercase")} // CSS 2.1, Section 17 - Tables ${new_style_struct("Table", is_inherited=False)} ${single_keyword("table-layout", "auto fixed")} // CSS 2.1, Section 18 - User interface // CSS Writing Modes Level 3 // http://dev.w3.org/csswg/css-writing-modes/ ${switch_to_style_struct("InheritedBox")} ${single_keyword("writing-mode", "horizontal-tb vertical-rl vertical-lr", experimental=True)} // FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support) // FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented ${single_keyword("text-orientation", "sideways sideways-left sideways-right", experimental=True)} // CSS Basic User Interface Module Level 3 // http://dev.w3.org/csswg/css-ui/ ${switch_to_style_struct("Box")} ${single_keyword("box-sizing", "content-box border-box")} ${new_style_struct("Effects", is_inherited=False)} <%self:single_component_value name="opacity"> pub type SpecifiedValue = CSSFloat; pub mod computed_value { use super::super::CSSFloat; pub type T = CSSFloat; } #[inline] pub fn get_initial_value() -> computed_value::T { 1.0 } #[inline] pub fn to_computed_value(value: SpecifiedValue, _: &computed::Context) -> computed_value::T { if value < 0.0 { 0.0 } else if value > 1.0 { 1.0 } else { value } } fn from_component_value(input: &ComponentValue, _: &Url) -> Result { match *input { Number(ref value) => Ok(value.value), _ => Err(()) } } } pub mod shorthands { pub use super::*; pub use super::longhands::*; <%def name="shorthand(name, sub_properties)"> <% shorthand = Shorthand(name, sub_properties.split()) SHORTHANDS.append(shorthand) %> pub mod ${shorthand.ident} { use super::*; pub struct Longhands { % for sub_property in shorthand.sub_properties: pub ${sub_property.ident}: Option<${sub_property.ident}::SpecifiedValue>, % endfor } pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result { ${caller.body()} } } <%def name="four_sides_shorthand(name, sub_property_pattern, parser_function)"> <%self:shorthand name="${name}" sub_properties="${ ' '.join(sub_property_pattern % side for side in ['top', 'right', 'bottom', 'left'])}"> let mut iter = input.skip_whitespace().map(|c| ${parser_function}(c, base_url).ok()); // zero or more than four values is invalid. // one value sets them all // two values set (top, bottom) and (left, right) // three values set top, (left, right) and bottom // four values set them in order let top = iter.next().unwrap_or(None); let right = iter.next().unwrap_or(top); let bottom = iter.next().unwrap_or(top); let left = iter.next().unwrap_or(right); if top.is_some() && right.is_some() && bottom.is_some() && left.is_some() && iter.next().is_none() { Ok(Longhands { % for side in ["top", "right", "bottom", "left"]: ${to_rust_ident(sub_property_pattern % side)}: ${side}, % endfor }) } else { Err(()) } // TODO: other background-* properties <%self:shorthand name="background" sub_properties="background-color background-position background-repeat background-attachment background-image"> use std::mem; let (mut color, mut image, mut position, mut repeat, mut attachment) = (None, None, None, None, None); let mut unused_component_value = None; let mut any = false; for component_value in input.skip_whitespace() { // Try `background-position` first because it might not use the value. if position.is_none() { match mem::replace(&mut unused_component_value, None) { Some(saved_component_value) => { // First try parsing a pair of values, then a single value. match background_position::parse_two(saved_component_value, component_value) { Ok(v) => { position = Some(v); any = true; continue }, Err(()) => { match background_position::parse_one(saved_component_value) { Ok(v) => { position = Some(v); any = true; // We haven't used the current `component_value`; // keep attempting to parse it below. }, // If we get here, parsing failed. Err(()) => return Err(()) } } } } None => () // Wait until we have a pair of potential values. } } if color.is_none() { match background_color::from_component_value(component_value, base_url) { Ok(v) => { color = Some(v); any = true; continue }, Err(()) => () } } if image.is_none() { match background_image::from_component_value(component_value, base_url) { Ok(v) => { image = Some(v); any = true; continue }, Err(()) => (), } } if repeat.is_none() { match background_repeat::from_component_value(component_value, base_url) { Ok(v) => { repeat = Some(v); any = true; continue }, Err(()) => () } } if attachment.is_none() { match background_attachment::from_component_value(component_value, base_url) { Ok(v) => { attachment = Some(v); any = true; continue }, Err(()) => () } } // Save the component value. It may the first of a background-position pair. unused_component_value = Some(component_value); } if position.is_none() { // Check for a lone trailing background-position value. match mem::replace(&mut unused_component_value, None) { Some(saved_component_value) => { match background_position::parse_one(saved_component_value) { Ok(v) => { position = Some(v); any = true; }, Err(()) => return Err(()) } } None => () } } if any && unused_component_value.is_none() { Ok(Longhands { background_color: color, background_image: image, background_position: position, background_repeat: repeat, background_attachment: attachment, }) } else { Err(()) } ${four_sides_shorthand("margin", "margin-%s", "margin_top::from_component_value")} ${four_sides_shorthand("padding", "padding-%s", "padding_top::from_component_value")} pub fn parse_color(value: &ComponentValue, _base_url: &Url) -> Result { specified::CSSColor::parse(value) } ${four_sides_shorthand("border-color", "border-%s-color", "parse_color")} ${four_sides_shorthand("border-style", "border-%s-style", "border_top_style::from_component_value")} ${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")} pub fn parse_border(input: &[ComponentValue], base_url: &Url) -> Result<(Option, Option, Option), ()> { let mut color = None; let mut style = None; let mut width = None; let mut any = false; for component_value in input.skip_whitespace() { if color.is_none() { match specified::CSSColor::parse(component_value) { Ok(c) => { color = Some(c); any = true; continue }, Err(()) => () } } if style.is_none() { match border_top_style::from_component_value(component_value, base_url) { Ok(s) => { style = Some(s); any = true; continue }, Err(()) => () } } if width.is_none() { match parse_border_width(component_value, base_url) { Ok(w) => { width = Some(w); any = true; continue }, Err(()) => () } } return Err(()) } if any { Ok((color, style, width)) } else { Err(()) } } % for side in ["top", "right", "bottom", "left"]: <%self:shorthand name="border-${side}" sub_properties="${' '.join( 'border-%s-%s' % (side, prop) for prop in ['color', 'style', 'width'] )}"> parse_border(input, base_url).map(|(color, style, width)| { Longhands { % for prop in ["color", "style", "width"]: ${"border_%s_%s: %s," % (side, prop, prop)} % endfor } }) % endfor <%self:shorthand name="border" sub_properties="${' '.join( 'border-%s-%s' % (side, prop) for side in ['top', 'right', 'bottom', 'left'] for prop in ['color', 'style', 'width'] )}"> parse_border(input, base_url).map(|(color, style, width)| { Longhands { % for side in ["top", "right", "bottom", "left"]: % for prop in ["color", "style", "width"]: ${"border_%s_%s: %s," % (side, prop, prop)} % endfor % endfor } }) <%self:shorthand name="border-radius" sub_properties="${' '.join( 'border-%s-radius' % (corner) for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left'] )}"> use std::iter::Peekable; let _ignored = base_url; fn parse_one_set_of_border_radii<'a,I>(mut input: Peekable< &'a ComponentValue,I >) -> Result<[specified::LengthOrPercentage, ..4],()> where I: Iterator< &'a ComponentValue > { let (mut count, mut values) = (0u, [specified::LP_Length(specified::Au_(Au(0))), ..4]); while count < 4 { let token = match input.peek() { None => break, Some(token) => *token, }; let value = match specified::LengthOrPercentage::parse(token) { Err(_) => break, Ok(value) => value, }; drop(input.next()); values[count] = value; count += 1 } match count { 1 => Ok([values[0], values[0], values[0], values[0]]), 2 => Ok([values[0], values[1], values[0], values[1]]), 3 => Ok([values[0], values[1], values[2], values[1]]), 4 => Ok([values[0], values[1], values[2], values[3]]), _ => Err(()), } } let input = input.skip_whitespace().peekable(); let radii = try!(parse_one_set_of_border_radii(input)); // TODO(pcwalton): Elliptical borders. Ok(Longhands { border_top_left_radius: Some(border_top_left_radius::SpecifiedValue { radius: radii[0], }), border_top_right_radius: Some(border_top_left_radius::SpecifiedValue { radius: radii[1], }), border_bottom_right_radius: Some(border_top_left_radius::SpecifiedValue { radius: radii[2], }), border_bottom_left_radius: Some(border_top_left_radius::SpecifiedValue { radius: radii[3], }), }) <%self:shorthand name="outline" sub_properties="outline-color outline-style outline-width"> let (mut color, mut style, mut width, mut any) = (None, None, None, false); for component_value in input.skip_whitespace() { if color.is_none() { match specified::CSSColor::parse(component_value) { Ok(c) => { color = Some(c); any = true; continue } Err(()) => {} } } if style.is_none() { match border_top_style::from_component_value(component_value, base_url) { Ok(s) => { style = Some(s); any = true; continue } Err(()) => {} } } if width.is_none() { match parse_border_width(component_value, base_url) { Ok(w) => { width = Some(w); any = true; continue } Err(()) => {} } } return Err(()) } if any { Ok(Longhands { outline_color: color, outline_style: style, outline_width: width, }) } else { Err(()) } <%self:shorthand name="font" sub_properties="font-style font-variant font-weight font-size line-height font-family"> let mut iter = input.skip_whitespace(); let mut nb_normals = 0u; let mut style = None; let mut variant = None; let mut weight = None; let mut size = None; let mut line_height = None; for component_value in iter { // Special-case 'normal' because it is valid in each of // font-style, font-weight and font-variant. // Leaves the values to None, 'normal' is the initial value for each of them. match get_ident_lower(component_value) { Ok(ref ident) if ident.as_slice().eq_ignore_ascii_case("normal") => { nb_normals += 1; continue; } _ => {} } if style.is_none() { match font_style::from_component_value(component_value, base_url) { Ok(s) => { style = Some(s); continue }, Err(()) => () } } if weight.is_none() { match font_weight::from_component_value(component_value, base_url) { Ok(w) => { weight = Some(w); continue }, Err(()) => () } } if variant.is_none() { match font_variant::from_component_value(component_value, base_url) { Ok(v) => { variant = Some(v); continue }, Err(()) => () } } match font_size::from_component_value(component_value, base_url) { Ok(s) => { size = Some(s); break }, Err(()) => return Err(()) } } #[inline] fn count(opt: &Option) -> uint { match opt { &Some(_) => 1, &None => 0, } } if size.is_none() || (count(&style) + count(&weight) + count(&variant) + nb_normals) > 3 { return Err(()) } let mut copied_iter = iter.clone(); match copied_iter.next() { Some(&Delim('/')) => { iter = copied_iter; line_height = match iter.next() { Some(v) => line_height::from_component_value(v, base_url).ok(), _ => return Err(()), }; if line_height.is_none() { return Err(()) } } _ => () } let family = try!(parse_comma_separated( &mut BufferedIter::new(iter), font_family::parse_one_family)); Ok(Longhands { font_style: style, font_variant: variant, font_weight: weight, font_size: size, line_height: line_height, font_family: Some(family) }) // Per CSS-TEXT 6.2, "for legacy reasons, UAs must treat `word-wrap` as an alternate name for // the `overflow-wrap` property, as if it were a shorthand of `overflow-wrap`." <%self:shorthand name="word-wrap" sub_properties="overflow-wrap"> overflow_wrap::parse(input, base_url).map(|specified_value| { Longhands { overflow_wrap: Some(specified_value), } }) } // TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template. // Maybe submit for inclusion in libstd? mod property_bit_field { use std::uint; use std::mem; pub struct PropertyBitField { storage: [uint, ..(${len(LONGHANDS)} - 1 + uint::BITS) / uint::BITS] } impl PropertyBitField { #[inline] pub fn new() -> PropertyBitField { PropertyBitField { storage: unsafe { mem::zeroed() } } } #[inline] fn get(&self, bit: uint) -> bool { (self.storage[bit / uint::BITS] & (1 << (bit % uint::BITS))) != 0 } #[inline] fn set(&mut self, bit: uint) { self.storage[bit / uint::BITS] |= 1 << (bit % uint::BITS) } % for i, property in enumerate(LONGHANDS): % if property.derived_from is None: #[allow(non_snake_case)] #[inline] pub fn get_${property.ident}(&self) -> bool { self.get(${i}) } #[allow(non_snake_case)] #[inline] pub fn set_${property.ident}(&mut self) { self.set(${i}) } % endif % endfor } } /// Declarations are stored in reverse order. /// Overridden declarations are skipped. pub struct PropertyDeclarationBlock { pub important: Arc>, pub normal: Arc>, } pub fn parse_style_attribute(input: &str, base_url: &Url) -> PropertyDeclarationBlock { parse_property_declaration_list(tokenize(input), base_url) } pub fn parse_property_declaration_list>(input: I, base_url: &Url) -> PropertyDeclarationBlock { let mut important_declarations = vec!(); let mut normal_declarations = vec!(); let mut important_seen = PropertyBitField::new(); let mut normal_seen = PropertyBitField::new(); let items: Vec = ErrorLoggerIterator(parse_declaration_list(input)).collect(); for item in items.into_iter().rev() { match item { DeclAtRule(rule) => log_css_error( rule.location, format!("Unsupported at-rule in declaration list: @{:s}", rule.name).as_slice()), Declaration_(Declaration{ location: l, name: n, value: v, important: i}) => { // TODO: only keep the last valid declaration for a given name. let (list, seen) = if i { (&mut important_declarations, &mut important_seen) } else { (&mut normal_declarations, &mut normal_seen) }; match PropertyDeclaration::parse(n.as_slice(), v.as_slice(), list, base_url, seen) { UnknownProperty => log_css_error(l, format!( "Unsupported property: {}:{}", n, v.iter().to_css()).as_slice()), ExperimentalProperty => log_css_error(l, format!( "Experimental property, use `servo --enable_experimental` \ or `servo -e` to enable: {}:{}", n, v.iter().to_css()).as_slice()), InvalidValue => log_css_error(l, format!( "Invalid value: {}:{}", n, v.iter().to_css()).as_slice()), ValidOrIgnoredDeclaration => (), } } } } PropertyDeclarationBlock { important: Arc::new(important_declarations), normal: Arc::new(normal_declarations), } } pub enum CSSWideKeyword { InitialKeyword, InheritKeyword, UnsetKeyword, } impl CSSWideKeyword { pub fn parse(input: &[ComponentValue]) -> Result { one_component_value(input).and_then(get_ident_lower).and_then(|keyword| { match keyword.as_slice() { "initial" => Ok(InitialKeyword), "inherit" => Ok(InheritKeyword), "unset" => Ok(UnsetKeyword), _ => Err(()) } }) } } #[deriving(Clone)] pub enum DeclaredValue { SpecifiedValue(T), Initial, Inherit, // There is no Unset variant here. // The 'unset' keyword is represented as either Initial or Inherit, // depending on whether the property is inherited. } #[deriving(Clone)] pub enum PropertyDeclaration { % for property in LONGHANDS: ${property.camel_case}Declaration(DeclaredValue), % endfor } pub enum PropertyDeclarationParseResult { UnknownProperty, ExperimentalProperty, InvalidValue, ValidOrIgnoredDeclaration, } impl PropertyDeclaration { pub fn parse(name: &str, value: &[ComponentValue], result_list: &mut Vec, base_url: &Url, seen: &mut PropertyBitField) -> PropertyDeclarationParseResult { match name.to_ascii_lower().as_slice() { % for property in LONGHANDS: % if property.derived_from is None: "${property.name}" => { % if property.experimental: if !::servo_util::opts::experimental_enabled() { return ExperimentalProperty } % endif if seen.get_${property.ident}() { return ValidOrIgnoredDeclaration } match longhands::${property.ident}::parse_declared(value, base_url) { Ok(value) => { seen.set_${property.ident}(); result_list.push(${property.camel_case}Declaration(value)); ValidOrIgnoredDeclaration }, Err(()) => InvalidValue, } }, % else: "${property.name}" => UnknownProperty, % endif % endfor % for shorthand in SHORTHANDS: "${shorthand.name}" => { if ${" && ".join("seen.get_%s()" % sub_property.ident for sub_property in shorthand.sub_properties)} { return ValidOrIgnoredDeclaration } match CSSWideKeyword::parse(value) { Ok(InheritKeyword) => { % for sub_property in shorthand.sub_properties: if !seen.get_${sub_property.ident}() { seen.set_${sub_property.ident}(); result_list.push( ${sub_property.camel_case}Declaration(Inherit)); } % endfor ValidOrIgnoredDeclaration }, Ok(InitialKeyword) => { % for sub_property in shorthand.sub_properties: if !seen.get_${sub_property.ident}() { seen.set_${sub_property.ident}(); result_list.push( ${sub_property.camel_case}Declaration(Initial)); } % endfor ValidOrIgnoredDeclaration }, Ok(UnsetKeyword) => { % for sub_property in shorthand.sub_properties: if !seen.get_${sub_property.ident}() { seen.set_${sub_property.ident}(); result_list.push(${sub_property.camel_case}Declaration( ${"Inherit" if sub_property.style_struct.inherited else "Initial"} )); } % endfor ValidOrIgnoredDeclaration }, Err(()) => match shorthands::${shorthand.ident}::parse(value, base_url) { Ok(result) => { % for sub_property in shorthand.sub_properties: if !seen.get_${sub_property.ident}() { seen.set_${sub_property.ident}(); result_list.push(${sub_property.camel_case}Declaration( match result.${sub_property.ident} { Some(value) => SpecifiedValue(value), None => Initial, } )); } % endfor ValidOrIgnoredDeclaration }, Err(()) => InvalidValue, } } }, % endfor _ => UnknownProperty, } } } pub mod style_structs { use super::longhands; % for style_struct in STYLE_STRUCTS: #[deriving(PartialEq, Clone)] pub struct ${style_struct.name} { % for longhand in style_struct.longhands: pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T, % endfor } % endfor } #[deriving(Clone)] pub struct ComputedValues { % for style_struct in STYLE_STRUCTS: ${style_struct.ident}: Arc, % endfor shareable: bool, pub writing_mode: WritingMode, pub root_font_size: Au, } impl ComputedValues { /// Resolves the currentColor keyword. /// Any color value form computed values (except for the 'color' property itself) /// should go through this method. /// /// Usage example: /// let top_color = style.resolve_color(style.Border.border_top_color); #[inline] pub fn resolve_color(&self, color: computed::CSSColor) -> RGBA { match color { RGBAColor(rgba) => rgba, CurrentColor => self.get_color().color, } } #[inline] pub fn content_inline_size(&self) -> computed_values::LengthOrPercentageOrAuto { let box_style = self.get_box(); if self.writing_mode.is_vertical() { box_style.height } else { box_style.width } } #[inline] pub fn content_block_size(&self) -> computed_values::LengthOrPercentageOrAuto { let box_style = self.get_box(); if self.writing_mode.is_vertical() { box_style.width } else { box_style.height } } #[inline] pub fn min_inline_size(&self) -> computed_values::LengthOrPercentage { let box_style = self.get_box(); if self.writing_mode.is_vertical() { box_style.min_height } else { box_style.min_width } } #[inline] pub fn min_block_size(&self) -> computed_values::LengthOrPercentage { let box_style = self.get_box(); if self.writing_mode.is_vertical() { box_style.min_width } else { box_style.min_height } } #[inline] pub fn max_inline_size(&self) -> computed_values::LengthOrPercentageOrNone { let box_style = self.get_box(); if self.writing_mode.is_vertical() { box_style.max_height } else { box_style.max_width } } #[inline] pub fn max_block_size(&self) -> computed_values::LengthOrPercentageOrNone { let box_style = self.get_box(); if self.writing_mode.is_vertical() { box_style.max_width } else { box_style.max_height } } #[inline] pub fn logical_padding(&self) -> LogicalMargin { let padding_style = self.get_padding(); LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( padding_style.padding_top, padding_style.padding_right, padding_style.padding_bottom, padding_style.padding_left, )) } #[inline] pub fn logical_border_width(&self) -> LogicalMargin { let border_style = self.get_border(); LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( border_style.border_top_width, border_style.border_right_width, border_style.border_bottom_width, border_style.border_left_width, )) } #[inline] pub fn logical_margin(&self) -> LogicalMargin { let margin_style = self.get_margin(); LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( margin_style.margin_top, margin_style.margin_right, margin_style.margin_bottom, margin_style.margin_left, )) } #[inline] pub fn logical_position(&self) -> LogicalMargin { // FIXME(SimonSapin): should be the writing mode of the containing block, maybe? let position_style = self.get_positionoffsets(); LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( position_style.top, position_style.right, position_style.bottom, position_style.left, )) } #[inline] pub fn get_font_arc(&self) -> Arc { self.font.clone() } % for style_struct in STYLE_STRUCTS: #[inline] pub fn get_${style_struct.name.lower()} <'a>(&'a self) -> &'a style_structs::${style_struct.name} { &*self.${style_struct.ident} } % endfor } /// Return a WritingMode bitflags from the relevant CSS properties. fn get_writing_mode(inheritedbox_style: &style_structs::InheritedBox) -> WritingMode { use servo_util::logical_geometry; let mut flags = WritingMode::empty(); match inheritedbox_style.direction { computed_values::direction::ltr => {}, computed_values::direction::rtl => { flags.insert(logical_geometry::FLAG_RTL); }, } match inheritedbox_style.writing_mode { computed_values::writing_mode::horizontal_tb => {}, computed_values::writing_mode::vertical_rl => { flags.insert(logical_geometry::FLAG_VERTICAL); }, computed_values::writing_mode::vertical_lr => { flags.insert(logical_geometry::FLAG_VERTICAL); flags.insert(logical_geometry::FLAG_VERTICAL_LR); }, } match inheritedbox_style.text_orientation { computed_values::text_orientation::sideways_right => {}, computed_values::text_orientation::sideways_left => { flags.insert(logical_geometry::FLAG_VERTICAL_LR); }, computed_values::text_orientation::sideways => { if flags.intersects(logical_geometry::FLAG_VERTICAL_LR) { flags.insert(logical_geometry::FLAG_SIDEWAYS_LEFT); } }, } flags } /// The initial values for all style structs as defined by the specification. lazy_static! { static ref INITIAL_VALUES: ComputedValues = ComputedValues { % for style_struct in STYLE_STRUCTS: ${style_struct.ident}: Arc::new(style_structs::${style_struct.name} { % for longhand in style_struct.longhands: ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(), % endfor }), % endfor shareable: true, writing_mode: WritingMode::empty(), root_font_size: longhands::font_size::get_initial_value(), }; } #[test] fn initial_writing_mode_is_empty() { assert_eq!(get_writing_mode(INITIAL_VALUES.get_inheritedbox()), WritingMode::empty()) } /// Fast path for the function below. Only computes new inherited styles. #[allow(unused_mut)] fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock], shareable: bool, parent_style: &ComputedValues, cached_style: &ComputedValues, context: &computed::Context) -> ComputedValues { % for style_struct in STYLE_STRUCTS: % if style_struct.inherited: let mut style_${style_struct.ident} = parent_style.${style_struct.ident}.clone(); % else: let mut style_${style_struct.ident} = cached_style.${style_struct.ident}.clone(); % endif % endfor let mut seen = PropertyBitField::new(); // Declaration blocks are stored in increasing precedence order, // we want them in decreasing order here. for sub_list in applicable_declarations.iter().rev() { // Declarations are already stored in reverse order. for declaration in sub_list.declarations.iter() { match *declaration { % for style_struct in STYLE_STRUCTS: % for property in style_struct.longhands: % if property.derived_from is None: ${property.camel_case}Declaration(ref ${'_' if not style_struct.inherited else ''}declared_value) => { % if style_struct.inherited: if seen.get_${property.ident}() { continue } seen.set_${property.ident}(); let computed_value = match *declared_value { SpecifiedValue(ref specified_value) => longhands::${property.ident}::to_computed_value( (*specified_value).clone(), context ), Initial => longhands::${property.ident}::get_initial_value(), Inherit => { // This is a bit slow, but this is rare so it shouldn't // matter. // // FIXME: is it still? parent_style.${style_struct.ident} .${property.ident} .clone() } }; style_${style_struct.ident}.make_unique() .${property.ident} = computed_value; % endif % if property.name in DERIVED_LONGHANDS: % if not style_struct.inherited: // Use the cached value. let computed_value = style_${style_struct.ident} .${property.ident}.clone(); % endif % for derived in DERIVED_LONGHANDS[property.name]: style_${derived.style_struct.ident} .make_unique() .${derived.ident} = longhands::${derived.ident} ::derive_from_${property.ident}( computed_value, context); % endfor % endif } % else: ${property.camel_case}Declaration(_) => { // Do not allow stylesheets to set derived properties. } % endif % endfor % endfor } } } ComputedValues { writing_mode: get_writing_mode(&*style_inheritedbox), % for style_struct in STYLE_STRUCTS: ${style_struct.ident}: style_${style_struct.ident}, % endfor shareable: shareable, root_font_size: parent_style.root_font_size, } } /// Performs the CSS cascade, computing new styles for an element from its parent style and /// optionally a cached related style. The arguments are: /// /// * `applicable_declarations`: The list of CSS rules that matched. /// /// * `shareable`: Whether the `ComputedValues` structure to be constructed should be considered /// shareable. /// /// * `parent_style`: The parent style, if applicable; if `None`, this is the root node. /// /// * `cached_style`: If present, cascading is short-circuited for everything but inherited /// values and these values are used instead. Obviously, you must be careful when supplying /// this that it is safe to only provide inherited declarations. If `parent_style` is `None`, /// this is ignored. /// /// Returns the computed values and a boolean indicating whether the result is cacheable. pub fn cascade(applicable_declarations: &[DeclarationBlock], shareable: bool, parent_style: Option< &ComputedValues >, cached_style: Option< &ComputedValues >) -> (ComputedValues, bool) { let initial_values = &*INITIAL_VALUES; let (is_root_element, inherited_style) = match parent_style { Some(parent_style) => (false, parent_style), None => (true, initial_values), }; let mut context = { let inherited_font_style = inherited_style.get_font(); computed::Context { is_root_element: is_root_element, inherited_font_weight: inherited_font_style.font_weight, inherited_font_size: inherited_font_style.font_size, inherited_height: inherited_style.get_box().height, inherited_text_decorations_in_effect: inherited_style.get_inheritedtext()._servo_text_decorations_in_effect, // To be overridden by applicable declarations: font_size: inherited_font_style.font_size, root_font_size: inherited_style.root_font_size, display: longhands::display::get_initial_value(), color: inherited_style.get_color().color, text_decoration: longhands::text_decoration::get_initial_value(), positioned: false, floated: false, border_top_present: false, border_right_present: false, border_bottom_present: false, border_left_present: false, } }; // This assumes that the computed and specified values have the same Rust type. macro_rules! get_specified( ($style_struct_getter: ident, $property: ident, $declared_value: expr) => { match *$declared_value { SpecifiedValue(specified_value) => specified_value, Initial => longhands::$property::get_initial_value(), Inherit => inherited_style.$style_struct_getter().$property.clone(), } }; ) // Initialize `context` // Declarations blocks are already stored in increasing precedence order. for sub_list in applicable_declarations.iter() { // Declarations are stored in reverse source order, we want them in forward order here. for declaration in sub_list.declarations.iter().rev() { match *declaration { FontSizeDeclaration(ref value) => { context.font_size = match *value { SpecifiedValue(specified_value) => computed::compute_Au_with_font_size( specified_value, context.inherited_font_size, context.root_font_size), Initial => longhands::font_size::get_initial_value(), Inherit => context.inherited_font_size, } } ColorDeclaration(ref value) => { context.color = get_specified!(get_color, color, value); } DisplayDeclaration(ref value) => { context.display = get_specified!(get_box, display, value); } PositionDeclaration(ref value) => { context.positioned = match get_specified!(get_box, position, value) { longhands::position::absolute | longhands::position::fixed => true, _ => false, } } FloatDeclaration(ref value) => { context.floated = get_specified!(get_box, float, value) != longhands::float::none; } TextDecorationDeclaration(ref value) => { context.text_decoration = get_specified!(get_text, text_decoration, value); } % for side in ["top", "right", "bottom", "left"]: Border${side.capitalize()}StyleDeclaration(ref value) => { context.border_${side}_present = match get_specified!(get_border, border_${side}_style, value) { longhands::border_top_style::none | longhands::border_top_style::hidden => false, _ => true, }; } % endfor _ => {} } } } match (cached_style, parent_style) { (Some(cached_style), Some(parent_style)) => { return (cascade_with_cached_declarations(applicable_declarations, shareable, parent_style, cached_style, &context), false) } (_, _) => {} } // Set computed values, overwriting earlier declarations for the same property. % for style_struct in STYLE_STRUCTS: let mut style_${style_struct.ident} = % if style_struct.inherited: inherited_style % else: initial_values % endif .${style_struct.ident}.clone(); % endfor let mut cacheable = true; let mut seen = PropertyBitField::new(); // Declaration blocks are stored in increasing precedence order, // we want them in decreasing order here. for sub_list in applicable_declarations.iter().rev() { // Declarations are already stored in reverse order. for declaration in sub_list.declarations.iter() { match *declaration { % for style_struct in STYLE_STRUCTS: % for property in style_struct.longhands: % if property.derived_from is None: ${property.camel_case}Declaration(ref declared_value) => { if seen.get_${property.ident}() { continue } seen.set_${property.ident}(); let computed_value = match *declared_value { SpecifiedValue(ref specified_value) => longhands::${property.ident}::to_computed_value( (*specified_value).clone(), &context ), Initial => longhands::${property.ident}::get_initial_value(), Inherit => { // This is a bit slow, but this is rare so it shouldn't // matter. // // FIXME: is it still? cacheable = false; inherited_style.${style_struct.ident} .${property.ident} .clone() } }; style_${style_struct.ident}.make_unique() .${property.ident} = computed_value; % if property.name in DERIVED_LONGHANDS: % for derived in DERIVED_LONGHANDS[property.name]: style_${derived.style_struct.ident} .make_unique() .${derived.ident} = longhands::${derived.ident} ::derive_from_${property.ident}( computed_value, &context); % endfor % endif } % else: ${property.camel_case}Declaration(_) => { // Do not allow stylesheets to set derived properties. } % endif % endfor % endfor } } } // The initial value of border-*-width may be changed at computed value time. { let border = style_border.make_unique(); % for side in ["top", "right", "bottom", "left"]: // Like calling to_computed_value, which wouldn't type check. if !context.border_${side}_present { border.border_${side}_width = Au(0); } % endfor } // The initial value of display may be changed at computed value time. if !seen.get_display() { let box_ = style_box_.make_unique(); box_.display = longhands::display::to_computed_value(box_.display, &context); } if is_root_element { context.root_font_size = context.font_size; } (ComputedValues { writing_mode: get_writing_mode(&*style_inheritedbox), % for style_struct in STYLE_STRUCTS: ${style_struct.ident}: style_${style_struct.ident}, % endfor shareable: shareable, root_font_size: context.root_font_size, }, cacheable) } /// Equivalent to `cascade()` with an empty `applicable_declarations` /// Performs the CSS cascade for an anonymous box. /// /// * `parent_style`: Computed style of the element this anonymous box inherits from. pub fn cascade_anonymous(parent_style: &ComputedValues) -> ComputedValues { let initial_values = &*INITIAL_VALUES; let mut result = ComputedValues { % for style_struct in STYLE_STRUCTS: ${style_struct.ident}: % if style_struct.inherited: parent_style % else: initial_values % endif .${style_struct.ident}.clone(), % endfor shareable: false, writing_mode: parent_style.writing_mode, root_font_size: parent_style.root_font_size, }; { let border = result.border.make_unique(); % for side in ["top", "right", "bottom", "left"]: // Like calling to_computed_value, which wouldn't type check. border.border_${side}_width = Au(0); % endfor } // None of the teaks on 'display' apply here. result } // Only re-export the types for computed values. pub mod computed_values { % for property in LONGHANDS: pub use super::longhands::${property.ident}::computed_value as ${property.ident}; % endfor // Don't use a side-specific name needlessly: pub use super::longhands::border_top_style::computed_value as border_style; pub use cssparser::RGBA; pub use super::common_types::computed::{ LengthOrPercentage, LP_Length, LP_Percentage, LengthOrPercentageOrAuto, LPA_Length, LPA_Percentage, LPA_Auto, LengthOrPercentageOrNone, LPN_Length, LPN_Percentage, LPN_None}; }