1use crate::{
2 color::Color,
3 json::{json, ToJson},
4 text_layout::RunStyle,
5 FontCache,
6};
7use anyhow::anyhow;
8pub use font_kit::{
9 metrics::Metrics,
10 properties::{Properties, Stretch, Style, Weight},
11};
12use serde::{de, Deserialize};
13use serde_json::Value;
14use std::{cell::RefCell, sync::Arc};
15
16#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
17pub struct FontId(pub usize);
18
19pub type GlyphId = u32;
20
21#[derive(Clone, Debug)]
22pub struct TextStyle {
23 pub color: Color,
24 pub font_family_name: Arc<str>,
25 pub font_id: FontId,
26 pub font_size: f32,
27 pub font_properties: Properties,
28 pub underline: bool,
29}
30
31#[derive(Clone, Debug, Default)]
32pub struct HighlightStyle {
33 pub color: Color,
34 pub font_properties: Properties,
35 pub underline: bool,
36}
37
38#[allow(non_camel_case_types)]
39#[derive(Deserialize)]
40enum WeightJson {
41 thin,
42 extra_light,
43 light,
44 normal,
45 medium,
46 semibold,
47 bold,
48 extra_bold,
49 black,
50}
51
52thread_local! {
53 static FONT_CACHE: RefCell<Option<Arc<FontCache>>> = Default::default();
54}
55
56#[derive(Deserialize)]
57struct TextStyleJson {
58 color: Color,
59 family: String,
60 weight: Option<WeightJson>,
61 size: f32,
62 #[serde(default)]
63 italic: bool,
64 #[serde(default)]
65 underline: bool,
66}
67
68#[derive(Deserialize)]
69struct HighlightStyleJson {
70 color: Color,
71 weight: Option<WeightJson>,
72 #[serde(default)]
73 italic: bool,
74 #[serde(default)]
75 underline: bool,
76}
77
78impl TextStyle {
79 pub fn new(
80 font_family_name: impl Into<Arc<str>>,
81 font_size: f32,
82 font_properties: Properties,
83 underline: bool,
84 color: Color,
85 font_cache: &FontCache,
86 ) -> anyhow::Result<Self> {
87 let font_family_name = font_family_name.into();
88 let family_id = font_cache.load_family(&[&font_family_name])?;
89 let font_id = font_cache.select_font(family_id, &font_properties)?;
90 Ok(Self {
91 color,
92 font_family_name,
93 font_id,
94 font_size,
95 font_properties,
96 underline,
97 })
98 }
99
100 pub fn to_run(&self) -> RunStyle {
101 RunStyle {
102 font_id: self.font_id,
103 color: self.color,
104 underline: self.underline,
105 }
106 }
107
108 fn from_json(json: TextStyleJson) -> anyhow::Result<Self> {
109 FONT_CACHE.with(|font_cache| {
110 if let Some(font_cache) = font_cache.borrow().as_ref() {
111 let font_properties = properties_from_json(json.weight, json.italic);
112 Self::new(
113 json.family,
114 json.size,
115 font_properties,
116 json.underline,
117 json.color,
118 font_cache,
119 )
120 } else {
121 Err(anyhow!(
122 "TextStyle can only be deserialized within a call to with_font_cache"
123 ))
124 }
125 })
126 }
127}
128
129impl HighlightStyle {
130 fn from_json(json: HighlightStyleJson) -> Self {
131 let font_properties = properties_from_json(json.weight, json.italic);
132 Self {
133 color: json.color,
134 font_properties,
135 underline: json.underline,
136 }
137 }
138}
139
140impl From<Color> for HighlightStyle {
141 fn from(color: Color) -> Self {
142 Self {
143 color,
144 font_properties: Default::default(),
145 underline: false,
146 }
147 }
148}
149
150impl<'de> Deserialize<'de> for TextStyle {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152 where
153 D: serde::Deserializer<'de>,
154 {
155 Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
156 .map_err(|e| de::Error::custom(e))?)
157 }
158}
159
160impl ToJson for TextStyle {
161 fn to_json(&self) -> Value {
162 json!({
163 "color": self.color.to_json(),
164 "font_family": self.font_family_name.as_ref(),
165 "font_properties": self.font_properties.to_json(),
166 })
167 }
168}
169
170impl<'de> Deserialize<'de> for HighlightStyle {
171 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
172 where
173 D: serde::Deserializer<'de>,
174 {
175 let json = serde_json::Value::deserialize(deserializer)?;
176 if json.is_object() {
177 Ok(Self::from_json(
178 serde_json::from_value(json).map_err(de::Error::custom)?,
179 ))
180 } else {
181 Ok(Self {
182 color: serde_json::from_value(json).map_err(de::Error::custom)?,
183 font_properties: Properties::new(),
184 underline: false,
185 })
186 }
187 }
188}
189
190fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
191 let weight = match weight.unwrap_or(WeightJson::normal) {
192 WeightJson::thin => Weight::THIN,
193 WeightJson::extra_light => Weight::EXTRA_LIGHT,
194 WeightJson::light => Weight::LIGHT,
195 WeightJson::normal => Weight::NORMAL,
196 WeightJson::medium => Weight::MEDIUM,
197 WeightJson::semibold => Weight::SEMIBOLD,
198 WeightJson::bold => Weight::BOLD,
199 WeightJson::extra_bold => Weight::EXTRA_BOLD,
200 WeightJson::black => Weight::BLACK,
201 };
202 let style = if italic { Style::Italic } else { Style::Normal };
203 *Properties::new().weight(weight).style(style)
204}
205
206impl ToJson for Properties {
207 fn to_json(&self) -> crate::json::Value {
208 json!({
209 "style": self.style.to_json(),
210 "weight": self.weight.to_json(),
211 "stretch": self.stretch.to_json(),
212 })
213 }
214}
215
216impl ToJson for Style {
217 fn to_json(&self) -> crate::json::Value {
218 match self {
219 Style::Normal => json!("normal"),
220 Style::Italic => json!("italic"),
221 Style::Oblique => json!("oblique"),
222 }
223 }
224}
225
226impl ToJson for Weight {
227 fn to_json(&self) -> crate::json::Value {
228 if self.0 == Weight::THIN.0 {
229 json!("thin")
230 } else if self.0 == Weight::EXTRA_LIGHT.0 {
231 json!("extra light")
232 } else if self.0 == Weight::LIGHT.0 {
233 json!("light")
234 } else if self.0 == Weight::NORMAL.0 {
235 json!("normal")
236 } else if self.0 == Weight::MEDIUM.0 {
237 json!("medium")
238 } else if self.0 == Weight::SEMIBOLD.0 {
239 json!("semibold")
240 } else if self.0 == Weight::BOLD.0 {
241 json!("bold")
242 } else if self.0 == Weight::EXTRA_BOLD.0 {
243 json!("extra bold")
244 } else if self.0 == Weight::BLACK.0 {
245 json!("black")
246 } else {
247 json!(self.0)
248 }
249 }
250}
251
252impl ToJson for Stretch {
253 fn to_json(&self) -> serde_json::Value {
254 json!(self.0)
255 }
256}
257
258pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
259where
260 F: FnOnce() -> T,
261{
262 FONT_CACHE.with(|cache| {
263 *cache.borrow_mut() = Some(font_cache);
264 let result = callback();
265 cache.borrow_mut().take();
266 result
267 })
268}