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