Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


SSL font.rs   Sprache: unbekannt

 
/* 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/. */

//! Specified values for font properties

use crate::context::QuirksMode;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::font::{FamilyName, FontFamilyList, SingleFontFamily};
use crate::values::computed::Percentage as ComputedPercentage;
use crate::values::computed::{font as computed, Length, NonNegativeLength};
use crate::values::computed::{CSSPixelLength, Context, ToComputedValue};
use crate::values::generics::font::{
    self as generics, FeatureTagValue, FontSettings, FontTag, GenericLineHeight, VariationValue,
};
use crate::values::generics::NonNegative;
use crate::values::specified::length::{FontBaseSize, LineHeightBase, PX_PER_PT};
use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage};
use crate::values::specified::{
    FontRelativeLength, NoCalcLength, NonNegativeLengthPercentage, NonNegativeNumber,
    NonNegativePercentage, Number,
};
use crate::values::{serialize_atom_identifier, CustomIdent, SelectorParseErrorKind};
use crate::Atom;
use cssparser::{Parser, Token};
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
use std::fmt::{self, Write};
use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};

// FIXME(emilio): The system font code is copy-pasta, and should be cleaned up.
macro_rules! system_font_methods {
    ($ty:ident, $field:ident) => {
        system_font_methods!($ty);

        fn compute_system(&self, _context: &Context) -> <$ty as ToComputedValue>::ComputedValue {
            debug_assert!(matches!(*self, $ty::System(..)));
            #[cfg(feature = "gecko")]
            {
                _context.cached_system_font.as_ref().unwrap().$field.clone()
            }
            #[cfg(feature = "servo")]
            {
                unreachable!()
            }
        }
    };

    ($ty:ident) => {
        /// Get a specified value that represents a system font.
        pub fn system_font(f: SystemFont) -> Self {
            $ty::System(f)
        }

        /// Retreive a SystemFont from the specified value.
        pub fn get_system(&self) -> Option<SystemFont> {
            if let $ty::System(s) = *self {
                Some(s)
            } else {
                None
            }
        }
    };
}

/// System fonts.
#[repr(u8)]
#[derive(
    Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
#[allow(missing_docs)]
#[cfg(feature = "gecko")]
pub enum SystemFont {
    /// https://drafts.csswg.org/css-fonts/#valdef-font-caption
    Caption,
    /// https://drafts.csswg.org/css-fonts/#valdef-font-icon
    Icon,
    /// https://drafts.csswg.org/css-fonts/#valdef-font-menu
    Menu,
    /// https://drafts.csswg.org/css-fonts/#valdef-font-message-box
    MessageBox,
    /// https://drafts.csswg.org/css-fonts/#valdef-font-small-caption
    SmallCaption,
    /// https://drafts.csswg.org/css-fonts/#valdef-font-status-bar
    StatusBar,
    /// Internal system font, used by the `<menupopup>`s on macOS.
    #[parse(condition = "ParserContext::chrome_rules_enabled")]
    MozPullDownMenu,
    /// Internal system font, used for `<button>` elements.
    #[parse(condition = "ParserContext::chrome_rules_enabled")]
    MozButton,
    /// Internal font, used by `<select>` elements.
    #[parse(condition = "ParserContext::chrome_rules_enabled")]
    MozList,
    /// Internal font, used by `<input>` elements.
    #[parse(condition = "ParserContext::chrome_rules_enabled")]
    MozField,
    #[css(skip)]
    End, // Just for indexing purposes.
}

// We don't parse system fonts in servo, but in the interest of not
// littering a lot of code with `if engine == "gecko"` conditionals,
// we have a dummy system font module that does nothing

#[derive(
    Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem
)]
#[allow(missing_docs)]
#[cfg(feature = "servo")]
/// void enum for system font, can never exist
pub enum SystemFont {}

#[allow(missing_docs)]
#[cfg(feature = "servo")]
impl SystemFont {
    pub fn parse(_: &mut Parser) -> Result<Self, ()> {
        Err(())
    }
}

const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8;
const DEFAULT_SCRIPT_SIZE_MULTIPLIER: f64 = 0.71;

/// The minimum font-weight value per:
///
/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
pub const MIN_FONT_WEIGHT: f32 = 1.;

/// The maximum font-weight value per:
///
/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
pub const MAX_FONT_WEIGHT: f32 = 1000.;

/// A specified font-weight value.
///
/// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight
#[derive(
    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
pub enum FontWeight {
    /// `<font-weight-absolute>`
    Absolute(AbsoluteFontWeight),
    /// Bolder variant
    Bolder,
    /// Lighter variant
    Lighter,
    /// System font variant.
    #[css(skip)]
    System(SystemFont),
}

impl FontWeight {
    system_font_methods!(FontWeight, font_weight);

    /// `normal`
    #[inline]
    pub fn normal() -> Self {
        FontWeight::Absolute(AbsoluteFontWeight::Normal)
    }

    /// Get a specified FontWeight from a gecko keyword
    pub fn from_gecko_keyword(kw: u32) -> Self {
        debug_assert!(kw % 100 == 0);
        debug_assert!(kw as f32 <= MAX_FONT_WEIGHT);
        FontWeight::Absolute(AbsoluteFontWeight::Weight(Number::new(kw as f32)))
    }
}

impl ToComputedValue for FontWeight {
    type ComputedValue = computed::FontWeight;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        match *self {
            FontWeight::Absolute(ref abs) => abs.compute(),
            FontWeight::Bolder => context
                .builder
                .get_parent_font()
                .clone_font_weight()
                .bolder(),
            FontWeight::Lighter => context
                .builder
                .get_parent_font()
                .clone_font_weight()
                .lighter(),
            FontWeight::System(_) => self.compute_system(context),
        }
    }

    #[inline]
    fn from_computed_value(computed: &computed::FontWeight) -> Self {
        FontWeight::Absolute(AbsoluteFontWeight::Weight(Number::from_computed_value(
            &computed.value(),
        )))
    }
}

/// An absolute font-weight value for a @font-face rule.
///
/// https://drafts.csswg.org/css-fonts-4/#font-weight-absolute-values
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub enum AbsoluteFontWeight {
    /// A `<number>`, with the additional constraints specified in:
    ///
    ///   https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
    Weight(Number),
    /// Normal font weight. Same as 400.
    Normal,
    /// Bold font weight. Same as 700.
    Bold,
}

impl AbsoluteFontWeight {
    /// Returns the computed value for this absolute font weight.
    pub fn compute(&self) -> computed::FontWeight {
        match *self {
            AbsoluteFontWeight::Weight(weight) => computed::FontWeight::from_float(weight.get()),
            AbsoluteFontWeight::Normal => computed::FontWeight::NORMAL,
            AbsoluteFontWeight::Bold => computed::FontWeight::BOLD,
        }
    }
}

