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
159#[derive(JsonSchema)]
160#[serde(remote = "Properties")]
161pub struct PropertiesDef {
162 /// The font style, as defined in CSS.
163 pub style: StyleDef,
164 /// The font weight, as defined in CSS.
165 pub weight: f32,
166 /// The font stretchiness, as defined in CSS.
167 pub stretch: f32,
168}
169
170#[derive(JsonSchema)]
171#[schemars(remote = "Style")]
172pub enum StyleDef {
173 /// A face that is neither italic not obliqued.
174 Normal,
175 /// A form that is generally cursive in nature.
176 Italic,
177 /// A typically-sloped version of the regular face.
178 Oblique,
179}
180
181#[derive(Copy, Clone, Debug, Default, PartialEq, JsonSchema)]
182pub struct HighlightStyle {
183 pub color: Option<Color>,
184 #[schemars(with = "Option::<f32>")]
185 pub weight: Option<Weight>,
186 pub italic: Option<bool>,
187 pub underline: Option<Underline>,
188 pub fade_out: Option<f32>,
189}
190
191impl Eq for HighlightStyle {}
192
193#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, JsonSchema)]
194pub struct Underline {
195 pub color: Option<Color>,
196 #[schemars(with = "f32")]
197 pub thickness: OrderedFloat<f32>,
198 pub squiggly: bool,
199}
200
201#[allow(non_camel_case_types)]
202#[derive(Deserialize)]
203enum WeightJson {
204 thin,
205 extra_light,
206 light,
207 normal,
208 medium,
209 semibold,
210 bold,
211 extra_bold,
212 black,
213}
214
215thread_local! {
216 static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
217}
218
219#[derive(Deserialize)]
220struct TextStyleJson {
221 color: Color,
222 family: String,
223 #[serde(default)]
224 features: Features,
225 weight: Option<WeightJson>,
226 size: f32,
227 #[serde(default)]
228 italic: bool,
229 #[serde(default)]
230 underline: UnderlineStyleJson,
231}
232
233#[derive(Deserialize)]
234struct HighlightStyleJson {
235 color: Option<Color>,
236 weight: Option<WeightJson>,
237 italic: Option<bool>,
238 underline: Option<UnderlineStyleJson>,
239 fade_out: Option<f32>,
240}
241
242#[derive(Deserialize)]
243#[serde(untagged)]
244enum UnderlineStyleJson {
245 Underlined(bool),
246 UnderlinedWithProperties {
247 #[serde(default)]
248 color: Option<Color>,
249 #[serde(default)]
250 thickness: Option<f32>,
251 #[serde(default)]
252 squiggly: bool,
253 },
254}
255
256impl TextStyle {
257 pub fn new(
258 font_family_name: impl Into<Arc<str>>,
259 font_size: f32,
260 font_properties: Properties,
261 font_features: Features,
262 underline: Underline,
263 color: Color,
264 font_cache: &FontCache,
265 ) -> Result<Self> {
266 let font_family_name = font_family_name.into();
267 let font_family_id = font_cache.load_family(&[&font_family_name], &font_features)?;
268 let font_id = font_cache.select_font(font_family_id, &font_properties)?;
269 Ok(Self {
270 color,
271 font_family_name,
272 font_family_id,
273 font_id,
274 font_size,
275 font_properties,
276 underline,
277 soft_wrap: false,
278 })
279 }
280
281 pub fn default(font_cache: &FontCache) -> Self {
282 let font_family_id = font_cache.known_existing_family();
283 let font_id = font_cache
284 .select_font(font_family_id, &Default::default())
285 .expect("did not have any font in system-provided family");
286 let font_family_name = font_cache
287 .family_name(font_family_id)
288 .expect("we loaded this family from the font cache, so this should work");
289
290 Self {
291 color: Color::default(),
292 font_family_name,
293 font_family_id,
294 font_id,
295 font_size: 14.,
296 font_properties: Default::default(),
297 underline: Default::default(),
298 soft_wrap: true,
299 }
300 }
301
302 pub fn with_font_size(mut self, font_size: f32) -> Self {
303 self.font_size = font_size;
304 self
305 }
306
307 pub fn highlight(mut self, style: HighlightStyle, font_cache: &FontCache) -> Result<Self> {
308 let mut font_properties = self.font_properties;
309 if let Some(weight) = style.weight {
310 font_properties.weight(weight);
311 }
312 if let Some(italic) = style.italic {
313 if italic {
314 font_properties.style(Style::Italic);
315 } else {
316 font_properties.style(Style::Normal);
317 }
318 }
319
320 if self.font_properties != font_properties {
321 self.font_id = font_cache.select_font(self.font_family_id, &font_properties)?;
322 }
323 if let Some(color) = style.color {
324 self.color = Color::blend(color, self.color);
325 }
326 if let Some(factor) = style.fade_out {
327 self.color.fade_out(factor);
328 }
329 if let Some(underline) = style.underline {
330 self.underline = underline;
331 }
332
333 Ok(self)
334 }
335
336 pub fn to_run(&self) -> RunStyle {
337 RunStyle {
338 font_id: self.font_id,
339 color: self.color,
340 underline: self.underline,
341 }
342 }
343
344 fn from_json(json: TextStyleJson) -> Result<Self> {
345 FONT_CACHE.with(|font_cache| {
346 if let Some(font_cache) = font_cache.borrow().as_ref() {
347 let font_properties = properties_from_json(json.weight, json.italic);
348 Self::new(
349 json.family,
350 json.size,
351 font_properties,
352 json.features,
353 underline_from_json(json.underline),
354 json.color,
355 font_cache,
356 )
357 } else {
358 Err(anyhow!(
359 "TextStyle can only be deserialized within a call to with_font_cache"
360 ))
361 }
362 })
363 }
364
365 pub fn line_height(&self, font_cache: &FontCache) -> f32 {
366 font_cache.line_height(self.font_size)
367 }
368
369 pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
370 font_cache.cap_height(self.font_id, self.font_size)
371 }
372
373 pub fn x_height(&self, font_cache: &FontCache) -> f32 {
374 font_cache.x_height(self.font_id, self.font_size)
375 }
376
377 pub fn em_width(&self, font_cache: &FontCache) -> f32 {
378 font_cache.em_width(self.font_id, self.font_size)
379 }
380
381 pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
382 font_cache.em_advance(self.font_id, self.font_size)
383 }
384
385 pub fn descent(&self, font_cache: &FontCache) -> f32 {
386 font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
387 }
388
389 pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
390 font_cache.baseline_offset(self.font_id, self.font_size)
391 }
392
393 fn em_scale(&self, font_cache: &FontCache) -> f32 {
394 font_cache.em_scale(self.font_id, self.font_size)
395 }
396}
397
398impl From<TextStyle> for HighlightStyle {
399 fn from(other: TextStyle) -> Self {
400 Self::from(&other)
401 }
402}
403
404impl From<&TextStyle> for HighlightStyle {
405 fn from(other: &TextStyle) -> Self {
406 Self {
407 color: Some(other.color),
408 weight: Some(other.font_properties.weight),
409 italic: Some(other.font_properties.style == Style::Italic),
410 underline: Some(other.underline),
411 fade_out: None,
412 }
413 }
414}
415
416impl Default for UnderlineStyleJson {
417 fn default() -> Self {
418 Self::Underlined(false)
419 }
420}
421
422impl Default for TextStyle {
423 fn default() -> Self {
424 FONT_CACHE.with(|font_cache| {
425 let font_cache = font_cache.borrow();
426 let font_cache = font_cache
427 .as_ref()
428 .expect("TextStyle::default can only be called within a call to with_font_cache");
429 Self::default(font_cache)
430 })
431 }
432}
433
434impl HighlightStyle {
435 fn from_json(json: HighlightStyleJson) -> Self {
436 Self {
437 color: json.color,
438 weight: json.weight.map(weight_from_json),
439 italic: json.italic,
440 underline: json.underline.map(underline_from_json),
441 fade_out: json.fade_out,
442 }
443 }
444
445 pub fn highlight(&mut self, other: HighlightStyle) {
446 match (self.color, other.color) {
447 (Some(self_color), Some(other_color)) => {
448 self.color = Some(Color::blend(other_color, self_color));
449 }
450 (None, Some(other_color)) => {
451 self.color = Some(other_color);
452 }
453 _ => {}
454 }
455
456 if other.weight.is_some() {
457 self.weight = other.weight;
458 }
459
460 if other.italic.is_some() {
461 self.italic = other.italic;
462 }
463
464 if other.underline.is_some() {
465 self.underline = other.underline;
466 }
467
468 match (other.fade_out, self.fade_out) {
469 (Some(source_fade), None) => self.fade_out = Some(source_fade),
470 (Some(source_fade), Some(dest_fade)) => {
471 let source_alpha = 1. - source_fade;
472 let dest_alpha = 1. - dest_fade;
473 let blended_alpha = source_alpha + (dest_alpha * source_fade);
474 let blended_fade = 1. - blended_alpha;
475 self.fade_out = Some(blended_fade);
476 }
477 _ => {}
478 }
479 }
480}
481
482impl From<Color> for HighlightStyle {
483 fn from(color: Color) -> Self {
484 Self {
485 color: Some(color),
486 ..Default::default()
487 }
488 }
489}
490
491impl<'de> Deserialize<'de> for TextStyle {
492 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
493 where
494 D: serde::Deserializer<'de>,
495 {
496 Self::from_json(TextStyleJson::deserialize(deserializer)?).map_err(de::Error::custom)
497 }
498}
499
500impl ToJson for TextStyle {
501 fn to_json(&self) -> Value {
502 json!({
503 "color": self.color.to_json(),
504 "font_family": self.font_family_name.as_ref(),
505 "font_properties": self.font_properties.to_json(),
506 })
507 }
508}
509
510impl<'de> Deserialize<'de> for HighlightStyle {
511 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
512 where
513 D: serde::Deserializer<'de>,
514 {
515 let json = serde_json::Value::deserialize(deserializer)?;
516 if json.is_object() {
517 Ok(Self::from_json(
518 serde_json::from_value(json).map_err(de::Error::custom)?,
519 ))
520 } else {
521 Ok(Self {
522 color: serde_json::from_value(json).map_err(de::Error::custom)?,
523 ..Default::default()
524 })
525 }
526 }
527}
528
529fn underline_from_json(json: UnderlineStyleJson) -> Underline {
530 match json {
531 UnderlineStyleJson::Underlined(false) => Underline::default(),
532 UnderlineStyleJson::Underlined(true) => Underline {
533 color: None,
534 thickness: 1.0.into(),
535 squiggly: false,
536 },
537 UnderlineStyleJson::UnderlinedWithProperties {
538 color,
539 thickness,
540 squiggly,
541 } => Underline {
542 color,
543 thickness: thickness.unwrap_or(1.).into(),
544 squiggly,
545 },
546 }
547}
548
549fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
550 let weight = weight.map(weight_from_json).unwrap_or_default();
551 let style = if italic { Style::Italic } else { Style::Normal };
552 *Properties::new().weight(weight).style(style)
553}
554
555fn weight_from_json(weight: WeightJson) -> Weight {
556 match weight {
557 WeightJson::thin => Weight::THIN,
558 WeightJson::extra_light => Weight::EXTRA_LIGHT,
559 WeightJson::light => Weight::LIGHT,
560 WeightJson::normal => Weight::NORMAL,
561 WeightJson::medium => Weight::MEDIUM,
562 WeightJson::semibold => Weight::SEMIBOLD,
563 WeightJson::bold => Weight::BOLD,
564 WeightJson::extra_bold => Weight::EXTRA_BOLD,
565 WeightJson::black => Weight::BLACK,
566 }
567}
568
569impl ToJson for Properties {
570 fn to_json(&self) -> crate::json::Value {
571 json!({
572 "style": self.style.to_json(),
573 "weight": self.weight.to_json(),
574 "stretch": self.stretch.to_json(),
575 })
576 }
577}
578
579impl ToJson for Style {
580 fn to_json(&self) -> crate::json::Value {
581 match self {
582 Style::Normal => json!("normal"),
583 Style::Italic => json!("italic"),
584 Style::Oblique => json!("oblique"),
585 }
586 }
587}
588
589impl ToJson for Weight {
590 fn to_json(&self) -> crate::json::Value {
591 if self.0 == Weight::THIN.0 {
592 json!("thin")
593 } else if self.0 == Weight::EXTRA_LIGHT.0 {
594 json!("extra light")
595 } else if self.0 == Weight::LIGHT.0 {
596 json!("light")
597 } else if self.0 == Weight::NORMAL.0 {
598 json!("normal")
599 } else if self.0 == Weight::MEDIUM.0 {
600 json!("medium")
601 } else if self.0 == Weight::SEMIBOLD.0 {
602 json!("semibold")
603 } else if self.0 == Weight::BOLD.0 {
604 json!("bold")
605 } else if self.0 == Weight::EXTRA_BOLD.0 {
606 json!("extra bold")
607 } else if self.0 == Weight::BLACK.0 {
608 json!("black")
609 } else {
610 json!(self.0)
611 }
612 }
613}
614
615impl ToJson for Stretch {
616 fn to_json(&self) -> serde_json::Value {
617 json!(self.0)
618 }
619}
620
621pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
622where
623 F: FnOnce() -> T,
624{
625 FONT_CACHE.with(|cache| {
626 *cache.borrow_mut() = Some(font_cache);
627 let result = callback();
628 cache.borrow_mut().take();
629 result
630 })
631}