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