impl Parse for AbsoluteFontWeight {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        if let Ok(number) = input.try_parse(|input| Number::parse(context, input)) {
            // We could add another AllowedNumericType value, but it doesn't
            // seem worth it just for a single property with such a weird range,
            // so we do the clamping here manually.
            if !number.was_calc() &&
                (number.get() < MIN_FONT_WEIGHT || number.get() > MAX_FONT_WEIGHT)
            {
                return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
            }
            return Ok(AbsoluteFontWeight::Weight(number));
        }

        Ok(try_match_ident_ignore_ascii_case! { input,
            "normal" => AbsoluteFontWeight::Normal,
            "bold" => AbsoluteFontWeight::Bold,
        })
    }
}

/// The specified value of the `font-style` property, without the system font
/// crap.
pub type SpecifiedFontStyle = generics::FontStyle<Angle>;

impl ToCss for SpecifiedFontStyle {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        match *self {
            generics::FontStyle::Normal => dest.write_str("normal"),
            generics::FontStyle::Italic => dest.write_str("italic"),
            generics::FontStyle::Oblique(ref angle) => {
                dest.write_str("oblique")?;
                if *angle != Self::default_angle() {
                    dest.write_char(' ')?;
                    angle.to_css(dest)?;
                }
                Ok(())
            },
        }
    }
}

impl Parse for SpecifiedFontStyle {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        Ok(try_match_ident_ignore_ascii_case! { input,
            "normal" => generics::FontStyle::Normal,
            "italic" => generics::FontStyle::Italic,
            "oblique" => {
                let angle = input.try_parse(|input| Self::parse_angle(context, input))
                    .unwrap_or_else(|_| Self::default_angle());

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

impl ToComputedValue for SpecifiedFontStyle {
    type ComputedValue = computed::FontStyle;

    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
        match *self {
            Self::Normal => computed::FontStyle::NORMAL,
            Self::Italic => computed::FontStyle::ITALIC,
            Self::Oblique(ref angle) => computed::FontStyle::oblique(angle.degrees()),
        }
    }

    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        if *computed == computed::FontStyle::NORMAL {
            return Self::Normal;
        }
        if *computed == computed::FontStyle::ITALIC {
            return Self::Italic;
        }
        let degrees = computed.oblique_degrees();
        generics::FontStyle::Oblique(Angle::from_degrees(degrees, /* was_calc = */ false))
    }
}

/// From https://drafts.csswg.org/css-fonts-4/#valdef-font-style-oblique-angle:
///
///     Values less than -90deg or values greater than 90deg are
///     invalid and are treated as parse errors.
///
/// The maximum angle value that `font-style: oblique` should compute to.
pub const FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES: f32 = 90.;

/// The minimum angle value that `font-style: oblique` should compute to.
pub const FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES: f32 = -90.;

impl SpecifiedFontStyle {
    /// Gets a clamped angle in degrees from a specified Angle.
    pub fn compute_angle_degrees(angle: &Angle) -> f32 {
        angle
            .degrees()
            .max(FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES)
            .min(FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES)
    }

    /// Parse a suitable angle for font-style: oblique.
    pub fn parse_angle<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Angle, ParseError<'i>> {
        let angle = Angle::parse(context, input)?;
        if angle.was_calc() {
            return Ok(angle);
        }

        let degrees = angle.degrees();
        if degrees < FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES ||
            degrees > FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES
        {
            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
        }
        return Ok(angle);
    }

    /// The default angle for `font-style: oblique`.
    pub fn default_angle() -> Angle {
        Angle::from_degrees(
            computed::FontStyle::DEFAULT_OBLIQUE_DEGREES as f32,
            /* was_calc = */ false,
        )
    }
}

/// The specified value of the `font-style` property.
#[derive(
    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
#[allow(missing_docs)]
pub enum FontStyle {
    Specified(SpecifiedFontStyle),
    #[css(skip)]
    System(SystemFont),
}

impl FontStyle {
    /// Return the `normal` value.
    #[inline]
    pub fn normal() -> Self {
        FontStyle::Specified(generics::FontStyle::Normal)
    }

    system_font_methods!(FontStyle, font_style);
}

impl ToComputedValue for FontStyle {
    type ComputedValue = computed::FontStyle;

    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        match *self {
            FontStyle::Specified(ref specified) => specified.to_computed_value(context),
            FontStyle::System(..) => self.compute_system(context),
        }
    }

    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        FontStyle::Specified(SpecifiedFontStyle::from_computed_value(computed))
    }
}

/// A value for the `font-stretch` property.
///
/// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop
#[allow(missing_docs)]
#[derive(
    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
pub enum FontStretch {
    Stretch(NonNegativePercentage),
    Keyword(FontStretchKeyword),
    #[css(skip)]
    System(SystemFont),
}

/// A keyword value for `font-stretch`.
#[derive(
    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
#[allow(missing_docs)]
pub enum FontStretchKeyword {
    Normal,
    Condensed,
    UltraCondensed,
    ExtraCondensed,
    SemiCondensed,
    SemiExpanded,
    Expanded,
    ExtraExpanded,
    UltraExpanded,
}

impl FontStretchKeyword {
    /// Turns the keyword into a computed value.
    pub fn compute(&self) -> computed::FontStretch {
        computed::FontStretch::from_keyword(*self)
    }

    /// Does the opposite operation to `compute`, in order to serialize keywords
    /// if possible.
    pub fn from_percentage(p: f32) -> Option<Self> {
        computed::FontStretch::from_percentage(p).as_keyword()
    }
}

impl FontStretch {
    /// `normal`.
    pub fn normal() -> Self {
        FontStretch::Keyword(FontStretchKeyword::Normal)
    }

    system_font_methods!(FontStretch, font_stretch);
}

impl ToComputedValue for FontStretch {
    type ComputedValue = computed::FontStretch;

    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        match *self {
            FontStretch::Stretch(ref percentage) => {
                let percentage = percentage.to_computed_value(context).0;
                computed::FontStretch::from_percentage(percentage.0)
            },
            FontStretch::Keyword(ref kw) => kw.compute(),
            FontStretch::System(_) => self.compute_system(context),
        }
    }

    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative(
            computed.to_percentage(),
        )))
    }
}

/// CSS font keywords
#[derive(
    Animate,
    Clone,
    ComputeSquaredDistance,
    Copy,
    Debug,
    MallocSizeOf,
    Parse,
    PartialEq,
    SpecifiedValueInfo,
    ToAnimatedValue,
    ToAnimatedZero,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
    Serialize,
    Deserialize,
)]
#[allow(missing_docs)]
#[repr(u8)]
pub enum FontSizeKeyword {
    #[css(keyword = "xx-small")]
    XXSmall,
    XSmall,
    Small,
    Medium,
    Large,
    XLarge,
    #[css(keyword = "xx-large")]
    XXLarge,
    #[css(keyword = "xxx-large")]
    XXXLarge,
    /// Indicate whether to apply font-size: math is specified so that extra
    /// scaling due to math-depth changes is applied during the cascade.
    #[cfg(feature = "gecko")]
    Math,
    #[css(skip)]
    None,
}

