Skip to main content

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