--- 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,