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