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
13use crate::error::SelectionError;
14use crate::family::Family;
15use crate::family_handle::FamilyHandle;
16use crate::family_name::FamilyName;
17use crate::font::Font;
18use crate::handle::Handle;
19use crate::matching;
20use crate::properties::Properties;
21use std::any::Any;
22
23#[cfg(all(
24 any(target_os = "macos", target_os = "ios"),
25 not(feature = "loader-freetype-default")
26))]
27pub use crate::sources::core_text::CoreTextSource as SystemSource;
28#[cfg(all(target_family = "windows", not(feature = "source-fontconfig-default")))]
29pub 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))]
40pub use crate::sources::fontconfig::FontconfigSource as SystemSource;
41#[cfg(all(target_os = "android", not(feature = "source-fontconfig-default")))]
42pub 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"))]
46const DEFAULT_FONT_FAMILY_SERIF: &'static str = "Times New Roman";
47#[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))]
48const DEFAULT_FONT_FAMILY_SANS_SERIF: &'static str = "Arial";
49#[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))]
50const DEFAULT_FONT_FAMILY_MONOSPACE: &'static str = "Courier New";
51#[cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))]
52const DEFAULT_FONT_FAMILY_CURSIVE: &'static str = "Comic Sans MS";
53#[cfg(target_family = "windows")]
54const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Impact";
55#[cfg(any(target_os = "macos", target_os = "ios"))]
56const DEFAULT_FONT_FAMILY_FANTASY: &'static str = "Papyrus";
57
58#[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))]
59const DEFAULT_FONT_FAMILY_SERIF: &'static str = "serif";
60#[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))]
61const DEFAULT_FONT_FAMILY_SANS_SERIF: &'static str = "sans-serif";
62#[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))]
63const DEFAULT_FONT_FAMILY_MONOSPACE: &'static str = "monospace";
64#[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))]
65const DEFAULT_FONT_FAMILY_CURSIVE: &'static str = "cursive";
66#[cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))]
67const 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.
72pub 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