1use crate::{
2 color::Color,
3 font_cache::FamilyId,
4 json::{json, ToJson},
5 text_layout::RunStyle,
6 FontCache,
7};
8use anyhow::anyhow;
9pub use font_kit::{
10 metrics::Metrics,
11 properties::{Properties, Stretch, Style, Weight},
12};
13use serde::{de, Deserialize};
14use serde_json::Value;
15use std::{cell::RefCell, sync::Arc};
16
17#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
18pub struct FontId(pub usize);
19
20pub type GlyphId = u32;
21
22#[derive(Clone, Debug)]
23pub struct TextStyle {
24 pub color: Color,
25 pub font_family_name: Arc<str>,
26 pub font_family_id: FamilyId,
27 pub font_id: FontId,
28 pub font_size: f32,
29 pub font_properties: Properties,
30 pub underline: bool,
31}
32
33#[derive(Clone, Debug, Default)]
34pub struct HighlightStyle {
35 pub color: Color,
36 pub font_properties: Properties,
37 pub underline: bool,
38}
39
40#[allow(non_camel_case_types)]
41#[derive(Deserialize)]
42enum WeightJson {
43 thin,
44 extra_light,
45 light,
46 normal,
47 medium,
48 semibold,
49 bold,
50 extra_bold,
51 black,
52}
53
54thread_local! {
55 static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
56}
57
58#[derive(Deserialize)]
59struct TextStyleJson {
60 color: Color,
61 family: String,
62 weight: Option<WeightJson>,
63 size: f32,
64 #[serde(default)]
65 italic: bool,
66 #[serde(default)]
67 underline: bool,
68}
69
70#[derive(Deserialize)]
71struct HighlightStyleJson {
72 color: Color,
73 weight: Option<WeightJson>,
74 #[serde(default)]
75 italic: bool,
76 #[serde(default)]
77 underline: bool,
78}
79
80impl TextStyle {
81 pub fn new(
82 font_family_name: impl Into<Arc<str>>,
83 font_size: f32,
84 font_properties: Properties,
85 underline: bool,
86 color: Color,
87 font_cache: &FontCache,
88 ) -> anyhow::Result<Self> {
89 let font_family_name = font_family_name.into();
90 let font_family_id = font_cache.load_family(&[&font_family_name])?;
91 let font_id = font_cache.select_font(font_family_id, &font_properties)?;
92 Ok(Self {
93 color,
94 font_family_name,
95 font_family_id,
96 font_id,
97 font_size,
98 font_properties,
99 underline,
100 })
101 }
102
103 pub fn to_run(&self) -> RunStyle {
104 RunStyle {
105 font_id: self.font_id,
106 color: self.color,
107 underline: self.underline,
108 }
109 }
110
111 fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
112 FONT_CACHE.with(|font_cache| {
113 if let Some(font_cache) = font_cache.borrow().as_ref() {
114 let font_properties = properties_from_json(json.weight, json.italic);
115 Self::new(
116 json.family,
117 json.size,
118 font_properties,
119 json.underline,
120 json.color,
121 font_cache,
122 )
123 } else {
124 Err(anyhow!(
125 "TextStyle can only be deserialized within a call to with_font_cache"
126 ))
127 }
128 })
129 }
130
131 pub fn line_height(&self, font_cache: &FontCache) -> f32 {
132 font_cache.line_height(self.font_id, self.font_size)
133 }
134
135 pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
136 font_cache.cap_height(self.font_id, self.font_size)
137 }
138
139 pub fn x_height(&self, font_cache: &FontCache) -> f32 {
140 font_cache.x_height(self.font_id, self.font_size)
141 }
142
143 pub fn em_width(&self, font_cache: &FontCache) -> f32 {
144 font_cache.em_width(self.font_id, self.font_size)
145 }
146
147 pub fn descent(&self, font_cache: &FontCache) -> f32 {
148 font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
149 }
150
151 pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
152 font_cache.baseline_offset(self.font_id, self.font_size)
153 }
154
155 fn em_scale(&self, font_cache: &FontCache) -> f32 {
156 font_cache.em_scale(self.font_id, self.font_size)
157 }
158}
159
160impl From<TextStyle> for HighlightStyle {
161 fn from(other: TextStyle) -> Self {
162 Self {
163 color: other.color,
164 font_properties: other.font_properties,
165 underline: other.underline,
166 }
167 }
168}
169
170impl Default for TextStyle {
171 fn default() -> Self {
172 FONT_CACHE.with(|font_cache| {
173 let font_cache = font_cache.borrow();
174 let font_cache = font_cache
175 .as_ref()
176 .expect("TextStyle::default can only be called within a call to with_font_cache");
177
178 let font_family_name = Arc::from("Courier");
179 let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
180 let font_id = font_cache
181 .select_font(font_family_id, &Default::default())
182 .unwrap();
183 Self {
184 color: Default::default(),
185 font_family_name,
186 font_family_id,
187 font_id,
188 font_size: 14.,
189 font_properties: Default::default(),
190 underline: Default::default(),
191 }
192 })
193 }
194}
195
196impl HighlightStyle {
197 fn from_json(json: HighlightStyleJson) -> Self {
198 let font_properties = properties_from_json(json.weight, json.italic);
199 Self {
200 color: json.color,
201 font_properties,
202 underline: json.underline,
203 }
204 }
205}
206
207impl From<Color> for HighlightStyle {
208 fn from(color: Color) -> Self {
209 Self {
210 color,
211 font_properties: Default::default(),
212 underline: false,
213 }
214 }
215}
216
217impl<'de> Deserialize<'de> for TextStyle {
218 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219 where
220 D: serde::Deserializer<'de>,
221 {
222 Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
223 .map_err(|e| de::Error::custom(e))?)
224 }
225}
226
227impl ToJson for TextStyle {
228 fn to_json(&self) -> Value {
229 json!({
230 "color": self.color.to_json(),
231 "font_family": self.font_family_name.as_ref(),
232 "font_properties": self.font_properties.to_json(),
233 })
234 }
235}
236
237impl<'de> Deserialize<'de> for HighlightStyle {
238 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239 where
240 D: serde::Deserializer<'de>,
241 {
242 let json = serde_json::Value::deserialize(deserializer)?;
243 if json.is_object() {
244 Ok(Self::from_json(
245 serde_json::from_value(json).map_err(de::Error::custom)?,
246 ))
247 } else {
248 Ok(Self {
249 color: serde_json::from_value(json).map_err(de::Error::custom)?,
250 font_properties: Properties::new(),
251 underline: false,
252 })
253 }
254 }
255}
256
257fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
258 let weight = match weight.unwrap_or(WeightJson::normal) {
259 WeightJson::thin => Weight::THIN,
260 WeightJson::extra_light => Weight::EXTRA_LIGHT,
261 WeightJson::light => Weight::LIGHT,
262 WeightJson::normal => Weight::NORMAL,
263 WeightJson::medium => Weight::MEDIUM,
264 WeightJson::semibold => Weight::SEMIBOLD,
265 WeightJson::bold => Weight::BOLD,
266 WeightJson::extra_bold => Weight::EXTRA_BOLD,
267 WeightJson::black => Weight::BLACK,
268 };
269 let style = if italic { Style::Italic } else { Style::Normal };
270 *Properties::new().weight(weight).style(style)
271}
272
273impl ToJson for Properties {
274 fn to_json(&self) -> crate::json::Value {
275 json!({
276 "style": self.style.to_json(),
277 "weight": self.weight.to_json(),
278 "stretch": self.stretch.to_json(),
279 })
280 }
281}
282
283impl ToJson for Style {
284 fn to_json(&self) -> crate::json::Value {
285 match self {
286 Style::Normal => json!("normal"),
287 Style::Italic => json!("italic"),
288 Style::Oblique => json!("oblique"),
289 }
290 }
291}
292
293impl ToJson for Weight {
294 fn to_json(&self) -> crate::json::Value {
295 if self.0 == Weight::THIN.0 {
296 json!("thin")
297 } else if self.0 == Weight::EXTRA_LIGHT.0 {
298 json!("extra light")
299 } else if self.0 == Weight::LIGHT.0 {
300 json!("light")
301 } else if self.0 == Weight::NORMAL.0 {
302 json!("normal")
303 } else if self.0 == Weight::MEDIUM.0 {
304 json!("medium")
305 } else if self.0 == Weight::SEMIBOLD.0 {
306 json!("semibold")
307 } else if self.0 == Weight::BOLD.0 {
308 json!("bold")
309 } else if self.0 == Weight::EXTRA_BOLD.0 {
310 json!("extra bold")
311 } else if self.0 == Weight::BLACK.0 {
312 json!("black")
313 } else {
314 json!(self.0)
315 }
316 }
317}
318
319impl ToJson for Stretch {
320 fn to_json(&self) -> serde_json::Value {
321 json!(self.0)
322 }
323}
324
325pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
326where
327 F: FnOnce() -> T,
328{
329 FONT_CACHE.with(|cache| {
330 *cache.borrow_mut() = Some(font_cache);
331 let result = callback();
332 cache.borrow_mut().take();
333 result
334 })
335}