Coverage Report

Created: 2026-10-28 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/lightningcss-1.0.0-alpha.72/src/properties/font.rs
Line
Count
Source
1
//! CSS properties related to fonts.
2
3
use std::collections::HashSet;
4
5
use super::{Property, PropertyId};
6
use crate::compat::Feature;
7
use crate::context::PropertyHandlerContext;
8
use crate::declaration::{DeclarationBlock, DeclarationList};
9
use crate::error::{ParserError, PrinterError};
10
use crate::macros::*;
11
use crate::printer::Printer;
12
use crate::targets::should_compile;
13
use crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
14
use crate::values::length::LengthValue;
15
use crate::values::number::CSSNumber;
16
use crate::values::string::CowArcStr;
17
use crate::values::{angle::Angle, length::LengthPercentage, percentage::Percentage};
18
#[cfg(feature = "visitor")]
19
use crate::visitor::Visit;
20
use cssparser::*;
21
22
/// A value for the [font-weight](https://www.w3.org/TR/css-fonts-4/#font-weight-prop) property.
23
132
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
24
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::FontWeight as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontWeight as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontWeight as lightningcss::visitor::Visit<_, _>>::visit_children
25
#[cfg_attr(
26
  feature = "serde",
27
  derive(serde::Serialize, serde::Deserialize),
28
  serde(tag = "type", content = "value", rename_all = "kebab-case")
29
)]
30
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
31
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
32
pub enum FontWeight {
33
  /// An absolute font weight.
34
  Absolute(AbsoluteFontWeight),
35
  /// The `bolder` keyword.
36
  Bolder,
37
  /// The `lighter` keyword.
38
  Lighter,
39
}
40
41
impl Default for FontWeight {
42
2.16k
  fn default() -> FontWeight {
43
2.16k
    FontWeight::Absolute(AbsoluteFontWeight::default())
44
2.16k
  }
45
}
46
47
impl IsCompatible for FontWeight {
48
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
49
0
    match self {
50
0
      FontWeight::Absolute(a) => a.is_compatible(browsers),
51
0
      FontWeight::Bolder | FontWeight::Lighter => true,
52
    }
53
0
  }
54
}
55
56
/// An [absolute font weight](https://www.w3.org/TR/css-fonts-4/#font-weight-absolute-values),
57
/// as used in the `font-weight` property.
58
///
59
/// See [FontWeight](FontWeight).
60
132
#[derive(Debug, Clone, PartialEq, Parse)]
61
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::AbsoluteFontWeight as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::AbsoluteFontWeight as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::AbsoluteFontWeight as lightningcss::visitor::Visit<_, _>>::visit_children
62
#[cfg_attr(
63
  feature = "serde",
64
  derive(serde::Serialize, serde::Deserialize),
65
  serde(tag = "type", content = "value", rename_all = "kebab-case")
66
)]
67
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
68
pub enum AbsoluteFontWeight {
69
  /// An explicit weight.
70
  Weight(CSSNumber),
71
  /// Same as `400`.
72
  Normal,
73
  /// Same as `700`.
74
  Bold,
75
}
76
77
impl Default for AbsoluteFontWeight {
78
2.16k
  fn default() -> AbsoluteFontWeight {
79
2.16k
    AbsoluteFontWeight::Normal
80
2.16k
  }
81
}
82
83
impl ToCss for AbsoluteFontWeight {
84
0
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
85
0
  where
86
0
    W: std::fmt::Write,
87
  {
88
    use AbsoluteFontWeight::*;
89
0
    match self {
90
0
      Weight(val) => val.to_css(dest),
91
0
      Normal => dest.write_str(if dest.minify { "400" } else { "normal" }),
92
0
      Bold => dest.write_str(if dest.minify { "700" } else { "bold" }),
93
    }
94
0
  }
95
}
96
97
impl IsCompatible for AbsoluteFontWeight {
98
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
99
0
    match self {
100
      // Older browsers only supported 100, 200, 300, ...900 rather than arbitrary values.
101
0
      AbsoluteFontWeight::Weight(val) if !(*val >= 100.0 && *val <= 900.0 && *val % 100.0 == 0.0) => {
102
0
        Feature::FontWeightNumber.is_compatible(browsers)
103
      }
104
0
      _ => true,
105
    }
106
0
  }
107
}
108
109
enum_property! {
110
  /// An [absolute font size](https://www.w3.org/TR/css-fonts-3/#absolute-size-value),
111
  /// as used in the `font-size` property.
112
  ///
113
  /// See [FontSize](FontSize).
114
  #[allow(missing_docs)]
115
  pub enum AbsoluteFontSize {
116
    "xx-small": XXSmall,
117
    "x-small": XSmall,
118
    "small": Small,
119
    "medium": Medium,
120
    "large": Large,
121
    "x-large": XLarge,
122
    "xx-large": XXLarge,
123
    "xxx-large": XXXLarge,
124
  }
125
}
126
127
impl IsCompatible for AbsoluteFontSize {
128
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
129
    use AbsoluteFontSize::*;
130
0
    match self {
131
0
      XXXLarge => Feature::FontSizeXXXLarge.is_compatible(browsers),
132
0
      _ => true,
133
    }
134
0
  }
