style/stylesheets/
font_feature_values_rule.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The [`@font-feature-values`][font-feature-values] at-rule.
6//!
7//! [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
8
9use crate::error_reporting::ContextualParseError;
10#[cfg(feature = "gecko")]
11use crate::gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry;
12#[cfg(feature = "gecko")]
13use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet};
14use crate::parser::{Parse, ParserContext};
15use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
16use crate::stylesheets::CssRuleType;
17use crate::values::computed::font::FamilyName;
18use crate::values::serialize_atom_identifier;
19use crate::Atom;
20use cssparser::{
21    AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, Parser, ParserState,
22    QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, Token,
23};
24use std::fmt::{self, Write};
25use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
26#[cfg(feature = "gecko")]
27use thin_vec::ThinVec;
28
29/// A @font-feature-values block declaration.
30/// It is `<ident>: <integer>+`.
31/// This struct can take 3 value types.
32/// - `SingleValue` is to keep just one unsigned integer value.
33/// - `PairValues` is to keep one or two unsigned integer values.
34/// - `VectorValues` is to keep a list of unsigned integer values.
35#[derive(Clone, Debug, PartialEq, ToShmem)]
36pub struct FFVDeclaration<T> {
37    /// An `<ident>` for declaration name.
38    pub name: Atom,
39    /// An `<integer>+` for declaration value.
40    pub value: T,
41}
42
43impl<T: ToCss> ToCss for FFVDeclaration<T> {
44    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
45    where
46        W: Write,
47    {
48        serialize_atom_identifier(&self.name, dest)?;
49        dest.write_str(": ")?;
50        self.value.to_css(dest)?;
51        dest.write_char(';')
52    }
53}
54
55/// A trait for @font-feature-values rule to gecko values conversion.
56#[cfg(feature = "gecko")]
57pub trait ToGeckoFontFeatureValues {
58    /// Sets the equivalent of declaration to gecko `ThinVec<u32>` array.
59    fn to_gecko_font_feature_values(&self) -> ThinVec<u32>;
60}
61
62/// A @font-feature-values block declaration value that keeps one value.
63#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
64pub struct SingleValue(pub u32);
65
66impl Parse for SingleValue {
67    fn parse<'i, 't>(
68        _context: &ParserContext,
69        input: &mut Parser<'i, 't>,
70    ) -> Result<SingleValue, ParseError<'i>> {
71        let location = input.current_source_location();
72        match *input.next()? {
73            Token::Number {
74                int_value: Some(v), ..
75            } if v >= 0 => Ok(SingleValue(v as u32)),
76            ref t => Err(location.new_unexpected_token_error(t.clone())),
77        }
78    }
79}
80
81#[cfg(feature = "gecko")]
82impl ToGeckoFontFeatureValues for SingleValue {
83    fn to_gecko_font_feature_values(&self) -> ThinVec<u32> {
84        thin_vec::thin_vec![self.0 as u32]
85    }
86}
87
88/// A @font-feature-values block declaration value that keeps one or two values.
89#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
90pub struct PairValues(pub u32, pub Option<u32>);
91
92impl Parse for PairValues {
93    fn parse<'i, 't>(
94        _context: &ParserContext,
95        input: &mut Parser<'i, 't>,
96    ) -> Result<PairValues, ParseError<'i>> {
97        let location = input.current_source_location();
98        let first = match *input.next()? {
99            Token::Number {
100                int_value: Some(a), ..
101            } if a >= 0 => a as u32,
102            ref t => return Err(location.new_unexpected_token_error(t.clone())),
103        };
104        let location = input.current_source_location();
105        match input.next() {
106            Ok(&Token::Number {
107                int_value: Some(b), ..
108            }) if b >= 0 => Ok(PairValues(first, Some(b as u32))),
109            // It can't be anything other than number.
110            Ok(t) => Err(location.new_unexpected_token_error(t.clone())),
111            // It can be just one value.
112            Err(_) => Ok(PairValues(first, None)),
113        }
114    }
115}
116
117#[cfg(feature = "gecko")]
118impl ToGeckoFontFeatureValues for PairValues {
119    fn to_gecko_font_feature_values(&self) -> ThinVec<u32> {
120        let mut result = thin_vec::thin_vec![self.0 as u32];
121        if let Some(second) = self.1 {
122            result.push(second as u32);
123        }
124        result
125    }
126}
127
128/// A @font-feature-values block declaration value that keeps a list of values.
129#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
130pub struct VectorValues(#[css(iterable)] pub Vec<u32>);
131
132impl Parse for VectorValues {
133    fn parse<'i, 't>(
134        _context: &ParserContext,
135        input: &mut Parser<'i, 't>,
136    ) -> Result<VectorValues, ParseError<'i>> {
137        let mut vec = vec![];
138        loop {
139            let location = input.current_source_location();
140            match input.next() {
141                Ok(&Token::Number {
142                    int_value: Some(a), ..
143                }) if a >= 0 => {
144                    vec.push(a as u32);
145                },
146                // It can't be anything other than number.
147                Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
148                Err(_) => break,
149            }
150        }
151
152        if vec.len() == 0 {
153            return Err(input.new_error(BasicParseErrorKind::EndOfInput));
154        }
155
156        Ok(VectorValues(vec))
157    }
158}
159
160#[cfg(feature = "gecko")]
161impl ToGeckoFontFeatureValues for VectorValues {
162    fn to_gecko_font_feature_values(&self) -> ThinVec<u32> {
163        self.0.iter().copied().collect()
164    }
165}
166
167/// Parses a list of `FamilyName`s.
168pub fn parse_family_name_list<'i, 't>(
169    context: &ParserContext,
170    input: &mut Parser<'i, 't>,
171) -> Result<Vec<FamilyName>, ParseError<'i>> {
172    input
173        .parse_comma_separated(|i| FamilyName::parse(context, i))
174        .map_err(|e| e.into())
175}
176
177/// @font-feature-values inside block parser. Parses a list of `FFVDeclaration`.
178/// (`<ident>: <integer>+`)
179struct FFVDeclarationsParser<'a, 'b: 'a, T: 'a> {
180    context: &'a ParserContext<'b>,
181    declarations: &'a mut Vec<FFVDeclaration<T>>,
182}
183
184/// Default methods reject all at rules.
185impl<'a, 'b, 'i, T> AtRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> {
186    type Prelude = ();
187    type AtRule = ();
188    type Error = StyleParseErrorKind<'i>;
189}
190
191impl<'a, 'b, 'i, T> QualifiedRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> {
192    type Prelude = ();
193    type QualifiedRule = ();
194    type Error = StyleParseErrorKind<'i>;
195}
196
197impl<'a, 'b, 'i, T> DeclarationParser<'i> for FFVDeclarationsParser<'a, 'b, T>
198where
199    T: Parse,
200{
201    type Declaration = ();
202    type Error = StyleParseErrorKind<'i>;
203
204    fn parse_value<'t>(
205        &mut self,
206        name: CowRcStr<'i>,
207        input: &mut Parser<'i, 't>,
208        _declaration_start: &ParserState,
209    ) -> Result<(), ParseError<'i>> {
210        let value = input.parse_entirely(|i| T::parse(self.context, i))?;
211        let new = FFVDeclaration {
212            name: Atom::from(&*name),
213            value,
214        };
215        update_or_push(&mut self.declarations, new);
216        Ok(())
217    }
218}
219
220impl<'a, 'b, 'i, T> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
221    for FFVDeclarationsParser<'a, 'b, T>
222where
223    T: Parse,
224{
225    fn parse_declarations(&self) -> bool {
226        true
227    }
228    fn parse_qualified(&self) -> bool {
229        false
230    }
231}
232
233macro_rules! font_feature_values_blocks {
234    (
235        blocks = [
236            $( #[$doc: meta] $name: tt $ident: ident / $ident_camel: ident / $gecko_enum: ident: $ty: ty, )*
237        ]
238    ) => {
239        /// The [`@font-feature-values`][font-feature-values] at-rule.
240        ///
241        /// [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
242        #[derive(Clone, Debug, PartialEq, ToShmem)]
243        pub struct FontFeatureValuesRule {
244            /// Font family list for @font-feature-values rule.
245            /// Family names cannot contain generic families. FamilyName
246            /// also accepts only non-generic names.
247            pub family_names: Vec<FamilyName>,
248            $(
249                #[$doc]
250                pub $ident: Vec<FFVDeclaration<$ty>>,
251            )*
252            /// The line and column of the rule's source code.
253            pub source_location: SourceLocation,
254        }
255
256        impl FontFeatureValuesRule {
257            /// Creates an empty FontFeatureValuesRule with given location and family name list.
258            fn new(family_names: Vec<FamilyName>, location: SourceLocation) -> Self {
259                FontFeatureValuesRule {
260                    family_names: family_names,
261                    $(
262                        $ident: vec![],
263                    )*
264                    source_location: location,
265                }
266            }
267
268            /// Parses a `FontFeatureValuesRule`.
269            pub fn parse(
270                context: &ParserContext,
271                input: &mut Parser,
272                family_names: Vec<FamilyName>,
273                location: SourceLocation,
274            ) -> Self {
275                let mut rule = FontFeatureValuesRule::new(family_names, location);
276                let mut parser = FontFeatureValuesRuleParser {
277                    context,
278                    rule: &mut rule,
279                };
280                let mut iter = RuleBodyParser::new(input, &mut parser);
281                while let Some(result) = iter.next() {
282                    if let Err((error, slice)) = result {
283                        let location = error.location;
284                        let error = ContextualParseError::UnsupportedRule(slice, error);
285                        context.log_css_error(location, error);
286                    }
287                }
288                rule
289            }
290
291            /// Prints inside of `@font-feature-values` block.
292            pub fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
293            where
294                W: Write,
295            {
296                $(
297                    if self.$ident.len() > 0 {
298                        dest.write_str(concat!("@", $name, " {\n"))?;
299                        let iter = self.$ident.iter();
300                        for val in iter {
301                            val.to_css(dest)?;
302                            dest.write_str("\n")?
303                        }
304                        dest.write_str("}\n")?
305                    }
306                )*
307                Ok(())
308            }
309
310            /// Returns length of all at-rules.
311            pub fn len(&self) -> usize {
312                let mut len = 0;
313                $(
314                    len += self.$ident.len();
315                )*
316                len
317            }
318
319            /// Convert to Gecko gfxFontFeatureValueSet.
320            #[cfg(feature = "gecko")]
321            pub fn set_at_rules(&self, dest: *mut gfxFontFeatureValueSet) {
322                for ref family in self.family_names.iter() {
323                    let family = family.name.to_ascii_lowercase();
324                    $(
325                        if self.$ident.len() > 0 {
326                            for val in self.$ident.iter() {
327                                let array = unsafe {
328                                    Gecko_AppendFeatureValueHashEntry(
329                                        dest,
330                                        family.as_ptr(),
331                                        structs::$gecko_enum,
332                                        val.name.as_ptr()
333                                    )
334                                };
335                                unsafe {
336                                    *array = val.value.to_gecko_font_feature_values();
337                                }
338                            }
339                        }
340                    )*
341                }
342            }
343        }
344
345        impl ToCssWithGuard for FontFeatureValuesRule {
346            fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
347                dest.write_str("@font-feature-values ")?;
348                self.family_names.to_css(&mut CssWriter::new(dest))?;
349                dest.write_str(" {\n")?;
350                self.value_to_css(&mut CssWriter::new(dest))?;
351                dest.write_char('}')
352            }
353        }
354
355        /// Updates with new value if same `ident` exists, otherwise pushes to the vector.
356        fn update_or_push<T>(vec: &mut Vec<FFVDeclaration<T>>, element: FFVDeclaration<T>) {
357            if let Some(item) = vec.iter_mut().find(|item| item.name == element.name) {
358                item.value = element.value;
359            } else {
360                vec.push(element);
361            }
362        }
363
364        /// Keeps the information about block type like @swash, @styleset etc.
365        enum BlockType {
366            $(
367                $ident_camel,
368            )*
369        }
370
371        /// Parser for `FontFeatureValuesRule`. Parses all blocks
372        /// <feature-type> {
373        ///   <feature-value-declaration-list>
374        /// }
375        /// <feature-type> = @stylistic | @historical-forms | @styleset |
376        /// @character-variant | @swash | @ornaments | @annotation
377        struct FontFeatureValuesRuleParser<'a> {
378            context: &'a ParserContext<'a>,
379            rule: &'a mut FontFeatureValuesRule,
380        }
381
382        /// Default methods reject all qualified rules.
383        impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a> {
384            type Prelude = ();
385            type QualifiedRule = ();
386            type Error = StyleParseErrorKind<'i>;
387        }
388
389        impl<'a, 'i> AtRuleParser<'i> for FontFeatureValuesRuleParser<'a> {
390            type Prelude = BlockType;
391            type AtRule = ();
392            type Error = StyleParseErrorKind<'i>;
393
394            fn parse_prelude<'t>(
395                &mut self,
396                name: CowRcStr<'i>,
397                input: &mut Parser<'i, 't>,
398            ) -> Result<BlockType, ParseError<'i>> {
399                match_ignore_ascii_case! { &*name,
400                    $(
401                        $name => Ok(BlockType::$ident_camel),
402                    )*
403                    _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
404                }
405            }
406
407            fn parse_block<'t>(
408                &mut self,
409                prelude: BlockType,
410                _: &ParserState,
411                input: &mut Parser<'i, 't>
412            ) -> Result<Self::AtRule, ParseError<'i>> {
413                debug_assert!(self.context.rule_types().contains(CssRuleType::FontFeatureValues));
414                match prelude {
415                    $(
416                        BlockType::$ident_camel => {
417                            let mut parser = FFVDeclarationsParser {
418                                context: &self.context,
419                                declarations: &mut self.rule.$ident,
420                            };
421
422                            let mut iter = RuleBodyParser::new(input, &mut parser);
423                            while let Some(declaration) = iter.next() {
424                                if let Err((error, slice)) = declaration {
425                                    let location = error.location;
426                                    // TODO(emilio): Maybe add a more specific error kind for
427                                    // font-feature-values descriptors.
428                                    let error = ContextualParseError::UnsupportedPropertyDeclaration(slice, error, &[]);
429                                    self.context.log_css_error(location, error);
430                                }
431                            }
432                        },
433                    )*
434                }
435
436                Ok(())
437            }
438        }
439
440        impl<'a, 'i> DeclarationParser<'i> for FontFeatureValuesRuleParser<'a> {
441            type Declaration = ();
442            type Error = StyleParseErrorKind<'i>;
443        }
444
445        impl<'a, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> for FontFeatureValuesRuleParser<'a> {
446            fn parse_declarations(&self) -> bool { false }
447            fn parse_qualified(&self) -> bool { true }
448        }
449    }
450}
451
452font_feature_values_blocks! {
453    blocks = [
454        #[doc = "A @swash blocksck. \
455                 Specifies a feature name that will work with the swash() \
456                 functional notation of font-variant-alternates."]
457        "swash" swash / Swash / NS_FONT_VARIANT_ALTERNATES_SWASH: SingleValue,
458
459        #[doc = "A @stylistic block. \
460                 Specifies a feature name that will work with the annotation() \
461                 functional notation of font-variant-alternates."]
462        "stylistic" stylistic / Stylistic / NS_FONT_VARIANT_ALTERNATES_STYLISTIC: SingleValue,
463
464        #[doc = "A @ornaments block. \
465                 Specifies a feature name that will work with the ornaments() ] \
466                 functional notation of font-variant-alternates."]
467        "ornaments" ornaments / Ornaments / NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: SingleValue,
468
469        #[doc = "A @annotation block. \
470                 Specifies a feature name that will work with the stylistic() \
471                 functional notation of font-variant-alternates."]
472        "annotation" annotation / Annotation / NS_FONT_VARIANT_ALTERNATES_ANNOTATION: SingleValue,
473
474        #[doc = "A @character-variant block. \
475                 Specifies a feature name that will work with the styleset() \
476                 functional notation of font-variant-alternates. The value can be a pair."]
477        "character-variant" character_variant / CharacterVariant / NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT:
478            PairValues,
479
480        #[doc = "A @styleset block. \
481                 Specifies a feature name that will work with the character-variant() \
482                 functional notation of font-variant-alternates. The value can be a list."]
483        "styleset" styleset / Styleset / NS_FONT_VARIANT_ALTERNATES_STYLESET: VectorValues,
484    ]
485}