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