/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! The [`@font-face`][ff] at-rule.
//!
//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule

use crate::error_reporting::ContextualParseError;
use crate::parser::{Parse, ParserContext};
use crate::properties::longhands::font_language_override;
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::values::computed::font::{FamilyName, FontStretch};
use crate::values::generics::font::FontStyle as GenericFontStyle;
use crate::values::specified::font::{
    AbsoluteFontWeight, FontFeatureSettings, FontStretch as SpecifiedFontStretch,
    FontVariationSettings, MetricsOverride, SpecifiedFontStyle,
};
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::{Angle, NonNegativePercentage};
use cssparser::UnicodeRange;
use cssparser::{
    AtRuleParser, CowRcStr, DeclarationParser, Parser, ParserState, QualifiedRuleParser,
    RuleBodyItemParser, RuleBodyParser, SourceLocation,
};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write};
use style_traits::{CssStringWriter, CssWriter, ParseError};
use style_traits::{StyleParseErrorKind, ToCss};

/// A source for a font-face rule.
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
pub enum Source {
    /// A `url()` source.
    Url(UrlSource),
    /// A `local()` source.
    #[css(function)]
    Local(FamilyName),
}

/// A list of sources for the font-face src descriptor.
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
#[css(comma)]
pub struct SourceList(#[css(iterable)] pub Vec<Source>);

// We can't just use OneOrMoreSeparated to derive Parse for the Source list,
// because we want to filter out components that parsed as None, then fail if no
// valid components remain. So we provide our own implementation here.
impl Parse for SourceList {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        // Parse the comma-separated list, then let filter_map discard any None items.
        let list = input
            .parse_comma_separated(|input| {
                let s = input.parse_entirely(|input| Source::parse(context, input));
                while input.next().is_ok() {}
                Ok(s.ok())
            })?
            .into_iter()
            .filter_map(|s| s)
            .collect::<Vec<Source>>();
        if list.is_empty() {
            Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
        } else {
            Ok(SourceList(list))
        }
    }
}

/// Keywords for the font-face src descriptor's format() function.
/// ('None' and 'Unknown' are for internal use in gfx, not exposed to CSS.)
#[derive(Clone, Copy, Debug, Eq, Parse, PartialEq, ToCss, ToShmem)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceFormatKeyword {
    #[css(skip)]
    None,
    Collection,
    EmbeddedOpentype,
    Opentype,
    Svg,
    Truetype,
    Woff,
    Woff2,
    #[css(skip)]
    Unknown,
}

/// Flags for the @font-face tech() function, indicating font technologies
/// required by the resource.
#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(C)]
pub struct FontFaceSourceTechFlags(u16);
bitflags! {
    impl FontFaceSourceTechFlags: u16 {
        /// Font requires OpenType feature support.
        const FEATURES_OPENTYPE = 1 << 0;
        /// Font requires Apple Advanced Typography support.
        const FEATURES_AAT = 1 << 1;
        /// Font requires Graphite shaping support.
        const FEATURES_GRAPHITE = 1 << 2;
        /// Font requires COLRv0 rendering support (simple list of colored layers).
        const COLOR_COLRV0 = 1 << 3;
        /// Font requires COLRv1 rendering support (graph of paint operations).
        const COLOR_COLRV1 = 1 << 4;
        /// Font requires SVG glyph rendering support.
        const COLOR_SVG = 1 << 5;
        /// Font has bitmap glyphs in 'sbix' format.
        const COLOR_SBIX = 1 << 6;
        /// Font has bitmap glyphs in 'CBDT' format.
        const COLOR_CBDT = 1 << 7;
        /// Font requires OpenType Variations support.
        const VARIATIONS = 1 << 8;
        /// Font requires CPAL palette selection support.
        const PALETTES = 1 << 9;
        /// Font requires support for incremental downloading.
        const INCREMENTAL = 1 << 10;
    }
}

