Skip to main content

style/values/computed/
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//! Computed values for font properties
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{ToTyped, TypedValue};
10use crate::values::animated::ToAnimatedValue;
11use crate::values::computed::{
12    Angle, Context, Integer, Length, NonNegativeLength, NonNegativeNumber, Number, Percentage,
13    ToComputedValue, Zoom,
14};
15use crate::values::generics::font::{
16    FeatureTagValue, FontSettings, TaggedFontValue, VariationValue,
17};
18use crate::values::generics::{font as generics, NonNegative};
19use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue};
20use crate::values::specified::font::{
21    self as specified, KeywordInfo, MAX_FONT_WEIGHT, MIN_FONT_WEIGHT,
22};
23use crate::values::specified::length::{FontBaseSize, LineHeightBase};
24use crate::values::CSSInteger;
25use crate::Atom;
26use cssparser::{match_ignore_ascii_case, serialize_identifier, CssStringWriter, Parser};
27use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
28use num_traits::abs;
29use num_traits::cast::AsPrimitive;
30use std::fmt::{self, Write};
31use style_traits::{CssWriter, ParseError, ToCss};
32use thin_vec::ThinVec;
33
34pub use crate::values::computed::Length as MozScriptMinSize;
35pub use crate::values::specified::font::MozScriptSizeMultiplier;
36pub use crate::values::specified::font::{FontPalette, FontSynthesis, FontSynthesisStyle};
37pub use crate::values::specified::font::{
38    FontVariantAlternates, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric,
39    QueryFontMetricsFlags, XLang, XTextScale,
40};
41pub use crate::values::specified::Integer as SpecifiedInteger;
42pub use crate::values::specified::Number as SpecifiedNumber;
43
44/// Generic template for font property type classes that use a fixed-point
45/// internal representation with `FRACTION_BITS` for the fractional part.
46///
47/// Values are constructed from and exposed as floating-point, but stored
48/// internally as fixed point, so there will be a quantization effect on
49/// fractional values, depending on the number of fractional bits used.
50///
51/// Using (16-bit) fixed-point types rather than floats for these style
52/// attributes reduces the memory footprint of gfxFontEntry and gfxFontStyle; it
53/// will also tend to reduce the number of distinct font instances that get
54/// created, particularly when styles are animated or set to arbitrary values
55/// (e.g. by sliders in the UI), which should reduce pressure on graphics
56/// resources and improve cache hit rates.
57///
58/// cbindgen:derive-lt
59/// cbindgen:derive-lte
60/// cbindgen:derive-gt
61/// cbindgen:derive-gte
62#[repr(C)]
63#[derive(
64    Clone,
65    ComputeSquaredDistance,
66    Copy,
67    Debug,
68    Deserialize,
69    Eq,
70    Hash,
71    MallocSizeOf,
72    PartialEq,
73    PartialOrd,
74    Serialize,
75    ToResolvedValue,
76)]
77pub struct FixedPoint<T, const FRACTION_BITS: u16> {
78    /// The actual representation.
79    pub value: T,
80}
81
82impl<T, const FRACTION_BITS: u16> FixedPoint<T, FRACTION_BITS>
83where
84    T: AsPrimitive<f32>,
85    f32: AsPrimitive<T>,
86    u16: AsPrimitive<T>,
87{
88    const SCALE: u16 = 1 << FRACTION_BITS;
89    const INVERSE_SCALE: f32 = 1.0 / Self::SCALE as f32;
90
91    /// Returns a fixed-point bit from a floating-point context.
92    pub fn from_float(v: f32) -> Self {
93        Self {
94            value: (v * Self::SCALE as f32).round().as_(),
95        }
96    }
97
98    /// Returns the floating-point representation.
99    pub fn to_float(&self) -> f32 {
100        self.value.as_() * Self::INVERSE_SCALE
101    }
102}
103
104// We implement this and mul below only for u16 types, because u32 types might need more care about
105// overflow. But it's not hard to implement in either case.
106impl<const FRACTION_BITS: u16> std::ops::Div for FixedPoint<u16, FRACTION_BITS> {
107    type Output = Self;
108    fn div(self, rhs: Self) -> Self {
109        Self {
110            value: (((self.value as u32) << (FRACTION_BITS as u32)) / (rhs.value as u32)) as u16,
111        }
112    }
113}
114impl<const FRACTION_BITS: u16> std::ops::Mul for FixedPoint<u16, FRACTION_BITS> {
115    type Output = Self;
116    fn mul(self, rhs: Self) -> Self {
117        Self {
118            value: (((self.value as u32) * (rhs.value as u32)) >> (FRACTION_BITS as u32)) as u16,
119        }
120    }
121}
122
123/// font-weight: range 1..1000, fractional values permitted; keywords
124/// 'normal', 'bold' aliased to 400, 700 respectively.
125///
126/// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375)
127pub const FONT_WEIGHT_FRACTION_BITS: u16 = 6;
128
129/// This is an alias which is useful mostly as a cbindgen / C++ inference
130/// workaround.
131pub type FontWeightFixedPoint = FixedPoint<u16, FONT_WEIGHT_FRACTION_BITS>;
132
133/// A value for the font-weight property per:
134///
135/// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight
136///
137/// cbindgen:derive-lt
138/// cbindgen:derive-lte
139/// cbindgen:derive-gt
140/// cbindgen:derive-gte
141#[derive(
142    Clone,
143    ComputeSquaredDistance,
144    Copy,
145    Debug,
146    Deserialize,
147    Hash,
148    MallocSizeOf,
149    PartialEq,
150    PartialOrd,
151    Serialize,
152    ToResolvedValue,
153)]
154#[repr(C)]
155pub struct FontWeight(FontWeightFixedPoint);
156impl ToAnimatedValue for FontWeight {
157    type AnimatedValue = Number;
158
159    #[inline]
160    fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue {
161        self.value()
162    }
163
164    #[inline]
165    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
166        FontWeight::from_float(animated)
167    }
168}
169
170impl ToCss for FontWeight {
171    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
172    where
173        W: fmt::Write,
174    {
175        self.value().to_css(dest)
176    }
177}
178
179impl ToTyped for FontWeight {
180    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
181        self.value().to_typed(dest)
182    }
183}
184
185impl FontWeight {
186    /// The `normal` keyword.
187    pub const NORMAL: FontWeight = FontWeight(FontWeightFixedPoint {
188        value: 400 << FONT_WEIGHT_FRACTION_BITS,
189    });
190
191    /// The `bold` value.
192    pub const BOLD: FontWeight = FontWeight(FontWeightFixedPoint {
193        value: 700 << FONT_WEIGHT_FRACTION_BITS,
194    });
195
196    /// The threshold from which we consider a font bold.
197    pub const BOLD_THRESHOLD: FontWeight = FontWeight(FontWeightFixedPoint {
198        value: 600 << FONT_WEIGHT_FRACTION_BITS,
199    });
200
201    /// The threshold above which CSS font matching prefers bolder faces
202    /// over lighter ones.
203    pub const PREFER_BOLD_THRESHOLD: FontWeight = FontWeight(FontWeightFixedPoint {
204        value: 500 << FONT_WEIGHT_FRACTION_BITS,
205    });
206
207    /// Returns the `normal` keyword value.
208    pub fn normal() -> Self {
209        Self::NORMAL
210    }
211
212    /// Whether this weight is bold
213    pub fn is_bold(&self) -> bool {
214        *self >= Self::BOLD_THRESHOLD
215    }
216
217    /// Returns the value as a float.
218    pub fn value(&self) -> f32 {
219        self.0.to_float()
220    }
221
222    /// Construct a valid weight from a float value.
223    pub fn from_float(v: f32) -> Self {
224        Self(FixedPoint::from_float(
225            v.max(MIN_FONT_WEIGHT).min(MAX_FONT_WEIGHT),
226        ))
227    }
228
229    /// Return the bolder weight.
230    ///
231    /// See the table in:
232    /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
233    pub fn bolder(self) -> Self {
234        let value = self.value();
235        if value < 350. {
236            return Self::NORMAL;
237        }
238        if value < 550. {
239            return Self::BOLD;
240        }
241        Self::from_float(value.max(900.))
242    }
243
244    /// Return the lighter weight.
245    ///
246    /// See the table in:
247    /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
248    pub fn lighter(self) -> Self {
249        let value = self.value();
250        if value < 550. {
251            return Self::from_float(value.min(100.));
252        }
253        if value < 750. {
254            return Self::NORMAL;
255        }
256        Self::BOLD
257    }
258}
259
260#[derive(
261    Animate,
262    Clone,
263    ComputeSquaredDistance,
264    Copy,
265    Debug,
266    Deserialize,
267    MallocSizeOf,
268    PartialEq,
269    Serialize,
270    ToAnimatedZero,
271    ToCss,
272    ToTyped,
273)]
274/// The computed value of font-size
275pub struct FontSize {
276    /// The computed size, that we use to compute ems etc. This accounts for
277    /// e.g., text-zoom.
278    pub computed_size: NonNegativeLength,
279    /// The actual used size. This is the computed font size, potentially
280    /// constrained by other factors like minimum font-size settings and so on.
281    #[css(skip)]
282    pub used_size: NonNegativeLength,
283    /// If derived from a keyword, the keyword and additional transformations applied to it
284    #[css(skip)]
285    pub keyword_info: KeywordInfo,
286}
287
288impl FontSize {
289    /// The actual computed font size.
290    #[inline]
291    pub fn computed_size(&self) -> Length {
292        self.computed_size.0
293    }
294
295    /// The actual used font size.
296    #[inline]
297    pub fn used_size(&self) -> Length {
298        self.used_size.0
299    }
300
301    /// Apply zoom to the font-size. This is usually done by ToComputedValue.
302    #[inline]
303    pub fn zoom(&self, zoom: Zoom) -> Self {
304        Self {
305            computed_size: NonNegative(Length::new(zoom.zoom(self.computed_size.0.px()))),
306            used_size: NonNegative(Length::new(zoom.zoom(self.used_size.0.px()))),
307            keyword_info: self.keyword_info,
308        }
309    }
310
311    #[inline]
312    /// Get default value of font size.
313    pub fn medium() -> Self {
314        Self {
315            computed_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)),
316            used_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)),
317            keyword_info: KeywordInfo::medium(),
318        }
319    }
320}
321
322impl ToAnimatedValue for FontSize {
323    type AnimatedValue = Length;
324
325    #[inline]
326    fn to_animated_value(self, context: &crate::values::animated::Context) -> Self::AnimatedValue {
327        self.computed_size.0.to_animated_value(context)
328    }
329
330    #[inline]
331    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
332        FontSize {
333            computed_size: NonNegative(animated.clamp_to_non_negative()),
334            used_size: NonNegative(animated.clamp_to_non_negative()),
335            keyword_info: KeywordInfo::none(),
336        }
337    }
338}
339
340impl ToResolvedValue for FontSize {
341    type ResolvedValue = NonNegativeLength;
342
343    #[inline]
344    fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue {
345        self.computed_size.to_resolved_value(context)
346    }
347
348    #[inline]
349    fn from_resolved_value(resolved: Self::ResolvedValue) -> Self {
350        let computed_size = NonNegativeLength::from_resolved_value(resolved);
351        Self {
352            computed_size,
353            used_size: computed_size,
354            keyword_info: KeywordInfo::none(),
355        }
356    }
357}
358
359#[derive(
360    Clone,
361    Debug,
362    Deserialize,
363    Eq,
364    Hash,
365    PartialEq,
366    Serialize,
367    ToComputedValue,
368    ToResolvedValue,
369    ToTyped,
370)]
371/// Specifies a prioritized list of font family names or generic family names.
372#[repr(C)]
373#[typed(todo_derive_fields)]
374pub struct FontFamily {
375    /// The actual list of family names.
376    pub families: FontFamilyList,
377    /// Whether this font-family came from a specified system-font.
378    pub is_system_font: bool,
379    /// Whether this is the initial font-family that might react to language
380    /// changes.
381    pub is_initial: bool,
382}
383
384macro_rules! static_font_family {
385    ($ident:ident, $family:expr) => {
386        static $ident: std::sync::LazyLock<FontFamily> = std::sync::LazyLock::new(|| FontFamily {
387            families: FontFamilyList {
388                list: crate::ArcSlice::from_iter_leaked(std::iter::once($family)),
389            },
390            is_system_font: false,
391            is_initial: false,
392        });
393    };
394}
395
396impl FontFamily {
397    #[inline]
398    /// Get default font family as `serif` which is a generic font-family
399    pub fn serif() -> Self {
400        Self::generic(GenericFontFamily::Serif).clone()
401    }
402
403    /// Returns the font family for `-moz-bullet-font`.
404    #[cfg(feature = "gecko")]
405    pub(crate) fn moz_bullet() -> &'static Self {
406        static_font_family!(
407            MOZ_BULLET,
408            SingleFontFamily::FamilyName(FamilyName {
409                name: atom!("-moz-bullet-font"),
410                syntax: FontFamilyNameSyntax::Identifiers,
411            })
412        );
413
414        &MOZ_BULLET
415    }
416
417    /// Returns a font family for a single system font.
418    #[cfg(feature = "gecko")]
419    pub fn for_system_font(name: &str) -> Self {
420        Self {
421            families: FontFamilyList {
422                list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName(
423                    FamilyName {
424                        name: Atom::from(name),
425                        syntax: FontFamilyNameSyntax::Identifiers,
426                    },
427                ))),
428            },
429            is_system_font: true,
430            is_initial: false,
431        }
432    }
433
434    /// Returns a generic font family.
435    pub fn generic(generic: GenericFontFamily) -> &'static Self {
436        macro_rules! generic_font_family {
437            ($ident:ident, $family:ident) => {
438                static_font_family!(
439                    $ident,
440                    SingleFontFamily::Generic(GenericFontFamily::$family)
441                )
442            };
443        }
444
445        generic_font_family!(SERIF, Serif);
446        generic_font_family!(SANS_SERIF, SansSerif);
447        generic_font_family!(MONOSPACE, Monospace);
448        generic_font_family!(CURSIVE, Cursive);
449        generic_font_family!(FANTASY, Fantasy);
450        #[cfg(feature = "gecko")]
451        generic_font_family!(MATH, Math);
452        #[cfg(feature = "gecko")]
453        generic_font_family!(MOZ_EMOJI, MozEmoji);
454        generic_font_family!(SYSTEM_UI, SystemUi);
455
456        let family = match generic {
457            GenericFontFamily::None => {
458                debug_assert!(false, "Bogus caller!");
459                &*SERIF
460            },
461            GenericFontFamily::Serif => &*SERIF,
462            GenericFontFamily::SansSerif => &*SANS_SERIF,
463            GenericFontFamily::Monospace => &*MONOSPACE,
464            GenericFontFamily::Cursive => &*CURSIVE,
465            GenericFontFamily::Fantasy => &*FANTASY,
466            #[cfg(feature = "gecko")]
467            GenericFontFamily::Math => &*MATH,
468            #[cfg(feature = "gecko")]
469            GenericFontFamily::MozEmoji => &*MOZ_EMOJI,
470            GenericFontFamily::SystemUi => &*SYSTEM_UI,
471        };
472        debug_assert_eq!(
473            *family.families.iter().next().unwrap(),
474            SingleFontFamily::Generic(generic)
475        );
476        family
477    }
478}
479
480impl MallocSizeOf for FontFamily {
481    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
482        use malloc_size_of::MallocUnconditionalSizeOf;
483        // SharedFontList objects are generally measured from the pointer stored
484        // in the specified value. So only count this if the SharedFontList is
485        // unshared.
486        let shared_font_list = &self.families.list;
487        if shared_font_list.is_unique() {
488            shared_font_list.unconditional_size_of(ops)
489        } else {
490            0
491        }
492    }
493}
494
495impl ToCss for FontFamily {
496    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
497    where
498        W: fmt::Write,
499    {
500        let mut iter = self.families.iter();
501        match iter.next() {
502            Some(f) => f.to_css(dest)?,
503            None => return Ok(()),
504        }
505        for family in iter {
506            dest.write_str(", ")?;
507            family.to_css(dest)?;
508        }
509        Ok(())
510    }
511}
512
513/// The name of a font family of choice.
514#[derive(
515    Clone,
516    Debug,
517    Deserialize,
518    Eq,
519    Hash,
520    MallocSizeOf,
521    PartialEq,
522    Serialize,
523    ToComputedValue,
524    ToResolvedValue,
525    ToShmem,
526)]
527#[repr(C)]
528pub struct FamilyName {
529    /// Name of the font family.
530    pub name: Atom,
531    /// Syntax of the font family.
532    pub syntax: FontFamilyNameSyntax,
533}
534
535#[cfg(feature = "gecko")]
536impl FamilyName {
537    fn is_known_icon_font_family(&self) -> bool {
538        use crate::gecko_bindings::bindings;
539        unsafe { bindings::Gecko_IsKnownIconFontFamily(self.name.as_ptr()) }
540    }
541}
542
543#[cfg(feature = "servo")]
544impl FamilyName {
545    fn is_known_icon_font_family(&self) -> bool {
546        false
547    }
548}
549
550impl ToCss for FamilyName {
551    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
552    where
553        W: fmt::Write,
554    {
555        match self.syntax {
556            FontFamilyNameSyntax::Quoted => {
557                dest.write_char('"')?;
558                write!(CssStringWriter::new(dest), "{}", self.name)?;
559                dest.write_char('"')
560            },
561            FontFamilyNameSyntax::Identifiers => {
562                let mut first = true;
563                for ident in self.name.to_string().split(' ') {
564                    if first {
565                        first = false;
566                    } else {
567                        dest.write_char(' ')?;
568                    }
569                    debug_assert!(
570                        !ident.is_empty(),
571                        "Family name with leading, \
572                         trailing, or consecutive white spaces should \
573                         have been marked quoted by the parser"
574                    );
575                    serialize_identifier(ident, dest)?;
576                }
577                Ok(())
578            },
579        }
580    }
581}
582
583#[derive(
584    Clone,
585    Copy,
586    Debug,
587    Deserialize,
588    Eq,
589    Hash,
590    MallocSizeOf,
591    PartialEq,
592    Serialize,
593    ToComputedValue,
594    ToResolvedValue,
595    ToShmem,
596)]
597/// Font family names must either be given quoted as strings,
598/// or unquoted as a sequence of one or more identifiers.
599#[repr(u8)]
600pub enum FontFamilyNameSyntax {
601    /// The family name was specified in a quoted form, e.g. "Font Name"
602    /// or 'Font Name'.
603    Quoted,
604
605    /// The family name was specified in an unquoted form as a sequence of
606    /// identifiers.
607    Identifiers,
608}
609
610/// A set of faces that vary in weight, width or slope.
611/// cbindgen:derive-mut-casts=true
612#[derive(
613    Clone,
614    Debug,
615    Deserialize,
616    Eq,
617    Hash,
618    MallocSizeOf,
619    PartialEq,
620    Serialize,
621    ToCss,
622    ToComputedValue,
623    ToResolvedValue,
624    ToShmem,
625)]
626#[repr(u8)]
627pub enum SingleFontFamily {
628    /// The name of a font family of choice.
629    FamilyName(FamilyName),
630    /// Generic family name.
631    Generic(GenericFontFamily),
632}
633
634fn system_ui_enabled(_: &ParserContext) -> bool {
635    crate::pref!("layout.css.system-ui.enabled")
636}
637
638#[cfg(feature = "gecko")]
639fn math_enabled(context: &ParserContext) -> bool {
640    context.chrome_rules_enabled() || crate::pref!("mathml.font_family_math.enabled")
641}
642
643/// A generic font-family name.
644///
645/// The order here is important, if you change it make sure that
646/// `gfxPlatformFontList.h`s ranged array and `gfxFontFamilyList`'s
647/// sSingleGenerics are updated as well.
648///
649/// NOTE(emilio): Should be u8, but it's a u32 because of ABI issues between GCC
650/// and LLVM see https://bugs.llvm.org/show_bug.cgi?id=44228 / bug 1600735 /
651/// bug 1726515.
652#[derive(
653    Clone,
654    Copy,
655    Debug,
656    Deserialize,
657    Eq,
658    Hash,
659    MallocSizeOf,
660    PartialEq,
661    Parse,
662    Serialize,
663    ToCss,
664    ToComputedValue,
665    ToResolvedValue,
666    ToShmem,
667)]
668#[repr(u32)]
669#[allow(missing_docs)]
670pub enum GenericFontFamily {
671    /// No generic family specified, only for internal usage.
672    ///
673    /// NOTE(emilio): Gecko code relies on this variant being zero.
674    #[css(skip)]
675    None = 0,
676    Serif,
677    SansSerif,
678    #[parse(aliases = "-moz-fixed")]
679    Monospace,
680    Cursive,
681    Fantasy,
682    #[cfg(feature = "gecko")]
683    #[parse(condition = "math_enabled")]
684    Math,
685    #[parse(condition = "system_ui_enabled")]
686    SystemUi,
687    /// An internal value for emoji font selection.
688    #[css(skip)]
689    #[cfg(feature = "gecko")]
690    MozEmoji,
691}
692
693impl GenericFontFamily {
694    /// When we disallow websites to override fonts, we ignore some generic
695    /// families that the website might specify, since they're not configured by
696    /// the user. See bug 789788 and bug 1730098.
697    pub(crate) fn valid_for_user_font_prioritization(self) -> bool {
698        match self {
699            Self::None | Self::Cursive | Self::Fantasy | Self::SystemUi => false,
700            #[cfg(feature = "gecko")]
701            Self::Math | Self::MozEmoji => false,
702            Self::Serif | Self::SansSerif | Self::Monospace => true,
703        }
704    }
705}
706
707impl Parse for SingleFontFamily {
708    /// Parse a font-family value.
709    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
710        if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) {
711            return Ok(SingleFontFamily::FamilyName(FamilyName {
712                name: Atom::from(&*value),
713                syntax: FontFamilyNameSyntax::Quoted,
714            }));
715        }
716
717        if let Ok(generic) = input.try_parse(|i| GenericFontFamily::parse(context, i)) {
718            return Ok(SingleFontFamily::Generic(generic));
719        }
720
721        let first_ident = input.expect_ident_cloned()?;
722        let reserved = match_ignore_ascii_case! { &first_ident,
723            // https://drafts.csswg.org/css-fonts/#propdef-font-family
724            // "Font family names that happen to be the same as a keyword value
725            //  (`inherit`, `serif`, `sans-serif`, `monospace`, `fantasy`, and `cursive`)
726            //  must be quoted to prevent confusion with the keywords with the same names.
727            //  The keywords ‘initial’ and ‘default’ are reserved for future use
728            //  and must also be quoted when used as font names.
729            //  UAs must not consider these keywords as matching the <family-name> type."
730            "inherit" | "initial" | "unset" | "revert" | "default" => true,
731            _ => false,
732        };
733
734        let mut value = first_ident.as_ref().to_owned();
735        let mut serialize_quoted = value.contains(' ');
736
737        // These keywords are not allowed by themselves.
738        // The only way this value can be valid with with another keyword.
739        if reserved {
740            let ident = input.expect_ident()?;
741            serialize_quoted = serialize_quoted || ident.contains(' ');
742            value.push(' ');
743            value.push_str(ident);
744        }
745        while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
746            serialize_quoted = serialize_quoted || ident.contains(' ');
747            value.push(' ');
748            value.push_str(&ident);
749        }
750        let syntax = if serialize_quoted {
751            // For font family names which contains special white spaces, e.g.
752            // `font-family: \ a\ \ b\ \ c\ ;`, it is tricky to serialize them
753            // as identifiers correctly. Just mark them quoted so we don't need
754            // to worry about them in serialization code.
755            FontFamilyNameSyntax::Quoted
756        } else {
757            FontFamilyNameSyntax::Identifiers
758        };
759        Ok(SingleFontFamily::FamilyName(FamilyName {
760            name: Atom::from(value),
761            syntax,
762        }))
763    }
764}
765
766/// A list of font families.
767#[derive(
768    Clone,
769    Debug,
770    Deserialize,
771    Hash,
772    Serialize,
773    ToComputedValue,
774    ToResolvedValue,
775    ToShmem,
776    PartialEq,
777    Eq,
778)]
779#[repr(C)]
780pub struct FontFamilyList {
781    /// The actual list of font families specified.
782    pub list: crate::ArcSlice<SingleFontFamily>,
783}
784
785impl FontFamilyList {
786    /// Return iterator of SingleFontFamily
787    pub fn iter(&self) -> impl Iterator<Item = &SingleFontFamily> {
788        self.list.iter()
789    }
790
791    /// If there's a generic font family on the list which is suitable for user
792    /// font prioritization, then move it ahead of the other families in the list,
793    /// except for any families known to be ligature-based icon fonts, where using a
794    /// generic instead of the site's specified font may cause substantial breakage.
795    /// If no suitable generic is found in the list, insert the default generic ahead
796    /// of all the listed families except for known ligature-based icon fonts.
797    #[cfg_attr(feature = "servo", allow(unused))]
798    pub(crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) {
799        let mut index_of_first_generic = None;
800        let mut target_index = None;
801
802        for (i, f) in self.iter().enumerate() {
803            match f {
804                SingleFontFamily::Generic(f) => {
805                    if index_of_first_generic.is_none() && f.valid_for_user_font_prioritization() {
806                        // If we haven't found a target position, there's nothing to do;
807                        // this entry is already ahead of everything except any whitelisted
808                        // icon fonts.
809                        if target_index.is_none() {
810                            return;
811                        }
812                        index_of_first_generic = Some(i);
813                        break;
814                    }
815                    // A non-prioritized generic (e.g. cursive, fantasy) becomes the target
816                    // position for prioritization, just like arbitrary named families.
817                    if target_index.is_none() {
818                        target_index = Some(i);
819                    }
820                },
821                SingleFontFamily::FamilyName(fam) => {
822                    // Target position for the first generic is in front of the first
823                    // non-whitelisted icon font family we find.
824                    if target_index.is_none() && !fam.is_known_icon_font_family() {
825                        target_index = Some(i);
826                    }
827                },
828            }
829        }
830
831        let mut new_list = self.list.iter().cloned().collect::<Vec<_>>();
832        let first_generic = match index_of_first_generic {
833            Some(i) => new_list.remove(i),
834            None => SingleFontFamily::Generic(generic),
835        };
836
837        if let Some(i) = target_index {
838            new_list.insert(i, first_generic);
839        } else {
840            new_list.push(first_generic);
841        }
842        self.list = crate::ArcSlice::from_iter(new_list.into_iter());
843    }
844
845    /// Returns whether we need to prioritize user fonts.
846    #[cfg_attr(feature = "servo", allow(unused))]
847    pub(crate) fn needs_user_font_prioritization(&self) -> bool {
848        self.iter().next().is_none_or(|f| match f {
849            SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(),
850            _ => true,
851        })
852    }
853
854    /// Return the generic ID if it is a single generic font
855    pub fn single_generic(&self) -> Option<GenericFontFamily> {
856        let mut iter = self.iter();
857        if let Some(SingleFontFamily::Generic(f)) = iter.next() {
858            if iter.next().is_none() {
859                return Some(*f);
860            }
861        }
862        None
863    }
864}
865
866/// Preserve the readability of text when font fallback occurs.
867pub type FontSizeAdjust = generics::GenericFontSizeAdjust<NonNegativeNumber>;
868
869impl FontSizeAdjust {
870    #[inline]
871    /// Default value of font-size-adjust
872    pub fn none() -> Self {
873        FontSizeAdjust::None
874    }
875}
876
877impl ToComputedValue for specified::FontSizeAdjust {
878    type ComputedValue = FontSizeAdjust;
879
880    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
881        use crate::font_metrics::FontMetricsOrientation;
882
883        let font_metrics = |vertical, flags| {
884            let orient = if vertical {
885                FontMetricsOrientation::MatchContextPreferVertical
886            } else {
887                FontMetricsOrientation::Horizontal
888            };
889            let metrics = context.query_font_metrics(FontBaseSize::CurrentStyle, orient, flags);
890            let font_size = context.style().get_font().clone_font_size().used_size.0;
891            (metrics, font_size)
892        };
893
894        // Macro to resolve a from-font value using the given metric field. If not present,
895        // returns the fallback value, or if that is negative, resolves using ascent instead
896        // of the missing field (this is the fallback for cap-height).
897        macro_rules! resolve {
898            ($basis:ident, $value:expr, $vertical:expr, $field:ident, $fallback:expr, $flags:expr) => {{
899                match $value {
900                    specified::FontSizeAdjustFactor::Number(f) => {
901                        FontSizeAdjust::$basis(f.to_computed_value(context))
902                    },
903                    specified::FontSizeAdjustFactor::FromFont => {
904                        let (metrics, font_size) = font_metrics($vertical, $flags);
905                        let ratio = if let Some(metric) = metrics.$field {
906                            metric / font_size
907                        } else if $fallback >= 0.0 {
908                            $fallback
909                        } else {
910                            metrics.ascent / font_size
911                        };
912                        if ratio.is_nan() {
913                            FontSizeAdjust::$basis(NonNegative(abs($fallback)))
914                        } else {
915                            FontSizeAdjust::$basis(NonNegative(ratio))
916                        }
917                    },
918                }
919            }};
920        }
921
922        match self {
923            Self::None => FontSizeAdjust::None,
924            Self::ExHeight(val) => {
925                resolve!(
926                    ExHeight,
927                    val,
928                    false,
929                    x_height,
930                    0.5,
931                    QueryFontMetricsFlags::empty()
932                )
933            },
934            Self::CapHeight(val) => {
935                resolve!(
936                    CapHeight,
937                    val,
938                    false,
939                    cap_height,
940                    -1.0, /* fall back to ascent */
941                    QueryFontMetricsFlags::empty()
942                )
943            },
944            Self::ChWidth(val) => {
945                resolve!(
946                    ChWidth,
947                    val,
948                    false,
949                    zero_advance_measure,
950                    0.5,
951                    QueryFontMetricsFlags::NEEDS_CH
952                )
953            },
954            Self::IcWidth(val) => {
955                resolve!(
956                    IcWidth,
957                    val,
958                    false,
959                    ic_width,
960                    1.0,
961                    QueryFontMetricsFlags::NEEDS_IC
962                )
963            },
964            Self::IcHeight(val) => {
965                resolve!(
966                    IcHeight,
967                    val,
968                    true,
969                    ic_width,
970                    1.0,
971                    QueryFontMetricsFlags::NEEDS_IC
972                )
973            },
974        }
975    }
976
977    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
978        macro_rules! case {
979            ($basis:ident, $val:expr) => {
980                Self::$basis(specified::FontSizeAdjustFactor::Number(
981                    ToComputedValue::from_computed_value($val),
982                ))
983            };
984        }
985        match *computed {
986            FontSizeAdjust::None => Self::None,
987            FontSizeAdjust::ExHeight(ref val) => case!(ExHeight, val),
988            FontSizeAdjust::CapHeight(ref val) => case!(CapHeight, val),
989            FontSizeAdjust::ChWidth(ref val) => case!(ChWidth, val),
990            FontSizeAdjust::IcWidth(ref val) => case!(IcWidth, val),
991            FontSizeAdjust::IcHeight(ref val) => case!(IcHeight, val),
992        }
993    }
994}
995
996/// Use FontSettings as computed type of FontFeatureSettings.
997pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>;
998
999/// The computed value for font-variation-settings.
1000pub type FontVariationSettings = FontSettings<VariationValue<Number>>;
1001
1002// The computed value of font-{feature,variation}-settings discards values
1003// with duplicate tags, keeping only the last occurrence of each tag.
1004fn dedup_font_settings<T>(settings_list: &mut Vec<T>)
1005where
1006    T: TaggedFontValue,
1007{
1008    if settings_list.len() > 1 {
1009        settings_list.sort_by_key(|k| k.tag().0);
1010        // dedup() keeps the first of any duplicates, but we want the last,
1011        // so swap elements in the dedup_by closure if their tags are equal.
1012        settings_list.dedup_by(|a, b| {
1013            if a.tag() == b.tag() {
1014                std::mem::swap(a, b);
1015                true
1016            } else {
1017                false
1018            }
1019        });
1020    }
1021}
1022
1023impl<T> ToComputedValue for FontSettings<T>
1024where
1025    T: ToComputedValue,
1026    <T as ToComputedValue>::ComputedValue: TaggedFontValue,
1027{
1028    type ComputedValue = FontSettings<T::ComputedValue>;
1029
1030    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
1031        let mut v = self
1032            .0
1033            .iter()
1034            .map(|item| item.to_computed_value(context))
1035            .collect::<Vec<_>>();
1036        dedup_font_settings(&mut v);
1037        FontSettings(v.into_boxed_slice())
1038    }
1039
1040    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1041        Self(computed.0.iter().map(T::from_computed_value).collect())
1042    }
1043}
1044
1045/// font-language-override can only have a single 1-4 ASCII character
1046/// OpenType "language system" tag, so we should be able to compute
1047/// it and store it as a 32-bit integer
1048/// (see http://www.microsoft.com/typography/otspec/languagetags.htm).
1049#[derive(
1050    Clone,
1051    Copy,
1052    Debug,
1053    Deserialize,
1054    Eq,
1055    MallocSizeOf,
1056    PartialEq,
1057    Serialize,
1058    SpecifiedValueInfo,
1059    ToComputedValue,
1060    ToResolvedValue,
1061    ToShmem,
1062    ToTyped,
1063)]
1064#[repr(C)]
1065#[typed(todo_derive_fields)]
1066#[value_info(other_values = "normal")]
1067pub struct FontLanguageOverride(pub u32);
1068
1069impl FontLanguageOverride {
1070    #[inline]
1071    /// Get computed default value of `font-language-override` with 0
1072    pub fn normal() -> FontLanguageOverride {
1073        FontLanguageOverride(0)
1074    }
1075
1076    /// Returns this value as a `&str`, backed by `storage`.
1077    #[inline]
1078    pub(crate) fn to_str(self, storage: &mut [u8; 4]) -> &str {
1079        *storage = u32::to_be_bytes(self.0);
1080        // Safe because we ensure it's ASCII during parsing
1081        let slice = if cfg!(debug_assertions) {
1082            std::str::from_utf8(&storage[..]).unwrap()
1083        } else {
1084            unsafe { std::str::from_utf8_unchecked(&storage[..]) }
1085        };
1086        slice.trim_end()
1087    }
1088
1089    /// Unsafe because `Self::to_str` requires the value to represent a UTF-8
1090    /// string.
1091    #[inline]
1092    pub unsafe fn from_u32(value: u32) -> Self {
1093        Self(value)
1094    }
1095}
1096
1097impl ToCss for FontLanguageOverride {
1098    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1099    where
1100        W: fmt::Write,
1101    {
1102        if self.0 == 0 {
1103            return dest.write_str("normal");
1104        }
1105        self.to_str(&mut [0; 4]).to_css(dest)
1106    }
1107}
1108
1109impl ToComputedValue for specified::MozScriptMinSize {
1110    type ComputedValue = MozScriptMinSize;
1111
1112    fn to_computed_value(&self, cx: &Context) -> MozScriptMinSize {
1113        // this value is used in the computation of font-size, so
1114        // we use the parent size
1115        let base_size = FontBaseSize::InheritedStyle;
1116        let line_height_base = LineHeightBase::InheritedStyle;
1117        self.0
1118            .to_computed_value_with_base_size(cx, base_size, line_height_base)
1119    }
1120
1121    fn from_computed_value(other: &MozScriptMinSize) -> Self {
1122        specified::MozScriptMinSize(ToComputedValue::from_computed_value(other))
1123    }
1124}
1125
1126/// The computed value of the math-depth property.
1127pub type MathDepth = i8;
1128
1129impl ToComputedValue for specified::MathDepth {
1130    type ComputedValue = MathDepth;
1131
1132    fn to_computed_value(&self, cx: &Context) -> i8 {
1133        use crate::properties::longhands::math_style::SpecifiedValue as MathStyleValue;
1134
1135        let int = match self {
1136            specified::MathDepth::AutoAdd => {
1137                let parent = cx.builder.get_parent_font().clone_math_depth() as i32;
1138                let style = cx.builder.get_parent_font().clone_math_style();
1139                if style == MathStyleValue::Compact {
1140                    parent.saturating_add(1)
1141                } else {
1142                    parent
1143                }
1144            },
1145            specified::MathDepth::Add(rel) => {
1146                let parent = cx.builder.get_parent_font().clone_math_depth();
1147                (parent as i32).saturating_add(rel.to_computed_value(cx))
1148            },
1149            specified::MathDepth::Absolute(abs) => abs.to_computed_value(cx),
1150        };
1151        std::cmp::min(int, i8::MAX as i32) as i8
1152    }
1153
1154    fn from_computed_value(other: &i8) -> Self {
1155        let computed_value = *other as i32;
1156        specified::MathDepth::Absolute(SpecifiedInteger::from_computed_value(&computed_value))
1157    }
1158}
1159
1160impl ToAnimatedValue for MathDepth {
1161    type AnimatedValue = CSSInteger;
1162
1163    #[inline]
1164    fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue {
1165        self.into()
1166    }
1167
1168    #[inline]
1169    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
1170        std::cmp::min(animated, i8::MAX as i32) as i8
1171    }
1172}
1173
1174/// - Use a signed 8.8 fixed-point value (representable range -128.0..128)
1175///
1176/// Values of <angle> below -90 or above 90 are not permitted, so we use an out
1177/// of range value to represent `italic`.
1178pub const FONT_STYLE_FRACTION_BITS: u16 = 8;
1179
1180/// This is an alias which is useful mostly as a cbindgen / C++ inference
1181/// workaround.
1182pub type FontStyleFixedPoint = FixedPoint<i16, FONT_STYLE_FRACTION_BITS>;
1183
1184/// The computed value of `font-style`.
1185///
1186/// - Define angle of zero degrees as `normal`
1187/// - Define out-of-range value 100 degrees as `italic`
1188/// - Other values represent `oblique <angle>`
1189///
1190/// cbindgen:derive-lt
1191/// cbindgen:derive-lte
1192/// cbindgen:derive-gt
1193/// cbindgen:derive-gte
1194#[derive(
1195    Clone,
1196    ComputeSquaredDistance,
1197    Copy,
1198    Debug,
1199    Deserialize,
1200    Eq,
1201    Hash,
1202    MallocSizeOf,
1203    PartialEq,
1204    PartialOrd,
1205    Serialize,
1206    ToResolvedValue,
1207    ToTyped,
1208)]
1209#[repr(C)]
1210#[typed(todo_derive_fields)]
1211pub struct FontStyle(FontStyleFixedPoint);
1212
1213impl FontStyle {
1214    /// The `normal` keyword, equal to `oblique` with angle zero.
1215    pub const NORMAL: FontStyle = FontStyle(FontStyleFixedPoint {
1216        value: 0 << FONT_STYLE_FRACTION_BITS,
1217    });
1218
1219    /// The italic keyword.
1220    pub const ITALIC: FontStyle = FontStyle(FontStyleFixedPoint {
1221        value: 100 << FONT_STYLE_FRACTION_BITS,
1222    });
1223
1224    /// The default angle for `font-style: oblique`.
1225    /// See also https://github.com/w3c/csswg-drafts/issues/2295
1226    pub const DEFAULT_OBLIQUE_DEGREES: i16 = 14;
1227
1228    /// The `oblique` keyword with the default degrees.
1229    pub const OBLIQUE: FontStyle = FontStyle(FontStyleFixedPoint {
1230        value: Self::DEFAULT_OBLIQUE_DEGREES << FONT_STYLE_FRACTION_BITS,
1231    });
1232
1233    /// The `normal` value.
1234    #[inline]
1235    pub fn normal() -> Self {
1236        Self::NORMAL
1237    }
1238
1239    /// Returns the oblique angle for this style.
1240    pub fn oblique(degrees: f32) -> Self {
1241        Self(FixedPoint::from_float(
1242            degrees
1243                .max(specified::FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES)
1244                .min(specified::FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES),
1245        ))
1246    }
1247
1248    /// Returns the oblique angle for this style.
1249    pub fn oblique_degrees(&self) -> f32 {
1250        debug_assert_ne!(*self, Self::ITALIC);
1251        self.0.to_float()
1252    }
1253}
1254
1255impl ToCss for FontStyle {
1256    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1257    where
1258        W: fmt::Write,
1259    {
1260        if *self == Self::NORMAL {
1261            return dest.write_str("normal");
1262        }
1263        if *self == Self::ITALIC {
1264            return dest.write_str("italic");
1265        }
1266        dest.write_str("oblique")?;
1267        if *self != Self::OBLIQUE {
1268            // It's not the default oblique amount, so append the angle in degrees.
1269            dest.write_char(' ')?;
1270            Angle::from_degrees(self.oblique_degrees()).to_css(dest)?;
1271        }
1272        Ok(())
1273    }
1274}
1275
1276impl ToAnimatedValue for FontStyle {
1277    type AnimatedValue = generics::FontStyle<Angle>;
1278
1279    #[inline]
1280    fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue {
1281        if self == Self::ITALIC {
1282            return generics::FontStyle::Italic;
1283        }
1284        generics::FontStyle::Oblique(Angle::from_degrees(self.oblique_degrees()))
1285    }
1286
1287    #[inline]
1288    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
1289        match animated {
1290            generics::FontStyle::Italic => Self::ITALIC,
1291            generics::FontStyle::Oblique(ref angle) => Self::oblique(angle.degrees()),
1292        }
1293    }
1294}
1295
1296/// font-width is a percentage relative to normal.
1297///
1298/// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375)
1299///
1300/// We arbitrarily limit here to 1000%. (If that becomes a problem, we could
1301/// reduce the number of fractional bits and increase the limit.)
1302pub const FONT_WIDTH_FRACTION_BITS: u16 = 6;
1303
1304/// This is an alias which is useful mostly as a cbindgen / C++ inference
1305/// workaround.
1306pub type FontWidthFixedPoint = FixedPoint<u16, FONT_WIDTH_FRACTION_BITS>;
1307
1308/// A value for the font-width property per:
1309///
1310/// https://drafts.csswg.org/css-fonts-4/#propdef-font-width
1311///
1312/// (Note that this property was formerly named font-stretch.)
1313///
1314/// cbindgen:derive-lt
1315/// cbindgen:derive-lte
1316/// cbindgen:derive-gt
1317/// cbindgen:derive-gte
1318#[derive(
1319    Clone,
1320    ComputeSquaredDistance,
1321    Copy,
1322    Debug,
1323    Deserialize,
1324    Hash,
1325    MallocSizeOf,
1326    PartialEq,
1327    PartialOrd,
1328    Serialize,
1329    ToResolvedValue,
1330)]
1331#[repr(C)]
1332pub struct FontWidth(pub FontWidthFixedPoint);
1333
1334impl FontWidth {
1335    /// The fraction bits, as an easy-to-access-constant.
1336    pub const FRACTION_BITS: u16 = FONT_WIDTH_FRACTION_BITS;
1337    /// 0.5 in our floating point representation.
1338    pub const HALF: u16 = 1 << (Self::FRACTION_BITS - 1);
1339
1340    /// The `ultra-condensed` keyword.
1341    pub const ULTRA_CONDENSED: FontWidth = FontWidth(FontWidthFixedPoint {
1342        value: 50 << Self::FRACTION_BITS,
1343    });
1344    /// The `extra-condensed` keyword.
1345    pub const EXTRA_CONDENSED: FontWidth = FontWidth(FontWidthFixedPoint {
1346        value: (62 << Self::FRACTION_BITS) + Self::HALF,
1347    });
1348    /// The `condensed` keyword.
1349    pub const CONDENSED: FontWidth = FontWidth(FontWidthFixedPoint {
1350        value: 75 << Self::FRACTION_BITS,
1351    });
1352    /// The `semi-condensed` keyword.
1353    pub const SEMI_CONDENSED: FontWidth = FontWidth(FontWidthFixedPoint {
1354        value: (87 << Self::FRACTION_BITS) + Self::HALF,
1355    });
1356    /// The `normal` keyword.
1357    pub const NORMAL: FontWidth = FontWidth(FontWidthFixedPoint {
1358        value: 100 << Self::FRACTION_BITS,
1359    });
1360    /// The `semi-expanded` keyword.
1361    pub const SEMI_EXPANDED: FontWidth = FontWidth(FontWidthFixedPoint {
1362        value: (112 << Self::FRACTION_BITS) + Self::HALF,
1363    });
1364    /// The `expanded` keyword.
1365    pub const EXPANDED: FontWidth = FontWidth(FontWidthFixedPoint {
1366        value: 125 << Self::FRACTION_BITS,
1367    });
1368    /// The `extra-expanded` keyword.
1369    pub const EXTRA_EXPANDED: FontWidth = FontWidth(FontWidthFixedPoint {
1370        value: 150 << Self::FRACTION_BITS,
1371    });
1372    /// The `ultra-expanded` keyword.
1373    pub const ULTRA_EXPANDED: FontWidth = FontWidth(FontWidthFixedPoint {
1374        value: 200 << Self::FRACTION_BITS,
1375    });
1376
1377    /// 100%
1378    pub fn hundred() -> Self {
1379        Self::NORMAL
1380    }
1381
1382    /// Converts to a computed percentage.
1383    #[inline]
1384    pub fn to_percentage(&self) -> Percentage {
1385        Percentage(self.0.to_float() / 100.0)
1386    }
1387
1388    /// Converts from a computed percentage value.
1389    pub fn from_percentage(p: f32) -> Self {
1390        Self(FixedPoint::from_float((p * 100.).max(0.0).min(1000.0)))
1391    }
1392
1393    /// Returns a relevant width value from a keyword.
1394    /// https://drafts.csswg.org/css-fonts-4/#font-width-prop
1395    pub fn from_keyword(kw: specified::FontWidthKeyword) -> Self {
1396        use specified::FontWidthKeyword::*;
1397        match kw {
1398            UltraCondensed => Self::ULTRA_CONDENSED,
1399            ExtraCondensed => Self::EXTRA_CONDENSED,
1400            Condensed => Self::CONDENSED,
1401            SemiCondensed => Self::SEMI_CONDENSED,
1402            Normal => Self::NORMAL,
1403            SemiExpanded => Self::SEMI_EXPANDED,
1404            Expanded => Self::EXPANDED,
1405            ExtraExpanded => Self::EXTRA_EXPANDED,
1406            UltraExpanded => Self::ULTRA_EXPANDED,
1407        }
1408    }
1409
1410    /// Returns the width keyword if we map to one of the relevant values.
1411    pub fn as_keyword(&self) -> Option<specified::FontWidthKeyword> {
1412        use specified::FontWidthKeyword::*;
1413        // TODO: Can we use match here?
1414        if *self == Self::ULTRA_CONDENSED {
1415            return Some(UltraCondensed);
1416        }
1417        if *self == Self::EXTRA_CONDENSED {
1418            return Some(ExtraCondensed);
1419        }
1420        if *self == Self::CONDENSED {
1421            return Some(Condensed);
1422        }
1423        if *self == Self::SEMI_CONDENSED {
1424            return Some(SemiCondensed);
1425        }
1426        if *self == Self::NORMAL {
1427            return Some(Normal);
1428        }
1429        if *self == Self::SEMI_EXPANDED {
1430            return Some(SemiExpanded);
1431        }
1432        if *self == Self::EXPANDED {
1433            return Some(Expanded);
1434        }
1435        if *self == Self::EXTRA_EXPANDED {
1436            return Some(ExtraExpanded);
1437        }
1438        if *self == Self::ULTRA_EXPANDED {
1439            return Some(UltraExpanded);
1440        }
1441        None
1442    }
1443}
1444
1445impl ToCss for FontWidth {
1446    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1447    where
1448        W: fmt::Write,
1449    {
1450        self.to_percentage().to_css(dest)
1451    }
1452}
1453
1454impl ToTyped for FontWidth {
1455    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
1456        match self.as_keyword() {
1457            Some(keyword) => keyword.to_typed(dest),
1458            None => self.to_percentage().to_typed(dest),
1459        }
1460    }
1461}
1462
1463impl ToAnimatedValue for FontWidth {
1464    type AnimatedValue = Percentage;
1465
1466    #[inline]
1467    fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue {
1468        self.to_percentage()
1469    }
1470
1471    #[inline]
1472    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
1473        Self::from_percentage(animated.0)
1474    }
1475}
1476
1477/// A computed value for the `line-height` property.
1478pub type LineHeight = generics::GenericLineHeight<NonNegativeNumber, NonNegativeLength>;
1479
1480impl ToResolvedValue for LineHeight {
1481    type ResolvedValue = Self;
1482
1483    fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue {
1484        #[cfg(feature = "gecko")]
1485        {
1486            // Resolve <number> to an absolute <length> based on font size.
1487            if matches!(self, Self::Normal) {
1488                return self;
1489            }
1490            let wm = context.style.writing_mode;
1491            Self::Length(
1492                context
1493                    .device
1494                    .calc_line_height(
1495                        context.style.get_font(),
1496                        wm,
1497                        Some(context.element_info.element),
1498                    )
1499                    .to_resolved_value(context),
1500            )
1501        }
1502        #[cfg(feature = "servo")]
1503        {
1504            if let LineHeight::Number(num) = &self {
1505                let size = context.style.get_font().clone_font_size().computed_size();
1506                LineHeight::Length(NonNegativeLength::new(size.px() * num.0))
1507            } else {
1508                self
1509            }
1510        }
1511    }
1512
1513    #[inline]
1514    fn from_resolved_value(value: Self::ResolvedValue) -> Self {
1515        value
1516    }
1517}