Skip to main content

style/values/specified/
font.rs

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