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