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