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