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