style/
font_face.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-face`][ff] at-rule.
6//!
7//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule
8
9use crate::error_reporting::ContextualParseError;
10use crate::parser::{Parse, ParserContext};
11use crate::properties::longhands::font_language_override;
12use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
13use crate::str::CssStringWriter;
14use crate::values::computed::font::{FamilyName, FontStretch};
15use crate::values::generics::font::FontStyle as GenericFontStyle;
16use crate::values::specified::font::{
17    AbsoluteFontWeight, FontFeatureSettings, FontStretch as SpecifiedFontStretch,
18    FontVariationSettings, MetricsOverride, SpecifiedFontStyle,
19};
20use crate::values::specified::url::SpecifiedUrl;
21use crate::values::specified::{Angle, NonNegativePercentage};
22use cssparser::UnicodeRange;
23use cssparser::{
24    AtRuleParser, CowRcStr, DeclarationParser, Parser, ParserState, QualifiedRuleParser,
25    RuleBodyItemParser, RuleBodyParser, SourceLocation,
26};
27use selectors::parser::SelectorParseErrorKind;
28use std::fmt::{self, Write};
29use style_traits::{CssWriter, ParseError};
30use style_traits::{StyleParseErrorKind, ToCss};
31
32/// A source for a font-face rule.
33#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
34#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
35pub enum Source {
36    /// A `url()` source.
37    Url(UrlSource),
38    /// A `local()` source.
39    #[css(function)]
40    Local(FamilyName),
41}
42
43/// A list of sources for the font-face src descriptor.
44#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
45#[css(comma)]
46pub struct SourceList(#[css(iterable)] pub Vec<Source>);
47
48// We can't just use OneOrMoreSeparated to derive Parse for the Source list,
49// because we want to filter out components that parsed as None, then fail if no
50// valid components remain. So we provide our own implementation here.
51impl Parse for SourceList {
52    fn parse<'i, 't>(
53        context: &ParserContext,
54        input: &mut Parser<'i, 't>,
55    ) -> Result<Self, ParseError<'i>> {
56        // Parse the comma-separated list, then let filter_map discard any None items.
57        let list = input
58            .parse_comma_separated(|input| {
59                let s = input.parse_entirely(|input| Source::parse(context, input));
60                while input.next().is_ok() {}
61                Ok(s.ok())
62            })?
63            .into_iter()
64            .filter_map(|s| s)
65            .collect::<Vec<Source>>();
66        if list.is_empty() {
67            Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
68        } else {
69            Ok(SourceList(list))
70        }
71    }
72}
73
74/// Keywords for the font-face src descriptor's format() function.
75/// ('None' and 'Unknown' are for internal use in gfx, not exposed to CSS.)
76#[derive(Clone, Copy, Debug, Eq, Parse, PartialEq, ToCss, ToShmem)]
77#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
78#[repr(u8)]
79#[allow(missing_docs)]
80pub enum FontFaceSourceFormatKeyword {
81    #[css(skip)]
82    None,
83    Collection,
84    EmbeddedOpentype,
85    Opentype,
86    Svg,
87    Truetype,
88    Woff,
89    Woff2,
90    #[css(skip)]
91    Unknown,
92}
93
94/// Flags for the @font-face tech() function, indicating font technologies
95/// required by the resource.
96#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
97#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
98#[repr(C)]
99pub struct FontFaceSourceTechFlags(u16);
100bitflags! {
101    impl FontFaceSourceTechFlags: u16 {
102        /// Font requires OpenType feature support.
103        const FEATURES_OPENTYPE = 1 << 0;
104        /// Font requires Apple Advanced Typography support.
105        const FEATURES_AAT = 1 << 1;
106        /// Font requires Graphite shaping support.
107        const FEATURES_GRAPHITE = 1 << 2;
108        /// Font requires COLRv0 rendering support (simple list of colored layers).
109        const COLOR_COLRV0 = 1 << 3;
110        /// Font requires COLRv1 rendering support (graph of paint operations).
111        const COLOR_COLRV1 = 1 << 4;
112        /// Font requires SVG glyph rendering support.
113        const COLOR_SVG = 1 << 5;
114        /// Font has bitmap glyphs in 'sbix' format.
115        const COLOR_SBIX = 1 << 6;
116        /// Font has bitmap glyphs in 'CBDT' format.
117        const COLOR_CBDT = 1 << 7;
118        /// Font requires OpenType Variations support.
119        const VARIATIONS = 1 << 8;
120        /// Font requires CPAL palette selection support.
121        const PALETTES = 1 << 9;
122        /// Font requires support for incremental downloading.
123        const INCREMENTAL = 1 << 10;
124    }
125}
126
127impl FontFaceSourceTechFlags {
128    /// Parse a single font-technology keyword and return its flag.
129    pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
130        Ok(try_match_ident_ignore_ascii_case! { input,
131            "features-opentype" => Self::FEATURES_OPENTYPE,
132            "features-aat" => Self::FEATURES_AAT,
133            "features-graphite" => Self::FEATURES_GRAPHITE,
134            "color-colrv0" => Self::COLOR_COLRV0,
135            "color-colrv1" => Self::COLOR_COLRV1,
136            "color-svg" => Self::COLOR_SVG,
137            "color-sbix" => Self::COLOR_SBIX,
138            "color-cbdt" => Self::COLOR_CBDT,
139            "variations" => Self::VARIATIONS,
140            "palettes" => Self::PALETTES,
141            "incremental" => Self::INCREMENTAL,
142        })
143    }
144}
145
146impl Parse for FontFaceSourceTechFlags {
147    fn parse<'i, 't>(
148        _context: &ParserContext,
149        input: &mut Parser<'i, 't>,
150    ) -> Result<Self, ParseError<'i>> {
151        let location = input.current_source_location();
152        // We don't actually care about the return value of parse_comma_separated,
153        // because we insert the flags into result as we go.
154        let mut result = Self::empty();
155        input.parse_comma_separated(|input| {
156            let flag = Self::parse_one(input)?;
157            result.insert(flag);
158            Ok(())
159        })?;
160        if !result.is_empty() {
161            Ok(result)
162        } else {
163            Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
164        }
165    }
166}
167
168#[allow(unused_assignments)]
169impl ToCss for FontFaceSourceTechFlags {
170    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
171    where
172        W: fmt::Write,
173    {
174        let mut first = true;
175
176        macro_rules! write_if_flag {
177            ($s:expr => $f:ident) => {
178                if self.contains(Self::$f) {
179                    if first {
180                        first = false;
181                    } else {
182                        dest.write_str(", ")?;
183                    }
184                    dest.write_str($s)?;
185                }
186            };
187        }
188
189        write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
190        write_if_flag!("features-aat" => FEATURES_AAT);
191        write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
192        write_if_flag!("color-colrv0" => COLOR_COLRV0);
193        write_if_flag!("color-colrv1" => COLOR_COLRV1);
194        write_if_flag!("color-svg" => COLOR_SVG);
195        write_if_flag!("color-sbix" => COLOR_SBIX);
196        write_if_flag!("color-cbdt" => COLOR_CBDT);
197        write_if_flag!("variations" => VARIATIONS);
198        write_if_flag!("palettes" => PALETTES);
199        write_if_flag!("incremental" => INCREMENTAL);
200
201        Ok(())
202    }
203}
204
205/// A POD representation for Gecko. All pointers here are non-owned and as such
206/// can't outlive the rule they came from, but we can't enforce that via C++.
207///
208/// All the strings are of course utf8.
209#[cfg(feature = "gecko")]
210#[derive(Clone, Copy, Debug, Eq, PartialEq)]
211#[repr(u8)]
212#[allow(missing_docs)]
213pub enum FontFaceSourceListComponent {
214    Url(*const crate::gecko::url::CssUrl),
215    Local(*mut crate::gecko_bindings::structs::nsAtom),
216    FormatHintKeyword(FontFaceSourceFormatKeyword),
217    FormatHintString {
218        length: usize,
219        utf8_bytes: *const u8,
220    },
221    TechFlags(FontFaceSourceTechFlags),
222}
223
224#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
225#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
226#[repr(u8)]
227#[allow(missing_docs)]
228pub enum FontFaceSourceFormat {
229    Keyword(FontFaceSourceFormatKeyword),
230    String(String),
231}
232
233/// A `UrlSource` represents a font-face source that has been specified with a
234/// `url()` function.
235///
236/// <https://drafts.csswg.org/css-fonts/#src-desc>
237#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
238#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
239pub struct UrlSource {
240    /// The specified url.
241    pub url: SpecifiedUrl,
242    /// The format hint specified with the `format()` function, if present.
243    pub format_hint: Option<FontFaceSourceFormat>,
244    /// The font technology flags specified with the `tech()` function, if any.
245    pub tech_flags: FontFaceSourceTechFlags,
246}
247
248impl ToCss for UrlSource {
249    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
250    where
251        W: fmt::Write,
252    {
253        self.url.to_css(dest)?;
254        if let Some(hint) = &self.format_hint {
255            dest.write_str(" format(")?;
256            hint.to_css(dest)?;
257            dest.write_char(')')?;
258        }
259        if !self.tech_flags.is_empty() {
260            dest.write_str(" tech(")?;
261            self.tech_flags.to_css(dest)?;
262            dest.write_char(')')?;
263        }
264        Ok(())
265    }
266}
267
268/// A font-display value for a @font-face rule.
269/// The font-display descriptor determines how a font face is displayed based
270/// on whether and when it is downloaded and ready to use.
271#[allow(missing_docs)]
272#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
273#[derive(
274    Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
275)]
276#[repr(u8)]
277pub enum FontDisplay {
278    Auto,
279    Block,
280    Swap,
281    Fallback,
282    Optional,
283}
284
285macro_rules! impl_range {
286    ($range:ident, $component:ident) => {
287        impl Parse for $range {
288            fn parse<'i, 't>(
289                context: &ParserContext,
290                input: &mut Parser<'i, 't>,
291            ) -> Result<Self, ParseError<'i>> {
292                let first = $component::parse(context, input)?;
293                let second = input
294                    .try_parse(|input| $component::parse(context, input))
295                    .unwrap_or_else(|_| first.clone());
296                Ok($range(first, second))
297            }
298        }
299        impl ToCss for $range {
300            fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
301            where
302                W: fmt::Write,
303            {
304                self.0.to_css(dest)?;
305                if self.0 != self.1 {
306                    dest.write_char(' ')?;
307                    self.1.to_css(dest)?;
308                }
309                Ok(())
310            }
311        }
312    };
313}
314
315/// The font-weight descriptor:
316///
317/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight
318#[derive(Clone, Debug, PartialEq, ToShmem)]
319pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
320impl_range!(FontWeightRange, AbsoluteFontWeight);
321
322/// The computed representation of the above so Gecko can read them easily.
323///
324/// This one is needed because cbindgen doesn't know how to generate
325/// specified::Number.
326#[repr(C)]
327#[allow(missing_docs)]
328pub struct ComputedFontWeightRange(f32, f32);
329
330#[inline]
331fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
332    if a > b {
333        (b, a)
334    } else {
335        (a, b)
336    }
337}
338
339impl FontWeightRange {
340    /// Returns a computed font-stretch range.
341    pub fn compute(&self) -> ComputedFontWeightRange {
342        let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value());
343        ComputedFontWeightRange(min, max)
344    }
345}
346
347/// The font-stretch descriptor:
348///
349/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
350#[derive(Clone, Debug, PartialEq, ToShmem)]
351pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
352impl_range!(FontStretchRange, SpecifiedFontStretch);
353
354/// The computed representation of the above, so that Gecko can read them
355/// easily.
356#[repr(C)]
357#[allow(missing_docs)]
358pub struct ComputedFontStretchRange(FontStretch, FontStretch);
359
360impl FontStretchRange {
361    /// Returns a computed font-stretch range.
362    pub fn compute(&self) -> ComputedFontStretchRange {
363        fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch {
364            match *s {
365                SpecifiedFontStretch::Keyword(ref kw) => kw.compute(),
366                SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()),
367                SpecifiedFontStretch::System(..) => unreachable!(),
368            }
369        }
370
371        let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
372        ComputedFontStretchRange(min, max)
373    }
374}
375
376/// The font-style descriptor:
377///
378/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style
379#[derive(Clone, Debug, PartialEq, ToShmem)]
380#[allow(missing_docs)]
381pub enum FontStyle {
382    Italic,
383    Oblique(Angle, Angle),
384}
385
386/// The computed representation of the above, with angles in degrees, so that
387/// Gecko can read them easily.
388#[repr(u8)]
389#[allow(missing_docs)]
390pub enum ComputedFontStyleDescriptor {
391    Italic,
392    Oblique(f32, f32),
393}
394
395impl Parse for FontStyle {
396    fn parse<'i, 't>(
397        context: &ParserContext,
398        input: &mut Parser<'i, 't>,
399    ) -> Result<Self, ParseError<'i>> {
400        // We parse 'normal' explicitly here to distinguish it from 'oblique 0deg',
401        // because we must not accept a following angle.
402        if input
403            .try_parse(|i| i.expect_ident_matching("normal"))
404            .is_ok()
405        {
406            return Ok(FontStyle::Oblique(Angle::zero(), Angle::zero()));
407        }
408
409        let style = SpecifiedFontStyle::parse(context, input)?;
410        Ok(match style {
411            GenericFontStyle::Italic => FontStyle::Italic,
412            GenericFontStyle::Oblique(angle) => {
413                let second_angle = input
414                    .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
415                    .unwrap_or_else(|_| angle.clone());
416
417                FontStyle::Oblique(angle, second_angle)
418            },
419        })
420    }
421}
422
423impl ToCss for FontStyle {
424    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
425    where
426        W: fmt::Write,
427    {
428        match *self {
429            FontStyle::Italic => dest.write_str("italic"),
430            FontStyle::Oblique(ref first, ref second) => {
431                // Not first.is_zero() because we don't want to serialize
432                // `oblique calc(0deg)` as `normal`.
433                if *first == Angle::zero() && first == second {
434                    return dest.write_str("normal");
435                }
436                dest.write_str("oblique")?;
437                if *first != SpecifiedFontStyle::default_angle() || first != second {
438                    dest.write_char(' ')?;
439                    first.to_css(dest)?;
440                }
441                if first != second {
442                    dest.write_char(' ')?;
443                    second.to_css(dest)?;
444                }
445                Ok(())
446            },
447        }
448    }
449}
450
451impl FontStyle {
452    /// Returns a computed font-style descriptor.
453    pub fn compute(&self) -> ComputedFontStyleDescriptor {
454        match *self {
455            FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
456            FontStyle::Oblique(ref first, ref second) => {
457                let (min, max) = sort_range(
458                    SpecifiedFontStyle::compute_angle_degrees(first),
459                    SpecifiedFontStyle::compute_angle_degrees(second),
460                );
461                ComputedFontStyleDescriptor::Oblique(min, max)
462            },
463        }
464    }
465}
466
467/// Parse the block inside a `@font-face` rule.
468///
469/// Note that the prelude parsing code lives in the `stylesheets` module.
470pub fn parse_font_face_block(
471    context: &ParserContext,
472    input: &mut Parser,
473    location: SourceLocation,
474) -> FontFaceRuleData {
475    let mut rule = FontFaceRuleData::empty(location);
476    {
477        let mut parser = FontFaceRuleParser {
478            context,
479            rule: &mut rule,
480        };
481        let mut iter = RuleBodyParser::new(input, &mut parser);
482        while let Some(declaration) = iter.next() {
483            if let Err((error, slice)) = declaration {
484                let location = error.location;
485                let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
486                context.log_css_error(location, error)
487            }
488        }
489    }
490    rule
491}
492
493/// A @font-face rule that is known to have font-family and src declarations.
494#[cfg(feature = "servo")]
495pub struct FontFace<'a>(&'a FontFaceRuleData);
496
497struct FontFaceRuleParser<'a, 'b: 'a> {
498    context: &'a ParserContext<'b>,
499    rule: &'a mut FontFaceRuleData,
500}
501
502/// Default methods reject all at rules.
503impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
504    type Prelude = ();
505    type AtRule = ();
506    type Error = StyleParseErrorKind<'i>;
507}
508
509impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
510    type Prelude = ();
511    type QualifiedRule = ();
512    type Error = StyleParseErrorKind<'i>;
513}
514
515impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
516    for FontFaceRuleParser<'a, 'b>
517{
518    fn parse_qualified(&self) -> bool {
519        false
520    }
521    fn parse_declarations(&self) -> bool {
522        true
523    }
524}
525
526impl Parse for Source {
527    fn parse<'i, 't>(
528        context: &ParserContext,
529        input: &mut Parser<'i, 't>,
530    ) -> Result<Source, ParseError<'i>> {
531        if input
532            .try_parse(|input| input.expect_function_matching("local"))
533            .is_ok()
534        {
535            return input
536                .parse_nested_block(|input| FamilyName::parse(context, input))
537                .map(Source::Local);
538        }
539
540        let url = SpecifiedUrl::parse(context, input)?;
541
542        // Parsing optional format()
543        let format_hint = if input
544            .try_parse(|input| input.expect_function_matching("format"))
545            .is_ok()
546        {
547            input.parse_nested_block(|input| {
548                if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
549                    Ok(Some(FontFaceSourceFormat::Keyword(kw)))
550                } else {
551                    let s = input.expect_string()?.as_ref().to_owned();
552                    Ok(Some(FontFaceSourceFormat::String(s)))
553                }
554            })?
555        } else {
556            None
557        };
558
559        // Parse optional tech()
560        let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled")
561            && input
562                .try_parse(|input| input.expect_function_matching("tech"))
563                .is_ok()
564        {
565            input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
566        } else {
567            FontFaceSourceTechFlags::empty()
568        };
569
570        Ok(Source::Url(UrlSource {
571            url,
572            format_hint,
573            tech_flags,
574        }))
575    }
576}
577
578macro_rules! is_descriptor_enabled {
579    ("font-variation-settings") => {
580        static_prefs::pref!("layout.css.font-variations.enabled")
581    };
582    ("size-adjust") => {
583        cfg!(feature = "gecko")
584    };
585    ($name:tt) => {
586        true
587    };
588}
589
590macro_rules! font_face_descriptors_common {
591    (
592        $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
593    ) => {
594        /// Data inside a `@font-face` rule.
595        ///
596        /// <https://drafts.csswg.org/css-fonts/#font-face-rule>
597        #[derive(Clone, Debug, PartialEq, ToShmem)]
598        pub struct FontFaceRuleData {
599            $(
600                #[$doc]
601                pub $ident: Option<$ty>,
602            )*
603            /// Line and column of the @font-face rule source code.
604            pub source_location: SourceLocation,
605        }
606
607        impl FontFaceRuleData {
608            /// Create an empty font-face rule
609            pub fn empty(location: SourceLocation) -> Self {
610                FontFaceRuleData {
611                    $(
612                        $ident: None,
613                    )*
614                    source_location: location,
615                }
616            }
617
618            /// Serialization of declarations in the FontFaceRule
619            pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
620                $(
621                    if let Some(ref value) = self.$ident {
622                        dest.write_str(concat!($name, ": "))?;
623                        value.to_css(&mut CssWriter::new(dest))?;
624                        dest.write_str("; ")?;
625                    }
626                )*
627                Ok(())
628            }
629        }
630
631       impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
632           type Declaration = ();
633           type Error = StyleParseErrorKind<'i>;
634
635           fn parse_value<'t>(
636               &mut self,
637               name: CowRcStr<'i>,
638               input: &mut Parser<'i, 't>,
639               _declaration_start: &ParserState,
640            ) -> Result<(), ParseError<'i>> {
641                match_ignore_ascii_case! { &*name,
642                    $(
643                        $name if is_descriptor_enabled!($name) => {
644                            // DeclarationParser also calls parse_entirely
645                            // so we’d normally not need to,
646                            // but in this case we do because we set the value as a side effect
647                            // rather than returning it.
648                            let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
649                            self.rule.$ident = Some(value)
650                        },
651                    )*
652                    _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
653                }
654                Ok(())
655            }
656        }
657    }
658}
659
660impl ToCssWithGuard for FontFaceRuleData {
661    // Serialization of FontFaceRule is not specced.
662    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
663        dest.write_str("@font-face { ")?;
664        self.decl_to_css(dest)?;
665        dest.write_char('}')
666    }
667}
668
669macro_rules! font_face_descriptors {
670    (
671        mandatory descriptors = [
672            $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
673        ]
674        optional descriptors = [
675            $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
676        ]
677    ) => {
678        font_face_descriptors_common! {
679            $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
680            $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
681        }
682
683        impl FontFaceRuleData {
684            /// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
685            /// is valid as far as the CSS parser is concerned even if it doesn’t have
686            /// a font-family or src declaration.
687            ///
688            /// However both are required for the rule to represent an actual font face.
689            #[cfg(feature = "servo")]
690            pub fn font_face(&self) -> Option<FontFace> {
691                if $( self.$m_ident.is_some() )&&* {
692                    Some(FontFace(self))
693                } else {
694                    None
695                }
696            }
697        }
698
699        #[cfg(feature = "servo")]
700        impl<'a> FontFace<'a> {
701            $(
702                #[$m_doc]
703                pub fn $m_ident(&self) -> &$m_ty {
704                    self.0 .$m_ident.as_ref().unwrap()
705                }
706            )*
707        }
708    }
709}
710
711font_face_descriptors! {
712    mandatory descriptors = [
713        /// The name of this font face
714        "font-family" family / mFamily: FamilyName,
715
716        /// The alternative sources for this font face.
717        "src" sources / mSrc: SourceList,
718    ]
719    optional descriptors = [
720        /// The style of this font face.
721        "font-style" style / mStyle: FontStyle,
722
723        /// The weight of this font face.
724        "font-weight" weight / mWeight: FontWeightRange,
725
726        /// The stretch of this font face.
727        "font-stretch" stretch / mStretch: FontStretchRange,
728
729        /// The display of this font face.
730        "font-display" display / mDisplay: FontDisplay,
731
732        /// The ranges of code points outside of which this font face should not be used.
733        "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
734
735        /// The feature settings of this font face.
736        "font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings,
737
738        /// The variation settings of this font face.
739        "font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings,
740
741        /// The language override of this font face.
742        "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
743
744        /// The ascent override for this font face.
745        "ascent-override" ascent_override / mAscentOverride: MetricsOverride,
746
747        /// The descent override for this font face.
748        "descent-override" descent_override / mDescentOverride: MetricsOverride,
749
750        /// The line-gap override for this font face.
751        "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride,
752
753        /// The size adjustment for this font face.
754        "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage,
755    ]
756}