1use std::fmt::{self, Debug, Formatter};
2
3use ecow::EcoString;
4use serde::{Deserialize, Serialize};
5
6use crate::foundations::{Cast, IntoValue, Repr, cast};
7use crate::layout::Ratio;
8
9#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
11#[derive(Serialize, Deserialize)]
12pub struct FontVariant {
13 pub style: FontStyle,
15 pub weight: FontWeight,
17 pub stretch: FontStretch,
19}
20
21impl FontVariant {
22 pub fn new(style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Self {
24 Self { style, weight, stretch }
25 }
26}
27
28impl Debug for FontVariant {
29 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
30 write!(f, "{:?}-{:?}-{:?}", self.style, self.weight, self.stretch)
31 }
32}
33
34#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
36#[derive(Cast, Serialize, Deserialize)]
37#[serde(rename_all = "kebab-case")]
38pub enum FontStyle {
39 Normal,
41 Italic,
43 Oblique,
45}
46
47impl FontStyle {
48 pub fn distance(self, other: Self) -> u16 {
50 if self == other {
51 0
52 } else if self != Self::Normal && other != Self::Normal {
53 1
54 } else {
55 2
56 }
57 }
58}
59
60impl Default for FontStyle {
61 fn default() -> Self {
62 Self::Normal
63 }
64}
65
66impl From<usvg::FontStyle> for FontStyle {
67 fn from(style: usvg::FontStyle) -> Self {
68 match style {
69 usvg::FontStyle::Normal => Self::Normal,
70 usvg::FontStyle::Italic => Self::Italic,
71 usvg::FontStyle::Oblique => Self::Oblique,
72 }
73 }
74}
75
76#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
78#[derive(Serialize, Deserialize)]
79#[serde(transparent)]
80pub struct FontWeight(pub(super) u16);
81
82impl FontWeight {
85 pub const THIN: Self = Self(100);
87
88 pub const EXTRALIGHT: Self = Self(200);
90
91 pub const LIGHT: Self = Self(300);
93
94 pub const REGULAR: Self = Self(400);
96
97 pub const MEDIUM: Self = Self(500);
99
100 pub const SEMIBOLD: Self = Self(600);
102
103 pub const BOLD: Self = Self(700);
105
106 pub const EXTRABOLD: Self = Self(800);
108
109 pub const BLACK: Self = Self(900);
111
112 pub fn from_number(weight: u16) -> Self {
115 Self(weight.clamp(100, 900))
116 }
117
118 pub fn to_number(self) -> u16 {
120 self.0
121 }
122
123 pub fn thicken(self, delta: i16) -> Self {
125 Self((self.0 as i16).saturating_add(delta).clamp(100, 900) as u16)
126 }
127
128 pub fn distance(self, other: Self) -> u16 {
130 (self.0 as i16 - other.0 as i16).unsigned_abs()
131 }
132}
133
134impl Default for FontWeight {
135 fn default() -> Self {
136 Self::REGULAR
137 }
138}
139
140impl Debug for FontWeight {
141 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
142 write!(f, "{}", self.0)
143 }
144}
145
146impl From<fontdb::Weight> for FontWeight {
147 fn from(weight: fontdb::Weight) -> Self {
148 Self::from_number(weight.0)
149 }
150}
151
152cast! {
153 FontWeight,
154 self => IntoValue::into_value(match self {
155 FontWeight::THIN => "thin",
156 FontWeight::EXTRALIGHT => "extralight",
157 FontWeight::LIGHT => "light",
158 FontWeight::REGULAR => "regular",
159 FontWeight::MEDIUM => "medium",
160 FontWeight::SEMIBOLD => "semibold",
161 FontWeight::BOLD => "bold",
162 FontWeight::EXTRABOLD => "extrabold",
163 FontWeight::BLACK => "black",
164 _ => return self.to_number().into_value(),
165 }),
166 v: i64 => Self::from_number(v.clamp(0, u16::MAX as i64) as u16),
167 "thin" => Self::THIN,
169 "extralight" => Self::EXTRALIGHT,
171 "light" => Self::LIGHT,
173 "regular" => Self::REGULAR,
175 "medium" => Self::MEDIUM,
177 "semibold" => Self::SEMIBOLD,
179 "bold" => Self::BOLD,
181 "extrabold" => Self::EXTRABOLD,
183 "black" => Self::BLACK,
185}
186
187#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
189#[derive(Serialize, Deserialize)]
190#[serde(transparent)]
191pub struct FontStretch(pub(super) u16);
192
193impl FontStretch {
194 pub const ULTRA_CONDENSED: Self = Self(500);
196
197 pub const EXTRA_CONDENSED: Self = Self(625);
199
200 pub const CONDENSED: Self = Self(750);
202
203 pub const SEMI_CONDENSED: Self = Self(875);
205
206 pub const NORMAL: Self = Self(1000);
208
209 pub const SEMI_EXPANDED: Self = Self(1125);
211
212 pub const EXPANDED: Self = Self(1250);
214
215 pub const EXTRA_EXPANDED: Self = Self(1500);
217
218 pub const ULTRA_EXPANDED: Self = Self(2000);
220
221 pub fn from_ratio(ratio: Ratio) -> Self {
224 Self((ratio.get().clamp(0.5, 2.0) * 1000.0) as u16)
225 }
226
227 pub fn from_number(stretch: u16) -> Self {
230 match stretch {
231 0 | 1 => Self::ULTRA_CONDENSED,
232 2 => Self::EXTRA_CONDENSED,
233 3 => Self::CONDENSED,
234 4 => Self::SEMI_CONDENSED,
235 5 => Self::NORMAL,
236 6 => Self::SEMI_EXPANDED,
237 7 => Self::EXPANDED,
238 8 => Self::EXTRA_EXPANDED,
239 _ => Self::ULTRA_EXPANDED,
240 }
241 }
242
243 pub fn to_ratio(self) -> Ratio {
245 Ratio::new(self.0 as f64 / 1000.0)
246 }
247
248 pub fn round(self) -> Self {
250 match self.0 {
251 ..=562 => Self::ULTRA_CONDENSED,
252 563..=687 => Self::EXTRA_CONDENSED,
253 688..=812 => Self::CONDENSED,
254 813..=937 => Self::SEMI_CONDENSED,
255 938..=1062 => Self::NORMAL,
256 1063..=1187 => Self::SEMI_EXPANDED,
257 1188..=1374 => Self::EXPANDED,
258 1375..=1749 => Self::EXTRA_EXPANDED,
259 1750.. => Self::ULTRA_EXPANDED,
260 }
261 }
262
263 pub fn distance(self, other: Self) -> Ratio {
265 (self.to_ratio() - other.to_ratio()).abs()
266 }
267}
268
269impl Default for FontStretch {
270 fn default() -> Self {
271 Self::NORMAL
272 }
273}
274
275impl Repr for FontStretch {
276 fn repr(&self) -> EcoString {
277 self.to_ratio().repr()
278 }
279}
280
281impl From<usvg::FontStretch> for FontStretch {
282 fn from(stretch: usvg::FontStretch) -> Self {
283 match stretch {
284 usvg::FontStretch::UltraCondensed => Self::ULTRA_CONDENSED,
285 usvg::FontStretch::ExtraCondensed => Self::EXTRA_CONDENSED,
286 usvg::FontStretch::Condensed => Self::CONDENSED,
287 usvg::FontStretch::SemiCondensed => Self::SEMI_CONDENSED,
288 usvg::FontStretch::Normal => Self::NORMAL,
289 usvg::FontStretch::SemiExpanded => Self::SEMI_EXPANDED,
290 usvg::FontStretch::Expanded => Self::EXPANDED,
291 usvg::FontStretch::ExtraExpanded => Self::EXTRA_EXPANDED,
292 usvg::FontStretch::UltraExpanded => Self::ULTRA_EXPANDED,
293 }
294 }
295}
296
297cast! {
298 FontStretch,
299 self => self.to_ratio().into_value(),
300 v: Ratio => Self::from_ratio(v),
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_font_weight_distance() {
309 let d = |a, b| FontWeight(a).distance(FontWeight(b));
310 assert_eq!(d(500, 200), 300);
311 assert_eq!(d(500, 500), 0);
312 assert_eq!(d(500, 900), 400);
313 assert_eq!(d(10, 100), 90);
314 }
315
316 #[test]
317 fn test_font_stretch_debug() {
318 assert_eq!(FontStretch::EXPANDED.repr(), "125%")
319 }
320}