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