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