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