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