1use crate::FcFontCache;
6use crate::OperatingSystem;
7use crate::UnicodeRange;
8use alloc::collections::BTreeMap;
9use alloc::string::{String, ToString};
10use alloc::vec::Vec;
11use std::path::{Path, PathBuf};
12
13pub const FONT_STYLE_TOKENS: &[&str] = &[
20 "Regular",
21 "Bold",
22 "Italic",
23 "Light",
24 "Medium",
25 "Thin",
26 "Black",
27 "ExtraLight",
28 "ExtraBold",
29 "SemiBold",
30 "DemiBold",
31 "Heavy",
32 "Oblique",
33 "Condensed",
34 "Expanded",
35 "Extra",
38 "Semi",
39 "Demi",
40];
41
42pub fn is_generic_family(family: &str) -> bool {
44 GenericFamily::from_css(family).is_some()
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub enum GenericFamily {
50 Serif,
51 SansSerif,
52 Monospace,
53 Cursive,
54 Fantasy,
55 SystemUi,
56 UiSerif,
57 UiSansSerif,
58 UiMonospace,
59 UiRounded,
60 Emoji,
61 Math,
62 Fangsong,
63}
64
65impl GenericFamily {
66 pub const ALL: &'static [GenericFamily] = &[
68 GenericFamily::Serif,
69 GenericFamily::SansSerif,
70 GenericFamily::Monospace,
71 GenericFamily::Cursive,
72 GenericFamily::Fantasy,
73 GenericFamily::SystemUi,
74 GenericFamily::UiSerif,
75 GenericFamily::UiSansSerif,
76 GenericFamily::UiMonospace,
77 GenericFamily::UiRounded,
78 GenericFamily::Emoji,
79 GenericFamily::Math,
80 GenericFamily::Fangsong,
81 ];
82
83 pub fn from_css(name: &str) -> Option<Self> {
85 let key: String = name
86 .chars()
87 .filter(|c| c.is_ascii_alphanumeric())
88 .map(|c| c.to_ascii_lowercase())
89 .collect();
90 Some(match key.as_str() {
91 "serif" => GenericFamily::Serif,
92 "sansserif" => GenericFamily::SansSerif,
93 "monospace" => GenericFamily::Monospace,
94 "cursive" => GenericFamily::Cursive,
95 "fantasy" => GenericFamily::Fantasy,
96 "systemui" => GenericFamily::SystemUi,
97 "uiserif" => GenericFamily::UiSerif,
98 "uisansserif" => GenericFamily::UiSansSerif,
99 "uimonospace" => GenericFamily::UiMonospace,
100 "uirounded" => GenericFamily::UiRounded,
101 "emoji" => GenericFamily::Emoji,
102 "math" => GenericFamily::Math,
103 "fangsong" => GenericFamily::Fangsong,
104 _ => return None,
105 })
106 }
107
108 pub fn as_css(self) -> &'static str {
110 match self {
111 GenericFamily::Serif => "serif",
112 GenericFamily::SansSerif => "sans-serif",
113 GenericFamily::Monospace => "monospace",
114 GenericFamily::Cursive => "cursive",
115 GenericFamily::Fantasy => "fantasy",
116 GenericFamily::SystemUi => "system-ui",
117 GenericFamily::UiSerif => "ui-serif",
118 GenericFamily::UiSansSerif => "ui-sans-serif",
119 GenericFamily::UiMonospace => "ui-monospace",
120 GenericFamily::UiRounded => "ui-rounded",
121 GenericFamily::Emoji => "emoji",
122 GenericFamily::Math => "math",
123 GenericFamily::Fangsong => "fangsong",
124 }
125 }
126
127 pub fn parent(self) -> Option<Self> {
129 match self {
130 GenericFamily::Serif | GenericFamily::SansSerif | GenericFamily::Monospace => None,
131 GenericFamily::UiSerif | GenericFamily::Fangsong => Some(GenericFamily::Serif),
132 GenericFamily::UiMonospace => Some(GenericFamily::Monospace),
133 GenericFamily::Cursive
134 | GenericFamily::Fantasy
135 | GenericFamily::SystemUi
136 | GenericFamily::UiSansSerif
137 | GenericFamily::UiRounded
138 | GenericFamily::Emoji
139 | GenericFamily::Math => Some(GenericFamily::SansSerif),
140 }
141 }
142
143 fn lineage(self) -> impl Iterator<Item = GenericFamily> {
145 let mut next = Some(self);
146 core::iter::from_fn(move || {
147 let current = next?;
148 next = current.parent();
149 Some(current)
150 })
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct FcScriptFallback {
157 pub range: UnicodeRange,
159 pub generic: Option<GenericFamily>,
161 pub families: Vec<String>,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct FcFallbackConfig {
168 pub generic_families: BTreeMap<GenericFamily, Vec<String>>,
170 pub substitutions: BTreeMap<String, Vec<String>>,
172 pub script_fallbacks: Vec<FcScriptFallback>,
174 pub last_resort: Vec<String>,
176 pub default_generic: GenericFamily,
178}
179
180impl Default for FcFallbackConfig {
181 fn default() -> Self {
182 Self::empty()
183 }
184}
185
186pub mod blocks {
188 use crate::UnicodeRange;
189
190 pub const ARABIC: UnicodeRange = UnicodeRange {
191 start: 0x0600,
192 end: 0x06FF,
193 };
194 pub const HEBREW: UnicodeRange = UnicodeRange {
195 start: 0x0590,
196 end: 0x05FF,
197 };
198 pub const THAI: UnicodeRange = UnicodeRange {
199 start: 0x0E00,
200 end: 0x0E7F,
201 };
202 pub const CJK_SYMBOLS_AND_PUNCTUATION: UnicodeRange = UnicodeRange {
203 start: 0x3000,
204 end: 0x303F,
205 };
206 pub const HIRAGANA: UnicodeRange = UnicodeRange {
207 start: 0x3040,
208 end: 0x309F,
209 };
210 pub const KATAKANA: UnicodeRange = UnicodeRange {
211 start: 0x30A0,
212 end: 0x30FF,
213 };
214 pub const CJK_UNIFIED_IDEOGRAPHS: UnicodeRange = UnicodeRange {
215 start: 0x4E00,
216 end: 0x9FFF,
217 };
218 pub const HANGUL_SYLLABLES: UnicodeRange = UnicodeRange {
219 start: 0xAC00,
220 end: 0xD7A3,
221 };
222 pub const HALFWIDTH_AND_FULLWIDTH_FORMS: UnicodeRange = UnicodeRange {
223 start: 0xFF00,
224 end: 0xFFEF,
225 };
226}
227
228fn names(list: &[&str]) -> Vec<String> {
229 list.iter().map(|s| s.to_string()).collect()
230}
231
232fn push_unique(out: &mut Vec<String>, name: &str) {
233 if !out.iter().any(|e| e.eq_ignore_ascii_case(name)) {
234 out.push(name.to_string());
235 }
236}
237
238impl FcFallbackConfig {
239 pub fn empty() -> Self {
241 Self {
242 generic_families: BTreeMap::new(),
243 substitutions: BTreeMap::new(),
244 script_fallbacks: Vec::new(),
245 last_resort: Vec::new(),
246 default_generic: GenericFamily::SansSerif,
247 }
248 }
249
250 pub fn os_defaults(os: OperatingSystem) -> Self {
252 use blocks::*;
253 use GenericFamily::{Monospace, SansSerif, Serif, SystemUi};
254 let mut config = Self::empty();
255 let mut generic = |g: GenericFamily, list: &[&str]| {
256 config.generic_families.insert(g, names(list));
257 };
258 match os {
259 OperatingSystem::Windows => {
260 generic(Serif, &["Times New Roman"]);
261 generic(
262 SansSerif,
263 &[
264 "Segoe UI",
265 "Tahoma",
266 "Microsoft Sans Serif",
267 "MS Sans Serif",
268 "Helv",
269 ],
270 );
271 generic(
272 Monospace,
273 &[
274 "Segoe UI Mono",
275 "Courier New",
276 "Cascadia Code",
277 "Cascadia Mono",
278 "Consolas",
279 ],
280 );
281 }
282 OperatingSystem::Linux => {
283 generic(
284 Serif,
285 &[
286 "Times",
287 "Times New Roman",
288 "DejaVu Serif",
289 "Free Serif",
290 "Noto Serif",
291 "Bitstream Vera Serif",
292 "Roman",
293 "Regular",
294 ],
295 );
296 generic(
297 SansSerif,
298 &[
299 "Ubuntu",
300 "Arial",
301 "DejaVu Sans",
302 "Noto Sans",
303 "Liberation Sans",
304 ],
305 );
306 generic(
307 Monospace,
308 &[
309 "Source Code Pro",
310 "Cantarell",
311 "DejaVu Sans Mono",
312 "Roboto Mono",
313 "Ubuntu Monospace",
314 "Droid Sans Mono",
315 ],
316 );
317 }
318 OperatingSystem::MacOS | OperatingSystem::IOS => {
319 generic(
320 SystemUi,
321 &[
322 "San Francisco",
323 "SFNS",
324 "SFNSDisplay",
325 "SFNSText",
326 "SFUI",
327 ".AppleSystemUIFont",
328 ".SFUIText",
329 ".SFUI-Regular",
330 "System Font",
331 ],
332 );
333 generic(Serif, &["Times New Roman", "Times", "New York", "Palatino"]);
334 generic(SansSerif, &["Helvetica Neue", "Helvetica", "Lucida Grande"]);
335 generic(
336 Monospace,
337 &[
338 "SF Mono",
339 "Menlo",
340 "Monaco",
341 "Courier",
342 "Oxygen Mono",
343 "Source Code Pro",
344 "Fira Mono",
345 ],
346 );
347 }
348 OperatingSystem::Android => {
349 generic(Serif, &["Noto Serif", "Roboto Serif", "Droid Serif"]);
350 generic(
351 SansSerif,
352 &["Roboto", "Roboto-Regular", "Noto Sans", "Droid Sans"],
353 );
354 generic(
355 Monospace,
356 &[
357 "Roboto Mono",
358 "Droid Sans Mono",
359 "Noto Sans Mono",
360 "DejaVu Sans Mono",
361 ],
362 );
363 }
364 OperatingSystem::Wasm => {}
365 }
366
367 let mut script = |g: GenericFamily, range: UnicodeRange, list: &[&str]| {
368 config.script_fallbacks.push(FcScriptFallback {
369 range,
370 generic: Some(g),
371 families: names(list),
372 });
373 };
374
375 let mut cjk = |g: GenericFamily, ideographs: &[&str], kana: &[&str], hangul: &[&str]| {
379 for block in [
380 CJK_SYMBOLS_AND_PUNCTUATION,
381 CJK_UNIFIED_IDEOGRAPHS,
382 HALFWIDTH_AND_FULLWIDTH_FORMS,
383 ] {
384 script(g, block, ideographs);
385 }
386 for block in [HIRAGANA, KATAKANA] {
387 script(g, block, kana);
388 }
389 script(g, HANGUL_SYLLABLES, hangul);
390 };
391 match os {
392 OperatingSystem::Windows => {
393 cjk(
394 Serif,
395 &["MS Mincho", "SimSun", "MingLiU"],
396 &["MS Mincho", "SimSun", "MingLiU"],
397 &["SimSun", "MS Mincho", "MingLiU"],
398 );
399 cjk(
400 SansSerif,
401 &["Microsoft YaHei", "MS Gothic", "Malgun Gothic", "SimHei"],
402 &["MS Gothic", "Microsoft YaHei", "Malgun Gothic", "SimHei"],
403 &["Malgun Gothic", "Microsoft YaHei", "MS Gothic", "SimHei"],
404 );
405 cjk(
406 Monospace,
407 &["MS Gothic", "SimHei"],
408 &["MS Gothic", "SimHei"],
409 &["MS Gothic", "SimHei"],
410 );
411 script(Serif, ARABIC, &["Traditional Arabic"]);
412 script(SansSerif, ARABIC, &["Segoe UI Arabic"]);
413 script(SansSerif, HEBREW, &["Segoe UI Hebrew"]);
414 script(SansSerif, THAI, &["Leelawadee UI"]);
415 }
416 OperatingSystem::Linux => {
417 cjk(
418 Serif,
419 &[
420 "Noto Serif CJK SC",
421 "Noto Serif CJK JP",
422 "Noto Serif CJK KR",
423 ],
424 &[
425 "Noto Serif CJK JP",
426 "Noto Serif CJK SC",
427 "Noto Serif CJK KR",
428 ],
429 &[
430 "Noto Serif CJK KR",
431 "Noto Serif CJK SC",
432 "Noto Serif CJK JP",
433 ],
434 );
435 cjk(
436 SansSerif,
437 &[
438 "Noto Sans CJK SC",
439 "Noto Sans CJK JP",
440 "Noto Sans CJK KR",
441 "WenQuanYi Micro Hei",
442 "Droid Sans Fallback",
443 ],
444 &[
445 "Noto Sans CJK JP",
446 "Noto Sans CJK SC",
447 "Noto Sans CJK KR",
448 "WenQuanYi Micro Hei",
449 "Droid Sans Fallback",
450 ],
451 &[
452 "Noto Sans CJK KR",
453 "Noto Sans CJK SC",
454 "Noto Sans CJK JP",
455 "WenQuanYi Micro Hei",
456 "Droid Sans Fallback",
457 ],
458 );
459 cjk(
460 Monospace,
461 &[
462 "Noto Sans Mono CJK SC",
463 "Noto Sans Mono CJK JP",
464 "WenQuanYi Zen Hei Mono",
465 ],
466 &[
467 "Noto Sans Mono CJK JP",
468 "Noto Sans Mono CJK SC",
469 "WenQuanYi Zen Hei Mono",
470 ],
471 &[
472 "Noto Sans Mono CJK SC",
473 "Noto Sans Mono CJK JP",
474 "WenQuanYi Zen Hei Mono",
475 ],
476 );
477 script(Serif, ARABIC, &["Noto Serif Arabic"]);
478 script(SansSerif, ARABIC, &["Noto Sans Arabic"]);
479 script(SansSerif, HEBREW, &["Noto Sans Hebrew"]);
480 script(SansSerif, THAI, &["Noto Sans Thai"]);
481 }
482 OperatingSystem::MacOS | OperatingSystem::IOS => {
483 cjk(
484 Serif,
485 &["Hiragino Mincho ProN", "STSong", "AppleMyungjo"],
486 &["Hiragino Mincho ProN", "STSong", "AppleMyungjo"],
487 &["AppleMyungjo", "Hiragino Mincho ProN", "STSong"],
488 );
489 cjk(
490 SansSerif,
491 &[
492 "Hiragino Sans",
493 "Hiragino Kaku Gothic ProN",
494 "PingFang SC",
495 "PingFang TC",
496 "Apple SD Gothic Neo",
497 ],
498 &[
499 "Hiragino Sans",
500 "Hiragino Kaku Gothic ProN",
501 "PingFang SC",
502 "PingFang TC",
503 "Apple SD Gothic Neo",
504 ],
505 &[
506 "Apple SD Gothic Neo",
507 "Hiragino Sans",
508 "Hiragino Kaku Gothic ProN",
509 "PingFang SC",
510 "PingFang TC",
511 ],
512 );
513 cjk(
514 Monospace,
515 &["Hiragino Sans", "PingFang SC"],
516 &["Hiragino Sans", "PingFang SC"],
517 &["Hiragino Sans", "PingFang SC"],
518 );
519 script(Serif, ARABIC, &["Geeza Pro"]);
520 script(SansSerif, ARABIC, &["Geeza Pro"]);
521 script(SansSerif, HEBREW, &["Arial Hebrew"]);
522 script(SansSerif, THAI, &["Thonburi"]);
523 }
524 OperatingSystem::Android => {
525 cjk(
526 Serif,
527 &[
528 "Noto Serif CJK SC",
529 "Noto Serif CJK JP",
530 "Noto Serif CJK KR",
531 ],
532 &[
533 "Noto Serif CJK JP",
534 "Noto Serif CJK SC",
535 "Noto Serif CJK KR",
536 ],
537 &[
538 "Noto Serif CJK KR",
539 "Noto Serif CJK SC",
540 "Noto Serif CJK JP",
541 ],
542 );
543 cjk(
544 SansSerif,
545 &[
546 "Noto Sans CJK SC",
547 "Noto Sans CJK JP",
548 "Noto Sans CJK KR",
549 "Droid Sans Fallback",
550 ],
551 &[
552 "Noto Sans CJK JP",
553 "Noto Sans CJK SC",
554 "Noto Sans CJK KR",
555 "Droid Sans Fallback",
556 ],
557 &[
558 "Noto Sans CJK KR",
559 "Noto Sans CJK SC",
560 "Noto Sans CJK JP",
561 "Droid Sans Fallback",
562 ],
563 );
564 cjk(
565 Monospace,
566 &["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP"],
567 &["Noto Sans Mono CJK JP", "Noto Sans Mono CJK SC"],
568 &["Noto Sans Mono CJK SC", "Noto Sans Mono CJK JP"],
569 );
570 script(Serif, ARABIC, &["Noto Naskh Arabic"]);
571 script(SansSerif, ARABIC, &["Noto Sans Arabic"]);
572 script(SansSerif, HEBREW, &["Noto Sans Hebrew"]);
573 script(SansSerif, THAI, &["Noto Sans Thai"]);
574 }
575 OperatingSystem::Wasm => {}
576 }
577
578 config
579 }
580
581 pub fn generic_candidates(&self, generic: GenericFamily) -> &[String] {
584 generic
585 .lineage()
586 .find_map(|g| self.generic_families.get(&g))
587 .map(Vec::as_slice)
588 .unwrap_or(&[])
589 }
590
591 pub fn substitutions_for(&self, family: &str) -> &[String] {
593 self.substitutions
594 .get(&crate::utils::normalize_family_name(family))
595 .map(Vec::as_slice)
596 .unwrap_or(&[])
597 }
598
599 pub fn script_candidates(
603 &self,
604 generic: Option<GenericFamily>,
605 block: &UnicodeRange,
606 ) -> Vec<String> {
607 let mut out = Vec::new();
608 if let Some(generic) = generic {
609 for g in generic.lineage() {
610 for entry in &self.script_fallbacks {
611 if entry.generic == Some(g) && entry.range.overlaps(block) {
612 entry.families.iter().for_each(|f| push_unique(&mut out, f));
613 }
614 }
615 }
616 }
617 for entry in &self.script_fallbacks {
618 if entry.generic.is_none() && entry.range.overlaps(block) {
619 entry.families.iter().for_each(|f| push_unique(&mut out, f));
620 }
621 }
622 out
623 }
624
625 pub fn expand_generic(&self, generic: GenericFamily, ranges: &[UnicodeRange]) -> Vec<String> {
629 let mut out = Vec::new();
630 for block in ranges {
631 self.script_candidates(Some(generic), block)
632 .iter()
633 .for_each(|f| push_unique(&mut out, f));
634 }
635 self.generic_candidates(generic)
636 .iter()
637 .for_each(|f| push_unique(&mut out, f));
638 out
639 }
640
641 pub fn expand_family(&self, family: &str, ranges: &[UnicodeRange]) -> Vec<String> {
644 match GenericFamily::from_css(family) {
645 Some(generic) => self.expand_generic(generic, ranges),
646 None => {
647 let mut out = Vec::new();
648 push_unique(&mut out, family);
649 self.substitutions_for(family)
650 .iter()
651 .for_each(|f| push_unique(&mut out, f));
652 out
653 }
654 }
655 }
656
657 pub fn candidate_families(&self, stack: &[String], ranges: &[UnicodeRange]) -> Vec<String> {
660 let mut out = Vec::new();
661 let mut any_generic = false;
662 for family in stack {
663 any_generic |= GenericFamily::from_css(family).is_some();
664 self.expand_family(family, ranges)
665 .iter()
666 .for_each(|f| push_unique(&mut out, f));
667 }
668 for block in ranges {
669 let generic = if any_generic {
670 None
671 } else {
672 Some(self.default_generic)
673 };
674 self.script_candidates(generic, block)
675 .iter()
676 .for_each(|f| push_unique(&mut out, f));
677 }
678 self.last_resort
679 .iter()
680 .for_each(|f| push_unique(&mut out, f));
681 out
682 }
683
684 pub fn merge_defaults(&mut self, defaults: &FcFallbackConfig) {
687 for (generic, families) in &defaults.generic_families {
688 self.generic_families
689 .entry(*generic)
690 .or_insert_with(|| families.clone());
691 }
692 for (family, replacements) in &defaults.substitutions {
693 self.substitutions
694 .entry(family.clone())
695 .or_insert_with(|| replacements.clone());
696 }
697 for entry in &defaults.script_fallbacks {
698 let already = self
699 .script_fallbacks
700 .iter()
701 .any(|e| e.generic == entry.generic && e.range.overlaps(&entry.range));
702 if !already {
703 self.script_fallbacks.push(entry.clone());
704 }
705 }
706 if self.last_resort.is_empty() {
707 self.last_resort = defaults.last_resort.clone();
708 }
709 }
710
711 pub fn extract_all_families(&self) -> Vec<String> {
717 let mut out = Vec::new();
718 for families in self.generic_families.values() {
719 families.iter().for_each(|f| push_unique(&mut out, f));
720 }
721 for replacements in self.substitutions.values() {
722 replacements.iter().for_each(|f| push_unique(&mut out, f));
723 }
724 for entry in &self.script_fallbacks {
725 entry.families.iter().for_each(|f| push_unique(&mut out, f));
726 }
727 self.last_resort
728 .iter()
729 .for_each(|f| push_unique(&mut out, f));
730 out
731 }
732
733 pub fn absorb_system_aliases(&mut self, aliases: BTreeMap<String, Vec<String>>) {
734 for (key, prefs) in aliases {
735 match GenericFamily::from_css(&key) {
736 Some(generic) => {
737 self.generic_families.insert(generic, prefs);
738 }
739 None => {
740 self.substitutions.insert(key, prefs);
741 }
742 }
743 }
744 }
745}
746
747pub fn font_directories(os: OperatingSystem) -> Vec<PathBuf> {
750 let mut dirs = Vec::new();
751 match os {
752 OperatingSystem::MacOS => {
753 dirs.push(PathBuf::from("/System/Library/Fonts"));
754 dirs.push(PathBuf::from("/Library/Fonts"));
755 dirs.push(PathBuf::from("/System/Library/AssetsV2"));
756 if let Ok(home) = std::env::var("HOME") {
757 dirs.push(PathBuf::from(format!("{}/Library/Fonts", home)));
758 }
759 }
760 OperatingSystem::Linux => {
761 dirs.push(PathBuf::from("/usr/share/fonts"));
762 dirs.push(PathBuf::from("/usr/local/share/fonts"));
763 if let Ok(home) = std::env::var("HOME") {
764 dirs.push(PathBuf::from(format!("{}/.fonts", home)));
765 dirs.push(PathBuf::from(format!("{}/.local/share/fonts", home)));
766 }
767 }
768 OperatingSystem::Windows => {
769 let system_root = std::env::var("SystemRoot")
770 .or_else(|_| std::env::var("WINDIR"))
771 .unwrap_or_else(|_| "C:\\Windows".to_string());
772 let user_profile =
773 std::env::var("USERPROFILE").unwrap_or_else(|_| "C:\\Users\\Default".to_string());
774 dirs.push(PathBuf::from(format!("{}\\Fonts", system_root)));
775 dirs.push(PathBuf::from(format!(
776 "{}\\AppData\\Local\\Microsoft\\Windows\\Fonts",
777 user_profile
778 )));
779 }
780 OperatingSystem::Android => {
781 dirs.push(PathBuf::from("/system/fonts"));
782 dirs.push(PathBuf::from("/product/fonts"));
783 dirs.push(PathBuf::from("/system_ext/fonts"));
784 dirs.push(PathBuf::from("/data/fonts"));
785 }
786 OperatingSystem::IOS | OperatingSystem::Wasm => {}
787 }
788
789 dirs
790}
791
792pub fn common_font_families(os: OperatingSystem) -> &'static [&'static str] {
795 match os {
796 OperatingSystem::MacOS => &[
797 "San Francisco",
799 "SFNS",
800 "System Font",
801 "Helvetica Neue",
803 "Helvetica",
804 "Arial",
805 "Lucida Grande",
806 "Times New Roman",
808 "Georgia",
809 "Menlo",
811 "SF Mono",
812 "Courier",
813 ],
814 OperatingSystem::Linux => &[
815 "DejaVu Sans",
817 "Ubuntu",
818 "Roboto",
819 "Noto Sans",
820 "Liberation Sans",
821 "Droid Sans",
822 "Arial",
823 "DejaVu Serif",
825 "Noto Serif",
826 "DejaVu Sans Mono",
828 ],
829 OperatingSystem::Windows => &[
830 "Segoe UI",
832 "Arial",
833 "Tahoma",
834 "Verdana",
835 "Times New Roman",
837 "Calibri",
838 "Consolas",
840 "Courier New",
841 ],
842 OperatingSystem::IOS => &[
843 "San Francisco",
845 "SFNS",
846 "SFNSDisplay",
847 "SFNSText",
848 "SFUI",
849 ".AppleSystemUIFont",
850 "System Font",
851 "Helvetica Neue",
853 "Helvetica",
854 "Avenir",
855 "Avenir Next",
856 "Times New Roman",
858 "Georgia",
859 "Menlo",
861 "SF Mono",
862 "Courier",
863 ],
864 OperatingSystem::Android => &[
865 "Roboto",
867 "Roboto Flex",
868 "Roboto Condensed",
869 "Noto Sans",
871 "Droid Sans",
872 "Noto Serif",
874 "Roboto Serif",
875 "Droid Serif",
876 "Roboto Mono",
878 "Droid Sans Mono",
879 "Noto Sans Mono",
880 ],
881 OperatingSystem::Wasm => &[],
882 }
883}
884
885#[derive(Debug, Clone, PartialEq)]
888pub struct FcScanConfig {
889 pub font_dirs: Vec<PathBuf>,
891 pub priority_families: Vec<String>,
894}
895
896impl FcScanConfig {
897 pub fn os_defaults(os: OperatingSystem) -> Self {
899 Self {
900 font_dirs: font_directories(os),
901 priority_families: FcFallbackConfig::os_defaults(os).extract_all_families(),
902 }
903 }
904 pub fn empty() -> Self {
906 Self {
907 font_dirs: Vec::new(),
908 priority_families: Vec::new(),
909 }
910 }
911 pub fn priority_token_sets(&self) -> Vec<Vec<String>> {
913 self.priority_families
914 .iter()
915 .map(|family| tokenize_lowercase(family))
916 .collect()
917 }
918}
919
920pub fn tokenize_common_families(os: OperatingSystem) -> Vec<Vec<String>> {
924 FcScanConfig::os_defaults(os).priority_token_sets()
925}
926
927pub fn matches_common_family_tokens(
934 file_tokens: &[String],
935 common_token_sets: &[Vec<String>],
936) -> bool {
937 let file_joined: String = file_tokens.concat();
938 common_token_sets.iter().any(|family_tokens| {
939 let family_joined: String = family_tokens.concat();
940 file_joined.contains(&family_joined)
941 })
942}
943
944pub fn tokenize_lowercase(name: &str) -> Vec<String> {
948 FcFontCache::extract_font_name_tokens(name)
949 .into_iter()
950 .map(|t| t.to_lowercase())
951 .collect()
952}
953
954pub fn tokenize_font_stem(stem: &str) -> Vec<String> {
966 tokenize_lowercase(stem)
967 .into_iter()
968 .filter(|t| !FONT_STYLE_TOKENS.iter().any(|s| s.eq_ignore_ascii_case(t)))
969 .collect()
970}
971
972pub fn guess_family_from_filename(path: &Path) -> String {
983 let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
984 tokenize_font_stem(stem).join("")
985}
986
987#[cfg(test)]
988#[path = "config_test.rs"]
989mod tests;