impl FontSizeKeyword {
    /// Convert to an HTML <font size> value
    #[inline]
    pub fn html_size(self) -> u8 {
        self as u8
    }
}

impl Default for FontSizeKeyword {
    fn default() -> Self {
        FontSizeKeyword::Medium
    }
}

#[derive(
    Animate,
    Clone,
    ComputeSquaredDistance,
    Copy,
    Debug,
    MallocSizeOf,
    PartialEq,
    ToAnimatedValue,
    ToAnimatedZero,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Serialize, Deserialize))]
/// Additional information for keyword-derived font sizes.
pub struct KeywordInfo {
    /// The keyword used
    pub kw: FontSizeKeyword,
    /// A factor to be multiplied by the computed size of the keyword
    #[css(skip)]
    pub factor: f32,
    /// An additional fixed offset to add to the kw * factor in the case of
    /// `calc()`.
    #[css(skip)]
    pub offset: CSSPixelLength,
}

impl KeywordInfo {
    /// KeywordInfo value for font-size: medium
    pub fn medium() -> Self {
        Self::new(FontSizeKeyword::Medium)
    }

    /// KeywordInfo value for font-size: none
    pub fn none() -> Self {
        Self::new(FontSizeKeyword::None)
    }

    fn new(kw: FontSizeKeyword) -> Self {
        KeywordInfo {
            kw,
            factor: 1.,
            offset: CSSPixelLength::new(0.),
        }
    }

    /// Computes the final size for this font-size keyword, accounting for
    /// text-zoom.
    fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
        debug_assert_ne!(self.kw, FontSizeKeyword::None);
        #[cfg(feature="gecko")]
        debug_assert_ne!(self.kw, FontSizeKeyword::Math);
        let base = context.maybe_zoom_text(self.kw.to_length(context).0);
        base * self.factor + context.maybe_zoom_text(self.offset)
    }

    /// Given a parent keyword info (self), apply an additional factor/offset to
    /// it.
    fn compose(self, factor: f32) -> Self {
        if self.kw == FontSizeKeyword::None {
            return self;
        }
        KeywordInfo {
            kw: self.kw,
            factor: self.factor * factor,
            offset: self.offset * factor,
        }
    }
}

impl SpecifiedValueInfo for KeywordInfo {
    fn collect_completion_keywords(f: KeywordsCollectFn) {
        <FontSizeKeyword as SpecifiedValueInfo>::collect_completion_keywords(f);
    }
}

#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
/// A specified font-size value
pub enum FontSize {
    /// A length; e.g. 10px.
    Length(LengthPercentage),
    /// A keyword value, along with a ratio and absolute offset.
    /// The ratio in any specified keyword value
    /// will be 1 (with offset 0), but we cascade keywordness even
    /// after font-relative (percent and em) values
    /// have been applied, which is where the ratio
    /// comes in. The offset comes in if we cascaded a calc value,
    /// where the font-relative portion (em and percentage) will
    /// go into the ratio, and the remaining units all computed together
    /// will go into the offset.
    /// See bug 1355707.
    Keyword(KeywordInfo),
    /// font-size: smaller
    Smaller,
    /// font-size: larger
    Larger,
    /// Derived from a specified system font.
    #[css(skip)]
    System(SystemFont),
}

/// Specifies a prioritized list of font family names or generic family names.
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
#[cfg_attr(feature = "servo", derive(Hash))]
pub enum FontFamily {
    /// List of `font-family`
    #[css(comma)]
    Values(#[css(iterable)] FontFamilyList),
    /// System font
    #[css(skip)]
    System(SystemFont),
}

impl FontFamily {
    system_font_methods!(FontFamily, font_family);
}

impl ToComputedValue for FontFamily {
    type ComputedValue = computed::FontFamily;

    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        match *self {
            FontFamily::Values(ref list) => computed::FontFamily {
                families: list.clone(),
                is_system_font: false,
                is_initial: false,
            },
            FontFamily::System(_) => self.compute_system(context),
        }
    }

    fn from_computed_value(other: &computed::FontFamily) -> Self {
        FontFamily::Values(other.families.clone())
    }
}

#[cfg(feature = "gecko")]
impl MallocSizeOf for FontFamily {
    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
        match *self {
            FontFamily::Values(ref v) => {
                // Although the family list is refcounted, we always attribute
                // its size to the specified value.
                v.list.unconditional_size_of(ops)
            },
            FontFamily::System(_) => 0,
        }
    }
}

impl Parse for FontFamily {
    /// <family-name>#
    /// <family-name> = <string> | [ <ident>+ ]
    /// TODO: <generic-family>
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<FontFamily, ParseError<'i>> {
        let values =
            input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?;
        Ok(FontFamily::Values(FontFamilyList {
            list: crate::ArcSlice::from_iter(values.into_iter()),
        }))
    }
}

impl SpecifiedValueInfo for FontFamily {}

/// `FamilyName::parse` is based on `SingleFontFamily::parse` and not the other
/// way around because we want the former to exclude generic family keywords.
impl Parse for FamilyName {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        match SingleFontFamily::parse(context, input) {
            Ok(SingleFontFamily::FamilyName(name)) => Ok(name),
            Ok(SingleFontFamily::Generic(_)) => {
                Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
            },
            Err(e) => Err(e),
        }
    }
}

/// A factor for one of the font-size-adjust metrics, which may be either a number
/// or the `from-font` keyword.
#[derive(
    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
pub enum FontSizeAdjustFactor {
    /// An explicitly-specified number.
    Number(NonNegativeNumber),
    /// The from-font keyword: resolve the number from font metrics.
    FromFont,
}

/// Specified value for font-size-adjust, intended to help
/// preserve the readability of text when font fallback occurs.
///
/// https://drafts.csswg.org/css-fonts-5/#font-size-adjust-prop
pub type FontSizeAdjust = generics::GenericFontSizeAdjust<FontSizeAdjustFactor>;

impl Parse for FontSizeAdjust {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let location = input.current_source_location();
        // First check if we have an adjustment factor without a metrics-basis keyword.
        if let Ok(factor) = input.try_parse(|i| FontSizeAdjustFactor::parse(context, i)) {
            return Ok(Self::ExHeight(factor));
        }

        let ident = input.expect_ident()?;
        let basis = match_ignore_ascii_case! { &ident,
            "none" => return Ok(Self::None),
            // Check for size adjustment basis keywords.
            "ex-height" => Self::ExHeight,
            "cap-height" => Self::CapHeight,
            "ch-width" => Self::ChWidth,
            "ic-width" => Self::IcWidth,
            "ic-height" => Self::IcHeight,
            // Unknown keyword.
            _ => return Err(location.new_custom_error(
                SelectorParseErrorKind::UnexpectedIdent(ident.clone())
            )),
        };

