textiler_core/theme/
typography.rs

1use std::collections::HashMap;
2/// Typography provides
3use std::fmt::{Display, Formatter};
4
5use serde::{Deserialize, Deserializer};
6use yew::html::IntoPropValue;
7
8use crate::style::Size;
9use crate::theme::sx::SxValue;
10use crate::Sx;
11
12/// The level for typography
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub enum TypographyLevel {
15    H1,
16    H2,
17    H3,
18    H4,
19    Title { size: Size },
20    Body { size: Size },
21    Custom(String),
22    Star,
23}
24
25impl<'de> Deserialize<'de> for TypographyLevel {
26    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
27    where
28        D: Deserializer<'de>,
29    {
30        let as_string = <&'de str as Deserialize<'de>>::deserialize(deserializer)?;
31        let parsed = TypographyLevel::from(as_string);
32        Ok(parsed)
33    }
34}
35
36impl Display for TypographyLevel {
37    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38        match self {
39            TypographyLevel::H1 => {
40                write!(f, "h1")
41            }
42            TypographyLevel::H2 => {
43                write!(f, "h2")
44            }
45            TypographyLevel::H3 => {
46                write!(f, "h3")
47            }
48            TypographyLevel::H4 => {
49                write!(f, "h4")
50            }
51            TypographyLevel::Title { size } => {
52                write!(f, "title-{size}")
53            }
54            TypographyLevel::Body { size } => {
55                write!(f, "body-{size}")
56            }
57            TypographyLevel::Custom(s) => {
58                write!(f, "{s}")
59            }
60            TypographyLevel::Star => {
61                write!(f, "*")
62            }
63        }
64    }
65}
66
67impl IntoPropValue<TypographyLevel> for &str {
68    fn into_prop_value(self) -> TypographyLevel {
69        TypographyLevel::from(self)
70    }
71}
72
73impl From<&str> for TypographyLevel {
74    fn from(value: &str) -> Self {
75        match value {
76            "*" => TypographyLevel::Star,
77            "h1" => TypographyLevel::H1,
78            "h2" => TypographyLevel::H2,
79            "h3" => TypographyLevel::H3,
80            "h4" => TypographyLevel::H4,
81            title if title.starts_with("title-") => {
82                let size = title.strip_prefix("title-").unwrap();
83                let size = IntoPropValue::<Size>::into_prop_value(size);
84                TypographyLevel::Title { size }
85            }
86            body if body.starts_with("body-") => {
87                let size = body.strip_prefix("body-").unwrap();
88                let size = IntoPropValue::<Size>::into_prop_value(size);
89                TypographyLevel::Body { size }
90            }
91            other => TypographyLevel::Custom(other.to_string()),
92        }
93    }
94}
95
96impl Default for TypographyLevel {
97    fn default() -> Self {
98        TypographyLevel::Body { size: Size::Md }
99    }
100}
101
102/// Provides the scale details for typography, giving weights, sizes, and margins for each level
103#[derive(Debug, Default, Clone, PartialEq)]
104pub struct TypographyScale {
105    /// The scale details for given levels
106    levels: HashMap<TypographyLevel, LevelScale>,
107}
108
109impl TypographyScale {
110    /// Creates a new scale from
111    pub fn new<I: IntoIterator<Item = (TypographyLevel, LevelScale)>>(levels: I) -> Self {
112        Self {
113            levels: levels.into_iter().collect(),
114        }
115    }
116
117    /// Shortcut for getting an [`Sx`](Sx) instance for a given, and also merges it with the "*" sx level
118    pub fn at(&self, level: &TypographyLevel) -> Option<Sx> {
119        self.scale(level)
120            .map(LevelScale::sx)
121            .map(|sx| match self.scale(&TypographyLevel::Star) {
122                None => sx,
123                Some(star_sx) => {
124                    let star_sx = star_sx.sx();
125                    sx.merge(star_sx)
126                }
127            })
128    }
129
130    /// Gets the scale at the given level
131    pub fn scale(&self, level: &TypographyLevel) -> Option<&LevelScale> {
132        self.levels.get(level)
133    }
134
135    /// Gets a mutable reference to the scale at the given level
136    pub fn scale_mut(&mut self, level: &TypographyLevel) -> Option<&mut LevelScale> {
137        self.levels.get_mut(level)
138    }
139
140    /// Insert a scale for the given level
141    pub fn insert(&mut self, level: TypographyLevel, level_sx: LevelScale) {
142        let _ = self.levels.insert(level, level_sx);
143    }
144}
145
146impl<'a> IntoIterator for &'a TypographyScale {
147    type Item = (&'a TypographyLevel, &'a LevelScale);
148    type IntoIter = <&'a HashMap<TypographyLevel, LevelScale> as IntoIterator>::IntoIter;
149
150    fn into_iter(self) -> Self::IntoIter {
151        self.levels.iter()
152    }
153}
154
155/// Details for a specific level within the [`TypographyScale`](TypographyScale)
156#[derive(Debug, Clone, PartialEq)]
157pub struct LevelScale {
158    sx: Sx,
159}
160
161impl LevelScale {
162    /// Creates a new level scale from an sx instance
163    pub fn new(sx: Sx) -> Self {
164        Self { sx }
165    }
166
167    /// Creates an [`Sx`](Sx) instance for this level scale
168    pub fn sx(&self) -> Sx {
169        self.sx.clone()
170    }
171}
172
173impl From<Sx> for LevelScale {
174    fn from(value: Sx) -> Self {
175        Self::new(value)
176    }
177}