impl FontFaceSourceTechFlags {
    /// Parse a single font-technology keyword and return its flag.
    pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
        Ok(try_match_ident_ignore_ascii_case! { input,
            "features-opentype" => Self::FEATURES_OPENTYPE,
            "features-aat" => Self::FEATURES_AAT,
            "features-graphite" => Self::FEATURES_GRAPHITE,
            "color-colrv0" => Self::COLOR_COLRV0,
            "color-colrv1" => Self::COLOR_COLRV1,
            "color-svg" => Self::COLOR_SVG,
            "color-sbix" => Self::COLOR_SBIX,
            "color-cbdt" => Self::COLOR_CBDT,
            "variations" => Self::VARIATIONS,
            "palettes" => Self::PALETTES,
            "incremental" => Self::INCREMENTAL,
        })
    }
}

impl Parse for FontFaceSourceTechFlags {
    fn parse<'i, 't>(
        _context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let location = input.current_source_location();
        // We don't actually care about the return value of parse_comma_separated,
        // because we insert the flags into result as we go.
        let mut result = Self::empty();
        input.parse_comma_separated(|input| {
            let flag = Self::parse_one(input)?;
            result.insert(flag);
            Ok(())
        })?;
        if !result.is_empty() {
            Ok(result)
        } else {
            Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
        }
    }
}

#[allow(unused_assignments)]
impl ToCss for FontFaceSourceTechFlags {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: fmt::Write,
    {
        let mut first = true;

        macro_rules! write_if_flag {
            ($s:expr => $f:ident) => {
                if self.contains(Self::$f) {
                    if first {
                        first = false;
                    } else {
                        dest.write_str(", ")?;
                    }
                    dest.write_str($s)?;
                }
            };
        }

        write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
        write_if_flag!("features-aat" => FEATURES_AAT);
        write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
        write_if_flag!("color-colrv0" => COLOR_COLRV0);
        write_if_flag!("color-colrv1" => COLOR_COLRV1);
        write_if_flag!("color-svg" => COLOR_SVG);
        write_if_flag!("color-sbix" => COLOR_SBIX);
        write_if_flag!("color-cbdt" => COLOR_CBDT);
        write_if_flag!("variations" => VARIATIONS);
        write_if_flag!("palettes" => PALETTES);
        write_if_flag!("incremental" => INCREMENTAL);

        Ok(())
    }
}

/// A POD representation for Gecko. All pointers here are non-owned and as such
/// can't outlive the rule they came from, but we can't enforce that via C++.
///
/// All the strings are of course utf8.
#[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceListComponent {
    Url(*const crate::gecko::url::CssUrl),
    Local(*mut crate::gecko_bindings::structs::nsAtom),
    FormatHintKeyword(FontFaceSourceFormatKeyword),
    FormatHintString {
        length: usize,
        utf8_bytes: *const u8,
    },
    TechFlags(FontFaceSourceTechFlags),
}

#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceFormat {
    Keyword(FontFaceSourceFormatKeyword),
    String(String),
}

/// A `UrlSource` represents a font-face source that has been specified with a
/// `url()` function.
///
/// <https://drafts.csswg.org/css-fonts/#src-desc>
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
pub struct UrlSource {
    /// The specified url.
    pub url: SpecifiedUrl,
    /// The format hint specified with the `format()` function, if present.
    pub format_hint: Option<FontFaceSourceFormat>,
    /// The font technology flags specified with the `tech()` function, if any.
    pub tech_flags: FontFaceSourceTechFlags,
}

impl ToCss for UrlSource {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: fmt::Write,
    {
        self.url.to_css(dest)?;
        if let Some(hint) = &self.format_hint {
            dest.write_str(" format(")?;
            hint.to_css(dest)?;
            dest.write_char(')')?;
        }
        if !self.tech_flags.is_empty() {
            dest.write_str(" tech(")?;
            self.tech_flags.to_css(dest)?;
            dest.write_char(')')?;
        }
        Ok(())
    }
}

