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 From<TextStyle> for HighlightStyle {
130 fn from(other: TextStyle) -> Self {
131 Self {
132 color: other.color,
133 font_properties: other.font_properties,
134 underline: other.underline,
135 }
136 }
137}
138
139impl HighlightStyle {
140 fn from_json(json: HighlightStyleJson) -> Self {
141 let font_properties = properties_from_json(json.weight, json.italic);
142 Self {
143 color: json.color,
144 font_properties,
145 underline: json.underline,
146 }
147 }
148}
149
150impl From<Color> for HighlightStyle {
151 fn from(color: Color) -> Self {
152 Self {
153 color,
154 font_properties: Default::default(),
155 underline: false,
156 }
157 }
158}
159
160impl<'de> Deserialize<'de> for TextStyle {
161 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162 where
163 D: serde::Deserializer<'de>,
164 {
165 Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?)
166 .map_err(|e| de::Error::custom(e))?)
167 }
168}
169
170impl ToJson for TextStyle {
171 fn to_json(&self) -> Value {
172 json!({
173 "color": self.color.to_json(),
174 "font_family": self.font_family_name.as_ref(),
175 "font_properties": self.font_properties.to_json(),
176 })
177 }
178}
179
180impl<'de> Deserialize<'de> for HighlightStyle {
181 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182 where
183 D: serde::Deserializer<'de>,
184 {
185 let json = serde_json::Value::deserialize(deserializer)?;
186 if json.is_object() {
187 Ok(Self::from_json(
188 serde_json::from_value(json).map_err(de::Error::custom)?,
189 ))
190 } else {
191 Ok(Self {
192 color: serde_json::from_value(json).map_err(de::Error::custom)?,
193 font_properties: Properties::new(),
194 underline: false,
195 })
196 }
197 }
198}
199
200fn properties_from_json(weight: Option<WeightJson>, italic: bool) -> Properties {
201 let weight = match weight.unwrap_or(WeightJson::normal) {
202 WeightJson::thin => Weight::THIN,
203 WeightJson::extra_light => Weight::EXTRA_LIGHT,
204 WeightJson::light => Weight::LIGHT,
205 WeightJson::normal => Weight::NORMAL,
206 WeightJson::medium => Weight::MEDIUM,
207 WeightJson::semibold => Weight::SEMIBOLD,
208 WeightJson::bold => Weight::BOLD,
209 WeightJson::extra_bold => Weight::EXTRA_BOLD,
210 WeightJson::black => Weight::BLACK,
211 };
212 let style = if italic { Style::Italic } else { Style::Normal };
213 *Properties::new().weight(weight).style(style)
214}
215
216impl ToJson for Properties {
217 fn to_json(&self) -> crate::json::Value {
218 json!({
219 "style": self.style.to_json(),
220 "weight": self.weight.to_json(),
221 "stretch": self.stretch.to_json(),
222 })
223 }
224}
225
226impl ToJson for Style {
227 fn to_json(&self) -> crate::json::Value {
228 match self {
229 Style::Normal => json!("normal"),
230 Style::Italic => json!("italic"),
231 Style::Oblique => json!("oblique"),
232 }
233 }
234}
235
236impl ToJson for Weight {
237 fn to_json(&self) -> crate::json::Value {
238 if self.0 == Weight::THIN.0 {
239 json!("thin")
240 } else if self.0 == Weight::EXTRA_LIGHT.0 {
241 json!("extra light")
242 } else if self.0 == Weight::LIGHT.0 {
243 json!("light")
244 } else if self.0 == Weight::NORMAL.0 {
245 json!("normal")
246 } else if self.0 == Weight::MEDIUM.0 {
247 json!("medium")
248 } else if self.0 == Weight::SEMIBOLD.0 {
249 json!("semibold")
250 } else if self.0 == Weight::BOLD.0 {
251 json!("bold")
252 } else if self.0 == Weight::EXTRA_BOLD.0 {
253 json!("extra bold")
254 } else if self.0 == Weight::BLACK.0 {
255 json!("black")
256 } else {
257 json!(self.0)
258 }
259 }
260}
261
262impl ToJson for Stretch {
263 fn to_json(&self) -> serde_json::Value {
264 json!(self.0)
265 }
266}
267
268pub fn with_font_cache<F, T>(font_cache: Arc<FontCache>, callback: F) -> T
269where
270 F: FnOnce() -> T,
271{
272 FONT_CACHE.with(|cache| {
273 *cache.borrow_mut() = Some(font_cache);
274 let result = callback();
275 cache.borrow_mut().take();
276 result
277 })
278}