135
}
136
137
enum_property! {
138
  /// A [relative font size](https://www.w3.org/TR/css-fonts-3/#relative-size-value),
139
  /// as used in the `font-size` property.
140
  ///
141
  /// See [FontSize](FontSize).
142
  #[allow(missing_docs)]
143
  pub enum RelativeFontSize {
144
    Smaller,
145
    Larger,
146
  }
147
}
148
149
/// A value for the [font-size](https://www.w3.org/TR/css-fonts-4/#font-size-prop) property.
150
8.04k
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
151
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::FontSize as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontSize as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontSize as lightningcss::visitor::Visit<_, _>>::visit_children
152
#[cfg_attr(
153
  feature = "serde",
154
  derive(serde::Serialize, serde::Deserialize),
155
  serde(tag = "type", content = "value", rename_all = "kebab-case")
156
)]
157
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
158
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
159
pub enum FontSize {
160
  /// An explicit size.
161
  Length(LengthPercentage),
162
  /// An absolute font size keyword.
163
  Absolute(AbsoluteFontSize),
164
  /// A relative font size keyword.
165
  Relative(RelativeFontSize),
166
}
167
168
impl IsCompatible for FontSize {
169
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
170
0
    match self {
171
0
      FontSize::Length(LengthPercentage::Dimension(LengthValue::Rem(..))) => {
172
0
        Feature::FontSizeRem.is_compatible(browsers)
173
      }
174
0
      FontSize::Length(l) => l.is_compatible(browsers),
175
0
      FontSize::Absolute(a) => a.is_compatible(browsers),
176
0
      FontSize::Relative(..) => true,
177
    }
178
0
  }
179
}
180
181
enum_property! {
182
  /// A [font stretch keyword](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop),
183
  /// as used in the `font-stretch` property.
184
  ///
185
  /// See [FontStretch](FontStretch).
186
  pub enum FontStretchKeyword {
187
    /// 100%
188
    "normal": Normal,
189
    /// 50%
190
    "ultra-condensed": UltraCondensed,
191
    /// 62.5%
192
    "extra-condensed": ExtraCondensed,
193
    /// 75%
194
    "condensed": Condensed,
195
    /// 87.5%
196
    "semi-condensed": SemiCondensed,
197
    /// 112.5%
198
    "semi-expanded": SemiExpanded,
199
    /// 125%
200
    "expanded": Expanded,
201
    /// 150%
202
    "extra-expanded": ExtraExpanded,
203
    /// 200%
204
    "ultra-expanded": UltraExpanded,
205
  }
206
}
207
208
impl Default for FontStretchKeyword {
209
2.16k
  fn default() -> FontStretchKeyword {
210
2.16k
    FontStretchKeyword::Normal
211
2.16k
  }
212
}
213
214
impl Into<Percentage> for &FontStretchKeyword {
215
0
  fn into(self) -> Percentage {
216
    use FontStretchKeyword::*;
217
0
    let val = match self {
218
0
      UltraCondensed => 0.5,
219
0
      ExtraCondensed => 0.625,
220
0
      Condensed => 0.75,
221
0
      SemiCondensed => 0.875,
222
0
      Normal => 1.0,
223
0
      SemiExpanded => 1.125,
224
0
      Expanded => 1.25,
225
0
      ExtraExpanded => 1.5,
226
0
      UltraExpanded => 2.0,
227
    };
228
0
    Percentage(val)
229
0
  }
230
}
231
232
/// A value for the [font-stretch](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop) property.
233
36
#[derive(Debug, Clone, PartialEq, Parse)]
234
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::FontStretch as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontStretch as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontStretch as lightningcss::visitor::Visit<_, _>>::visit_children
235
#[cfg_attr(
236
  feature = "serde",
237
  derive(serde::Serialize, serde::Deserialize),
238
  serde(tag = "type", content = "value", rename_all = "kebab-case")
239
)]
240
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
241
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
242
pub enum FontStretch {
243
  /// A font stretch keyword.
244
  Keyword(FontStretchKeyword),
245
  /// A percentage.
246
  Percentage(Percentage),
247
}
248
249
impl Default for FontStretch {
250
2.16k
  fn default() -> FontStretch {
251
2.16k
    FontStretch::Keyword(FontStretchKeyword::default())
252
2.16k
  }
253
}
254
255
impl Into<Percentage> for &FontStretch {
256
0
  fn into(self) -> Percentage {
257
0
    match self {
258
0
      FontStretch::Percentage(val) => val.clone(),
259
0
      FontStretch::Keyword(keyword) => keyword.into(),
260
    }
261
0
  }
262
}
263
264
impl ToCss for FontStretch {
265
0
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
266
0
  where
267
0
    W: std::fmt::Write,
268
  {
269
0
    if dest.minify {
270
0
      let percentage: Percentage = self.into();
271
0
      return percentage.to_css(dest);
272
0
    }
273
274
0
    match self {
275
0
      FontStretch::Percentage(val) => val.to_css(dest),
276
0
      FontStretch::Keyword(val) => val.to_css(dest),
277
    }
278
0
  }
279
}
280
281
impl IsCompatible for FontStretch {
282
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
283
0
    match self {
284
0
      FontStretch::Percentage(..) => Feature::FontStretchPercentage.is_compatible(browsers),
285
0
      FontStretch::Keyword(..) => true,
286
    }
287
0
  }
