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