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