tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

font_face.rs (25748B)


      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 
      9 use crate::derives::*;
     10 use crate::error_reporting::ContextualParseError;
     11 use crate::parser::{Parse, ParserContext};
     12 use crate::properties::longhands::font_language_override;
     13 use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
     14 use crate::values::computed::font::{FamilyName, FontStretch};
     15 use crate::values::generics::font::FontStyle as GenericFontStyle;
     16 use crate::values::specified::font::{
     17    AbsoluteFontWeight, FontFeatureSettings, FontStretch as SpecifiedFontStretch,
     18    FontVariationSettings, MetricsOverride, SpecifiedFontStyle,
     19 };
     20 use crate::values::specified::url::SpecifiedUrl;
     21 use crate::values::specified::{Angle, NonNegativePercentage};
     22 use cssparser::{
     23    match_ignore_ascii_case, AtRuleParser, CowRcStr, DeclarationParser, Parser, ParserState,
     24    QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, UnicodeRange,
     25 };
     26 use selectors::parser::SelectorParseErrorKind;
     27 use std::fmt::{self, Write};
     28 use style_traits::{CssStringWriter, CssWriter, ParseError};
     29 use 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)]
     34 pub 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)]
     45 pub 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.
     50 impl 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)]
     79 pub 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)]
     98 pub struct FontFaceSourceTechFlags(u16);
     99 bitflags! {
    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 
    126 impl 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 
    145 impl 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)]
    168 impl 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)]
    212 pub 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)]
    227 pub 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)]
    238 pub 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 
    247 impl 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)]
    276 pub enum FontDisplay {
    277    Auto,
    278    Block,
    279    Swap,
    280    Fallback,
    281    Optional,
    282 }
    283 
    284 macro_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)]
    318 pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
    319 impl_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)]
    327 pub struct ComputedFontWeightRange(f32, f32);
    328 
    329 #[inline]
    330 fn 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 
    338 impl 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)]
    350 pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
    351 impl_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)]
    357 pub struct ComputedFontStretchRange(FontStretch, FontStretch);
    358 
    359 impl 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)]
    380 pub 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)]
    389 pub enum ComputedFontStyleDescriptor {
    390    Italic,
    391    Oblique(f32, f32),
    392 }
    393 
    394 impl 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 
    422 impl 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 
    450 impl 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.
    469 pub 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")]
    494 pub struct FontFace<'a>(&'a FontFaceRuleData);
    495 
    496 struct FontFaceRuleParser<'a, 'b: 'a> {
    497    context: &'a ParserContext<'b>,
    498    rule: &'a mut FontFaceRuleData,
    499 }
    500 
    501 /// Default methods reject all at rules.
    502 impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
    503    type Prelude = ();
    504    type AtRule = ();
    505    type Error = StyleParseErrorKind<'i>;
    506 }
    507 
    508 impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
    509    type Prelude = ();
    510    type QualifiedRule = ();
    511    type Error = StyleParseErrorKind<'i>;
    512 }
    513 
    514 impl<'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 
    525 impl 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 
    577 macro_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 
    589 macro_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 
    659 impl 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 
    668 macro_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 
    710 font_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 }