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