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