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