288
}
289
290
enum_property! {
291
  /// A [generic font family](https://www.w3.org/TR/css-fonts-4/#generic-font-families) name,
292
  /// as used in the `font-family` property.
293
  ///
294
  /// See [FontFamily](FontFamily).
295
  #[allow(missing_docs)]
296
  #[derive(Eq, Hash)]
297
  pub enum GenericFontFamily {
298
    "serif": Serif,
299
    "sans-serif": SansSerif,
300
    "cursive": Cursive,
301
    "fantasy": Fantasy,
302
    "monospace": Monospace,
303
    "system-ui": SystemUI,
304
    "emoji": Emoji,
305
    "math": Math,
306
    "fangsong": FangSong,
307
    "ui-serif": UISerif,
308
    "ui-sans-serif": UISansSerif,
309
    "ui-monospace": UIMonospace,
310
    "ui-rounded": UIRounded,
311
312
    // CSS wide keywords. These must be parsed as identifiers so they
313
    // don't get serialized as strings.
314
    // https://www.w3.org/TR/css-values-4/#common-keywords
315
    "initial": Initial,
316
    "inherit": Inherit,
317
    "unset": Unset,
318
    // Default is also reserved by the <custom-ident> type.
319
    // https://www.w3.org/TR/css-values-4/#custom-idents
320
    "default": Default,
321
322
    // CSS defaulting keywords
323
    // https://drafts.csswg.org/css-cascade-5/#defaulting-keywords
324
    "revert": Revert,
325
    "revert-layer": RevertLayer,
326
  }
327
}
328
329
impl IsCompatible for GenericFontFamily {
330
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
331
    use GenericFontFamily::*;
332
0
    match self {
333
0
      SystemUI => Feature::FontFamilySystemUi.is_compatible(browsers),
334
0
      UISerif | UISansSerif | UIMonospace | UIRounded => Feature::ExtendedSystemFonts.is_compatible(browsers),
335
0
      _ => true,
336
    }
337
0
  }
338
}
339
340
/// A value for the [font-family](https://www.w3.org/TR/css-fonts-4/#font-family-prop) property.
341
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
342
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::FontFamily as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontFamily as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontFamily as lightningcss::visitor::Visit<_, _>>::visit_children
343
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
344
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(untagged))]
345
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
346
pub enum FontFamily<'i> {
347
  /// A generic family name.
348
  Generic(GenericFontFamily),
349
  /// A custom family name.
350
  #[cfg_attr(feature = "serde", serde(borrow))]
351
  FamilyName(FamilyName<'i>),
352
}
353
354
impl<'i> Parse<'i> for FontFamily<'i> {
355
21.8k
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
356
21.8k
    if let Ok(value) = input.try_parse(GenericFontFamily::parse) {
357
4
      return Ok(FontFamily::Generic(value));
358
21.8k
    }
359
360
21.8k
    let family = FamilyName::parse(input)?;
361
21.3k
    Ok(FontFamily::FamilyName(family))
362
21.8k
  }
363
}
364
365
impl<'i> ToCss for FontFamily<'i> {
366
0
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
367
0
  where
368
0
    W: std::fmt::Write,
369
  {
370
0
    match self {
371
0
      FontFamily::Generic(val) => val.to_css(dest),
372
0
      FontFamily::FamilyName(val) => val.to_css(dest),
373
    }
374
0
  }
375
}
376
377
/// A font [family name](https://drafts.csswg.org/css-fonts/#family-name-syntax).
378
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
379
#[cfg_attr(feature = "visitor", derive(Visit))]
380
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
381
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
382
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
383
pub struct FamilyName<'i>(#[cfg_attr(feature = "serde", serde(borrow))] CowArcStr<'i>);
384
385
impl<'i> Parse<'i> for FamilyName<'i> {
386
21.8k
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
387
21.8k
    if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) {
388
2
      return Ok(FamilyName(value.into()));
389
21.8k
    }
390
391
21.8k
    let value: CowArcStr<'i> = input.expect_ident()?.into();
392
21.3k
    let mut string = None;
393
28.8k
    while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
394
7.50k
      if string.is_none() {
395
6.16k
        string = Some(value.to_string());
396
6.16k
      }
397
398
7.50k
      if let Some(string) = &mut string {
399
7.50k
        string.push(' ');
400
7.50k
        string.push_str(&ident);
401
7.50k
      }