/// A font-display value for a @font-face rule.
/// The font-display descriptor determines how a font face is displayed based
/// on whether and when it is downloaded and ready to use.
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
    Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
)]
#[repr(u8)]
pub enum FontDisplay {
    Auto,
    Block,
    Swap,
    Fallback,
    Optional,
}

macro_rules! impl_range {
    ($range:ident, $component:ident) => {
        impl Parse for $range {
            fn parse<'i, 't>(
                context: &ParserContext,
                input: &mut Parser<'i, 't>,
            ) -> Result<Self, ParseError<'i>> {
                let first = $component::parse(context, input)?;
                let second = input
                    .try_parse(|input| $component::parse(context, input))
                    .unwrap_or_else(|_| first.clone());
                Ok($range(first, second))
            }
        }
        impl ToCss for $range {
            fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
            where
                W: fmt::Write,
            {
                self.0.to_css(dest)?;
                if self.0 != self.1 {
                    dest.write_char(' ')?;
                    self.1.to_css(dest)?;
                }
                Ok(())
            }
        }
    };
}

/// The font-weight descriptor:
///
/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
impl_range!(FontWeightRange, AbsoluteFontWeight);

/// The computed representation of the above so Gecko can read them easily.
///
/// This one is needed because cbindgen doesn't know how to generate
/// specified::Number.
#[repr(C)]
#[allow(missing_docs)]
pub struct ComputedFontWeightRange(f32, f32);

#[inline]
fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
    if a > b {
        (b, a)
    } else {
        (a, b)
    }
}

impl FontWeightRange {
    /// Returns a computed font-stretch range.
    pub fn compute(&self) -> ComputedFontWeightRange {
        let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value());
        ComputedFontWeightRange(min, max)
    }
}

/// The font-stretch descriptor:
///
/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
impl_range!(FontStretchRange, SpecifiedFontStretch);

/// The computed representation of the above, so that Gecko can read them
/// easily.
#[repr(C)]
#[allow(missing_docs)]
pub struct ComputedFontStretchRange(FontStretch, FontStretch);

impl FontStretchRange {
    /// Returns a computed font-stretch range.
    pub fn compute(&self) -> ComputedFontStretchRange {
        fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch {
            match *s {
                SpecifiedFontStretch::Keyword(ref kw) => kw.compute(),
                SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()),
                SpecifiedFontStretch::System(..) => unreachable!(),
            }
        }

        let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
        ComputedFontStretchRange(min, max)
    }
}

/// The font-style descriptor:
///
/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style
#[derive(Clone, Debug, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub enum FontStyle {
    Italic,
    Oblique(Angle, Angle),
}

/// The computed representation of the above, with angles in degrees, so that
/// Gecko can read them easily.
#[repr(u8)]
#[allow(missing_docs)]
pub enum ComputedFontStyleDescriptor {
    Italic,
    Oblique(f32, f32),
}

impl Parse for FontStyle {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        // We parse 'normal' explicitly here to distinguish it from 'oblique 0deg',
        // because we must not accept a following angle.
        if input
            .try_parse(|i| i.expect_ident_matching("normal"))
            .is_ok()
        {
            return Ok(FontStyle::Oblique(Angle::zero(), Angle::zero()));
        }

        let style = SpecifiedFontStyle::parse(context, input)?;
        Ok(match style {
            GenericFontStyle::Italic => FontStyle::Italic,
            GenericFontStyle::Oblique(angle) => {
                let second_angle = input
                    .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
                    .unwrap_or_else(|_| angle.clone());

                FontStyle::Oblique(angle, second_angle)
            },
        })
    }
}

impl ToCss for FontStyle {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: fmt::Write,
    {
        match *self {
            FontStyle::Italic => dest.write_str("italic"),
            FontStyle::Oblique(ref first, ref second) => {
                // Not first.is_zero() because we don't want to serialize
                // `oblique calc(0deg)` as `normal`.
                if *first == Angle::zero() && first == second {
                    return dest.write_str("normal");
                }
                dest.write_str("oblique")?;
                if *first != SpecifiedFontStyle::default_angle() || first != second {
                    dest.write_char(' ')?;
                    first.to_css(dest)?;
                }
                if first != second {
                    dest.write_char(' ')?;
                    second.to_css(dest)?;
                }
                Ok(())
            },
        }
    }
}

