1use super::util::*;
14use fontdue::layout::{CoordinateSystem, Layout, TextStyle};
15use fontdue::Font;
16use once_cell::sync::OnceCell;
17use snafu::Snafu;
18use std::collections::HashMap;
19
20#[derive(Debug, Snafu)]
21pub enum Error {
22 #[snafu(display("Error font: {name} not found"))]
23 FontNotFound { name: String },
24 #[snafu(display("Error parse font: {message}"))]
25 ParseFont { message: String },
26}
27
28impl From<&str> for Error {
29 fn from(value: &str) -> Self {
30 Error::ParseFont {
31 message: value.to_string(),
32 }
33 }
34}
35
36pub type Result<T, E = Error> = std::result::Result<T, E>;
37
38pub static DEFAULT_FONT_FAMILY: &str = "Roboto";
39pub static DEFAULT_FONT_DATA: &[u8] = include_bytes!("../Roboto.ttf");
40
41fn get_family_from_font(font: &fontdue::Font) -> String {
42 if let Ok(re) = regex::Regex::new(r#"name:( ?)Some\("(?P<family>[\S ]+)"\)"#) {
43 let desc = format!("{:?}", font);
44 if let Some(caps) = re.captures(&desc) {
45 let mut family = caps["family"].to_string();
46 if let Ok(weight) = regex::Regex::new(r#"Thin|Light|Regular|Medium|Bold|Black$"#) {
49 family = weight.replace_all(&family, "").to_string();
50 }
51 return family.trim().to_string();
52 }
53 }
54 "".to_string()
55}
56
57pub fn get_or_try_init_fonts(fonts: Option<Vec<&[u8]>>) -> Result<&'static HashMap<String, Font>> {
58 static GLOBAL_FONTS: OnceCell<HashMap<String, Font>> = OnceCell::new();
59 GLOBAL_FONTS.get_or_try_init(|| {
60 let mut m = HashMap::new();
61 let font = fontdue::Font::from_bytes(DEFAULT_FONT_DATA, fontdue::FontSettings::default())?;
63 m.insert(DEFAULT_FONT_FAMILY.to_string(), font);
64 let mut font_datas = vec![DEFAULT_FONT_DATA];
65 if let Some(value) = fonts {
66 for data in value.iter() {
67 let font = fontdue::Font::from_bytes(*data, fontdue::FontSettings::default())?;
68 let family = get_family_from_font(&font);
69 if !family.is_empty() {
70 m.insert(family, font);
71 font_datas.push(*data);
72 }
73 }
74 }
75 #[cfg(feature = "image-encoder")]
76 crate::get_or_init_fontdb(Some(font_datas));
77 Ok(m)
78 })
79}
80pub fn get_font(name: &str) -> Result<&Font> {
82 let fonts = get_or_try_init_fonts(None)?;
83 if let Some(font) = fonts.get(name).or_else(|| fonts.get(DEFAULT_FONT_FAMILY)) {
84 Ok(font)
85 } else {
86 FontNotFoundSnafu {
87 name: name.to_string(),
88 }
89 .fail()
90 }
91}
92pub fn get_font_families() -> Result<Vec<String>> {
94 let fonts = get_or_try_init_fonts(None)?;
95 let mut families = vec![];
96 for (name, _) in fonts.iter() {
97 families.push(name.to_string());
98 }
99 Ok(families)
100}
101
102pub fn measure_text(font: &Font, font_size: f32, text: &str) -> Box {
104 let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
105 layout.append(&[font], &TextStyle::new(text, font_size, 0));
106
107 let mut right = 0.0_f32;
108 let mut bottom = 0.0_f32;
109 for g in layout.glyphs().iter() {
110 let x = g.x + g.width as f32;
111 let y = g.y + g.height as f32;
112 if x > right {
113 right = x;
114 }
115 if y > bottom {
116 bottom = y;
117 }
118 }
119 Box {
120 right,
121 bottom,
122 ..Default::default()
123 }
124}
125
126pub fn measure_text_width_family(font_family: &str, font_size: f32, text: &str) -> Result<Box> {
128 let font = get_font(font_family)?;
129 Ok(measure_text(font, font_size, text))
130}
131
132pub fn measure_max_text_width_family(
134 font_family: &str,
135 font_size: f32,
136 texts: Vec<&str>,
137) -> Result<Box> {
138 let font = get_font(font_family)?;
139 let mut result = Box::default();
140 for item in texts.iter() {
141 let b = measure_text(font, font_size, item);
142 if b.width() > result.width() {
143 result = b;
144 }
145 }
146 Ok(result)
147}
148
149pub fn text_wrap_fit(
151 font_family: &str,
152 font_size: f32,
153 text: &str,
154 width: f32,
155) -> Result<Vec<String>> {
156 let font = get_font(font_family)?;
157 let b = measure_text(font, font_size, text);
158 if b.width() <= width {
159 return Ok(vec![text.to_string()]);
160 }
161
162 let mut current = "".to_string();
163 let mut result = vec![];
164 for item in text.chars() {
165 let new_str = current.clone() + &item.to_string();
166 let b = measure_text(font, font_size, &new_str);
167 if b.width() > width {
168 result.push(current);
169 current = item.to_string();
170 continue;
171 }
172 current = new_str;
173 }
174 if !current.is_empty() {
175 result.push(current);
176 }
177 Ok(result)
178}
179
180#[cfg(test)]
181mod tests {
182 use super::{get_font, get_font_families, measure_text_width_family, text_wrap_fit};
183 use pretty_assertions::assert_eq;
184 #[test]
185 fn measure_text() {
186 let name = "Roboto";
187 get_font(name).unwrap();
188
189 let str = "Hello World!";
190 let b = measure_text_width_family(name, 14.0, str).unwrap();
191
192 assert_eq!(79.0, b.width().ceil());
193 assert_eq!(14.0, b.height());
194
195 assert_eq!("Roboto", get_font_families().unwrap().join(","));
196 }
197 #[test]
198 fn wrap_fit() {
199 let name = "Roboto";
200 let result = text_wrap_fit(name, 14.0, "An event-driven, non-blocking I/O platform for writing asynchronous I/O backed applications", 100.0).unwrap();
201 assert_eq!(
202 vec![
203 "An event-drive",
204 "n, non-blocking ",
205 "I/O platform fo",
206 "r writing async",
207 "hronous I/O ba",
208 "cked applicati",
209 "ons",
210 ],
211 result
212 );
213 }
214}