402
    }
403
404
21.3k
    let value = if let Some(string) = string {
405
6.16k
      string.into()
406
    } else {
407
15.1k
      value
408
    };
409
410
21.3k
    Ok(FamilyName(value))
411
21.8k
  }
412
}
413
414
impl<'i> ToCss for FamilyName<'i> {
415
0
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
416
0
  where
417
0
    W: std::fmt::Write,
418
  {
419
    // Generic family names such as sans-serif must be quoted if parsed as a string.
420
    // CSS wide keywords, as well as "default", must also be quoted.
421
    // https://www.w3.org/TR/css-fonts-4/#family-name-syntax
422
0
    let val = &self.0;
423
0
    if !val.is_empty() && !GenericFontFamily::parse_string(val).is_ok() {
424
      // Family names with two or more consecutive spaces must be quoted to preserve the spaces.
425
0
      let needs_quotes = val.contains("  ");
426
0
      if needs_quotes {
427
0
        serialize_string(&val, dest)?;
428
0
        return Ok(());
429
0
      }
430
431
0
      let mut id = String::new();
432
0
      let mut first = true;
433
0
      for slice in val.split(' ') {
434
0
        if first {
435
0
          first = false;
436
0
        } else {
437
0
          id.push(' ');
438
0
        }
439
0
        serialize_identifier(slice, &mut id)?;
440
      }
441
0
      if id.len() < val.len() + 2 {
442
0
        return dest.write_str(&id);
443
0
      }
444
0
    }
445
0
    serialize_string(&val, dest)?;
446
0
    Ok(())
447
0
  }
448
}
449
450
impl IsCompatible for FontFamily<'_> {
451
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
452
0
    match self {
453
0
      FontFamily::Generic(g) => g.is_compatible(browsers),
454
0
      FontFamily::FamilyName(..) => true,
455
    }
456
0
  }
457
}
458
459
/// A value for the [font-style](https://www.w3.org/TR/css-fonts-4/#font-style-prop) property.
460
#[derive(Debug, Clone, PartialEq)]
461
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::FontStyle as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontStyle as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::FontStyle as lightningcss::visitor::Visit<_, _>>::visit_children
462
#[cfg_attr(
463
  feature = "serde",
464
  derive(serde::Serialize, serde::Deserialize),
465
  serde(tag = "type", content = "value", rename_all = "kebab-case")
466
)]
467
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
468
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
469
pub enum FontStyle {
470
  /// Normal font style.
471
  Normal,
472
  /// Italic font style.
473
  Italic,
474
  /// Oblique font style, with a custom angle.
475
  Oblique(#[cfg_attr(feature = "serde", serde(default = "FontStyle::default_oblique_angle"))] Angle),
476
}
477
478
impl Default for FontStyle {
479
2.16k
  fn default() -> FontStyle {
480
2.16k
    FontStyle::Normal
481
2.16k
  }
482
}
483
484
impl FontStyle {
485
  #[inline]
486
0
  pub(crate) fn default_oblique_angle() -> Angle {
487
0
    Angle::Deg(14.0)
488
0
  }
489
}
490
491
impl<'i> Parse<'i> for FontStyle {
492
15.3k
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
493
15.3k
    let location = input.current_source_location();
494
15.3k
    let ident = input.expect_ident()?;
495
15.1k
    match_ignore_ascii_case! { &*ident,
496
15.1k
      "normal" => Ok(FontStyle::Normal),
497
15.1k
      "italic" => Ok(FontStyle::Italic),
498
15.1k
      "oblique" => {
499
0
        let angle = input.try_parse(Angle::parse).unwrap_or(FontStyle::default_oblique_angle());
500
0
        Ok(FontStyle::Oblique(angle))
501
      },
502
15.1k
      _ => Err(location.new_unexpected_token_error(
503
15.1k
        cssparser::Token::Ident(ident.clone())
504
15.1k
      ))
505
    }
506
15.3k
  }
507
}
508
509
impl ToCss for FontStyle {
510
0
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
511
0
  where
512
0
    W: std::fmt::Write,
513
  {
514
0
    match self {
515
0
      FontStyle::Normal => dest.write_str("normal"),
516
0
      FontStyle::Italic => dest.write_str("italic"),
517
0
      FontStyle::Oblique(angle) => {
518
0
        dest.write_str("oblique")?;
519
0
        if *angle != FontStyle::default_oblique_angle() {
520
0
          dest.write_char(' ')?;
521
0
          angle.to_css(dest)?;
522
0
        }
523
0
        Ok(())
524
      }
525
    }
526
0
  }
527
}
528
529
impl IsCompatible for FontStyle {
530
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
531
0
    match self {
532
0
      FontStyle::Oblique(angle) if *angle != FontStyle::default_oblique_angle() => {
533
0
        Feature::FontStyleObliqueAngle.is_compatible(browsers)
534
      }
535
0
      FontStyle::Normal | FontStyle::Italic | FontStyle::Oblique(..) => true,
536
    }
537
0
  }
