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