impl FontStyle {
    /// Returns a computed font-style descriptor.
    pub fn compute(&self) -> ComputedFontStyleDescriptor {
        match *self {
            FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
            FontStyle::Oblique(ref first, ref second) => {
                let (min, max) = sort_range(
                    SpecifiedFontStyle::compute_angle_degrees(first),
                    SpecifiedFontStyle::compute_angle_degrees(second),
                );
                ComputedFontStyleDescriptor::Oblique(min, max)
            },
        }
    }
}

/// Parse the block inside a `@font-face` rule.
///
/// Note that the prelude parsing code lives in the `stylesheets` module.
pub fn parse_font_face_block(
    context: &ParserContext,
    input: &mut Parser,
    location: SourceLocation,
) -> FontFaceRuleData {
    let mut rule = FontFaceRuleData::empty(location);
    {
        let mut parser = FontFaceRuleParser {
            context,
            rule: &mut rule,
        };
        let mut iter = RuleBodyParser::new(input, &mut parser);
        while let Some(declaration) = iter.next() {
            if let Err((error, slice)) = declaration {
                let location = error.location;
                let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
                context.log_css_error(location, error)
            }
        }
    }
    rule
}

/// A @font-face rule that is known to have font-family and src declarations.
#[cfg(feature = "servo")]
pub struct FontFace<'a>(&'a FontFaceRuleData);

struct FontFaceRuleParser<'a, 'b: 'a> {
    context: &'a ParserContext<'b>,
    rule: &'a mut FontFaceRuleData,
}

/// Default methods reject all at rules.
impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
    type Prelude = ();
    type AtRule = ();
    type Error = StyleParseErrorKind<'i>;
}

impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
    type Prelude = ();
    type QualifiedRule = ();
    type Error = StyleParseErrorKind<'i>;
}

impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
    for FontFaceRuleParser<'a, 'b>
{
    fn parse_qualified(&self) -> bool {
        false
    }
    fn parse_declarations(&self) -> bool {
        true
    }
}

impl Parse for Source {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Source, ParseError<'i>> {
        if input
            .try_parse(|input| input.expect_function_matching("local"))
            .is_ok()
        {
            return input
                .parse_nested_block(|input| FamilyName::parse(context, input))
                .map(Source::Local);
        }

        let url = SpecifiedUrl::parse(context, input)?;

        // Parsing optional format()
        let format_hint = if input
            .try_parse(|input| input.expect_function_matching("format"))
            .is_ok()
        {
            input.parse_nested_block(|input| {
                if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
                    Ok(Some(FontFaceSourceFormat::Keyword(kw)))
                } else {
                    let s = input.expect_string()?.as_ref().to_owned();
                    Ok(Some(FontFaceSourceFormat::String(s)))
                }
            })?
        } else {
            None
        };

        // Parse optional tech()
        let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled")
            && input
                .try_parse(|input| input.expect_function_matching("tech"))
                .is_ok()
        {
            input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
        } else {
            FontFaceSourceTechFlags::empty()
        };

        Ok(Source::Url(UrlSource {
            url,
            format_hint,
            tech_flags,
        }))
    }
}

macro_rules! is_descriptor_enabled {
    ("font-variation-settings") => {
        static_prefs::pref!("layout.css.font-variations.enabled")
    };
    ("size-adjust") => {
        cfg!(feature = "gecko")
    };
    ($name:tt) => {
        true
    };
}