538
}
539
540
enum_property! {
541
  /// A value for the [font-variant-caps](https://www.w3.org/TR/css-fonts-4/#font-variant-caps-prop) property.
542
  pub enum FontVariantCaps {
543
    /// No special capitalization features are applied.
544
    Normal,
545
    /// The small capitals feature is used for lower case letters.
546
    SmallCaps,
547
    /// Small capitals are used for both upper and lower case letters.
548
    AllSmallCaps,
549
    /// Petite capitals are used.
550
    PetiteCaps,
551
    /// Petite capitals are used for both upper and lower case letters.
552
    AllPetiteCaps,
553
    /// Enables display of mixture of small capitals for uppercase letters with normal lowercase letters.
554
    Unicase,
555
    /// Uses titling capitals.
556
    TitlingCaps,
557
  }
558
}
559
560
impl Default for FontVariantCaps {
561
2.16k
  fn default() -> FontVariantCaps {
562
2.16k
    FontVariantCaps::Normal
563
2.16k
  }
564
}
565
566
impl FontVariantCaps {
567
14
  fn is_css2(&self) -> bool {
568
14
    matches!(self, FontVariantCaps::Normal | FontVariantCaps::SmallCaps)
569
14
  }
570
571
15.3k
  fn parse_css2<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
572
15.3k
    let value = Self::parse(input)?;
573
14
    if !value.is_css2() {
574
3
      return Err(input.new_custom_error(ParserError::InvalidValue));
575
11
    }
576
11
    Ok(value)
577
15.3k
  }
578
}
579
580
impl IsCompatible for FontVariantCaps {
581
0
  fn is_compatible(&self, _browsers: crate::targets::Browsers) -> bool {
582
0
    true
583
0
  }
584
}
585
586
/// A value for the [line-height](https://www.w3.org/TR/2020/WD-css-inline-3-20200827/#propdef-line-height) property.
587
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
588
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::LineHeight as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::LineHeight as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::LineHeight as lightningcss::visitor::Visit<_, _>>::visit_children
589
#[cfg_attr(
590
  feature = "serde",
591
  derive(serde::Serialize, serde::Deserialize),
592
  serde(tag = "type", content = "value", rename_all = "kebab-case")
593
)]
594
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
595
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
596
pub enum LineHeight {
597
  /// The UA sets the line height based on the font.
598
  Normal,
599
  /// A multiple of the element's font size.
600
  Number(CSSNumber),
601
  /// An explicit height.
602
  Length(LengthPercentage),
603
}
604
605
impl Default for LineHeight {
606
2.16k
  fn default() -> LineHeight {
607
2.16k
    LineHeight::Normal
608
2.16k
  }
609
}
610
611
impl IsCompatible for LineHeight {
612
0
  fn is_compatible(&self, browsers: crate::targets::Browsers) -> bool {
613
0
    match self {
614
0
      LineHeight::Length(l) => l.is_compatible(browsers),
615
0
      LineHeight::Normal | LineHeight::Number(..) => true,
616
    }
617
0
  }
618
}
619
620
enum_property! {
621
  /// A keyword for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.
622
  pub enum VerticalAlignKeyword {
623
    /// Align the baseline of the box with the baseline of the parent box.
624
    Baseline,
625
    /// Lower the baseline of the box to the proper position for subscripts of the parent’s box.
626
    Sub,
627
    /// Raise the baseline of the box to the proper position for superscripts of the parent’s box.
628
    Super,
629
    /// Align the top of the aligned subtree with the top of the line box.
630
    Top,
631
    /// Align the top of the box with the top of the parent’s content area.
632
    TextTop,
633
    /// Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent.
634
    Middle,
635
    /// Align the bottom of the aligned subtree with the bottom of the line box.
636
    Bottom,
637
    /// Align the bottom of the box with the bottom of the parent’s content area.
638
    TextBottom,
639
  }
640
}
641
642
/// A value for the [vertical align](https://drafts.csswg.org/css2/#propdef-vertical-align) property.
643
// TODO: there is a more extensive spec in CSS3 but it doesn't seem any browser implements it? https://www.w3.org/TR/css-inline-3/#transverse-alignment
644
593
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
645
0
#[cfg_attr(feature = "visitor", derive(Visit))]
Unexecuted instantiation: <lightningcss::properties::font::VerticalAlign as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::ValueCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::VerticalAlign as lightningcss::visitor::Visit<lightningcss::parser::DefaultAtRule, fallow_extract::css_metrics::FirstRgbColorCollector>>::visit_children
Unexecuted instantiation: <lightningcss::properties::font::VerticalAlign as lightningcss::visitor::Visit<_, _>>::visit_children
646
#[cfg_attr(
647
  feature = "serde",
648
  derive(serde::Serialize, serde::Deserialize),
649
  serde(tag = "type", content = "value", rename_all = "kebab-case")
