Skip to main content

style/values/specified/
font.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Specified values for font properties
6
7use crate::context::QuirksMode;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::typed_om::NumericBaseType;
11use crate::values::computed::font::{FamilyName, FontFamilyList, SingleFontFamily};
12use crate::values::computed::Percentage as ComputedPercentage;
13use crate::values::computed::{font as computed, Length, NonNegativeLength};
14use crate::values::computed::{CSSPixelLength, Context, ToComputedValue};
15use crate::values::generics::font::{
16    self as generics, FeatureTagValue, FontSettings, FontTag, GenericLineHeight, VariationValue,
17};
18use crate::values::generics::NonNegative;
19use crate::values::specified::calc::{Leaf, PercentageContext};
20use crate::values::specified::length::{FontBaseSize, LengthUnit, LineHeightBase, PX_PER_PT};
21use crate::values::specified::number::parse_number_with_clamping_mode;
22use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage};
23use crate::values::specified::{
24    NoCalcLength, NonNegativeLengthPercentage, NonNegativeNumber, NonNegativePercentage, Number,
25};
26use crate::values::{serialize_atom_identifier, CustomIdent, SelectorParseErrorKind};
27use crate::Atom;
28use cssparser::{match_ignore_ascii_case, Parser, Token};
29#[cfg(feature = "gecko")]
30use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
31use std::fmt::{self, Write};
32use style_traits::values::specified::AllowedNumericType;
33use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
34use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
35
36// FIXME(emilio): The system font code is copy-pasta, and should be cleaned up.
37macro_rules! system_font_methods {
38    ($ty:ident, $field:ident) => {
39        system_font_methods!($ty);
40
41        fn compute_system(&self, _context: &Context) -> <$ty as ToComputedValue>::ComputedValue {
42            debug_assert!(matches!(*self, $ty::System(..)));
43            #[cfg(feature = "gecko")]
44            {
45                _context.cached_system_font.as_ref().unwrap().$field.clone()
46            }
47            #[cfg(feature = "servo")]
48            {
49                unreachable!()
50            }
51        }
52    };
53
54    ($ty:ident) => {
55        /// Get a specified value that represents a system font.
56        pub fn system_font(f: SystemFont) -> Self {
57            $ty::System(f)
58        }
59
60        /// Retreive a SystemFont from the specified value.
61        pub fn get_system(&self) -> Option<SystemFont> {
62            if let $ty::System(s) = *self {
63                Some(s)
64            } else {
65                None
66            }
67        }
68    };
69}
70
71/// System fonts.
72#[repr(u8)]
73#[derive(
74    Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
75)]
76#[allow(missing_docs)]
77#[cfg(feature = "gecko")]
78pub enum SystemFont {
79    /// https://drafts.csswg.org/css-fonts/#valdef-font-caption
80    Caption,
81    /// https://drafts.csswg.org/css-fonts/#valdef-font-icon
82    Icon,
83    /// https://drafts.csswg.org/css-fonts/#valdef-font-menu
84    Menu,
85    /// https://drafts.csswg.org/css-fonts/#valdef-font-message-box
86    MessageBox,
87    /// https://drafts.csswg.org/css-fonts/#valdef-font-small-caption
88    SmallCaption,
89    /// https://drafts.csswg.org/css-fonts/#valdef-font-status-bar
90    StatusBar,
91    /// Internal system font, used by the `<menupopup>`s on macOS.
92    #[parse(condition = "ParserContext::chrome_rules_enabled")]
93    MozPullDownMenu,
94    /// Internal system font, used for `<button>` elements.
95    #[parse(condition = "ParserContext::chrome_rules_enabled")]
96    MozButton,
97    /// Internal font, used by `<select>` elements.
98    #[parse(condition = "ParserContext::chrome_rules_enabled")]
99    MozList,
100    /// Internal font, used by `<input>` elements.
101    #[parse(condition = "ParserContext::chrome_rules_enabled")]
102    MozField,
103    #[css(skip)]
104    End, // Just for indexing purposes.
105}
106
107// We don't parse system fonts in servo, but in the interest of not
108// littering a lot of code with `if engine == "gecko"` conditionals,
109// we have a dummy system font module that does nothing
110
111#[derive(
112    Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
113)]
114#[allow(missing_docs)]
115#[cfg(feature = "servo")]
116/// void enum for system font, can never exist
117pub enum SystemFont {}
118
119#[allow(missing_docs)]
120#[cfg(feature = "servo")]
121impl SystemFont {
122    pub fn parse(_: &mut Parser) -> Result<Self, ()> {
123        Err(())
124    }
125}
126
127const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8;
128const DEFAULT_SCRIPT_SIZE_MULTIPLIER: f64 = 0.71;
129
130/// The minimum font-weight value per:
131///
132/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
133pub const MIN_FONT_WEIGHT: f32 = 1.;
134
135/// The maximum font-weight value per:
136///
137/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
138pub const MAX_FONT_WEIGHT: f32 = 1000.;
139
140/// A specified font-weight value.
141///
142/// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight
143#[derive(
144    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
145)]
146pub enum FontWeight {
147    /// `<font-weight-absolute>`
148    Absolute(AbsoluteFontWeight),
149    /// Bolder variant
150    Bolder,
151    /// Lighter variant
152    Lighter,
153    /// System font variant.
154    #[css(skip)]
155    System(SystemFont),
156}
157
158impl FontWeight {
159    system_font_methods!(FontWeight, font_weight);
160
161    /// `normal`
162    #[inline]
163    pub fn normal() -> Self {
164        FontWeight::Absolute(AbsoluteFontWeight::Normal)
165    }
166
167    /// Get a specified FontWeight from a gecko keyword
168    pub fn from_gecko_keyword(kw: u32) -> Self {
169        debug_assert!(kw.is_multiple_of(100));
170        debug_assert!(kw as f32 <= MAX_FONT_WEIGHT);
171        FontWeight::Absolute(AbsoluteFontWeight::Weight(Number::new(kw as f32)))
172    }
173}
174
175impl ToComputedValue for FontWeight {
176    type ComputedValue = computed::FontWeight;
177
178    #[inline]
179    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
180        match *self {
181            FontWeight::Absolute(ref abs) => abs.to_computed_value(context),
182            FontWeight::Bolder => context
183                .builder
184                .get_parent_font()
185                .clone_font_weight()
186                .bolder(),
187            FontWeight::Lighter => context
188                .builder
189                .get_parent_font()
190                .clone_font_weight()
191                .lighter(),
192            FontWeight::System(_) => self.compute_system(context),
193        }
194    }
195
196    #[inline]
197    fn from_computed_value(computed: &computed::FontWeight) -> Self {
198        FontWeight::Absolute(AbsoluteFontWeight::from_computed_value(computed))
199    }
200}
201
202/// An absolute font-weight value for a @font-face rule.
203///
204/// https://drafts.csswg.org/css-fonts-4/#font-weight-absolute-values
205#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
206pub enum AbsoluteFontWeight {
207    /// A `<number>`, with the additional constraints specified in:
208    ///
209    ///   https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
210    Weight(Number),
211    /// Normal font weight. Same as 400.
212    Normal,
213    /// Bold font weight. Same as 700.
214    Bold,
215}
216
217impl AbsoluteFontWeight {
218    /// Returns the computed weight for use when computed value context is unavailable.
219    /// Returns None if the weight is a calc expression that requires computed-value context.
220    pub fn compute(&self) -> Option<computed::FontWeight> {
221        match self {
222            AbsoluteFontWeight::Weight(weight) => {
223                Some(computed::FontWeight::from_float(weight.resolve()?))
224            },
225            AbsoluteFontWeight::Normal => Some(computed::FontWeight::NORMAL),
226            AbsoluteFontWeight::Bold => Some(computed::FontWeight::BOLD),
227        }
228    }
229}
230
231impl ToComputedValue for AbsoluteFontWeight {
232    type ComputedValue = computed::FontWeight;
233
234    fn to_computed_value(&self, context: &Context) -> computed::FontWeight {
235        match self {
236            AbsoluteFontWeight::Weight(weight) => {
237                computed::FontWeight::from_float(weight.to_computed_value(context))
238            },
239            AbsoluteFontWeight::Normal => computed::FontWeight::NORMAL,
240            AbsoluteFontWeight::Bold => computed::FontWeight::BOLD,
241        }
242    }
243
244    fn from_computed_value(computed: &computed::FontWeight) -> Self {
245        AbsoluteFontWeight::Weight(Number::from_computed_value(&computed.value()))
246    }
247}
248
249impl Parse for AbsoluteFontWeight {
250    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
251        if let Ok(number) = input.try_parse(|input| Number::parse(context, input)) {
252            // We could add another AllowedNumericType value, but it doesn't
253            // seem worth it just for a single property with such a weird range,
254            // so we do the clamping here manually.
255            if matches!(number.get(), Some(v) if !(MIN_FONT_WEIGHT..=MAX_FONT_WEIGHT).contains(&v))
256            {
257                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
258            }
259            return Ok(AbsoluteFontWeight::Weight(number));
260        }
261
262        Ok(try_match_ident_ignore_ascii_case! { input,
263            "normal" => AbsoluteFontWeight::Normal,
264            "bold" => AbsoluteFontWeight::Bold,
265        })
266    }
267}
268
269/// The specified value of the `font-style` property, without the system font
270/// crap.
271pub type SpecifiedFontStyle = generics::FontStyle<Angle>;
272
273impl ToCss for SpecifiedFontStyle {
274    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
275    where
276        W: Write,
277    {
278        match *self {
279            generics::FontStyle::Italic => dest.write_str("italic"),
280            generics::FontStyle::Oblique(ref angle) => {
281                // Not angle.is_zero() because we don't want to serialize
282                // `oblique calc(0deg)` as `normal`.
283                if *angle == Angle::zero() {
284                    dest.write_str("normal")?;
285                } else {
286                    dest.write_str("oblique")?;
287                    if *angle != Self::default_angle() {
288                        dest.write_char(' ')?;
289                        angle.to_css(dest)?;
290                    }
291                }
292                Ok(())
293            },
294        }
295    }
296}
297
298impl Parse for SpecifiedFontStyle {
299    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
300        Ok(try_match_ident_ignore_ascii_case! { input,
301            "normal" => generics::FontStyle::normal(),
302            "italic" => generics::FontStyle::Italic,
303            "oblique" => {
304                let angle = input.try_parse(|input| Self::parse_angle(context, input))
305                    .unwrap_or_else(|_| Self::default_angle());
306
307                generics::FontStyle::Oblique(angle)
308            },
309        })
310    }
311}
312
313impl ToComputedValue for SpecifiedFontStyle {
314    type ComputedValue = computed::FontStyle;
315
316    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
317        match *self {
318            Self::Italic => computed::FontStyle::ITALIC,
319            Self::Oblique(ref angle) => {
320                computed::FontStyle::oblique(angle.to_computed_value(context).degrees())
321            },
322        }
323    }
324
325    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
326        if *computed == computed::FontStyle::ITALIC {
327            return Self::Italic;
328        }
329        let degrees = computed.oblique_degrees();
330        generics::FontStyle::Oblique(Angle::from_degrees(degrees))
331    }
332}
333
334/// From https://drafts.csswg.org/css-fonts-4/#valdef-font-style-oblique-angle:
335///
336///     Values less than -90deg or values greater than 90deg are
337///     invalid and are treated as parse errors.
338///
339/// The maximum angle value that `font-style: oblique` should compute to.
340pub const FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES: f32 = 90.;
341
342/// The minimum angle value that `font-style: oblique` should compute to.
343pub const FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES: f32 = -90.;
344
345impl SpecifiedFontStyle {
346    /// Parse a suitable angle for font-style: oblique.
347    pub fn parse_angle(context: &ParserContext, input: &mut Parser) -> Result<Angle, ParseError> {
348        let angle = Angle::parse(context, input)?;
349        // Calc angles can exceed the range and are clamped at computed-value time.
350        if angle.is_calc() {
351            return Ok(angle);
352        }
353
354        let degrees = angle.degrees().unwrap();
355        if !(FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES..=FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES)
356            .contains(&degrees)
357        {
358            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
359        }
360        Ok(angle)
361    }
362
363    /// The default angle for `font-style: oblique`.
364    pub fn default_angle() -> Angle {
365        Angle::from_degrees(computed::FontStyle::DEFAULT_OBLIQUE_DEGREES as f32)
366    }
367}
368
369/// The specified value of the `font-style` property.
370#[derive(
371    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
372)]
373#[allow(missing_docs)]
374#[typed(todo_derive_fields)]
375pub enum FontStyle {
376    Specified(SpecifiedFontStyle),
377    #[css(skip)]
378    System(SystemFont),
379}
380
381impl FontStyle {
382    /// Return the `normal` value.
383    #[inline]
384    pub fn normal() -> Self {
385        FontStyle::Specified(generics::FontStyle::normal())
386    }
387
388    system_font_methods!(FontStyle, font_style);
389}
390
391impl ToComputedValue for FontStyle {
392    type ComputedValue = computed::FontStyle;
393
394    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
395        match *self {
396            FontStyle::Specified(ref specified) => specified.to_computed_value(context),
397            FontStyle::System(..) => self.compute_system(context),
398        }
399    }
400
401    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
402        FontStyle::Specified(SpecifiedFontStyle::from_computed_value(computed))
403    }
404}
405
406/// A value for the `font-width` property.
407///
408/// https://drafts.csswg.org/css-fonts-4/#font-width-prop
409#[allow(missing_docs)]
410#[derive(
411    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
412)]
413pub enum FontWidth {
414    Width(NonNegativePercentage),
415    Keyword(FontWidthKeyword),
416    #[css(skip)]
417    System(SystemFont),
418}
419
420/// A keyword value for `font-width`.
421#[derive(
422    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
423)]
424#[allow(missing_docs)]
425pub enum FontWidthKeyword {
426    Normal,
427    Condensed,
428    UltraCondensed,
429    ExtraCondensed,
430    SemiCondensed,
431    SemiExpanded,
432    Expanded,
433    ExtraExpanded,
434    UltraExpanded,
435}
436
437impl FontWidthKeyword {
438    /// Turns the keyword into a computed value.
439    pub fn compute(&self) -> computed::FontWidth {
440        computed::FontWidth::from_keyword(*self)
441    }
442
443    /// Does the opposite operation to `compute`, in order to serialize keywords
444    /// if possible.
445    pub fn from_percentage(p: f32) -> Option<Self> {
446        computed::FontWidth::from_percentage(p).as_keyword()
447    }
448}
449
450impl FontWidth {
451    /// `normal`.
452    pub fn normal() -> Self {
453        FontWidth::Keyword(FontWidthKeyword::Normal)
454    }
455
456    system_font_methods!(FontWidth, font_width);
457}
458
459impl ToComputedValue for FontWidth {
460    type ComputedValue = computed::FontWidth;
461
462    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
463        match *self {
464            FontWidth::Width(ref percentage) => {
465                let percentage = percentage.to_computed_value(context).0;
466                computed::FontWidth::from_percentage(percentage.0)
467            },
468            FontWidth::Keyword(ref kw) => kw.compute(),
469            FontWidth::System(_) => self.compute_system(context),
470        }
471    }
472
473    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
474        FontWidth::Width(NonNegativePercentage::from_computed_value(&NonNegative(
475            computed.to_percentage(),
476        )))
477    }
478}
479
480/// CSS font keywords
481#[derive(
482    Animate,
483    Clone,
484    ComputeSquaredDistance,
485    Copy,
486    Debug,
487    Default,
488    MallocSizeOf,
489    Parse,
490    PartialEq,
491    SpecifiedValueInfo,
492    ToAnimatedValue,
493    ToAnimatedZero,
494    ToComputedValue,
495    ToCss,
496    ToResolvedValue,
497    ToShmem,
498    Serialize,
499    Deserialize,
500    ToTyped,
501)]
502#[allow(missing_docs)]
503#[repr(u8)]
504pub enum FontSizeKeyword {
505    #[css(keyword = "xx-small")]
506    XXSmall,
507    XSmall,
508    Small,
509    #[default]
510    Medium,
511    Large,
512    XLarge,
513    #[css(keyword = "xx-large")]
514    XXLarge,
515    #[css(keyword = "xxx-large")]
516    XXXLarge,
517    /// Indicate whether to apply font-size: math is specified so that extra
518    /// scaling due to math-depth changes is applied during the cascade.
519    #[cfg(feature = "gecko")]
520    Math,
521    #[css(skip)]
522    None,
523}
524
525impl FontSizeKeyword {
526    /// Convert to an HTML <font size> value
527    #[inline]
528    pub fn html_size(self) -> u8 {
529        self as u8
530    }
531
532    /// Returns true if the font size is the math keyword
533    #[cfg(feature = "gecko")]
534    pub fn is_math(self) -> bool {
535        matches!(self, Self::Math)
536    }
537
538    /// Returns true if the font size is the math keyword
539    #[cfg(feature = "servo")]
540    pub fn is_math(self) -> bool {
541        false
542    }
543}
544
545#[derive(
546    Animate,
547    Clone,
548    ComputeSquaredDistance,
549    Copy,
550    Debug,
551    Deserialize,
552    MallocSizeOf,
553    PartialEq,
554    Serialize,
555    ToAnimatedValue,
556    ToAnimatedZero,
557    ToComputedValue,
558    ToCss,
559    ToResolvedValue,
560    ToShmem,
561    ToTyped,
562)]
563/// Additional information for keyword-derived font sizes.
564pub struct KeywordInfo {
565    /// The keyword used
566    pub kw: FontSizeKeyword,
567    /// A factor to be multiplied by the computed size of the keyword
568    #[css(skip)]
569    pub factor: f32,
570    /// An additional fixed offset to add to the kw * factor in the case of
571    /// `calc()`.
572    #[css(skip)]
573    pub offset: CSSPixelLength,
574}
575
576impl KeywordInfo {
577    /// KeywordInfo value for font-size: medium
578    pub fn medium() -> Self {
579        Self::new(FontSizeKeyword::Medium)
580    }
581
582    /// KeywordInfo value for font-size: none
583    pub fn none() -> Self {
584        Self::new(FontSizeKeyword::None)
585    }
586
587    fn new(kw: FontSizeKeyword) -> Self {
588        KeywordInfo {
589            kw,
590            factor: 1.,
591            offset: CSSPixelLength::new(0.),
592        }
593    }
594
595    /// Computes the final size for this font-size keyword, accounting for
596    /// text-zoom.
597    fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
598        debug_assert_ne!(self.kw, FontSizeKeyword::None);
599        #[cfg(feature = "gecko")]
600        debug_assert_ne!(self.kw, FontSizeKeyword::Math);
601        let base = context.maybe_zoom_text(self.kw.to_length(context).0);
602        let zoom_factor = context.style().effective_zoom.value();
603        CSSPixelLength::new(base.px() * self.factor * zoom_factor)
604            + context.maybe_zoom_text(self.offset)
605    }
606
607    /// Given a parent keyword info (self), apply an additional factor/offset to
608    /// it.
609    fn compose(self, factor: f32) -> Self {
610        if self.kw == FontSizeKeyword::None {
611            return self;
612        }
613        KeywordInfo {
614            kw: self.kw,
615            factor: self.factor * factor,
616            offset: self.offset * factor,
617        }
618    }
619}
620
621impl SpecifiedValueInfo for KeywordInfo {
622    fn collect_completion_keywords(f: KeywordsCollectFn) {
623        <FontSizeKeyword as SpecifiedValueInfo>::collect_completion_keywords(f);
624    }
625}
626
627#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
628/// A specified font-size value
629pub enum FontSize {
630    /// A length; e.g. 10px.
631    Length(LengthPercentage),
632    /// A keyword value, along with a ratio and absolute offset.
633    /// The ratio in any specified keyword value
634    /// will be 1 (with offset 0), but we cascade keywordness even
635    /// after font-relative (percent and em) values
636    /// have been applied, which is where the ratio
637    /// comes in. The offset comes in if we cascaded a calc value,
638    /// where the font-relative portion (em and percentage) will
639    /// go into the ratio, and the remaining units all computed together
640    /// will go into the offset.
641    /// See bug 1355707.
642    Keyword(KeywordInfo),
643    /// font-size: smaller
644    Smaller,
645    /// font-size: larger
646    Larger,
647    /// Derived from a specified system font.
648    #[css(skip)]
649    System(SystemFont),
650}
651
652/// Specifies a prioritized list of font family names or generic family names.
653#[derive(Clone, Debug, Eq, Hash, PartialEq, ToCss, ToShmem, ToTyped)]
654#[typed(todo_derive_fields)]
655pub enum FontFamily {
656    /// List of `font-family`
657    #[css(comma)]
658    Values(#[css(iterable)] FontFamilyList),
659    /// System font
660    #[css(skip)]
661    System(SystemFont),
662}
663
664impl FontFamily {
665    system_font_methods!(FontFamily, font_family);
666}
667
668impl ToComputedValue for FontFamily {
669    type ComputedValue = computed::FontFamily;
670
671    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
672        match *self {
673            FontFamily::Values(ref list) => computed::FontFamily {
674                families: list.clone(),
675                is_system_font: false,
676                is_initial: false,
677            },
678            FontFamily::System(_) => self.compute_system(context),
679        }
680    }
681
682    fn from_computed_value(other: &computed::FontFamily) -> Self {
683        FontFamily::Values(other.families.clone())
684    }
685}
686
687#[cfg(feature = "gecko")]
688impl MallocSizeOf for FontFamily {
689    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
690        match *self {
691            FontFamily::Values(ref v) => {
692                // Although the family list is refcounted, we always attribute
693                // its size to the specified value.
694                v.list.unconditional_size_of(ops)
695            },
696            FontFamily::System(_) => 0,
697        }
698    }
699}
700
701impl Parse for FontFamily {
702    /// <family-name>#
703    /// <family-name> = <string> | [ <ident>+ ]
704    /// TODO: <generic-family>
705    fn parse(context: &ParserContext, input: &mut Parser) -> Result<FontFamily, ParseError> {
706        let values =
707            input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?;
708        Ok(FontFamily::Values(FontFamilyList {
709            list: crate::ArcSlice::from_iter(values.into_iter()),
710        }))
711    }
712}
713
714impl SpecifiedValueInfo for FontFamily {}
715
716/// `FamilyName::parse` is based on `SingleFontFamily::parse` and not the other
717/// way around because we want the former to exclude generic family keywords.
718impl Parse for FamilyName {
719    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
720        match SingleFontFamily::parse(context, input) {
721            Ok(SingleFontFamily::FamilyName(name)) => Ok(name),
722            Ok(SingleFontFamily::Generic(_)) => {
723                Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
724            },
725            Err(e) => Err(e),
726        }
727    }
728}
729
730/// A factor for one of the font-size-adjust metrics, which may be either a number
731/// or the `from-font` keyword.
732#[derive(
733    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
734)]
735pub enum FontSizeAdjustFactor {
736    /// An explicitly-specified number.
737    Number(NonNegativeNumber),
738    /// The from-font keyword: resolve the number from font metrics.
739    FromFont,
740}
741
742/// Specified value for font-size-adjust, intended to help
743/// preserve the readability of text when font fallback occurs.
744///
745/// https://drafts.csswg.org/css-fonts-5/#font-size-adjust-prop
746pub type FontSizeAdjust = generics::GenericFontSizeAdjust<FontSizeAdjustFactor>;
747
748impl Parse for FontSizeAdjust {
749    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
750        // First check if we have an adjustment factor without a metrics-basis keyword.
751        if let Ok(factor) = input.try_parse(|i| FontSizeAdjustFactor::parse(context, i)) {
752            return Ok(Self::ExHeight(factor));
753        }
754
755        let ident = input.expect_ident()?;
756        let basis = match_ignore_ascii_case! { &ident,
757            "none" => return Ok(Self::None),
758            // Check for size adjustment basis keywords.
759            "ex-height" => Self::ExHeight,
760            "cap-height" => Self::CapHeight,
761            "ch-width" => Self::ChWidth,
762            "ic-width" => Self::IcWidth,
763            "ic-height" => Self::IcHeight,
764            // Unknown keyword.
765            _ => return Err(ParseError::custom(
766                SelectorParseErrorKind::UnexpectedIdent
767            )),
768        };
769
770        Ok(basis(FontSizeAdjustFactor::parse(context, input)?))
771    }
772}
773
774/// This is the ratio applied for font-size: larger
775/// and smaller by both Firefox and Chrome
776const LARGER_FONT_SIZE_RATIO: f32 = 1.2;
777
778/// The default font size.
779pub const FONT_MEDIUM_PX: f32 = 16.0;
780/// The default line height.
781pub const FONT_MEDIUM_LINE_HEIGHT_PX: f32 = FONT_MEDIUM_PX * 1.2;
782/// The default ex height -- https://drafts.csswg.org/css-values/#ex
783/// > In the cases where it is impossible or impractical to determine the x-height, a value of 0.5em must be assumed
784pub const FONT_MEDIUM_EX_PX: f32 = FONT_MEDIUM_PX * 0.5;
785/// The default cap height -- https://drafts.csswg.org/css-values/#cap
786/// > In the cases where it is impossible or impractical to determine the cap-height, the font’s ascent must be used
787pub const FONT_MEDIUM_CAP_PX: f32 = FONT_MEDIUM_PX;
788/// The default advance measure -- https://drafts.csswg.org/css-values/#ch
789/// > Thus, the ch unit falls back to 0.5em in the general case
790pub const FONT_MEDIUM_CH_PX: f32 = FONT_MEDIUM_PX * 0.5;
791/// The default idographic advance measure -- https://drafts.csswg.org/css-values/#ic
792/// > In the cases where it is impossible or impractical to determine the ideographic advance measure, it must be assumed to be 1em
793pub const FONT_MEDIUM_IC_PX: f32 = FONT_MEDIUM_PX;
794
795impl FontSizeKeyword {
796    #[inline]
797    fn to_length(&self, cx: &Context) -> NonNegativeLength {
798        let font = cx.style().get_font();
799
800        #[cfg(feature = "servo")]
801        let family = &font.font_family.families;
802        #[cfg(feature = "gecko")]
803        let family = &font.mFont.family.families;
804
805        let generic = family
806            .single_generic()
807            .unwrap_or(computed::GenericFontFamily::None);
808
809        #[cfg(feature = "gecko")]
810        let base_size = unsafe {
811            Atom::with(font.mLanguage.mRawPtr, |language| {
812                cx.device().base_size_for_generic(language, generic)
813            })
814        };
815        #[cfg(feature = "servo")]
816        let base_size = cx.device().base_size_for_generic(generic);
817
818        self.to_length_without_context(cx.quirks_mode, base_size)
819    }
820
821    /// Resolve a keyword length without any context, with explicit arguments.
822    #[inline]
823    pub fn to_length_without_context(
824        &self,
825        quirks_mode: QuirksMode,
826        base_size: Length,
827    ) -> NonNegativeLength {
828        #[cfg(feature = "gecko")]
829        debug_assert_ne!(*self, FontSizeKeyword::Math);
830        // The tables in this function are originally from
831        // nsRuleNode::CalcFontPointSize in Gecko:
832        //
833        // https://searchfox.org/mozilla-central/rev/c05d9d61188d32b8/layout/style/nsRuleNode.cpp#3150
834        //
835        // Mapping from base size and HTML size to pixels
836        // The first index is (base_size - 9), the second is the
837        // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
838        // since HTML size 0 is the same as 1.
839        //
840        //  xxs   xs      s      m     l      xl     xxl   -
841        //  -     0/1     2      3     4      5      6     7
842        static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
843            [9, 9, 9, 9, 11, 14, 18, 27],
844            [9, 9, 9, 10, 12, 15, 20, 30],
845            [9, 9, 10, 11, 13, 17, 22, 33],
846            [9, 9, 10, 12, 14, 18, 24, 36],
847            [9, 10, 12, 13, 16, 20, 26, 39],
848            [9, 10, 12, 14, 17, 21, 28, 42],
849            [9, 10, 13, 15, 18, 23, 30, 45],
850            [9, 10, 13, 16, 18, 24, 32, 48],
851        ];
852
853        // This table gives us compatibility with WinNav4 for the default fonts only.
854        // In WinNav4, the default fonts were:
855        //
856        //     Times/12pt ==   Times/16px at 96ppi
857        //   Courier/10pt == Courier/13px at 96ppi
858        //
859        // xxs   xs     s      m      l     xl     xxl    -
860        // -     1      2      3      4     5      6      7
861        static QUIRKS_FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
862            [9, 9, 9, 9, 11, 14, 18, 28],
863            [9, 9, 9, 10, 12, 15, 20, 31],
864            [9, 9, 9, 11, 13, 17, 22, 34],
865            [9, 9, 10, 12, 14, 18, 24, 37],
866            [9, 9, 10, 13, 16, 20, 26, 40],
867            [9, 9, 11, 14, 17, 21, 28, 42],
868            [9, 10, 12, 15, 17, 23, 30, 45],
869            [9, 10, 13, 16, 18, 24, 32, 48],
870        ];
871
872        static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];
873        let base_size_px = base_size.px().round() as i32;
874        let html_size = self.html_size() as usize;
875        NonNegative(if (9..=16).contains(&base_size_px) {
876            let mapping = if quirks_mode == QuirksMode::Quirks {
877                QUIRKS_FONT_SIZE_MAPPING
878            } else {
879                FONT_SIZE_MAPPING
880            };
881            Length::new(mapping[(base_size_px - 9) as usize][html_size] as f32)
882        } else {
883            base_size * FONT_SIZE_FACTORS[html_size] as f32 / 100.0
884        })
885    }
886}
887
888impl FontSize {
889    /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size>
890    pub fn from_html_size(size: u8) -> Self {
891        FontSize::Keyword(KeywordInfo::new(match size {
892            // If value is less than 1, let it be 1.
893            0 | 1 => FontSizeKeyword::XSmall,
894            2 => FontSizeKeyword::Small,
895            3 => FontSizeKeyword::Medium,
896            4 => FontSizeKeyword::Large,
897            5 => FontSizeKeyword::XLarge,
898            6 => FontSizeKeyword::XXLarge,
899            // If value is greater than 7, let it be 7.
900            _ => FontSizeKeyword::XXXLarge,
901        }))
902    }
903
904    /// Compute it against a given base font size
905    pub fn to_computed_value_against(
906        &self,
907        context: &Context,
908        base_size: FontBaseSize,
909        line_height_base: LineHeightBase,
910    ) -> computed::FontSize {
911        let compose_keyword = |factor| {
912            context
913                .style()
914                .get_parent_font()
915                .clone_font_size()
916                .keyword_info
917                .compose(factor)
918        };
919        let mut info = KeywordInfo::none();
920        let size =
921            match *self {
922                FontSize::Length(LengthPercentage::Length(ref l)) => {
923                    if l.length_unit() == LengthUnit::Em {
924                        // If the parent font was keyword-derived, this is
925                        // too. Tack the em unit onto the factor
926                        info = compose_keyword(l.unitless_value());
927                    }
928                    let result =
929                        l.to_computed_value_with_base_size(context, base_size, line_height_base);
930                    if l.should_zoom_text() {
931                        context.maybe_zoom_text(result)
932                    } else {
933                        result
934                    }
935                },
936                FontSize::Length(LengthPercentage::Percentage(pc)) => {
937                    // If the parent font was keyword-derived, this is too.
938                    // Tack the % onto the factor
939                    info = compose_keyword(pc.get());
940                    (base_size.resolve(context).computed_size() * pc.get()).normalized()
941                },
942                FontSize::Length(LengthPercentage::Calc(ref calc)) => {
943                    let calc = calc.to_computed_value_zoomed(context, base_size, line_height_base);
944                    calc.resolve(base_size.resolve(context).computed_size())
945                },
946                FontSize::Keyword(i) => {
947                    if i.kw.is_math() {
948                        // Scaling is done in recompute_math_font_size_if_needed().
949                        info = compose_keyword(1.);
950                        // i.kw will always be FontSizeKeyword::Math here. But writing it this
951                        // allows this code to compile for servo where the Math variant is cfg'd out.
952                        info.kw = i.kw;
953                        NoCalcLength::from_em(1.).to_computed_value_with_base_size(
954                            context,
955                            base_size,
956                            line_height_base,
957                        )
958                    } else {
959                        // As a specified keyword, this is keyword derived
960                        info = i;
961                        i.to_computed_value(context).clamp_to_non_negative()
962                    }
963                },
964                FontSize::Smaller => {
965                    info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
966                    NoCalcLength::from_em(1. / LARGER_FONT_SIZE_RATIO)
967                        .to_computed_value_with_base_size(context, base_size, line_height_base)
968                },
969                FontSize::Larger => {
970                    info = compose_keyword(LARGER_FONT_SIZE_RATIO);
971                    NoCalcLength::from_em(LARGER_FONT_SIZE_RATIO).to_computed_value_with_base_size(
972                        context,
973                        base_size,
974                        line_height_base,
975                    )
976                },
977                FontSize::System(_) => {
978                    #[cfg(feature = "servo")]
979                    {
980                        unreachable!()
981                    }
982                    #[cfg(feature = "gecko")]
983                    {
984                        context
985                            .cached_system_font
986                            .as_ref()
987                            .unwrap()
988                            .font_size
989                            .computed_size()
990                            .zoom(context.builder.effective_zoom)
991                    }
992                },
993            };
994        let size = NonNegative(Self::quantize_font_size(size));
995        computed::FontSize {
996            computed_size: size,
997            used_size: size,
998            keyword_info: info,
999        }
1000    }
1001
1002    /// Quantize a value intended for use as a font size, to avoid creating a near-infinity
1003    /// of different styles and font instances when "random" floating-point sizes are used.
1004    #[inline]
1005    pub fn quantize_font_size(size: CSSPixelLength) -> CSSPixelLength {
1006        // Based on the Veltkamp-Dekker float-splitting algorithm, see e.g.
1007        // https://indico.cern.ch/event/313684/contributions/1687773/attachments/600513/826490/FPArith-Part2.pdf
1008        // A 32-bit float has 24 bits of precision (23 stored, plus an implicit 1 bit
1009        // at the start of the mantissa).
1010        // (Compare also QuantizeFontSize in dom/canvas/CanvasRenderingContext2D.cpp.)
1011        // If we ever change the representation of CSSPixelLength (e.g. to f64 or some fixed-point type)
1012        // this will need to be revised.
1013        size_of_test!(CSSPixelLength, std::mem::size_of::<f32>());
1014        const BITS_TO_DROP: u32 = 14; // leaving 10 bits of precision
1015        const SCALE_PLUS_ONE: f32 = ((1 << BITS_TO_DROP) + 1) as f32;
1016        const LIMIT: f32 = f32::MAX / SCALE_PLUS_ONE;
1017        if size.px() >= LIMIT {
1018            return CSSPixelLength::new(LIMIT);
1019        }
1020        let d = size.px() * SCALE_PLUS_ONE;
1021        let t = d - size.px();
1022        CSSPixelLength::new(d - t)
1023    }
1024}
1025
1026impl ToComputedValue for FontSize {
1027    type ComputedValue = computed::FontSize;
1028
1029    #[inline]
1030    fn to_computed_value(&self, context: &Context) -> computed::FontSize {
1031        self.to_computed_value_against(
1032            context,
1033            FontBaseSize::InheritedStyle,
1034            LineHeightBase::InheritedStyle,
1035        )
1036    }
1037
1038    #[inline]
1039    fn from_computed_value(computed: &computed::FontSize) -> Self {
1040        FontSize::Length(LengthPercentage::Length(
1041            ToComputedValue::from_computed_value(&computed.computed_size()),
1042        ))
1043    }
1044}
1045
1046impl FontSize {
1047    system_font_methods!(FontSize);
1048
1049    /// Get initial value for specified font size.
1050    #[inline]
1051    pub fn medium() -> Self {
1052        FontSize::Keyword(KeywordInfo::medium())
1053    }
1054
1055    /// Parses a font-size, with quirks.
1056    pub fn parse_quirky(
1057        context: &ParserContext,
1058        input: &mut Parser,
1059        allow_quirks: AllowQuirks,
1060    ) -> Result<FontSize, ParseError> {
1061        if let Ok(lp) = input
1062            .try_parse(|i| LengthPercentage::parse_non_negative_quirky(context, i, allow_quirks))
1063        {
1064            return Ok(FontSize::Length(lp));
1065        }
1066
1067        if let Ok(kw) = input.try_parse(|i| FontSizeKeyword::parse(i)) {
1068            return Ok(FontSize::Keyword(KeywordInfo::new(kw)));
1069        }
1070
1071        try_match_ident_ignore_ascii_case! { input,
1072            "smaller" => Ok(FontSize::Smaller),
1073            "larger" => Ok(FontSize::Larger),
1074        }
1075    }
1076}
1077
1078impl Parse for FontSize {
1079    /// <length> | <percentage> | <absolute-size> | <relative-size>
1080    fn parse(context: &ParserContext, input: &mut Parser) -> Result<FontSize, ParseError> {
1081        FontSize::parse_quirky(context, input, AllowQuirks::No)
1082    }
1083}
1084
1085bitflags! {
1086    #[derive(Clone, Copy)]
1087    /// Flags of variant alternates in bit
1088    struct VariantAlternatesParsingFlags: u8 {
1089        /// None of variant alternates enabled
1090        const NORMAL = 0;
1091        /// Historical forms
1092        const HISTORICAL_FORMS = 0x01;
1093        /// Stylistic Alternates
1094        const STYLISTIC = 0x02;
1095        /// Stylistic Sets
1096        const STYLESET = 0x04;
1097        /// Character Variant
1098        const CHARACTER_VARIANT = 0x08;
1099        /// Swash glyphs
1100        const SWASH = 0x10;
1101        /// Ornaments glyphs
1102        const ORNAMENTS = 0x20;
1103        /// Annotation forms
1104        const ANNOTATION = 0x40;
1105    }
1106}
1107
1108#[derive(
1109    Clone,
1110    Debug,
1111    Deserialize,
1112    Hash,
1113    MallocSizeOf,
1114    PartialEq,
1115    Serialize,
1116    SpecifiedValueInfo,
1117    ToCss,
1118    ToComputedValue,
1119    ToResolvedValue,
1120    ToShmem,
1121)]
1122#[repr(C, u8)]
1123/// Set of variant alternates
1124pub enum VariantAlternates {
1125    /// Enables display of stylistic alternates
1126    #[css(function)]
1127    Stylistic(CustomIdent),
1128    /// Enables display with stylistic sets
1129    #[css(comma, function)]
1130    Styleset(#[css(iterable)] crate::OwnedSlice<CustomIdent>),
1131    /// Enables display of specific character variants
1132    #[css(comma, function)]
1133    CharacterVariant(#[css(iterable)] crate::OwnedSlice<CustomIdent>),
1134    /// Enables display of swash glyphs
1135    #[css(function)]
1136    Swash(CustomIdent),
1137    /// Enables replacement of default glyphs with ornaments
1138    #[css(function)]
1139    Ornaments(CustomIdent),
1140    /// Enables display of alternate annotation forms
1141    #[css(function)]
1142    Annotation(CustomIdent),
1143    /// Enables display of historical forms
1144    HistoricalForms,
1145}
1146
1147#[derive(
1148    Clone,
1149    Debug,
1150    Default,
1151    Deserialize,
1152    Hash,
1153    MallocSizeOf,
1154    PartialEq,
1155    Serialize,
1156    SpecifiedValueInfo,
1157    ToComputedValue,
1158    ToCss,
1159    ToResolvedValue,
1160    ToShmem,
1161    ToTyped,
1162)]
1163#[repr(transparent)]
1164#[typed(todo_derive_fields)]
1165/// List of Variant Alternates
1166pub struct FontVariantAlternates(
1167    #[css(if_empty = "normal", iterable)] crate::OwnedSlice<VariantAlternates>,
1168);
1169
1170impl FontVariantAlternates {
1171    /// Returns true if the list is empty.
1172    pub fn is_empty(&self) -> bool {
1173        self.0.is_empty()
1174    }
1175
1176    /// Iterates over all alternates in the list.
1177    pub fn iter(&self) -> impl Iterator<Item = &VariantAlternates> {
1178        self.0.iter()
1179    }
1180
1181    /// Returns the length of all variant alternates.
1182    pub fn len(&self) -> usize {
1183        self.0.iter().fold(0, |acc, alternate| match *alternate {
1184            VariantAlternates::Swash(_)
1185            | VariantAlternates::Stylistic(_)
1186            | VariantAlternates::Ornaments(_)
1187            | VariantAlternates::Annotation(_) => acc + 1,
1188            VariantAlternates::Styleset(ref slice)
1189            | VariantAlternates::CharacterVariant(ref slice) => acc + slice.len(),
1190            _ => acc,
1191        })
1192    }
1193}
1194
1195impl Parse for FontVariantAlternates {
1196    /// normal |
1197    ///  [ stylistic(<feature-value-name>)           ||
1198    ///    historical-forms                          ||
1199    ///    styleset(<feature-value-name> #)          ||
1200    ///    character-variant(<feature-value-name> #) ||
1201    ///    swash(<feature-value-name>)               ||
1202    ///    ornaments(<feature-value-name>)           ||
1203    ///    annotation(<feature-value-name>) ]
1204    fn parse(_: &ParserContext, input: &mut Parser) -> Result<FontVariantAlternates, ParseError> {
1205        if input
1206            .try_parse(|input| input.expect_ident_matching("normal"))
1207            .is_ok()
1208        {
1209            return Ok(Default::default());
1210        }
1211
1212        let mut stylistic = None;
1213        let mut historical = None;
1214        let mut styleset = None;
1215        let mut character_variant = None;
1216        let mut swash = None;
1217        let mut ornaments = None;
1218        let mut annotation = None;
1219
1220        // Parse values for the various alternate types in any order.
1221        let mut parsed_alternates = VariantAlternatesParsingFlags::empty();
1222        macro_rules! check_if_parsed(
1223            ($input:expr, $flag:path) => (
1224                if parsed_alternates.contains($flag) {
1225                    return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1226                }
1227                parsed_alternates |= $flag;
1228            )
1229        );
1230        while input.try_parse(|input| match *input.next()? {
1231            Token::Ident(ref value) if value.eq_ignore_ascii_case("historical-forms") => {
1232                check_if_parsed!(input, VariantAlternatesParsingFlags::HISTORICAL_FORMS);
1233                historical = Some(VariantAlternates::HistoricalForms);
1234                Ok(())
1235            },
1236            Token::Function(ref name) => {
1237                let name = name.clone();
1238                input.parse_nested_block(|i| {
1239                    match_ignore_ascii_case! { &name,
1240                        "swash" => {
1241                            check_if_parsed!(i, VariantAlternatesParsingFlags::SWASH);
1242                            let ident = CustomIdent::parse(i, &[])?;
1243                            swash = Some(VariantAlternates::Swash(ident));
1244                            Ok(())
1245                        },
1246                        "stylistic" => {
1247                            check_if_parsed!(i, VariantAlternatesParsingFlags::STYLISTIC);
1248                            let ident = CustomIdent::parse(i, &[])?;
1249                            stylistic = Some(VariantAlternates::Stylistic(ident));
1250                            Ok(())
1251                        },
1252                        "ornaments" => {
1253                            check_if_parsed!(i, VariantAlternatesParsingFlags::ORNAMENTS);
1254                            let ident = CustomIdent::parse(i, &[])?;
1255                            ornaments = Some(VariantAlternates::Ornaments(ident));
1256                            Ok(())
1257                        },
1258                        "annotation" => {
1259                            check_if_parsed!(i, VariantAlternatesParsingFlags::ANNOTATION);
1260                            let ident = CustomIdent::parse(i, &[])?;
1261                            annotation = Some(VariantAlternates::Annotation(ident));
1262                            Ok(())
1263                        },
1264                        "styleset" => {
1265                            check_if_parsed!(i, VariantAlternatesParsingFlags::STYLESET);
1266                            let idents = i.parse_comma_separated(|i| {
1267                                CustomIdent::parse(i, &[])
1268                            })?;
1269                            styleset = Some(VariantAlternates::Styleset(idents.into()));
1270                            Ok(())
1271                        },
1272                        "character-variant" => {
1273                            check_if_parsed!(i, VariantAlternatesParsingFlags::CHARACTER_VARIANT);
1274                            let idents = i.parse_comma_separated(|i| {
1275                                CustomIdent::parse(i, &[])
1276                            })?;
1277                            character_variant = Some(VariantAlternates::CharacterVariant(idents.into()));
1278                            Ok(())
1279                        },
1280                        _ => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
1281                    }
1282                })
1283            },
1284            _ => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
1285        }).is_ok() {}
1286
1287        if parsed_alternates.is_empty() {
1288            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1289        }
1290
1291        // Collect the parsed values in canonical order, so that we'll serialize correctly.
1292        let mut alternates = Vec::new();
1293        macro_rules! push_if_some(
1294            ($value:expr) => (
1295                if let Some(v) = $value {
1296                    alternates.push(v);
1297                }
1298            )
1299        );
1300        push_if_some!(stylistic);
1301        push_if_some!(historical);
1302        push_if_some!(styleset);
1303        push_if_some!(character_variant);
1304        push_if_some!(swash);
1305        push_if_some!(ornaments);
1306        push_if_some!(annotation);
1307
1308        Ok(FontVariantAlternates(alternates.into()))
1309    }
1310}
1311
1312#[derive(
1313    Clone,
1314    Copy,
1315    Debug,
1316    Deserialize,
1317    Eq,
1318    Hash,
1319    MallocSizeOf,
1320    PartialEq,
1321    Parse,
1322    Serialize,
1323    SpecifiedValueInfo,
1324    ToComputedValue,
1325    ToCss,
1326    ToResolvedValue,
1327    ToShmem,
1328    ToTyped,
1329)]
1330#[css(bitflags(
1331    single = "normal",
1332    mixed = "jis78,jis83,jis90,jis04,simplified,traditional,full-width,proportional-width,ruby",
1333    validate_mixed = "Self::validate_mixed_flags",
1334))]
1335#[repr(C)]
1336/// Variants for east asian variant
1337pub struct FontVariantEastAsian(u16);
1338bitflags! {
1339    impl FontVariantEastAsian: u16 {
1340        /// None of the features
1341        const NORMAL = 0;
1342        /// Enables rendering of JIS78 forms (OpenType feature: jp78)
1343        const JIS78  = 1 << 0;
1344        /// Enables rendering of JIS83 forms (OpenType feature: jp83).
1345        const JIS83 = 1 << 1;
1346        /// Enables rendering of JIS90 forms (OpenType feature: jp90).
1347        const JIS90 = 1 << 2;
1348        /// Enables rendering of JIS2004 forms (OpenType feature: jp04).
1349        const JIS04 = 1 << 3;
1350        /// Enables rendering of simplified forms (OpenType feature: smpl).
1351        const SIMPLIFIED = 1 << 4;
1352        /// Enables rendering of traditional forms (OpenType feature: trad).
1353        const TRADITIONAL = 1 << 5;
1354
1355        /// These values are exclusive with each other.
1356        const JIS_GROUP = Self::JIS78.0 | Self::JIS83.0 | Self::JIS90.0 | Self::JIS04.0 | Self::SIMPLIFIED.0 | Self::TRADITIONAL.0;
1357
1358        /// Enables rendering of full-width variants (OpenType feature: fwid).
1359        const FULL_WIDTH = 1 << 6;
1360        /// Enables rendering of proportionally-spaced variants (OpenType feature: pwid).
1361        const PROPORTIONAL_WIDTH = 1 << 7;
1362        /// Enables display of ruby variant glyphs (OpenType feature: ruby).
1363        const RUBY = 1 << 8;
1364    }
1365}
1366
1367impl FontVariantEastAsian {
1368    /// The number of variants.
1369    pub const COUNT: usize = 9;
1370
1371    fn validate_mixed_flags(&self) -> bool {
1372        if self.contains(Self::FULL_WIDTH | Self::PROPORTIONAL_WIDTH) {
1373            // full-width and proportional-width are exclusive with each other.
1374            return false;
1375        }
1376        let jis = self.intersection(Self::JIS_GROUP);
1377        if !jis.is_empty() && !jis.bits().is_power_of_two() {
1378            return false;
1379        }
1380        true
1381    }
1382}
1383
1384#[derive(
1385    Clone,
1386    Copy,
1387    Debug,
1388    Deserialize,
1389    Eq,
1390    Hash,
1391    MallocSizeOf,
1392    PartialEq,
1393    Parse,
1394    Serialize,
1395    SpecifiedValueInfo,
1396    ToComputedValue,
1397    ToCss,
1398    ToResolvedValue,
1399    ToShmem,
1400    ToTyped,
1401)]
1402#[css(bitflags(
1403    single = "normal,none",
1404    mixed = "common-ligatures,no-common-ligatures,discretionary-ligatures,no-discretionary-ligatures,historical-ligatures,no-historical-ligatures,contextual,no-contextual",
1405    validate_mixed = "Self::validate_mixed_flags",
1406))]
1407#[repr(C)]
1408/// Variants of ligatures
1409pub struct FontVariantLigatures(u16);
1410bitflags! {
1411    impl FontVariantLigatures: u16 {
1412        /// Specifies that common default features are enabled
1413        const NORMAL = 0;
1414        /// Specifies that no features are enabled;
1415        const NONE = 1;
1416        /// Enables display of common ligatures
1417        const COMMON_LIGATURES  = 1 << 1;
1418        /// Disables display of common ligatures
1419        const NO_COMMON_LIGATURES  = 1 << 2;
1420        /// Enables display of discretionary ligatures
1421        const DISCRETIONARY_LIGATURES = 1 << 3;
1422        /// Disables display of discretionary ligatures
1423        const NO_DISCRETIONARY_LIGATURES = 1 << 4;
1424        /// Enables display of historical ligatures
1425        const HISTORICAL_LIGATURES = 1 << 5;
1426        /// Disables display of historical ligatures
1427        const NO_HISTORICAL_LIGATURES = 1 << 6;
1428        /// Enables display of contextual alternates
1429        const CONTEXTUAL = 1 << 7;
1430        /// Disables display of contextual alternates
1431        const NO_CONTEXTUAL = 1 << 8;
1432    }
1433}
1434
1435impl FontVariantLigatures {
1436    /// The number of variants.
1437    pub const COUNT: usize = 9;
1438
1439    fn validate_mixed_flags(&self) -> bool {
1440        // Mixing a value and its disabling value is forbidden.
1441        if self.contains(Self::COMMON_LIGATURES | Self::NO_COMMON_LIGATURES)
1442            || self.contains(Self::DISCRETIONARY_LIGATURES | Self::NO_DISCRETIONARY_LIGATURES)
1443            || self.contains(Self::HISTORICAL_LIGATURES | Self::NO_HISTORICAL_LIGATURES)
1444            || self.contains(Self::CONTEXTUAL | Self::NO_CONTEXTUAL)
1445        {
1446            return false;
1447        }
1448        true
1449    }
1450}
1451
1452/// Variants of numeric values
1453#[derive(
1454    Clone,
1455    Copy,
1456    Debug,
1457    Deserialize,
1458    Eq,
1459    Hash,
1460    MallocSizeOf,
1461    PartialEq,
1462    Parse,
1463    Serialize,
1464    SpecifiedValueInfo,
1465    ToComputedValue,
1466    ToCss,
1467    ToResolvedValue,
1468    ToShmem,
1469    ToTyped,
1470)]
1471#[css(bitflags(
1472    single = "normal",
1473    mixed = "lining-nums,oldstyle-nums,proportional-nums,tabular-nums,diagonal-fractions,stacked-fractions,ordinal,slashed-zero",
1474    validate_mixed = "Self::validate_mixed_flags",
1475))]
1476#[repr(C)]
1477pub struct FontVariantNumeric(u8);
1478bitflags! {
1479    impl FontVariantNumeric : u8 {
1480        /// Specifies that common default features are enabled
1481        const NORMAL = 0;
1482        /// Enables display of lining numerals.
1483        const LINING_NUMS = 1 << 0;
1484        /// Enables display of old-style numerals.
1485        const OLDSTYLE_NUMS = 1 << 1;
1486        /// Enables display of proportional numerals.
1487        const PROPORTIONAL_NUMS = 1 << 2;
1488        /// Enables display of tabular numerals.
1489        const TABULAR_NUMS = 1 << 3;
1490        /// Enables display of lining diagonal fractions.
1491        const DIAGONAL_FRACTIONS = 1 << 4;
1492        /// Enables display of lining stacked fractions.
1493        const STACKED_FRACTIONS = 1 << 5;
1494        /// Enables display of slashed zeros.
1495        const SLASHED_ZERO = 1 << 6;
1496        /// Enables display of letter forms used with ordinal numbers.
1497        const ORDINAL = 1 << 7;
1498    }
1499}
1500
1501impl FontVariantNumeric {
1502    /// The number of variants.
1503    pub const COUNT: usize = 8;
1504
1505    /// normal |
1506    ///  [ <numeric-figure-values>   ||
1507    ///    <numeric-spacing-values>  ||
1508    ///    <numeric-fraction-values> ||
1509    ///    ordinal                   ||
1510    ///    slashed-zero ]
1511    /// <numeric-figure-values>   = [ lining-nums | oldstyle-nums ]
1512    /// <numeric-spacing-values>  = [ proportional-nums | tabular-nums ]
1513    /// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
1514    fn validate_mixed_flags(&self) -> bool {
1515        if self.contains(Self::LINING_NUMS | Self::OLDSTYLE_NUMS)
1516            || self.contains(Self::PROPORTIONAL_NUMS | Self::TABULAR_NUMS)
1517            || self.contains(Self::DIAGONAL_FRACTIONS | Self::STACKED_FRACTIONS)
1518        {
1519            return false;
1520        }
1521        true
1522    }
1523}
1524
1525/// This property provides low-level control over OpenType or TrueType font features.
1526pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>;
1527
1528impl FontFeatureSettings {
1529    /// Like `parse`, but rejects calc expressions that cannot be resolved at parse time,
1530    /// since @font-face descriptors require concrete values.
1531    pub fn parse_for_font_face_rule(
1532        context: &ParserContext,
1533        input: &mut Parser,
1534    ) -> Result<Self, ParseError> {
1535        let settings = FontFeatureSettings::parse(context, input)?;
1536        if settings
1537            .0
1538            .iter()
1539            .any(|setting| setting.value.resolve().is_none())
1540        {
1541            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1542        }
1543        Ok(settings)
1544    }
1545}
1546
1547/// For font-language-override, use the same representation as the computed value.
1548pub use crate::values::computed::font::FontLanguageOverride;
1549
1550impl Parse for FontLanguageOverride {
1551    /// normal | <string>
1552    fn parse(_: &ParserContext, input: &mut Parser) -> Result<FontLanguageOverride, ParseError> {
1553        if input
1554            .try_parse(|input| input.expect_ident_matching("normal"))
1555            .is_ok()
1556        {
1557            return Ok(FontLanguageOverride::normal());
1558        }
1559
1560        let string = input.expect_string()?;
1561
1562        // The OpenType spec requires tags to be 1 to 4 ASCII characters:
1563        // https://learn.microsoft.com/en-gb/typography/opentype/spec/otff#data-types
1564        if string.is_empty() || string.len() > 4 || !string.is_ascii() {
1565            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1566        }
1567
1568        let mut bytes = [b' '; 4];
1569        for (byte, str_byte) in bytes.iter_mut().zip(string.as_bytes()) {
1570            *byte = *str_byte;
1571        }
1572
1573        Ok(FontLanguageOverride(u32::from_be_bytes(bytes)))
1574    }
1575}
1576
1577/// A value for any of the font-synthesis-{weight,small-caps,position} properties.
1578#[repr(u8)]
1579#[derive(
1580    Clone,
1581    Copy,
1582    Debug,
1583    Deserialize,
1584    Eq,
1585    Hash,
1586    MallocSizeOf,
1587    Parse,
1588    PartialEq,
1589    Serialize,
1590    SpecifiedValueInfo,
1591    ToComputedValue,
1592    ToCss,
1593    ToResolvedValue,
1594    ToShmem,
1595    ToTyped,
1596)]
1597pub enum FontSynthesis {
1598    /// This attribute may be synthesized if not supported by a face.
1599    Auto,
1600    /// Do not attempt to synthesis this style attribute.
1601    None,
1602}
1603
1604/// A value for the font-synthesis-style property.
1605#[repr(u8)]
1606#[derive(
1607    Clone,
1608    Copy,
1609    Debug,
1610    Eq,
1611    MallocSizeOf,
1612    Parse,
1613    PartialEq,
1614    SpecifiedValueInfo,
1615    ToComputedValue,
1616    ToCss,
1617    ToResolvedValue,
1618    ToShmem,
1619    ToTyped,
1620)]
1621pub enum FontSynthesisStyle {
1622    /// This attribute may be synthesized if not supported by a face.
1623    Auto,
1624    /// Do not attempt to synthesis this style attribute.
1625    None,
1626    /// Allow synthesis for oblique, but not for italic.
1627    ObliqueOnly,
1628}
1629
1630#[derive(
1631    Clone,
1632    Debug,
1633    Eq,
1634    MallocSizeOf,
1635    PartialEq,
1636    SpecifiedValueInfo,
1637    ToComputedValue,
1638    ToResolvedValue,
1639    ToShmem,
1640    ToTyped,
1641)]
1642#[repr(C)]
1643#[typed(todo_derive_fields)]
1644/// Allows authors to choose a palette from those supported by a color font
1645/// (and potentially @font-palette-values overrides).
1646pub struct FontPalette(Atom);
1647
1648#[allow(missing_docs)]
1649impl FontPalette {
1650    pub fn normal() -> Self {
1651        Self(atom!("normal"))
1652    }
1653    pub fn light() -> Self {
1654        Self(atom!("light"))
1655    }
1656    pub fn dark() -> Self {
1657        Self(atom!("dark"))
1658    }
1659}
1660
1661impl Parse for FontPalette {
1662    /// normal | light | dark | dashed-ident
1663    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<FontPalette, ParseError> {
1664        let ident = input.expect_ident()?;
1665        match_ignore_ascii_case! { &ident,
1666            "normal" => Ok(Self::normal()),
1667            "light" => Ok(Self::light()),
1668            "dark" => Ok(Self::dark()),
1669            _ => if ident.starts_with("--") {
1670                Ok(Self(Atom::from(ident.as_ref())))
1671            } else {
1672                Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
1673            },
1674        }
1675    }
1676}
1677
1678impl ToCss for FontPalette {
1679    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1680    where
1681        W: Write,
1682    {
1683        serialize_atom_identifier(&self.0, dest)
1684    }
1685}
1686
1687/// This property provides low-level control over OpenType or TrueType font
1688/// variations.
1689pub type FontVariationSettings = FontSettings<VariationValue<Number>>;
1690
1691fn parse_one_feature_value(
1692    context: &ParserContext,
1693    input: &mut Parser,
1694) -> Result<Integer, ParseError> {
1695    if let Ok(integer) = input.try_parse(|i| Integer::parse_non_negative(context, i)) {
1696        return Ok(integer);
1697    }
1698
1699    try_match_ident_ignore_ascii_case! { input,
1700        "on" => Ok(Integer::new(1)),
1701        "off" => Ok(Integer::new(0)),
1702    }
1703}
1704
1705impl Parse for FeatureTagValue<Integer> {
1706    /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
1707    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1708        let tag = FontTag::parse(context, input)?;
1709        let value = input
1710            .try_parse(|i| parse_one_feature_value(context, i))
1711            .unwrap_or_else(|_| Integer::new(1));
1712
1713        Ok(Self { tag, value })
1714    }
1715}
1716
1717impl Parse for VariationValue<Number> {
1718    /// This is the `<string> <number>` part of the font-variation-settings
1719    /// syntax.
1720    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1721        let tag = FontTag::parse(context, input)?;
1722        let value = Number::parse(context, input)?;
1723        Ok(Self { tag, value })
1724    }
1725}
1726
1727impl FontVariationSettings {
1728    /// Like `parse`, but rejects calc expressions that cannot be resolved at parse time,
1729    /// since @font-face descriptors require concrete values.
1730    pub fn parse_for_font_face_rule(
1731        context: &ParserContext,
1732        input: &mut Parser,
1733    ) -> Result<Self, ParseError> {
1734        let settings = FontVariationSettings::parse(context, input)?;
1735        if settings
1736            .0
1737            .iter()
1738            .any(|setting| setting.value.resolve().is_none())
1739        {
1740            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1741        }
1742        Ok(settings)
1743    }
1744}
1745
1746/// A metrics override value for a @font-face descriptor
1747///
1748/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc
1749#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
1750pub enum MetricsOverride {
1751    /// A non-negative `<percentage>` of the computed font size
1752    Override(NonNegativePercentage),
1753    /// Normal metrics from the font.
1754    Normal,
1755}
1756
1757impl MetricsOverride {
1758    #[inline]
1759    /// Get default value with `normal`
1760    pub fn normal() -> MetricsOverride {
1761        MetricsOverride::Normal
1762    }
1763
1764    /// The ToComputedValue implementation, used for @font-face descriptors.
1765    ///
1766    /// Valid override percentages must be non-negative; we return -1.0 to indicate
1767    /// the absence of an override (i.e. 'normal'). Returns None if the value contains
1768    /// a calc expression that cannot be resolved at parse time.
1769    #[inline]
1770    pub fn compute(&self) -> Option<ComputedPercentage> {
1771        Some(ComputedPercentage(match self {
1772            MetricsOverride::Normal => -1.0,
1773            MetricsOverride::Override(percent) => percent.compute()?.0,
1774        }))
1775    }
1776}
1777
1778#[derive(
1779    Clone,
1780    Copy,
1781    Debug,
1782    Deserialize,
1783    MallocSizeOf,
1784    Parse,
1785    PartialEq,
1786    Serialize,
1787    SpecifiedValueInfo,
1788    ToComputedValue,
1789    ToCss,
1790    ToResolvedValue,
1791    ToShmem,
1792    ToTyped,
1793)]
1794#[repr(u8)]
1795/// How to do font-size scaling.
1796pub enum XTextScale {
1797    /// Both min-font-size and text zoom are enabled.
1798    All,
1799    /// Text-only zoom is enabled, but min-font-size is not honored.
1800    ZoomOnly,
1801    /// Neither of them is enabled.
1802    None,
1803}
1804
1805impl XTextScale {
1806    /// Returns whether text zoom is enabled.
1807    #[inline]
1808    pub fn text_zoom_enabled(self) -> bool {
1809        self != Self::None
1810    }
1811}
1812
1813#[derive(
1814    Clone,
1815    Debug,
1816    Deserialize,
1817    Eq,
1818    Hash,
1819    MallocSizeOf,
1820    PartialEq,
1821    Serialize,
1822    SpecifiedValueInfo,
1823    ToComputedValue,
1824    ToCss,
1825    ToResolvedValue,
1826    ToShmem,
1827    ToTyped,
1828)]
1829/// Internal property that reflects the lang attribute
1830pub struct XLang(#[css(skip)] pub Atom);
1831
1832impl XLang {
1833    #[inline]
1834    /// Get default value for `-x-lang`
1835    pub fn get_initial_value() -> XLang {
1836        XLang(atom!(""))
1837    }
1838}
1839
1840impl Parse for XLang {
1841    fn parse(_: &ParserContext, _input: &mut Parser) -> Result<XLang, ParseError> {
1842        debug_assert!(
1843            false,
1844            "Should be set directly by presentation attributes only."
1845        );
1846        Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1847    }
1848}
1849
1850#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1851#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
1852/// Specifies the minimum font size allowed due to changes in scriptlevel.
1853/// Ref: https://wiki.mozilla.org/MathML:mstyle
1854pub struct MozScriptMinSize(pub NoCalcLength);
1855
1856impl MozScriptMinSize {
1857    #[inline]
1858    /// Calculate initial value of -moz-script-min-size.
1859    pub fn get_initial_value() -> Length {
1860        Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * PX_PER_PT)
1861    }
1862}
1863
1864impl Parse for MozScriptMinSize {
1865    fn parse(_: &ParserContext, _input: &mut Parser) -> Result<MozScriptMinSize, ParseError> {
1866        debug_assert!(
1867            false,
1868            "Should be set directly by presentation attributes only."
1869        );
1870        Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1871    }
1872}
1873
1874/// A value for the `math-depth` property.
1875/// https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property
1876#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1877#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
1878pub enum MathDepth {
1879    /// Increment math-depth if math-style is compact.
1880    AutoAdd,
1881
1882    /// Add the function's argument to math-depth.
1883    #[css(function)]
1884    Add(Integer),
1885
1886    /// Set math-depth to the specified value.
1887    Absolute(Integer),
1888}
1889
1890impl Parse for MathDepth {
1891    fn parse(context: &ParserContext, input: &mut Parser) -> Result<MathDepth, ParseError> {
1892        if input
1893            .try_parse(|i| i.expect_ident_matching("auto-add"))
1894            .is_ok()
1895        {
1896            return Ok(MathDepth::AutoAdd);
1897        }
1898        if let Ok(math_depth_value) = input.try_parse(|input| Integer::parse(context, input)) {
1899            return Ok(MathDepth::Absolute(math_depth_value));
1900        }
1901        input.expect_function_matching("add")?;
1902        let math_depth_delta_value =
1903            input.parse_nested_block(|input| Integer::parse(context, input))?;
1904        Ok(MathDepth::Add(math_depth_delta_value))
1905    }
1906}
1907
1908#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1909#[derive(
1910    Clone,
1911    Copy,
1912    Debug,
1913    PartialEq,
1914    SpecifiedValueInfo,
1915    ToComputedValue,
1916    ToCss,
1917    ToResolvedValue,
1918    ToShmem,
1919)]
1920/// Specifies the multiplier to be used to adjust font size
1921/// due to changes in scriptlevel.
1922///
1923/// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs
1924pub struct MozScriptSizeMultiplier(pub f32);
1925
1926impl MozScriptSizeMultiplier {
1927    #[inline]
1928    /// Get default value of `-moz-script-size-multiplier`
1929    pub fn get_initial_value() -> MozScriptSizeMultiplier {
1930        MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32)
1931    }
1932}
1933
1934impl Parse for MozScriptSizeMultiplier {
1935    fn parse(
1936        _: &ParserContext,
1937        _input: &mut Parser,
1938    ) -> Result<MozScriptSizeMultiplier, ParseError> {
1939        debug_assert!(
1940            false,
1941            "Should be set directly by presentation attributes only."
1942        );
1943        Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1944    }
1945}
1946
1947impl From<f32> for MozScriptSizeMultiplier {
1948    fn from(v: f32) -> Self {
1949        MozScriptSizeMultiplier(v)
1950    }
1951}
1952
1953impl From<MozScriptSizeMultiplier> for f32 {
1954    fn from(v: MozScriptSizeMultiplier) -> f32 {
1955        v.0
1956    }
1957}
1958
1959/// A specified value for the `line-height` property.
1960pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>;
1961
1962/// Parses a line height <number> value. Percentages in <number>-typed calc expressions
1963/// are allowed in the `line-height` property, relative to the computed value of 1em.
1964/// https://drafts.csswg.org/css-inline/#line-height-property
1965fn parse_line_height_number(
1966    context: &ParserContext,
1967    input: &mut Parser,
1968) -> Result<NonNegativeNumber, ParseError> {
1969    parse_number_with_clamping_mode(
1970        context,
1971        input,
1972        AllowedNumericType::NonNegative,
1973        PercentageContext::allowed_with_hint(NumericBaseType::Length),
1974    )
1975    .map(NonNegative::<Number>)
1976}
1977
1978impl Parse for LineHeight {
1979    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1980        if let Ok(v) = input.try_parse(|input| parse_line_height_number(context, input)) {
1981            return Ok(GenericLineHeight::Number(v));
1982        }
1983        if let Ok(v) = input.try_parse(|input| NonNegativeLengthPercentage::parse(context, input)) {
1984            return Ok(GenericLineHeight::Length(v));
1985        }
1986        let ident = input.expect_ident()?;
1987        match_ignore_ascii_case! { &ident,
1988            "normal" => Ok(GenericLineHeight::Normal),
1989            _ => Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent)),
1990        }
1991    }
1992}
1993
1994/// Resolves a line-height length into an absolute pixel value, properly applying text
1995/// scaling and ensuring any `lh` lengths are resolved against the inherited line-height.
1996fn resolve_line_height_length(context: &Context, length: NoCalcLength) -> CSSPixelLength {
1997    let result = length.to_computed_value_with_base_size(
1998        context,
1999        FontBaseSize::CurrentStyle,
2000        LineHeightBase::InheritedStyle,
2001    );
2002    if length.should_zoom_text() {
2003        context.maybe_zoom_text(result)
2004    } else {
2005        result
2006    }
2007}
2008
2009/// Maps a line-height calc leaf into a resolved leaf. Percentages are replaced
2010/// with equivalent `em` lengths, and all lengths are resolved to absolute lengths.
2011fn map_line_height_leaf(context: &Context, leaf: &Leaf) -> Leaf {
2012    let length = match leaf {
2013        Leaf::Percentage(p) => NoCalcLength::from_em(p.get()),
2014        Leaf::Length(l) => *l,
2015        _ => return leaf.clone(),
2016    };
2017    Leaf::Length(NoCalcLength::from_px(
2018        resolve_line_height_length(context, length).px(),
2019    ))
2020}
2021
2022impl ToComputedValue for LineHeight {
2023    type ComputedValue = computed::LineHeight;
2024
2025    #[inline]
2026    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
2027        match self {
2028            GenericLineHeight::Normal => GenericLineHeight::Normal,
2029            GenericLineHeight::Number(number) => {
2030                let value = match number.as_calc() {
2031                    None => number.to_computed_value(context).0,
2032                    Some(calc) => {
2033                        let resolved = calc
2034                            .node
2035                            .resolve_map(|leaf| Ok(map_line_height_leaf(context, leaf)));
2036                        let value = match resolved {
2037                            Ok(Leaf::Number(n)) => n.get(),
2038                            _ => {
2039                                debug_assert!(
2040                                    false,
2041                                    "Unexpected LineHeight number calc without resolved number"
2042                                );
2043                                f32::NAN
2044                            },
2045                        };
2046                        // The `NonNegative` clamping mode ensures that -infinity isn't produced
2047                        calc.clamping_mode
2048                            .clamp(crate::values::normalize(value).min(f32::MAX))
2049                    },
2050                };
2051                GenericLineHeight::Number(NonNegative(value))
2052            },
2053            GenericLineHeight::Length(non_negative_lp) => {
2054                let result = match non_negative_lp.0 {
2055                    LengthPercentage::Length(ref length) => {
2056                        resolve_line_height_length(context, *length)
2057                    },
2058                    LengthPercentage::Percentage(ref p) => {
2059                        resolve_line_height_length(context, NoCalcLength::from_em(p.get()))
2060                    },
2061                    LengthPercentage::Calc(ref calc) => calc
2062                        .to_computed_value_zoomed(
2063                            context,
2064                            FontBaseSize::CurrentStyle,
2065                            LineHeightBase::InheritedStyle,
2066                        )
2067                        .resolve(FontBaseSize::CurrentStyle.resolve(context).computed_size()),
2068                };
2069                GenericLineHeight::Length(result.into())
2070            },
2071        }
2072    }
2073
2074    #[inline]
2075    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
2076        match *computed {
2077            GenericLineHeight::Normal => GenericLineHeight::Normal,
2078            GenericLineHeight::Number(ref number) => {
2079                GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
2080            },
2081            GenericLineHeight::Length(ref length) => {
2082                GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
2083            },
2084        }
2085    }
2086}
2087
2088/// Flags for the query_font_metrics() function.
2089#[repr(C)]
2090pub struct QueryFontMetricsFlags(u8);
2091
2092bitflags! {
2093    impl QueryFontMetricsFlags: u8 {
2094        /// Should we use the user font set?
2095        const USE_USER_FONT_SET = 1 << 0;
2096        /// Does the caller need the `ch` unit (width of the ZERO glyph)?
2097        const NEEDS_CH = 1 << 1;
2098        /// Does the caller need the `ic` unit (width of the WATER ideograph)?
2099        const NEEDS_IC = 1 << 2;
2100        /// Does the caller need math scales to be retrieved?
2101        const NEEDS_MATH_SCALES = 1 << 3;
2102    }
2103}