Bug 1365900 - (wip) add initial style system support for @font-feature-falues rule. draft
authorJeremy Chen <jeremychen@mozilla.com>
Wed, 17 May 2017 10:52:20 +0800
changeset 582920 07d56991b17bb5abc726b81d1ee88d6982746980
parent 582912 8cc336b9806d0d16a0ae19d81fed5b2372ac7ea1
child 629909 eb4c722c2e77cde55fa52c6eb31e0700f8f98693
push id60234
push userbmo:jeremychen@mozilla.com
push dateTue, 23 May 2017 10:15:13 +0000
bugs1365900
milestone55.0a1
Bug 1365900 - (wip) add initial style system support for @font-feature-falues rule. Not ready for review yet! TODO: implement parser for each feature values MozReview-Commit-ID: DFnnhXzQ6kH
layout/style/ServoBindings.toml
servo/components/script/dom/cssrule.rs
servo/components/style/font_feature_values.rs
servo/components/style/lib.rs
servo/components/style/stylesheets.rs
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -152,16 +152,17 @@ whitelist-types = [
     "Keyframe",
     "nsAttrName",
     "nsAttrValue",
     "nsBorderColors",
     "nscolor",
     "nsChangeHint",
     "nsCSSCounterStyleRule",
     "nsCSSFontFaceRule",
+    "nsCSSFontFeatureValuesRule",
     "nsCSSKeyword",
     "nsCSSPropertyID",
     "nsCSSPropertyIDSet",
     "nsCSSProps",
     "nsCSSRect",
     "nsCSSRect_heap",
     "nsCSSShadowArray",
     "nsCSSValue",
--- a/servo/components/script/dom/cssrule.rs
+++ b/servo/components/script/dom/cssrule.rs
@@ -73,16 +73,17 @@ impl CSSRule {
     // CSSRule based on which rule it is
     pub fn new_specific(window: &Window, parent_stylesheet: &CSSStyleSheet,
                         rule: StyleCssRule) -> Root<CSSRule> {
         // be sure to update the match in as_specific when this is updated
         match rule {
             StyleCssRule::Import(s) => Root::upcast(CSSImportRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Style(s) => Root::upcast(CSSStyleRule::new(window, parent_stylesheet, s)),
             StyleCssRule::FontFace(s) => Root::upcast(CSSFontFaceRule::new(window, parent_stylesheet, s)),
+            StyleCssRule::FontFeatureValues(_) => unimplemented!(),
             StyleCssRule::CounterStyle(_) => unimplemented!(),
             StyleCssRule::Keyframes(s) => Root::upcast(CSSKeyframesRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Media(s) => Root::upcast(CSSMediaRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Namespace(s) => Root::upcast(CSSNamespaceRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Viewport(s) => Root::upcast(CSSViewportRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Supports(s) => Root::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)),
             StyleCssRule::Page(_) => unreachable!(),
             StyleCssRule::Document(_) => unimplemented!(), // TODO
new file mode 100644
--- /dev/null
+++ b/servo/components/style/font_feature_values.rs
@@ -0,0 +1,214 @@
+/* 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/. */
+
+//! The [`@font-feature-values`][font-feature-values] at-rule.
+//!
+//! [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
+
+use computed_values::font_family::FamilyName;
+use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
+use parser::{ParserContext, log_css_error, Parse};
+use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+use style_traits::ToCss;
+
+
+/// A parsed name-value pair for a single feature type
+#[derive(Debug, Clone)]
+pub struct FeatureValuePair {
+  /// A feature name.
+  pub name: Option<String>,
+  /// A feature value.
+  pub value: Option<i32>,
+}
+
+impl Parse for FeatureValuePair {
+    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        let name = try!(input.expect_ident());
+        try!(input.expect_colon());
+        let value = try!(input.expect_integer());
+        try!(input.expect_semicolon());
+        Ok(FeatureValuePair { name: Some(name.to_lowercase()), value: Some(value) })
+    }
+}
+
+impl ToCss for FeatureValuePair {
+  fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+    if self.name.is_some() && self.value.is_some() {
+        if let Some(ref value) = self.name {
+            write!(dest, "{}", value)?;
+        }
+        dest.write_str(": ")?;
+        if let Some(ref value) = self.value {
+            write!(dest, "{}", value)?;
+        }
+        dest.write_str(";")?;
+    }
+    Ok(())
+  }
+}
+
+/// A parsed struct for a `@font-feature-values` rule.
+#[derive(Debug)]
+pub struct FontFeatureValuesRuleData {
+  /// family-name
+  pub family_name: Option<FamilyName>,
+  /// @swash feature type
+  pub swash: Option<FontFeatureRuleData>,
+  // TODO: [ @stylistic, @styleset, @character-variant, @ornaments, @annotation ]
+}
+
+impl FontFeatureValuesRuleData {
+  fn empty() -> Self {
+      FontFeatureValuesRuleData {
+          family_name: None,
+          swash: None,
+      }
+  }
+}
+
+impl ToCssWithGuard for FontFeatureValuesRuleData {
+    fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+    where W: fmt::Write {
+        if self.family_name.is_some() && self.swash.is_some() {
+            dest.write_str("@")?;
+            if let Some(ref value) = self.family_name {
+                ToCss::to_css(value, dest)?;
+            }
+            dest.write_str(" {\n")?;
+            if let Some(ref value) = self.swash {
+                value.to_css(dest)?;
+            }
+            dest.write_str("}")?;
+        }
+        Ok(())
+    }
+}
+
+/// Parse the block inside a `@font-feature-values` rule.
+pub fn parse_font_feature_values_block(context: &ParserContext, input: &mut Parser) -> FontFeatureValuesRuleData {
+  let mut rule = FontFeatureValuesRuleData::empty();
+  {
+      let family_name = FamilyName::parse(context, input).unwrap();
+      if input.expect_curly_bracket_block().is_ok() {
+          // TODO:
+          //   Need a loop to parse every single value set containing name-value pairs for a single feature type
+          //   @<feature-type> { [ <feature-ident> : <feature-index>+ ; ]* }
+          //   Ex: @swash { flowing: 1; delicate: 2; }
+
+          rule.swash = Some(parse_single_feature_block(context, input));
+
+          // Fillin FontFeatureValuesRuleData only if we at least parse one feature type.
+          rule.family_name = Some(family_name);
+      }
+  }
+
+  rule
+}
+
+/// Parse the block for a single sub at rule for a single feature values, e.g., @swash
+pub fn parse_single_feature_block(context: &ParserContext, input: &mut Parser) -> FontFeatureRuleData {
+    // FIXME: parse an at keyword (e.g., @swash)
+    let mut rule = FontFeatureRuleData::empty("swash".to_lowercase());
+    {
+        let parser = FontFeatureRuleParser {
+            context: context,
+            rule: &mut rule,
+        };
+        let mut iter = DeclarationListParser::new(input, parser);
+        while let Some(declaration) = iter.next() {
+            if let Err(range) = declaration {
+                let pos = range.start;
+                let message = format!("Unsupported @font-feature-values descriptor declaration: '{}'",
+                                      iter.input.slice(range));
+                log_css_error(iter.input, pos, &*message, context);
+            }
+        }
+    }
+    rule
+}
+
+struct FontFeatureRuleParser<'a, 'b: 'a> {
+    context: &'a ParserContext<'b>,
+    rule: &'a mut FontFeatureRuleData,
+}
+
+/// Default methods reject all at rules.
+impl<'a, 'b> AtRuleParser for FontFeatureRuleParser<'a, 'b> {
+    type Prelude = ();
+    type AtRule = ();
+}
+
+macro_rules! font_feature_values_descriptors {
+    (
+        $( $name: tt $ident: ident $ty: ty = $initial: tt )+
+    ) => {
+        /// An @font-feature-values sub feature rule
+        #[derive(Debug, Clone)]
+        pub struct FontFeatureRuleData {
+            name: String,
+            features: Vec<FeatureValuePair>
+        }
+
+        impl FontFeatureRuleData {
+            fn empty(name: String) -> Self {
+                FontFeatureRuleData {
+                    name: name,
+                    features: vec!()
+                }
+            }
+
+            /// Get the name of the font feature rule.
+            pub fn name(&self) -> &String {
+                &self.name
+            }
+        }
+
+       impl<'a, 'b> DeclarationParser for FontFeatureRuleParser<'a, 'b> {
+            type Declaration = ();
+
+            fn parse_value(&mut self, _name: &str, input: &mut Parser) -> Result<(), ()> {
+                let mut result = Vec::new();
+                while let Ok(value) = input.try(|i| FeatureValuePair::parse(self.context, i)) {
+                    result.push(value);
+                }
+
+                if !result.is_empty() {
+                    self.rule.features  = result;
+                    Ok(())
+                } else {
+                    Err(())
+                }
+            }
+        }
+
+        impl ToCss for FontFeatureRuleData {
+            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+            where W: fmt::Write {
+                dest.write_str("@")?;
+                write!(dest, "{}", self.name)?;
+                dest.write_str(" {\n")?;
+                let mut first = true;
+                for feature in self.features.clone() {
+                    if !first {
+                        try!(dest.write_str("\n"));
+                    }
+                    first = false;
+                    try!(feature.to_css(dest));
+                }
+                dest.write_str("}")
+            }
+        }
+    }
+}
+
+/// https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
+font_feature_values_descriptors! {
+    "swash" swash FeatureValuePair = {
+        FeatureValuePair {
+            name: None,
+            value: None
+        }
+    }
+}
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -102,16 +102,17 @@ pub mod counter_style;
 pub mod custom_properties;
 pub mod data;
 pub mod document_condition;
 pub mod dom;
 pub mod element_state;
 #[cfg(feature = "servo")] mod encoding_support;
 pub mod error_reporting;
 pub mod font_face;
+pub mod font_feature_values;
 pub mod font_metrics;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko_bindings;
 pub mod keyframes;
 #[allow(missing_docs)] // TODO.
 pub mod logical_geometry;
 pub mod matching;
 pub mod media_queries;
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -14,16 +14,18 @@ use counter_style::CounterStyleRuleData;
 use cssparser::{AtRuleParser, Parser, QualifiedRuleParser};
 use cssparser::{AtRuleType, RuleListParser, parse_one_rule, SourceLocation};
 use cssparser::ToCss as ParserToCss;
 use document_condition::DocumentCondition;
 use error_reporting::{ParseErrorReporter, NullReporter};
 #[cfg(feature = "servo")]
 use font_face::FontFaceRuleData;
 use font_face::parse_font_face_block;
+use font_feature_values::FontFeatureValuesRuleData;
+use font_feature_values::parse_font_feature_values_block;
 #[cfg(feature = "gecko")]
 pub use gecko::rules::{CounterStyleRule, FontFaceRule};
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::URLExtraData;
 #[cfg(feature = "gecko")]
 use gecko_bindings::sugar::refptr::RefPtr;
 use keyframes::{Keyframe, KeyframeSelector, parse_keyframe_list};
 use media_queries::{Device, MediaList, parse_media_query_list};
@@ -292,16 +294,17 @@ pub enum CssRule {
     // No Charset here, CSSCharsetRule has been removed from CSSOM
     // https://drafts.csswg.org/cssom/#changes-from-5-december-2013
 
     Namespace(Arc<Locked<NamespaceRule>>),
     Import(Arc<Locked<ImportRule>>),
     Style(Arc<Locked<StyleRule>>),
     Media(Arc<Locked<MediaRule>>),
     FontFace(Arc<Locked<FontFaceRule>>),
+    FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
     CounterStyle(Arc<Locked<CounterStyleRule>>),
     Viewport(Arc<Locked<ViewportRule>>),
     Keyframes(Arc<Locked<KeyframesRule>>),
     Supports(Arc<Locked<SupportsRule>>),
     Page(Arc<Locked<PageRule>>),
     Document(Arc<Locked<DocumentRule>>),
 }
 
@@ -352,16 +355,17 @@ pub enum SingleRuleParseError {
 impl CssRule {
     #[allow(missing_docs)]
     pub fn rule_type(&self) -> CssRuleType {
         match *self {
             CssRule::Style(_) => CssRuleType::Style,
             CssRule::Import(_) => CssRuleType::Import,
             CssRule::Media(_) => CssRuleType::Media,
             CssRule::FontFace(_) => CssRuleType::FontFace,
+            CssRule::FontFeatureValues(_) => CssRuleType::FontFeatureValues,
             CssRule::CounterStyle(_) => CssRuleType::CounterStyle,
             CssRule::Keyframes(_) => CssRuleType::Keyframes,
             CssRule::Namespace(_) => CssRuleType::Namespace,
             CssRule::Viewport(_) => CssRuleType::Viewport,
             CssRule::Supports(_) => CssRuleType::Supports,
             CssRule::Page(_) => CssRuleType::Page,
             CssRule::Document(_)  => CssRuleType::Document,
         }
@@ -391,16 +395,17 @@ impl CssRule {
                 let rules = rule.stylesheet.rules.read_with(guard);
                 // FIXME(emilio): Include the nested rules if the stylesheet is
                 // loaded.
                 f(NestedRulesResult::RulesWithMediaQueries(&rules.0, &media))
             }
             CssRule::Namespace(_) |
             CssRule::Style(_) |
             CssRule::FontFace(_) |
+            CssRule::FontFeatureValues(_) |
             CssRule::CounterStyle(_) |
             CssRule::Viewport(_) |
             CssRule::Keyframes(_) |
             CssRule::Page(_) => {
                 f(NestedRulesResult::Rules(&[]))
             }
             CssRule::Media(ref lock) => {
                 let media_rule = lock.read_with(guard);
@@ -475,16 +480,17 @@ impl ToCssWithGuard for CssRule {
     // https://drafts.csswg.org/cssom/#serialize-a-css-rule
     fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
     where W: fmt::Write {
         match *self {
             CssRule::Namespace(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::FontFeatureValues(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest),
         }
@@ -710,16 +716,19 @@ impl ToCssWithGuard for StyleRule {
         Ok(())
     }
 }
 
 /// A @font-face rule
 #[cfg(feature = "servo")]
 pub type FontFaceRule = FontFaceRuleData;
 
+/// A @font-feature-values rule
+pub type FontFeatureValuesRule = FontFeatureValuesRuleData;
+
 /// A @counter-style rule
 #[cfg(feature = "servo")]
 pub type CounterStyleRule = CounterStyleRuleData;
 
 #[derive(Debug)]
 /// A @-moz-document rule
 pub struct DocumentRule {
     /// The parsed condition
@@ -951,16 +960,17 @@ macro_rules! rule_filter {
         }
     }
 }
 
 rule_filter! {
     effective_style_rules(Style => StyleRule),
     effective_media_rules(Media => MediaRule),
     effective_font_face_rules(FontFace => FontFaceRule),
+    effective_font_feature_values_rules(FontFeatureValues => FontFeatureValuesRule),
     effective_counter_style_rules(CounterStyle => CounterStyleRule),
     effective_viewport_rules(Viewport => ViewportRule),
     effective_keyframes_rules(Keyframes => KeyframesRule),
     effective_supports_rules(Supports => SupportsRule),
     effective_page_rules(Page => PageRule),
     effective_document_rules(Document => DocumentRule),
 }
 
@@ -1034,16 +1044,18 @@ pub enum VendorPrefix {
     Moz,
     /// -webkit prefix.
     WebKit,
 }
 
 enum AtRulePrelude {
     /// A @font-face rule prelude.
     FontFace(SourceLocation),
+    /// A @font-feature-values rule prelude.
+    FontFeatureValues,
     /// A @counter-style rule prelude, with its counter style name.
     CounterStyle(CustomIdent),
     /// A @media rule prelude, with its media queries.
     Media(Arc<Locked<MediaList>>, SourceLocation),
     /// An @supports rule, with its conditional
     Supports(SupportsCondition, SourceLocation),
     /// A @viewport rule prelude.
     Viewport,
@@ -1232,16 +1244,23 @@ impl<'a, 'b> AtRuleParser for NestedRule
             },
             "supports" => {
                 let cond = SupportsCondition::parse(input)?;
                 Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond, location)))
             },
             "font-face" => {
                 Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace(location)))
             },
+            "font-feature-values" => {
+                if !cfg!(feature = "gecko") {
+                    // Support for this rule is not fully implemented in Servo yet.
+                    return Err(())
+                }
+                Ok(AtRuleType::WithBlock(AtRulePrelude::FontFeatureValues))
+            },
             "counter-style" => {
                 if !cfg!(feature = "gecko") {
                     // Support for this rule is not fully implemented in Servo yet.
                     return Err(())
                 }
                 let name = parse_counter_style_name(input)?;
                 // ASCII-case-insensitive matches for "decimal" are already lower-cased
                 // by `parse_counter_style_name`, so we can use == here.
@@ -1296,16 +1315,21 @@ impl<'a, 'b> AtRuleParser for NestedRule
 
     fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> {
         match prelude {
             AtRulePrelude::FontFace(location) => {
                 let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFace));
                 Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
                    parse_font_face_block(&context, input, location).into()))))
             }
+            AtRulePrelude::FontFeatureValues => {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFeatureValues));
+                Ok(CssRule::FontFeatureValues(Arc::new(self.shared_lock.wrap(
+                   parse_font_feature_values_block(&context, input).into()))))
+            }
             AtRulePrelude::CounterStyle(name) => {
                 let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle));
                 Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(
                    parse_counter_style_body(name, &context, input)?.into()))))
             }
             AtRulePrelude::Media(media_queries, location) => {
                 Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
                     media_queries: media_queries,