650
)]
651
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
652
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
653
pub enum VerticalAlign {
654
  /// A vertical align keyword.
655
  Keyword(VerticalAlignKeyword),
656
  /// An explicit length.
657
  Length(LengthPercentage),
658
}
659
660
define_shorthand! {
661
  /// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property.
662
  pub struct Font<'i> {
663
    /// The font family.
664
    #[cfg_attr(feature = "serde", serde(borrow))]
665
    family: FontFamily(Vec<FontFamily<'i>>),
666
    /// The font size.
667
    size: FontSize(FontSize),
668
    /// The font style.
669
    style: FontStyle(FontStyle),
670
    /// The font weight.
671
    weight: FontWeight(FontWeight),
672
    /// The font stretch.
673
    stretch: FontStretch(FontStretch),
674
    /// The line height.
675
    line_height: LineHeight(LineHeight),
676
    /// How the text should be capitalized. Only CSS 2.1 values are supported.
677
    variant_caps: FontVariantCaps(FontVariantCaps),
678
  }
679
}
680
681
impl<'i> Parse<'i> for Font<'i> {
682
15.3k
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
683
15.3k
    let mut style = None;
684
15.3k
    let mut weight = None;
685
15.3k
    let mut stretch = None;
686
    let size;
687
15.3k
    let mut variant_caps = None;
688
15.3k
    let mut count = 0;
689
690
    loop {
691
      // Skip "normal" since it is valid for several properties, but we don't know which ones it will be used for yet.
692
15.3k
      if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
693
0
        count += 1;
694
0
        continue;
695
15.3k
      }
696
15.3k
      if style.is_none() {
697
15.3k
        if let Ok(value) = input.try_parse(FontStyle::parse) {
698
0
          style = Some(value);
699
0
          count += 1;
700
0
          continue;
701
15.3k
        }
702
0
      }
703
15.3k
      if weight.is_none() {
704
15.3k
        if let Ok(value) = input.try_parse(FontWeight::parse) {
705
21
          weight = Some(value);
706
21
          count += 1;
707
21
          continue;
708
15.3k
        }
709
21
      }
710
15.3k
      if variant_caps.is_none() {
711
15.3k
        if let Ok(value) = input.try_parse(FontVariantCaps::parse_css2) {
712
11
          variant_caps = Some(value);
713
11
          count += 1;
714
11
          continue;
715
15.3k
        }
716
11
      }
717
718
15.3k
      if stretch.is_none() {
719
15.3k
        if let Ok(value) = input.try_parse(FontStretchKeyword::parse) {
720
0
          stretch = Some(FontStretch::Keyword(value));
721
0
          count += 1;
722
0
          continue;
723
15.3k
        }
724
0
      }
725
15.3k
      size = Some(FontSize::parse(input)?);
726
7.27k
      break;
727
    }
728
729
7.27k
    if count > 4 {
730
0
      return Err(input.new_custom_error(ParserError::InvalidDeclaration));
731
7.27k
    }
732
733
7.27k
    let size = match size {
734
7.27k
      Some(s) => s,
735
0
      None => return Err(input.new_custom_error(ParserError::InvalidDeclaration)),
736
    };
737
738
7.27k
    let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
739
1
      Some(LineHeight::parse(input)?)
740
    } else {
741
7.27k
      None
742
    };
743
744
7.27k
    let family = input.parse_comma_separated(FontFamily::parse)?;
745
2.16k
    Ok(Font {
746
2.16k
      family,
747
2.16k
      size,
748
2.16k
      style: style.unwrap_or_default(),
749
2.16k
      weight: weight.unwrap_or_default(),
750
2.16k
      stretch: stretch.unwrap_or_default(),
751
2.16k
      line_height: line_height.unwrap_or_default(),
752
2.16k
      variant_caps: variant_caps.unwrap_or_default(),
753
2.16k
    })
754
15.3k
  }
