diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index 9283a14a71d5..85ebdfdaaabf 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -1646,7 +1646,7 @@ fn static_assert() { <%self:impl_trait style_struct_name="InheritedText" - skip_longhands="text-align text-shadow line-height letter-spacing word-spacing"> + skip_longhands="text-align text-emphasis-style text-shadow line-height letter-spacing word-spacing"> <% text_align_keyword = Keyword("text-align", "start end left right center justify -moz-center -moz-left " + "-moz-right match-parent") %> @@ -1744,6 +1744,55 @@ fn static_assert() { <%call expr="impl_coord_copy('word_spacing', 'mWordSpacing')"> + fn clear_text_emphasis_style_if_string(&mut self) { + use nsstring::nsString; + if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 { + self.gecko.mTextEmphasisStyleString.assign(&nsString::new()); + self.gecko.mTextEmphasisStyle = structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE as u8; + } + } + + pub fn set_text_emphasis_style(&mut self, v: longhands::text_emphasis_style::computed_value::T) { + use nsstring::nsCString; + use properties::longhands::text_emphasis_style::computed_value::T; + use properties::longhands::text_emphasis_style::ShapeKeyword; + + self.clear_text_emphasis_style_if_string(); + let (te, s) = match v { + T::None => (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE, ""), + T::Keyword(ref keyword) => { + let fill = if keyword.fill { + structs::NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED + } else { + structs::NS_STYLE_TEXT_EMPHASIS_STYLE_OPEN + }; + let shape = match keyword.shape { + ShapeKeyword::Dot => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOT, + ShapeKeyword::Circle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE, + ShapeKeyword::DoubleCircle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE, + ShapeKeyword::Triangle => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE, + ShapeKeyword::Sesame => structs::NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME, + }; + + (shape | fill, keyword.shape.char(keyword.fill)) + }, + T::String(ref s) => { + (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING, &**s) + }, + }; + self.gecko.mTextEmphasisStyleString.assign_utf8(&nsCString::from(s)); + self.gecko.mTextEmphasisStyle = te as u8; + } + + pub fn copy_text_emphasis_style_from(&mut self, other: &Self) { + self.clear_text_emphasis_style_if_string(); + if other.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 { + self.gecko.mTextEmphasisStyleString + .assign(&other.gecko.mTextEmphasisStyleString) + } + self.gecko.mTextEmphasisStyle = other.gecko.mTextEmphasisStyle; + } + <%self:impl_trait style_struct_name="Text" diff --git a/servo/components/style/properties/longhand/inherited_text.mako.rs b/servo/components/style/properties/longhand/inherited_text.mako.rs index c4006efea5f1..2d9c93dc8364 100644 --- a/servo/components/style/properties/longhand/inherited_text.mako.rs +++ b/servo/components/style/properties/longhand/inherited_text.mako.rs @@ -737,7 +737,205 @@ ${helpers.single_keyword("text-align-last", } +<%helpers:longhand name="text-emphasis-style" products="gecko" need_clone="True" animatable="False"> + use computed_values::writing_mode::T as writing_mode; + use cssparser::ToCss; + use std::fmt; + use values::LocalToCss; + use values::NoViewportPercentage; + impl NoViewportPercentage for SpecifiedValue {} + + pub mod computed_value { + #[derive(Debug, Clone, PartialEq)] + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub enum T { + Keyword(KeywordValue), + None, + String(String), + } + + #[derive(Debug, Clone, PartialEq)] + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub struct KeywordValue { + pub fill: bool, + pub shape: super::ShapeKeyword, + } + } + + #[derive(Debug, Clone, PartialEq)] + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub enum SpecifiedValue { + Keyword(KeywordValue), + None, + String(String), + } + + impl ToCss for computed_value::T { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + computed_value::T::Keyword(ref keyword) => keyword.to_css(dest), + computed_value::T::None => dest.write_str("none"), + computed_value::T::String(ref string) => write!(dest, "\"{}\"", string), + } + } + } + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + SpecifiedValue::Keyword(ref keyword) => keyword.to_css(dest), + SpecifiedValue::None => dest.write_str("none"), + SpecifiedValue::String(ref string) => write!(dest, "\"{}\"", string), + } + } + } + + #[derive(Debug, Clone, PartialEq)] + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub enum KeywordValue { + Fill(bool), + Shape(ShapeKeyword), + FillAndShape(bool, ShapeKeyword), + } + + impl ToCss for KeywordValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if let Some(fill) = self.fill() { + if fill { + try!(dest.write_str("filled")); + } else { + try!(dest.write_str("open")); + } + } + if let Some(shape) = self.shape() { + if self.fill().is_some() { + try!(dest.write_str(" ")); + } + try!(shape.to_css(dest)); + } + Ok(()) + } + } + impl ToCss for computed_value::KeywordValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.fill { + try!(dest.write_str("filled")); + } else { + try!(dest.write_str("open")); + } + try!(dest.write_str(" ")); + self.shape.to_css(dest) + } + } + + impl KeywordValue { + fn fill(&self) -> Option { + match *self { + KeywordValue::Fill(fill) | + KeywordValue::FillAndShape(fill,_) => Some(fill), + _ => None, + } + } + fn shape(&self) -> Option { + match *self { + KeywordValue::Shape(shape) | + KeywordValue::FillAndShape(_, shape) => Some(shape), + _ => None, + } + } + } + + define_css_keyword_enum!(ShapeKeyword: + "dot" => Dot, + "circle" => Circle, + "double-circle" => DoubleCircle, + "triangle" => Triangle, + "sesame" => Sesame); + + impl ShapeKeyword { + pub fn char(&self, fill: bool) -> &str { + match *self { + ShapeKeyword::Dot => if fill { "\u{2022}" } else { "\u{25e6}" }, + ShapeKeyword::Circle => if fill { "\u{25cf}" } else { "\u{25cb}" }, + ShapeKeyword::DoubleCircle => if fill { "\u{25c9}" } else { "\u{25ce}" }, + ShapeKeyword::Triangle => if fill { "\u{25b2}" } else { "\u{25b3}" }, + ShapeKeyword::Sesame => if fill { "\u{fe45}" } else { "\u{fe46}" }, + } + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::None + } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, context: &Context) -> computed_value::T { + match *self { + SpecifiedValue::Keyword(ref keyword) => { + let default_shape = if context.style().get_inheritedbox() + .clone_writing_mode() == writing_mode::horizontal_tb { + ShapeKeyword::Circle + } else { + ShapeKeyword::Sesame + }; + computed_value::T::Keyword(computed_value::KeywordValue { + fill: keyword.fill().unwrap_or(true), + shape: keyword.shape().unwrap_or(default_shape), + }) + }, + SpecifiedValue::None => computed_value::T::None, + SpecifiedValue::String(ref s) => { + let string = s.chars().next().as_ref().map(ToString::to_string).unwrap_or_default(); + computed_value::T::String(string) + } + } + } + #[inline] + fn from_computed_value(computed: &computed_value::T) -> Self { + match *computed { + computed_value::T::Keyword(ref keyword) => + SpecifiedValue::Keyword(KeywordValue::FillAndShape(keyword.fill,keyword.shape)), + computed_value::T::None => SpecifiedValue::None, + computed_value::T::String(ref string) => SpecifiedValue::String(string.clone()) + } + } + } + + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(SpecifiedValue::None); + } + + if let Ok(s) = input.try(|input| input.expect_string()) { + // Handle + return Ok(SpecifiedValue::String(s.into_owned())); + } + + // Handle a pair of keywords + let mut shape = input.try(ShapeKeyword::parse); + let fill = if input.try(|input| input.expect_ident_matching("filled")).is_ok() { + Some(true) + } else if input.try(|input| input.expect_ident_matching("open")).is_ok() { + Some(false) + } else { None }; + if shape.is_err() { + shape = input.try(ShapeKeyword::parse); + } + + // At least one of shape or fill must be handled + let keyword_value = match (fill, shape) { + (Some(fill), Ok(shape)) => KeywordValue::FillAndShape(fill,shape), + (Some(fill), Err(_)) => KeywordValue::Fill(fill), + (None, Ok(shape)) => KeywordValue::Shape(shape), + _ => return Err(()), + }; + Ok(SpecifiedValue::Keyword(keyword_value)) + } + // TODO(pcwalton): `full-width` ${helpers.single_keyword("text-transform", diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs index 613fe4e203ee..bfa7c7e9edf4 100644 --- a/servo/components/style/properties/properties.mako.rs +++ b/servo/components/style/properties/properties.mako.rs @@ -1639,7 +1639,8 @@ pub fn cascade(viewport_size: Size2D, PropertyDeclaration::Color(_) | PropertyDeclaration::Position(_) | PropertyDeclaration::Float(_) | - PropertyDeclaration::TextDecoration${'' if product == 'servo' else 'Line'}(_) + PropertyDeclaration::TextDecoration${'' if product == 'servo' else 'Line'}(_) | + PropertyDeclaration::WritingMode(_) ); if % if category_to_cascade_now == "early": diff --git a/servo/tests/unit/style/parsing/inherited_text.rs b/servo/tests/unit/style/parsing/inherited_text.rs new file mode 100644 index 000000000000..4248107b0c08 --- /dev/null +++ b/servo/tests/unit/style/parsing/inherited_text.rs @@ -0,0 +1,46 @@ +/* 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 cssparser::Parser; +use media_queries::CSSErrorReporterTest; +use style::parser::ParserContext; +use style::stylesheets::Origin; +use url::Url; + +#[test] +fn text_emphasis_style_longhand_should_parse_properly() { + use style::properties::longhands::text_emphasis_style; + use style::properties::longhands::text_emphasis_style::{ShapeKeyword, SpecifiedValue, KeywordValue}; + + let none = parse_longhand!(text_emphasis_style, "none"); + assert_eq!(none, SpecifiedValue::None); + + let fill = parse_longhand!(text_emphasis_style, "open"); + let fill_struct = SpecifiedValue::Keyword(KeywordValue::Fill(false)); + assert_eq!(fill, fill_struct); + + let shape = parse_longhand!(text_emphasis_style, "triangle"); + let shape_struct = SpecifiedValue::Keyword(KeywordValue::Shape(ShapeKeyword::Triangle)); + assert_eq!(shape, shape_struct); + + let fill_shape = parse_longhand!(text_emphasis_style, "filled dot"); + let fill_shape_struct = SpecifiedValue::Keyword(KeywordValue::FillAndShape(true, ShapeKeyword::Dot)); + assert_eq!(fill_shape, fill_shape_struct); + + let shape_fill = parse_longhand!(text_emphasis_style, "dot filled"); + let shape_fill_struct = SpecifiedValue::Keyword(KeywordValue::FillAndShape(true, ShapeKeyword::Dot)); + assert_eq!(shape_fill, shape_fill_struct); + + let a_string = parse_longhand!(text_emphasis_style, "\"a\""); + let a_string_struct = SpecifiedValue::String("a".to_string()); + assert_eq!(a_string, a_string_struct); + + let chinese_string = parse_longhand!(text_emphasis_style, "\"点\""); + let chinese_string_struct = SpecifiedValue::String("点".to_string()); + assert_eq!(chinese_string, chinese_string_struct); + + let unicode_string = parse_longhand!(text_emphasis_style, "\"\\25B2\""); + let unicode_string_struct = SpecifiedValue::String("▲".to_string()); + assert_eq!(unicode_string, unicode_string_struct); +} diff --git a/servo/tests/unit/style/parsing/mod.rs b/servo/tests/unit/style/parsing/mod.rs index c481ea865da0..27c64180a59d 100644 --- a/servo/tests/unit/style/parsing/mod.rs +++ b/servo/tests/unit/style/parsing/mod.rs @@ -63,6 +63,7 @@ macro_rules! parse_longhand { mod basic_shape; mod image; +mod inherited_text; mod mask; mod position; mod selectors;