1#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2pub use desktop::*;
3
4#[cfg(target_arch = "wasm32")]
5pub use wasm::*;
6
7#[cfg(target_os = "android")]
8pub use android::*;
9
10#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
11mod desktop {
12 use std::{
13 path::{Path, PathBuf},
14 sync::Arc,
15 };
16
17 use parking_lot::Mutex;
18 use zng_var::ResponseVar;
19
20 use crate::{FontBytes, FontLoadingError, FontName, FontStretch, FontStyle, FontWeight, GlyphLoadingError, WeakFontBytes};
21
22 static DATA_CACHE: Mutex<Vec<(PathBuf, WeakFontBytes)>> = Mutex::new(vec![]);
23
24 pub fn system_all() -> ResponseVar<Vec<FontName>> {
25 zng_task::wait_respond(|| {
26 font_kit::source::SystemSource::new()
27 .all_families()
28 .unwrap_or_default()
29 .into_iter()
30 .map(FontName::from)
31 .collect()
32 })
33 }
34
35 pub fn best(
36 font_name: &FontName,
37 style: FontStyle,
38 weight: FontWeight,
39 stretch: FontStretch,
40 ) -> Result<Option<(FontBytes, u32)>, FontLoadingError> {
41 if font_name == "Ubuntu"
42 && let Ok(Some(h)) = workaround_ubuntu(style, weight, stretch)
43 {
44 return Ok(Some(h));
45 }
46
47 let family_name = font_kit::family_name::FamilyName::from(font_name.clone());
48 match font_kit::source::SystemSource::new().select_best_match(
49 &[family_name],
50 &font_kit::properties::Properties {
51 style: style.into(),
52 weight: weight.into(),
53 stretch: stretch.into(),
54 },
55 ) {
56 Ok(handle) => {
57 let r = load_handle(&handle)?;
58 Ok(Some(r))
59 }
60 Err(font_kit::error::SelectionError::NotFound) => {
61 tracing::debug!(target: "font_loading", "system font not found\nquery: {:?}", (font_name, style, weight, stretch));
62 Ok(None)
63 }
64 Err(font_kit::error::SelectionError::CannotAccessSource { reason }) => {
65 Err(FontLoadingError::Io(Arc::new(std::io::Error::other(reason.unwrap_or_default()))))
66 }
67 }
68 }
69
70 fn workaround_ubuntu(style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Result<Option<(FontBytes, u32)>, FontLoadingError> {
72 let source = font_kit::source::SystemSource::new();
73 let ubuntu = match source.select_family_by_name("Ubuntu") {
74 Ok(u) => u,
75 Err(e) => {
76 return match e {
77 font_kit::error::SelectionError::NotFound => Ok(None),
78 font_kit::error::SelectionError::CannotAccessSource { reason } => {
79 Err(FontLoadingError::Io(Arc::new(std::io::Error::other(reason.unwrap_or_default()))))
80 }
81 };
82 }
83 };
84 for handle in ubuntu.fonts() {
85 let font = handle.load()?;
86 let name = match font.postscript_name() {
87 Some(n) => n,
88 None => continue,
89 };
90
91 if (style == FontStyle::Italic) != name.contains("Italic") {
118 continue;
119 }
120
121 if (FontWeight::MEDIUM..FontWeight::SEMIBOLD).contains(&weight) != name.contains("Medium") {
122 continue;
123 }
124 if (weight >= FontWeight::EXTRA_BOLD) != name.contains("ExtraBold") {
125 continue;
126 }
127 if (FontWeight::SEMIBOLD..FontWeight::EXTRA_BOLD).contains(&weight) != name.contains("Bold") {
128 continue;
129 }
130
131 if (FontWeight::EXTRA_LIGHT..FontWeight::LIGHT).contains(&weight) != name.contains("Light") {
132 continue;
133 }
134 if (weight < FontWeight::EXTRA_LIGHT) != name.contains("Thin") {
135 continue;
136 }
137
138 if (stretch <= FontStretch::CONDENSED) != name.contains("Condensed") {
139 continue;
140 }
141
142 return Ok(Some(load_handle(handle)?));
143 }
144 Ok(None)
145 }
146
147 fn load_handle(handle: &font_kit::handle::Handle) -> Result<(FontBytes, u32), FontLoadingError> {
148 match handle {
149 font_kit::handle::Handle::Path { path, font_index } => {
150 let mut path = path.clone();
151 if let Ok(base) = path.strip_prefix("/usr/share/fonts/type1/")
158 && let Some(name) = base.file_name()
159 && let Some(name) = name.to_str()
160 && name.ends_with(".t1")
161 {
162 let rep = Path::new("/usr/share/fonts/opentype/").join(base.with_extension("otf"));
163 if rep.exists() {
164 tracing::debug!("replaced `{name}` with .otf of same name");
165 path = rep;
166 }
167 }
168
169 for (k, data) in DATA_CACHE.lock().iter() {
170 if *k == *path
171 && let Some(data) = data.upgrade()
172 {
173 return Ok((data, *font_index));
174 }
175 }
176
177 let data = FontBytes::from_file(path.clone())?;
178
179 let mut cache = DATA_CACHE.lock();
180 cache.retain(|(_, v)| v.strong_count() > 0);
181 cache.push((path.clone(), data.downgrade()));
182
183 Ok((data, *font_index))
184 }
185 font_kit::handle::Handle::Memory { bytes, font_index } => Ok((FontBytes::from_arc(bytes.clone()), *font_index)),
186 }
187 }
188
189 impl From<font_kit::error::FontLoadingError> for FontLoadingError {
190 fn from(ve: font_kit::error::FontLoadingError) -> Self {
191 match ve {
192 font_kit::error::FontLoadingError::UnknownFormat => Self::UnknownFormat,
193 font_kit::error::FontLoadingError::NoSuchFontInCollection => Self::NoSuchFontInCollection,
194 font_kit::error::FontLoadingError::Parse => Self::Parse(ttf_parser::FaceParsingError::MalformedFont),
195 font_kit::error::FontLoadingError::NoFilesystem => Self::NoFilesystem,
196 font_kit::error::FontLoadingError::Io(e) => Self::Io(Arc::new(e)),
197 }
198 }
199 }
200
201 impl From<FontName> for font_kit::family_name::FamilyName {
202 fn from(font_name: FontName) -> Self {
203 use font_kit::family_name::FamilyName::*;
204 match font_name.name() {
205 "serif" => Serif,
206 "sans-serif" => SansSerif,
207 "monospace" => Monospace,
208 "cursive" => Cursive,
209 "fantasy" => Fantasy,
210 _ => Title(font_name.txt.into()),
211 }
212 }
213 }
214
215 impl From<FontStretch> for font_kit::properties::Stretch {
216 fn from(value: FontStretch) -> Self {
217 font_kit::properties::Stretch(value.0)
218 }
219 }
220
221 impl From<FontStyle> for font_kit::properties::Style {
222 fn from(value: FontStyle) -> Self {
223 use font_kit::properties::Style::*;
224 match value {
225 FontStyle::Normal => Normal,
226 FontStyle::Italic => Italic,
227 FontStyle::Oblique => Oblique,
228 }
229 }
230 }
231
232 impl From<FontWeight> for font_kit::properties::Weight {
233 fn from(value: FontWeight) -> Self {
234 font_kit::properties::Weight(value.0)
235 }
236 }
237
238 impl From<font_kit::error::GlyphLoadingError> for GlyphLoadingError {
239 fn from(value: font_kit::error::GlyphLoadingError) -> Self {
240 use GlyphLoadingError::*;
241 match value {
242 font_kit::error::GlyphLoadingError::NoSuchGlyph => NoSuchGlyph,
243 font_kit::error::GlyphLoadingError::PlatformError => PlatformError,
244 }
245 }
246 }
247}
248
249#[cfg(target_arch = "wasm32")]
250mod wasm {
251 use zng_var::ResponseVar;
252
253 use crate::{FontBytes, FontLoadingError, FontName, FontStretch, FontStyle, FontWeight};
254
255 pub fn system_all() -> ResponseVar<Vec<FontName>> {
256 zng_var::response_done_var(vec![])
257 }
258
259 pub fn best(
260 font_name: &FontName,
261 style: FontStyle,
262 weight: FontWeight,
263 stretch: FontStretch,
264 ) -> Result<Option<(FontBytes, u32)>, FontLoadingError> {
265 let _ = (font_name, style, weight, stretch);
266 Err(FontLoadingError::NoFilesystem)
267 }
268}
269
270#[cfg(target_os = "android")]
271mod android {
272 use std::{borrow::Cow, path::PathBuf, sync::Arc};
276
277 use zng_var::ResponseVar;
278
279 use crate::{FontBytes, FontLoadingError, FontName, FontStretch, FontStyle, FontWeight};
280
281 pub fn system_all() -> ResponseVar<Vec<FontName>> {
282 zng_task::wait_respond(|| {
283 let mut prev = None;
284 cached_system_all()
285 .iter()
286 .flat_map(|(k, _)| {
287 if prev == Some(k) {
288 None
289 } else {
290 prev = Some(k);
291 Some(k)
292 }
293 })
294 .cloned()
295 .collect()
296 })
297 }
298
299 fn cached_system_all() -> parking_lot::MappedRwLockReadGuard<'static, Vec<(FontName, PathBuf)>> {
300 let lock = SYSTEM_ALL.read();
301 if !lock.is_empty() {
302 return lock;
303 }
304
305 drop(lock);
306 let mut lock = SYSTEM_ALL.write();
307 if lock.is_empty() {
308 for entry in ["/system/fonts/", "/system/font/", "/data/fonts/", "/system/product/fonts/"]
309 .iter()
310 .flat_map(std::fs::read_dir)
311 .flatten()
312 .flatten()
313 {
314 let entry = entry.path();
315 let ext = entry.extension().and_then(|e| e.to_str()).unwrap_or_default().to_ascii_lowercase();
316 if ["ttf", "otf"].contains(&ext.as_str()) && entry.is_file() {
317 if let Ok(bytes) = FontBytes::from_file(entry.clone()) {
318 match crate::FontFace::load(bytes, 0) {
319 Ok(f) => {
320 lock.push((f.family_name().clone(), entry));
321 }
322 Err(e) => tracing::error!("error parsing '{}', {e}", entry.display()),
323 }
324 }
325 }
326 }
327 lock.sort_by(|a, b| a.0.cmp(&b.0));
328 }
329 drop(lock);
330 SYSTEM_ALL.read()
331 }
332
333 pub fn best(
334 font_name: &FontName,
335 style: FontStyle,
336 weight: FontWeight,
337 stretch: FontStretch,
338 ) -> Result<Option<(FontBytes, u32)>, FontLoadingError> {
339 let lock = cached_system_all();
340 let lock = &*lock;
341
342 let font_name = match font_name.name() {
345 "sans-serif" => Cow::Owned(FontName::new("Roboto")),
346 "serif" | "fantasy" => Cow::Owned(FontName::new("Noto Serif")),
347 "cursive" => Cow::Owned(FontName::new("Dancing Script")),
348 "monospace" => Cow::Owned(FontName::new("Droid Sans Mono")),
349 _ => Cow::Borrowed(font_name),
350 };
351 let font_name = &*font_name;
352
353 let mut start_i = match lock.binary_search_by(|a| a.0.cmp(font_name)) {
354 Ok(i) => i,
355 Err(_) => {
356 tracing::debug!(target: "font_loading", "system font not found\nquery: {:?}", (font_name, style, weight, stretch));
357 return Ok(None);
358 }
359 };
360 while start_i > 0 && &lock[start_i - 1].0 == font_name {
361 start_i -= 1
362 }
363 let mut end_i = start_i;
364 while end_i + 1 < lock.len() && &lock[end_i + 1].0 == font_name {
365 end_i += 1
366 }
367
368 let family_len = end_i - start_i;
369 let mut options = Vec::with_capacity(family_len);
370 let mut candidates = Vec::with_capacity(family_len);
371
372 for (_, path) in &lock[start_i..=end_i] {
373 if let Ok(bytes) = FontBytes::from_file(path.clone()) {
374 if let Ok(f) = ttf_parser::Face::parse(&bytes, 0) {
375 candidates.push(matching::Properties {
376 style: f.style(),
377 weight: f.weight(),
378 stretch: f.width(),
379 });
380 options.push(bytes);
381 }
382 }
383 }
384
385 match matching::find_best_match(
386 &candidates,
387 &matching::Properties {
388 style: style.into(),
389 weight: weight.into(),
390 stretch: stretch.into(),
391 },
392 ) {
393 Ok(i) => {
394 let bytes = options.swap_remove(i);
395 Ok(Some((bytes, 0)))
396 }
397 Err(FontLoadingError::NoSuchFontInCollection) => {
398 tracing::debug!(target: "font_loading", "system font not found\nquery: {:?}", (font_name, style, weight, stretch));
399 Ok(None)
400 }
401 Err(e) => Err(FontLoadingError::Io(Arc::new(std::io::Error::other(e)))),
402 }
403 }
404
405 zng_app_context::app_local! {
406 static SYSTEM_ALL: Vec<(FontName, PathBuf)> = vec![];
407 }
408
409 mod matching {
410 use ttf_parser::{Style, Weight, Width as Stretch};
423
424 use crate::FontLoadingError;
425
426 pub struct Properties {
427 pub style: Style,
428 pub weight: Weight,
429 pub stretch: Stretch,
430 }
431
432 pub fn find_best_match(candidates: &[Properties], query: &Properties) -> Result<usize, FontLoadingError> {
436 let mut matching_set: Vec<usize> = (0..candidates.len()).collect();
438 if matching_set.is_empty() {
439 return Err(FontLoadingError::NoSuchFontInCollection);
440 }
441
442 let matching_stretch = if matching_set.iter().any(|&index| candidates[index].stretch == query.stretch) {
444 query.stretch
446 } else if query.stretch <= Stretch::Normal {
447 match matching_set
449 .iter()
450 .filter(|&&index| candidates[index].stretch < query.stretch)
451 .min_by_key(|&&index| query.stretch.to_number() - candidates[index].stretch.to_number())
452 {
453 Some(&matching_index) => candidates[matching_index].stretch,
454 None => {
455 let matching_index = *matching_set
456 .iter()
457 .min_by_key(|&&index| candidates[index].stretch.to_number() - query.stretch.to_number())
458 .unwrap();
459 candidates[matching_index].stretch
460 }
461 }
462 } else {
463 match matching_set
465 .iter()
466 .filter(|&&index| candidates[index].stretch > query.stretch)
467 .min_by_key(|&&index| candidates[index].stretch.to_number() - query.stretch.to_number())
468 {
469 Some(&matching_index) => candidates[matching_index].stretch,
470 None => {
471 let matching_index = *matching_set
472 .iter()
473 .min_by_key(|&&index| query.stretch.to_number() - candidates[index].stretch.to_number())
474 .unwrap();
475 candidates[matching_index].stretch
476 }
477 }
478 };
479 matching_set.retain(|&index| candidates[index].stretch == matching_stretch);
480
481 let style_preference = match query.style {
483 Style::Italic => [Style::Italic, Style::Oblique, Style::Normal],
484 Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal],
485 Style::Normal => [Style::Normal, Style::Oblique, Style::Italic],
486 };
487 let matching_style = *style_preference
488 .iter()
489 .find(|&query_style| matching_set.iter().any(|&index| candidates[index].style == *query_style))
490 .unwrap();
491 matching_set.retain(|&index| candidates[index].style == matching_style);
492
493 let matching_weight = if matching_set.iter().any(|&index| candidates[index].weight == query.weight) {
498 query.weight
499 } else if query.weight.to_number() >= 400
500 && query.weight.to_number() < 450
501 && matching_set.iter().any(|&index| candidates[index].weight == Weight::from(500))
502 {
503 Weight::from(500)
505 } else if query.weight.to_number() >= 450
506 && query.weight.to_number() <= 500
507 && matching_set.iter().any(|&index| candidates[index].weight.to_number() == 400)
508 {
509 Weight::from(400)
511 } else if query.weight.to_number() <= 500 {
512 match matching_set
514 .iter()
515 .filter(|&&index| candidates[index].weight.to_number() <= query.weight.to_number())
516 .min_by_key(|&&index| query.weight.to_number() - candidates[index].weight.to_number())
517 {
518 Some(&matching_index) => candidates[matching_index].weight,
519 None => {
520 let matching_index = *matching_set
521 .iter()
522 .min_by_key(|&&index| candidates[index].weight.to_number() - query.weight.to_number())
523 .unwrap();
524 candidates[matching_index].weight
525 }
526 }
527 } else {
528 match matching_set
530 .iter()
531 .filter(|&&index| candidates[index].weight.to_number() >= query.weight.to_number())
532 .min_by_key(|&&index| candidates[index].weight.to_number() - query.weight.to_number())
533 {
534 Some(&matching_index) => candidates[matching_index].weight,
535 None => {
536 let matching_index = *matching_set
537 .iter()
538 .min_by_key(|&&index| query.weight.to_number() - candidates[index].weight.to_number())
539 .unwrap();
540 candidates[matching_index].weight
541 }
542 }
543 };
544 matching_set.retain(|&index| candidates[index].weight == matching_weight);
545
546 matching_set.into_iter().next().ok_or(FontLoadingError::NoSuchFontInCollection)
550 }
551 }
552}