755
}
756
757
impl<'i> ToCss for Font<'i> {
758
0
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
759
0
  where
760
0
    W: std::fmt::Write,
761
  {
762
0
    if self.style != FontStyle::default() {
763
0
      self.style.to_css(dest)?;
764
0
      dest.write_char(' ')?;
765
0
    }
766
767
0
    if self.variant_caps != FontVariantCaps::default() {
768
0
      self.variant_caps.to_css(dest)?;
769
0
      dest.write_char(' ')?;
770
0
    }
771
772
0
    if self.weight != FontWeight::default() {
773
0
      self.weight.to_css(dest)?;
774
0
      dest.write_char(' ')?;
775
0
    }
776
777
0
    if self.stretch != FontStretch::default() {
778
0
      self.stretch.to_css(dest)?;
779
0
      dest.write_char(' ')?;
780
0
    }
781
782
0
    self.size.to_css(dest)?;
783
784
0
    if self.line_height != LineHeight::default() {
785
0
      dest.delim('/', true)?;
786
0
      self.line_height.to_css(dest)?;
787
0
    }
788
789
0
    dest.write_char(' ')?;
790
791
0
    let len = self.family.len();
792
0
    for (idx, val) in self.family.iter().enumerate() {
793
0
      val.to_css(dest)?;
794
0
      if idx < len - 1 {
795
0
        dest.delim(',', false)?;
796
0
      }
797
    }
798
799
0
    Ok(())
800
0
  }
801
}
802
803
property_bitflags! {
804
  #[derive(Default, Debug)]
805
  struct FontProperty: u8 {
806
    const FontFamily = 1 << 0;
807
    const FontSize = 1 << 1;
808
    const FontStyle = 1 << 2;
809
    const FontWeight = 1 << 3;
810
    const FontStretch = 1 << 4;
811
    const LineHeight = 1 << 5;
812
    const FontVariantCaps = 1 << 6;
813
    const Font = Self::FontFamily.bits() | Self::FontSize.bits() | Self::FontStyle.bits() | Self::FontWeight.bits() | Self::FontStretch.bits() | Self::LineHeight.bits() | Self::FontVariantCaps.bits();
814
  }
815
}
816
817
#[derive(Default, Debug)]
818
pub(crate) struct FontHandler<'i> {
819
  family: Option<Vec<FontFamily<'i>>>,
820
  size: Option<FontSize>,
821
  style: Option<FontStyle>,
822
  weight: Option<FontWeight>,
823
  stretch: Option<FontStretch>,
824
  line_height: Option<LineHeight>,
825
  variant_caps: Option<FontVariantCaps>,
826
  flushed_properties: FontProperty,
827
  has_any: bool,
828
}
829
830
impl<'i> PropertyHandler<'i> for FontHandler<'i> {
831
0
  fn handle_property(
832
0
    &mut self,
833
0
    property: &Property<'i>,
834
0
    dest: &mut DeclarationList<'i>,
835
0
    context: &mut PropertyHandlerContext<'i, '_>,
836
0
  ) -> bool {
837
    use Property::*;
838
839
    macro_rules! flush {
840
      ($prop: ident, $val: expr) => {{
841
        if self.$prop.is_some() && self.$prop.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
842
          self.flush(dest, context);
843
        }
844
      }};
845
    }
846
847
    macro_rules! property {
848
      ($prop: ident, $val: ident) => {{
849
        flush!($prop, $val);
850
        self.$prop = Some($val.clone());
851
        self.has_any = true;
852
      }};
853
    }
854
855
0
    match property {
856
0
      FontFamily(val) => property!(family, val),
857
0
      FontSize(val) => property!(size, val),
858
0
      FontStyle(val) => property!(style, val),
859
0
      FontWeight(val) => property!(weight, val),
860
0
      FontStretch(val) => property!(stretch, val),
861
0
      FontVariantCaps(val) => property!(variant_caps, val),
862
0
      LineHeight(val) => property!(line_height, val),
863
0
      Font(val) => {
864
0
        flush!(family, &val.family);
865
0
        flush!(size, &val.size);
866
0
        flush!(style, &val.style);
867
0
        flush!(weight, &val.weight);
868
0
        flush!(stretch, &val.stretch);
869
0
        flush!(line_height, &val.line_height);
870
0
        flush!(variant_caps, &val.variant_caps);
871
0
        self.family = Some(val.family.clone());
872
0
        self.size = Some(val.size.clone());
873
0
        self.style = Some(val.style.clone());
874
0
        self.weight = Some(val.weight.clone());
875
0
        self.stretch = Some(val.stretch.clone());
876
0
        self.line_height = Some(val.line_height.clone());
877
0
        self.variant_caps = Some(val.variant_caps.clone());
878
0
        self.has_any = true;
879
        // TODO: reset other properties
880
      }
881
0
      Unparsed(val) if is_font_property(&val.property_id) => {
882
0
        self.flush(dest, context);
883
0
        self
884
0
          .flushed_properties
885
0
          .insert(FontProperty::try_from(&val.property_id).unwrap());
886
0
        dest.push(property.clone());
887
0
      }
888
0
      _ => return false,
889
    }
890
891
0
    true
892
0
  }
893
894
0
  fn finalize(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
895
0
    self.flush(decls, context);
896
0
    self.flushed_properties = FontProperty::empty();
897
0
  }
