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