        Ok(basis(FontSizeAdjustFactor::parse(context, input)?))
    }
}

/// This is the ratio applied for font-size: larger
/// and smaller by both Firefox and Chrome
const LARGER_FONT_SIZE_RATIO: f32 = 1.2;

/// The default font size.
pub const FONT_MEDIUM_PX: f32 = 16.0;
/// The default line height.
pub const FONT_MEDIUM_LINE_HEIGHT_PX: f32 = FONT_MEDIUM_PX * 1.2;

impl FontSizeKeyword {
    #[inline]
    #[cfg(feature = "servo")]
    fn to_length(&self, _: &Context) -> NonNegativeLength {
        let medium = Length::new(FONT_MEDIUM_PX);
        // https://drafts.csswg.org/css-fonts-3/#font-size-prop
        NonNegative(match *self {
            FontSizeKeyword::XXSmall => medium * 3.0 / 5.0,
            FontSizeKeyword::XSmall => medium * 3.0 / 4.0,
            FontSizeKeyword::Small => medium * 8.0 / 9.0,
            FontSizeKeyword::Medium => medium,
            FontSizeKeyword::Large => medium * 6.0 / 5.0,
            FontSizeKeyword::XLarge => medium * 3.0 / 2.0,
            FontSizeKeyword::XXLarge => medium * 2.0,
            FontSizeKeyword::XXXLarge => medium * 3.0,
            FontSizeKeyword::Math | FontSizeKeyword::None => unreachable!(),
        })
    }

    #[cfg(feature = "gecko")]
    #[inline]
    fn to_length(&self, cx: &Context) -> NonNegativeLength {
        let font = cx.style().get_font();
        let family = &font.mFont.family.families;
        let generic = family
            .single_generic()
            .unwrap_or(computed::GenericFontFamily::None);
        let base_size = unsafe {
            Atom::with(font.mLanguage.mRawPtr, |language| {
                cx.device().base_size_for_generic(language, generic)
            })
        };
        self.to_length_without_context(cx.quirks_mode, base_size)
    }

    /// Resolve a keyword length without any context, with explicit arguments.
    #[cfg(feature = "gecko")]
    #[inline]
    pub fn to_length_without_context(
        &self,
        quirks_mode: QuirksMode,
        base_size: Length,
    ) -> NonNegativeLength {
        debug_assert_ne!(*self, FontSizeKeyword::Math);
        // The tables in this function are originally from
        // nsRuleNode::CalcFontPointSize in Gecko:
        //
        // https://searchfox.org/mozilla-central/rev/c05d9d61188d32b8/layout/style/nsRuleNode.cpp#3150
        //
        // Mapping from base size and HTML size to pixels
        // The first index is (base_size - 9), the second is the
        // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
        // since HTML size 0 is the same as 1.
        //
        //  xxs   xs      s      m     l      xl     xxl   -
        //  -     0/1     2      3     4      5      6     7
        static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
            [9, 9, 9, 9, 11, 14, 18, 27],
            [9, 9, 9, 10, 12, 15, 20, 30],
            [9, 9, 10, 11, 13, 17, 22, 33],
            [9, 9, 10, 12, 14, 18, 24, 36],
            [9, 10, 12, 13, 16, 20, 26, 39],
            [9, 10, 12, 14, 17, 21, 28, 42],
            [9, 10, 13, 15, 18, 23, 30, 45],
            [9, 10, 13, 16, 18, 24, 32, 48],
        ];

        // This table gives us compatibility with WinNav4 for the default fonts only.
        // In WinNav4, the default fonts were:
        //
        //     Times/12pt ==   Times/16px at 96ppi
        //   Courier/10pt == Courier/13px at 96ppi
        //
        // xxs   xs     s      m      l     xl     xxl    -
        // -     1      2      3      4     5      6      7
        static QUIRKS_FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
            [9, 9, 9, 9, 11, 14, 18, 28],
            [9, 9, 9, 10, 12, 15, 20, 31],
            [9, 9, 9, 11, 13, 17, 22, 34],
            [9, 9, 10, 12, 14, 18, 24, 37],
            [9, 9, 10, 13, 16, 20, 26, 40],
            [9, 9, 11, 14, 17, 21, 28, 42],
            [9, 10, 12, 15, 17, 23, 30, 45],
            [9, 10, 13, 16, 18, 24, 32, 48],
        ];

        static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];
        let base_size_px = base_size.px().round() as i32;
        let html_size = self.html_size() as usize;
        NonNegative(if base_size_px >= 9 && base_size_px <= 16 {
            let mapping = if quirks_mode == QuirksMode::Quirks {
                QUIRKS_FONT_SIZE_MAPPING
            } else {
                FONT_SIZE_MAPPING
            };
            Length::new(mapping[(base_size_px - 9) as usize][html_size] as f32)
        } else {
            base_size * FONT_SIZE_FACTORS[html_size] as f32 / 100.0
        })
    }
}

impl FontSize {
    /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size>
    pub fn from_html_size(size: u8) -> Self {
        FontSize::Keyword(KeywordInfo::new(match size {
            // If value is less than 1, let it be 1.
            0 | 1 => FontSizeKeyword::XSmall,
            2 => FontSizeKeyword::Small,
            3 => FontSizeKeyword::Medium,
            4 => FontSizeKeyword::Large,
            5 => FontSizeKeyword::XLarge,
            6 => FontSizeKeyword::XXLarge,
            // If value is greater than 7, let it be 7.
            _ => FontSizeKeyword::XXXLarge,
        }))
    }

    /// Compute it against a given base font size
    pub fn to_computed_value_against(
        &self,
        context: &Context,
        base_size: FontBaseSize,
        line_height_base: LineHeightBase,
    ) -> computed::FontSize {
        let compose_keyword = |factor| {
            context
                .style()
                .get_parent_font()
                .clone_font_size()
                .keyword_info
                .compose(factor)
        };
        let mut info = KeywordInfo::none();
        let size = match *self {
            FontSize::Length(LengthPercentage::Length(ref l)) => {
                if let NoCalcLength::FontRelative(ref value) = *l {
                    if let FontRelativeLength::Em(em) = *value {
                        // If the parent font was keyword-derived, this is
                        // too. Tack the em unit onto the factor
                        info = compose_keyword(em);
                    }
                }
                let result =
                    l.to_computed_value_with_base_size(context, base_size, line_height_base);
                if l.should_zoom_text() {
                    context.maybe_zoom_text(result)
                } else {
                    result
                }
            },
            FontSize::Length(LengthPercentage::Percentage(pc)) => {
                // If the parent font was keyword-derived, this is too.
                // Tack the % onto the factor
                info = compose_keyword(pc.0);
                (base_size.resolve(context).computed_size() * pc.0).normalized()
            },
            FontSize::Length(LengthPercentage::Calc(ref calc)) => {
                let calc = calc.to_computed_value_zoomed(context, base_size, line_height_base);
                calc.resolve(base_size.resolve(context).computed_size())
            },
            FontSize::Keyword(i) => {
                if i.kw == FontSizeKeyword::Math {
                    // Scaling is done in recompute_math_font_size_if_needed().
                    info = compose_keyword(1.);
                    info.kw = FontSizeKeyword::Math;
                    FontRelativeLength::Em(1.).to_computed_value(
                        context,
                        base_size,
                        line_height_base,
                    )
                } else {
                    // As a specified keyword, this is keyword derived
                    info = i;
                    i.to_computed_value(context).clamp_to_non_negative()
                }
            },
            FontSize::Smaller => {
                info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
                FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO).to_computed_value(
                    context,
                    base_size,
                    line_height_base,
                )
            },
            FontSize::Larger => {
                info = compose_keyword(LARGER_FONT_SIZE_RATIO);
                FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO).to_computed_value(
                    context,
                    base_size,
                    line_height_base,
                )
            },

