diff --git a/properties/common_types.rs b/properties/common_types.rs index 16cdc1cbe7d..53b30dde27c 100644 --- a/properties/common_types.rs +++ b/properties/common_types.rs @@ -3,6 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use cssparser::*; +use parsing_utils::*; + pub type Float = f64; pub type Integer = i64; @@ -16,6 +19,7 @@ pub mod specified { Au(Integer), // application units Em(Float), Ex(Float), + // XXX uncomment when supported: // Ch(Float), // Rem(Float), // Vw(Float), @@ -136,3 +140,30 @@ pub mod computed { } } } + + +pub enum BorderStyle { + BorderStyleSolid, + // Uncomment when supported +// BorderStyleDotted, +// BorderStyleDashed, +// BorderStyleDouble, +// BorderStyleGroove, +// BorderStyleRidge, +// BorderStyleInset, +// BorderStyleOutset, +// BorderStyleHidden, + BorderStyleNone, +} +impl BorderStyle { + pub fn parse(input: &ComponentValue) -> Option { + do get_ident_lower(input).chain |keyword| { + match keyword.as_slice() { + "solid" => Some(BorderStyleSolid), + "none" => Some(BorderStyleNone), + _ => None, + } + } + } +} + diff --git a/properties/longhands.rs b/properties/longhands.rs index 06c193b7616..26aee4c2acb 100644 --- a/properties/longhands.rs +++ b/properties/longhands.rs @@ -4,23 +4,26 @@ pub use std::ascii::{to_ascii_lower, eq_ignore_ascii_case}; +pub use std::iterator; pub use std::option; pub use cssparser::*; pub use CSSColor = cssparser::Color; pub use parsing_utils::*; -pub use super::common_types::specified; -pub use super::common_types; +pub use super::common_types::*; macro_rules! single_keyword( ($property_name: ident, $( $lower_case_keyword_string: pat => $variant: ident ),+ ) => { - mod $property_name { + pub mod $property_name { use super::*; - enum SpecifiedValue { + pub enum SpecifiedValue { $( $variant ),+ } - fn parse(input: &[ComponentValue]) -> option::Option { - do one_component_value(input).chain(get_ident_lower).chain |keyword| { + pub fn parse(input: &[ComponentValue]) -> option::Option { + one_component_value(input).chain(from_component_value) + } + pub fn from_component_value(v: &ComponentValue) -> option::Option { + do get_ident_lower(v).chain |keyword| { match keyword.as_slice() { $( $lower_case_keyword_string => option::Some($variant) ),+ , _ => option::None, @@ -37,10 +40,10 @@ macro_rules! single_type( single_type!($property_name, $type_, $type_::parse) }; ($property_name: ident, $type_: ty, $parse_function: expr) => { - mod $property_name { + pub mod $property_name { use super::*; - type SpecifiedValue = $type_; - fn parse(input: &[ComponentValue]) -> Option { + pub type SpecifiedValue = $type_; + pub fn parse(input: &[ComponentValue]) -> Option { one_component_value(input).chain($parse_function) } } @@ -74,6 +77,11 @@ single_type!(border_right_color, CSSColor) single_type!(border_bottom_color, CSSColor) single_type!(border_left_color, CSSColor) +single_type!(border_top_style, BorderStyle) +single_type!(border_right_style, BorderStyle) +single_type!(border_bottom_style, BorderStyle) +single_type!(border_left_style, BorderStyle) + pub fn parse_border_width(component_value: &ComponentValue) -> Option { match component_value { &Ident(ref value) => match to_ascii_lower(value.as_slice()).as_slice() { @@ -91,6 +99,7 @@ single_type!(border_right_width, specified::Length, parse_border_width) single_type!(border_bottom_width, specified::Length, parse_border_width) single_type!(border_left_width, specified::Length, parse_border_width) + // CSS 2.1, Section 9 - Visual formatting model // TODO: don’t parse values we don’t support @@ -125,24 +134,27 @@ single_type!(width, specified::LengthOrPercentageOrAuto, single_type!(height, specified::LengthOrPercentageOrAuto, specified::LengthOrPercentageOrAuto::parse_non_negative) -mod line_height { +pub mod line_height { use super::*; - enum SpecifiedValue { + pub enum SpecifiedValue { Normal, Length(specified::Length), - Percentage(common_types::Float), - Number(common_types::Float), + Percentage(Float), + Number(Float), } /// normal | | | - fn parse(input: &[ComponentValue]) -> Option { - match one_component_value(input) { - Some(&ast::Number(ref value)) if value.value >= 0. + pub fn parse(input: &[ComponentValue]) -> Option { + one_component_value(input).chain(from_component_value) + } + pub fn from_component_value(input: &ComponentValue) -> Option { + match input { + &ast::Number(ref value) if value.value >= 0. => Some(Number(value.value)), - Some(&ast::Percentage(ref value)) if value.value >= 0. + &ast::Percentage(ref value) if value.value >= 0. => Some(Percentage(value.value)), - Some(&Dimension(ref value, ref unit)) if value.value >= 0. + &Dimension(ref value, ref unit) if value.value >= 0. => specified::Length::parse_dimension(value.value, unit.as_slice()).map_move(Length), - Some(&Ident(ref value)) if eq_ignore_ascii_case(value.as_slice(), "auto") + &Ident(ref value) if eq_ignore_ascii_case(value.as_slice(), "auto") => Some(Normal), _ => None, } @@ -163,7 +175,7 @@ single_type!(color, CSSColor) // CSS 2.1, Section 15 - Fonts -mod font_family { +pub mod font_family { use super::*; enum FontFamily { FamilyName(~str), @@ -174,13 +186,17 @@ mod font_family { Fantasy, Monospace, } - type SpecifiedValue = ~[FontFamily]; + pub type SpecifiedValue = ~[FontFamily]; /// # /// = | [ + ] /// TODO: - fn parse(input: &[ComponentValue]) -> Option { + pub fn parse(input: &[ComponentValue]) -> Option { + // XXX Using peekable() for compat with parsing of the 'font' shorthand. + from_iter(input.skip_whitespace().peekable()) + } + type Iter<'self> = iterator::Peekable<&'self ComponentValue, SkipWhitespaceIterator<'self>>; + pub fn from_iter(mut iter: Iter) -> Option { let mut result = ~[]; - let mut iter = input.skip_whitespace(); macro_rules! add( ($value: expr) => { { @@ -232,11 +248,15 @@ mod font_family { } single_keyword!(font_style, "normal" => Normal, "italic" => Italic, "oblique" => Oblique) -single_keyword!(font_variant, "normal" => Normal, "small-caps" => SmallCaps) +single_keyword!(font_variant, + // Uncomment when supported + //"small-caps" => SmallCaps, + "normal" => Normal +) -mod font_weight { +pub mod font_weight { use super::*; - enum SpecifiedValue { + pub enum SpecifiedValue { Bolder, Lighther, Weight100, @@ -250,16 +270,19 @@ mod font_weight { Weight900, } /// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 - fn parse(input: &[ComponentValue]) -> Option { - match one_component_value(input) { - Some(&Ident(ref value)) => match to_ascii_lower(value.as_slice()).as_slice() { + pub fn parse(input: &[ComponentValue]) -> Option { + one_component_value(input).chain(from_component_value) + } + pub fn from_component_value(input: &ComponentValue) -> Option { + match input { + &Ident(ref value) => match to_ascii_lower(value.as_slice()).as_slice() { "bold" => Some(Weight700), "normal" => Some(Weight400), "bolder" => Some(Bolder), "lighter" => Some(Lighther), _ => None, }, - Some(&Number(ref value)) => match value.int_value { + &Number(ref value) => match value.int_value { Some(100) => Some(Weight100), Some(200) => Some(Weight200), Some(300) => Some(Weight300), @@ -276,14 +299,16 @@ mod font_weight { } } -mod font_size { +pub mod font_size { use super::*; - type SpecifiedValue = specified::Length; // Percentages are the same as em. + pub type SpecifiedValue = specified::Length; // Percentages are the same as em. /// | /// TODO: support and - fn parse(input: &[ComponentValue]) -> Option { - do one_component_value(input).chain(specified::LengthOrPercentage::parse_non_negative) - .map_move |value| { + pub fn parse(input: &[ComponentValue]) -> Option { + one_component_value(input).chain(from_component_value) + } + pub fn from_component_value(input: &ComponentValue) -> Option { + do specified::LengthOrPercentage::parse_non_negative(input).map_move |value| { match value { specified::Length(value) => value, specified::Percentage(value) => specified::Em(value), @@ -297,9 +322,9 @@ mod font_size { single_keyword!(text_align, "left" => Left, "right" => Right, "center" => Center, "justify" => Justify) -mod text_decoration { +pub mod text_decoration { use super::*; - struct SpecifiedValue { + pub struct SpecifiedValue { underline: bool, overline: bool, line_through: bool, diff --git a/properties/mod.rs b/properties/mod.rs index 393fb66bea2..73a8716a828 100644 --- a/properties/mod.rs +++ b/properties/mod.rs @@ -6,8 +6,9 @@ use std::ascii::to_ascii_lower; use cssparser::*; use errors::{ErrorLoggerIterator, log_css_error}; -pub mod longhands; pub mod common_types; +pub mod longhands; +pub mod shorthands; pub struct PropertyDeclarationBlock { diff --git a/properties/shorthands.rs b/properties/shorthands.rs new file mode 100644 index 00000000000..014969b6b7f --- /dev/null +++ b/properties/shorthands.rs @@ -0,0 +1,286 @@ +/* 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 super::longhands::*; + + +macro_rules! shorthand( + ($name: ident[$($longhand: ident),+] |input| $parser: expr) => { + pub mod $name { + use super::*; + struct Longhands { + $( $longhand: Option<$longhand::SpecifiedValue> ),+ + } + fn parse(input: &[ComponentValue]) -> Option { $parser } + } + }; +) + + +// The value of each longhand is the same as the value of the shorthand +macro_rules! duplicating_shorthand( + ($name: ident, $parser_function: expr, $($longhand: ident),+) => { + shorthand!($name [$($longhand),+] |input| { + do $parser_function(input).map_move |value| { + Longhands { $( $longhand: Some(value) ),+ } + } + }) + }; +) + + +macro_rules! four_side_shorthand( + ($name: ident, $parser_function: expr, + $top: ident, $right: ident, $bottom: ident, $left: ident) => { + shorthand!($name [$top, $right, $bottom, $left] |input| { + let mut iter = input.skip_whitespace().map($parser_function); + // 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_default(None); + let right = iter.next().unwrap_or_default(top); + let bottom = iter.next().unwrap_or_default(top); + let left = iter.next().unwrap_or_default(right); + if top.is_some() && right.is_some() && bottom.is_some() && left.is_some() + && iter.next().is_none() { + Some(Longhands { $top: top, $right: right, $bottom: bottom, $left: left }) + } else { + None + } + }) + }; +) + + +// TODO: other background-* properties +shorthand!(background [ + background_color +] |input| { + do one_component_value(input).chain(CSSColor::parse).map_move |color| { + Longhands { background_color: Some(color) } + } +}) + + +duplicating_shorthand!(border_color, border_top_color::parse, + border_top_color, + border_right_color, + border_bottom_color, + border_left_color +) + +duplicating_shorthand!(border_width, border_top_width::parse, + border_top_width, + border_right_width, + border_bottom_width, + border_left_width +) + +duplicating_shorthand!(border_style, border_top_style::parse, + border_top_style, + border_right_style, + border_bottom_style, + border_left_style +) + + +pub fn parse_border(input: &[ComponentValue]) -> Option<(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 CSSColor::parse(component_value) { + Some(c) => { color = Some(c); any = true; loop }, + None => () + } + } + if style.is_none() { + match BorderStyle::parse(component_value) { + Some(s) => { style = Some(s); any = true; loop }, + None => () + } + } + if width.is_none() { + match parse_border_width(component_value) { + Some(w) => { width = Some(w); any = true; loop }, + None => () + } + } + return None + } + if any { Some((color, style, width)) } else { None } +} + + +shorthand!(border_top [ + border_top_color, + border_top_width, + border_top_style +] |input| { + do parse_border(input).map_move |(color, style, width)| { + Longhands { border_top_color: color, border_top_style: style, + border_top_width: width } + } +}) + +shorthand!(border_right [ + border_right_color, + border_right_width, + border_right_style +] |input| { + do parse_border(input).map_move |(color, style, width)| { + Longhands { border_right_color: color, border_right_style: style, + border_right_width: width } + } +}) + +shorthand!(border_bottom [ + border_bottom_color, + border_bottom_width, + border_bottom_style +] |input| { + do parse_border(input).map_move |(color, style, width)| { + Longhands { border_bottom_color: color, border_bottom_style: style, + border_bottom_width: width } + } +}) + +shorthand!(border_left [ + border_left_color, + border_left_width, + border_left_style +] |input| { + do parse_border(input).map_move |(color, style, width)| { + Longhands { border_left_color: color, border_left_style: style, + border_left_width: width } + } +}) + +shorthand!(border [ + border_top_color, + border_top_width, + border_top_style, + border_right_color, + border_right_width, + border_right_style, + border_bottom_color, + border_bottom_width, + border_bottom_style, + border_left_color, + border_left_width, + border_left_style +] |input| { + do parse_border(input).map_move |(color, style, width)| { + Longhands { + border_top_color: color, border_top_style: style, border_top_width: width, + border_right_color: color, border_right_style: style, border_right_width: width, + border_bottom_color: color, border_bottom_style: style, border_bottom_width: width, + border_left_color: color, border_left_style: style, border_left_width: width, + } + } +}) + + +// TODO: system fonts +shorthand!(font [ + font_style, + font_variant, + font_weight, + font_size, + line_height, + font_family +] |input| { + 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. + if get_ident_lower(component_value).filtered( + |v| eq_ignore_ascii_case(v.as_slice(), "normal")).is_some() { + nb_normals += 1; + loop; + } + if style.is_none() { + match font_style::from_component_value(component_value) { + Some(s) => { style = Some(s); loop }, + None => () + } + } + if weight.is_none() { + match font_weight::from_component_value(component_value) { + Some(w) => { weight = Some(w); loop }, + None => () + } + } + if variant.is_none() { + match font_variant::from_component_value(component_value) { + Some(v) => { variant = Some(v); loop }, + None => () + } + } + match font_size::from_component_value(component_value) { + Some(s) => { size = Some(s); break }, + None => return None + } + } + #[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 None + } + let mut iter = iter.peekable(); + match iter.peek() { + Some(& &Delim('/')) => { + iter.next(); + line_height = match iter.next() { + Some(v) => line_height::from_component_value(v), + _ => return None, + }; + if line_height.is_none() { return None } + } + _ => () + } + let family = font_family::from_iter(iter); + if family.is_none() { return None } + Some(Longhands{ + font_style: style, + font_variant: variant, + font_weight: weight, + font_size: size, + line_height: line_height, + font_family: family + }) +}) + + +four_side_shorthand!(margin, specified::LengthOrPercentageOrAuto::parse, + margin_top, + margin_right, + margin_bottom, + margin_left +) + + +four_side_shorthand!(padding, specified::LengthOrPercentage::parse, + padding_top, + padding_right, + padding_bottom, + padding_left +)