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