            FontSize::System(_) => {
                #[cfg(feature = "servo")]
                {
                    unreachable!()
                }
                #[cfg(feature = "gecko")]
                {
                    context
                        .cached_system_font
                        .as_ref()
                        .unwrap()
                        .font_size
                        .computed_size()
                }
            },
        };
        computed::FontSize {
            computed_size: NonNegative(size),
            used_size: NonNegative(size),
            keyword_info: info,
        }
    }
}

impl ToComputedValue for FontSize {
    type ComputedValue = computed::FontSize;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> computed::FontSize {
        self.to_computed_value_against(
            context,
            FontBaseSize::InheritedStyle,
            LineHeightBase::InheritedStyle,
        )
    }

    #[inline]
    fn from_computed_value(computed: &computed::FontSize) -> Self {
        FontSize::Length(LengthPercentage::Length(
            ToComputedValue::from_computed_value(&computed.computed_size()),
        ))
    }
}

impl FontSize {
    system_font_methods!(FontSize);

    /// Get initial value for specified font size.
    #[inline]
    pub fn medium() -> Self {
        FontSize::Keyword(KeywordInfo::medium())
    }

    /// Parses a font-size, with quirks.
    pub fn parse_quirky<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
        allow_quirks: AllowQuirks,
    ) -> Result<FontSize, ParseError<'i>> {
        if let Ok(lp) = input
            .try_parse(|i| LengthPercentage::parse_non_negative_quirky(context, i, allow_quirks))
        {
            return Ok(FontSize::Length(lp));
        }

        if let Ok(kw) = input.try_parse(|i| FontSizeKeyword::parse(i)) {
            return Ok(FontSize::Keyword(KeywordInfo::new(kw)));
        }

        try_match_ident_ignore_ascii_case! { input,
            "smaller" => Ok(FontSize::Smaller),
            "larger" => Ok(FontSize::Larger),
        }
    }
}

impl Parse for FontSize {
    /// <length> | <percentage> | <absolute-size> | <relative-size>
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<FontSize, ParseError<'i>> {
        FontSize::parse_quirky(context, input, AllowQuirks::No)
    }
}

bitflags! {
    #[derive(Clone, Copy)]
    /// Flags of variant alternates in bit
    struct VariantAlternatesParsingFlags: u8 {
        /// None of variant alternates enabled
        const NORMAL = 0;
        /// Historical forms
        const HISTORICAL_FORMS = 0x01;
        /// Stylistic Alternates
        const STYLISTIC = 0x02;
        /// Stylistic Sets
        const STYLESET = 0x04;
        /// Character Variant
        const CHARACTER_VARIANT = 0x08;
        /// Swash glyphs
        const SWASH = 0x10;
        /// Ornaments glyphs
        const ORNAMENTS = 0x20;
        /// Annotation forms
        const ANNOTATION = 0x40;
    }
}

