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#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))]
1310#[css(bitflags(
1311    single = "normal",
1312    mixed = "jis78,jis83,jis90,jis04,simplified,traditional,full-width,proportional-width,ruby",
1313    validate_mixed = "Self::validate_mixed_flags",
1314))]
1315#[repr(C)]
1316/// Variants for east asian variant
1317pub struct FontVariantEastAsian(u16);
1318bitflags! {
1319    impl FontVariantEastAsian: u16 {
1320        /// None of the features
1321        const NORMAL = 0;
1322        /// Enables rendering of JIS78 forms (OpenType feature: jp78)
1323        const JIS78  = 1 << 0;
1324        /// Enables rendering of JIS83 forms (OpenType feature: jp83).
1325        const JIS83 = 1 << 1;
1326        /// Enables rendering of JIS90 forms (OpenType feature: jp90).
1327        const JIS90 = 1 << 2;
1328        /// Enables rendering of JIS2004 forms (OpenType feature: jp04).
1329        const JIS04 = 1 << 3;
1330        /// Enables rendering of simplified forms (OpenType feature: smpl).
1331        const SIMPLIFIED = 1 << 4;
1332        /// Enables rendering of traditional forms (OpenType feature: trad).
1333        const TRADITIONAL = 1 << 5;
1334
1335        /// These values are exclusive with each other.
1336        const JIS_GROUP = Self::JIS78.0 | Self::JIS83.0 | Self::JIS90.0 | Self::JIS04.0 | Self::SIMPLIFIED.0 | Self::TRADITIONAL.0;
1337
1338        /// Enables rendering of full-width variants (OpenType feature: fwid).
1339        const FULL_WIDTH = 1 << 6;
1340        /// Enables rendering of proportionally-spaced variants (OpenType feature: pwid).
1341        const PROPORTIONAL_WIDTH = 1 << 7;
1342        /// Enables display of ruby variant glyphs (OpenType feature: ruby).
1343        const RUBY = 1 << 8;
1344    }
1345}
1346
1347impl FontVariantEastAsian {
1348    /// The number of variants.
1349    pub const COUNT: usize = 9;
1350
1351    fn validate_mixed_flags(&self) -> bool {
1352        if self.contains(Self::FULL_WIDTH | Self::PROPORTIONAL_WIDTH) {
1353            // full-width and proportional-width are exclusive with each other.
1354            return false;
1355        }
1356        let jis = self.intersection(Self::JIS_GROUP);
1357        if !jis.is_empty() && !jis.bits().is_power_of_two() {
1358            return false;
1359        }
1360        true
1361    }
1362}
1363
1364#[derive(
1365    Clone,
1366    Copy,
1367    Debug,
1368    Eq,
1369    MallocSizeOf,
1370    PartialEq,
1371    Parse,
1372    SpecifiedValueInfo,
1373    ToComputedValue,
1374    ToCss,
1375    ToResolvedValue,
1376    ToShmem,
1377    ToTyped,
1378)]
1379#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))]
1380#[css(bitflags(
1381    single = "normal,none",
1382    mixed = "common-ligatures,no-common-ligatures,discretionary-ligatures,no-discretionary-ligatures,historical-ligatures,no-historical-ligatures,contextual,no-contextual",
1383    validate_mixed = "Self::validate_mixed_flags",
1384))]
1385#[repr(C)]
1386/// Variants of ligatures
1387pub struct FontVariantLigatures(u16);
1388bitflags! {
1389    impl FontVariantLigatures: u16 {
1390        /// Specifies that common default features are enabled
1391        const NORMAL = 0;
1392        /// Specifies that no features are enabled;
1393        const NONE = 1;
1394        /// Enables display of common ligatures
1395        const COMMON_LIGATURES  = 1 << 1;
1396        /// Disables display of common ligatures
1397        const NO_COMMON_LIGATURES  = 1 << 2;
1398        /// Enables display of discretionary ligatures
1399        const DISCRETIONARY_LIGATURES = 1 << 3;
1400        /// Disables display of discretionary ligatures
1401        const NO_DISCRETIONARY_LIGATURES = 1 << 4;
1402        /// Enables display of historical ligatures
1403        const HISTORICAL_LIGATURES = 1 << 5;
1404        /// Disables display of historical ligatures
1405        const NO_HISTORICAL_LIGATURES = 1 << 6;
1406        /// Enables display of contextual alternates
1407        const CONTEXTUAL = 1 << 7;
1408        /// Disables display of contextual alternates
1409        const NO_CONTEXTUAL = 1 << 8;
1410    }
1411}
1412
1413impl FontVariantLigatures {
1414    /// The number of variants.
1415    pub const COUNT: usize = 9;
1416
1417    fn validate_mixed_flags(&self) -> bool {
1418        // Mixing a value and its disabling value is forbidden.
1419        if self.contains(Self::COMMON_LIGATURES | Self::NO_COMMON_LIGATURES)
1420            || self.contains(Self::DISCRETIONARY_LIGATURES | Self::NO_DISCRETIONARY_LIGATURES)
1421            || self.contains(Self::HISTORICAL_LIGATURES | Self::NO_HISTORICAL_LIGATURES)
1422            || self.contains(Self::CONTEXTUAL | Self::NO_CONTEXTUAL)
1423        {
1424            return false;
1425        }
1426        true
1427    }
1428}
1429
1430/// Variants of numeric values
1431#[derive(
1432    Clone,
1433    Copy,
1434    Debug,
1435    Eq,
1436    MallocSizeOf,
1437    PartialEq,
1438    Parse,
1439    SpecifiedValueInfo,
1440    ToComputedValue,
1441    ToCss,
1442    ToResolvedValue,
1443    ToShmem,
1444    ToTyped,
1445)]
1446#[css(bitflags(
1447    single = "normal",
1448    mixed = "lining-nums,oldstyle-nums,proportional-nums,tabular-nums,diagonal-fractions,stacked-fractions,ordinal,slashed-zero",
1449    validate_mixed = "Self::validate_mixed_flags",
1450))]
1451#[cfg_attr(feature = "servo", derive(Serialize, Deserialize, Hash))]
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#[typed(todo_derive_fields)]
1603/// Allows authors to choose a palette from those supported by a color font
1604/// (and potentially @font-palette-values overrides).
1605pub struct FontPalette(Atom);
1606
1607#[allow(missing_docs)]
1608impl FontPalette {
1609    pub fn normal() -> Self {
1610        Self(atom!("normal"))
1611    }
1612    pub fn light() -> Self {
1613        Self(atom!("light"))
1614    }
1615    pub fn dark() -> Self {
1616        Self(atom!("dark"))
1617    }
1618}
1619
1620impl Parse for FontPalette {
1621    /// normal | light | dark | dashed-ident
1622    fn parse<'i, 't>(
1623        _context: &ParserContext,
1624        input: &mut Parser<'i, 't>,
1625    ) -> Result<FontPalette, ParseError<'i>> {
1626        let location = input.current_source_location();
1627        let ident = input.expect_ident()?;
1628        match_ignore_ascii_case! { &ident,
1629            "normal" => Ok(Self::normal()),
1630            "light" => Ok(Self::light()),
1631            "dark" => Ok(Self::dark()),
1632            _ => if ident.starts_with("--") {
1633                Ok(Self(Atom::from(ident.as_ref())))
1634            } else {
1635                Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
1636            },
1637        }
1638    }
1639}
1640
1641impl ToCss for FontPalette {
1642    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1643    where
1644        W: Write,
1645    {
1646        serialize_atom_identifier(&self.0, dest)
1647    }
1648}
1649
1650/// This property provides low-level control over OpenType or TrueType font
1651/// variations.
1652pub type FontVariationSettings = FontSettings<VariationValue<Number>>;
1653
1654fn parse_one_feature_value<'i, 't>(
1655    context: &ParserContext,
1656    input: &mut Parser<'i, 't>,
1657) -> Result<Integer, ParseError<'i>> {
1658    if let Ok(integer) = input.try_parse(|i| Integer::parse_non_negative(context, i)) {
1659        return Ok(integer);
1660    }
1661
1662    try_match_ident_ignore_ascii_case! { input,
1663        "on" => Ok(Integer::new(1)),
1664        "off" => Ok(Integer::new(0)),
1665    }
1666}
1667
1668impl Parse for FeatureTagValue<Integer> {
1669    /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
1670    fn parse<'i, 't>(
1671        context: &ParserContext,
1672        input: &mut Parser<'i, 't>,
1673    ) -> Result<Self, ParseError<'i>> {
1674        let tag = FontTag::parse(context, input)?;
1675        let value = input
1676            .try_parse(|i| parse_one_feature_value(context, i))
1677            .unwrap_or_else(|_| Integer::new(1));
1678
1679        Ok(Self { tag, value })
1680    }
1681}
1682
1683impl Parse for VariationValue<Number> {
1684    /// This is the `<string> <number>` part of the font-variation-settings
1685    /// syntax.
1686    fn parse<'i, 't>(
1687        context: &ParserContext,
1688        input: &mut Parser<'i, 't>,
1689    ) -> Result<Self, ParseError<'i>> {
1690        let tag = FontTag::parse(context, input)?;
1691        let value = Number::parse(context, input)?;
1692        Ok(Self { tag, value })
1693    }
1694}
1695
1696/// A metrics override value for a @font-face descriptor
1697///
1698/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc
1699#[derive(
1700    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
1701)]
1702pub enum MetricsOverride {
1703    /// A non-negative `<percentage>` of the computed font size
1704    Override(NonNegativePercentage),
1705    /// Normal metrics from the font.
1706    Normal,
1707}
1708
1709impl MetricsOverride {
1710    #[inline]
1711    /// Get default value with `normal`
1712    pub fn normal() -> MetricsOverride {
1713        MetricsOverride::Normal
1714    }
1715
1716    /// The ToComputedValue implementation, used for @font-face descriptors.
1717    ///
1718    /// Valid override percentages must be non-negative; we return -1.0 to indicate
1719    /// the absence of an override (i.e. 'normal').
1720    #[inline]
1721    pub fn compute(&self) -> ComputedPercentage {
1722        match *self {
1723            MetricsOverride::Normal => ComputedPercentage(-1.0),
1724            MetricsOverride::Override(percent) => ComputedPercentage(percent.0.get()),
1725        }
1726    }
1727}
1728
1729#[derive(
1730    Clone,
1731    Copy,
1732    Debug,
1733    MallocSizeOf,
1734    Parse,
1735    PartialEq,
1736    SpecifiedValueInfo,
1737    ToComputedValue,
1738    ToCss,
1739    ToResolvedValue,
1740    ToShmem,
1741    ToTyped,
1742)]
1743#[repr(u8)]
1744/// How to do font-size scaling.
1745pub enum XTextScale {
1746    /// Both min-font-size and text zoom are enabled.
1747    All,
1748    /// Text-only zoom is enabled, but min-font-size is not honored.
1749    ZoomOnly,
1750    /// Neither of them is enabled.
1751    None,
1752}
1753
1754impl XTextScale {
1755    /// Returns whether text zoom is enabled.
1756    #[inline]
1757    pub fn text_zoom_enabled(self) -> bool {
1758        self != Self::None
1759    }
1760}
1761
1762#[derive(
1763    Clone,
1764    Debug,
1765    MallocSizeOf,
1766    PartialEq,
1767    SpecifiedValueInfo,
1768    ToComputedValue,
1769    ToCss,
1770    ToResolvedValue,
1771    ToShmem,
1772    ToTyped,
1773)]
1774#[cfg_attr(feature = "servo", derive(Deserialize, Eq, Hash, Serialize))]
1775/// Internal property that reflects the lang attribute
1776pub struct XLang(#[css(skip)] pub Atom);
1777
1778impl XLang {
1779    #[inline]
1780    /// Get default value for `-x-lang`
1781    pub fn get_initial_value() -> XLang {
1782        XLang(atom!(""))
1783    }
1784}
1785
1786impl Parse for XLang {
1787    fn parse<'i, 't>(
1788        _: &ParserContext,
1789        input: &mut Parser<'i, 't>,
1790    ) -> Result<XLang, ParseError<'i>> {
1791        debug_assert!(
1792            false,
1793            "Should be set directly by presentation attributes only."
1794        );
1795        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1796    }
1797}
1798
1799#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1800#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
1801/// Specifies the minimum font size allowed due to changes in scriptlevel.
1802/// Ref: https://wiki.mozilla.org/MathML:mstyle
1803pub struct MozScriptMinSize(pub NoCalcLength);
1804
1805impl MozScriptMinSize {
1806    #[inline]
1807    /// Calculate initial value of -moz-script-min-size.
1808    pub fn get_initial_value() -> Length {
1809        Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * PX_PER_PT)
1810    }
1811}
1812
1813impl Parse for MozScriptMinSize {
1814    fn parse<'i, 't>(
1815        _: &ParserContext,
1816        input: &mut Parser<'i, 't>,
1817    ) -> Result<MozScriptMinSize, ParseError<'i>> {
1818        debug_assert!(
1819            false,
1820            "Should be set directly by presentation attributes only."
1821        );
1822        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1823    }
1824}
1825
1826/// A value for the `math-depth` property.
1827/// https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property
1828#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1829#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
1830pub enum MathDepth {
1831    /// Increment math-depth if math-style is compact.
1832    AutoAdd,
1833
1834    /// Add the function's argument to math-depth.
1835    #[css(function)]
1836    Add(Integer),
1837
1838    /// Set math-depth to the specified value.
1839    Absolute(Integer),
1840}
1841
1842impl Parse for MathDepth {
1843    fn parse<'i, 't>(
1844        context: &ParserContext,
1845        input: &mut Parser<'i, 't>,
1846    ) -> Result<MathDepth, ParseError<'i>> {
1847        if input
1848            .try_parse(|i| i.expect_ident_matching("auto-add"))
1849            .is_ok()
1850        {
1851            return Ok(MathDepth::AutoAdd);
1852        }
1853        if let Ok(math_depth_value) = input.try_parse(|input| Integer::parse(context, input)) {
1854            return Ok(MathDepth::Absolute(math_depth_value));
1855        }
1856        input.expect_function_matching("add")?;
1857        let math_depth_delta_value =
1858            input.parse_nested_block(|input| Integer::parse(context, input))?;
1859        Ok(MathDepth::Add(math_depth_delta_value))
1860    }
1861}
1862
1863#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1864#[derive(
1865    Clone,
1866    Copy,
1867    Debug,
1868    PartialEq,
1869    SpecifiedValueInfo,
1870    ToComputedValue,
1871    ToCss,
1872    ToResolvedValue,
1873    ToShmem,
1874)]
1875/// Specifies the multiplier to be used to adjust font size
1876/// due to changes in scriptlevel.
1877///
1878/// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs
1879pub struct MozScriptSizeMultiplier(pub f32);
1880
1881impl MozScriptSizeMultiplier {
1882    #[inline]
1883    /// Get default value of `-moz-script-size-multiplier`
1884    pub fn get_initial_value() -> MozScriptSizeMultiplier {
1885        MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32)
1886    }
1887}
1888
1889impl Parse for MozScriptSizeMultiplier {
1890    fn parse<'i, 't>(
1891        _: &ParserContext,
1892        input: &mut Parser<'i, 't>,
1893    ) -> Result<MozScriptSizeMultiplier, ParseError<'i>> {
1894        debug_assert!(
1895            false,
1896            "Should be set directly by presentation attributes only."
1897        );
1898        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1899    }
1900}
1901
1902impl From<f32> for MozScriptSizeMultiplier {
1903    fn from(v: f32) -> Self {
1904        MozScriptSizeMultiplier(v)
1905    }
1906}
1907
1908impl From<MozScriptSizeMultiplier> for f32 {
1909    fn from(v: MozScriptSizeMultiplier) -> f32 {
1910        v.0
1911    }
1912}
1913
1914/// A specified value for the `line-height` property.
1915pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>;
1916
1917impl ToComputedValue for LineHeight {
1918    type ComputedValue = computed::LineHeight;
1919
1920    #[inline]
1921    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
1922        match *self {
1923            GenericLineHeight::Normal => GenericLineHeight::Normal,
1924            #[cfg(feature = "gecko")]
1925            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
1926            GenericLineHeight::Number(number) => {
1927                GenericLineHeight::Number(number.to_computed_value(context))
1928            },
1929            GenericLineHeight::Length(ref non_negative_lp) => {
1930                let result = match non_negative_lp.0 {
1931                    LengthPercentage::Length(NoCalcLength::Absolute(ref abs)) => {
1932                        context.maybe_zoom_text(abs.to_computed_value(context))
1933                    },
1934                    LengthPercentage::Length(ref length) => {
1935                        // line-height units specifically resolve against parent's
1936                        // font and line-height properties, while the rest of font
1937                        // relative units still resolve against the element's own
1938                        // properties.
1939                        length.to_computed_value_with_base_size(
1940                            context,
1941                            FontBaseSize::CurrentStyle,
1942                            LineHeightBase::InheritedStyle,
1943                        )
1944                    },
1945                    LengthPercentage::Percentage(ref p) => FontRelativeLength::Em(p.0)
1946                        .to_computed_value(
1947                            context,
1948                            FontBaseSize::CurrentStyle,
1949                            LineHeightBase::InheritedStyle,
1950                        ),
1951                    LengthPercentage::Calc(ref calc) => {
1952                        let computed_calc = calc.to_computed_value_zoomed(
1953                            context,
1954                            FontBaseSize::CurrentStyle,
1955                            LineHeightBase::InheritedStyle,
1956                        );
1957                        let base = context.style().get_font().clone_font_size().computed_size();
1958                        computed_calc.resolve(base)
1959                    },
1960                };
1961                GenericLineHeight::Length(result.into())
1962            },
1963        }
1964    }
1965
1966    #[inline]
1967    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1968        match *computed {
1969            GenericLineHeight::Normal => GenericLineHeight::Normal,
1970            #[cfg(feature = "gecko")]
1971            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
1972            GenericLineHeight::Number(ref number) => {
1973                GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
1974            },
1975            GenericLineHeight::Length(ref length) => {
1976                GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
1977            },
1978        }
1979    }
1980}
1981
1982/// Flags for the query_font_metrics() function.
1983#[repr(C)]
1984pub struct QueryFontMetricsFlags(u8);
1985
1986bitflags! {
1987    impl QueryFontMetricsFlags: u8 {
1988        /// Should we use the user font set?
1989        const USE_USER_FONT_SET = 1 << 0;
1990        /// Does the caller need the `ch` unit (width of the ZERO glyph)?
1991        const NEEDS_CH = 1 << 1;
1992        /// Does the caller need the `ic` unit (width of the WATER ideograph)?
1993        const NEEDS_IC = 1 << 2;
1994        /// Does the caller need math scales to be retrieved?
1995        const NEEDS_MATH_SCALES = 1 << 3;
1996    }
1997}