Skip to main content

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