#[derive(
    Clone,
    Debug,
    MallocSizeOf,
    PartialEq,
    SpecifiedValueInfo,
    ToCss,
    ToComputedValue,
    ToResolvedValue,
    ToShmem,
)]
#[repr(C, u8)]
/// Set of variant alternates
pub enum VariantAlternates {
    /// Enables display of stylistic alternates
    #[css(function)]
    Stylistic(CustomIdent),
    /// Enables display with stylistic sets
    #[css(comma, function)]
    Styleset(#[css(iterable)] crate::OwnedSlice<CustomIdent>),
    /// Enables display of specific character variants
    #[css(comma, function)]
    CharacterVariant(#[css(iterable)] crate::OwnedSlice<CustomIdent>),
    /// Enables display of swash glyphs
    #[css(function)]
    Swash(CustomIdent),
    /// Enables replacement of default glyphs with ornaments
    #[css(function)]
    Ornaments(CustomIdent),
    /// Enables display of alternate annotation forms
    #[css(function)]
    Annotation(CustomIdent),
    /// Enables display of historical forms
    HistoricalForms,
}

#[derive(
    Clone,
    Debug,
    Default,
    MallocSizeOf,
    PartialEq,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[repr(transparent)]
/// List of Variant Alternates
pub struct FontVariantAlternates(
    #[css(if_empty = "normal", iterable)] crate::OwnedSlice<VariantAlternates>,
);

impl FontVariantAlternates {
    /// Returns the length of all variant alternates.
    pub fn len(&self) -> usize {
        self.0.iter().fold(0, |acc, alternate| match *alternate {
            VariantAlternates::Swash(_) |
            VariantAlternates::Stylistic(_) |
            VariantAlternates::Ornaments(_) |
            VariantAlternates::Annotation(_) => acc + 1,
            VariantAlternates::Styleset(ref slice) |
            VariantAlternates::CharacterVariant(ref slice) => acc + slice.len(),
            _ => acc,
        })
    }
}

impl FontVariantAlternates {
    #[inline]
    /// Get initial specified value with VariantAlternatesList
    pub fn get_initial_specified_value() -> Self {
        Default::default()
    }
}

impl Parse for FontVariantAlternates {
    /// normal |
    ///  [ stylistic(<feature-value-name>)           ||
    ///    historical-forms                          ||
    ///    styleset(<feature-value-name> #)          ||
    ///    character-variant(<feature-value-name> #) ||
    ///    swash(<feature-value-name>)               ||
    ///    ornaments(<feature-value-name>)           ||
    ///    annotation(<feature-value-name>) ]
    fn parse<'i, 't>(
        _: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<FontVariantAlternates, ParseError<'i>> {
        if input
            .try_parse(|input| input.expect_ident_matching("normal"))
            .is_ok()
        {
            return Ok(Default::default());
        }

        let mut stylistic = None;
        let mut historical = None;
        let mut styleset = None;
        let mut character_variant = None;
        let mut swash = None;
        let mut ornaments = None;
        let mut annotation = None;

        // Parse values for the various alternate types in any order.
        let mut parsed_alternates = VariantAlternatesParsingFlags::empty();
        macro_rules! check_if_parsed(
            ($input:expr, $flag:path) => (
                if parsed_alternates.contains($flag) {
                    return Err($input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
                }
                parsed_alternates |= $flag;
            )
        );
        while let Ok(_) = input.try_parse(|input| match *input.next()? {
            Token::Ident(ref value) if value.eq_ignore_ascii_case("historical-forms") => {
                check_if_parsed!(input, VariantAlternatesParsingFlags::HISTORICAL_FORMS);
                historical = Some(VariantAlternates::HistoricalForms);
                Ok(())
            },
            Token::Function(ref name) => {
                let name = name.clone();
                input.parse_nested_block(|i| {
                    match_ignore_ascii_case! { &name,
                        "swash" => {
                            check_if_parsed!(i, VariantAlternatesParsingFlags::SWASH);
                            let ident = CustomIdent::parse(i, &[])?;
                            swash = Some(VariantAlternates::Swash(ident));
                            Ok(())
                        },
                        "stylistic" => {
                            check_if_parsed!(i, VariantAlternatesParsingFlags::STYLISTIC);
                            let ident = CustomIdent::parse(i, &[])?;
                            stylistic = Some(VariantAlternates::Stylistic(ident));
                            Ok(())
                        },
                        "ornaments" => {
                            check_if_parsed!(i, VariantAlternatesParsingFlags::ORNAMENTS);
                            let ident = CustomIdent::parse(i, &[])?;
                            ornaments = Some(VariantAlternates::Ornaments(ident));
                            Ok(())
                        },
                        "annotation" => {
                            check_if_parsed!(i, VariantAlternatesParsingFlags::ANNOTATION);
                            let ident = CustomIdent::parse(i, &[])?;
                            annotation = Some(VariantAlternates::Annotation(ident));
                            Ok(())
                        },
                        "styleset" => {
                            check_if_parsed!(i, VariantAlternatesParsingFlags::STYLESET);
                            let idents = i.parse_comma_separated(|i| {
                                CustomIdent::parse(i, &[])
                            })?;
                            styleset = Some(VariantAlternates::Styleset(idents.into()));
                            Ok(())
                        },
                        "character-variant" => {
                            check_if_parsed!(i, VariantAlternatesParsingFlags::CHARACTER_VARIANT);
                            let idents = i.parse_comma_separated(|i| {
                                CustomIdent::parse(i, &[])
                            })?;
                            character_variant = Some(VariantAlternates::CharacterVariant(idents.into()));
                            Ok(())
                        },
                        _ => return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
                    }
                })
            },
            _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
        }) {}

        if parsed_alternates.is_empty() {
            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
        }

        // Collect the parsed values in canonical order, so that we'll serialize correctly.
        let mut alternates = Vec::new();
        macro_rules! push_if_some(
            ($value:expr) => (
                if let Some(v) = $value {
                    alternates.push(v);
                }
            )
        );
        push_if_some!(stylistic);
        push_if_some!(historical);
        push_if_some!(styleset);
        push_if_some!(character_variant);
        push_if_some!(swash);
        push_if_some!(ornaments);
        push_if_some!(annotation);

        Ok(FontVariantAlternates(alternates.into()))
    }
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    MallocSizeOf,
    PartialEq,
    Parse,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[css(bitflags(
    single = "normal",
    mixed = "jis78,jis83,jis90,jis04,simplified,traditional,full-width,proportional-width,ruby",
    validate_mixed = "Self::validate_mixed_flags",
))]
#[repr(C)]
/// Variants for east asian variant
pub struct FontVariantEastAsian(u16);
bitflags! {
    impl FontVariantEastAsian: u16 {
        /// None of the features
        const NORMAL = 0;
        /// Enables rendering of JIS78 forms (OpenType feature: jp78)
        const JIS78  = 1 << 0;
        /// Enables rendering of JIS83 forms (OpenType feature: jp83).
        const JIS83 = 1 << 1;
        /// Enables rendering of JIS90 forms (OpenType feature: jp90).
        const JIS90 = 1 << 2;
        /// Enables rendering of JIS2004 forms (OpenType feature: jp04).
        const JIS04 = 1 << 3;
        /// Enables rendering of simplified forms (OpenType feature: smpl).
        const SIMPLIFIED = 1 << 4;
        /// Enables rendering of traditional forms (OpenType feature: trad).
        const TRADITIONAL = 1 << 5;

        /// These values are exclusive with each other.
        const JIS_GROUP = Self::JIS78.0 | Self::JIS83.0 | Self::JIS90.0 | Self::JIS04.0 | Self::SIMPLIFIED.0 | Self::TRADITIONAL.0;

        /// Enables rendering of full-width variants (OpenType feature: fwid).
        const FULL_WIDTH = 1 << 6;
        /// Enables rendering of proportionally-spaced variants (OpenType feature: pwid).
        const PROPORTIONAL_WIDTH = 1 << 7;
        /// Enables display of ruby variant glyphs (OpenType feature: ruby).
        const RUBY = 1 << 8;
    }
}

impl FontVariantEastAsian {
    /// The number of variants.
    pub const COUNT: usize = 9;

    fn validate_mixed_flags(&self) -> bool {
        if self.contains(Self::FULL_WIDTH | Self::PROPORTIONAL_WIDTH) {
            // full-width and proportional-width are exclusive with each other.
            return false;
        }
        let jis = self.intersection(Self::JIS_GROUP);
        if !jis.is_empty() && !jis.bits().is_power_of_two() {
            return false;
        }
        true
    }
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    MallocSizeOf,
    PartialEq,
    Parse,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[css(bitflags(
    single = "normal,none",
    mixed = "common-ligatures,no-common-ligatures,discretionary-ligatures,no-discretionary-ligatures,historical-ligatures,no-historical-ligatures,contextual,no-contextual",
    validate_mixed = "Self::validate_mixed_flags",
))]
#[repr(C)]
/// Variants of ligatures
pub struct FontVariantLigatures(u16);
bitflags! {
    impl FontVariantLigatures: u16 {
        /// Specifies that common default features are enabled
        const NORMAL = 0;
        /// Specifies that no features are enabled;
        const NONE = 1;
        /// Enables display of common ligatures
        const COMMON_LIGATURES  = 1 << 1;
        /// Disables display of common ligatures
        const NO_COMMON_LIGATURES  = 1 << 2;
        /// Enables display of discretionary ligatures
        const DISCRETIONARY_LIGATURES = 1 << 3;
        /// Disables display of discretionary ligatures
        const NO_DISCRETIONARY_LIGATURES = 1 << 4;
        /// Enables display of historical ligatures
        const HISTORICAL_LIGATURES = 1 << 5;
        /// Disables display of historical ligatures
        const NO_HISTORICAL_LIGATURES = 1 << 6;
        /// Enables display of contextual alternates
        const CONTEXTUAL = 1 << 7;
        /// Disables display of contextual alternates
        const NO_CONTEXTUAL = 1 << 8;
    }
}

impl FontVariantLigatures {
    /// The number of variants.
    pub const COUNT: usize = 9;

    fn validate_mixed_flags(&self) -> bool {
        // Mixing a value and its disabling value is forbidden.
        if self.contains(Self::COMMON_LIGATURES | Self::NO_COMMON_LIGATURES) ||
            self.contains(Self::DISCRETIONARY_LIGATURES | Self::NO_DISCRETIONARY_LIGATURES) ||
            self.contains(Self::HISTORICAL_LIGATURES | Self::NO_HISTORICAL_LIGATURES) ||
            self.contains(Self::CONTEXTUAL | Self::NO_CONTEXTUAL)
        {
            return false;
        }
        true
    }
}

/// Variants of numeric values
#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    MallocSizeOf,
    PartialEq,
    Parse,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[css(bitflags(
    single = "normal",
    mixed = "lining-nums,oldstyle-nums,proportional-nums,tabular-nums,diagonal-fractions,stacked-fractions,ordinal,slashed-zero",
    validate_mixed = "Self::validate_mixed_flags",
))]
#[repr(C)]
pub struct FontVariantNumeric(u8);
bitflags! {
    impl FontVariantNumeric : u8 {
        /// Specifies that common default features are enabled
        const NORMAL = 0;
        /// Enables display of lining numerals.
        const LINING_NUMS = 1 << 0;
        /// Enables display of old-style numerals.
        const OLDSTYLE_NUMS = 1 << 1;
        /// Enables display of proportional numerals.
        const PROPORTIONAL_NUMS = 1 << 2;
        /// Enables display of tabular numerals.
        const TABULAR_NUMS = 1 << 3;
        /// Enables display of lining diagonal fractions.
        const DIAGONAL_FRACTIONS = 1 << 4;
        /// Enables display of lining stacked fractions.
        const STACKED_FRACTIONS = 1 << 5;
        /// Enables display of slashed zeros.
        const SLASHED_ZERO = 1 << 6;
        /// Enables display of letter forms used with ordinal numbers.
        const ORDINAL = 1 << 7;
    }
}

impl FontVariantNumeric {
    /// The number of variants.
    pub const COUNT: usize = 8;

    /// normal |
    ///  [ <numeric-figure-values>   ||
    ///    <numeric-spacing-values>  ||
    ///    <numeric-fraction-values> ||
    ///    ordinal                   ||
    ///    slashed-zero ]
    /// <numeric-figure-values>   = [ lining-nums | oldstyle-nums ]
    /// <numeric-spacing-values>  = [ proportional-nums | tabular-nums ]
    /// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
    fn validate_mixed_flags(&self) -> bool {
        if self.contains(Self::LINING_NUMS | Self::OLDSTYLE_NUMS) ||
            self.contains(Self::PROPORTIONAL_NUMS | Self::TABULAR_NUMS) ||
            self.contains(Self::DIAGONAL_FRACTIONS | Self::STACKED_FRACTIONS)
        {
            return false;
        }
        true
    }
}

/// This property provides low-level control over OpenType or TrueType font features.
pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>;

/// For font-language-override, use the same representation as the computed value.
pub use crate::values::computed::font::FontLanguageOverride;

impl Parse for FontLanguageOverride {
    /// normal | <string>
    fn parse<'i, 't>(
        _: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<FontLanguageOverride, ParseError<'i>> {
        if input
            .try_parse(|input| input.expect_ident_matching("normal"))
            .is_ok()
        {
            return Ok(FontLanguageOverride::normal());
        }

        let string = input.expect_string()?;

        // The OpenType spec requires tags to be 1 to 4 ASCII characters:
        // https://learn.microsoft.com/en-gb/typography/opentype/spec/otff#data-types
        if string.is_empty() || string.len() > 4 || !string.is_ascii() {
            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
        }

        let mut bytes = [b' '; 4];
        for (byte, str_byte) in bytes.iter_mut().zip(string.as_bytes()) {
            *byte = *str_byte;
        }

        Ok(FontLanguageOverride(u32::from_be_bytes(bytes)))
    }
}

/// A value for any of the font-synthesis-{weight,style,small-caps} properties.
#[repr(u8)]
#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    MallocSizeOf,
    Parse,
    PartialEq,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
pub enum FontSynthesis {
    /// This attribute may be synthesized if not supported by a face.
    Auto,
    /// Do not attempt to synthesis this style attribute.
    None,
}

#[derive(
    Clone,
    Debug,
    Eq,
    MallocSizeOf,
    PartialEq,
    SpecifiedValueInfo,
    ToComputedValue,
    ToResolvedValue,
    ToShmem,
)]
#[repr(C)]
/// Allows authors to choose a palette from those supported by a color font
/// (and potentially @font-palette-values overrides).
pub struct FontPalette(Atom);

#[allow(missing_docs)]
impl FontPalette {
    pub fn normal() -> Self {
        Self(atom!("normal"))
    }
    pub fn light() -> Self {
        Self(atom!("light"))
    }
    pub fn dark() -> Self {
        Self(atom!("dark"))
    }
}

impl Parse for FontPalette {
    /// normal | light | dark | dashed-ident
    fn parse<'i, 't>(
        _context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<FontPalette, ParseError<'i>> {
        let location = input.current_source_location();
        let ident = input.expect_ident()?;
        match_ignore_ascii_case! { &ident,
            "normal" => Ok(Self::normal()),
            "light" => Ok(Self::light()),
            "dark" => Ok(Self::dark()),
            _ => if ident.starts_with("--") {
                Ok(Self(Atom::from(ident.as_ref())))
            } else {
                Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
            },
        }
    }
}

impl ToCss for FontPalette {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        serialize_atom_identifier(&self.0, dest)
    }
}

/// This property provides low-level control over OpenType or TrueType font
/// variations.
pub type FontVariationSettings = FontSettings<VariationValue<Number>>;

fn parse_one_feature_value<'i, 't>(
    context: &ParserContext,
    input: &mut Parser<'i, 't>,
) -> Result<Integer, ParseError<'i>> {
    if let Ok(integer) = input.try_parse(|i| Integer::parse_non_negative(context, i)) {
        return Ok(integer);
    }

    try_match_ident_ignore_ascii_case! { input,
        "on" => Ok(Integer::new(1)),
        "off" => Ok(Integer::new(0)),
    }
}

impl Parse for FeatureTagValue<Integer> {
    /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let tag = FontTag::parse(context, input)?;
        let value = input
            .try_parse(|i| parse_one_feature_value(context, i))
            .unwrap_or_else(|_| Integer::new(1));

