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    Eq,
1614    MallocSizeOf,
1615    Parse,
1616    PartialEq,
1617    SpecifiedValueInfo,
1618    ToComputedValue,
1619    ToCss,
1620    ToResolvedValue,
1621    ToShmem,
1622    ToTyped,
1623)]
1624pub enum FontSynthesisStyle {
1625    /// This attribute may be synthesized if not supported by a face.
1626    Auto,
1627    /// Do not attempt to synthesis this style attribute.
1628    None,
1629    /// Allow synthesis for oblique, but not for italic.
1630    ObliqueOnly,
1631}
1632
1633#[derive(
1634    Clone,
1635    Debug,
1636    Eq,
1637    MallocSizeOf,
1638    PartialEq,
1639    SpecifiedValueInfo,
1640    ToComputedValue,
1641    ToResolvedValue,
1642    ToShmem,
1643    ToTyped,
1644)]
1645#[repr(C)]
1646#[typed(todo_derive_fields)]
1647/// Allows authors to choose a palette from those supported by a color font
1648/// (and potentially @font-palette-values overrides).
1649pub struct FontPalette(Atom);
1650
1651#[allow(missing_docs)]
1652impl FontPalette {
1653    pub fn normal() -> Self {
1654        Self(atom!("normal"))
1655    }
1656    pub fn light() -> Self {
1657        Self(atom!("light"))
1658    }
1659    pub fn dark() -> Self {
1660        Self(atom!("dark"))
1661    }
1662}
1663
1664impl Parse for FontPalette {
1665    /// normal | light | dark | dashed-ident
1666    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<FontPalette, ParseError> {
1667        let ident = input.expect_ident()?;
1668        match_ignore_ascii_case! { &ident,
1669            "normal" => Ok(Self::normal()),
1670            "light" => Ok(Self::light()),
1671            "dark" => Ok(Self::dark()),
1672            _ => if ident.starts_with("--") {
1673                Ok(Self(Atom::from(ident.as_ref())))
1674            } else {
1675                Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent))
1676            },
1677        }
1678    }
1679}
1680
1681impl ToCss for FontPalette {
1682    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1683    where
1684        W: Write,
1685    {
1686        serialize_atom_identifier(&self.0, dest)
1687    }
1688}
1689
1690/// This property provides low-level control over OpenType or TrueType font
1691/// variations.
1692pub type FontVariationSettings = FontSettings<VariationValue<Number>>;
1693
1694fn parse_one_feature_value(
1695    context: &ParserContext,
1696    input: &mut Parser,
1697) -> Result<Integer, ParseError> {
1698    if let Ok(integer) = input.try_parse(|i| Integer::parse_non_negative(context, i)) {
1699        return Ok(integer);
1700    }
1701
1702    try_match_ident_ignore_ascii_case! { input,
1703        "on" => Ok(Integer::new(1)),
1704        "off" => Ok(Integer::new(0)),
1705    }
1706}
1707
1708impl Parse for FeatureTagValue<Integer> {
1709    /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
1710    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1711        let tag = FontTag::parse(context, input)?;
1712        let value = input
1713            .try_parse(|i| parse_one_feature_value(context, i))
1714            .unwrap_or_else(|_| Integer::new(1));
1715
1716        Ok(Self { tag, value })
1717    }
1718}
1719
1720impl Parse for VariationValue<Number> {
1721    /// This is the `<string> <number>` part of the font-variation-settings
1722    /// syntax.
1723    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1724        let tag = FontTag::parse(context, input)?;
1725        let value = Number::parse(context, input)?;
1726        Ok(Self { tag, value })
1727    }
1728}
1729
1730impl FontVariationSettings {
1731    /// Like `parse`, but rejects calc expressions that cannot be resolved at parse time,
1732    /// since @font-face descriptors require concrete values.
1733    pub fn parse_for_font_face_rule(
1734        context: &ParserContext,
1735        input: &mut Parser,
1736    ) -> Result<Self, ParseError> {
1737        let settings = FontVariationSettings::parse(context, input)?;
1738        if settings
1739            .0
1740            .iter()
1741            .any(|setting| setting.value.resolve().is_none())
1742        {
1743            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1744        }
1745        Ok(settings)
1746    }
1747}
1748
1749/// A metrics override value for a @font-face descriptor
1750///
1751/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc
1752#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
1753pub enum MetricsOverride {
1754    /// A non-negative `<percentage>` of the computed font size
1755    Override(NonNegativePercentage),
1756    /// Normal metrics from the font.
1757    Normal,
1758}
1759
1760impl MetricsOverride {
1761    #[inline]
1762    /// Get default value with `normal`
1763    pub fn normal() -> MetricsOverride {
1764        MetricsOverride::Normal
1765    }
1766
1767    /// The ToComputedValue implementation, used for @font-face descriptors.
1768    ///
1769    /// Valid override percentages must be non-negative; we return -1.0 to indicate
1770    /// the absence of an override (i.e. 'normal'). Returns None if the value contains
1771    /// a calc expression that cannot be resolved at parse time.
1772    #[inline]
1773    pub fn compute(&self) -> Option<ComputedPercentage> {
1774        Some(ComputedPercentage(match self {
1775            MetricsOverride::Normal => -1.0,
1776            MetricsOverride::Override(percent) => percent.compute()?.0,
1777        }))
1778    }
1779}
1780
1781#[derive(
1782    Clone,
1783    Copy,
1784    Debug,
1785    Deserialize,
1786    MallocSizeOf,
1787    Parse,
1788    PartialEq,
1789    Serialize,
1790    SpecifiedValueInfo,
1791    ToComputedValue,
1792    ToCss,
1793    ToResolvedValue,
1794    ToShmem,
1795    ToTyped,
1796)]
1797#[repr(u8)]
1798/// How to do font-size scaling.
1799pub enum XTextScale {
1800    /// Both min-font-size and text zoom are enabled.
1801    All,
1802    /// Text-only zoom is enabled, but min-font-size is not honored.
1803    ZoomOnly,
1804    /// Neither of them is enabled.
1805    None,
1806}
1807
1808impl XTextScale {
1809    /// Returns whether text zoom is enabled.
1810    #[inline]
1811    pub fn text_zoom_enabled(self) -> bool {
1812        self != Self::None
1813    }
1814}
1815
1816#[derive(
1817    Clone,
1818    Debug,
1819    Deserialize,
1820    Eq,
1821    Hash,
1822    MallocSizeOf,
1823    PartialEq,
1824    Serialize,
1825    SpecifiedValueInfo,
1826    ToComputedValue,
1827    ToCss,
1828    ToResolvedValue,
1829    ToShmem,
1830    ToTyped,
1831)]
1832#[repr(transparent)]
1833/// Internal property that reflects the lang attribute
1834pub struct XLang(#[css(skip)] pub Atom);
1835
1836impl XLang {
1837    #[inline]
1838    /// Get default value for `-x-lang`
1839    pub fn get_initial_value() -> XLang {
1840        XLang(atom!(""))
1841    }
1842}
1843
1844impl Parse for XLang {
1845    fn parse(_: &ParserContext, _input: &mut Parser) -> Result<XLang, ParseError> {
1846        debug_assert!(
1847            false,
1848            "Should be set directly by presentation attributes only."
1849        );
1850        Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1851    }
1852}
1853
1854#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
1855/// Specifies the minimum font size allowed due to changes in scriptlevel.
1856/// Ref: https://wiki.mozilla.org/MathML:mstyle
1857pub struct MozScriptMinSize(pub NoCalcLength);
1858
1859impl MozScriptMinSize {
1860    #[inline]
1861    /// Calculate initial value of -moz-script-min-size.
1862    pub fn get_initial_value() -> Length {
1863        Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * PX_PER_PT)
1864    }
1865}
1866
1867impl Parse for MozScriptMinSize {
1868    fn parse(_: &ParserContext, _input: &mut Parser) -> Result<MozScriptMinSize, ParseError> {
1869        debug_assert!(
1870            false,
1871            "Should be set directly by presentation attributes only."
1872        );
1873        Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1874    }
1875}
1876
1877/// A value for the `math-depth` property.
1878/// https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property
1879#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
1880pub enum MathDepth {
1881    /// Increment math-depth if math-style is compact.
1882    AutoAdd,
1883
1884    /// Add the function's argument to math-depth.
1885    #[css(function)]
1886    Add(Integer),
1887
1888    /// Set math-depth to the specified value.
1889    Absolute(Integer),
1890}
1891
1892impl Parse for MathDepth {
1893    fn parse(context: &ParserContext, input: &mut Parser) -> Result<MathDepth, ParseError> {
1894        if input
1895            .try_parse(|i| i.expect_ident_matching("auto-add"))
1896            .is_ok()
1897        {
1898            return Ok(MathDepth::AutoAdd);
1899        }
1900        if let Ok(math_depth_value) = input.try_parse(|input| Integer::parse(context, input)) {
1901            return Ok(MathDepth::Absolute(math_depth_value));
1902        }
1903        input.expect_function_matching("add")?;
1904        let math_depth_delta_value =
1905            input.parse_nested_block(|input| Integer::parse(context, input))?;
1906        Ok(MathDepth::Add(math_depth_delta_value))
1907    }
1908}
1909
1910#[derive(
1911    Clone,
1912    Copy,
1913    Debug,
1914    MallocSizeOf,
1915    PartialEq,
1916    SpecifiedValueInfo,
1917    ToComputedValue,
1918    ToCss,
1919    ToResolvedValue,
1920    ToShmem,
1921)]
1922/// Specifies the multiplier to be used to adjust font size
1923/// due to changes in scriptlevel.
1924///
1925/// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs
1926pub struct MozScriptSizeMultiplier(pub f32);
1927
1928impl MozScriptSizeMultiplier {
1929    #[inline]
1930    /// Get default value of `-moz-script-size-multiplier`
1931    pub fn get_initial_value() -> MozScriptSizeMultiplier {
1932        MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32)
1933    }
1934}
1935
1936impl Parse for MozScriptSizeMultiplier {
1937    fn parse(
1938        _: &ParserContext,
1939        _input: &mut Parser,
1940    ) -> Result<MozScriptSizeMultiplier, ParseError> {
1941        debug_assert!(
1942            false,
1943            "Should be set directly by presentation attributes only."
1944        );
1945        Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1946    }
1947}
1948
1949impl From<f32> for MozScriptSizeMultiplier {
1950    fn from(v: f32) -> Self {
1951        MozScriptSizeMultiplier(v)
1952    }
1953}
1954
1955impl From<MozScriptSizeMultiplier> for f32 {
1956    fn from(v: MozScriptSizeMultiplier) -> f32 {
1957        v.0
1958    }
1959}
1960
1961/// A specified value for the `line-height` property.
1962pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>;
1963
1964/// Parses a line height <number> value. Percentages in <number>-typed calc expressions
1965/// are allowed in the `line-height` property, relative to the computed value of 1em.
1966/// https://drafts.csswg.org/css-inline/#line-height-property
1967fn parse_line_height_number(
1968    context: &ParserContext,
1969    input: &mut Parser,
1970) -> Result<NonNegativeNumber, ParseError> {
1971    parse_number_with_clamping_mode(
1972        context,
1973        input,
1974        AllowedNumericType::NonNegative,
1975        PercentageContext::allowed_with_hint(NumericBaseType::Length),
1976    )
1977    .map(NonNegative::<Number>)
1978}
1979
1980impl Parse for LineHeight {
1981    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1982        if let Ok(v) = input.try_parse(|input| parse_line_height_number(context, input)) {
1983            return Ok(GenericLineHeight::Number(v));
1984        }
1985        if let Ok(v) = input.try_parse(|input| NonNegativeLengthPercentage::parse(context, input)) {
1986            return Ok(GenericLineHeight::Length(v));
1987        }
1988        let ident = input.expect_ident()?;
1989        match_ignore_ascii_case! { &ident,
1990            "normal" => Ok(GenericLineHeight::Normal),
1991            _ => Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent)),
1992        }
1993    }
1994}
1995
1996/// Resolves a line-height length into an absolute pixel value, properly applying text
1997/// scaling and ensuring any `lh` lengths are resolved against the inherited line-height.
1998fn resolve_line_height_length(context: &Context, length: NoCalcLength) -> CSSPixelLength {
1999    let result = length.to_computed_value_with_base_size(
2000        context,
2001        FontBaseSize::CurrentStyle,
2002        LineHeightBase::InheritedStyle,
2003    );
2004    if length.should_zoom_text() {
2005        context.maybe_zoom_text(result)
2006    } else {
2007        result
2008    }
2009}
2010
2011/// Maps a line-height calc leaf into a resolved leaf. Percentages are replaced
2012/// with equivalent `em` lengths, and all lengths are resolved to absolute lengths.
2013fn map_line_height_leaf(context: &Context, leaf: &Leaf) -> Leaf {
2014    let length = match leaf {
2015        Leaf::Percentage(p) => NoCalcLength::from_em(p.get()),
2016        Leaf::Length(l) => *l,
2017        _ => return leaf.clone(),
2018    };
2019    Leaf::Length(NoCalcLength::from_px(
2020        resolve_line_height_length(context, length).px(),
2021    ))
2022}
2023
2024impl ToComputedValue for LineHeight {
2025    type ComputedValue = computed::LineHeight;
2026
2027    #[inline]
2028    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
2029        match self {
2030            GenericLineHeight::Normal => GenericLineHeight::Normal,
2031            GenericLineHeight::Number(number) => {
2032                let value = match number.as_calc() {
2033                    None => number.to_computed_value(context).0,
2034                    Some(calc) => {
2035                        let resolved = calc
2036                            .node
2037                            .resolve_map(|leaf| Ok(map_line_height_leaf(context, leaf)));
2038                        let value = match resolved {
2039                            Ok(Leaf::Number(n)) => n.get(),
2040                            _ => {
2041                                debug_assert!(
2042                                    false,
2043                                    "Unexpected LineHeight number calc without resolved number"
2044                                );
2045                                f32::NAN
2046                            },
2047                        };
2048                        // The `NonNegative` clamping mode ensures that -infinity isn't produced
2049                        calc.clamping_mode
2050                            .clamp(crate::values::normalize(value).min(f32::MAX))
2051                    },
2052                };
2053                GenericLineHeight::Number(NonNegative(value))
2054            },
2055            GenericLineHeight::Length(non_negative_lp) => {
2056                let result = match non_negative_lp.0 {
2057                    LengthPercentage::Length(ref length) => {
2058                        resolve_line_height_length(context, *length)
2059                    },
2060                    LengthPercentage::Percentage(ref p) => {
2061                        resolve_line_height_length(context, NoCalcLength::from_em(p.get()))
2062                    },
2063                    LengthPercentage::Calc(ref calc) => calc
2064                        .to_computed_value_zoomed(
2065                            context,
2066                            FontBaseSize::CurrentStyle,
2067                            LineHeightBase::InheritedStyle,
2068                        )
2069                        .resolve(FontBaseSize::CurrentStyle.resolve(context).computed_size()),
2070                };
2071                GenericLineHeight::Length(result.into())
2072            },
2073        }
2074    }
2075
2076    #[inline]
2077    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
2078        match *computed {
2079            GenericLineHeight::Normal => GenericLineHeight::Normal,
2080            GenericLineHeight::Number(ref number) => {
2081                GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
2082            },
2083            GenericLineHeight::Length(ref length) => {
2084                GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
2085            },
2086        }
2087    }
2088}
2089
2090/// Flags for the query_font_metrics() function.
2091#[repr(C)]
2092pub struct QueryFontMetricsFlags(u8);
2093
2094bitflags! {
2095    impl QueryFontMetricsFlags: u8 {
2096        /// Should we use the user font set?
2097        const USE_USER_FONT_SET = 1 << 0;
2098        /// Does the caller need the `ch` unit (width of the ZERO glyph)?
2099        const NEEDS_CH = 1 << 1;
2100        /// Does the caller need the `ic` unit (width of the WATER ideograph)?
2101        const NEEDS_IC = 1 << 2;
2102        /// Does the caller need math scales to be retrieved?
2103        const NEEDS_MATH_SCALES = 1 << 3;
2104    }
2105}
2106
2107/// https://w3c.github.io/mathml-core/#the-math-shift
2108#[allow(missing_docs)]
2109#[derive(
2110    Clone,
2111    Copy,
2112    Debug,
2113    Deserialize,
2114    Eq,
2115    FromPrimitive,
2116    Hash,
2117    MallocSizeOf,
2118    Parse,
2119    PartialEq,
2120    Serialize,
2121    SpecifiedValueInfo,
2122    ToComputedValue,
2123    ToCss,
2124    ToResolvedValue,
2125    ToShmem,
2126    ToTyped,
2127)]
2128#[repr(u8)]
2129pub enum MathShift {
2130    Normal,
2131    Compact,
2132}
2133
2134/// https://mathml-refresh.github.io/mathml-core/#the-math-style-property
2135#[allow(missing_docs)]
2136#[derive(
2137    Clone,
2138    Copy,
2139    Debug,
2140    Deserialize,
2141    Eq,
2142    FromPrimitive,
2143    Hash,
2144    MallocSizeOf,
2145    Parse,
2146    PartialEq,
2147    Serialize,
2148    SpecifiedValueInfo,
2149    ToComputedValue,
2150    ToCss,
2151    ToResolvedValue,
2152    ToShmem,
2153    ToTyped,
2154)]
2155#[repr(u8)]
2156pub enum MathStyle {
2157    Normal,
2158    Compact,
2159}
2160
2161/// Internal (not web-exposed). Presentation attribute for legacy mathml mathvariant.
2162#[allow(missing_docs)]
2163#[derive(
2164    Clone,
2165    Copy,
2166    Debug,
2167    Deserialize,
2168    Eq,
2169    FromPrimitive,
2170    Hash,
2171    MallocSizeOf,
2172    Parse,
2173    PartialEq,
2174    Serialize,
2175    SpecifiedValueInfo,
2176    ToComputedValue,
2177    ToCss,
2178    ToResolvedValue,
2179    ToShmem,
2180    ToTyped,
2181)]
2182#[repr(u8)]
2183pub enum MathVariant {
2184    None,
2185    Normal,
2186    Bold,
2187    Italic,
2188    BoldItalic,
2189    Script,
2190    BoldScript,
2191    Fraktur,
2192    DoubleStruck,
2193    BoldFraktur,
2194    SansSerif,
2195    BoldSansSerif,
2196    SansSerifItalic,
2197    SansSerifBoldItalic,
2198    Monospace,
2199    Initial,
2200    Tailed,
2201    Looped,
2202    Stretched,
2203}
2204
2205/// https://drafts.csswg.org/css-fonts/#propdef-font-variant-emoji
2206#[allow(missing_docs)]
2207#[derive(
2208    Clone,
2209    Copy,
2210    Debug,
2211    Deserialize,
2212    Eq,
2213    FromPrimitive,
2214    Hash,
2215    MallocSizeOf,
2216    Parse,
2217    PartialEq,
2218    Serialize,
2219    SpecifiedValueInfo,
2220    ToComputedValue,
2221    ToCss,
2222    ToResolvedValue,
2223    ToShmem,
2224    ToTyped,
2225)]
2226#[repr(u8)]
2227pub enum FontVariantEmoji {
2228    Normal,
2229    Text,
2230    Emoji,
2231    Unicode,
2232}
2233
2234/// https://www.w3.org/TR/css-fonts-4/#font-optical-sizing-def
2235#[allow(missing_docs)]
2236#[derive(
2237    Clone,
2238    Copy,
2239    Debug,
2240    Deserialize,
2241    Eq,
2242    FromPrimitive,
2243    Hash,
2244    MallocSizeOf,
2245    Parse,
2246    PartialEq,
2247    Serialize,
2248    SpecifiedValueInfo,
2249    ToComputedValue,
2250    ToCss,
2251    ToResolvedValue,
2252    ToShmem,
2253    ToTyped,
2254)]
2255#[repr(u8)]
2256pub enum FontOpticalSizing {
2257    Auto,
2258    None,
2259}
2260
2261/// https://drafts.csswg.org/css-fonts/#propdef-font-kerning
2262#[allow(missing_docs)]
2263#[derive(
2264    Clone,
2265    Copy,
2266    Debug,
2267    Deserialize,
2268    Eq,
2269    FromPrimitive,
2270    Hash,
2271    MallocSizeOf,
2272    Parse,
2273    PartialEq,
2274    Serialize,
2275    SpecifiedValueInfo,
2276    ToComputedValue,
2277    ToCss,
2278    ToResolvedValue,
2279    ToShmem,
2280    ToTyped,
2281)]
2282#[repr(u8)]
2283pub enum FontKerning {
2284    Auto,
2285    None,
2286    Normal,
2287}
2288
2289/// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth)
2290#[allow(missing_docs)]
2291#[derive(
2292    Clone,
2293    Copy,
2294    Debug,
2295    Deserialize,
2296    Eq,
2297    FromPrimitive,
2298    Hash,
2299    MallocSizeOf,
2300    Parse,
2301    PartialEq,
2302    Serialize,
2303    SpecifiedValueInfo,
2304    ToComputedValue,
2305    ToCss,
2306    ToResolvedValue,
2307    ToShmem,
2308    ToTyped,
2309)]
2310#[repr(u8)]
2311pub enum FontSmoothing {
2312    Auto,
2313    #[cfg_attr(feature = "gecko", parse(aliases = "antialiased"))]
2314    Grayscale,
2315    SubpixelAntialiased,
2316}
2317
2318/// https://drafts.csswg.org/css-fonts/#propdef-font-variant-position
2319#[allow(missing_docs)]
2320#[derive(
2321    Clone,
2322    Copy,
2323    Debug,
2324    Deserialize,
2325    Eq,
2326    FromPrimitive,
2327    Hash,
2328    MallocSizeOf,
2329    Parse,
2330    PartialEq,
2331    Serialize,
2332    SpecifiedValueInfo,
2333    ToComputedValue,
2334    ToCss,
2335    ToResolvedValue,
2336    ToShmem,
2337    ToTyped,
2338)]
2339#[repr(u8)]
2340pub enum FontVariantPosition {
2341    Normal,
2342    Sub,
2343    Super,
2344}
2345
2346/// https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps
2347#[allow(missing_docs)]
2348#[derive(
2349    Clone,
2350    Copy,
2351    Debug,
2352    Deserialize,
2353    Eq,
2354    FromPrimitive,
2355    Hash,
2356    MallocSizeOf,
2357    Parse,
2358    PartialEq,
2359    Serialize,
2360    SpecifiedValueInfo,
2361    ToComputedValue,
2362    ToCss,
2363    ToResolvedValue,
2364    ToShmem,
2365    ToTyped,
2366)]
2367#[repr(u8)]
2368pub enum FontVariantCaps {
2369    Normal,
2370    SmallCaps,
2371    #[cfg(feature = "gecko")]
2372    AllSmallCaps,
2373    #[cfg(feature = "gecko")]
2374    PetiteCaps,
2375    #[cfg(feature = "gecko")]
2376    AllPetiteCaps,
2377    #[cfg(feature = "gecko")]
2378    Unicase,
2379    #[cfg(feature = "gecko")]
2380    TitlingCaps,
2381}