diff options
Diffstat (limited to 'servo/components/style/stylesheets/font_feature_values_rule.rs')
-rw-r--r-- | servo/components/style/stylesheets/font_feature_values_rule.rs | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/servo/components/style/stylesheets/font_feature_values_rule.rs b/servo/components/style/stylesheets/font_feature_values_rule.rs new file mode 100644 index 0000000000..784628f0dd --- /dev/null +++ b/servo/components/style/stylesheets/font_feature_values_rule.rs @@ -0,0 +1,463 @@ +/* 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 https://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 crate::error_reporting::ContextualParseError; +#[cfg(feature = "gecko")] +use crate::gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry; +#[cfg(feature = "gecko")] +use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet, nsTArray}; +use crate::parser::{Parse, ParserContext}; +use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; +use crate::str::CssStringWriter; +use crate::stylesheets::CssRuleType; +use crate::values::computed::font::FamilyName; +use crate::values::serialize_atom_identifier; +use crate::Atom; +use cssparser::{AtRuleParser, BasicParseErrorKind, CowRcStr}; +use cssparser::{DeclarationListParser, DeclarationParser, Parser}; +use cssparser::{ParserState, QualifiedRuleParser, RuleListParser, SourceLocation, Token}; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; + +/// A @font-feature-values block declaration. +/// It is `<ident>: <integer>+`. +/// This struct can take 3 value types. +/// - `SingleValue` is to keep just one unsigned integer value. +/// - `PairValues` is to keep one or two unsigned integer values. +/// - `VectorValues` is to keep a list of unsigned integer values. +#[derive(Clone, Debug, PartialEq, ToShmem)] +pub struct FFVDeclaration<T> { + /// An `<ident>` for declaration name. + pub name: Atom, + /// An `<integer>+` for declaration value. + pub value: T, +} + +impl<T: ToCss> ToCss for FFVDeclaration<T> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + serialize_atom_identifier(&self.name, dest)?; + dest.write_str(": ")?; + self.value.to_css(dest)?; + dest.write_str(";") + } +} + +/// A trait for @font-feature-values rule to gecko values conversion. +#[cfg(feature = "gecko")] +pub trait ToGeckoFontFeatureValues { + /// Sets the equivalent of declaration to gecko `nsTArray<u32>` array. + fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>); +} + +/// A @font-feature-values block declaration value that keeps one value. +#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] +pub struct SingleValue(pub u32); + +impl Parse for SingleValue { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SingleValue, ParseError<'i>> { + let location = input.current_source_location(); + match *input.next()? { + Token::Number { + int_value: Some(v), .. + } if v >= 0 => Ok(SingleValue(v as u32)), + ref t => Err(location.new_unexpected_token_error(t.clone())), + } + } +} + +#[cfg(feature = "gecko")] +impl ToGeckoFontFeatureValues for SingleValue { + fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { + unsafe { + array.set_len_pod(1); + } + array[0] = self.0 as u32; + } +} + +/// A @font-feature-values block declaration value that keeps one or two values. +#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] +pub struct PairValues(pub u32, pub Option<u32>); + +impl Parse for PairValues { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PairValues, ParseError<'i>> { + let location = input.current_source_location(); + let first = match *input.next()? { + Token::Number { + int_value: Some(a), .. + } if a >= 0 => a as u32, + ref t => return Err(location.new_unexpected_token_error(t.clone())), + }; + let location = input.current_source_location(); + match input.next() { + Ok(&Token::Number { + int_value: Some(b), .. + }) if b >= 0 => Ok(PairValues(first, Some(b as u32))), + // It can't be anything other than number. + Ok(t) => Err(location.new_unexpected_token_error(t.clone())), + // It can be just one value. + Err(_) => Ok(PairValues(first, None)), + } + } +} + +#[cfg(feature = "gecko")] +impl ToGeckoFontFeatureValues for PairValues { + fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { + let len = if self.1.is_some() { 2 } else { 1 }; + + unsafe { + array.set_len_pod(len); + } + array[0] = self.0 as u32; + if let Some(second) = self.1 { + array[1] = second as u32; + }; + } +} + +/// A @font-feature-values block declaration value that keeps a list of values. +#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] +pub struct VectorValues(#[css(iterable)] pub Vec<u32>); + +impl Parse for VectorValues { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<VectorValues, ParseError<'i>> { + let mut vec = vec![]; + loop { + let location = input.current_source_location(); + match input.next() { + Ok(&Token::Number { + int_value: Some(a), .. + }) if a >= 0 => { + vec.push(a as u32); + }, + // It can't be anything other than number. + Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), + Err(_) => break, + } + } + + if vec.len() == 0 { + return Err(input.new_error(BasicParseErrorKind::EndOfInput)); + } + + Ok(VectorValues(vec)) + } +} + +#[cfg(feature = "gecko")] +impl ToGeckoFontFeatureValues for VectorValues { + fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { + array.assign_from_iter_pod(self.0.iter().map(|v| *v)); + } +} + +/// Parses a list of `FamilyName`s. +pub fn parse_family_name_list<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, +) -> Result<Vec<FamilyName>, ParseError<'i>> { + input + .parse_comma_separated(|i| FamilyName::parse(context, i)) + .map_err(|e| e.into()) +} + +/// @font-feature-values inside block parser. Parses a list of `FFVDeclaration`. +/// (`<ident>: <integer>+`) +struct FFVDeclarationsParser<'a, 'b: 'a, T: 'a> { + context: &'a ParserContext<'b>, + declarations: &'a mut Vec<FFVDeclaration<T>>, +} + +/// Default methods reject all at rules. +impl<'a, 'b, 'i, T> AtRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> { + type Prelude = (); + type AtRule = (); + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i, T> DeclarationParser<'i> for FFVDeclarationsParser<'a, 'b, T> +where + T: Parse, +{ + type Declaration = (); + type Error = StyleParseErrorKind<'i>; + + fn parse_value<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + let value = input.parse_entirely(|i| T::parse(self.context, i))?; + let new = FFVDeclaration { + name: Atom::from(&*name), + value: value, + }; + update_or_push(&mut self.declarations, new); + Ok(()) + } +} + +macro_rules! font_feature_values_blocks { + ( + blocks = [ + $( #[$doc: meta] $name: tt $ident: ident / $ident_camel: ident / $gecko_enum: ident: $ty: ty, )* + ] + ) => { + /// The [`@font-feature-values`][font-feature-values] at-rule. + /// + /// [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule + #[derive(Clone, Debug, PartialEq, ToShmem)] + pub struct FontFeatureValuesRule { + /// Font family list for @font-feature-values rule. + /// Family names cannot contain generic families. FamilyName + /// also accepts only non-generic names. + pub family_names: Vec<FamilyName>, + $( + #[$doc] + pub $ident: Vec<FFVDeclaration<$ty>>, + )* + /// The line and column of the rule's source code. + pub source_location: SourceLocation, + } + + impl FontFeatureValuesRule { + /// Creates an empty FontFeatureValuesRule with given location and family name list. + fn new(family_names: Vec<FamilyName>, location: SourceLocation) -> Self { + FontFeatureValuesRule { + family_names: family_names, + $( + $ident: vec![], + )* + source_location: location, + } + } + + /// Parses a `FontFeatureValuesRule`. + pub fn parse( + context: &ParserContext, + input: &mut Parser, + family_names: Vec<FamilyName>, + location: SourceLocation, + ) -> Self { + let mut rule = FontFeatureValuesRule::new(family_names, location); + + { + let mut iter = RuleListParser::new_for_nested_rule(input, FontFeatureValuesRuleParser { + context: context, + rule: &mut rule, + }); + while let Some(result) = iter.next() { + if let Err((error, slice)) = result { + let location = error.location; + let error = ContextualParseError::UnsupportedRule(slice, error); + context.log_css_error(location, error); + } + } + } + rule + } + + /// Prints inside of `@font-feature-values` block. + pub fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + $( + if self.$ident.len() > 0 { + dest.write_str(concat!("@", $name, " {\n"))?; + let iter = self.$ident.iter(); + for val in iter { + val.to_css(dest)?; + dest.write_str("\n")? + } + dest.write_str("}\n")? + } + )* + Ok(()) + } + + /// Returns length of all at-rules. + pub fn len(&self) -> usize { + let mut len = 0; + $( + len += self.$ident.len(); + )* + len + } + + /// Convert to Gecko gfxFontFeatureValueSet. + #[cfg(feature = "gecko")] + pub fn set_at_rules(&self, dest: *mut gfxFontFeatureValueSet) { + for ref family in self.family_names.iter() { + let family = family.name.to_ascii_lowercase(); + $( + if self.$ident.len() > 0 { + for val in self.$ident.iter() { + let array = unsafe { + Gecko_AppendFeatureValueHashEntry( + dest, + family.as_ptr(), + structs::$gecko_enum, + val.name.as_ptr() + ) + }; + unsafe { + val.value.to_gecko_font_feature_values(&mut *array); + } + } + } + )* + } + } + } + + impl ToCssWithGuard for FontFeatureValuesRule { + fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + dest.write_str("@font-feature-values ")?; + self.family_names.to_css(&mut CssWriter::new(dest))?; + dest.write_str(" {\n")?; + self.value_to_css(&mut CssWriter::new(dest))?; + dest.write_str("}") + } + } + + /// Updates with new value if same `ident` exists, otherwise pushes to the vector. + fn update_or_push<T>(vec: &mut Vec<FFVDeclaration<T>>, element: FFVDeclaration<T>) { + let position = vec.iter().position(|ref val| val.name == element.name); + if let Some(index) = position { + vec[index].value = element.value; + } else { + vec.push(element); + } + } + + /// Keeps the information about block type like @swash, @styleset etc. + enum BlockType { + $( + $ident_camel, + )* + } + + /// Parser for `FontFeatureValuesRule`. Parses all blocks + /// <feature-type> { + /// <feature-value-declaration-list> + /// } + /// <feature-type> = @stylistic | @historical-forms | @styleset | + /// @character-variant | @swash | @ornaments | @annotation + struct FontFeatureValuesRuleParser<'a> { + context: &'a ParserContext<'a>, + rule: &'a mut FontFeatureValuesRule, + } + + /// Default methods reject all qualified rules. + impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a> { + type Prelude = (); + type QualifiedRule = (); + type Error = StyleParseErrorKind<'i>; + } + + impl<'a, 'i> AtRuleParser<'i> for FontFeatureValuesRuleParser<'a> { + type Prelude = BlockType; + type AtRule = (); + type Error = StyleParseErrorKind<'i>; + + fn parse_prelude<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<BlockType, ParseError<'i>> { + match_ignore_ascii_case! { &*name, + $( + $name => Ok(BlockType::$ident_camel), + )* + _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), + } + } + + fn parse_block<'t>( + &mut self, + prelude: BlockType, + _: &ParserState, + input: &mut Parser<'i, 't> + ) -> Result<Self::AtRule, ParseError<'i>> { + debug_assert_eq!(self.context.rule_type(), CssRuleType::FontFeatureValues); + match prelude { + $( + BlockType::$ident_camel => { + let parser = FFVDeclarationsParser { + context: &self.context, + declarations: &mut self.rule.$ident, + }; + + let mut iter = DeclarationListParser::new(input, parser); + while let Some(declaration) = iter.next() { + if let Err((error, slice)) = declaration { + let location = error.location; + let error = ContextualParseError::UnsupportedKeyframePropertyDeclaration( + slice, error + ); + self.context.log_css_error(location, error); + } + } + }, + )* + } + + Ok(()) + } + } + } +} + +font_feature_values_blocks! { + blocks = [ + #[doc = "A @swash blocksck. \ + Specifies a feature name that will work with the swash() \ + functional notation of font-variant-alternates."] + "swash" swash / Swash / NS_FONT_VARIANT_ALTERNATES_SWASH: SingleValue, + + #[doc = "A @stylistic block. \ + Specifies a feature name that will work with the annotation() \ + functional notation of font-variant-alternates."] + "stylistic" stylistic / Stylistic / NS_FONT_VARIANT_ALTERNATES_STYLISTIC: SingleValue, + + #[doc = "A @ornaments block. \ + Specifies a feature name that will work with the ornaments() ] \ + functional notation of font-variant-alternates."] + "ornaments" ornaments / Ornaments / NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: SingleValue, + + #[doc = "A @annotation block. \ + Specifies a feature name that will work with the stylistic() \ + functional notation of font-variant-alternates."] + "annotation" annotation / Annotation / NS_FONT_VARIANT_ALTERNATES_ANNOTATION: SingleValue, + + #[doc = "A @character-variant block. \ + Specifies a feature name that will work with the styleset() \ + functional notation of font-variant-alternates. The value can be a pair."] + "character-variant" character_variant / CharacterVariant / NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT: + PairValues, + + #[doc = "A @styleset block. \ + Specifies a feature name that will work with the character-variant() \ + functional notation of font-variant-alternates. The value can be a list."] + "styleset" styleset / Styleset / NS_FONT_VARIANT_ALTERNATES_STYLESET: VectorValues, + ] +} |