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: Option<Color>,
31}
32
33#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
34pub struct HighlightStyle {
35 pub color: Color,
36 pub font_properties: Properties,
37 pub underline: Option<Color>,
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: UnderlineStyleJson,
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: UnderlineStyleJson,
78}
79
80#[derive(Deserialize)]
81#[serde(untagged)]
82enum UnderlineStyleJson {
83 Underlined(bool),
84 UnderlinedWithColor(Color),
85}
86
87impl TextStyle {
88 pub fn new(
89 font_family_name: impl Into<Arc<str>>,
90 font_size: f32,
91 font_properties: Properties,
92 underline: Option<Color>,
93 color: Color,
94 font_cache: &FontCache,
95 ) -> anyhow::Result<Self> {
96 let font_family_name = font_family_name.into();
97 let font_family_id = font_cache.load_family(&[&font_family_name])?;
98 let font_id = font_cache.select_font(font_family_id, &font_properties)?;
99 Ok(Self {
100 color,
101 font_family_name,
102 font_family_id,
103 font_id,
104 font_size,
105 font_properties,
106 underline,
107 })
108 }
109
110 pub fn to_run(&self) -> RunStyle {
111 RunStyle {
112 font_id: self.font_id,
113 color: self.color,
114 underline: self.underline,
115 }
116 }
117
118 fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
119 FONT_CACHE.with(|font_cache| {
120 if let Some(font_cache) = font_cache.borrow().as_ref() {
121 let font_properties = properties_from_json(json.weight, json.italic);
122 Self::new(
123 json.family,
124 json.size,
125 font_properties,
126 underline_from_json(json.underline, json.color),
127 json.color,
128 font_cache,
129 )
130 } else {
131 Err(anyhow!(
132 "TextStyle can only be deserialized within a call to with_font_cache"
133 ))
134 }
135 })
136 }
137
138 pub fn line_height(&self, font_cache: &FontCache) -> f32 {
139 font_cache.line_height(self.font_id, self.font_size)
140 }
141
142 pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
143 font_cache.cap_height(self.font_id, self.font_size)
144 }
145
146 pub fn x_height(&self, font_cache: &FontCache) -> f32 {
147 font_cache.x_height(self.font_id, self.font_size)
148 }
149
150 pub fn em_width(&self, font_cache: &FontCache) -> f32 {
151 font_cache.em_width(self.font_id, self.font_size)
152 }
153
154 pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
155 font_cache.em_advance(self.font_id, self.font_size)
156 }
157
158 pub fn descent(&self, font_cache: &FontCache) -> f32 {
159 font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
160 }
161
162 pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
163 font_cache.baseline_offset(self.font_id, self.font_size)
164 }
165
166 fn em_scale(&self, font_cache: &FontCache) -> f32 {
167 font_cache.em_scale(self.font_id, self.font_size)
168 }
169}
170
171impl From<TextStyle> for HighlightStyle {
172 fn from(other: TextStyle) -> Self {
173 Self {
174 color: other.color,
175 font_properties: other.font_properties,
176 underline: other.underline,
177 }
178 }
179}
180
181impl Default for UnderlineStyleJson {
182 fn default() -> Self {
183 Self::Underlined(false)
184 }
185}
186
187impl Default for TextStyle {
188 fn default() -> Self {
189 FONT_CACHE.with(|font_cache| {
190 let font_cache = font_cache.borrow();
191 let font_cache = font_cache
192 .as_ref()
193 .expect("TextStyle::default can only be called within a call to with_font_cache");
194
195 let font_family_name = Arc::from("Courier");
196 let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap();
197 let font_id = font_cache
198 .select_font(font_family_id, &Default::default())
199 .unwrap();
200 Self {
201 color: Default::default(),
202 font_family_name,
203 font_family_id,
204 font_id,
205 font_size: 14.,
206 font_properties: Default::default(),
207 underline: Default::default(),
208 }
209 })
210 }
211}
212
213impl HighlightStyle {
214 fn from_json(json: HighlightStyleJson) -> Self {
215 let font_properties = properties_from_json(json.weight, json.italic);
216 Self {
217 color: json.color,
218 font_properties,
219 underline: underline_from_json(json.underline, json.color),
220 }
221 }
222}
223
224impl From<Color> for HighlightStyle {
225 fn from(color: Color) -> Self {
226 Self {
227 color,
228 font_properties: Default::default(),
229 underline: None,
230 }
231 }
232}
233
234impl<'de> Deserialize<'de> for TextStyle {
235 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
236 where
237 D: serde::Deserializer<'de>,
238 {
239 Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
240 .map_err(|e| de::Error::custom(e))?)
241 }
242}
243
244impl ToJson for TextStyle {
245 fn to_json(&self) -> Value {
246 json!({
247 "color": self.color.to_json(),
248 "font_family": self.font_family_name.as_ref(),
249 "font_properties": self.font_properties.to_json(),
250 })
251 }
252}
253
254impl<'de> Deserialize<'de> for HighlightStyle {
255 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256 where
257 D: serde::Deserializer<'de>,
258 {
259 let json = serde_json::Value::deserialize(deserializer)?;
260 if json.is_object() {
261 Ok(Self::from_json(
262 serde_json::from_value(json).map_err(de::Error::custom)?,
263 ))
264 } else {
265 Ok(Self {
266 color: serde_json::from_value(json).map_err(de::Error::custom)?,
267 font_properties: Properties::new(),
268 underline: None,
269 })
270 }
271 }
272}
273
274fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option<Color> {
275 match json {
276 UnderlineStyleJson::Underlined(false) => None,
277 UnderlineStyleJson::Underlined(true) => Some(text_color),
278 UnderlineStyleJson::UnderlinedWithColor(color) => Some(color),
279 }
280}
281
282fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
283 let weight = match weight.unwrap_or(WeightJson::normal) {
284 WeightJson::thin => Weight::THIN,
285 WeightJson::extra_light => Weight::EXTRA_LIGHT,
286 WeightJson::light => Weight::LIGHT,
287 WeightJson::normal => Weight::NORMAL,
288 WeightJson::medium => Weight::MEDIUM,
289 WeightJson::semibold => Weight::SEMIBOLD,
290 WeightJson::bold => Weight::BOLD,
291 WeightJson::extra_bold => Weight::EXTRA_BOLD,
292 WeightJson::black => Weight::BLACK,
293 };
294 let style = if italic { Style::Italic } else { Style::Normal };
295 *Properties::new().weight(weight).style(style)
296}
297
298impl ToJson for Properties {
299 fn to_json(&self) -> crate::json::Value {
300 json!({
301 "style": self.style.to_json(),
302 "weight": self.weight.to_json(),
303 "stretch": self.stretch.to_json(),
304 })
305 }
306}
307
308impl ToJson for Style {
309 fn to_json(&self) -> crate::json::Value {
310 match self {
311 Style::Normal => json!("normal"),
312 Style::Italic => json!("italic"),
313 Style::Oblique => json!("oblique"),
314 }
315 }
316}
317
318impl ToJson for Weight {
319 fn to_json(&self) -> crate::json::Value {
320 if self.0 == Weight::THIN.0 {
321 json!("thin")
322 } else if self.0 == Weight::EXTRA_LIGHT.0 {
323 json!("extra light")
324 } else if self.0 == Weight::LIGHT.0 {
325 json!("light")
326 } else if self.0 == Weight::NORMAL.0 {
327 json!("normal")
328 } else if self.0 == Weight::MEDIUM.0 {
329 json!("medium")
330 } else if self.0 == Weight::SEMIBOLD.0 {
331 json!("semibold")
332 } else if self.0 == Weight::BOLD.0 {
333 json!("bold")
334 } else if self.0 == Weight::EXTRA_BOLD.0 {
335 json!("extra bold")
336 } else if self.0 == Weight::BLACK.0 {
337 json!("black")
338 } else {
339 json!(self.0)
340 }
341 }
342}
343
344impl ToJson for Stretch {
345 fn to_json(&self) -> serde_json::Value {
346 json!(self.0)
347 }
348}
349
350pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
351where
352 F: FnOnce() -> T,
353{
354 FONT_CACHE.with(|cache| {
355 *cache.borrow_mut() = Some(font_cache);
356 let result = callback();
357 cache.borrow_mut().take();
358 result
359 })
360}