1 | // font-kit/src/source.rs |
2 | // |
3 | // Copyright © 2018 The Pathfinder Project Developers. |
4 | // |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
8 | // option. This file may not be copied, modified, or distributed |
9 | // except according to those terms. |
10 | |
11 | //! A database of installed fonts that can be queried. |
12 | |
13 | use crate::error::SelectionError; |
14 | use crate::family::Family; |
15 | use crate::family_handle::FamilyHandle; |
16 | use crate::family_name::FamilyName; |
17 | use crate::font::Font; |
18 | use crate::handle::Handle; |
19 | use crate::matching; |
20 | use crate::properties::Properties; |
21 | use std::any::Any; |
22 | |
23 | #[cfg (all( |
24 | any(target_os = "macos" , target_os = "ios" ), |
25 | not(feature = "loader-freetype-default" ) |
26 | ))] |
27 | pub use crate::sources::core_text::CoreTextSource as SystemSource; |
28 | #[cfg (all(target_family = "windows" , not(feature = "source-fontconfig-default" )))] |
29 | pub use crate::sources::directwrite::DirectWriteSource as SystemSource; |
30 | #[cfg (any( |
31 | not(any( |
32 | target_os = "android" , |
33 | target_os = "macos" , |
34 | target_os = "ios" , |
35 | target_family = "windows" , |
36 | target_arch = "wasm32" |
37 | )), |
38 | feature = "source-fontconfig-default" |
39 | ))] |
40 | pub use crate::sources::fontconfig::FontconfigSource as SystemSource; |
41 | #[cfg (all(target_os = "android" , not(feature = "source-fontconfig-default" )))] |
42 | pub use crate::sources::fs::FsSource as SystemSource; |
43 | |
44 | // FIXME(pcwalton): These could expand to multiple fonts, and they could be language-specific. |
45 | #[cfg (any(target_family = "windows" , target_os = "macos" , target_os = "ios" ))] |
46 | const DEFAULT_FONT_FAMILY_SERIF: &'static str = "Times New Roman" ; |
47 | #[cfg (any(target_family = "windows" , target_os = "macos" , target_os = "ios" ))] |
48 | const DEFAULT_FONT_FAMILY_SANS_SERIF: &'static str = "Arial" ; |
49 | #[cfg (any(target_family = "windows" , target_os = "macos" , target_os = "ios" ))] |
50 | const DEFAULT_FONT_FAMILY_MONOSPACE: &'static str = "Courier New" ; |
51 | #[cfg (any(target_family = "windows" , target_os = "macos" , target_os = "ios" ))] |
52 | const DEFAULT_FONT_FAMILY_CURSIVE: &'static str = "Comic Sans MS" ; |
53 | #[cfg (target_family = "windows" )] |
54 | const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Impact" ; |
55 | #[cfg (any(target_os = "macos" , target_os = "ios" ))] |
56 | const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Papyrus" ; |
57 | |
58 | #[cfg (not(any(target_family = "windows" , target_os = "macos" , target_os = "ios" )))] |
59 | const DEFAULT_FONT_FAMILY_SERIF: &'static str = "serif" ; |
60 | #[cfg (not(any(target_family = "windows" , target_os = "macos" , target_os = "ios" )))] |
61 | const DEFAULT_FONT_FAMILY_SANS_SERIF: &'static str = "sans-serif" ; |
62 | #[cfg (not(any(target_family = "windows" , target_os = "macos" , target_os = "ios" )))] |
63 | const DEFAULT_FONT_FAMILY_MONOSPACE: &'static str = "monospace" ; |
64 | #[cfg (not(any(target_family = "windows" , target_os = "macos" , target_os = "ios" )))] |
65 | const DEFAULT_FONT_FAMILY_CURSIVE: &'static str = "cursive" ; |
66 | #[cfg (not(any(target_family = "windows" , target_os = "macos" , target_os = "ios" )))] |
67 | const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "fantasy" ; |
68 | |
69 | /// A database of installed fonts that can be queried. |
70 | /// |
71 | /// This trait is object-safe. |
72 | pub trait Source: Any { |
73 | /// Returns paths of all fonts installed on the system. |
74 | fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError>; |
75 | |
76 | /// Returns the names of all families installed on the system. |
77 | fn all_families(&self) -> Result<Vec<String>, SelectionError>; |
78 | |
79 | /// Looks up a font family by name and returns the handles of all the fonts in that family. |
80 | fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError>; |
81 | |
82 | /// Selects a font by PostScript name, which should be a unique identifier. |
83 | /// |
84 | /// The default implementation, which is used by the DirectWrite and the filesystem backends, |
85 | /// does a brute-force search of installed fonts to find the one that matches. |
86 | fn select_by_postscript_name(&self, postscript_name: &str) -> Result<Handle, SelectionError> { |
87 | // TODO(pcwalton): Optimize this by searching for families with similar names first. |
88 | for family_name in self.all_families()? { |
89 | if let Ok(family_handle) = self.select_family_by_name(&family_name) { |
90 | if let Ok(family) = Family::<Font>::from_handle(&family_handle) { |
91 | for (handle, font) in family_handle.fonts().iter().zip(family.fonts().iter()) { |
92 | if let Some(font_postscript_name) = font.postscript_name() { |
93 | if font_postscript_name == postscript_name { |
94 | return Ok((*handle).clone()); |
95 | } |
96 | } |
97 | } |
98 | } |
99 | } |
100 | } |
101 | Err(SelectionError::NotFound) |
102 | } |
103 | |
104 | // FIXME(pcwalton): This only returns one family instead of multiple families for the generic |
105 | // family names. |
106 | #[doc (hidden)] |
107 | fn select_family_by_generic_name( |
108 | &self, |
109 | family_name: &FamilyName, |
110 | ) -> Result<FamilyHandle, SelectionError> { |
111 | match *family_name { |
112 | FamilyName::Title(ref title) => self.select_family_by_name(title), |
113 | FamilyName::Serif => self.select_family_by_name(DEFAULT_FONT_FAMILY_SERIF), |
114 | FamilyName::SansSerif => self.select_family_by_name(DEFAULT_FONT_FAMILY_SANS_SERIF), |
115 | FamilyName::Monospace => self.select_family_by_name(DEFAULT_FONT_FAMILY_MONOSPACE), |
116 | FamilyName::Cursive => self.select_family_by_name(DEFAULT_FONT_FAMILY_CURSIVE), |
117 | FamilyName::Fantasy => self.select_family_by_name(DEFAULT_FONT_FAMILY_FANTASY), |
118 | } |
119 | } |
120 | |
121 | /// Performs font matching according to the CSS Fonts Level 3 specification and returns the |
122 | /// handle. |
123 | #[inline ] |
124 | fn select_best_match( |
125 | &self, |
126 | family_names: &[FamilyName], |
127 | properties: &Properties, |
128 | ) -> Result<Handle, SelectionError> { |
129 | for family_name in family_names { |
130 | if let Ok(family_handle) = self.select_family_by_generic_name(family_name) { |
131 | let candidates = self.select_descriptions_in_family(&family_handle)?; |
132 | if let Ok(index) = matching::find_best_match(&candidates, properties) { |
133 | return Ok(family_handle.fonts[index].clone()); |
134 | } |
135 | } |
136 | } |
137 | Err(SelectionError::NotFound) |
138 | } |
139 | |
140 | #[doc (hidden)] |
141 | fn select_descriptions_in_family( |
142 | &self, |
143 | family: &FamilyHandle, |
144 | ) -> Result<Vec<Properties>, SelectionError> { |
145 | let mut fields = vec![]; |
146 | for font_handle in family.fonts() { |
147 | match Font::from_handle(font_handle) { |
148 | Ok(font) => fields.push(font.properties()), |
149 | Err(e) => log::warn!("Error loading font from handle: {:?}" , e), |
150 | } |
151 | } |
152 | Ok(fields) |
153 | } |
154 | |
155 | /// Accesses this `Source` as `Any`, which allows downcasting back to a concrete type from a |
156 | /// trait object. |
157 | fn as_any(&self) -> &dyn Any; |
158 | |
159 | /// Accesses this `Source` as `Any`, which allows downcasting back to a concrete type from a |
160 | /// trait object. |
161 | fn as_mut_any(&mut self) -> &mut dyn Any; |
162 | } |
163 | |