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