Skip to main content

style/values/computed/
font.rs

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