From 9ea09f9d0d94b534fcbb204d2c412a11e5f358f2 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 13 Aug 2013 20:17:08 +0100 Subject: [PATCH] Parse all longhand properties that Servo seems to support. --- parsing_utils.rs | 21 ++ properties/common_types.rs | 138 ++++++++++++ properties/longhands.rs | 346 +++++++++++++++++++++++++++++ properties.rs => properties/mod.rs | 7 + servo-style.rc | 1 + 5 files changed, 513 insertions(+) create mode 100644 parsing_utils.rs create mode 100644 properties/common_types.rs create mode 100644 properties/longhands.rs rename properties.rs => properties/mod.rs (93%) diff --git a/parsing_utils.rs b/parsing_utils.rs new file mode 100644 index 00000000000..9f3cfddcab0 --- /dev/null +++ b/parsing_utils.rs @@ -0,0 +1,21 @@ +/* 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/. */ + + +use std::ascii::to_ascii_lower; +use cssparser::*; + + +pub fn one_component_value<'a>(input: &'a [ComponentValue]) -> Option<&'a ComponentValue> { + let mut iter = input.skip_whitespace(); + iter.next().filtered(|_| iter.next().is_none()) +} + + +pub fn get_ident_lower(component_value: &ComponentValue) -> Option<~str> { + match component_value { + &Ident(ref value) => Some(to_ascii_lower(value.as_slice())), + _ => None, + } +} diff --git a/properties/common_types.rs b/properties/common_types.rs new file mode 100644 index 00000000000..16cdc1cbe7d --- /dev/null +++ b/properties/common_types.rs @@ -0,0 +1,138 @@ +/* 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 type Float = f64; +pub type Integer = i64; + + +pub mod specified { + use std::ascii::{to_ascii_lower, eq_ignore_ascii_case}; + use cssparser::*; + use super::{Integer, Float}; + + pub enum Length { + Au(Integer), // application units + Em(Float), + Ex(Float), +// Ch(Float), +// Rem(Float), +// Vw(Float), +// Vh(Float), +// Vmin(Float), +// Vmax(Float), + } + static AU_PER_PX: Float = 60.; + static AU_PER_IN: Float = AU_PER_PX * 96.; + static AU_PER_CM: Float = AU_PER_IN / 2.54; + static AU_PER_MM: Float = AU_PER_IN / 25.4; + static AU_PER_PT: Float = AU_PER_IN / 72.; + static AU_PER_PC: Float = AU_PER_PT * 12.; + impl Length { + #[inline] + fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Option { + match input { + &Dimension(ref value, ref unit) if negative_ok || value.value >= 0. + => Length::parse_dimension(value.value, unit.as_slice()), + &Number(ref value) if value.value == 0. => Some(Au(0)), + _ => None + } + } + pub fn parse(input: &ComponentValue) -> Option { + Length::parse_internal(input, /* negative_ok = */ true) + } + pub fn parse_non_negative(input: &ComponentValue) -> Option { + Length::parse_internal(input, /* negative_ok = */ false) + } + pub fn parse_dimension(value: Float, unit: &str) -> Option { + match to_ascii_lower(unit).as_slice() { + "px" => Some(Length::from_px(value)), + "in" => Some(Au((value * AU_PER_IN) as Integer)), + "cm" => Some(Au((value * AU_PER_CM) as Integer)), + "mm" => Some(Au((value * AU_PER_MM) as Integer)), + "pt" => Some(Au((value * AU_PER_PT) as Integer)), + "pc" => Some(Au((value * AU_PER_PC) as Integer)), + "em" => Some(Em(value)), + "ex" => Some(Ex(value)), + _ => None + } + } + #[inline] + pub fn from_px(px_value: Float) -> Length { + Au((px_value * AU_PER_PX) as Integer) + } + } + + pub enum LengthOrPercentage { + Length(Length), + Percentage(Float), + } + impl LengthOrPercentage { + fn parse_internal(input: &ComponentValue, negative_ok: bool) + -> Option { + match input { + &Dimension(ref value, ref unit) if negative_ok || value.value >= 0. + => Length::parse_dimension(value.value, unit.as_slice()).map_move(Length), + &ast::Percentage(ref value) if negative_ok || value.value >= 0. + => Some(Percentage(value.value)), + &Number(ref value) if value.value == 0. => Some(Length(Au(0))), + _ => None + } + } + pub fn parse(input: &ComponentValue) -> Option { + LengthOrPercentage::parse_internal(input, /* negative_ok = */ true) + } + pub fn parse_non_negative(input: &ComponentValue) -> Option { + LengthOrPercentage::parse_internal(input, /* negative_ok = */ false) + } + } + + pub enum LengthOrPercentageOrAuto { + Length_(Length), + Percentage_(Float), + Auto, + } + impl LengthOrPercentageOrAuto { + #[inline] + fn parse_internal(input: &ComponentValue, negative_ok: bool) + -> Option { + match input { + &Dimension(ref value, ref unit) if negative_ok || value.value >= 0. + => Length::parse_dimension(value.value, unit.as_slice()).map_move(Length_), + &ast::Percentage(ref value) if negative_ok || value.value >= 0. + => Some(Percentage_(value.value)), + &Number(ref value) if value.value == 0. => Some(Length_(Au(0))), + &Ident(ref value) if eq_ignore_ascii_case(value.as_slice(), "auto") => Some(Auto), + _ => None + } + } + pub fn parse(input: &ComponentValue) -> Option { + LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true) + } + pub fn parse_non_negative(input: &ComponentValue) -> Option { + LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false) + } + } +} + +pub mod computed { + use super::*; + struct Length(Integer); // in application units + impl Length { + fn times(self, factor: Float) -> Length { + Length(((*self as Float) * factor) as Integer) + } + + pub fn compute(parent_font_size: Length, value: specified::Length) -> Length { + match value { + specified::Au(value) => Length(value), + specified::Em(value) => parent_font_size.times(value), + specified::Ex(value) => { + let x_height = 0.5; // TODO: find that form the font + parent_font_size.times(value * x_height) + }, + } + } + } +} diff --git a/properties/longhands.rs b/properties/longhands.rs new file mode 100644 index 00000000000..3335b0d6ec3 --- /dev/null +++ b/properties/longhands.rs @@ -0,0 +1,346 @@ +/* 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 std::ascii::{to_ascii_lower, eq_ignore_ascii_case}; +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; + + +macro_rules! single_keyword( + ($property_name: ident, $( $lower_case_keyword_string: pat => $variant: ident ),+ ) => { + mod $property_name { + use super::*; + enum SpecifiedValue { + $( $variant ),+ + } + fn parse(input: &[ComponentValue]) -> option::Option { + do one_component_value(input).chain(get_ident_lower).chain |keyword| { + match keyword.as_slice() { + $( $lower_case_keyword_string => option::Some($variant) ),+ , + _ => option::None, + } + } + } + } + }; +) + + +macro_rules! single_type( + ($property_name: ident, $type_: ident) => { + single_type!($property_name, $type_, $type_::parse) + }; + ($property_name: ident, $type_: ty, $parse_function: expr) => { + mod $property_name { + use super::*; + type SpecifiedValue = $type_; + fn parse(input: &[ComponentValue]) -> Option { + one_component_value(input).chain($parse_function) + } + } + }; +) + + + +// CSS 2.1, Section 8 - Box model + +single_type!(margin_top, specified::LengthOrPercentageOrAuto, + specified::LengthOrPercentageOrAuto::parse_non_negative) +single_type!(margin_right, specified::LengthOrPercentageOrAuto, + specified::LengthOrPercentageOrAuto::parse_non_negative) +single_type!(margin_bottom, specified::LengthOrPercentageOrAuto, + specified::LengthOrPercentageOrAuto::parse_non_negative) +single_type!(margin_left, specified::LengthOrPercentageOrAuto, + specified::LengthOrPercentageOrAuto::parse_non_negative) + +single_type!(padding_top, specified::LengthOrPercentage, + specified::LengthOrPercentage::parse_non_negative) +single_type!(padding_right, specified::LengthOrPercentage, + specified::LengthOrPercentage::parse_non_negative) +single_type!(padding_bottom, specified::LengthOrPercentage, + specified::LengthOrPercentage::parse_non_negative) +single_type!(padding_left, specified::LengthOrPercentage, + specified::LengthOrPercentage::parse_non_negative) + +single_type!(border_top_color, CSSColor) +single_type!(border_right_color, CSSColor) +single_type!(border_bottom_color, CSSColor) +single_type!(border_left_color, CSSColor) + +pub fn parse_border_width(component_value: &ComponentValue) -> Option { + match component_value { + &Ident(ref value) => match to_ascii_lower(value.as_slice()).as_slice() { + "thin" => Some(specified::Length::from_px(1.)), + "medium" => Some(specified::Length::from_px(3.)), + "thick" => Some(specified::Length::from_px(5.)), + _ => None + }, + _ => specified::Length::parse_non_negative(component_value) + } +} + +single_type!(border_top_width, specified::Length, parse_border_width) +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 +single_keyword!(display, + "inline" => Inline, + "block" => Block, + "list-item" => ListItem, + "inline-block" => InlineBlock, + "table" => Table, + "inline-table" => InlineTable, + "table-row-group" => TableRowGroup, + "table-header-group" => TableHeaderGroup, + "table-footer-group" => TableFooterGroup, + "table-row" => TableRow, + "table-column-group" => TableColumnGroup, + "table-column" => TableColumn, + "table-cell" => TableCell, + "table-caption" => TableCaption, + "none" => None +) + +single_keyword!(position, + "static" => Static, "absolute" => Absolute, "relative" => Relative, "fixed" => Fixed) +single_keyword!(float, "left" => Left, "right" => Right, "none" => None) +single_keyword!(clear, "left" => Left, "right" => Right, "none" => None, "both" => Both) + + +// CSS 2.1, Section 10 - Visual formatting model details + +single_type!(width, specified::Length, specified::Length::parse_non_negative) +single_type!(height, specified::Length, specified::Length::parse_non_negative) + +mod line_height { + use super::*; + enum SpecifiedValue { + Normal, + Length(specified::Length), + Percentage(common_types::Float), + Number(common_types::Float), + } + /// normal | | | + fn parse(input: &[ComponentValue]) -> Option { + match one_component_value(input) { + Some(&ast::Number(ref value)) if value.value >= 0. + => Some(Number(value.value)), + Some(&ast::Percentage(ref value)) if value.value >= 0. + => Some(Percentage(value.value)), + Some(&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") + => Some(Normal), + _ => None, + } + } +} + + +// CSS 2.1, Section 11 - Visual effects + +// CSS 2.1, Section 12 - Generated content, automatic numbering, and lists + +// CSS 2.1, Section 13 - Paged media + +// CSS 2.1, Section 14 - Colors and Backgrounds + +single_type!(background_color, CSSColor) +single_type!(color, CSSColor) + +// CSS 2.1, Section 15 - Fonts + +mod font_family { + use super::*; + enum FontFamily { + FamilyName(~str), + // Generic + Serif, + SansSerif, + Cursive, + Fantasy, + Monospace, + } + type SpecifiedValue = ~[FontFamily]; + /// # + /// = | [ + ] + /// TODO: + fn parse(input: &[ComponentValue]) -> Option { + let mut result = ~[]; + let mut iter = input.skip_whitespace(); + macro_rules! add( + ($value: expr) => { + { + result.push($value); + match iter.next() { + Some(&Comma) => (), + None => break 'outer, + _ => return None, + } + } + } + ) + 'outer: loop { + match iter.next() { + // TODO: avoid copying strings? + Some(&String(ref value)) => add!(FamilyName(value.to_owned())), + Some(&Ident(ref value)) => { + let value = value.as_slice(); + match to_ascii_lower(value).as_slice() { + "serif" => add!(Serif), + "sans-serif" => add!(SansSerif), + "cursive" => add!(Cursive), + "fantasy" => add!(Fantasy), + "monospace" => add!(Monospace), + _ => { + let mut idents = ~[value]; + loop { + match iter.next() { + Some(&Ident(ref value)) => idents.push(value.as_slice()), + Some(&Comma) => { + result.push(FamilyName(idents.connect(" "))); + break + }, + None => { + result.push(FamilyName(idents.connect(" "))); + break 'outer + }, + _ => return None, + } + } + } + } + } + _ => return None, + } + } + Some(result) + } +} + +single_keyword!(font_style, "normal" => Normal, "italic" => Italic, "oblique" => Oblique) +single_keyword!(font_variant, "normal" => Normal, "small-caps" => SmallCaps) + +mod font_weight { + use super::*; + enum SpecifiedValue { + Bolder, + Lighther, + Weight100, + Weight200, + Weight300, + Weight400, + Weight500, + Weight600, + Weight700, + Weight800, + 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() { + "bold" => Some(Weight700), + "normal" => Some(Weight400), + "bolder" => Some(Bolder), + "lighter" => Some(Lighther), + _ => None, + }, + Some(&Number(ref value)) => match value.int_value { + Some(100) => Some(Weight100), + Some(200) => Some(Weight200), + Some(300) => Some(Weight300), + Some(400) => Some(Weight400), + Some(500) => Some(Weight500), + Some(600) => Some(Weight600), + Some(700) => Some(Weight700), + Some(800) => Some(Weight800), + Some(900) => Some(Weight800), + _ => None, + }, + _ => None + } + } +} + +mod font_size { + use super::*; + 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| { + match value { + specified::Length(value) => value, + specified::Percentage(value) => specified::Em(value), + } + } + } +} + +// CSS 2.1, Section 16 - Text + +single_keyword!(text_align, "left" => Left, "right" => Right, + "center" => Center, "justify" => Justify) + +mod text_decoration { + use super::*; + struct SpecifiedValue { + underline: bool, + overline: bool, + line_through: bool, + // 'blink' is accepted in the parser but ignored. + // Just not blinking the text is a conforming implementation per CSS 2.1. + } + /// none | [ underline || overline || line-through || blink ] + fn parse(input: &[ComponentValue]) -> Option { + let mut result = SpecifiedValue { + underline: false, overline: false, line_through: false, + }; + let mut blink = false; + let mut empty = true; + macro_rules! found( + ($flag: expr) => ( + { + if $flag { return None } + empty = false; + $flag = true; + } + ); + ) + for component_value in input.skip_whitespace() { + match get_ident_lower(component_value) { + None => return None, + Some(keyword) => match keyword.as_slice() { + "underline" => if result.underline { return None } + else { empty = false; result.underline = true }, + "overline" => if result.overline { return None } + else { empty = false; result.overline = true }, + "line-through" => if result.line_through { return None } + else { empty = false; result.line_through = true }, + "blink" => if blink { return None } + else { empty = false; blink = true }, + "none" => return if empty { Some(result) } else { None }, + _ => return None, + } + } + } + if !empty { Some(result) } else { None } + } +} + +// CSS 2.1, Section 17 - Tables + +// CSS 2.1, Section 18 - User interface diff --git a/properties.rs b/properties/mod.rs similarity index 93% rename from properties.rs rename to properties/mod.rs index 81f947129ab..393fb66bea2 100644 --- a/properties.rs +++ b/properties/mod.rs @@ -6,6 +6,9 @@ use std::ascii::to_ascii_lower; use cssparser::*; use errors::{ErrorLoggerIterator, log_css_error}; +pub mod longhands; +pub mod common_types; + pub struct PropertyDeclarationBlock { important: ~[PropertyDeclaration], @@ -36,5 +39,9 @@ pub fn parse_property_declaration_list(input: ~[Node]) -> PropertyDeclarationBlo fn parse_one_property_declaration(name: &str, value: ~[ComponentValue], result_list: &mut ~[PropertyDeclaration]) -> bool { + + let _ = name; + let _ = value; + let _ = result_list; false } diff --git a/servo-style.rc b/servo-style.rc index 8425b53c8df..c4b891dd2d7 100644 --- a/servo-style.rc +++ b/servo-style.rc @@ -14,3 +14,4 @@ pub mod selectors; pub mod properties; pub mod namespaces; pub mod media_queries; +pub mod parsing_utils;