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#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))]
1379#[css(bitflags(
1380    single = "normal,none",
1381    mixed = "common-ligatures,no-common-ligatures,discretionary-ligatures,no-discretionary-ligatures,historical-ligatures,no-historical-ligatures,contextual,no-contextual",
1382    validate_mixed = "Self::validate_mixed_flags",
1383))]
1384#[repr(C)]
1385/// Variants of ligatures
1386pub struct FontVariantLigatures(u16);
1387bitflags! {
1388    impl FontVariantLigatures: u16 {
1389        /// Specifies that common default features are enabled
1390        const NORMAL = 0;
1391        /// Specifies that no features are enabled;
1392        const NONE = 1;
1393        /// Enables display of common ligatures
1394        const COMMON_LIGATURES  = 1 << 1;
1395        /// Disables display of common ligatures
1396        const NO_COMMON_LIGATURES  = 1 << 2;
1397        /// Enables display of discretionary ligatures
1398        const DISCRETIONARY_LIGATURES = 1 << 3;
1399        /// Disables display of discretionary ligatures
1400        const NO_DISCRETIONARY_LIGATURES = 1 << 4;
1401        /// Enables display of historical ligatures
1402        const HISTORICAL_LIGATURES = 1 << 5;
1403        /// Disables display of historical ligatures
1404        const NO_HISTORICAL_LIGATURES = 1 << 6;
1405        /// Enables display of contextual alternates
1406        const CONTEXTUAL = 1 << 7;
1407        /// Disables display of contextual alternates
1408        const NO_CONTEXTUAL = 1 << 8;
1409    }
1410}
1411
1412impl FontVariantLigatures {
1413    /// The number of variants.
1414    pub const COUNT: usize = 9;
1415
1416    fn validate_mixed_flags(&self) -> bool {
1417        // Mixing a value and its disabling value is forbidden.
1418        if self.contains(Self::COMMON_LIGATURES | Self::NO_COMMON_LIGATURES)
1419            || self.contains(Self::DISCRETIONARY_LIGATURES | Self::NO_DISCRETIONARY_LIGATURES)
1420            || self.contains(Self::HISTORICAL_LIGATURES | Self::NO_HISTORICAL_LIGATURES)
1421            || self.contains(Self::CONTEXTUAL | Self::NO_CONTEXTUAL)
1422        {
1423            return false;
1424        }
1425        true
1426    }
1427}
1428
1429/// Variants of numeric values
1430#[derive(
1431    Clone,
1432    Copy,
1433    Debug,
1434    Eq,
1435    MallocSizeOf,
1436    PartialEq,
1437    Parse,
1438    SpecifiedValueInfo,
1439    ToComputedValue,
1440    ToCss,
1441    ToResolvedValue,
1442    ToShmem,
1443    ToTyped,
1444)]
1445#[css(bitflags(
1446    single = "normal",
1447    mixed = "lining-nums,oldstyle-nums,proportional-nums,tabular-nums,diagonal-fractions,stacked-fractions,ordinal,slashed-zero",
1448    validate_mixed = "Self::validate_mixed_flags",
1449))]
1450#[cfg_attr(feature = "servo", derive(Serialize, Deserialize, Hash))]
1451#[repr(C)]
1452pub struct FontVariantNumeric(u8);
1453bitflags! {
1454    impl FontVariantNumeric : u8 {
1455        /// Specifies that common default features are enabled
1456        const NORMAL = 0;
1457        /// Enables display of lining numerals.
1458        const LINING_NUMS = 1 << 0;
1459        /// Enables display of old-style numerals.
1460        const OLDSTYLE_NUMS = 1 << 1;
1461        /// Enables display of proportional numerals.
1462        const PROPORTIONAL_NUMS = 1 << 2;
1463        /// Enables display of tabular numerals.
1464        const TABULAR_NUMS = 1 << 3;
1465        /// Enables display of lining diagonal fractions.
1466        const DIAGONAL_FRACTIONS = 1 << 4;
1467        /// Enables display of lining stacked fractions.
1468        const STACKED_FRACTIONS = 1 << 5;
1469        /// Enables display of slashed zeros.
1470        const SLASHED_ZERO = 1 << 6;
1471        /// Enables display of letter forms used with ordinal numbers.
1472        const ORDINAL = 1 << 7;
1473    }
1474}
1475
1476impl FontVariantNumeric {
1477    /// The number of variants.
1478    pub const COUNT: usize = 8;
1479
1480    /// normal |
1481    ///  [ <numeric-figure-values>   ||
1482    ///    <numeric-spacing-values>  ||
1483    ///    <numeric-fraction-values> ||
1484    ///    ordinal                   ||
1485    ///    slashed-zero ]
1486    /// <numeric-figure-values>   = [ lining-nums | oldstyle-nums ]
1487    /// <numeric-spacing-values>  = [ proportional-nums | tabular-nums ]
1488    /// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
1489    fn validate_mixed_flags(&self) -> bool {
1490        if self.contains(Self::LINING_NUMS | Self::OLDSTYLE_NUMS)
1491            || self.contains(Self::PROPORTIONAL_NUMS | Self::TABULAR_NUMS)
1492            || self.contains(Self::DIAGONAL_FRACTIONS | Self::STACKED_FRACTIONS)
1493        {
1494            return false;
1495        }
1496        true
1497    }
1498}
1499
1500/// This property provides low-level control over OpenType or TrueType font features.
1501pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>;
1502
1503/// For font-language-override, use the same representation as the computed value.
1504pub use crate::values::computed::font::FontLanguageOverride;
1505
1506impl Parse for FontLanguageOverride {
1507    /// normal | <string>
1508    fn parse<'i, 't>(
1509        _: &ParserContext,
1510        input: &mut Parser<'i, 't>,
1511    ) -> Result<FontLanguageOverride, ParseError<'i>> {
1512        if input
1513            .try_parse(|input| input.expect_ident_matching("normal"))
1514            .is_ok()
1515        {
1516            return Ok(FontLanguageOverride::normal());
1517        }
1518
1519        let string = input.expect_string()?;
1520
1521        // The OpenType spec requires tags to be 1 to 4 ASCII characters:
1522        // https://learn.microsoft.com/en-gb/typography/opentype/spec/otff#data-types
1523        if string.is_empty() || string.len() > 4 || !string.is_ascii() {
1524            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1525        }
1526
1527        let mut bytes = [b' '; 4];
1528        for (byte, str_byte) in bytes.iter_mut().zip(string.as_bytes()) {
1529            *byte = *str_byte;
1530        }
1531
1532        Ok(FontLanguageOverride(u32::from_be_bytes(bytes)))
1533    }
1534}
1535
1536/// A value for any of the font-synthesis-{weight,small-caps,position} properties.
1537#[repr(u8)]
1538#[derive(
1539    Clone,
1540    Copy,
1541    Debug,
1542    Eq,
1543    Hash,
1544    MallocSizeOf,
1545    Parse,
1546    PartialEq,
1547    SpecifiedValueInfo,
1548    ToComputedValue,
1549    ToCss,
1550    ToResolvedValue,
1551    ToShmem,
1552    ToTyped,
1553)]
1554#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1555pub enum FontSynthesis {
1556    /// This attribute may be synthesized if not supported by a face.
1557    Auto,
1558    /// Do not attempt to synthesis this style attribute.
1559    None,
1560}
1561
1562/// A value for the font-synthesis-style property.
1563#[repr(u8)]
1564#[derive(
1565    Clone,
1566    Copy,
1567    Debug,
1568    Eq,
1569    MallocSizeOf,
1570    Parse,
1571    PartialEq,
1572    SpecifiedValueInfo,
1573    ToComputedValue,
1574    ToCss,
1575    ToResolvedValue,
1576    ToShmem,
1577    ToTyped,
1578)]
1579pub enum FontSynthesisStyle {
1580    /// This attribute may be synthesized if not supported by a face.
1581    Auto,
1582    /// Do not attempt to synthesis this style attribute.
1583    None,
1584    /// Allow synthesis for oblique, but not for italic.
1585    ObliqueOnly,
1586}
1587
1588#[derive(
1589    Clone,
1590    Debug,
1591    Eq,
1592    MallocSizeOf,
1593    PartialEq,
1594    SpecifiedValueInfo,
1595    ToComputedValue,
1596    ToResolvedValue,
1597    ToShmem,
1598    ToTyped,
1599)]
1600#[repr(C)]
1601#[typed(todo_derive_fields)]
1602/// Allows authors to choose a palette from those supported by a color font
1603/// (and potentially @font-palette-values overrides).
1604pub struct FontPalette(Atom);
1605
1606#[allow(missing_docs)]
1607impl FontPalette {
1608    pub fn normal() -> Self {
1609        Self(atom!("normal"))
1610    }
1611    pub fn light() -> Self {
1612        Self(atom!("light"))
1613    }
1614    pub fn dark() -> Self {
1615        Self(atom!("dark"))
1616    }
1617}
1618
1619impl Parse for FontPalette {
1620    /// normal | light | dark | dashed-ident
1621    fn parse<'i, 't>(
1622        _context: &ParserContext,
1623        input: &mut Parser<'i, 't>,
1624    ) -> Result<FontPalette, ParseError<'i>> {
1625        let location = input.current_source_location();
1626        let ident = input.expect_ident()?;
1627        match_ignore_ascii_case! { &ident,
1628            "normal" => Ok(Self::normal()),
1629            "light" => Ok(Self::light()),
1630            "dark" => Ok(Self::dark()),
1631            _ => if ident.starts_with("--") {
1632                Ok(Self(Atom::from(ident.as_ref())))
1633            } else {
1634                Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
1635            },
1636        }
1637    }
1638}
1639
1640impl ToCss for FontPalette {
1641    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1642    where
1643        W: Write,
1644    {
1645        serialize_atom_identifier(&self.0, dest)
1646    }
1647}
1648
1649/// This property provides low-level control over OpenType or TrueType font
1650/// variations.
1651pub type FontVariationSettings = FontSettings<VariationValue<Number>>;
1652
1653fn parse_one_feature_value<'i, 't>(
1654    context: &ParserContext,
1655    input: &mut Parser<'i, 't>,
1656) -> Result<Integer, ParseError<'i>> {
1657    if let Ok(integer) = input.try_parse(|i| Integer::parse_non_negative(context, i)) {
1658        return Ok(integer);
1659    }
1660
1661    try_match_ident_ignore_ascii_case! { input,
1662        "on" => Ok(Integer::new(1)),
1663        "off" => Ok(Integer::new(0)),
1664    }
1665}
1666
1667impl Parse for FeatureTagValue<Integer> {
1668    /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
1669    fn parse<'i, 't>(
1670        context: &ParserContext,
1671        input: &mut Parser<'i, 't>,
1672    ) -> Result<Self, ParseError<'i>> {
1673        let tag = FontTag::parse(context, input)?;
1674        let value = input
1675            .try_parse(|i| parse_one_feature_value(context, i))
1676            .unwrap_or_else(|_| Integer::new(1));
1677
1678        Ok(Self { tag, value })
1679    }
1680}
1681
1682impl Parse for VariationValue<Number> {
1683    /// This is the `<string> <number>` part of the font-variation-settings
1684    /// syntax.
1685    fn parse<'i, 't>(
1686        context: &ParserContext,
1687        input: &mut Parser<'i, 't>,
1688    ) -> Result<Self, ParseError<'i>> {
1689        let tag = FontTag::parse(context, input)?;
1690        let value = Number::parse(context, input)?;
1691        Ok(Self { tag, value })
1692    }
1693}
1694
1695/// A metrics override value for a @font-face descriptor
1696///
1697/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc
1698#[derive(
1699    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
1700)]
1701pub enum MetricsOverride {
1702    /// A non-negative `<percentage>` of the computed font size
1703    Override(NonNegativePercentage),
1704    /// Normal metrics from the font.
1705    Normal,
1706}
1707
1708impl MetricsOverride {
1709    #[inline]
1710    /// Get default value with `normal`
1711    pub fn normal() -> MetricsOverride {
1712        MetricsOverride::Normal
1713    }
1714
1715    /// The ToComputedValue implementation, used for @font-face descriptors.
1716    ///
1717    /// Valid override percentages must be non-negative; we return -1.0 to indicate
1718    /// the absence of an override (i.e. 'normal').
1719    #[inline]
1720    pub fn compute(&self) -> ComputedPercentage {
1721        match *self {
1722            MetricsOverride::Normal => ComputedPercentage(-1.0),
1723            MetricsOverride::Override(percent) => ComputedPercentage(percent.0.get()),
1724        }
1725    }
1726}
1727
1728#[derive(
1729    Clone,
1730    Copy,
1731    Debug,
1732    MallocSizeOf,
1733    Parse,
1734    PartialEq,
1735    SpecifiedValueInfo,
1736    ToComputedValue,
1737    ToCss,
1738    ToResolvedValue,
1739    ToShmem,
1740    ToTyped,
1741)]
1742#[repr(u8)]
1743/// How to do font-size scaling.
1744pub enum XTextScale {
1745    /// Both min-font-size and text zoom are enabled.
1746    All,
1747    /// Text-only zoom is enabled, but min-font-size is not honored.
1748    ZoomOnly,
1749    /// Neither of them is enabled.
1750    None,
1751}
1752
1753impl XTextScale {
1754    /// Returns whether text zoom is enabled.
1755    #[inline]
1756    pub fn text_zoom_enabled(self) -> bool {
1757        self != Self::None
1758    }
1759}
1760
1761#[derive(
1762    Clone,
1763    Debug,
1764    MallocSizeOf,
1765    PartialEq,
1766    SpecifiedValueInfo,
1767    ToComputedValue,
1768    ToCss,
1769    ToResolvedValue,
1770    ToShmem,
1771    ToTyped,
1772)]
1773#[cfg_attr(feature = "servo", derive(Deserialize, Eq, Hash, Serialize))]
1774/// Internal property that reflects the lang attribute
1775pub struct XLang(#[css(skip)] pub Atom);
1776
1777impl XLang {
1778    #[inline]
1779    /// Get default value for `-x-lang`
1780    pub fn get_initial_value() -> XLang {
1781        XLang(atom!(""))
1782    }
1783}
1784
1785impl Parse for XLang {
1786    fn parse<'i, 't>(
1787        _: &ParserContext,
1788        input: &mut Parser<'i, 't>,
1789    ) -> Result<XLang, ParseError<'i>> {
1790        debug_assert!(
1791            false,
1792            "Should be set directly by presentation attributes only."
1793        );
1794        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1795    }
1796}
1797
1798#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1799#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
1800/// Specifies the minimum font size allowed due to changes in scriptlevel.
1801/// Ref: https://wiki.mozilla.org/MathML:mstyle
1802pub struct MozScriptMinSize(pub NoCalcLength);
1803
1804impl MozScriptMinSize {
1805    #[inline]
1806    /// Calculate initial value of -moz-script-min-size.
1807    pub fn get_initial_value() -> Length {
1808        Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * PX_PER_PT)
1809    }
1810}
1811
1812impl Parse for MozScriptMinSize {
1813    fn parse<'i, 't>(
1814        _: &ParserContext,
1815        input: &mut Parser<'i, 't>,
1816    ) -> Result<MozScriptMinSize, ParseError<'i>> {
1817        debug_assert!(
1818            false,
1819            "Should be set directly by presentation attributes only."
1820        );
1821        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1822    }
1823}
1824
1825/// A value for the `math-depth` property.
1826/// https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property
1827#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1828#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
1829pub enum MathDepth {
1830    /// Increment math-depth if math-style is compact.
1831    AutoAdd,
1832
1833    /// Add the function's argument to math-depth.
1834    #[css(function)]
1835    Add(Integer),
1836
1837    /// Set math-depth to the specified value.
1838    Absolute(Integer),
1839}
1840
1841impl Parse for MathDepth {
1842    fn parse<'i, 't>(
1843        context: &ParserContext,
1844        input: &mut Parser<'i, 't>,
1845    ) -> Result<MathDepth, ParseError<'i>> {
1846        if input
1847            .try_parse(|i| i.expect_ident_matching("auto-add"))
1848            .is_ok()
1849        {
1850            return Ok(MathDepth::AutoAdd);
1851        }
1852        if let Ok(math_depth_value) = input.try_parse(|input| Integer::parse(context, input)) {
1853            return Ok(MathDepth::Absolute(math_depth_value));
1854        }
1855        input.expect_function_matching("add")?;
1856        let math_depth_delta_value =
1857            input.parse_nested_block(|input| Integer::parse(context, input))?;
1858        Ok(MathDepth::Add(math_depth_delta_value))
1859    }
1860}
1861
1862#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1863#[derive(
1864    Clone,
1865    Copy,
1866    Debug,
1867    PartialEq,
1868    SpecifiedValueInfo,
1869    ToComputedValue,
1870    ToCss,
1871    ToResolvedValue,
1872    ToShmem,
1873)]
1874/// Specifies the multiplier to be used to adjust font size
1875/// due to changes in scriptlevel.
1876///
1877/// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs
1878pub struct MozScriptSizeMultiplier(pub f32);
1879
1880impl MozScriptSizeMultiplier {
1881    #[inline]
1882    /// Get default value of `-moz-script-size-multiplier`
1883    pub fn get_initial_value() -> MozScriptSizeMultiplier {
1884        MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32)
1885    }
1886}
1887
1888impl Parse for MozScriptSizeMultiplier {
1889    fn parse<'i, 't>(
1890        _: &ParserContext,
1891        input: &mut Parser<'i, 't>,
1892    ) -> Result<MozScriptSizeMultiplier, ParseError<'i>> {
1893        debug_assert!(
1894            false,
1895            "Should be set directly by presentation attributes only."
1896        );
1897        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1898    }
1899}
1900
1901impl From<f32> for MozScriptSizeMultiplier {
1902    fn from(v: f32) -> Self {
1903        MozScriptSizeMultiplier(v)
1904    }
1905}
1906
1907impl From<MozScriptSizeMultiplier> for f32 {
1908    fn from(v: MozScriptSizeMultiplier) -> f32 {
1909        v.0
1910    }
1911}
1912
1913/// A specified value for the `line-height` property.
1914pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>;
1915
1916impl ToComputedValue for LineHeight {
1917    type ComputedValue = computed::LineHeight;
1918
1919    #[inline]
1920    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
1921        match *self {
1922            GenericLineHeight::Normal => GenericLineHeight::Normal,
1923            #[cfg(feature = "gecko")]
1924            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
1925            GenericLineHeight::Number(number) => {
1926                GenericLineHeight::Number(number.to_computed_value(context))
1927            },
1928            GenericLineHeight::Length(ref non_negative_lp) => {
1929                let result = match non_negative_lp.0 {
1930                    LengthPercentage::Length(NoCalcLength::Absolute(ref abs)) => {
1931                        context.maybe_zoom_text(abs.to_computed_value(context))
1932                    },
1933                    LengthPercentage::Length(ref length) => {
1934                        // line-height units specifically resolve against parent's
1935                        // font and line-height properties, while the rest of font
1936                        // relative units still resolve against the element's own
1937                        // properties.
1938                        length.to_computed_value_with_base_size(
1939                            context,
1940                            FontBaseSize::CurrentStyle,
1941                            LineHeightBase::InheritedStyle,
1942                        )
1943                    },
1944                    LengthPercentage::Percentage(ref p) => FontRelativeLength::Em(p.0)
1945                        .to_computed_value(
1946                            context,
1947                            FontBaseSize::CurrentStyle,
1948                            LineHeightBase::InheritedStyle,
1949                        ),
1950                    LengthPercentage::Calc(ref calc) => {
1951                        let computed_calc = calc.to_computed_value_zoomed(
1952                            context,
1953                            FontBaseSize::CurrentStyle,
1954                            LineHeightBase::InheritedStyle,
1955                        );
1956                        let base = context.style().get_font().clone_font_size().computed_size();
1957                        computed_calc.resolve(base)
1958                    },
1959                };
1960                GenericLineHeight::Length(result.into())
1961            },
1962        }
1963    }
1964
1965    #[inline]
1966    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1967        match *computed {
1968            GenericLineHeight::Normal => GenericLineHeight::Normal,
1969            #[cfg(feature = "gecko")]
1970            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
1971            GenericLineHeight::Number(ref number) => {
1972                GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
1973            },
1974            GenericLineHeight::Length(ref length) => {
1975                GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
1976            },
1977        }
1978    }
1979}
1980
1981/// Flags for the query_font_metrics() function.
1982#[repr(C)]
1983pub struct QueryFontMetricsFlags(u8);
1984
1985bitflags! {
1986    impl QueryFontMetricsFlags: u8 {
1987        /// Should we use the user font set?
1988        const USE_USER_FONT_SET = 1 << 0;
1989        /// Does the caller need the `ch` unit (width of the ZERO glyph)?
1990        const NEEDS_CH = 1 << 1;
1991        /// Does the caller need the `ic` unit (width of the WATER ideograph)?
1992        const NEEDS_IC = 1 << 2;
1993        /// Does the caller need math scales to be retrieved?
1994        const NEEDS_MATH_SCALES = 1 << 3;
1995    }
1996}