style/values/specified/
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//! Specified values for font properties
6
7use crate::context::QuirksMode;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::values::computed::font::{FamilyName, FontFamilyList, SingleFontFamily};
11use crate::values::computed::Percentage as ComputedPercentage;
12use crate::values::computed::{font as computed, Length, NonNegativeLength};
13use crate::values::computed::{CSSPixelLength, Context, ToComputedValue};
14use crate::values::generics::font::{
15    self as generics, FeatureTagValue, FontSettings, FontTag, GenericLineHeight, VariationValue,
16};
17use crate::values::generics::NonNegative;
18use crate::values::specified::length::{FontBaseSize, LineHeightBase, PX_PER_PT};
19use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage};
20use crate::values::specified::{
21    FontRelativeLength, NoCalcLength, NonNegativeLengthPercentage, NonNegativeNumber,
22    NonNegativePercentage, Number,
23};
24use crate::values::{serialize_atom_identifier, CustomIdent, SelectorParseErrorKind};
25use crate::Atom;
26use cssparser::{match_ignore_ascii_case, Parser, Token};
27#[cfg(feature = "gecko")]
28use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
29use std::fmt::{self, Write};
30use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
31use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
32
33// FIXME(emilio): The system font code is copy-pasta, and should be cleaned up.
34macro_rules! system_font_methods {
35    ($ty:ident, $field:ident) => {
36        system_font_methods!($ty);
37
38        fn compute_system(&self, _context: &Context) -> <$ty as ToComputedValue>::ComputedValue {
39            debug_assert!(matches!(*self, $ty::System(..)));
40            #[cfg(feature = "gecko")]
41            {
42                _context.cached_system_font.as_ref().unwrap().$field.clone()
43            }
44            #[cfg(feature = "servo")]
45            {
46                unreachable!()
47            }
48        }
49    };
50
51    ($ty:ident) => {
52        /// Get a specified value that represents a system font.
53        pub fn system_font(f: SystemFont) -> Self {
54            $ty::System(f)
55        }
56
57        /// Retreive a SystemFont from the specified value.
58        pub fn get_system(&self) -> Option<SystemFont> {
59            if let $ty::System(s) = *self {
60                Some(s)
61            } else {
62                None
63            }
64        }
65    };
66}
67
68/// System fonts.
69#[repr(u8)]
70#[derive(
71    Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
72)]
73#[allow(missing_docs)]
74#[cfg(feature = "gecko")]
75pub enum SystemFont {
76    /// https://drafts.csswg.org/css-fonts/#valdef-font-caption
77    Caption,
78    /// https://drafts.csswg.org/css-fonts/#valdef-font-icon
79    Icon,
80    /// https://drafts.csswg.org/css-fonts/#valdef-font-menu
81    Menu,
82    /// https://drafts.csswg.org/css-fonts/#valdef-font-message-box
83    MessageBox,
84    /// https://drafts.csswg.org/css-fonts/#valdef-font-small-caption
85    SmallCaption,
86    /// https://drafts.csswg.org/css-fonts/#valdef-font-status-bar
87    StatusBar,
88    /// Internal system font, used by the `<menupopup>`s on macOS.
89    #[parse(condition = "ParserContext::chrome_rules_enabled")]
90    MozPullDownMenu,
91    /// Internal system font, used for `<button>` elements.
92    #[parse(condition = "ParserContext::chrome_rules_enabled")]
93    MozButton,
94    /// Internal font, used by `<select>` elements.
95    #[parse(condition = "ParserContext::chrome_rules_enabled")]
96    MozList,
97    /// Internal font, used by `<input>` elements.
98    #[parse(condition = "ParserContext::chrome_rules_enabled")]
99    MozField,
100    #[css(skip)]
101    End, // Just for indexing purposes.
102}
103
104// We don't parse system fonts in servo, but in the interest of not
105// littering a lot of code with `if engine == "gecko"` conditionals,
106// we have a dummy system font module that does nothing
107
108#[derive(
109    Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
110)]
111#[allow(missing_docs)]
112#[cfg(feature = "servo")]
113/// void enum for system font, can never exist
114pub enum SystemFont {}
115
116#[allow(missing_docs)]
117#[cfg(feature = "servo")]
118impl SystemFont {
119    pub fn parse(_: &mut Parser) -> Result<Self, ()> {
120        Err(())
121    }
122}
123
124const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8;
125const DEFAULT_SCRIPT_SIZE_MULTIPLIER: f64 = 0.71;
126
127/// The minimum font-weight value per:
128///
129/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
130pub const MIN_FONT_WEIGHT: f32 = 1.;
131
132/// The maximum font-weight value per:
133///
134/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
135pub const MAX_FONT_WEIGHT: f32 = 1000.;
136
137/// A specified font-weight value.
138///
139/// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight
140#[derive(
141    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
142)]
143pub enum FontWeight {
144    /// `<font-weight-absolute>`
145    Absolute(AbsoluteFontWeight),
146    /// Bolder variant
147    Bolder,
148    /// Lighter variant
149    Lighter,
150    /// System font variant.
151    #[css(skip)]
152    System(SystemFont),
153}
154
155impl FontWeight {
156    system_font_methods!(FontWeight, font_weight);
157
158    /// `normal`
159    #[inline]
160    pub fn normal() -> Self {
161        FontWeight::Absolute(AbsoluteFontWeight::Normal)
162    }
163
164    /// Get a specified FontWeight from a gecko keyword
165    pub fn from_gecko_keyword(kw: u32) -> Self {
166        debug_assert!(kw % 100 == 0);
167        debug_assert!(kw as f32 <= MAX_FONT_WEIGHT);
168        FontWeight::Absolute(AbsoluteFontWeight::Weight(Number::new(kw as f32)))
169    }
170}
171
172impl ToComputedValue for FontWeight {
173    type ComputedValue = computed::FontWeight;
174
175    #[inline]
176    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
177        match *self {
178            FontWeight::Absolute(ref abs) => abs.compute(),
179            FontWeight::Bolder => context
180                .builder
181                .get_parent_font()
182                .clone_font_weight()
183                .bolder(),
184            FontWeight::Lighter => context
185                .builder
186                .get_parent_font()
187                .clone_font_weight()
188                .lighter(),
189            FontWeight::System(_) => self.compute_system(context),
190        }
191    }
192
193    #[inline]
194    fn from_computed_value(computed: &computed::FontWeight) -> Self {
195        FontWeight::Absolute(AbsoluteFontWeight::Weight(Number::from_computed_value(
196            &computed.value(),
197        )))
198    }
199}
200
201/// An absolute font-weight value for a @font-face rule.
202///
203/// https://drafts.csswg.org/css-fonts-4/#font-weight-absolute-values
204#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
205pub enum AbsoluteFontWeight {
206    /// A `<number>`, with the additional constraints specified in:
207    ///
208    ///   https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values
209    Weight(Number),
210    /// Normal font weight. Same as 400.
211    Normal,
212    /// Bold font weight. Same as 700.
213    Bold,
214}
215
216impl AbsoluteFontWeight {
217    /// Returns the computed value for this absolute font weight.
218    pub fn compute(&self) -> computed::FontWeight {
219        match *self {
220            AbsoluteFontWeight::Weight(weight) => computed::FontWeight::from_float(weight.get()),
221            AbsoluteFontWeight::Normal => computed::FontWeight::NORMAL,
222            AbsoluteFontWeight::Bold => computed::FontWeight::BOLD,
223        }
224    }
225}
226
227impl Parse for AbsoluteFontWeight {
228    fn parse<'i, 't>(
229        context: &ParserContext,
230        input: &mut Parser<'i, 't>,
231    ) -> Result<Self, ParseError<'i>> {
232        if let Ok(number) = input.try_parse(|input| Number::parse(context, input)) {
233            // We could add another AllowedNumericType value, but it doesn't
234            // seem worth it just for a single property with such a weird range,
235            // so we do the clamping here manually.
236            if !number.was_calc()
237                && (number.get() < MIN_FONT_WEIGHT || number.get() > MAX_FONT_WEIGHT)
238            {
239                return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
240            }
241            return Ok(AbsoluteFontWeight::Weight(number));
242        }
243
244        Ok(try_match_ident_ignore_ascii_case! { input,
245            "normal" => AbsoluteFontWeight::Normal,
246            "bold" => AbsoluteFontWeight::Bold,
247        })
248    }
249}
250
251/// The specified value of the `font-style` property, without the system font
252/// crap.
253pub type SpecifiedFontStyle = generics::FontStyle<Angle>;
254
255impl ToCss for SpecifiedFontStyle {
256    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
257    where
258        W: Write,
259    {
260        match *self {
261            generics::FontStyle::Italic => dest.write_str("italic"),
262            generics::FontStyle::Oblique(ref angle) => {
263                // Not angle.is_zero() because we don't want to serialize
264                // `oblique calc(0deg)` as `normal`.
265                if *angle == Angle::zero() {
266                    dest.write_str("normal")?;
267                } else {
268                    dest.write_str("oblique")?;
269                    if *angle != Self::default_angle() {
270                        dest.write_char(' ')?;
271                        angle.to_css(dest)?;
272                    }
273                }
274                Ok(())
275            },
276        }
277    }
278}
279
280impl Parse for SpecifiedFontStyle {
281    fn parse<'i, 't>(
282        context: &ParserContext,
283        input: &mut Parser<'i, 't>,
284    ) -> Result<Self, ParseError<'i>> {
285        Ok(try_match_ident_ignore_ascii_case! { input,
286            "normal" => generics::FontStyle::normal(),
287            "italic" => generics::FontStyle::Italic,
288            "oblique" => {
289                let angle = input.try_parse(|input| Self::parse_angle(context, input))
290                    .unwrap_or_else(|_| Self::default_angle());
291
292                generics::FontStyle::Oblique(angle)
293            },
294        })
295    }
296}
297
298impl ToComputedValue for SpecifiedFontStyle {
299    type ComputedValue = computed::FontStyle;
300
301    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
302        match *self {
303            Self::Italic => computed::FontStyle::ITALIC,
304            Self::Oblique(ref angle) => computed::FontStyle::oblique(angle.degrees()),
305        }
306    }
307
308    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
309        if *computed == computed::FontStyle::ITALIC {
310            return Self::Italic;
311        }
312        let degrees = computed.oblique_degrees();
313        generics::FontStyle::Oblique(Angle::from_degrees(degrees, /* was_calc = */ false))
314    }
315}
316
317/// From https://drafts.csswg.org/css-fonts-4/#valdef-font-style-oblique-angle:
318///
319///     Values less than -90deg or values greater than 90deg are
320///     invalid and are treated as parse errors.
321///
322/// The maximum angle value that `font-style: oblique` should compute to.
323pub const FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES: f32 = 90.;
324
325/// The minimum angle value that `font-style: oblique` should compute to.
326pub const FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES: f32 = -90.;
327
328impl SpecifiedFontStyle {
329    /// Gets a clamped angle in degrees from a specified Angle.
330    pub fn compute_angle_degrees(angle: &Angle) -> f32 {
331        angle
332            .degrees()
333            .max(FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES)
334            .min(FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES)
335    }
336
337    /// Parse a suitable angle for font-style: oblique.
338    pub fn parse_angle<'i, 't>(
339        context: &ParserContext,
340        input: &mut Parser<'i, 't>,
341    ) -> Result<Angle, ParseError<'i>> {
342        let angle = Angle::parse(context, input)?;
343        if angle.was_calc() {
344            return Ok(angle);
345        }
346
347        let degrees = angle.degrees();
348        if degrees < FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES
349            || degrees > FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES
350        {
351            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
352        }
353        return Ok(angle);
354    }
355
356    /// The default angle for `font-style: oblique`.
357    pub fn default_angle() -> Angle {
358        Angle::from_degrees(
359            computed::FontStyle::DEFAULT_OBLIQUE_DEGREES as f32,
360            /* was_calc = */ false,
361        )
362    }
363}
364
365/// The specified value of the `font-style` property.
366#[derive(
367    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
368)]
369#[allow(missing_docs)]
370pub enum FontStyle {
371    Specified(SpecifiedFontStyle),
372    #[css(skip)]
373    System(SystemFont),
374}
375
376impl FontStyle {
377    /// Return the `normal` value.
378    #[inline]
379    pub fn normal() -> Self {
380        FontStyle::Specified(generics::FontStyle::normal())
381    }
382
383    system_font_methods!(FontStyle, font_style);
384}
385
386impl ToComputedValue for FontStyle {
387    type ComputedValue = computed::FontStyle;
388
389    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
390        match *self {
391            FontStyle::Specified(ref specified) => specified.to_computed_value(context),
392            FontStyle::System(..) => self.compute_system(context),
393        }
394    }
395
396    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
397        FontStyle::Specified(SpecifiedFontStyle::from_computed_value(computed))
398    }
399}
400
401/// A value for the `font-stretch` property.
402///
403/// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop
404#[allow(missing_docs)]
405#[derive(
406    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
407)]
408pub enum FontStretch {
409    Stretch(NonNegativePercentage),
410    Keyword(FontStretchKeyword),
411    #[css(skip)]
412    System(SystemFont),
413}
414
415/// A keyword value for `font-stretch`.
416#[derive(
417    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
418)]
419#[allow(missing_docs)]
420pub enum FontStretchKeyword {
421    Normal,
422    Condensed,
423    UltraCondensed,
424    ExtraCondensed,
425    SemiCondensed,
426    SemiExpanded,
427    Expanded,
428    ExtraExpanded,
429    UltraExpanded,
430}
431
432impl FontStretchKeyword {
433    /// Turns the keyword into a computed value.
434    pub fn compute(&self) -> computed::FontStretch {
435        computed::FontStretch::from_keyword(*self)
436    }
437
438    /// Does the opposite operation to `compute`, in order to serialize keywords
439    /// if possible.
440    pub fn from_percentage(p: f32) -> Option<Self> {
441        computed::FontStretch::from_percentage(p).as_keyword()
442    }
443}
444
445impl FontStretch {
446    /// `normal`.
447    pub fn normal() -> Self {
448        FontStretch::Keyword(FontStretchKeyword::Normal)
449    }
450
451    system_font_methods!(FontStretch, font_stretch);
452}
453
454impl ToComputedValue for FontStretch {
455    type ComputedValue = computed::FontStretch;
456
457    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
458        match *self {
459            FontStretch::Stretch(ref percentage) => {
460                let percentage = percentage.to_computed_value(context).0;
461                computed::FontStretch::from_percentage(percentage.0)
462            },
463            FontStretch::Keyword(ref kw) => kw.compute(),
464            FontStretch::System(_) => self.compute_system(context),
465        }
466    }
467
468    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
469        FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative(
470            computed.to_percentage(),
471        )))
472    }
473}
474
475/// CSS font keywords
476#[derive(
477    Animate,
478    Clone,
479    ComputeSquaredDistance,
480    Copy,
481    Debug,
482    MallocSizeOf,
483    Parse,
484    PartialEq,
485    SpecifiedValueInfo,
486    ToAnimatedValue,
487    ToAnimatedZero,
488    ToComputedValue,
489    ToCss,
490    ToResolvedValue,
491    ToShmem,
492    Serialize,
493    Deserialize,
494)]
495#[allow(missing_docs)]
496#[repr(u8)]
497pub enum FontSizeKeyword {
498    #[css(keyword = "xx-small")]
499    XXSmall,
500    XSmall,
501    Small,
502    Medium,
503    Large,
504    XLarge,
505    #[css(keyword = "xx-large")]
506    XXLarge,
507    #[css(keyword = "xxx-large")]
508    XXXLarge,
509    /// Indicate whether to apply font-size: math is specified so that extra
510    /// scaling due to math-depth changes is applied during the cascade.
511    #[cfg(feature = "gecko")]
512    Math,
513    #[css(skip)]
514    None,
515}
516
517impl FontSizeKeyword {
518    /// Convert to an HTML <font size> value
519    #[inline]
520    pub fn html_size(self) -> u8 {
521        self as u8
522    }
523
524    /// Returns true if the font size is the math keyword
525    #[cfg(feature = "gecko")]
526    pub fn is_math(self) -> bool {
527        matches!(self, Self::Math)
528    }
529
530    /// Returns true if the font size is the math keyword
531    #[cfg(feature = "servo")]
532    pub fn is_math(self) -> bool {
533        false
534    }
535}
536
537impl Default for FontSizeKeyword {
538    fn default() -> Self {
539        FontSizeKeyword::Medium
540    }
541}
542
543#[derive(
544    Animate,
545    Clone,
546    ComputeSquaredDistance,
547    Copy,
548    Debug,
549    MallocSizeOf,
550    PartialEq,
551    ToAnimatedValue,
552    ToAnimatedZero,
553    ToComputedValue,
554    ToCss,
555    ToResolvedValue,
556    ToShmem,
557)]
558#[cfg_attr(feature = "servo", derive(Serialize, Deserialize))]
559/// Additional information for keyword-derived font sizes.
560pub struct KeywordInfo {
561    /// The keyword used
562    pub kw: FontSizeKeyword,
563    /// A factor to be multiplied by the computed size of the keyword
564    #[css(skip)]
565    pub factor: f32,
566    /// An additional fixed offset to add to the kw * factor in the case of
567    /// `calc()`.
568    #[css(skip)]
569    pub offset: CSSPixelLength,
570}
571
572impl KeywordInfo {
573    /// KeywordInfo value for font-size: medium
574    pub fn medium() -> Self {
575        Self::new(FontSizeKeyword::Medium)
576    }
577
578    /// KeywordInfo value for font-size: none
579    pub fn none() -> Self {
580        Self::new(FontSizeKeyword::None)
581    }
582
583    fn new(kw: FontSizeKeyword) -> Self {
584        KeywordInfo {
585            kw,
586            factor: 1.,
587            offset: CSSPixelLength::new(0.),
588        }
589    }
590
591    /// Computes the final size for this font-size keyword, accounting for
592    /// text-zoom.
593    fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
594        debug_assert_ne!(self.kw, FontSizeKeyword::None);
595        #[cfg(feature = "gecko")]
596        debug_assert_ne!(self.kw, FontSizeKeyword::Math);
597        let base = context.maybe_zoom_text(self.kw.to_length(context).0);
598        let zoom_factor = context.style().effective_zoom.value();
599        CSSPixelLength::new(base.px() * self.factor * zoom_factor)
600            + context.maybe_zoom_text(self.offset)
601    }
602
603    /// Given a parent keyword info (self), apply an additional factor/offset to
604    /// it.
605    fn compose(self, factor: f32) -> Self {
606        if self.kw == FontSizeKeyword::None {
607            return self;
608        }
609        KeywordInfo {
610            kw: self.kw,
611            factor: self.factor * factor,
612            offset: self.offset * factor,
613        }
614    }
615}
616
617impl SpecifiedValueInfo for KeywordInfo {
618    fn collect_completion_keywords(f: KeywordsCollectFn) {
619        <FontSizeKeyword as SpecifiedValueInfo>::collect_completion_keywords(f);
620    }
621}
622
623#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
624/// A specified font-size value
625pub enum FontSize {
626    /// A length; e.g. 10px.
627    Length(LengthPercentage),
628    /// A keyword value, along with a ratio and absolute offset.
629    /// The ratio in any specified keyword value
630    /// will be 1 (with offset 0), but we cascade keywordness even
631    /// after font-relative (percent and em) values
632    /// have been applied, which is where the ratio
633    /// comes in. The offset comes in if we cascaded a calc value,
634    /// where the font-relative portion (em and percentage) will
635    /// go into the ratio, and the remaining units all computed together
636    /// will go into the offset.
637    /// See bug 1355707.
638    Keyword(KeywordInfo),
639    /// font-size: smaller
640    Smaller,
641    /// font-size: larger
642    Larger,
643    /// Derived from a specified system font.
644    #[css(skip)]
645    System(SystemFont),
646}
647
648/// Specifies a prioritized list of font family names or generic family names.
649#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem, ToTyped)]
650#[cfg_attr(feature = "servo", derive(Hash))]
651pub enum FontFamily {
652    /// List of `font-family`
653    #[css(comma)]
654    Values(#[css(iterable)] FontFamilyList),
655    /// System font
656    #[css(skip)]
657    System(SystemFont),
658}
659
660impl FontFamily {
661    system_font_methods!(FontFamily, font_family);
662}
663
664impl ToComputedValue for FontFamily {
665    type ComputedValue = computed::FontFamily;
666
667    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
668        match *self {
669            FontFamily::Values(ref list) => computed::FontFamily {
670                families: list.clone(),
671                is_system_font: false,
672                is_initial: false,
673            },
674            FontFamily::System(_) => self.compute_system(context),
675        }
676    }
677
678    fn from_computed_value(other: &computed::FontFamily) -> Self {
679        FontFamily::Values(other.families.clone())
680    }
681}
682
683#[cfg(feature = "gecko")]
684impl MallocSizeOf for FontFamily {
685    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
686        match *self {
687            FontFamily::Values(ref v) => {
688                // Although the family list is refcounted, we always attribute
689                // its size to the specified value.
690                v.list.unconditional_size_of(ops)
691            },
692            FontFamily::System(_) => 0,
693        }
694    }
695}
696
697impl Parse for FontFamily {
698    /// <family-name>#
699    /// <family-name> = <string> | [ <ident>+ ]
700    /// TODO: <generic-family>
701    fn parse<'i, 't>(
702        context: &ParserContext,
703        input: &mut Parser<'i, 't>,
704    ) -> Result<FontFamily, ParseError<'i>> {
705        let values =
706            input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?;
707        Ok(FontFamily::Values(FontFamilyList {
708            list: crate::ArcSlice::from_iter(values.into_iter()),
709        }))
710    }
711}
712
713impl SpecifiedValueInfo for FontFamily {}
714
715/// `FamilyName::parse` is based on `SingleFontFamily::parse` and not the other
716/// way around because we want the former to exclude generic family keywords.
717impl Parse for FamilyName {
718    fn parse<'i, 't>(
719        context: &ParserContext,
720        input: &mut Parser<'i, 't>,
721    ) -> Result<Self, ParseError<'i>> {
722        match SingleFontFamily::parse(context, input) {
723            Ok(SingleFontFamily::FamilyName(name)) => Ok(name),
724            Ok(SingleFontFamily::Generic(_)) => {
725                Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
726            },
727            Err(e) => Err(e),
728        }
729    }
730}
731
732/// A factor for one of the font-size-adjust metrics, which may be either a number
733/// or the `from-font` keyword.
734#[derive(
735    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
736)]
737pub enum FontSizeAdjustFactor {
738    /// An explicitly-specified number.
739    Number(NonNegativeNumber),
740    /// The from-font keyword: resolve the number from font metrics.
741    FromFont,
742}
743
744/// Specified value for font-size-adjust, intended to help
745/// preserve the readability of text when font fallback occurs.
746///
747/// https://drafts.csswg.org/css-fonts-5/#font-size-adjust-prop
748pub type FontSizeAdjust = generics::GenericFontSizeAdjust<FontSizeAdjustFactor>;
749
750impl Parse for FontSizeAdjust {
751    fn parse<'i, 't>(
752        context: &ParserContext,
753        input: &mut Parser<'i, 't>,
754    ) -> Result<Self, ParseError<'i>> {
755        let location = input.current_source_location();
756        // First check if we have an adjustment factor without a metrics-basis keyword.
757        if let Ok(factor) = input.try_parse(|i| FontSizeAdjustFactor::parse(context, i)) {
758            return Ok(Self::ExHeight(factor));
759        }
760
761        let ident = input.expect_ident()?;
762        let basis = match_ignore_ascii_case! { &ident,
763            "none" => return Ok(Self::None),
764            // Check for size adjustment basis keywords.
765            "ex-height" => Self::ExHeight,
766            "cap-height" => Self::CapHeight,
767            "ch-width" => Self::ChWidth,
768            "ic-width" => Self::IcWidth,
769            "ic-height" => Self::IcHeight,
770            // Unknown keyword.
771            _ => return Err(location.new_custom_error(
772                SelectorParseErrorKind::UnexpectedIdent(ident.clone())
773            )),
774        };
775
776        Ok(basis(FontSizeAdjustFactor::parse(context, input)?))
777    }
778}
779
780/// This is the ratio applied for font-size: larger
781/// and smaller by both Firefox and Chrome
782const LARGER_FONT_SIZE_RATIO: f32 = 1.2;
783
784/// The default font size.
785pub const FONT_MEDIUM_PX: f32 = 16.0;
786/// The default line height.
787pub const FONT_MEDIUM_LINE_HEIGHT_PX: f32 = FONT_MEDIUM_PX * 1.2;
788/// The default ex height -- https://drafts.csswg.org/css-values/#ex
789/// > In the cases where it is impossible or impractical to determine the x-height, a value of 0.5em must be assumed
790pub const FONT_MEDIUM_EX_PX: f32 = FONT_MEDIUM_PX * 0.5;
791/// The default cap height -- https://drafts.csswg.org/css-values/#cap
792/// > In the cases where it is impossible or impractical to determine the cap-height, the font’s ascent must be used
793pub const FONT_MEDIUM_CAP_PX: f32 = FONT_MEDIUM_PX;
794/// The default advance measure -- https://drafts.csswg.org/css-values/#ch
795/// > Thus, the ch unit falls back to 0.5em in the general case
796pub const FONT_MEDIUM_CH_PX: f32 = FONT_MEDIUM_PX * 0.5;
797/// The default idographic advance measure -- https://drafts.csswg.org/css-values/#ic
798/// > In the cases where it is impossible or impractical to determine the ideographic advance measure, it must be assumed to be 1em
799pub const FONT_MEDIUM_IC_PX: f32 = FONT_MEDIUM_PX;
800
801impl FontSizeKeyword {
802    #[inline]
803    fn to_length(&self, cx: &Context) -> NonNegativeLength {
804        let font = cx.style().get_font();
805
806        #[cfg(feature = "servo")]
807        let family = &font.font_family.families;
808        #[cfg(feature = "gecko")]
809        let family = &font.mFont.family.families;
810
811        let generic = family
812            .single_generic()
813            .unwrap_or(computed::GenericFontFamily::None);
814
815        #[cfg(feature = "gecko")]
816        let base_size = unsafe {
817            Atom::with(font.mLanguage.mRawPtr, |language| {
818                cx.device().base_size_for_generic(language, generic)
819            })
820        };
821        #[cfg(feature = "servo")]
822        let base_size = cx.device().base_size_for_generic(generic);
823
824        self.to_length_without_context(cx.quirks_mode, base_size)
825    }
826
827    /// Resolve a keyword length without any context, with explicit arguments.
828    #[inline]
829    pub fn to_length_without_context(
830        &self,
831        quirks_mode: QuirksMode,
832        base_size: Length,
833    ) -> NonNegativeLength {
834        #[cfg(feature = "gecko")]
835        debug_assert_ne!(*self, FontSizeKeyword::Math);
836        // The tables in this function are originally from
837        // nsRuleNode::CalcFontPointSize in Gecko:
838        //
839        // https://searchfox.org/mozilla-central/rev/c05d9d61188d32b8/layout/style/nsRuleNode.cpp#3150
840        //
841        // Mapping from base size and HTML size to pixels
842        // The first index is (base_size - 9), the second is the
843        // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
844        // since HTML size 0 is the same as 1.
845        //
846        //  xxs   xs      s      m     l      xl     xxl   -
847        //  -     0/1     2      3     4      5      6     7
848        static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
849            [9, 9, 9, 9, 11, 14, 18, 27],
850            [9, 9, 9, 10, 12, 15, 20, 30],
851            [9, 9, 10, 11, 13, 17, 22, 33],
852            [9, 9, 10, 12, 14, 18, 24, 36],
853            [9, 10, 12, 13, 16, 20, 26, 39],
854            [9, 10, 12, 14, 17, 21, 28, 42],
855            [9, 10, 13, 15, 18, 23, 30, 45],
856            [9, 10, 13, 16, 18, 24, 32, 48],
857        ];
858
859        // This table gives us compatibility with WinNav4 for the default fonts only.
860        // In WinNav4, the default fonts were:
861        //
862        //     Times/12pt ==   Times/16px at 96ppi
863        //   Courier/10pt == Courier/13px at 96ppi
864        //
865        // xxs   xs     s      m      l     xl     xxl    -
866        // -     1      2      3      4     5      6      7
867        static QUIRKS_FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
868            [9, 9, 9, 9, 11, 14, 18, 28],
869            [9, 9, 9, 10, 12, 15, 20, 31],
870            [9, 9, 9, 11, 13, 17, 22, 34],
871            [9, 9, 10, 12, 14, 18, 24, 37],
872            [9, 9, 10, 13, 16, 20, 26, 40],
873            [9, 9, 11, 14, 17, 21, 28, 42],
874            [9, 10, 12, 15, 17, 23, 30, 45],
875            [9, 10, 13, 16, 18, 24, 32, 48],
876        ];
877
878        static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];
879        let base_size_px = base_size.px().round() as i32;
880        let html_size = self.html_size() as usize;
881        NonNegative(if base_size_px >= 9 && base_size_px <= 16 {
882            let mapping = if quirks_mode == QuirksMode::Quirks {
883                QUIRKS_FONT_SIZE_MAPPING
884            } else {
885                FONT_SIZE_MAPPING
886            };
887            Length::new(mapping[(base_size_px - 9) as usize][html_size] as f32)
888        } else {
889            base_size * FONT_SIZE_FACTORS[html_size] as f32 / 100.0
890        })
891    }
892}
893
894impl FontSize {
895    /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size>
896    pub fn from_html_size(size: u8) -> Self {
897        FontSize::Keyword(KeywordInfo::new(match size {
898            // If value is less than 1, let it be 1.
899            0 | 1 => FontSizeKeyword::XSmall,
900            2 => FontSizeKeyword::Small,
901            3 => FontSizeKeyword::Medium,
902            4 => FontSizeKeyword::Large,
903            5 => FontSizeKeyword::XLarge,
904            6 => FontSizeKeyword::XXLarge,
905            // If value is greater than 7, let it be 7.
906            _ => FontSizeKeyword::XXXLarge,
907        }))
908    }
909
910    /// Compute it against a given base font size
911    pub fn to_computed_value_against(
912        &self,
913        context: &Context,
914        base_size: FontBaseSize,
915        line_height_base: LineHeightBase,
916    ) -> computed::FontSize {
917        let compose_keyword = |factor| {
918            context
919                .style()
920                .get_parent_font()
921                .clone_font_size()
922                .keyword_info
923                .compose(factor)
924        };
925        let mut info = KeywordInfo::none();
926        let size = match *self {
927            FontSize::Length(LengthPercentage::Length(ref l)) => {
928                if let NoCalcLength::FontRelative(ref value) = *l {
929                    if let FontRelativeLength::Em(em) = *value {
930                        // If the parent font was keyword-derived, this is
931                        // too. Tack the em unit onto the factor
932                        info = compose_keyword(em);
933                    }
934                }
935                let result =
936                    l.to_computed_value_with_base_size(context, base_size, line_height_base);
937                if l.should_zoom_text() {
938                    context.maybe_zoom_text(result)
939                } else {
940                    result
941                }
942            },
943            FontSize::Length(LengthPercentage::Percentage(pc)) => {
944                // If the parent font was keyword-derived, this is too.
945                // Tack the % onto the factor
946                info = compose_keyword(pc.0);
947                (base_size.resolve(context).computed_size() * pc.0).normalized()
948            },
949            FontSize::Length(LengthPercentage::Calc(ref calc)) => {
950                let calc = calc.to_computed_value_zoomed(context, base_size, line_height_base);
951                calc.resolve(base_size.resolve(context).computed_size())
952            },
953            FontSize::Keyword(i) => {
954                if i.kw.is_math() {
955                    // Scaling is done in recompute_math_font_size_if_needed().
956                    info = compose_keyword(1.);
957                    // i.kw will always be FontSizeKeyword::Math here. But writing it this
958                    // allows this code to compile for servo where the Math variant is cfg'd out.
959                    info.kw = i.kw;
960                    FontRelativeLength::Em(1.).to_computed_value(
961                        context,
962                        base_size,
963                        line_height_base,
964                    )
965                } else {
966                    // As a specified keyword, this is keyword derived
967                    info = i;
968                    i.to_computed_value(context).clamp_to_non_negative()
969                }
970            },
971            FontSize::Smaller => {
972                info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
973                FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO).to_computed_value(
974                    context,
975                    base_size,
976                    line_height_base,
977                )
978            },
979            FontSize::Larger => {
980                info = compose_keyword(LARGER_FONT_SIZE_RATIO);
981                FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO).to_computed_value(
982                    context,
983                    base_size,
984                    line_height_base,
985                )
986            },
987
988            FontSize::System(_) => {
989                #[cfg(feature = "servo")]
990                {
991                    unreachable!()
992                }
993                #[cfg(feature = "gecko")]
994                {
995                    context
996                        .cached_system_font
997                        .as_ref()
998                        .unwrap()
999                        .font_size
1000                        .computed_size()
1001                }
1002            },
1003        };
1004        computed::FontSize {
1005            computed_size: NonNegative(size),
1006            used_size: NonNegative(size),
1007            keyword_info: info,
1008        }
1009    }
1010}
1011
1012impl ToComputedValue for FontSize {
1013    type ComputedValue = computed::FontSize;
1014
1015    #[inline]
1016    fn to_computed_value(&self, context: &Context) -> computed::FontSize {
1017        self.to_computed_value_against(
1018            context,
1019            FontBaseSize::InheritedStyle,
1020            LineHeightBase::InheritedStyle,
1021        )
1022    }
1023
1024    #[inline]
1025    fn from_computed_value(computed: &computed::FontSize) -> Self {
1026        FontSize::Length(LengthPercentage::Length(
1027            ToComputedValue::from_computed_value(&computed.computed_size()),
1028        ))
1029    }
1030}
1031
1032impl FontSize {
1033    system_font_methods!(FontSize);
1034
1035    /// Get initial value for specified font size.
1036    #[inline]
1037    pub fn medium() -> Self {
1038        FontSize::Keyword(KeywordInfo::medium())
1039    }
1040
1041    /// Parses a font-size, with quirks.
1042    pub fn parse_quirky<'i, 't>(
1043        context: &ParserContext,
1044        input: &mut Parser<'i, 't>,
1045        allow_quirks: AllowQuirks,
1046    ) -> Result<FontSize, ParseError<'i>> {
1047        if let Ok(lp) = input
1048            .try_parse(|i| LengthPercentage::parse_non_negative_quirky(context, i, allow_quirks))
1049        {
1050            return Ok(FontSize::Length(lp));
1051        }
1052
1053        if let Ok(kw) = input.try_parse(|i| FontSizeKeyword::parse(i)) {
1054            return Ok(FontSize::Keyword(KeywordInfo::new(kw)));
1055        }
1056
1057        try_match_ident_ignore_ascii_case! { input,
1058            "smaller" => Ok(FontSize::Smaller),
1059            "larger" => Ok(FontSize::Larger),
1060        }
1061    }
1062}
1063
1064impl Parse for FontSize {
1065    /// <length> | <percentage> | <absolute-size> | <relative-size>
1066    fn parse<'i, 't>(
1067        context: &ParserContext,
1068        input: &mut Parser<'i, 't>,
1069    ) -> Result<FontSize, ParseError<'i>> {
1070        FontSize::parse_quirky(context, input, AllowQuirks::No)
1071    }
1072}
1073
1074bitflags! {
1075    #[derive(Clone, Copy)]
1076    /// Flags of variant alternates in bit
1077    struct VariantAlternatesParsingFlags: u8 {
1078        /// None of variant alternates enabled
1079        const NORMAL = 0;
1080        /// Historical forms
1081        const HISTORICAL_FORMS = 0x01;
1082        /// Stylistic Alternates
1083        const STYLISTIC = 0x02;
1084        /// Stylistic Sets
1085        const STYLESET = 0x04;
1086        /// Character Variant
1087        const CHARACTER_VARIANT = 0x08;
1088        /// Swash glyphs
1089        const SWASH = 0x10;
1090        /// Ornaments glyphs
1091        const ORNAMENTS = 0x20;
1092        /// Annotation forms
1093        const ANNOTATION = 0x40;
1094    }
1095}
1096
1097#[derive(
1098    Clone,
1099    Debug,
1100    MallocSizeOf,
1101    PartialEq,
1102    SpecifiedValueInfo,
1103    ToCss,
1104    ToComputedValue,
1105    ToResolvedValue,
1106    ToShmem,
1107)]
1108#[repr(C, u8)]
1109/// Set of variant alternates
1110pub enum VariantAlternates {
1111    /// Enables display of stylistic alternates
1112    #[css(function)]
1113    Stylistic(CustomIdent),
1114    /// Enables display with stylistic sets
1115    #[css(comma, function)]
1116    Styleset(#[css(iterable)] crate::OwnedSlice<CustomIdent>),
1117    /// Enables display of specific character variants
1118    #[css(comma, function)]
1119    CharacterVariant(#[css(iterable)] crate::OwnedSlice<CustomIdent>),
1120    /// Enables display of swash glyphs
1121    #[css(function)]
1122    Swash(CustomIdent),
1123    /// Enables replacement of default glyphs with ornaments
1124    #[css(function)]
1125    Ornaments(CustomIdent),
1126    /// Enables display of alternate annotation forms
1127    #[css(function)]
1128    Annotation(CustomIdent),
1129    /// Enables display of historical forms
1130    HistoricalForms,
1131}
1132
1133#[derive(
1134    Clone,
1135    Debug,
1136    Default,
1137    MallocSizeOf,
1138    PartialEq,
1139    SpecifiedValueInfo,
1140    ToComputedValue,
1141    ToCss,
1142    ToResolvedValue,
1143    ToShmem,
1144    ToTyped,
1145)]
1146#[repr(transparent)]
1147/// List of Variant Alternates
1148pub struct FontVariantAlternates(
1149    #[css(if_empty = "normal", iterable)] crate::OwnedSlice<VariantAlternates>,
1150);
1151
1152impl FontVariantAlternates {
1153    /// Returns the length of all variant alternates.
1154    pub fn len(&self) -> usize {
1155        self.0.iter().fold(0, |acc, alternate| match *alternate {
1156            VariantAlternates::Swash(_)
1157            | VariantAlternates::Stylistic(_)
1158            | VariantAlternates::Ornaments(_)
1159            | VariantAlternates::Annotation(_) => acc + 1,
1160            VariantAlternates::Styleset(ref slice)
1161            | VariantAlternates::CharacterVariant(ref slice) => acc + slice.len(),
1162            _ => acc,
1163        })
1164    }
1165}
1166
1167impl FontVariantAlternates {
1168    #[inline]
1169    /// Get initial specified value with VariantAlternatesList
1170    pub fn get_initial_specified_value() -> Self {
1171        Default::default()
1172    }
1173}
1174
1175impl Parse for FontVariantAlternates {
1176    /// normal |
1177    ///  [ stylistic(<feature-value-name>)           ||
1178    ///    historical-forms                          ||
1179    ///    styleset(<feature-value-name> #)          ||
1180    ///    character-variant(<feature-value-name> #) ||
1181    ///    swash(<feature-value-name>)               ||
1182    ///    ornaments(<feature-value-name>)           ||
1183    ///    annotation(<feature-value-name>) ]
1184    fn parse<'i, 't>(
1185        _: &ParserContext,
1186        input: &mut Parser<'i, 't>,
1187    ) -> Result<FontVariantAlternates, ParseError<'i>> {
1188        if input
1189            .try_parse(|input| input.expect_ident_matching("normal"))
1190            .is_ok()
1191        {
1192            return Ok(Default::default());
1193        }
1194
1195        let mut stylistic = None;
1196        let mut historical = None;
1197        let mut styleset = None;
1198        let mut character_variant = None;
1199        let mut swash = None;
1200        let mut ornaments = None;
1201        let mut annotation = None;
1202
1203        // Parse values for the various alternate types in any order.
1204        let mut parsed_alternates = VariantAlternatesParsingFlags::empty();
1205        macro_rules! check_if_parsed(
1206            ($input:expr, $flag:path) => (
1207                if parsed_alternates.contains($flag) {
1208                    return Err($input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1209                }
1210                parsed_alternates |= $flag;
1211            )
1212        );
1213        while let Ok(_) = input.try_parse(|input| match *input.next()? {
1214            Token::Ident(ref value) if value.eq_ignore_ascii_case("historical-forms") => {
1215                check_if_parsed!(input, VariantAlternatesParsingFlags::HISTORICAL_FORMS);
1216                historical = Some(VariantAlternates::HistoricalForms);
1217                Ok(())
1218            },
1219            Token::Function(ref name) => {
1220                let name = name.clone();
1221                input.parse_nested_block(|i| {
1222                    match_ignore_ascii_case! { &name,
1223                        "swash" => {
1224                            check_if_parsed!(i, VariantAlternatesParsingFlags::SWASH);
1225                            let ident = CustomIdent::parse(i, &[])?;
1226                            swash = Some(VariantAlternates::Swash(ident));
1227                            Ok(())
1228                        },
1229                        "stylistic" => {
1230                            check_if_parsed!(i, VariantAlternatesParsingFlags::STYLISTIC);
1231                            let ident = CustomIdent::parse(i, &[])?;
1232                            stylistic = Some(VariantAlternates::Stylistic(ident));
1233                            Ok(())
1234                        },
1235                        "ornaments" => {
1236                            check_if_parsed!(i, VariantAlternatesParsingFlags::ORNAMENTS);
1237                            let ident = CustomIdent::parse(i, &[])?;
1238                            ornaments = Some(VariantAlternates::Ornaments(ident));
1239                            Ok(())
1240                        },
1241                        "annotation" => {
1242                            check_if_parsed!(i, VariantAlternatesParsingFlags::ANNOTATION);
1243                            let ident = CustomIdent::parse(i, &[])?;
1244                            annotation = Some(VariantAlternates::Annotation(ident));
1245                            Ok(())
1246                        },
1247                        "styleset" => {
1248                            check_if_parsed!(i, VariantAlternatesParsingFlags::STYLESET);
1249                            let idents = i.parse_comma_separated(|i| {
1250                                CustomIdent::parse(i, &[])
1251                            })?;
1252                            styleset = Some(VariantAlternates::Styleset(idents.into()));
1253                            Ok(())
1254                        },
1255                        "character-variant" => {
1256                            check_if_parsed!(i, VariantAlternatesParsingFlags::CHARACTER_VARIANT);
1257                            let idents = i.parse_comma_separated(|i| {
1258                                CustomIdent::parse(i, &[])
1259                            })?;
1260                            character_variant = Some(VariantAlternates::CharacterVariant(idents.into()));
1261                            Ok(())
1262                        },
1263                        _ => return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1264                    }
1265                })
1266            },
1267            _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1268        }) {}
1269
1270        if parsed_alternates.is_empty() {
1271            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1272        }
1273
1274        // Collect the parsed values in canonical order, so that we'll serialize correctly.
1275        let mut alternates = Vec::new();
1276        macro_rules! push_if_some(
1277            ($value:expr) => (
1278                if let Some(v) = $value {
1279                    alternates.push(v);
1280                }
1281            )
1282        );
1283        push_if_some!(stylistic);
1284        push_if_some!(historical);
1285        push_if_some!(styleset);
1286        push_if_some!(character_variant);
1287        push_if_some!(swash);
1288        push_if_some!(ornaments);
1289        push_if_some!(annotation);
1290
1291        Ok(FontVariantAlternates(alternates.into()))
1292    }
1293}
1294
1295#[derive(
1296    Clone,
1297    Copy,
1298    Debug,
1299    Eq,
1300    MallocSizeOf,
1301    PartialEq,
1302    Parse,
1303    SpecifiedValueInfo,
1304    ToComputedValue,
1305    ToCss,
1306    ToResolvedValue,
1307    ToShmem,
1308    ToTyped,
1309)]
1310#[css(bitflags(
1311    single = "normal",
1312    mixed = "jis78,jis83,jis90,jis04,simplified,traditional,full-width,proportional-width,ruby",
1313    validate_mixed = "Self::validate_mixed_flags",
1314))]
1315#[repr(C)]
1316/// Variants for east asian variant
1317pub struct FontVariantEastAsian(u16);
1318bitflags! {
1319    impl FontVariantEastAsian: u16 {
1320        /// None of the features
1321        const NORMAL = 0;
1322        /// Enables rendering of JIS78 forms (OpenType feature: jp78)
1323        const JIS78  = 1 << 0;
1324        /// Enables rendering of JIS83 forms (OpenType feature: jp83).
1325        const JIS83 = 1 << 1;
1326        /// Enables rendering of JIS90 forms (OpenType feature: jp90).
1327        const JIS90 = 1 << 2;
1328        /// Enables rendering of JIS2004 forms (OpenType feature: jp04).
1329        const JIS04 = 1 << 3;
1330        /// Enables rendering of simplified forms (OpenType feature: smpl).
1331        const SIMPLIFIED = 1 << 4;
1332        /// Enables rendering of traditional forms (OpenType feature: trad).
1333        const TRADITIONAL = 1 << 5;
1334
1335        /// These values are exclusive with each other.
1336        const JIS_GROUP = Self::JIS78.0 | Self::JIS83.0 | Self::JIS90.0 | Self::JIS04.0 | Self::SIMPLIFIED.0 | Self::TRADITIONAL.0;
1337
1338        /// Enables rendering of full-width variants (OpenType feature: fwid).
1339        const FULL_WIDTH = 1 << 6;
1340        /// Enables rendering of proportionally-spaced variants (OpenType feature: pwid).
1341        const PROPORTIONAL_WIDTH = 1 << 7;
1342        /// Enables display of ruby variant glyphs (OpenType feature: ruby).
1343        const RUBY = 1 << 8;
1344    }
1345}
1346
1347impl FontVariantEastAsian {
1348    /// The number of variants.
1349    pub const COUNT: usize = 9;
1350
1351    fn validate_mixed_flags(&self) -> bool {
1352        if self.contains(Self::FULL_WIDTH | Self::PROPORTIONAL_WIDTH) {
1353            // full-width and proportional-width are exclusive with each other.
1354            return false;
1355        }
1356        let jis = self.intersection(Self::JIS_GROUP);
1357        if !jis.is_empty() && !jis.bits().is_power_of_two() {
1358            return false;
1359        }
1360        true
1361    }
1362}
1363
1364#[derive(
1365    Clone,
1366    Copy,
1367    Debug,
1368    Eq,
1369    MallocSizeOf,
1370    PartialEq,
1371    Parse,
1372    SpecifiedValueInfo,
1373    ToComputedValue,
1374    ToCss,
1375    ToResolvedValue,
1376    ToShmem,
1377    ToTyped,
1378)]
1379#[css(bitflags(
1380    single = "normal,none",
1381    mixed = "common-ligatures,no-common-ligatures,discretionary-ligatures,no-discretionary-ligatures,historical-ligatures,no-historical-ligatures,contextual,no-contextual",
1382    validate_mixed = "Self::validate_mixed_flags",
1383))]
1384#[repr(C)]
1385/// Variants of ligatures
1386pub struct FontVariantLigatures(u16);
1387bitflags! {
1388    impl FontVariantLigatures: u16 {
1389        /// Specifies that common default features are enabled
1390        const NORMAL = 0;
1391        /// Specifies that no features are enabled;
1392        const NONE = 1;
1393        /// Enables display of common ligatures
1394        const COMMON_LIGATURES  = 1 << 1;
1395        /// Disables display of common ligatures
1396        const NO_COMMON_LIGATURES  = 1 << 2;
1397        /// Enables display of discretionary ligatures
1398        const DISCRETIONARY_LIGATURES = 1 << 3;
1399        /// Disables display of discretionary ligatures
1400        const NO_DISCRETIONARY_LIGATURES = 1 << 4;
1401        /// Enables display of historical ligatures
1402        const HISTORICAL_LIGATURES = 1 << 5;
1403        /// Disables display of historical ligatures
1404        const NO_HISTORICAL_LIGATURES = 1 << 6;
1405        /// Enables display of contextual alternates
1406        const CONTEXTUAL = 1 << 7;
1407        /// Disables display of contextual alternates
1408        const NO_CONTEXTUAL = 1 << 8;
1409    }
1410}
1411
1412impl FontVariantLigatures {
1413    /// The number of variants.
1414    pub const COUNT: usize = 9;
1415
1416    fn validate_mixed_flags(&self) -> bool {
1417        // Mixing a value and its disabling value is forbidden.
1418        if self.contains(Self::COMMON_LIGATURES | Self::NO_COMMON_LIGATURES)
1419            || self.contains(Self::DISCRETIONARY_LIGATURES | Self::NO_DISCRETIONARY_LIGATURES)
1420            || self.contains(Self::HISTORICAL_LIGATURES | Self::NO_HISTORICAL_LIGATURES)
1421            || self.contains(Self::CONTEXTUAL | Self::NO_CONTEXTUAL)
1422        {
1423            return false;
1424        }
1425        true
1426    }
1427}
1428
1429/// Variants of numeric values
1430#[derive(
1431    Clone,
1432    Copy,
1433    Debug,
1434    Eq,
1435    MallocSizeOf,
1436    PartialEq,
1437    Parse,
1438    SpecifiedValueInfo,
1439    ToComputedValue,
1440    ToCss,
1441    ToResolvedValue,
1442    ToShmem,
1443    ToTyped,
1444)]
1445#[css(bitflags(
1446    single = "normal",
1447    mixed = "lining-nums,oldstyle-nums,proportional-nums,tabular-nums,diagonal-fractions,stacked-fractions,ordinal,slashed-zero",
1448    validate_mixed = "Self::validate_mixed_flags",
1449))]
1450#[repr(C)]
1451pub struct FontVariantNumeric(u8);
1452bitflags! {
1453    impl FontVariantNumeric : u8 {
1454        /// Specifies that common default features are enabled
1455        const NORMAL = 0;
1456        /// Enables display of lining numerals.
1457        const LINING_NUMS = 1 << 0;
1458        /// Enables display of old-style numerals.
1459        const OLDSTYLE_NUMS = 1 << 1;
1460        /// Enables display of proportional numerals.
1461        const PROPORTIONAL_NUMS = 1 << 2;
1462        /// Enables display of tabular numerals.
1463        const TABULAR_NUMS = 1 << 3;
1464        /// Enables display of lining diagonal fractions.
1465        const DIAGONAL_FRACTIONS = 1 << 4;
1466        /// Enables display of lining stacked fractions.
1467        const STACKED_FRACTIONS = 1 << 5;
1468        /// Enables display of slashed zeros.
1469        const SLASHED_ZERO = 1 << 6;
1470        /// Enables display of letter forms used with ordinal numbers.
1471        const ORDINAL = 1 << 7;
1472    }
1473}
1474
1475impl FontVariantNumeric {
1476    /// The number of variants.
1477    pub const COUNT: usize = 8;
1478
1479    /// normal |
1480    ///  [ <numeric-figure-values>   ||
1481    ///    <numeric-spacing-values>  ||
1482    ///    <numeric-fraction-values> ||
1483    ///    ordinal                   ||
1484    ///    slashed-zero ]
1485    /// <numeric-figure-values>   = [ lining-nums | oldstyle-nums ]
1486    /// <numeric-spacing-values>  = [ proportional-nums | tabular-nums ]
1487    /// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
1488    fn validate_mixed_flags(&self) -> bool {
1489        if self.contains(Self::LINING_NUMS | Self::OLDSTYLE_NUMS)
1490            || self.contains(Self::PROPORTIONAL_NUMS | Self::TABULAR_NUMS)
1491            || self.contains(Self::DIAGONAL_FRACTIONS | Self::STACKED_FRACTIONS)
1492        {
1493            return false;
1494        }
1495        true
1496    }
1497}
1498
1499/// This property provides low-level control over OpenType or TrueType font features.
1500pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>;
1501
1502/// For font-language-override, use the same representation as the computed value.
1503pub use crate::values::computed::font::FontLanguageOverride;
1504
1505impl Parse for FontLanguageOverride {
1506    /// normal | <string>
1507    fn parse<'i, 't>(
1508        _: &ParserContext,
1509        input: &mut Parser<'i, 't>,
1510    ) -> Result<FontLanguageOverride, ParseError<'i>> {
1511        if input
1512            .try_parse(|input| input.expect_ident_matching("normal"))
1513            .is_ok()
1514        {
1515            return Ok(FontLanguageOverride::normal());
1516        }
1517
1518        let string = input.expect_string()?;
1519
1520        // The OpenType spec requires tags to be 1 to 4 ASCII characters:
1521        // https://learn.microsoft.com/en-gb/typography/opentype/spec/otff#data-types
1522        if string.is_empty() || string.len() > 4 || !string.is_ascii() {
1523            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1524        }
1525
1526        let mut bytes = [b' '; 4];
1527        for (byte, str_byte) in bytes.iter_mut().zip(string.as_bytes()) {
1528            *byte = *str_byte;
1529        }
1530
1531        Ok(FontLanguageOverride(u32::from_be_bytes(bytes)))
1532    }
1533}
1534
1535/// A value for any of the font-synthesis-{weight,small-caps,position} properties.
1536#[repr(u8)]
1537#[derive(
1538    Clone,
1539    Copy,
1540    Debug,
1541    Eq,
1542    Hash,
1543    MallocSizeOf,
1544    Parse,
1545    PartialEq,
1546    SpecifiedValueInfo,
1547    ToComputedValue,
1548    ToCss,
1549    ToResolvedValue,
1550    ToShmem,
1551    ToTyped,
1552)]
1553#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1554pub enum FontSynthesis {
1555    /// This attribute may be synthesized if not supported by a face.
1556    Auto,
1557    /// Do not attempt to synthesis this style attribute.
1558    None,
1559}
1560
1561/// A value for the font-synthesis-style property.
1562#[repr(u8)]
1563#[derive(
1564    Clone,
1565    Copy,
1566    Debug,
1567    Eq,
1568    MallocSizeOf,
1569    Parse,
1570    PartialEq,
1571    SpecifiedValueInfo,
1572    ToComputedValue,
1573    ToCss,
1574    ToResolvedValue,
1575    ToShmem,
1576    ToTyped,
1577)]
1578pub enum FontSynthesisStyle {
1579    /// This attribute may be synthesized if not supported by a face.
1580    Auto,
1581    /// Do not attempt to synthesis this style attribute.
1582    None,
1583    /// Allow synthesis for oblique, but not for italic.
1584    ObliqueOnly,
1585}
1586
1587#[derive(
1588    Clone,
1589    Debug,
1590    Eq,
1591    MallocSizeOf,
1592    PartialEq,
1593    SpecifiedValueInfo,
1594    ToComputedValue,
1595    ToResolvedValue,
1596    ToShmem,
1597    ToTyped,
1598)]
1599#[repr(C)]
1600/// Allows authors to choose a palette from those supported by a color font
1601/// (and potentially @font-palette-values overrides).
1602pub struct FontPalette(Atom);
1603
1604#[allow(missing_docs)]
1605impl FontPalette {
1606    pub fn normal() -> Self {
1607        Self(atom!("normal"))
1608    }
1609    pub fn light() -> Self {
1610        Self(atom!("light"))
1611    }
1612    pub fn dark() -> Self {
1613        Self(atom!("dark"))
1614    }
1615}
1616
1617impl Parse for FontPalette {
1618    /// normal | light | dark | dashed-ident
1619    fn parse<'i, 't>(
1620        _context: &ParserContext,
1621        input: &mut Parser<'i, 't>,
1622    ) -> Result<FontPalette, ParseError<'i>> {
1623        let location = input.current_source_location();
1624        let ident = input.expect_ident()?;
1625        match_ignore_ascii_case! { &ident,
1626            "normal" => Ok(Self::normal()),
1627            "light" => Ok(Self::light()),
1628            "dark" => Ok(Self::dark()),
1629            _ => if ident.starts_with("--") {
1630                Ok(Self(Atom::from(ident.as_ref())))
1631            } else {
1632                Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
1633            },
1634        }
1635    }
1636}
1637
1638impl ToCss for FontPalette {
1639    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1640    where
1641        W: Write,
1642    {
1643        serialize_atom_identifier(&self.0, dest)
1644    }
1645}
1646
1647/// This property provides low-level control over OpenType or TrueType font
1648/// variations.
1649pub type FontVariationSettings = FontSettings<VariationValue<Number>>;
1650
1651fn parse_one_feature_value<'i, 't>(
1652    context: &ParserContext,
1653    input: &mut Parser<'i, 't>,
1654) -> Result<Integer, ParseError<'i>> {
1655    if let Ok(integer) = input.try_parse(|i| Integer::parse_non_negative(context, i)) {
1656        return Ok(integer);
1657    }
1658
1659    try_match_ident_ignore_ascii_case! { input,
1660        "on" => Ok(Integer::new(1)),
1661        "off" => Ok(Integer::new(0)),
1662    }
1663}
1664
1665impl Parse for FeatureTagValue<Integer> {
1666    /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value
1667    fn parse<'i, 't>(
1668        context: &ParserContext,
1669        input: &mut Parser<'i, 't>,
1670    ) -> Result<Self, ParseError<'i>> {
1671        let tag = FontTag::parse(context, input)?;
1672        let value = input
1673            .try_parse(|i| parse_one_feature_value(context, i))
1674            .unwrap_or_else(|_| Integer::new(1));
1675
1676        Ok(Self { tag, value })
1677    }
1678}
1679
1680impl Parse for VariationValue<Number> {
1681    /// This is the `<string> <number>` part of the font-variation-settings
1682    /// syntax.
1683    fn parse<'i, 't>(
1684        context: &ParserContext,
1685        input: &mut Parser<'i, 't>,
1686    ) -> Result<Self, ParseError<'i>> {
1687        let tag = FontTag::parse(context, input)?;
1688        let value = Number::parse(context, input)?;
1689        Ok(Self { tag, value })
1690    }
1691}
1692
1693/// A metrics override value for a @font-face descriptor
1694///
1695/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc
1696#[derive(
1697    Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
1698)]
1699pub enum MetricsOverride {
1700    /// A non-negative `<percentage>` of the computed font size
1701    Override(NonNegativePercentage),
1702    /// Normal metrics from the font.
1703    Normal,
1704}
1705
1706impl MetricsOverride {
1707    #[inline]
1708    /// Get default value with `normal`
1709    pub fn normal() -> MetricsOverride {
1710        MetricsOverride::Normal
1711    }
1712
1713    /// The ToComputedValue implementation, used for @font-face descriptors.
1714    ///
1715    /// Valid override percentages must be non-negative; we return -1.0 to indicate
1716    /// the absence of an override (i.e. 'normal').
1717    #[inline]
1718    pub fn compute(&self) -> ComputedPercentage {
1719        match *self {
1720            MetricsOverride::Normal => ComputedPercentage(-1.0),
1721            MetricsOverride::Override(percent) => ComputedPercentage(percent.0.get()),
1722        }
1723    }
1724}
1725
1726#[derive(
1727    Clone,
1728    Copy,
1729    Debug,
1730    MallocSizeOf,
1731    Parse,
1732    PartialEq,
1733    SpecifiedValueInfo,
1734    ToComputedValue,
1735    ToCss,
1736    ToResolvedValue,
1737    ToShmem,
1738    ToTyped,
1739)]
1740#[repr(u8)]
1741/// How to do font-size scaling.
1742pub enum XTextScale {
1743    /// Both min-font-size and text zoom are enabled.
1744    All,
1745    /// Text-only zoom is enabled, but min-font-size is not honored.
1746    ZoomOnly,
1747    /// Neither of them is enabled.
1748    None,
1749}
1750
1751impl XTextScale {
1752    /// Returns whether text zoom is enabled.
1753    #[inline]
1754    pub fn text_zoom_enabled(self) -> bool {
1755        self != Self::None
1756    }
1757}
1758
1759#[derive(
1760    Clone,
1761    Debug,
1762    MallocSizeOf,
1763    PartialEq,
1764    SpecifiedValueInfo,
1765    ToComputedValue,
1766    ToCss,
1767    ToResolvedValue,
1768    ToShmem,
1769    ToTyped,
1770)]
1771#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1772/// Internal property that reflects the lang attribute
1773pub struct XLang(#[css(skip)] pub Atom);
1774
1775impl XLang {
1776    #[inline]
1777    /// Get default value for `-x-lang`
1778    pub fn get_initial_value() -> XLang {
1779        XLang(atom!(""))
1780    }
1781}
1782
1783impl Parse for XLang {
1784    fn parse<'i, 't>(
1785        _: &ParserContext,
1786        input: &mut Parser<'i, 't>,
1787    ) -> Result<XLang, ParseError<'i>> {
1788        debug_assert!(
1789            false,
1790            "Should be set directly by presentation attributes only."
1791        );
1792        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1793    }
1794}
1795
1796#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1797#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
1798/// Specifies the minimum font size allowed due to changes in scriptlevel.
1799/// Ref: https://wiki.mozilla.org/MathML:mstyle
1800pub struct MozScriptMinSize(pub NoCalcLength);
1801
1802impl MozScriptMinSize {
1803    #[inline]
1804    /// Calculate initial value of -moz-script-min-size.
1805    pub fn get_initial_value() -> Length {
1806        Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * PX_PER_PT)
1807    }
1808}
1809
1810impl Parse for MozScriptMinSize {
1811    fn parse<'i, 't>(
1812        _: &ParserContext,
1813        input: &mut Parser<'i, 't>,
1814    ) -> Result<MozScriptMinSize, ParseError<'i>> {
1815        debug_assert!(
1816            false,
1817            "Should be set directly by presentation attributes only."
1818        );
1819        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1820    }
1821}
1822
1823/// A value for the `math-depth` property.
1824/// https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property
1825#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1826#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
1827pub enum MathDepth {
1828    /// Increment math-depth if math-style is compact.
1829    AutoAdd,
1830
1831    /// Add the function's argument to math-depth.
1832    #[css(function)]
1833    Add(Integer),
1834
1835    /// Set math-depth to the specified value.
1836    Absolute(Integer),
1837}
1838
1839impl Parse for MathDepth {
1840    fn parse<'i, 't>(
1841        context: &ParserContext,
1842        input: &mut Parser<'i, 't>,
1843    ) -> Result<MathDepth, ParseError<'i>> {
1844        if input
1845            .try_parse(|i| i.expect_ident_matching("auto-add"))
1846            .is_ok()
1847        {
1848            return Ok(MathDepth::AutoAdd);
1849        }
1850        if let Ok(math_depth_value) = input.try_parse(|input| Integer::parse(context, input)) {
1851            return Ok(MathDepth::Absolute(math_depth_value));
1852        }
1853        input.expect_function_matching("add")?;
1854        let math_depth_delta_value =
1855            input.parse_nested_block(|input| Integer::parse(context, input))?;
1856        Ok(MathDepth::Add(math_depth_delta_value))
1857    }
1858}
1859
1860#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
1861#[derive(
1862    Clone,
1863    Copy,
1864    Debug,
1865    PartialEq,
1866    SpecifiedValueInfo,
1867    ToComputedValue,
1868    ToCss,
1869    ToResolvedValue,
1870    ToShmem,
1871)]
1872/// Specifies the multiplier to be used to adjust font size
1873/// due to changes in scriptlevel.
1874///
1875/// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs
1876pub struct MozScriptSizeMultiplier(pub f32);
1877
1878impl MozScriptSizeMultiplier {
1879    #[inline]
1880    /// Get default value of `-moz-script-size-multiplier`
1881    pub fn get_initial_value() -> MozScriptSizeMultiplier {
1882        MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32)
1883    }
1884}
1885
1886impl Parse for MozScriptSizeMultiplier {
1887    fn parse<'i, 't>(
1888        _: &ParserContext,
1889        input: &mut Parser<'i, 't>,
1890    ) -> Result<MozScriptSizeMultiplier, ParseError<'i>> {
1891        debug_assert!(
1892            false,
1893            "Should be set directly by presentation attributes only."
1894        );
1895        Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1896    }
1897}
1898
1899impl From<f32> for MozScriptSizeMultiplier {
1900    fn from(v: f32) -> Self {
1901        MozScriptSizeMultiplier(v)
1902    }
1903}
1904
1905impl From<MozScriptSizeMultiplier> for f32 {
1906    fn from(v: MozScriptSizeMultiplier) -> f32 {
1907        v.0
1908    }
1909}
1910
1911/// A specified value for the `line-height` property.
1912pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>;
1913
1914impl ToComputedValue for LineHeight {
1915    type ComputedValue = computed::LineHeight;
1916
1917    #[inline]
1918    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
1919        match *self {
1920            GenericLineHeight::Normal => GenericLineHeight::Normal,
1921            #[cfg(feature = "gecko")]
1922            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
1923            GenericLineHeight::Number(number) => {
1924                GenericLineHeight::Number(number.to_computed_value(context))
1925            },
1926            GenericLineHeight::Length(ref non_negative_lp) => {
1927                let result = match non_negative_lp.0 {
1928                    LengthPercentage::Length(NoCalcLength::Absolute(ref abs)) => {
1929                        context.maybe_zoom_text(abs.to_computed_value(context))
1930                    },
1931                    LengthPercentage::Length(ref length) => {
1932                        // line-height units specifically resolve against parent's
1933                        // font and line-height properties, while the rest of font
1934                        // relative units still resolve against the element's own
1935                        // properties.
1936                        length.to_computed_value_with_base_size(
1937                            context,
1938                            FontBaseSize::CurrentStyle,
1939                            LineHeightBase::InheritedStyle,
1940                        )
1941                    },
1942                    LengthPercentage::Percentage(ref p) => FontRelativeLength::Em(p.0)
1943                        .to_computed_value(
1944                            context,
1945                            FontBaseSize::CurrentStyle,
1946                            LineHeightBase::InheritedStyle,
1947                        ),
1948                    LengthPercentage::Calc(ref calc) => {
1949                        let computed_calc = calc.to_computed_value_zoomed(
1950                            context,
1951                            FontBaseSize::CurrentStyle,
1952                            LineHeightBase::InheritedStyle,
1953                        );
1954                        let base = context.style().get_font().clone_font_size().computed_size();
1955                        computed_calc.resolve(base)
1956                    },
1957                };
1958                GenericLineHeight::Length(result.into())
1959            },
1960        }
1961    }
1962
1963    #[inline]
1964    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
1965        match *computed {
1966            GenericLineHeight::Normal => GenericLineHeight::Normal,
1967            #[cfg(feature = "gecko")]
1968            GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
1969            GenericLineHeight::Number(ref number) => {
1970                GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
1971            },
1972            GenericLineHeight::Length(ref length) => {
1973                GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
1974            },
1975        }
1976    }
1977}
1978
1979/// Flags for the query_font_metrics() function.
1980#[repr(C)]
1981pub struct QueryFontMetricsFlags(u8);
1982
1983bitflags! {
1984    impl QueryFontMetricsFlags: u8 {
1985        /// Should we use the user font set?
1986        const USE_USER_FONT_SET = 1 << 0;
1987        /// Does the caller need the `ch` unit (width of the ZERO glyph)?
1988        const NEEDS_CH = 1 << 1;
1989        /// Does the caller need the `ic` unit (width of the WATER ideograph)?
1990        const NEEDS_IC = 1 << 2;
1991        /// Does the caller need math scales to be retrieved?
1992        const NEEDS_MATH_SCALES = 1 << 3;
1993    }
1994}