macro_rules! font_face_descriptors_common {
    (
        $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
    ) => {
        /// Data inside a `@font-face` rule.
        ///
        /// <https://drafts.csswg.org/css-fonts/#font-face-rule>
        #[derive(Clone, Debug, PartialEq, ToShmem)]
        pub struct FontFaceRuleData {
            $(
                #[$doc]
                pub $ident: Option<$ty>,
            )*
            /// Line and column of the @font-face rule source code.
            pub source_location: SourceLocation,
        }

        impl FontFaceRuleData {
            /// Create an empty font-face rule
            pub fn empty(location: SourceLocation) -> Self {
                FontFaceRuleData {
                    $(
                        $ident: None,
                    )*
                    source_location: location,
                }
            }

            /// Serialization of declarations in the FontFaceRule
            pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
                $(
                    if let Some(ref value) = self.$ident {
                        dest.write_str(concat!($name, ": "))?;
                        value.to_css(&mut CssWriter::new(dest))?;
                        dest.write_str("; ")?;
                    }
                )*
                Ok(())
            }
        }

       impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
           type Declaration = ();
           type Error = StyleParseErrorKind<'i>;

           fn parse_value<'t>(
               &mut self,
               name: CowRcStr<'i>,
               input: &mut Parser<'i, 't>,
               _declaration_start: &ParserState,
            ) -> Result<(), ParseError<'i>> {
                match_ignore_ascii_case! { &*name,
                    $(
                        $name if is_descriptor_enabled!($name) => {
                            // DeclarationParser also calls parse_entirely
                            // so we’d normally not need to,
                            // but in this case we do because we set the value as a side effect
                            // rather than returning it.
                            let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
                            self.rule.$ident = Some(value)
                        },
                    )*
                    _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
                }
                Ok(())
            }
        }
    }
}

impl ToCssWithGuard for FontFaceRuleData {
    // Serialization of FontFaceRule is not specced.
    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
        dest.write_str("@font-face { ")?;
        self.decl_to_css(dest)?;
        dest.write_char('}')
    }
}

macro_rules! font_face_descriptors {
    (
        mandatory descriptors = [
            $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
        ]
        optional descriptors = [
            $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
        ]
    ) => {
        font_face_descriptors_common! {
            $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
            $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
        }

        impl FontFaceRuleData {
            /// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
            /// is valid as far as the CSS parser is concerned even if it doesn’t have
            /// a font-family or src declaration.
            ///
            /// However both are required for the rule to represent an actual font face.
            #[cfg(feature = "servo")]
            pub fn font_face(&self) -> Option<FontFace> {
                if $( self.$m_ident.is_some() )&&* {
                    Some(FontFace(self))
                } else {
                    None
                }
            }
        }

        #[cfg(feature = "servo")]
        impl<'a> FontFace<'a> {
            $(
                #[$m_doc]
                pub fn $m_ident(&self) -> &$m_ty {
                    self.0 .$m_ident.as_ref().unwrap()
                }
            )*
        }
    }
}

font_face_descriptors! {
    mandatory descriptors = [
        /// The name of this font face
        "font-family" family / mFamily: FamilyName,

        /// The alternative sources for this font face.
        "src" sources / mSrc: SourceList,
    ]
    optional descriptors = [
        /// The style of this font face.
        "font-style" style / mStyle: FontStyle,

        /// The weight of this font face.
        "font-weight" weight / mWeight: FontWeightRange,

        /// The stretch of this font face.
        "font-stretch" stretch / mStretch: FontStretchRange,

        /// The display of this font face.
        "font-display" display / mDisplay: FontDisplay,

        /// The ranges of code points outside of which this font face should not be used.
        "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,

        /// The feature settings of this font face.
        "font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings,

        /// The variation settings of this font face.
        "font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings,

        /// The language override of this font face.
        "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,

        /// The ascent override for this font face.
        "ascent-override" ascent_override / mAscentOverride: MetricsOverride,

        /// The descent override for this font face.
        "descent-override" descent_override / mDescentOverride: MetricsOverride,

        /// The line-gap override for this font face.
        "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride,

        /// The size adjustment for this font face.
        "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage,
    ]
}
