1use std::cmp::Reverse;
2use std::collections::BTreeMap;
3use std::fmt::{self, Debug, Formatter};
4
5use serde::{Deserialize, Serialize};
6use ttf_parser::{PlatformId, Tag, name_id};
7use unicode_segmentation::UnicodeSegmentation;
8
9use super::exceptions::find_exception;
10use crate::text::{
11 Font, FontStretch, FontStyle, FontVariant, FontWeight, is_default_ignorable,
12};
13
14#[derive(Debug, Default, Clone, Hash)]
16pub struct FontBook {
17 families: BTreeMap<String, Vec<usize>>,
19 infos: Vec<FontInfo>,
21}
22
23impl FontBook {
24 pub fn new() -> Self {
26 Self { families: BTreeMap::new(), infos: vec![] }
27 }
28
29 pub fn from_infos(infos: impl IntoIterator<Item = FontInfo>) -> Self {
31 let mut book = Self::new();
32 for info in infos {
33 book.push(info);
34 }
35 book
36 }
37
38 pub fn from_fonts<'a>(fonts: impl IntoIterator<Item = &'a Font>) -> Self {
40 Self::from_infos(fonts.into_iter().map(|font| font.info().clone()))
41 }
42
43 pub fn push(&mut self, info: FontInfo) {
45 let index = self.infos.len();
46 let family = info.family.to_lowercase();
47 self.families.entry(family).or_default().push(index);
48 self.infos.push(info);
49 }
50
51 pub fn info(&self, index: usize) -> Option<&FontInfo> {
53 self.infos.get(index)
54 }
55
56 pub fn contains_family(&self, family: &str) -> bool {
58 self.families.contains_key(family)
59 }
60
61 pub fn families(
64 &self,
65 ) -> impl Iterator<Item = (&str, impl Iterator<Item = &FontInfo>)> + '_ {
66 self.families.values().map(|ids| {
69 let family = self.infos[ids[0]].family.as_str();
70 let infos = ids.iter().map(|&id| &self.infos[id]);
71 (family, infos)
72 })
73 }
74
75 pub fn select(&self, family: &str, variant: FontVariant) -> Option<usize> {
80 let ids = self.families.get(family)?;
81 self.find_best_variant(None, variant, ids.iter().copied())
82 }
83
84 pub fn select_family(&self, family: &str) -> impl Iterator<Item = usize> + '_ {
86 self.families
87 .get(family)
88 .map(|vec| vec.as_slice())
89 .unwrap_or_default()
90 .iter()
91 .copied()
92 }
93
94 pub fn select_fallback(
99 &self,
100 like: Option<&FontInfo>,
101 variant: FontVariant,
102 text: &str,
103 ) -> Option<usize> {
104 let c = text
107 .chars()
108 .find(|&c| !c.is_whitespace() && !is_default_ignorable(c))?;
109
110 let ids = self
111 .infos
112 .iter()
113 .enumerate()
114 .filter(|(_, info)| info.coverage.contains(c as u32))
115 .map(|(index, _)| index);
116
117 self.find_best_variant(like, variant, ids)
119 }
120
121 fn find_best_variant(
143 &self,
144 like: Option<&FontInfo>,
145 variant: FontVariant,
146 ids: impl IntoIterator<Item = usize>,
147 ) -> Option<usize> {
148 let mut best = None;
149 let mut best_key = None;
150
151 for id in ids {
152 let current = &self.infos[id];
153 let key = (
154 like.map(|like| {
155 (
156 current.flags.contains(FontFlags::MONOSPACE)
157 != like.flags.contains(FontFlags::MONOSPACE),
158 current.flags.contains(FontFlags::SERIF)
159 != like.flags.contains(FontFlags::SERIF),
160 Reverse(shared_prefix_words(¤t.family, &like.family)),
161 current.family.len(),
162 )
163 }),
164 current.variant.style.distance(variant.style),
165 current.variant.stretch.distance(variant.stretch),
166 current.variant.weight.distance(variant.weight),
167 );
168
169 if best_key.is_none_or(|b| key < b) {
170 best = Some(id);
171 best_key = Some(key);
172 }
173 }
174
175 best
176 }
177}
178
179#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
181pub struct FontInfo {
182 pub family: String,
184 pub variant: FontVariant,
187 pub flags: FontFlags,
189 pub coverage: Coverage,
191}
192
193bitflags::bitflags! {
194 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
196 #[derive(Serialize, Deserialize)]
197 #[serde(transparent)]
198 pub struct FontFlags: u32 {
199 const MONOSPACE = 1 << 0;
201 const SERIF = 1 << 1;
203 const MATH = 1 << 2;
205 const VARIABLE = 1 << 3;
207 }
208}
209
210impl FontInfo {
211 pub fn new(data: &[u8], index: u32) -> Option<Self> {
213 let ttf = ttf_parser::Face::parse(data, index).ok()?;
214 Self::from_ttf(&ttf)
215 }
216
217 pub fn iter(data: &[u8]) -> impl Iterator<Item = FontInfo> + '_ {
219 let count = ttf_parser::fonts_in_collection(data).unwrap_or(1);
220 (0..count).filter_map(move |index| Self::new(data, index))
221 }
222
223 pub(super) fn from_ttf(ttf: &ttf_parser::Face) -> Option<Self> {
225 let ps_name = find_name(ttf, name_id::POST_SCRIPT_NAME);
226 let exception = ps_name.as_deref().and_then(find_exception);
227 let family =
239 exception.and_then(|c| c.family.map(str::to_string)).or_else(|| {
240 let family = find_name(ttf, name_id::FAMILY)?;
241 Some(typographic_family(&family).to_string())
242 })?;
243
244 let variant = {
245 let style = exception.and_then(|c| c.style).unwrap_or_else(|| {
246 let mut full = find_name(ttf, name_id::FULL_NAME).unwrap_or_default();
247 full.make_ascii_lowercase();
248
249 let italic = ttf.is_italic() || full.contains("italic");
252 let oblique = ttf.is_oblique()
253 || full.contains("oblique")
254 || full.contains("slanted");
255
256 match (italic, oblique) {
257 (false, false) => FontStyle::Normal,
258 (true, _) => FontStyle::Italic,
259 (_, true) => FontStyle::Oblique,
260 }
261 });
262
263 let weight = exception.and_then(|c| c.weight).unwrap_or_else(|| {
264 let number = ttf.weight().to_number();
265 FontWeight::from_number(number)
266 });
267
268 let stretch = exception
269 .and_then(|c| c.stretch)
270 .unwrap_or_else(|| FontStretch::from_number(ttf.width().to_number()));
271
272 FontVariant { style, weight, stretch }
273 };
274
275 let mut codepoints = vec![];
277 for subtable in ttf.tables().cmap.into_iter().flat_map(|table| table.subtables) {
278 if subtable.is_unicode() {
279 subtable.codepoints(|c| codepoints.push(c));
280 }
281 }
282
283 let mut flags = FontFlags::empty();
284 flags.set(FontFlags::MONOSPACE, ttf.is_monospaced());
285 flags.set(FontFlags::MATH, ttf.tables().math.is_some());
286 flags.set(FontFlags::VARIABLE, ttf.is_variable());
287
288 if let Some(panose) = ttf
290 .raw_face()
291 .table(Tag::from_bytes(b"OS/2"))
292 .and_then(|os2| os2.get(32..45))
293 && matches!(panose, [2, 2..=10, ..])
294 {
295 flags.insert(FontFlags::SERIF);
296 }
297
298 Some(FontInfo {
299 family,
300 variant,
301 flags,
302 coverage: Coverage::from_vec(codepoints),
303 })
304 }
305
306 pub fn is_last_resort(&self) -> bool {
309 self.family == "LastResort"
310 }
311}
312
313pub(super) fn find_name(ttf: &ttf_parser::Face, name_id: u16) -> Option<String> {
315 ttf.names().into_iter().find_map(|entry| {
316 if entry.name_id == name_id {
317 if let Some(string) = entry.to_string() {
318 return Some(string);
319 }
320
321 if entry.platform_id == PlatformId::Macintosh && entry.encoding_id == 0 {
322 return Some(decode_mac_roman(entry.name));
323 }
324 }
325
326 None
327 })
328}
329
330fn decode_mac_roman(coded: &[u8]) -> String {
332 #[rustfmt::skip]
333 const TABLE: [char; 128] = [
334 'Ä', 'Å', 'Ç', 'É', 'Ñ', 'Ö', 'Ü', 'á', 'à', 'â', 'ä', 'ã', 'å', 'ç', 'é', 'è',
335 'ê', 'ë', 'í', 'ì', 'î', 'ï', 'ñ', 'ó', 'ò', 'ô', 'ö', 'õ', 'ú', 'ù', 'û', 'ü',
336 '†', '°', '¢', '£', '§', '•', '¶', 'ß', '®', '©', '™', '´', '¨', '≠', 'Æ', 'Ø',
337 '∞', '±', '≤', '≥', '¥', 'µ', '∂', '∑', '∏', 'π', '∫', 'ª', 'º', 'Ω', 'æ', 'ø',
338 '¿', '¡', '¬', '√', 'ƒ', '≈', '∆', '«', '»', '…', '\u{a0}', 'À', 'Ã', 'Õ', 'Œ', 'œ',
339 '–', '—', '“', '”', '‘', '’', '÷', '◊', 'ÿ', 'Ÿ', '⁄', '€', '‹', '›', 'fi', 'fl',
340 '‡', '·', '‚', '„', '‰', 'Â', 'Ê', 'Á', 'Ë', 'È', 'Í', 'Î', 'Ï', 'Ì', 'Ó', 'Ô',
341 '\u{f8ff}', 'Ò', 'Ú', 'Û', 'Ù', 'ı', 'ˆ', '˜', '¯', '˘', '˙', '˚', '¸', '˝', '˛', 'ˇ',
342 ];
343
344 fn char_from_mac_roman(code: u8) -> char {
345 if code < 128 { code as char } else { TABLE[(code - 128) as usize] }
346 }
347
348 coded.iter().copied().map(char_from_mac_roman).collect()
349}
350
351fn typographic_family(mut family: &str) -> &str {
353 const SEPARATORS: [char; 3] = [' ', '-', '_'];
355
356 const MODIFIERS: &[&str] =
358 &["extra", "ext", "ex", "x", "semi", "sem", "sm", "demi", "dem", "ultra"];
359
360 #[rustfmt::skip]
362 const SUFFIXES: &[&str] = &[
363 "normal", "italic", "oblique", "slanted",
364 "thin", "th", "hairline", "light", "lt", "regular", "medium", "med",
365 "md", "bold", "bd", "demi", "extb", "black", "blk", "bk", "heavy",
366 "narrow", "condensed", "cond", "cn", "cd", "compressed", "expanded", "exp"
367 ];
368
369 family = family.trim().trim_start_matches('.');
371
372 let lower = family.to_ascii_lowercase();
374 let mut len = usize::MAX;
375 let mut trimmed = lower.as_str();
376
377 while trimmed.len() < len {
379 len = trimmed.len();
380
381 let mut t = trimmed;
383 let mut shortened = false;
384 while let Some(s) = SUFFIXES.iter().find_map(|s| t.strip_suffix(s)) {
385 shortened = true;
386 t = s;
387 }
388
389 if !shortened {
390 break;
391 }
392
393 if let Some(s) = t.strip_suffix(SEPARATORS) {
395 trimmed = s;
396 t = s;
397 }
398
399 if let Some(t) = MODIFIERS.iter().find_map(|s| t.strip_suffix(s))
402 && let Some(stripped) = t.strip_suffix(SEPARATORS)
403 {
404 trimmed = stripped;
405 }
406 }
407
408 family = &family[..len];
410
411 family
412}
413
414fn shared_prefix_words(left: &str, right: &str) -> usize {
416 left.unicode_words()
417 .zip(right.unicode_words())
418 .take_while(|(l, r)| l == r)
419 .count()
420}
421
422#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
439#[serde(transparent)]
440pub struct Coverage(Vec<u32>);
441
442impl Coverage {
443 pub fn from_vec(mut codepoints: Vec<u32>) -> Self {
445 codepoints.sort();
446 codepoints.dedup();
447
448 let mut runs = Vec::new();
449 let mut next = 0;
450
451 for c in codepoints {
452 if let Some(run) = runs.last_mut().filter(|_| c == next) {
453 *run += 1;
454 } else {
455 runs.push(c - next);
456 runs.push(1);
457 }
458
459 next = c + 1;
460 }
461
462 Self(runs)
463 }
464
465 pub fn contains(&self, c: u32) -> bool {
467 let mut inside = false;
468 let mut cursor = 0;
469
470 for &run in &self.0 {
471 if (cursor..cursor + run).contains(&c) {
472 return inside;
473 }
474 cursor += run;
475 inside = !inside;
476 }
477
478 false
479 }
480
481 pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
483 let mut inside = false;
484 let mut cursor = 0;
485 self.0.iter().flat_map(move |run| {
486 let range = if inside { cursor..cursor + run } else { 0..0 };
487 inside = !inside;
488 cursor += run;
489 range
490 })
491 }
492}
493
494impl Debug for Coverage {
495 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
496 f.pad("Coverage(..)")
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn test_trim_styles() {
506 assert_eq!(typographic_family("Atma Light"), "Atma");
507 assert_eq!(typographic_family("eras bold"), "eras");
508 assert_eq!(typographic_family("footlight mt light"), "footlight mt");
509 assert_eq!(typographic_family("times new roman"), "times new roman");
510 assert_eq!(typographic_family("noto sans mono cond sembd"), "noto sans mono");
511 assert_eq!(typographic_family("noto serif SEMCOND sembd"), "noto serif");
512 assert_eq!(typographic_family("crimson text"), "crimson text");
513 assert_eq!(typographic_family("footlight light"), "footlight");
514 assert_eq!(typographic_family("Noto Sans"), "Noto Sans");
515 assert_eq!(typographic_family("Noto Sans Light"), "Noto Sans");
516 assert_eq!(typographic_family("Noto Sans Semicondensed Heavy"), "Noto Sans");
517 assert_eq!(typographic_family("Familx"), "Familx");
518 assert_eq!(typographic_family("Font Ultra"), "Font Ultra");
519 assert_eq!(typographic_family("Font Ultra Bold"), "Font");
520 }
521
522 #[test]
523 fn test_coverage() {
524 #[track_caller]
525 fn test(set: &[u32], runs: &[u32]) {
526 let coverage = Coverage::from_vec(set.to_vec());
527 assert_eq!(coverage.0, runs);
528
529 let max = 5 + set.iter().copied().max().unwrap_or_default();
530 for c in 0..max {
531 assert_eq!(set.contains(&c), coverage.contains(c));
532 }
533 }
534
535 test(&[], &[]);
536 test(&[0], &[0, 1]);
537 test(&[1], &[1, 1]);
538 test(&[0, 1], &[0, 2]);
539 test(&[0, 1, 3], &[0, 2, 1, 1]);
540 test(
541 &[18, 19, 2, 4, 9, 11, 15, 3, 3, 10],
543 &[2, 3, 4, 3, 3, 1, 2, 2],
544 )
545 }
546
547 #[test]
548 fn test_coverage_iter() {
549 let codepoints = vec![2, 3, 7, 8, 9, 14, 15, 19, 21];
550 let coverage = Coverage::from_vec(codepoints.clone());
551 assert_eq!(coverage.iter().collect::<Vec<_>>(), codepoints);
552 }
553}