898
}
899
900
impl<'i> FontHandler<'i> {
901
0
  fn flush(&mut self, decls: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
902
0
    if !self.has_any {
903
0
      return;
904
0
    }
905
906
0
    self.has_any = false;
907
908
    macro_rules! push {
909
      ($prop: ident, $val: expr) => {
910
        decls.push(Property::$prop($val));
911
        self.flushed_properties.insert(FontProperty::$prop);
912
      };
913
    }
914
915
0
    let mut family = std::mem::take(&mut self.family);
916
0
    if !self.flushed_properties.contains(FontProperty::FontFamily) {
917
0
      family = compatible_font_family(family, !should_compile!(context.targets, FontFamilySystemUi));
918
0
    }
919
0
    let size = std::mem::take(&mut self.size);
920
0
    let style = std::mem::take(&mut self.style);
921
0
    let weight = std::mem::take(&mut self.weight);
922
0
    let stretch = std::mem::take(&mut self.stretch);
923
0
    let line_height = std::mem::take(&mut self.line_height);
924
0
    let variant_caps = std::mem::take(&mut self.variant_caps);
925
926
0
    if let Some(family) = &mut family {
927
0
      if family.len() > 1 {
928
        // Dedupe.
929
0
        let mut seen = HashSet::new();
930
0
        family.retain(|f| seen.insert(f.clone()));
931
0
      }
932
0
    }
933
934
0
    if family.is_some()
935
0
      && size.is_some()
936
0
      && style.is_some()
937
0
      && weight.is_some()
938
0
      && stretch.is_some()
939
0
      && line_height.is_some()
940
0
      && variant_caps.is_some()
941
    {
942
0
      let caps = variant_caps.unwrap();
943
0
      push!(
944
        Font,
945
        Font {
946
0
          family: family.unwrap(),
947
0
          size: size.unwrap(),
948
0
          style: style.unwrap(),
949
0
          weight: weight.unwrap(),
950
0
          stretch: stretch.unwrap(),
951
0
          line_height: line_height.unwrap(),
952
0
          variant_caps: if caps.is_css2() {
953
0
            caps
954
          } else {
955
0
            FontVariantCaps::default()
956
          },
957
        }
958
      );
959
960
      // The `font` property only accepts CSS 2.1 values for font-variant caps.
961
      // If we have a CSS 3+ value, we need to add a separate property.
962
0
      if !caps.is_css2() {
963
0
        push!(FontVariantCaps, variant_caps.unwrap());
964
0
      }
965
    } else {
966
0
      if let Some(val) = family {
967
0
        push!(FontFamily, val);
968
0
      }
969
970
0
      if let Some(val) = size {
971
0
        push!(FontSize, val);
972
0
      }
973
974
0
      if let Some(val) = style {
975
0
        push!(FontStyle, val);
976
0
      }
977
978
0
      if let Some(val) = variant_caps {
979
0
        push!(FontVariantCaps, val);
980
0
      }
981
982
0
      if let Some(val) = weight {
983
0
        push!(FontWeight, val);
984
0
      }
985
986
0
      if let Some(val) = stretch {
987
0
        push!(FontStretch, val);
988
0
      }
989
990
0
      if let Some(val) = line_height {
991
0
        push!(LineHeight, val);
992
0
      }
993
    }
994
0
  }
995
}
996
997
const SYSTEM_UI: FontFamily = FontFamily::Generic(GenericFontFamily::SystemUI);
998
999
const DEFAULT_SYSTEM_FONTS: &[&str] = &[
1000
  // #1: Supported as the '-apple-system' value (macOS, Safari >= 9.2 < 11, Firefox >= 43)
1001
  "-apple-system",
1002
  // #2: Supported as the 'BlinkMacSystemFont' value (macOS, Chrome < 56)
1003
  "BlinkMacSystemFont",
1004
  "Segoe UI",  // Windows >= Vista
1005
  "Roboto",    // Android >= 4
1006
  "Noto Sans", // Plasma >= 5.5
1007
  "Ubuntu",    // Ubuntu >= 10.10
1008
  "Cantarell", // GNOME >= 3
1009
  "Helvetica Neue",
1010
];
1011
1012
/// [`system-ui`](https://www.w3.org/TR/css-fonts-4/#system-ui-def) is a special generic font family
1013
/// It is platform dependent but if not supported by the target will simply be ignored
1014
/// This list is an attempt at providing that support
1015
#[inline]
1016
0
fn compatible_font_family(mut family: Option<Vec<FontFamily>>, is_supported: bool) -> Option<Vec<FontFamily>> {
1017
0
  if is_supported {
1018
0
    return family;
1019
0
  }
1020
1021
0
  if let Some(families) = &mut family {
1022
0
    if let Some(position) = families.iter().position(|v| *v == SYSTEM_UI) {
1023
0
      families.splice(
1024
0
        (position + 1)..(position + 1),
1025
0
        DEFAULT_SYSTEM_FONTS
1026
0
          .iter()
1027
0
          .map(|name| FontFamily::FamilyName(FamilyName(CowArcStr::from(*name)))),
1028
      );
1029
0
    }
1030
0
  }
1031
1032
0
  return family;
1033
0
}
1034
1035
#[inline]
1036
0
fn is_font_property(property_id: &PropertyId) -> bool {
1037
0
  match property_id {
1038
    PropertyId::FontFamily
1039
    | PropertyId::FontSize
1040
    | PropertyId::FontStyle
1041
    | PropertyId::FontWeight
1042
    | PropertyId::FontStretch
1043
    | PropertyId::FontVariantCaps
1044
    | PropertyId::LineHeight
1045
0
    | PropertyId::Font => true,
1046
0
    _ => false,
1047
  }
1048
0
}