Skip to main content

takumi_css/style/properties/
font_size.rs

1use std::fmt;
2
3use cssparser::Parser;
4
5use crate::style::{
6  Animatable, Color, CssSyntaxKind, CssToken, FromCss, Length, MakeComputed, ParseResult,
7  SizingContext, ToCss, declare_enum_from_css_impl,
8};
9
10/// Absolute `font-size` keywords.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[non_exhaustive]
13pub enum FontSizeKeyword {
14  /// Maps to the `xx-small` keyword.
15  XXSmall,
16  /// Maps to the `x-small` keyword.
17  XSmall,
18  /// Maps to the `small` keyword.
19  Small,
20  /// Maps to the `medium` keyword.
21  #[default]
22  Medium,
23  /// Maps to the `large` keyword.
24  Large,
25  /// Maps to the `x-large` keyword.
26  XLarge,
27  /// Maps to the `xx-large` keyword.
28  XXLarge,
29  /// Maps to the `xxx-large` keyword.
30  XXXLarge,
31}
32
33impl FontSizeKeyword {
34  /// Resolves the keyword to its root-relative CSS length.
35  pub const fn to_length(self) -> Length {
36    match self {
37      Self::XXSmall => Length::Rem(0.6),
38      Self::XSmall => Length::Rem(0.75),
39      Self::Small => Length::Rem(8.0 / 9.0),
40      Self::Medium => Length::Rem(1.0),
41      Self::Large => Length::Rem(1.2),
42      Self::XLarge => Length::Rem(1.5),
43      Self::XXLarge => Length::Rem(2.0),
44      Self::XXXLarge => Length::Rem(3.0),
45    }
46  }
47}
48
49declare_enum_from_css_impl!(
50  FontSizeKeyword,
51  "xx-small" => FontSizeKeyword::XXSmall,
52  "x-small" => FontSizeKeyword::XSmall,
53  "small" => FontSizeKeyword::Small,
54  "medium" => FontSizeKeyword::Medium,
55  "large" => FontSizeKeyword::Large,
56  "x-large" => FontSizeKeyword::XLarge,
57  "xx-large" => FontSizeKeyword::XXLarge,
58  "xxx-large" => FontSizeKeyword::XXXLarge,
59);
60
61/// A `font-size` value, either a keyword or an explicit length.
62#[derive(Debug, Clone, Copy, PartialEq)]
63#[non_exhaustive]
64pub enum FontSize {
65  /// A CSS absolute-size keyword such as `medium`.
66  Keyword(FontSizeKeyword),
67  /// A concrete CSS length such as `16px` or `1rem`.
68  Length(Length),
69}
70
71impl FontSize {
72  pub fn to_px(self, sizing: &SizingContext, inherited_font_size: f32) -> f32 {
73    match self {
74      Self::Keyword(keyword) => keyword.to_length().to_px(sizing, inherited_font_size),
75      Self::Length(length) => length.to_px(sizing, inherited_font_size),
76    }
77  }
78}
79
80impl Default for FontSize {
81  fn default() -> Self {
82    Self::Keyword(FontSizeKeyword::Medium)
83  }
84}
85
86impl From<Length> for FontSize {
87  fn from(value: Length) -> Self {
88    Self::Length(value)
89  }
90}
91
92impl From<FontSizeKeyword> for FontSize {
93  fn from(value: FontSizeKeyword) -> Self {
94    Self::Keyword(value)
95  }
96}
97
98impl<'i> FromCss<'i> for FontSize {
99  fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
100    input
101      .try_parse(FontSizeKeyword::from_css)
102      .map(Self::Keyword)
103      .or_else(|_| Length::from_css(input).map(Self::Length))
104  }
105
106  const VALID_TOKENS: &'static [CssToken] = &[
107    CssToken::Keyword("xx-small"),
108    CssToken::Keyword("x-small"),
109    CssToken::Keyword("small"),
110    CssToken::Keyword("medium"),
111    CssToken::Keyword("large"),
112    CssToken::Keyword("x-large"),
113    CssToken::Keyword("xx-large"),
114    CssToken::Keyword("xxx-large"),
115    CssToken::Syntax(CssSyntaxKind::Length),
116  ];
117}
118
119impl MakeComputed for FontSize {
120  fn make_computed(&mut self, sizing: &SizingContext) {
121    if let Self::Length(length) = self {
122      length.make_computed(sizing);
123    }
124  }
125}
126
127impl Animatable for FontSize {
128  fn interpolate(
129    &mut self,
130    from: &Self,
131    to: &Self,
132    progress: f32,
133    sizing: &SizingContext,
134    current_color: Color,
135  ) {
136    let from_length = match *from {
137      Self::Keyword(keyword) => keyword.to_length(),
138      Self::Length(length) => length,
139    };
140    let to_length = match *to {
141      Self::Keyword(keyword) => keyword.to_length(),
142      Self::Length(length) => length,
143    };
144
145    let mut value = from_length;
146    value.interpolate(&from_length, &to_length, progress, sizing, current_color);
147    *self = Self::Length(value);
148  }
149}
150
151impl ToCss for FontSize {
152  fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
153    match self {
154      Self::Keyword(k) => k.to_css(dest),
155      Self::Length(l) => l.to_css(dest),
156    }
157  }
158}
159
160#[cfg(test)]
161mod tests {
162  use std::rc::Rc;
163
164  use taffy::Size;
165
166  use super::*;
167  use crate::{
168    style::{CalcArena, SizingContext},
169    viewport::Viewport,
170  };
171
172  #[test]
173  fn defaults_to_medium_keyword() {
174    assert_eq!(
175      FontSize::default(),
176      FontSize::Keyword(FontSizeKeyword::Medium)
177    );
178  }
179
180  #[test]
181  fn resolves_medium_keyword_to_default_font_size() {
182    let sizing = SizingContext {
183      viewport: Viewport::new((1200, 630)),
184      container_size: Size::NONE,
185      font_size: 16.0,
186      root_font_size: None,
187      line_height: 0.0,
188      root_line_height: None,
189      calc_arena: Rc::new(CalcArena::default()),
190    };
191
192    assert_eq!(FontSize::default().to_px(&sizing, sizing.font_size), 16.0);
193  }
194
195  #[test]
196  fn rem_font_size_in_descendant_does_not_double_apply_dpr() {
197    use crate::viewport::DEFAULT_FONT_SIZE;
198
199    let viewport = Viewport::new((1200, 630)).with_device_pixel_ratio(2.0);
200    let root_font_size_device_px = DEFAULT_FONT_SIZE * viewport.device_pixel_ratio;
201    let sizing = SizingContext {
202      viewport,
203      container_size: Size::NONE,
204      font_size: root_font_size_device_px,
205      root_font_size: Some(root_font_size_device_px),
206      line_height: 0.0,
207      root_line_height: None,
208      calc_arena: Rc::new(CalcArena::default()),
209    };
210
211    assert_eq!(
212      FontSize::Length(Length::Rem(0.5)).to_px(&sizing, sizing.font_size),
213      16.0
214    );
215    assert_eq!(
216      FontSize::Length(Length::Rem(1.0)).to_px(&sizing, sizing.font_size),
217      32.0
218    );
219  }
220}