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 schemars::JsonSchema;
15use serde::{de, Deserialize, Serialize};
16use serde_json::Value;
17use std::{cell::RefCell, sync::Arc};
18
19#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, JsonSchema)]
20pub struct FontId(pub usize);
21
22pub type GlyphId = u32;
23
24#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
25pub struct Features {
26 pub calt: Option<bool>,
27 pub case: Option<bool>,
28 pub cpsp: Option<bool>,
29 pub frac: Option<bool>,
30 pub liga: Option<bool>,
31 pub onum: Option<bool>,
32 pub ordn: Option<bool>,
33 pub pnum: Option<bool>,
34 pub ss01: Option<bool>,
35 pub ss02: Option<bool>,
36 pub ss03: Option<bool>,
37 pub ss04: Option<bool>,
38 pub ss05: Option<bool>,
39 pub ss06: Option<bool>,
40 pub ss07: Option<bool>,
41 pub ss08: Option<bool>,
42 pub ss09: Option<bool>,
43 pub ss10: Option<bool>,
44 pub ss11: Option<bool>,
45 pub ss12: Option<bool>,
46 pub ss13: Option<bool>,
47 pub ss14: Option<bool>,
48 pub ss15: Option<bool>,
49 pub ss16: Option<bool>,
50 pub ss17: Option<bool>,
51 pub ss18: Option<bool>,
52 pub ss19: Option<bool>,
53 pub ss20: Option<bool>,
54 pub subs: Option<bool>,
55 pub sups: Option<bool>,
56 pub swsh: Option<bool>,
57 pub titl: Option<bool>,
58 pub tnum: Option<bool>,
59 pub zero: Option<bool>,
60}
61
62#[derive(Clone, Debug, JsonSchema)]
63pub struct TextStyle {
64 pub color: Color,
65 pub font_family_name: Arc<str>,
66 pub font_family_id: FamilyId,
67 pub font_id: FontId,
68 pub font_size: f32,
69 #[schemars(with = "PropertiesDef")]
70 pub font_properties: Properties,
71 pub underline: Underline,
72}
73
74impl TextStyle {
75 pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
76 TextStyle {
77 color: refinement.color.unwrap_or(self.color),
78 font_family_name: refinement
79 .font_family_name
80 .unwrap_or_else(|| self.font_family_name.clone()),
81 font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
82 font_id: refinement.font_id.unwrap_or(self.font_id),
83 font_size: refinement.font_size.unwrap_or(self.font_size),
84 font_properties: refinement.font_properties.unwrap_or(self.font_properties),
85 underline: refinement.underline.unwrap_or(self.underline),
86 }
87 }
88}
89
90pub struct TextStyleRefinement {
91 pub color: Option<Color>,
92 pub font_family_name: Option<Arc<str>>,
93 pub font_family_id: Option<FamilyId>,
94 pub font_id: Option<FontId>,
95 pub font_size: Option<f32>,
96 pub font_properties: Option<Properties>,
97 pub underline: Option<Underline>,
98}
99
100#[derive(JsonSchema)]
101#[serde(remote = "Properties")]
102pub struct PropertiesDef {
103 /// The font style, as defined in CSS.
104 pub style: StyleDef,
105 /// The font weight, as defined in CSS.
106 pub weight: f32,
107 /// The font stretchiness, as defined in CSS.
108 pub stretch: f32,
109}
110
111#[derive(JsonSchema)]
112#[schemars(remote = "Style")]
113pub enum StyleDef {
114 /// A face that is neither italic not obliqued.
115 Normal,
116 /// A form that is generally cursive in nature.
117 Italic,
118 /// A typically-sloped version of the regular face.
119 Oblique,
120}
121
122#[derive(Copy, Clone, Debug, Default, PartialEq, JsonSchema)]
123pub struct HighlightStyle {
124 pub color: Option<Color>,
125 #[schemars(with = "Option::<f32>")]
126 pub weight: Option<Weight>,
127 pub italic: Option<bool>,
128 pub underline: Option<Underline>,
129 pub fade_out: Option<f32>,
130}
131
132impl Eq for HighlightStyle {}
133
134#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
135pub struct Underline {
136 pub color: Option<Color>,
137 #[schemars(with = "f32")]
138 pub thickness: OrderedFloat<f32>,
139 pub squiggly: bool,
140}
141
142#[allow(non_camel_case_types)]
143#[derive(Deserialize)]
144enum WeightJson {
145 thin,
146 extra_light,
147 light,
148 normal,
149 medium,
150 semibold,
151 bold,
152 extra_bold,
153 black,
154}
155
156thread_local! {
157 static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
158}
159
160#[derive(Deserialize)]
161struct TextStyleJson {
162 color: Color,
163 family: String,
164 #[serde(default)]
165 features: Features,
166 weight: Option<WeightJson>,
167 size: f32,
168 #[serde(default)]
169 italic: bool,
170 #[serde(default)]
171 underline: UnderlineStyleJson,
172}
173
174#[derive(Deserialize)]
175struct HighlightStyleJson {
176 color: Option<Color>,
177 weight: Option<WeightJson>,
178 italic: Option<bool>,
179 underline: Option<UnderlineStyleJson>,
180 fade_out: Option<f32>,
181}
182
183#[derive(Deserialize)]
184#[serde(untagged)]
185enum UnderlineStyleJson {
186 Underlined(bool),
187 UnderlinedWithProperties {
188 #[serde(default)]
189 color: Option<Color>,
190 #[serde(default)]
191 thickness: Option<f32>,
192 #[serde(default)]
193 squiggly: bool,
194 },
195}
196
197impl TextStyle {
198 pub fn new(
199 font_family_name: impl Into<Arc<str>>,
200 font_size: f32,
201 font_properties: Properties,
202 font_features: Features,
203 underline: Underline,
204 color: Color,
205 font_cache: &FontCache,
206 ) -> Result<Self> {
207 let font_family_name = font_family_name.into();
208 let font_family_id = font_cache.load_family(&[&font_family_name], &font_features)?;
209 let font_id = font_cache.select_font(font_family_id, &font_properties)?;
210 Ok(Self {
211 color,
212 font_family_name,
213 font_family_id,
214 font_id,
215 font_size,
216 font_properties,
217 underline,
218 })
219 }
220
221 pub fn with_font_size(mut self, font_size: f32) -> Self {
222 self.font_size = font_size;
223 self
224 }
225
226 pub fn highlight(mut self, style: HighlightStyle, font_cache: &FontCache) -> Result<Self> {
227 let mut font_properties = self.font_properties;
228 if let Some(weight) = style.weight {
229 font_properties.weight(weight);
230 }
231 if let Some(italic) = style.italic {
232 if italic {
233 font_properties.style(Style::Italic);
234 } else {
235 font_properties.style(Style::Normal);
236 }
237 }
238
239 if self.font_properties != font_properties {
240 self.font_id = font_cache.select_font(self.font_family_id, &font_properties)?;
241 }
242 if let Some(color) = style.color {
243 self.color = Color::blend(color, self.color);
244 }
245 if let Some(factor) = style.fade_out {
246 self.color.fade_out(factor);
247 }
248 if let Some(underline) = style.underline {
249 self.underline = underline;
250 }
251
252 Ok(self)
253 }
254
255 pub fn to_run(&self) -> RunStyle {
256 RunStyle {
257 font_id: self.font_id,
258 color: self.color,
259 underline: self.underline,
260 }
261 }
262
263 fn from_json(json: TextStyleJson) -> Result<Self> {
264 FONT_CACHE.with(|font_cache| {
265 if let Some(font_cache) = font_cache.borrow().as_ref() {
266 let font_properties = properties_from_json(json.weight, json.italic);
267 Self::new(
268 json.family,
269 json.size,
270 font_properties,
271 json.features,
272 underline_from_json(json.underline),
273 json.color,
274 font_cache,
275 )
276 } else {
277 Err(anyhow!(
278 "TextStyle can only be deserialized within a call to with_font_cache"
279 ))
280 }
281 })
282 }
283
284 pub fn line_height(&self, font_cache: &FontCache) -> f32 {
285 font_cache.line_height(self.font_size)
286 }
287
288 pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
289 font_cache.cap_height(self.font_id, self.font_size)
290 }
291
292 pub fn x_height(&self, font_cache: &FontCache) -> f32 {
293 font_cache.x_height(self.font_id, self.font_size)
294 }
295
296 pub fn em_width(&self, font_cache: &FontCache) -> f32 {
297 font_cache.em_width(self.font_id, self.font_size)
298 }
299
300 pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
301 font_cache.em_advance(self.font_id, self.font_size)
302 }
303
304 pub fn descent(&self, font_cache: &FontCache) -> f32 {
305 font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
306 }
307
308 pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
309 font_cache.baseline_offset(self.font_id, self.font_size)
310 }
311
312 fn em_scale(&self, font_cache: &FontCache) -> f32 {
313 font_cache.em_scale(self.font_id, self.font_size)
314 }
315}
316
317impl From<TextStyle> for HighlightStyle {
318 fn from(other: TextStyle) -> Self {
319 Self::from(&other)
320 }
321}
322
323impl From<&TextStyle> for HighlightStyle {
324 fn from(other: &TextStyle) -> Self {
325 Self {
326 color: Some(other.color),
327 weight: Some(other.font_properties.weight),
328 italic: Some(other.font_properties.style == Style::Italic),
329 underline: Some(other.underline),
330 fade_out: None,
331 }
332 }
333}
334
335impl Default for UnderlineStyleJson {
336 fn default() -> Self {
337 Self::Underlined(false)
338 }
339}
340
341impl Default for TextStyle {
342 fn default() -> Self {
343 FONT_CACHE.with(|font_cache| {
344 let font_cache = font_cache.borrow();
345 let font_cache = font_cache
346 .as_ref()
347 .expect("TextStyle::default can only be called within a call to with_font_cache");
348
349 let font_family_id = font_cache.known_existing_family();
350 let font_id = font_cache
351 .select_font(font_family_id, &Default::default())
352 .expect("did not have any font in system-provided family");
353 let font_family_name = font_cache
354 .family_name(font_family_id)
355 .expect("we loaded this family from the font cache, so this should work");
356
357 Self {
358 color: Default::default(),
359 font_family_name,
360 font_family_id,
361 font_id,
362 font_size: 14.,
363 font_properties: Default::default(),
364 underline: Default::default(),
365 }
366 })
367 }
368}
369
370impl HighlightStyle {
371 fn from_json(json: HighlightStyleJson) -> Self {
372 Self {
373 color: json.color,
374 weight: json.weight.map(weight_from_json),
375 italic: json.italic,
376 underline: json.underline.map(underline_from_json),
377 fade_out: json.fade_out,
378 }
379 }
380
381 pub fn highlight(&mut self, other: HighlightStyle) {
382 match (self.color, other.color) {
383 (Some(self_color), Some(other_color)) => {
384 self.color = Some(Color::blend(other_color, self_color));
385 }
386 (None, Some(other_color)) => {
387 self.color = Some(other_color);
388 }
389 _ => {}
390 }
391
392 if other.weight.is_some() {
393 self.weight = other.weight;
394 }
395
396 if other.italic.is_some() {
397 self.italic = other.italic;
398 }
399
400 if other.underline.is_some() {
401 self.underline = other.underline;
402 }
403
404 match (other.fade_out, self.fade_out) {
405 (Some(source_fade), None) => self.fade_out = Some(source_fade),
406 (Some(source_fade), Some(dest_fade)) => {
407 let source_alpha = 1. - source_fade;
408 let dest_alpha = 1. - dest_fade;
409 let blended_alpha = source_alpha + (dest_alpha * source_fade);
410 let blended_fade = 1. - blended_alpha;
411 self.fade_out = Some(blended_fade);
412 }
413 _ => {}
414 }
415 }
416}
417
418impl From<Color> for HighlightStyle {
419 fn from(color: Color) -> Self {
420 Self {
421 color: Some(color),
422 ..Default::default()
423 }
424 }
425}
426
427impl<'de> Deserialize<'de> for TextStyle {
428 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
429 where
430 D: serde::Deserializer<'de>,
431 {
432 Self::from_json(TextStyleJson::deserialize(deserializer)?).map_err(de::Error::custom)
433 }
434}
435
436impl ToJson for TextStyle {
437 fn to_json(&self) -> Value {
438 json!({
439 "color": self.color.to_json(),
440 "font_family": self.font_family_name.as_ref(),
441 "font_properties": self.font_properties.to_json(),
442 })
443 }
444}
445
446impl<'de> Deserialize<'de> for HighlightStyle {
447 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
448 where
449 D: serde::Deserializer<'de>,
450 {
451 let json = serde_json::Value::deserialize(deserializer)?;
452 if json.is_object() {
453 Ok(Self::from_json(
454 serde_json::from_value(json).map_err(de::Error::custom)?,
455 ))
456 } else {
457 Ok(Self {
458 color: serde_json::from_value(json).map_err(de::Error::custom)?,
459 ..Default::default()
460 })
461 }
462 }
463}
464
465fn underline_from_json(json: UnderlineStyleJson) -> Underline {
466 match json {
467 UnderlineStyleJson::Underlined(false) => Underline::default(),
468 UnderlineStyleJson::Underlined(true) => Underline {
469 color: None,
470 thickness: 1.0.into(),
471 squiggly: false,
472 },
473 UnderlineStyleJson::UnderlinedWithProperties {
474 color,
475 thickness,
476 squiggly,
477 } => Underline {
478 color,
479 thickness: thickness.unwrap_or(1.).into(),
480 squiggly,
481 },
482 }
483}
484
485fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
486 let weight = weight.map(weight_from_json).unwrap_or_default();
487 let style = if italic { Style::Italic } else { Style::Normal };
488 *Properties::new().weight(weight).style(style)
489}
490
491fn weight_from_json(weight: WeightJson) -> Weight {
492 match weight {
493 WeightJson::thin => Weight::THIN,
494 WeightJson::extra_light => Weight::EXTRA_LIGHT,
495 WeightJson::light => Weight::LIGHT,
496 WeightJson::normal => Weight::NORMAL,
497 WeightJson::medium => Weight::MEDIUM,
498 WeightJson::semibold => Weight::SEMIBOLD,
499 WeightJson::bold => Weight::BOLD,
500 WeightJson::extra_bold => Weight::EXTRA_BOLD,
501 WeightJson::black => Weight::BLACK,
502 }
503}
504
505impl ToJson for Properties {
506 fn to_json(&self) -> crate::json::Value {
507 json!({
508 "style": self.style.to_json(),
509 "weight": self.weight.to_json(),
510 "stretch": self.stretch.to_json(),
511 })
512 }
513}
514
515impl ToJson for Style {
516 fn to_json(&self) -> crate::json::Value {
517 match self {
518 Style::Normal => json!("normal"),
519 Style::Italic => json!("italic"),
520 Style::Oblique => json!("oblique"),
521 }
522 }
523}
524
525impl ToJson for Weight {
526 fn to_json(&self) -> crate::json::Value {
527 if self.0 == Weight::THIN.0 {
528 json!("thin")
529 } else if self.0 == Weight::EXTRA_LIGHT.0 {
530 json!("extra light")
531 } else if self.0 == Weight::LIGHT.0 {
532 json!("light")
533 } else if self.0 == Weight::NORMAL.0 {
534 json!("normal")
535 } else if self.0 == Weight::MEDIUM.0 {
536 json!("medium")
537 } else if self.0 == Weight::SEMIBOLD.0 {
538 json!("semibold")
539 } else if self.0 == Weight::BOLD.0 {
540 json!("bold")
541 } else if self.0 == Weight::EXTRA_BOLD.0 {
542 json!("extra bold")
543 } else if self.0 == Weight::BLACK.0 {
544 json!("black")
545 } else {
546 json!(self.0)
547 }
548 }
549}
550
551impl ToJson for Stretch {
552 fn to_json(&self) -> serde_json::Value {
553 json!(self.0)
554 }
555}
556
557pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
558where
559 F: FnOnce() -> T,
560{
561 FONT_CACHE.with(|cache| {
562 *cache.borrow_mut() = Some(font_cache);
563 let result = callback();
564 cache.borrow_mut().take();
565 result
566 })
567}