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