fontdb/
lib.rs

1/*!
2`fontdb` is a simple, in-memory font database with CSS-like queries.
3
4# Features
5
6- The database can load fonts from files, directories and raw data (`Vec<u8>`).
7- The database can match a font using CSS-like queries. See `Database::query`.
8- The database can try to load system fonts.
9  Currently, this is implemented by scanning predefined directories.
10  The library does not interact with the system API.
11- Provides a unique ID for each font face.
12
13# Non-goals
14
15- Advanced font properties querying.<br>
16  The database provides only storage and matching capabilities.
17  For font properties querying you can use [ttf-parser].
18
19- A font fallback mechanism.<br>
20  This library can be used to implement a font fallback mechanism, but it doesn't implement one.
21
22- Application's global database.<br>
23  The database doesn't use `static`, therefore it's up to the caller where it should be stored.
24
25- Font types support other than TrueType.
26
27# Font vs Face
28
29A font is a collection of font faces. Therefore, a font face is a subset of a font.
30A simple font (\*.ttf/\*.otf) usually contains a single font face,
31but a font collection (\*.ttc) can contain multiple font faces.
32
33`fontdb` stores and matches font faces, not fonts.
34Therefore, after loading a font collection with 5 faces (for example), the database will be populated
35with 5 `FaceInfo` objects, all of which will be pointing to the same file or binary data.
36
37# Performance
38
39The database performance is largely limited by the storage itself.
40We are using [ttf-parser], so the parsing should not be a bottleneck.
41
42On my machine with Samsung SSD 860 and Gentoo Linux, it takes ~20ms
43to load 1906 font faces (most of them are from Google Noto collection)
44with a hot disk cache and ~860ms with a cold one.
45
46On Mac Mini M1 it takes just 9ms to load 898 fonts.
47
48# Safety
49
50The library relies on memory-mapped files, which is inherently unsafe.
51But since we do not keep the files open it should be perfectly safe.
52
53If you would like to use a persistent memory mapping of the font files,
54then you can use the unsafe [`Database::make_shared_face_data`] function.
55
56[ttf-parser]: https://github.com/RazrFalcon/ttf-parser
57*/
58
59#![cfg_attr(not(feature = "std"), no_std)]
60#![warn(missing_docs)]
61#![warn(missing_debug_implementations)]
62#![warn(missing_copy_implementations)]
63
64extern crate alloc;
65
66#[cfg(not(feature = "std"))]
67use alloc::{
68    string::{String, ToString},
69    vec::Vec,
70};
71
72pub use ttf_parser::Language;
73pub use ttf_parser::Width as Stretch;
74
75use slotmap::SlotMap;
76use tinyvec::TinyVec;
77
78/// A unique per database face ID.
79///
80/// Since `Database` is not global/unique, we cannot guarantee that a specific ID
81/// is actually from the same db instance. This is up to the caller.
82///
83/// ID overflow will cause a panic, but it's highly unlikely that someone would
84/// load more than 4 billion font faces.
85///
86/// Because the internal representation of ID is private, The `Display` trait
87/// implementation for this type only promise that unequal IDs will be displayed
88/// as different strings, but does not make any guarantees about format or
89/// content of the strings.
90///
91/// [`KeyData`]: https://docs.rs/slotmap/latest/slotmap/struct.KeyData.html
92#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]
93pub struct ID(InnerId);
94
95slotmap::new_key_type! {
96    /// Internal ID type.
97    struct InnerId;
98}
99
100impl ID {
101    /// Creates a dummy ID.
102    ///
103    /// Should be used in tandem with [`Database::push_face_info`].
104    #[inline]
105    pub fn dummy() -> Self {
106        Self(InnerId::from(slotmap::KeyData::from_ffi(core::u64::MAX)))
107    }
108}
109
110impl core::fmt::Display for ID {
111    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112        write!(f, "{}", (self.0).0.as_ffi())
113    }
114}
115
116/// A list of possible font loading errors.
117#[derive(Debug)]
118enum LoadError {
119    /// A malformed font.
120    ///
121    /// Typically means that [ttf-parser](https://github.com/RazrFalcon/ttf-parser)
122    /// wasn't able to parse it.
123    MalformedFont,
124    /// A valid TrueType font without a valid *Family Name*.
125    UnnamedFont,
126    /// A file IO related error.
127    #[cfg(feature = "std")]
128    IoError(std::io::Error),
129}
130
131#[cfg(feature = "std")]
132impl From<std::io::Error> for LoadError {
133    #[inline]
134    fn from(e: std::io::Error) -> Self {
135        LoadError::IoError(e)
136    }
137}
138
139impl core::fmt::Display for LoadError {
140    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141        match self {
142            LoadError::MalformedFont => write!(f, "malformed font"),
143            LoadError::UnnamedFont => write!(f, "font doesn't have a family name"),
144            #[cfg(feature = "std")]
145            LoadError::IoError(ref e) => write!(f, "{}", e),
146        }
147    }
148}
149
150/// A font database.
151#[derive(Clone, Debug)]
152pub struct Database {
153    faces: SlotMap<InnerId, FaceInfo>,
154    family_serif: String,
155    family_sans_serif: String,
156    family_cursive: String,
157    family_fantasy: String,
158    family_monospace: String,
159}
160
161impl Default for Database {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167impl Database {
168    /// Create a new, empty `Database`.
169    ///
170    /// Generic font families would be set to:
171    ///
172    /// - `serif` - Times New Roman
173    /// - `sans-serif` - Arial
174    /// - `cursive` - Comic Sans MS
175    /// - `fantasy` - Impact (Papyrus on macOS)
176    /// - `monospace` - Courier New
177    #[inline]
178    pub fn new() -> Self {
179        Database {
180            faces: SlotMap::with_key(),
181            family_serif: "Times New Roman".to_string(),
182            family_sans_serif: "Arial".to_string(),
183            family_cursive: "Comic Sans MS".to_string(),
184            #[cfg(not(target_os = "macos"))]
185            family_fantasy: "Impact".to_string(),
186            #[cfg(target_os = "macos")]
187            family_fantasy: "Papyrus".to_string(),
188            family_monospace: "Courier New".to_string(),
189        }
190    }
191
192    /// Loads a font data into the `Database`.
193    ///
194    /// Will load all font faces in case of a font collection.
195    pub fn load_font_data(&mut self, data: Vec<u8>) {
196        self.load_font_source(Source::Binary(alloc::sync::Arc::new(data)));
197    }
198
199    /// Loads a font from the given source into the `Database` and returns
200    /// the ID of the loaded font.
201    ///
202    /// Will load all font faces in case of a font collection.
203    pub fn load_font_source(&mut self, source: Source) -> TinyVec<[ID; 8]> {
204        let ids = source.with_data(|data| {
205            let n = ttf_parser::fonts_in_collection(data).unwrap_or(1);
206            let mut ids = TinyVec::with_capacity(n as usize);
207
208            for index in 0..n {
209                match parse_face_info(source.clone(), data, index) {
210                    Ok(mut info) => {
211                        let id = self.faces.insert_with_key(|k| {
212                            info.id = ID(k);
213                            info
214                        });
215                        ids.push(ID(id));
216                    }
217                    Err(e) => log::warn!(
218                        "Failed to load a font face {} from source cause {}.",
219                        index,
220                        e
221                    ),
222                }
223            }
224
225            ids
226        });
227
228        ids.unwrap_or_default()
229    }
230
231    /// Backend function used by load_font_file to load font files.
232    #[cfg(feature = "fs")]
233    fn load_fonts_from_file(&mut self, path: &std::path::Path, data: &[u8]) {
234        let source = Source::File(path.into());
235
236        let n = ttf_parser::fonts_in_collection(data).unwrap_or(1);
237        for index in 0..n {
238            match parse_face_info(source.clone(), data, index) {
239                Ok(info) => self.push_face_info(info),
240                Err(e) => {
241                    log::warn!(
242                        "Failed to load a font face {} from '{}' cause {}.",
243                        index,
244                        path.display(),
245                        e
246                    )
247                }
248            }
249        }
250    }
251
252    /// Loads a font file into the `Database`.
253    ///
254    /// Will load all font faces in case of a font collection.
255    #[cfg(all(feature = "fs", feature = "memmap"))]
256    pub fn load_font_file<P: AsRef<std::path::Path>>(
257        &mut self,
258        path: P,
259    ) -> Result<(), std::io::Error> {
260        self.load_font_file_impl(path.as_ref())
261    }
262
263    // A non-generic version.
264    #[cfg(all(feature = "fs", feature = "memmap"))]
265    fn load_font_file_impl(&mut self, path: &std::path::Path) -> Result<(), std::io::Error> {
266        let file = std::fs::File::open(path)?;
267        let data: &[u8] = unsafe { &memmap2::MmapOptions::new().map(&file)? };
268
269        self.load_fonts_from_file(path, data);
270        Ok(())
271    }
272
273    /// Loads a font file into the `Database`.
274    ///
275    /// Will load all font faces in case of a font collection.
276    #[cfg(all(feature = "fs", not(feature = "memmap")))]
277    pub fn load_font_file<P: AsRef<std::path::Path>>(
278        &mut self,
279        path: P,
280    ) -> Result<(), std::io::Error> {
281        self.load_font_file_impl(path.as_ref())
282    }
283
284    // A non-generic version.
285    #[cfg(all(feature = "fs", not(feature = "memmap")))]
286    fn load_font_file_impl(&mut self, path: &std::path::Path) -> Result<(), std::io::Error> {
287        let data = std::fs::read(path)?;
288
289        self.load_fonts_from_file(path, &data);
290        Ok(())
291    }
292
293    /// Loads font files from the selected directory into the `Database`.
294    ///
295    /// This method will scan directories recursively.
296    ///
297    /// Will load `ttf`, `otf`, `ttc` and `otc` fonts.
298    ///
299    /// Unlike other `load_*` methods, this one doesn't return an error.
300    /// It will simply skip malformed fonts and will print a warning into the log for each of them.
301    #[cfg(feature = "fs")]
302    pub fn load_fonts_dir<P: AsRef<std::path::Path>>(&mut self, dir: P) {
303        self.load_fonts_dir_impl(dir.as_ref())
304    }
305
306    // A non-generic version.
307    #[rustfmt::skip] // keep extensions match as is
308    #[cfg(feature = "fs")]
309    fn load_fonts_dir_impl(&mut self, dir: &std::path::Path) {
310        let fonts_dir = match std::fs::read_dir(dir) {
311            Ok(dir) => dir,
312            Err(_) => return,
313        };
314
315        for entry in fonts_dir.flatten() {
316            let path = entry.path();
317            if path.is_file() {
318                match path.extension().and_then(|e| e.to_str()) {
319                    Some("ttf") | Some("ttc") | Some("TTF") | Some("TTC") |
320                    Some("otf") | Some("otc") | Some("OTF") | Some("OTC") => {
321                        if let Err(e) = self.load_font_file(&path) {
322                            log::warn!("Failed to load '{}' cause {}.", path.display(), e);
323                        }
324                    }
325                    _ => {}
326                }
327            } else if path.is_dir() {
328                // TODO: ignore symlinks?
329                self.load_fonts_dir(path);
330            }
331        }
332    }
333
334    /// Attempts to load system fonts.
335    ///
336    /// Supports Windows, Linux and macOS.
337    ///
338    /// System fonts loading is a surprisingly complicated task,
339    /// mostly unsolvable without interacting with system libraries.
340    /// And since `fontdb` tries to be small and portable, this method
341    /// will simply scan some predefined directories.
342    /// Which means that fonts that are not in those directories must
343    /// be added manually.
344    #[cfg(feature = "fs")]
345    pub fn load_system_fonts(&mut self) {
346        #[cfg(target_os = "windows")]
347        {
348            if let Some(ref system_root) = std::env::var_os("SYSTEMROOT") {
349                let system_root_path = std::path::Path::new(system_root);
350                self.load_fonts_dir(system_root_path.join("Fonts"));
351            } else {
352                self.load_fonts_dir("C:\\Windows\\Fonts\\");
353            }
354
355            if let Ok(ref home) = std::env::var("USERPROFILE") {
356                let home_path = std::path::Path::new(home);
357                self.load_fonts_dir(home_path.join("AppData\\Local\\Microsoft\\Windows\\Fonts"));
358                self.load_fonts_dir(home_path.join("AppData\\Roaming\\Microsoft\\Windows\\Fonts"));
359            }
360        }
361
362        #[cfg(target_os = "macos")]
363        {
364            self.load_fonts_dir("/Library/Fonts");
365            self.load_fonts_dir("/System/Library/Fonts");
366            // Downloadable fonts, location varies on major macOS releases
367            if let Ok(dir) = std::fs::read_dir("/System/Library/AssetsV2") {
368                for entry in dir {
369                    let entry = match entry {
370                        Ok(entry) => entry,
371                        Err(_) => continue,
372                    };
373                    if entry
374                        .file_name()
375                        .to_string_lossy()
376                        .starts_with("com_apple_MobileAsset_Font")
377                    {
378                        self.load_fonts_dir(entry.path());
379                    }
380                }
381            }
382            self.load_fonts_dir("/Network/Library/Fonts");
383
384            if let Ok(ref home) = std::env::var("HOME") {
385                let home_path = std::path::Path::new(home);
386                self.load_fonts_dir(home_path.join("Library/Fonts"));
387            }
388        }
389
390        // Redox OS.
391        #[cfg(target_os = "redox")]
392        {
393            self.load_fonts_dir("/ui/fonts");
394        }
395
396        // Linux.
397        #[cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))]
398        {
399            #[cfg(feature = "fontconfig")]
400            {
401                self.load_fontconfig();
402            }
403
404            #[cfg(not(feature = "fontconfig"))]
405            {
406                self.load_fonts_dir("/usr/share/fonts/");
407                self.load_fonts_dir("/usr/local/share/fonts/");
408
409                if let Ok(ref home) = std::env::var("HOME") {
410                    let home_path = std::path::Path::new(home);
411                    self.load_fonts_dir(home_path.join(".fonts"));
412                    self.load_fonts_dir(home_path.join(".local/share/fonts"));
413                }
414            }
415        }
416    }
417
418    // Linux.
419    #[cfg(all(
420        unix,
421        feature = "fontconfig",
422        not(any(target_os = "macos", target_os = "android"))
423    ))]
424    fn load_fontconfig(&mut self) {
425        use std::path::Path;
426
427        let mut fontconfig = fontconfig_parser::FontConfig::default();
428        let home = std::env::var("HOME");
429
430        if let Ok(ref config_file) = std::env::var("FONTCONFIG_FILE") {
431            let _ = fontconfig.merge_config(Path::new(config_file));
432        } else {
433            let xdg_config_home = if let Ok(val) = std::env::var("XDG_CONFIG_HOME") {
434                Some(val.into())
435            } else if let Ok(ref home) = home {
436                // according to https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
437                // $XDG_CONFIG_HOME should default to $HOME/.config if not set
438                Some(Path::new(home).join(".config"))
439            } else {
440                None
441            };
442
443            let read_global = match xdg_config_home {
444                Some(p) => fontconfig
445                    .merge_config(&p.join("fontconfig/fonts.conf"))
446                    .is_err(),
447                None => true,
448            };
449
450            if read_global {
451                let _ = fontconfig.merge_config(Path::new("/etc/fonts/local.conf"));
452            }
453            let _ = fontconfig.merge_config(Path::new("/etc/fonts/fonts.conf"));
454        }
455
456        for fontconfig_parser::Alias {
457            alias,
458            default,
459            prefer,
460            accept,
461        } in fontconfig.aliases
462        {
463            let name = prefer
464                .get(0)
465                .or_else(|| accept.get(0))
466                .or_else(|| default.get(0));
467
468            if let Some(name) = name {
469                match alias.to_lowercase().as_str() {
470                    "serif" => self.set_serif_family(name),
471                    "sans-serif" => self.set_sans_serif_family(name),
472                    "sans serif" => self.set_sans_serif_family(name),
473                    "monospace" => self.set_monospace_family(name),
474                    "cursive" => self.set_cursive_family(name),
475                    "fantasy" => self.set_fantasy_family(name),
476                    _ => {}
477                }
478            }
479        }
480
481        for dir in fontconfig.dirs {
482            let path = if dir.path.starts_with("~") {
483                if let Ok(ref home) = home {
484                    Path::new(home).join(dir.path.strip_prefix("~").unwrap())
485                } else {
486                    continue;
487                }
488            } else {
489                dir.path
490            };
491            self.load_fonts_dir(path);
492        }
493    }
494
495    /// Pushes a user-provided `FaceInfo` to the database.
496    ///
497    /// In some cases, a caller might want to ignore the font's metadata and provide their own.
498    /// This method doesn't parse the `source` font.
499    ///
500    /// The `id` field should be set to [`ID::dummy()`] and will be then overwritten by this method.
501    pub fn push_face_info(&mut self, mut info: FaceInfo) {
502        self.faces.insert_with_key(|k| {
503            info.id = ID(k);
504            info
505        });
506    }
507
508    /// Removes a font face by `id` from the database.
509    ///
510    /// Returns `false` while attempting to remove a non-existing font face.
511    ///
512    /// Useful when you want to ignore some specific font face(s)
513    /// after loading a large directory with fonts.
514    /// Or a specific face from a font.
515    pub fn remove_face(&mut self, id: ID) {
516        self.faces.remove(id.0);
517    }
518
519    /// Returns `true` if the `Database` contains no font faces.
520    #[inline]
521    pub fn is_empty(&self) -> bool {
522        self.faces.is_empty()
523    }
524
525    /// Returns the number of font faces in the `Database`.
526    ///
527    /// Note that `Database` stores font faces, not fonts.
528    /// For example, if a caller will try to load a font collection (`*.ttc`) that contains 5 faces,
529    /// then the `Database` will load 5 font faces and this method will return 5, not 1.
530    #[inline]
531    pub fn len(&self) -> usize {
532        self.faces.len()
533    }
534
535    /// Sets the family that will be used by `Family::Serif`.
536    pub fn set_serif_family<S: Into<String>>(&mut self, family: S) {
537        self.family_serif = family.into();
538    }
539
540    /// Sets the family that will be used by `Family::SansSerif`.
541    pub fn set_sans_serif_family<S: Into<String>>(&mut self, family: S) {
542        self.family_sans_serif = family.into();
543    }
544
545    /// Sets the family that will be used by `Family::Cursive`.
546    pub fn set_cursive_family<S: Into<String>>(&mut self, family: S) {
547        self.family_cursive = family.into();
548    }
549
550    /// Sets the family that will be used by `Family::Fantasy`.
551    pub fn set_fantasy_family<S: Into<String>>(&mut self, family: S) {
552        self.family_fantasy = family.into();
553    }
554
555    /// Sets the family that will be used by `Family::Monospace`.
556    pub fn set_monospace_family<S: Into<String>>(&mut self, family: S) {
557        self.family_monospace = family.into();
558    }
559
560    /// Returns the generic family name or the `Family::Name` itself.
561    ///
562    /// Generic family names should be set via `Database::set_*_family` methods.
563    pub fn family_name<'a>(&'a self, family: &'a Family) -> &'a str {
564        match family {
565            Family::Name(name) => name,
566            Family::Serif => self.family_serif.as_str(),
567            Family::SansSerif => self.family_sans_serif.as_str(),
568            Family::Cursive => self.family_cursive.as_str(),
569            Family::Fantasy => self.family_fantasy.as_str(),
570            Family::Monospace => self.family_monospace.as_str(),
571        }
572    }
573
574    /// Performs a CSS-like query and returns the best matched font face.
575    pub fn query(&self, query: &Query) -> Option<ID> {
576        for family in query.families {
577            let name = self.family_name(family);
578            let candidates: Vec<_> = self
579                .faces
580                .iter()
581                .filter(|(_, face)| face.families.iter().any(|family| family.0 == name))
582                .map(|(_, info)| info)
583                .collect();
584
585            if !candidates.is_empty() {
586                if let Some(index) = find_best_match(&candidates, query) {
587                    return Some(candidates[index].id);
588                }
589            }
590        }
591
592        None
593    }
594
595    /// Returns an iterator over the internal storage.
596    ///
597    /// This can be used for manual font matching.
598    #[inline]
599    pub fn faces(&self) -> impl Iterator<Item = &FaceInfo> + '_ {
600        self.faces.iter().map(|(_, info)| info)
601    }
602
603    /// Selects a `FaceInfo` by `id`.
604    ///
605    /// Returns `None` if a face with such ID was already removed,
606    /// or this ID belong to the other `Database`.
607    pub fn face(&self, id: ID) -> Option<&FaceInfo> {
608        self.faces.get(id.0)
609    }
610
611    /// Returns font face storage and the face index by `ID`.
612    pub fn face_source(&self, id: ID) -> Option<(Source, u32)> {
613        self.face(id).map(|info| (info.source.clone(), info.index))
614    }
615
616    /// Executes a closure with a font's data.
617    ///
618    /// We can't return a reference to a font binary data because of lifetimes.
619    /// So instead, you can use this method to process font's data.
620    ///
621    /// The closure accepts raw font data and font face index.
622    ///
623    /// In case of `Source::File`, the font file will be memory mapped.
624    ///
625    /// Returns `None` when font file loading failed.
626    ///
627    /// # Example
628    ///
629    /// ```ignore
630    /// let is_variable = db.with_face_data(id, |font_data, face_index| {
631    ///     let font = ttf_parser::Face::from_slice(font_data, face_index).unwrap();
632    ///     font.is_variable()
633    /// })?;
634    /// ```
635    pub fn with_face_data<P, T>(&self, id: ID, p: P) -> Option<T>
636    where
637        P: FnOnce(&[u8], u32) -> T,
638    {
639        let (src, face_index) = self.face_source(id)?;
640        src.with_data(|data| p(data, face_index))
641    }
642
643    /// Makes the font data that backs the specified face id shared so that the application can
644    /// hold a reference to it.
645    ///
646    /// # Safety
647    ///
648    /// If the face originates from a file from disk, then the file is mapped from disk. This is unsafe as
649    /// another process may make changes to the file on disk, which may become visible in this process'
650    /// mapping and possibly cause crashes.
651    ///
652    /// If the underlying font provides multiple faces, then all faces are updated to participate in
653    /// the data sharing. If the face was previously marked for data sharing, then this function will
654    /// return a clone of the existing reference.
655    #[cfg(all(feature = "fs", feature = "memmap"))]
656    pub unsafe fn make_shared_face_data(
657        &mut self,
658        id: ID,
659    ) -> Option<(std::sync::Arc<dyn AsRef<[u8]> + Send + Sync>, u32)> {
660        let face_info = self.faces.get(id.0)?;
661        let face_index = face_info.index;
662
663        let old_source = face_info.source.clone();
664
665        let (path, shared_data) = match &old_source {
666            Source::Binary(data) => {
667                return Some((data.clone(), face_index));
668            }
669            Source::File(ref path) => {
670                let file = std::fs::File::open(path).ok()?;
671                let shared_data = std::sync::Arc::new(memmap2::MmapOptions::new().map(&file).ok()?)
672                    as std::sync::Arc<dyn AsRef<[u8]> + Send + Sync>;
673                (path.clone(), shared_data)
674            }
675            Source::SharedFile(_, data) => {
676                return Some((data.clone(), face_index));
677            }
678        };
679
680        let shared_source = Source::SharedFile(path.clone(), shared_data.clone());
681
682        self.faces.iter_mut().for_each(|(_, face)| {
683            if matches!(&face.source, Source::File(old_path) if old_path == &path) {
684                face.source = shared_source.clone();
685            }
686        });
687
688        Some((shared_data, face_index))
689    }
690
691    /// Transfers ownership of shared font data back to the font database. This is the reverse operation
692    /// of [`Self::make_shared_face_data`]. If the font data belonging to the specified face is mapped
693    /// from a file on disk, then that mapping is closed and the data becomes private to the process again.
694    #[cfg(all(feature = "fs", feature = "memmap"))]
695    pub fn make_face_data_unshared(&mut self, id: ID) {
696        let face_info = match self.faces.get(id.0) {
697            Some(face_info) => face_info,
698            None => return,
699        };
700
701        let old_source = face_info.source.clone();
702
703        let shared_path = match old_source {
704            #[cfg(all(feature = "fs", feature = "memmap"))]
705            Source::SharedFile(path, _) => path,
706            _ => return,
707        };
708
709        let new_source = Source::File(shared_path.clone());
710
711        self.faces.iter_mut().for_each(|(_, face)| {
712            if matches!(&face.source, Source::SharedFile(path, ..) if path == &shared_path) {
713                face.source = new_source.clone();
714            }
715        });
716    }
717}
718
719/// A single font face info.
720///
721/// A font can have multiple faces.
722///
723/// A single item of the `Database`.
724#[derive(Clone, Debug)]
725pub struct FaceInfo {
726    /// An unique ID.
727    pub id: ID,
728
729    /// A font source.
730    ///
731    /// Note that multiple `FaceInfo` objects can reference the same data in case of
732    /// font collections, which means that they'll use the same Source.
733    pub source: Source,
734
735    /// A face index in the `source`.
736    pub index: u32,
737
738    /// A list of family names.
739    ///
740    /// Contains pairs of Name + Language. Where the first family is always English US,
741    /// unless it's missing from the font.
742    ///
743    /// Corresponds to a *Typographic Family* (ID 16) or a *Font Family* (ID 1) [name ID]
744    /// in a TrueType font.
745    ///
746    /// This is not an *Extended Typographic Family* or a *Full Name*.
747    /// Meaning it will contain _Arial_ and not _Arial Bold_.
748    ///
749    /// [name ID]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
750    pub families: Vec<(String, Language)>,
751
752    /// A PostScript name.
753    ///
754    /// Corresponds to a *PostScript name* (6) [name ID] in a TrueType font.
755    ///
756    /// [name ID]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
757    pub post_script_name: String,
758
759    /// A font face style.
760    pub style: Style,
761
762    /// A font face weight.
763    pub weight: Weight,
764
765    /// A font face stretch.
766    pub stretch: Stretch,
767
768    /// Indicates that the font face is monospaced.
769    pub monospaced: bool,
770}
771
772/// A font source.
773///
774/// Either a raw binary data or a file path.
775///
776/// Stores the whole font and not just a single face.
777#[derive(Clone)]
778pub enum Source {
779    /// A font's raw data, typically backed by a Vec<u8>.
780    Binary(alloc::sync::Arc<dyn AsRef<[u8]> + Sync + Send>),
781
782    /// A font's path.
783    #[cfg(feature = "fs")]
784    File(std::path::PathBuf),
785
786    /// A font's raw data originating from a shared file mapping.
787    #[cfg(all(feature = "fs", feature = "memmap"))]
788    SharedFile(
789        std::path::PathBuf,
790        std::sync::Arc<dyn AsRef<[u8]> + Sync + Send>,
791    ),
792}
793
794impl core::fmt::Debug for Source {
795    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
796        match self {
797            Self::Binary(arg0) => f
798                .debug_tuple("SharedBinary")
799                .field(&arg0.as_ref().as_ref())
800                .finish(),
801            #[cfg(feature = "fs")]
802            Self::File(arg0) => f.debug_tuple("File").field(arg0).finish(),
803            #[cfg(all(feature = "fs", feature = "memmap"))]
804            Self::SharedFile(arg0, arg1) => f
805                .debug_tuple("SharedFile")
806                .field(arg0)
807                .field(&arg1.as_ref().as_ref())
808                .finish(),
809        }
810    }
811}
812
813impl Source {
814    fn with_data<P, T>(&self, p: P) -> Option<T>
815    where
816        P: FnOnce(&[u8]) -> T,
817    {
818        match &self {
819            #[cfg(all(feature = "fs", not(feature = "memmap")))]
820            Source::File(ref path) => {
821                let data = std::fs::read(path).ok()?;
822
823                Some(p(&data))
824            }
825            #[cfg(all(feature = "fs", feature = "memmap"))]
826            Source::File(ref path) => {
827                let file = std::fs::File::open(path).ok()?;
828                let data = unsafe { &memmap2::MmapOptions::new().map(&file).ok()? };
829
830                Some(p(data))
831            }
832            Source::Binary(ref data) => Some(p(data.as_ref().as_ref())),
833            #[cfg(all(feature = "fs", feature = "memmap"))]
834            Source::SharedFile(_, ref data) => Some(p(data.as_ref().as_ref())),
835        }
836    }
837}
838
839/// A database query.
840///
841/// Mainly used by `Database::query()`.
842#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Hash)]
843pub struct Query<'a> {
844    /// A prioritized list of font family names or generic family names.
845    ///
846    /// [font-family](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#propdef-font-family) in CSS.
847    pub families: &'a [Family<'a>],
848
849    /// Specifies the weight of glyphs in the font, their degree of blackness or stroke thickness.
850    ///
851    /// [font-weight](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-weight-prop) in CSS.
852    pub weight: Weight,
853
854    /// Selects a normal, condensed, or expanded face from a font family.
855    ///
856    /// [font-stretch](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-stretch-prop) in CSS.
857    pub stretch: Stretch,
858
859    /// Allows italic or oblique faces to be selected.
860    ///
861    /// [font-style](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-style-prop) in CSS.
862    pub style: Style,
863}
864
865// Enum value descriptions are from the CSS spec.
866/// A [font family](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#propdef-font-family).
867#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
868pub enum Family<'a> {
869    /// The name of a font family of choice.
870    ///
871    /// This must be a *Typographic Family* (ID 16) or a *Family Name* (ID 1) in terms of TrueType.
872    /// Meaning you have to pass a family without any additional suffixes like _Bold_, _Italic_,
873    /// _Regular_, etc.
874    ///
875    /// Localized names are allowed.
876    Name(&'a str),
877
878    /// Serif fonts represent the formal text style for a script.
879    Serif,
880
881    /// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low contrast
882    /// and have stroke endings that are plain — without any flaring, cross stroke,
883    /// or other ornamentation.
884    SansSerif,
885
886    /// Glyphs in cursive fonts generally use a more informal script style,
887    /// and the result looks more like handwritten pen or brush writing than printed letterwork.
888    Cursive,
889
890    /// Fantasy fonts are primarily decorative or expressive fonts that
891    /// contain decorative or expressive representations of characters.
892    Fantasy,
893
894    /// The sole criterion of a monospace font is that all glyphs have the same fixed width.
895    Monospace,
896}
897
898/// Specifies the weight of glyphs in the font, their degree of blackness or stroke thickness.
899#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
900pub struct Weight(pub u16);
901
902impl Default for Weight {
903    #[inline]
904    fn default() -> Weight {
905        Weight::NORMAL
906    }
907}
908
909impl Weight {
910    /// Thin weight (100), the thinnest value.
911    pub const THIN: Weight = Weight(100);
912    /// Extra light weight (200).
913    pub const EXTRA_LIGHT: Weight = Weight(200);
914    /// Light weight (300).
915    pub const LIGHT: Weight = Weight(300);
916    /// Normal (400).
917    pub const NORMAL: Weight = Weight(400);
918    /// Medium weight (500, higher than normal).
919    pub const MEDIUM: Weight = Weight(500);
920    /// Semibold weight (600).
921    pub const SEMIBOLD: Weight = Weight(600);
922    /// Bold weight (700).
923    pub const BOLD: Weight = Weight(700);
924    /// Extra-bold weight (800).
925    pub const EXTRA_BOLD: Weight = Weight(800);
926    /// Black weight (900), the thickest value.
927    pub const BLACK: Weight = Weight(900);
928}
929
930/// Allows italic or oblique faces to be selected.
931#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
932pub enum Style {
933    /// A face that is neither italic not obliqued.
934    Normal,
935    /// A form that is generally cursive in nature.
936    Italic,
937    /// A typically-sloped version of the regular face.
938    Oblique,
939}
940
941impl Default for Style {
942    #[inline]
943    fn default() -> Style {
944        Style::Normal
945    }
946}
947
948fn parse_face_info(source: Source, data: &[u8], index: u32) -> Result<FaceInfo, LoadError> {
949    let raw_face = ttf_parser::RawFace::parse(data, index).map_err(|_| LoadError::MalformedFont)?;
950    let (families, post_script_name) = parse_names(&raw_face).ok_or(LoadError::UnnamedFont)?;
951    let (mut style, weight, stretch) = parse_os2(&raw_face);
952    let (monospaced, italic) = parse_post(&raw_face);
953
954    if style == Style::Normal && italic {
955        style = Style::Italic;
956    }
957
958    Ok(FaceInfo {
959        id: ID::dummy(),
960        source,
961        index,
962        families,
963        post_script_name,
964        style,
965        weight,
966        stretch,
967        monospaced,
968    })
969}
970
971fn parse_names(raw_face: &ttf_parser::RawFace) -> Option<(Vec<(String, Language)>, String)> {
972    const NAME_TAG: ttf_parser::Tag = ttf_parser::Tag::from_bytes(b"name");
973    let name_data = raw_face.table(NAME_TAG)?;
974    let name_table = ttf_parser::name::Table::parse(name_data)?;
975
976    let mut families = collect_families(ttf_parser::name_id::TYPOGRAPHIC_FAMILY, &name_table.names);
977
978    // We have to fallback to Family Name when no Typographic Family Name was set.
979    if families.is_empty() {
980        families = collect_families(ttf_parser::name_id::FAMILY, &name_table.names);
981    }
982
983    // Make English US the first one.
984    if families.len() > 1 {
985        if let Some(index) = families
986            .iter()
987            .position(|f| f.1 == Language::English_UnitedStates)
988        {
989            if index != 0 {
990                families.swap(0, index);
991            }
992        }
993    }
994
995    if families.is_empty() {
996        return None;
997    }
998
999    let post_script_name = name_table
1000        .names
1001        .into_iter()
1002        .find(|name| {
1003            name.name_id == ttf_parser::name_id::POST_SCRIPT_NAME && name.is_supported_encoding()
1004        })
1005        .and_then(|name| name_to_unicode(&name))?;
1006
1007    Some((families, post_script_name))
1008}
1009
1010fn collect_families(name_id: u16, names: &ttf_parser::name::Names) -> Vec<(String, Language)> {
1011    let mut families = Vec::new();
1012    for name in names.into_iter() {
1013        if name.name_id == name_id && name.is_unicode() {
1014            if let Some(family) = name_to_unicode(&name) {
1015                families.push((family, name.language()));
1016            }
1017        }
1018    }
1019
1020    // If no Unicode English US family name was found then look for English MacRoman as well.
1021    if !families
1022        .iter()
1023        .any(|f| f.1 == Language::English_UnitedStates)
1024    {
1025        for name in names.into_iter() {
1026            if name.name_id == name_id && name.is_mac_roman() {
1027                if let Some(family) = name_to_unicode(&name) {
1028                    families.push((family, name.language()));
1029                    break;
1030                }
1031            }
1032        }
1033    }
1034
1035    families
1036}
1037
1038fn name_to_unicode(name: &ttf_parser::name::Name) -> Option<String> {
1039    if name.is_unicode() {
1040        let mut raw_data: Vec<u16> = Vec::new();
1041        for c in ttf_parser::LazyArray16::<u16>::new(name.name) {
1042            raw_data.push(c);
1043        }
1044
1045        String::from_utf16(&raw_data).ok()
1046    } else if name.is_mac_roman() {
1047        // We support only MacRoman encoding here, which should be enough in most cases.
1048        let mut raw_data = Vec::with_capacity(name.name.len());
1049        for b in name.name {
1050            raw_data.push(MAC_ROMAN[*b as usize]);
1051        }
1052
1053        String::from_utf16(&raw_data).ok()
1054    } else {
1055        None
1056    }
1057}
1058
1059fn parse_os2(raw_face: &ttf_parser::RawFace) -> (Style, Weight, Stretch) {
1060    const OS2_TAG: ttf_parser::Tag = ttf_parser::Tag::from_bytes(b"OS/2");
1061    let table = match raw_face
1062        .table(OS2_TAG)
1063        .and_then(ttf_parser::os2::Table::parse)
1064    {
1065        Some(table) => table,
1066        None => return (Style::Normal, Weight::NORMAL, Stretch::Normal),
1067    };
1068
1069    let style = match table.style() {
1070        ttf_parser::Style::Normal => Style::Normal,
1071        ttf_parser::Style::Italic => Style::Italic,
1072        ttf_parser::Style::Oblique => Style::Oblique,
1073    };
1074
1075    let weight = table.weight();
1076    let stretch = table.width();
1077
1078    (style, Weight(weight.to_number()), stretch)
1079}
1080
1081fn parse_post(raw_face: &ttf_parser::RawFace) -> (bool, bool) {
1082    // We need just a single value from the `post` table, while ttf-parser will parse all.
1083    // Therefore we have a custom parser.
1084
1085    const POST_TAG: ttf_parser::Tag = ttf_parser::Tag::from_bytes(b"post");
1086    let data = match raw_face.table(POST_TAG) {
1087        Some(v) => v,
1088        None => return (false, false),
1089    };
1090
1091    // All we care about, it that u32 at offset 12 is non-zero.
1092    let monospaced = data.get(12..16) != Some(&[0, 0, 0, 0]);
1093
1094    // Italic angle as f16.16.
1095    let italic = data.get(4..8) != Some(&[0, 0, 0, 0]);
1096
1097    (monospaced, italic)
1098}
1099
1100trait NameExt {
1101    fn is_mac_roman(&self) -> bool;
1102    fn is_supported_encoding(&self) -> bool;
1103}
1104
1105impl NameExt for ttf_parser::name::Name<'_> {
1106    #[inline]
1107    fn is_mac_roman(&self) -> bool {
1108        use ttf_parser::PlatformId::Macintosh;
1109        // https://docs.microsoft.com/en-us/typography/opentype/spec/name#macintosh-encoding-ids-script-manager-codes
1110        const MACINTOSH_ROMAN_ENCODING_ID: u16 = 0;
1111
1112        self.platform_id == Macintosh && self.encoding_id == MACINTOSH_ROMAN_ENCODING_ID
1113    }
1114
1115    #[inline]
1116    fn is_supported_encoding(&self) -> bool {
1117        self.is_unicode() || self.is_mac_roman()
1118    }
1119}
1120
1121// https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-style-matching
1122// Based on https://github.com/servo/font-kit
1123#[inline(never)]
1124fn find_best_match(candidates: &[&FaceInfo], query: &Query) -> Option<usize> {
1125    debug_assert!(!candidates.is_empty());
1126
1127    // Step 4.
1128    let mut matching_set: Vec<usize> = (0..candidates.len()).collect();
1129
1130    // Step 4a (`font-stretch`).
1131    let matches = matching_set
1132        .iter()
1133        .any(|&index| candidates[index].stretch == query.stretch);
1134    let matching_stretch = if matches {
1135        // Exact match.
1136        query.stretch
1137    } else if query.stretch <= Stretch::Normal {
1138        // Closest stretch, first checking narrower values and then wider values.
1139        let stretch = matching_set
1140            .iter()
1141            .filter(|&&index| candidates[index].stretch < query.stretch)
1142            .min_by_key(|&&index| {
1143                query.stretch.to_number() - candidates[index].stretch.to_number()
1144            });
1145
1146        match stretch {
1147            Some(&matching_index) => candidates[matching_index].stretch,
1148            None => {
1149                let matching_index = *matching_set.iter().min_by_key(|&&index| {
1150                    candidates[index].stretch.to_number() - query.stretch.to_number()
1151                })?;
1152
1153                candidates[matching_index].stretch
1154            }
1155        }
1156    } else {
1157        // Closest stretch, first checking wider values and then narrower values.
1158        let stretch = matching_set
1159            .iter()
1160            .filter(|&&index| candidates[index].stretch > query.stretch)
1161            .min_by_key(|&&index| {
1162                candidates[index].stretch.to_number() - query.stretch.to_number()
1163            });
1164
1165        match stretch {
1166            Some(&matching_index) => candidates[matching_index].stretch,
1167            None => {
1168                let matching_index = *matching_set.iter().min_by_key(|&&index| {
1169                    query.stretch.to_number() - candidates[index].stretch.to_number()
1170                })?;
1171
1172                candidates[matching_index].stretch
1173            }
1174        }
1175    };
1176    matching_set.retain(|&index| candidates[index].stretch == matching_stretch);
1177
1178    // Step 4b (`font-style`).
1179    let style_preference = match query.style {
1180        Style::Italic => [Style::Italic, Style::Oblique, Style::Normal],
1181        Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal],
1182        Style::Normal => [Style::Normal, Style::Oblique, Style::Italic],
1183    };
1184    let matching_style = *style_preference.iter().find(|&query_style| {
1185        matching_set
1186            .iter()
1187            .any(|&index| candidates[index].style == *query_style)
1188    })?;
1189
1190    matching_set.retain(|&index| candidates[index].style == matching_style);
1191
1192    // Step 4c (`font-weight`).
1193    //
1194    // The spec doesn't say what to do if the weight is between 400 and 500 exclusive, so we
1195    // just use 450 as the cutoff.
1196    let weight = query.weight.0;
1197
1198    let matching_weight = if matching_set
1199        .iter()
1200        .any(|&index| candidates[index].weight.0 == weight)
1201    {
1202        Weight(weight)
1203    } else if (400..450).contains(&weight)
1204        && matching_set
1205            .iter()
1206            .any(|&index| candidates[index].weight.0 == 500)
1207    {
1208        // Check 500 first.
1209        Weight::MEDIUM
1210    } else if (450..=500).contains(&weight)
1211        && matching_set
1212            .iter()
1213            .any(|&index| candidates[index].weight.0 == 400)
1214    {
1215        // Check 400 first.
1216        Weight::NORMAL
1217    } else if weight <= 500 {
1218        // Closest weight, first checking thinner values and then fatter ones.
1219        let idx = matching_set
1220            .iter()
1221            .filter(|&&index| candidates[index].weight.0 <= weight)
1222            .min_by_key(|&&index| weight - candidates[index].weight.0);
1223
1224        match idx {
1225            Some(&matching_index) => candidates[matching_index].weight,
1226            None => {
1227                let matching_index = *matching_set
1228                    .iter()
1229                    .min_by_key(|&&index| candidates[index].weight.0 - weight)?;
1230                candidates[matching_index].weight
1231            }
1232        }
1233    } else {
1234        // Closest weight, first checking fatter values and then thinner ones.
1235        let idx = matching_set
1236            .iter()
1237            .filter(|&&index| candidates[index].weight.0 >= weight)
1238            .min_by_key(|&&index| candidates[index].weight.0 - weight);
1239
1240        match idx {
1241            Some(&matching_index) => candidates[matching_index].weight,
1242            None => {
1243                let matching_index = *matching_set
1244                    .iter()
1245                    .min_by_key(|&&index| weight - candidates[index].weight.0)?;
1246                candidates[matching_index].weight
1247            }
1248        }
1249    };
1250    matching_set.retain(|&index| candidates[index].weight == matching_weight);
1251
1252    // Ignore step 4d (`font-size`).
1253
1254    // Return the result.
1255    matching_set.into_iter().next()
1256}
1257
1258/// Macintosh Roman to UTF-16 encoding table.
1259///
1260/// https://en.wikipedia.org/wiki/Mac_OS_Roman
1261#[rustfmt::skip]
1262const MAC_ROMAN: &[u16; 256] = &[
1263    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
1264    0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
1265    0x0010, 0x2318, 0x21E7, 0x2325, 0x2303, 0x0015, 0x0016, 0x0017,
1266    0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
1267    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
1268    0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
1269    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
1270    0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
1271    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
1272    0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
1273    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
1274    0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
1275    0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
1276    0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
1277    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
1278    0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
1279    0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,
1280    0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
1281    0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,
1282    0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,
1283    0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
1284    0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,
1285    0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211,
1286    0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,
1287    0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,
1288    0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
1289    0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA,
1290    0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
1291    0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,
1292    0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,
1293    0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
1294    0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,
1295];