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: &Error) => 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: &Arc + Send + Sync + 'static>) => f&mut DebugTuple<'_, '_>
884 .debug_tuple(name:"SharedBinary")
885 .field(&arg0.as_ref().as_ref())
886 .finish(),
887 #[cfg(feature = "fs")]
888 Self::File(arg0: &PathBuf) => f.debug_tuple(name:"File").field(arg0).finish(),
889 #[cfg(all(feature = "fs", feature = "memmap"))]
890 Self::SharedFile(arg0: &PathBuf, arg1: &Arc + Send + Sync + 'static>) => f&mut DebugTuple<'_, '_>
891 .debug_tuple(name:"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: RawFace<'_> = ttf_parser::RawFace::parse(data, index).map_err(|_| LoadError::MalformedFont)?;
1036 let (families: Vec<(String, Language)>, post_script_name: String) = parse_names(&raw_face).ok_or(err:LoadError::UnnamedFont)?;
1037 let (mut style: Style, weight: Weight, stretch: Width) = parse_os2(&raw_face);
1038 let (monospaced: bool, italic: bool) = 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: u16 in ttf_parser::LazyArray16::<u16>::new(data: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 = Vec::with_capacity(name.name.len());
1135 for b: &u8 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: Table<'_> = match raw_faceOption<&[u8]>
1148 .table(OS2_TAG)
1149 .and_then(ttf_parser::os2::Table::parse)
1150 {
1151 Some(table: Table<'_>) => table,
1152 None => return (Style::Normal, Weight::NORMAL, Stretch::Normal),
1153 };
1154
1155 let style: 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: Weight = table.weight();
1162 let stretch: Width = 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: &[u8] = match raw_face.table(POST_TAG) {
1173 Some(v: &[u8]) => 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: bool = data.get(index:12..16) != Some(&[0, 0, 0, 0]);
1179
1180 // Italic angle as f16.16.
1181 let italic: bool = data.get(index: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];
1382