        Ok(Self { tag, value })
    }
}

impl Parse for VariationValue<Number> {
    /// This is the `<string> <number>` part of the font-variation-settings
    /// syntax.
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let tag = FontTag::parse(context, input)?;
        let value = Number::parse(context, input)?;
        Ok(Self { tag, value })
    }
}

/// A metrics override value for a @font-face descriptor
///
/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc
#[derive(
    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
pub enum MetricsOverride {
    /// A non-negative `<percentage>` of the computed font size
    Override(NonNegativePercentage),
    /// Normal metrics from the font.
    Normal,
}

impl MetricsOverride {
    #[inline]
    /// Get default value with `normal`
    pub fn normal() -> MetricsOverride {
        MetricsOverride::Normal
    }

    /// The ToComputedValue implementation, used for @font-face descriptors.
    ///
    /// Valid override percentages must be non-negative; we return -1.0 to indicate
    /// the absence of an override (i.e. 'normal').
    #[inline]
    pub fn compute(&self) -> ComputedPercentage {
        match *self {
            MetricsOverride::Normal => ComputedPercentage(-1.0),
            MetricsOverride::Override(percent) => ComputedPercentage(percent.0.get()),
        }
    }
}

#[derive(
    Clone,
    Copy,
    Debug,
    MallocSizeOf,
    Parse,
    PartialEq,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[repr(u8)]
