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