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, Refineable)]
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 pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
85 TextStyle {
86 color: refinement.color.unwrap_or(self.color),
87 font_family_name: refinement
88 .font_family_name
89 .unwrap_or_else(|| self.font_family_name.clone()),
90 font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
91 font_id: refinement.font_id.unwrap_or(self.font_id),
92 font_size: refinement.font_size.unwrap_or(self.font_size),
93 font_properties: refinement.font_properties.unwrap_or(self.font_properties),
94 underline: refinement.underline.unwrap_or(self.underline),
95 soft_wrap: refinement.soft_wrap.unwrap_or(self.soft_wrap),
96 }
97 }
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 soft_wrap: false,
219 })
220 }
221
222 pub fn default(font_cache: &FontCache) -> Self {
223 let font_family_id = font_cache.known_existing_family();
224 let font_id = font_cache
225 .select_font(font_family_id, &Default::default())
226 .expect("did not have any font in system-provided family");
227 let font_family_name = font_cache
228 .family_name(font_family_id)
229 .expect("we loaded this family from the font cache, so this should work");
230
231 Self {
232 color: Color::default(),
233 font_family_name,
234 font_family_id,
235 font_id,
236 font_size: 14.,
237 font_properties: Default::default(),
238 underline: Default::default(),
239 soft_wrap: true,
240 }
241 }
242
243 pub fn with_font_size(mut self, font_size: f32) -> Self {
244 self.font_size = font_size;
245 self
246 }
247
248 pub fn highlight(mut self, style: HighlightStyle, font_cache: &FontCache) -> Result<Self> {
249 let mut font_properties = self.font_properties;
250 if let Some(weight) = style.weight {
251 font_properties.weight(weight);
252 }
253 if let Some(italic) = style.italic {
254 if italic {
255 font_properties.style(Style::Italic);
256 } else {
257 font_properties.style(Style::Normal);
258 }
259 }
260
261 if self.font_properties != font_properties {
262 self.font_id = font_cache.select_font(self.font_family_id, &font_properties)?;
263 }
264 if let Some(color) = style.color {
265 self.color = Color::blend(color, self.color);
266 }
267 if let Some(factor) = style.fade_out {
268 self.color.fade_out(factor);
269 }
270 if let Some(underline) = style.underline {
271 self.underline = underline;
272 }
273
274 Ok(self)
275 }
276
277 pub fn to_run(&self) -> RunStyle {
278 RunStyle {
279 font_id: self.font_id,
280 color: self.color,
281 underline: self.underline,
282 }
283 }
284
285 fn from_json(json: TextStyleJson) -> Result<Self> {
286 FONT_CACHE.with(|font_cache| {
287 if let Some(font_cache) = font_cache.borrow().as_ref() {
288 let font_properties = properties_from_json(json.weight, json.italic);
289 Self::new(
290 json.family,
291 json.size,
292 font_properties,
293 json.features,
294 underline_from_json(json.underline),
295 json.color,
296 font_cache,
297 )
298 } else {
299 Err(anyhow!(
300 "TextStyle can only be deserialized within a call to with_font_cache"
301 ))
302 }
303 })
304 }
305
306 pub fn line_height(&self, font_cache: &FontCache) -> f32 {
307 font_cache.line_height(self.font_size)
308 }
309
310 pub fn cap_height(&self, font_cache: &FontCache) -> f32 {
311 font_cache.cap_height(self.font_id, self.font_size)
312 }
313
314 pub fn x_height(&self, font_cache: &FontCache) -> f32 {
315 font_cache.x_height(self.font_id, self.font_size)
316 }
317
318 pub fn em_width(&self, font_cache: &FontCache) -> f32 {
319 font_cache.em_width(self.font_id, self.font_size)
320 }
321
322 pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
323 font_cache.em_advance(self.font_id, self.font_size)
324 }
325
326 pub fn descent(&self, font_cache: &FontCache) -> f32 {
327 font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
328 }
329
330 pub fn baseline_offset(&self, font_cache: &FontCache) -> f32 {
331 font_cache.baseline_offset(self.font_id, self.font_size)
332 }
333
334 fn em_scale(&self, font_cache: &FontCache) -> f32 {
335 font_cache.em_scale(self.font_id, self.font_size)
336 }
337}
338
339impl From<TextStyle> for HighlightStyle {
340 fn from(other: TextStyle) -> Self {
341 Self::from(&other)
342 }
343}
344
345impl From<&TextStyle> for HighlightStyle {
346 fn from(other: &TextStyle) -> Self {
347 Self {
348 color: Some(other.color),
349 weight: Some(other.font_properties.weight),
350 italic: Some(other.font_properties.style == Style::Italic),
351 underline: Some(other.underline),
352 fade_out: None,
353 }
354 }
355}
356
357impl Default for UnderlineStyleJson {
358 fn default() -> Self {
359 Self::Underlined(false)
360 }
361}
362
363impl Default for TextStyle {
364 fn default() -> Self {
365 FONT_CACHE.with(|font_cache| {
366 let font_cache = font_cache.borrow();
367 let font_cache = font_cache
368 .as_ref()
369 .expect("TextStyle::default can only be called within a call to with_font_cache");
370 Self::default(font_cache)
371 })
372 }
373}
374
375impl HighlightStyle {
376 fn from_json(json: HighlightStyleJson) -> Self {
377 Self {
378 color: json.color,
379 weight: json.weight.map(weight_from_json),
380 italic: json.italic,
381 underline: json.underline.map(underline_from_json),
382 fade_out: json.fade_out,
383 }
384 }
385
386 pub fn highlight(&mut self, other: HighlightStyle) {
387 match (self.color, other.color) {
388 (Some(self_color), Some(other_color)) => {
389 self.color = Some(Color::blend(other_color, self_color));
390 }
391 (None, Some(other_color)) => {
392 self.color = Some(other_color);
393 }
394 _ => {}
395 }
396
397 if other.weight.is_some() {
398 self.weight = other.weight;
399 }
400
401 if other.italic.is_some() {
402 self.italic = other.italic;
403 }
404
405 if other.underline.is_some() {
406 self.underline = other.underline;
407 }
408
409 match (other.fade_out, self.fade_out) {
410 (Some(source_fade), None) => self.fade_out = Some(source_fade),
411 (Some(source_fade), Some(dest_fade)) => {
412 let source_alpha = 1. - source_fade;
413 let dest_alpha = 1. - dest_fade;
414 let blended_alpha = source_alpha + (dest_alpha * source_fade);
415 let blended_fade = 1. - blended_alpha;
416 self.fade_out = Some(blended_fade);
417 }
418 _ => {}
419 }
420 }
421}
422
423impl From<Color> for HighlightStyle {
424 fn from(color: Color) -> Self {
425 Self {
426 color: Some(color),
427 ..Default::default()
428 }
429 }
430}
431
432impl<'de> Deserialize<'de> for TextStyle {
433 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
434 where
435 D: serde::Deserializer<'de>,
436 {
437 Self::from_json(TextStyleJson::deserialize(deserializer)?).map_err(de::Error::custom)
438 }
439}
440
441impl ToJson for TextStyle {
442 fn to_json(&self) -> Value {
443 json!({
444 "color": self.color.to_json(),
445 "font_family": self.font_family_name.as_ref(),
446 "font_properties": self.font_properties.to_json(),
447 })
448 }
449}
450
451impl<'de> Deserialize<'de> for HighlightStyle {
452 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
453 where
454 D: serde::Deserializer<'de>,
455 {
456 let json = serde_json::Value::deserialize(deserializer)?;
457 if json.is_object() {
458 Ok(Self::from_json(
459 serde_json::from_value(json).map_err(de::Error::custom)?,
460 ))
461 } else {
462 Ok(Self {
463 color: serde_json::from_value(json).map_err(de::Error::custom)?,
464 ..Default::default()
465 })
466 }
467 }
468}
469
470fn underline_from_json(json: UnderlineStyleJson) -> Underline {
471 match json {
472 UnderlineStyleJson::Underlined(false) => Underline::default(),
473 UnderlineStyleJson::Underlined(true) => Underline {
474 color: None,
475 thickness: 1.0.into(),
476 squiggly: false,
477 },
478 UnderlineStyleJson::UnderlinedWithProperties {
479 color,
480 thickness,
481 squiggly,
482 } => Underline {
483 color,
484 thickness: thickness.unwrap_or(1.).into(),
485 squiggly,
486 },
487 }
488}
489
490fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
491 let weight = weight.map(weight_from_json).unwrap_or_default();
492 let style = if italic { Style::Italic } else { Style::Normal };
493 *Properties::new().weight(weight).style(style)
494}
495
496fn weight_from_json(weight: WeightJson) -> Weight {
497 match weight {
498 WeightJson::thin => Weight::THIN,
499 WeightJson::extra_light => Weight::EXTRA_LIGHT,
500 WeightJson::light => Weight::LIGHT,
501 WeightJson::normal => Weight::NORMAL,
502 WeightJson::medium => Weight::MEDIUM,
503 WeightJson::semibold => Weight::SEMIBOLD,
504 WeightJson::bold => Weight::BOLD,
505 WeightJson::extra_bold => Weight::EXTRA_BOLD,
506 WeightJson::black => Weight::BLACK,
507 }
508}
509
510impl ToJson for Properties {
511 fn to_json(&self) -> crate::json::Value {
512 json!({
513 "style": self.style.to_json(),
514 "weight": self.weight.to_json(),
515 "stretch": self.stretch.to_json(),
516 })
517 }
518}
519
520impl ToJson for Style {
521 fn to_json(&self) -> crate::json::Value {
522 match self {
523 Style::Normal => json!("normal"),
524 Style::Italic => json!("italic"),
525 Style::Oblique => json!("oblique"),
526 }
527 }
528}
529
530impl ToJson for Weight {
531 fn to_json(&self) -> crate::json::Value {
532 if self.0 == Weight::THIN.0 {
533 json!("thin")
534 } else if self.0 == Weight::EXTRA_LIGHT.0 {
535 json!("extra light")
536 } else if self.0 == Weight::LIGHT.0 {
537 json!("light")
538 } else if self.0 == Weight::NORMAL.0 {
539 json!("normal")
540 } else if self.0 == Weight::MEDIUM.0 {
541 json!("medium")
542 } else if self.0 == Weight::SEMIBOLD.0 {
543 json!("semibold")
544 } else if self.0 == Weight::BOLD.0 {
545 json!("bold")
546 } else if self.0 == Weight::EXTRA_BOLD.0 {
547 json!("extra bold")
548 } else if self.0 == Weight::BLACK.0 {
549 json!("black")
550 } else {
551 json!(self.0)
552 }
553 }
554}
555
556impl ToJson for Stretch {
557 fn to_json(&self) -> serde_json::Value {
558 json!(self.0)
559 }
560}
561
562pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
563where
564 F: FnOnce() -> T,
565{
566 FONT_CACHE.with(|cache| {
567 *cache.borrow_mut() = Some(font_cache);
568 let result = callback();
569 cache.borrow_mut().take();
570 result
571 })
572}