1use core::fmt;
12use easy_cast::Cast;
13
14#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
34pub struct FontWidth(u16);
35
36impl FontWidth {
37 pub const ULTRA_CONDENSED: Self = Self(128);
39
40 pub const EXTRA_CONDENSED: Self = Self(160);
42
43 pub const CONDENSED: Self = Self(192);
45
46 pub const SEMI_CONDENSED: Self = Self(224);
48
49 pub const NORMAL: Self = Self(256);
51
52 pub const SEMI_EXPANDED: Self = Self(288);
54
55 pub const EXPANDED: Self = Self(320);
57
58 pub const EXTRA_EXPANDED: Self = Self(384);
60
61 pub const ULTRA_EXPANDED: Self = Self(512);
63}
64
65impl FontWidth {
66 pub fn from_ratio(ratio: f32) -> Self {
79 let value = (ratio * 256.0).round();
80 assert!(0.0 <= value && value <= (u16::MAX as f32));
81 Self(value as u16)
82 }
83
84 pub fn from_percentage(percentage: f32) -> Self {
97 Self::from_ratio(percentage / 100.0)
98 }
99
100 pub fn ratio(self) -> f32 {
111 (self.0 as f32) / 256.0
112 }
113
114 pub fn percentage(self) -> f32 {
118 self.ratio() * 100.0
119 }
120
121 pub fn is_normal(self) -> bool {
125 self == Self::NORMAL
126 }
127
128 pub fn is_condensed(self) -> bool {
132 self < Self::NORMAL
133 }
134
135 pub fn is_expanded(self) -> bool {
139 self > Self::NORMAL
140 }
141
142 pub fn parse(s: &str) -> Option<Self> {
153 let s = s.trim();
154 Some(match s {
155 "ultra-condensed" => Self::ULTRA_CONDENSED,
156 "extra-condensed" => Self::EXTRA_CONDENSED,
157 "condensed" => Self::CONDENSED,
158 "semi-condensed" => Self::SEMI_CONDENSED,
159 "normal" => Self::NORMAL,
160 "semi-expanded" => Self::SEMI_EXPANDED,
161 "expanded" => Self::EXPANDED,
162 "extra-expanded" => Self::EXTRA_EXPANDED,
163 "ultra-expanded" => Self::ULTRA_EXPANDED,
164 _ => {
165 if s.ends_with('%') {
166 let p = s.get(..s.len() - 1)?.parse::<f32>().ok()?;
167 return Some(Self::from_percentage(p));
168 }
169 return None;
170 }
171 })
172 }
173}
174
175impl fmt::Display for FontWidth {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 let keyword = match *self {
178 v if v == Self::ULTRA_CONDENSED => "ultra-condensed",
179 v if v == Self::EXTRA_CONDENSED => "extra-condensed",
180 v if v == Self::CONDENSED => "condensed",
181 v if v == Self::SEMI_CONDENSED => "semi-condensed",
182 v if v == Self::NORMAL => "normal",
183 v if v == Self::SEMI_EXPANDED => "semi-expanded",
184 v if v == Self::EXPANDED => "expanded",
185 v if v == Self::EXTRA_EXPANDED => "extra-expanded",
186 v if v == Self::ULTRA_EXPANDED => "ultra-expanded",
187 _ => {
188 return write!(f, "{}%", self.percentage());
189 }
190 };
191 write!(f, "{keyword}")
192 }
193}
194
195impl Default for FontWidth {
196 fn default() -> Self {
197 Self::NORMAL
198 }
199}
200
201impl From<FontWidth> for fontique::FontWidth {
202 #[inline]
203 fn from(width: FontWidth) -> Self {
204 fontique::FontWidth::from_ratio(width.ratio())
205 }
206}
207
208#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
222pub struct FontWeight(u16);
223
224impl FontWeight {
225 pub const THIN: Self = Self(100);
227
228 pub const EXTRA_LIGHT: Self = Self(200);
230
231 pub const LIGHT: Self = Self(300);
233
234 pub const SEMI_LIGHT: Self = Self(350);
236
237 pub const NORMAL: Self = Self(400);
239
240 pub const MEDIUM: Self = Self(500);
242
243 pub const SEMI_BOLD: Self = Self(600);
245
246 pub const BOLD: Self = Self(700);
248
249 pub const EXTRA_BOLD: Self = Self(800);
251
252 pub const BLACK: Self = Self(900);
254
255 pub const EXTRA_BLACK: Self = Self(950);
257}
258
259impl FontWeight {
260 pub fn new(weight: u16) -> Self {
262 Self(weight)
263 }
264
265 pub fn value(self) -> u16 {
267 self.0
268 }
269
270 pub fn parse(s: &str) -> Option<Self> {
284 let s = s.trim();
285 Some(match s {
286 "normal" => Self::NORMAL,
287 "bold" => Self::BOLD,
288 _ => Self(s.parse::<u16>().ok()?),
289 })
290 }
291}
292
293impl Default for FontWeight {
294 fn default() -> Self {
295 Self::NORMAL
296 }
297}
298
299impl fmt::Display for FontWeight {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 let keyword = match self.0 {
302 400 => "normal",
303 700 => "bold",
304 _ => return write!(f, "{}", self.0),
305 };
306 write!(f, "{keyword}")
307 }
308}
309
310impl From<FontWeight> for fontique::FontWeight {
311 #[inline]
312 fn from(weight: FontWeight) -> Self {
313 fontique::FontWeight::new(weight.value().cast())
314 }
315}
316
317#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Hash)]
332pub enum FontStyle {
333 #[default]
335 Normal,
336 Italic,
339 Oblique(Option<i16>),
345}
346
347impl FontStyle {
348 pub fn parse(mut s: &str) -> Option<Self> {
350 s = s.trim();
351 Some(match s {
352 "normal" => Self::Normal,
353 "italic" => Self::Italic,
354 "oblique" => Self::Oblique(None),
355 _ => {
356 if s.starts_with("oblique ") {
357 s = s.get(8..)?;
358 if s.ends_with("deg") {
359 s = s.get(..s.len() - 3)?;
360 if let Ok(degrees) = s.trim().parse::<f32>() {
361 return Some(Self::from_degrees(degrees));
362 }
363 } else if s.ends_with("grad") {
364 s = s.get(..s.len() - 4)?;
365 if let Ok(gradians) = s.trim().parse::<f32>() {
366 return Some(Self::from_degrees(gradians / 400.0 * 360.0));
367 }
368 } else if s.ends_with("rad") {
369 s = s.get(..s.len() - 3)?;
370 if let Ok(radians) = s.trim().parse::<f32>() {
371 return Some(Self::from_degrees(radians.to_degrees()));
372 }
373 } else if s.ends_with("turn") {
374 s = s.get(..s.len() - 4)?;
375 if let Ok(turns) = s.trim().parse::<f32>() {
376 return Some(Self::from_degrees(turns * 360.0));
377 }
378 }
379 return Some(Self::Oblique(None));
380 }
381 return None;
382 }
383 })
384 }
385}
386
387impl FontStyle {
388 pub const fn oblique_degrees(angle: Option<i16>) -> f32 {
390 if let Some(a) = angle {
391 (a as f32) / 256.0
392 } else {
393 14.0
394 }
395 }
396
397 pub fn from_degrees(degrees: f32) -> Self {
401 assert!(-90.0 <= degrees && degrees <= 90.0);
402 let a = (degrees * 256.0).round();
403 Self::Oblique(Some(a as i16))
404 }
405}
406
407impl fmt::Display for FontStyle {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 let value = match *self {
410 Self::Normal => "normal",
411 Self::Italic => "italic",
412 Self::Oblique(None) => "oblique",
413 Self::Oblique(Some(angle)) => {
414 let degrees = (angle as f32) / 256.0;
415 return write!(f, "oblique {degrees}deg");
416 }
417 };
418 write!(f, "{value}")
419 }
420}
421
422impl From<FontStyle> for fontique::FontStyle {
423 #[inline]
424 fn from(style: FontStyle) -> Self {
425 match style {
426 FontStyle::Normal => fontique::FontStyle::Normal,
427 FontStyle::Italic => fontique::FontStyle::Italic,
428 FontStyle::Oblique(None) => fontique::FontStyle::Oblique(None),
429 FontStyle::Oblique(slant) => {
430 fontique::FontStyle::Oblique(Some(FontStyle::oblique_degrees(slant)))
431 }
432 }
433 }
434}
435
436#[cfg(feature = "serde")]
437mod serde_impls {
438 use super::*;
439 use serde::{de, ser};
440
441 impl ser::Serialize for FontWidth {
442 fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
443 ser.serialize_str(&format!("{}", self))
444 }
445 }
446
447 impl<'de> de::Deserialize<'de> for FontWidth {
448 fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FontWidth, D::Error> {
449 struct Visitor;
450 impl<'de> de::Visitor<'de> for Visitor {
451 type Value = FontWidth;
452
453 fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
454 write!(fmt, "a keyword or integer percentage")
455 }
456
457 fn visit_str<E: de::Error>(self, s: &str) -> Result<FontWidth, E> {
458 FontWidth::parse(s)
459 .ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
460 }
461 }
462
463 de.deserialize_str(Visitor)
464 }
465 }
466
467 impl ser::Serialize for FontWeight {
468 fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
469 ser.serialize_str(&format!("{}", self))
470 }
471 }
472
473 impl<'de> de::Deserialize<'de> for FontWeight {
474 fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FontWeight, D::Error> {
475 struct Visitor;
476 impl<'de> de::Visitor<'de> for Visitor {
477 type Value = FontWeight;
478
479 fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
480 write!(fmt, "a keyword or integer")
481 }
482
483 fn visit_str<E: de::Error>(self, s: &str) -> Result<FontWeight, E> {
484 FontWeight::parse(s)
485 .ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
486 }
487 }
488
489 de.deserialize_str(Visitor)
490 }
491 }
492
493 impl ser::Serialize for FontStyle {
494 fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
495 ser.serialize_str(&format!("{}", self))
496 }
497 }
498
499 impl<'de> de::Deserialize<'de> for FontStyle {
500 fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<FontStyle, D::Error> {
501 struct Visitor;
502 impl<'de> de::Visitor<'de> for Visitor {
503 type Value = FontStyle;
504
505 fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
506 write!(fmt, "a keyword or 'oblique' value")
507 }
508
509 fn visit_str<E: de::Error>(self, s: &str) -> Result<FontStyle, E> {
510 FontStyle::parse(s)
511 .ok_or_else(|| de::Error::invalid_value(de::Unexpected::Str(s), &self))
512 }
513 }
514
515 de.deserialize_str(Visitor)
516 }
517 }
518}