/// How to do font-size scaling.
pub enum XTextScale {
    /// Both min-font-size and text zoom are enabled.
    All,
    /// Text-only zoom is enabled, but min-font-size is not honored.
    ZoomOnly,
    /// Neither of them is enabled.
    None,
}

impl XTextScale {
    /// Returns whether text zoom is enabled.
    #[inline]
    pub fn text_zoom_enabled(self) -> bool {
        self != Self::None
    }
}

#[derive(
    Clone,
    Debug,
    MallocSizeOf,
    PartialEq,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
/// Internal property that reflects the lang attribute
pub struct XLang(#[css(skip)] pub Atom);

impl XLang {
    #[inline]
    /// Get default value for `-x-lang`
    pub fn get_initial_value() -> XLang {
        XLang(atom!(""))
    }
}

impl Parse for XLang {
    fn parse<'i, 't>(
        _: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<XLang, ParseError<'i>> {
        debug_assert!(
            false,
            "Should be set directly by presentation attributes only."
        );
        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
    }
}

#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
/// Specifies the minimum font size allowed due to changes in scriptlevel.
/// Ref: https://wiki.mozilla.org/MathML:mstyle
pub struct MozScriptMinSize(pub NoCalcLength);

impl MozScriptMinSize {
    #[inline]
    /// Calculate initial value of -moz-script-min-size.
    pub fn get_initial_value() -> Length {
        Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * PX_PER_PT)
    }
}

impl Parse for MozScriptMinSize {
    fn parse<'i, 't>(
        _: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<MozScriptMinSize, ParseError<'i>> {
        debug_assert!(
            false,
            "Should be set directly by presentation attributes only."
        );
        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
    }
}

/// A value for the `math-depth` property.
/// https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub enum MathDepth {
    /// Increment math-depth if math-style is compact.
    AutoAdd,

    /// Add the function's argument to math-depth.
    #[css(function)]
    Add(Integer),

    /// Set math-depth to the specified value.
    Absolute(Integer),
}

impl Parse for MathDepth {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<MathDepth, ParseError<'i>> {
        if input
            .try_parse(|i| i.expect_ident_matching("auto-add"))
            .is_ok()
        {
            return Ok(MathDepth::AutoAdd);
        }
        if let Ok(math_depth_value) = input.try_parse(|input| Integer::parse(context, input)) {
            return Ok(MathDepth::Absolute(math_depth_value));
        }
        input.expect_function_matching("add")?;
        let math_depth_delta_value =
            input.parse_nested_block(|input| Integer::parse(context, input))?;
        Ok(MathDepth::Add(math_depth_delta_value))
    }
}

#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(
    Clone,
    Copy,
    Debug,
    PartialEq,
    SpecifiedValueInfo,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
/// Specifies the multiplier to be used to adjust font size
/// due to changes in scriptlevel.
///
/// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs
pub struct MozScriptSizeMultiplier(pub f32);

impl MozScriptSizeMultiplier {
    #[inline]
    /// Get default value of `-moz-script-size-multiplier`
    pub fn get_initial_value() -> MozScriptSizeMultiplier {
        MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32)
    }
}

impl Parse for MozScriptSizeMultiplier {
    fn parse<'i, 't>(
        _: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<MozScriptSizeMultiplier, ParseError<'i>> {
        debug_assert!(
            false,
            "Should be set directly by presentation attributes only."
        );
        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
    }
}

impl From<f32> for MozScriptSizeMultiplier {
    fn from(v: f32) -> Self {
        MozScriptSizeMultiplier(v)
    }
}

impl From<MozScriptSizeMultiplier> for f32 {
    fn from(v: MozScriptSizeMultiplier) -> f32 {
        v.0
    }
}

/// A specified value for the `line-height` property.
pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>;

impl ToComputedValue for LineHeight {
    type ComputedValue = computed::LineHeight;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        match *self {
            GenericLineHeight::Normal => GenericLineHeight::Normal,
            #[cfg(feature = "gecko")]
            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
            GenericLineHeight::Number(number) => {
                GenericLineHeight::Number(number.to_computed_value(context))
            },
            GenericLineHeight::Length(ref non_negative_lp) => {
                let result = match non_negative_lp.0 {
                    LengthPercentage::Length(NoCalcLength::Absolute(ref abs)) => {
                        context.maybe_zoom_text(abs.to_computed_value(context))
                    },
                    LengthPercentage::Length(ref length) => {
                        // line-height units specifically resolve against parent's
                        // font and line-height properties, while the rest of font
                        // relative units still resolve against the element's own
                        // properties.
                        length.to_computed_value_with_base_size(
                            context,
                            FontBaseSize::CurrentStyle,
                            LineHeightBase::InheritedStyle,
                        )
                    },
                    LengthPercentage::Percentage(ref p) => FontRelativeLength::Em(p.0)
                        .to_computed_value(
                            context,
                            FontBaseSize::CurrentStyle,
                            LineHeightBase::InheritedStyle,
                        ),
                    LengthPercentage::Calc(ref calc) => {
                        let computed_calc = calc.to_computed_value_zoomed(
                            context,
                            FontBaseSize::CurrentStyle,
                            LineHeightBase::InheritedStyle,
                        );
                        let base = context.style().get_font().clone_font_size().computed_size();
                        computed_calc.resolve(base)
                    },
                };
                GenericLineHeight::Length(result.into())
            },
        }
    }

    #[inline]
    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        match *computed {
            GenericLineHeight::Normal => GenericLineHeight::Normal,
            #[cfg(feature = "gecko")]
            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
            GenericLineHeight::Number(ref number) => {
                GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
            },
            GenericLineHeight::Length(ref length) => {
                GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
            },
        }
    }
}

[ Verzeichnis aufwärts0.55unsichere Verbindung  Übersetzung europäischer Sprachen durch Browser  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge