1#[cfg(feature = "emacs")]
2use super::emacs::{EMACS_CHARSET_MAP, SCRIPT_REPRESENTATIVE_CHARS};
3use super::index::*;
4use super::index_data::*;
5use super::library::FontLibrary;
6use super::system::{Os, OS};
7use super::types::*;
8use crate::util::string::SmallString;
9use std::{
10 fs,
11 path::{Path, PathBuf},
12 sync::RwLock,
13 time::SystemTime,
14};
15#[cfg(feature = "emacs")]
16use swash::text::Language;
17use swash::Tag;
18use swash::{Attributes, CacheKey, FontDataRef, FontRef, Stretch, StringId, Style, Weight};
19
20#[derive(Copy, Clone, PartialEq, Eq, Debug)]
22pub enum MmapHint {
23 Never,
25 Always,
27 Threshold(usize),
30}
31
32impl Default for MmapHint {
33 fn default() -> Self {
34 Self::Never
35 }
36}
37
38#[derive(Default)]
40pub struct FontLibraryBuilder {
41 inner: Inner,
42 scanner: Scanner,
43 all_names: bool,
44 generics: bool,
45 fallbacks: bool,
46}
47
48impl FontLibraryBuilder {
49 pub fn all_names(&mut self, yes: bool) -> &mut Self {
52 self.all_names = yes;
53 self
54 }
55
56 pub fn mmap(&mut self, hint: MmapHint) -> &mut Self {
58 self.inner.mmap_hint = hint;
59 self
60 }
61
62 pub fn add_dir(&mut self, path: impl AsRef<Path>) -> &mut Self {
64 self.scanner.scan_dir(path, self.all_names, &mut self.inner);
65 self
66 }
67
68 pub fn add_file(&mut self, path: impl AsRef<Path>) -> &mut Self {
70 self.scanner
71 .scan_file(path, self.all_names, &mut self.inner);
72 self
73 }
74
75 pub fn add_system_fonts(&mut self) -> &mut Self {
77 match OS {
78 Os::Windows => {
79 if let Some(mut windir) = std::env::var_os("SYSTEMROOT") {
80 windir.push("\\Fonts\\");
81 self.add_dir(windir);
82 } else {
83 self.add_dir("C:\\Windows\\Fonts\\");
84 }
85 }
86 Os::MacOs => {
87 self.add_dir("/System/Library/Fonts/");
88 self.add_dir("/Library/Fonts/");
89 }
90 Os::Ios => {
91 self.add_dir("/System/Library/Fonts/");
92 self.add_dir("/Library/Fonts/");
93 }
94 Os::Android => {
95 self.add_dir("/system/fonts/");
96 }
97 Os::Unix => {
98 self.add_dir("/usr/share/fonts/");
99 self.add_dir("/usr/local/share/fonts/");
100 }
101 Os::Other => {}
102 }
103 self
104 }
105
106 pub fn add_user_fonts(&mut self) -> &mut Self {
108 match OS {
109 Os::Windows => {}
110 Os::MacOs => {
111 if let Some(mut homedir) = std::env::var_os("HOME") {
112 homedir.push("/Library/Fonts/");
113 self.add_dir(&homedir);
114 }
115 }
116 Os::Ios => {}
117 Os::Android => {}
118 Os::Unix => {
119 if let Some(mut homedir) = std::env::var_os("HOME") {
120 homedir.push("/.local/share/fonts/");
121 self.add_dir(&homedir);
122 }
123 }
124 Os::Other => {}
125 }
126 self
127 }
128
129 pub fn map_generic_families(&mut self, yes: bool) -> &mut Self {
132 self.generics = yes;
133 self
134 }
135
136 pub fn map_fallbacks(&mut self, yes: bool) -> &mut Self {
139 self.fallbacks = yes;
140 self
141 }
142
143 pub fn build(&mut self) -> FontLibrary {
145 #[cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))]
146 let fontconfig = load_fontconfig();
147 #[cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))]
148 {
149 let home = std::env::var("HOME");
150 fontconfig.dirs.iter().for_each(|dir| {
151 if dir.path.starts_with("~") {
152 if let Ok(ref home) = home {
153 let path = Path::new(home).join(dir.path.strip_prefix("~").unwrap());
154 self.add_dir(path);
155 }
156 } else {
157 self.add_dir(dir.clone().path);
158 };
159 });
160 }
161 let mut index = StaticIndex::default();
162 core::mem::swap(&mut index, &mut self.inner.index);
163 for family in index.families.iter_mut() {
164 family
165 .fonts
166 .sort_unstable_by(|a, b| a.weight.cmp(&b.weight));
167 }
168 if self.generics {
169 index.setup_default_generic();
170 }
171 if self.fallbacks {
172 index.setup_default_fallbacks();
173 }
174
175 #[cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))]
176 {
177 use fontconfig_parser::Alias;
178
179 if self.generics {
180 fontconfig.aliases.iter().for_each(
181 |Alias {
182 alias,
183 default,
184 prefer,
185 accept,
186 }| {
187 let names = [&prefer[..], &accept[..], &default[..]].concat();
188 let find_family = |families: Vec<String>| {
189 for family in families {
190 if let Some(id) = index
191 .base
192 .family_map
193 .get(family.trim().to_lowercase().as_str())
194 {
195 return Some(*id);
196 }
197 }
198 None
199 };
200 if let Some(generic_family) = GenericFamily::parse(alias) {
201 if let Some(id) = find_family(names.clone()) {
202 index.generic[generic_family as usize] = Some(id);
203 };
204 }
205 },
206 )
207 }
208 }
209
210 FontLibrary::new(index)
211 }
212}
213
214struct Inner {
215 path: PathBuf,
216 mmap: bool,
217 timestamp: SystemTime,
218 source: SourceId,
219 file_added: bool,
220 mmap_hint: MmapHint,
221 index: StaticIndex,
222 lowercase_name: String,
223}
224
225impl Default for Inner {
226 fn default() -> Self {
227 Self::new()
228 }
229}
230
231impl Inner {
232 fn new() -> Self {
233 Self {
234 path: PathBuf::new(),
235 mmap: false,
236 timestamp: SystemTime::UNIX_EPOCH,
237 source: SourceId(0),
238 file_added: false,
239 mmap_hint: MmapHint::default(),
240 index: StaticIndex::default(),
241 lowercase_name: String::default(),
242 }
243 }
244}
245
246impl ScannerSink for Inner {
247 fn enter_file(&mut self, path: PathBuf, timestamp: SystemTime, size: u64) {
248 let mmap = match self.mmap_hint {
249 MmapHint::Never => false,
250 MmapHint::Always => true,
251 MmapHint::Threshold(value) => (value as u64) < size,
252 };
253 self.path = path;
254 self.mmap = mmap;
255 self.timestamp = timestamp;
256 self.source = SourceId(self.index.base.sources.len() as u32);
257 self.file_added = false;
258 }
259
260 fn add_font(&mut self, font: &FontInfo) {
261 self.lowercase_name.clear();
262 self.lowercase_name
263 .extend(font.name.chars().map(|c| c.to_lowercase()).flatten());
264 let index = &mut self.index;
265 let family =
266 if let Some(family_id) = index.base.family_map.get(self.lowercase_name.as_str()) {
267 let family = &mut index.families[family_id.to_usize()];
268 if family.contains(font.stretch, font.weight, font.style) {
269 return;
270 }
271 family
272 } else {
273 let family_id = FamilyId(index.families.len() as u32);
274 let family = FamilyData {
275 id: family_id,
276 name: SmallString::new(&font.name),
277 fonts: Vec::new(),
278 has_stretch: true,
279 };
280 index.families.push(family);
281 index
282 .base
283 .family_map
284 .insert(SmallString::new(&self.lowercase_name), family_id);
285 &mut index.families[family_id.to_usize()]
286 };
287 if !self.file_added {
288 self.file_added = true;
289 let mut path2 = PathBuf::new();
290 core::mem::swap(&mut path2, &mut self.path);
291 index.base.sources.push(SourceData {
292 id: self.source,
293 kind: SourceKind::File(FileData {
294 path: path2.into(),
295 mmap: self.mmap,
296 timestamp: self.timestamp,
297 status: RwLock::new(FileDataStatus::Empty),
298 }),
299 });
300 }
301 let font_id = FontId(index.base.fonts.len() as u32);
302 let family_id = family.id;
303 let font_data = FontData {
304 id: font_id,
305 family: family_id,
306 source: self.source,
307 index: font.index,
308 offset: font.offset,
309 attributes: font.attrs,
310 key: CacheKey::new(),
311 };
312 index.base.fonts.push(font_data);
313 family.fonts.push(FamilyFontData {
314 id: font_id,
315 stretch: font.stretch,
316 weight: font.weight,
317 style: font.style,
318 writing_systems: font.writing_systems.clone(),
319 });
320 if font.stretch != Stretch::NORMAL {
321 family.has_stretch = true;
322 }
323 for name in font.all_names() {
324 if !index.base.family_map.contains_key(name.as_str()) {
325 index
326 .base
327 .family_map
328 .insert(SmallString::new(name.as_str()), family_id);
329 }
330 }
331
332 font.writing_systems
333 .iter()
334 .for_each(|(script_tag, language_tag, _)| {
335 index
336 .script_tag_map
337 .entry(*script_tag)
338 .and_modify(|families| families.push(family_id))
339 .or_insert(vec![family_id]);
340
341 index
342 .language_tag_map
343 .entry(*language_tag)
344 .and_modify(|families| families.push(family_id))
345 .or_insert(vec![family_id]);
346 });
347
348 #[cfg(feature = "emacs")]
349 for supported_charset in &font.supported_charsets {
350 index
351 .emacs_charset_map
352 .entry(SmallString::new(supported_charset.as_str()))
353 .and_modify(|familes| {
354 if !familes.contains(&family_id) {
355 familes.push(family_id);
356 }
357 })
358 .or_insert(vec![family_id]);
359 }
360
361 #[cfg(feature = "emacs")]
362 for supported_script in &font.supported_scripts {
363 index
364 .emacs_script_map
365 .entry(SmallString::new(supported_script.as_str()))
366 .and_modify(|families| {
367 if !families.contains(&family_id) {
368 families.push(family_id);
369 }
370 })
371 .or_insert(vec![family_id]);
372 }
373 }
374}
375
376#[derive(Default)]
377pub struct FontInfo {
378 pub offset: u32,
379 pub index: u32,
380 pub name: String,
381 pub attrs: Attributes,
382 pub stretch: Stretch,
383 pub weight: Weight,
384 pub style: Style,
385 all_names: Vec<String>,
386 name_count: usize,
387 pub writing_systems: Vec<(Tag, Tag, Vec<Tag>)>,
388 #[cfg(feature = "emacs")]
389 pub supported_charsets: Vec<SmallString>,
390 #[cfg(feature = "emacs")]
391 pub supported_scripts: Vec<SmallString>,
392}
393
394impl FontInfo {
395 pub fn all_names(&self) -> &[String] {
396 &self.all_names[..self.name_count]
397 }
398}
399
400pub trait ScannerSink {
401 fn enter_file(&mut self, path: PathBuf, timestamp: SystemTime, size: u64);
402 fn add_font(&mut self, font: &FontInfo);
403}
404
405#[derive(Default)]
406pub struct Scanner {
407 font: FontInfo,
408 name: String,
409}
410
411impl Scanner {
412 pub fn scan_dir(
413 &mut self,
414 path: impl AsRef<Path>,
415 all_names: bool,
416 sink: &mut impl ScannerSink,
417 ) -> Option<()> {
418 self.scan_dir_impl(path, all_names, sink, 0)
419 }
420
421 pub fn scan_file(
422 &mut self,
423 path: impl AsRef<Path>,
424 all_names: bool,
425 sink: &mut impl ScannerSink,
426 ) -> Option<()> {
427 let file = fs::File::open(path.as_ref()).ok()?;
428 let metadata = file.metadata().ok()?;
429 let timestamp = metadata.modified().ok()?;
430 let size = metadata.len();
431 let data = unsafe { memmap2::Mmap::map(&file).ok()? };
432 sink.enter_file(path.as_ref().into(), timestamp, size);
433 self.scan_data(&*data, all_names, |f| sink.add_font(f))
434 }
435
436 pub fn scan_data(
437 &mut self,
438 data: &[u8],
439 all_names: bool,
440 mut f: impl FnMut(&FontInfo),
441 ) -> Option<()> {
442 self.font.name.clear();
443 let font_data = FontDataRef::new(data)?;
444 for i in 0..font_data.len() {
445 if let Some(font) = font_data.get(i) {
446 self.scan_font(font, i as u32, all_names, &mut f);
447 }
448 }
449 Some(())
450 }
451
452 fn scan_dir_impl(
453 &mut self,
454 path: impl AsRef<Path>,
455 all_names: bool,
456 sink: &mut impl ScannerSink,
457 recurse: u32,
458 ) -> Option<()> {
459 if recurse > 4 {
460 return Some(());
461 }
462 let mut lower_ext = [0u8; 3];
463 for entry in fs::read_dir(path).ok()? {
464 if let Ok(entry) = entry {
465 let path = entry.path();
466 if path.is_file() {
467 let mut is_dfont = false;
468 match path.extension().map(|e| e.to_str()).flatten() {
469 Some("dfont") => is_dfont = true,
470 Some(ext) => {
471 let ext = ext.as_bytes();
472 if ext.len() != 3 {
473 continue;
474 }
475 for i in 0..3 {
476 lower_ext[i] = ext[i].to_ascii_lowercase();
477 }
478 }
479 None => continue,
480 };
481 if !is_dfont {
482 match &lower_ext {
483 b"ttf" | b"otf" | b"ttc" | b"otc" => {}
484 _ => continue,
485 }
486 }
487 self.scan_file(&path, all_names, sink);
488 } else if path.is_dir() {
489 self.scan_dir_impl(&path, all_names, sink, recurse + 1);
490 }
491 }
492 }
493 Some(())
494 }
495
496 fn scan_font(
497 &mut self,
498 font: FontRef,
499 index: u32,
500 all_names: bool,
501 f: &mut impl FnMut(&FontInfo),
502 ) -> Option<()> {
503 self.font.name_count = 0;
504 let strings = font.localized_strings();
505 let vars = font.variations();
506 let var_count = vars.len();
507 self.font.name.clear();
508 let mut nid = if var_count != 0 {
511 StringId::TypographicFamily
512 } else {
513 StringId::Family
514 };
515 if let Some(name) = strings.find_by_id(nid, Some("en")) {
516 self.font.name.extend(name.chars());
517 } else if let Some(name) = strings.find_by_id(nid, None) {
518 self.font.name.extend(name.chars());
519 }
520 if self.font.name.is_empty() {
521 nid = if nid == StringId::Family {
522 StringId::TypographicFamily
523 } else {
524 StringId::Family
525 };
526 if let Some(name) = strings.find_by_id(nid, Some("en")) {
527 self.name.extend(name.chars());
528 } else if let Some(name) = strings.find_by_id(nid, None) {
529 self.name.extend(name.chars());
530 }
531 }
532 if !self.name.is_empty() && self.name.len() < self.font.name.len() {
533 core::mem::swap(&mut self.name, &mut self.font.name);
534 }
535 if self.font.name.is_empty() {
536 if let Some(name) = strings.find_by_id(nid, Some("en")) {
537 self.font.name.extend(name.chars());
538 } else if let Some(name) = strings.find_by_id(nid, None) {
539 self.font.name.extend(name.chars());
540 }
541 }
542 if self.font.name.is_empty() {
543 return None;
544 }
545 self.font.attrs = font.attributes();
546 let (stretch, weight, style) = self.font.attrs.parts();
547 self.font.stretch = stretch;
548 self.font.weight = weight;
549 self.font.style = style;
550 self.font.index = index;
551 self.font.offset = font.offset;
552 let mut count = 0;
553 if all_names {
554 for name in strings
555 .clone()
556 .filter(|name| name.id() == nid && name.is_unicode())
557 {
558 if count >= self.font.all_names.len() {
559 self.font.all_names.push(String::default());
560 }
561 let name_buf = &mut self.font.all_names[count];
562 count += 1;
563 name_buf.clear();
564 for ch in name.chars() {
565 name_buf.extend(ch.to_lowercase());
566 }
567 }
568 }
569 self.font.name_count = count;
570 self.font.writing_systems = font
571 .writing_systems()
572 .map(|w| {
573 (
574 w.script_tag(),
575 w.language_tag(),
576 w.features().map(|feature| feature.tag()).collect(),
577 )
578 })
579 .collect();
580
581 #[cfg(feature = "emacs")]
582 {
583 let supported_charsets: Vec<SmallString> = EMACS_CHARSET_MAP
584 .iter()
585 .filter_map(|(charset, (chars, language))| {
586 if let Some(language) = language
587 .as_ref()
588 .map(|language| Language::parse(language.as_str()))
589 .flatten()
590 .map(|lang| lang.to_opentype())
591 .flatten()
592 {
593 if self
594 .font
595 .writing_systems
596 .iter()
597 .find(|(_, language_tag, _)| *language_tag == language)
598 .is_none()
599 {
600 return None;
601 }
602 }
603 if chars
604 .iter()
605 .all(|codepoint| font.charmap().map(*codepoint) != 0)
606 {
607 return Some(SmallString::new(charset.as_str()));
608 }
609
610 None
611 })
612 .collect();
613
614 self.font.supported_charsets = supported_charsets;
615
616 let supported_scripts: Vec<SmallString> = SCRIPT_REPRESENTATIVE_CHARS
617 .iter()
618 .filter_map(|(script, chars)| {
619 if chars
620 .iter()
621 .all(|codepoint| font.charmap().map(*codepoint) != 0)
622 {
623 return Some(SmallString::new(script.as_str()));
624 }
625
626 None
627 })
628 .collect();
629
630 self.font.supported_scripts = supported_scripts;
631 }
632
633 f(&self.font);
634 Some(())
635 }
636}
637
638#[cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))]
639fn load_fontconfig() -> fontconfig_parser::FontConfig {
640 let mut fontconfig = fontconfig_parser::FontConfig::default();
641 let home = std::env::var("HOME");
642
643 if let Ok(ref config_file) = std::env::var("FONTCONFIG_FILE") {
644 let _ = fontconfig.merge_config(Path::new(config_file));
645 } else {
646 let xdg_config_home = if let Ok(val) = std::env::var("XDG_CONFIG_HOME") {
647 Some(val.into())
648 } else if let Ok(ref home) = home {
649 Some(Path::new(home).join(".config"))
652 } else {
653 None
654 };
655
656 let read_global = match xdg_config_home {
657 Some(p) => fontconfig
658 .merge_config(&p.join("fontconfig/fonts.conf"))
659 .is_err(),
660 None => true,
661 };
662
663 if read_global {
664 let _ = fontconfig.merge_config(Path::new("/etc/fonts/local.conf"));
665 }
666 let _ = fontconfig.merge_config(Path::new("/etc/fonts/fonts.conf"));
667 }
668 fontconfig
669}