font_kit/sources/
fs.rs

1// font-kit/src/sources/fs.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 source that loads fonts from a directory or directories on disk.
12//!
13//! This source uses the WalkDir abstraction from the `walkdir` crate to locate fonts.
14//!
15//! This is the native source on Android and OpenHarmony.
16
17use std::any::Any;
18use std::fs::File;
19use std::path::{Path, PathBuf};
20use walkdir::WalkDir;
21
22#[cfg(not(any(target_os = "android", target_family = "windows", target_env = "ohos")))]
23use dirs;
24#[cfg(target_family = "windows")]
25use std::ffi::OsString;
26#[cfg(target_family = "windows")]
27use std::os::windows::ffi::OsStringExt;
28#[cfg(target_family = "windows")]
29use winapi::shared::minwindef::{MAX_PATH, UINT};
30#[cfg(target_family = "windows")]
31use winapi::um::sysinfoapi;
32
33use crate::error::SelectionError;
34use crate::family_handle::FamilyHandle;
35use crate::family_name::FamilyName;
36use crate::file_type::FileType;
37use crate::font::Font;
38use crate::handle::Handle;
39use crate::properties::Properties;
40use crate::source::Source;
41use crate::sources::mem::MemSource;
42
43/// A source that loads fonts from a directory or directories on disk.
44///
45/// This source uses the WalkDir abstraction from the `walkdir` crate to locate fonts.
46///
47/// This is the native source on Android and OpenHarmony.
48#[allow(missing_debug_implementations)]
49pub struct FsSource {
50    mem_source: MemSource,
51}
52
53impl Default for FsSource {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl FsSource {
60    /// Opens the default set of directories on this platform and indexes the fonts found within.
61    ///
62    /// Do not rely on this function for systems other than Android or OpenHarmony. It makes a best
63    /// effort to locate fonts in the typical platform directories, but it is too simple to pick up
64    /// fonts that are stored in unusual locations but nevertheless properly installed.
65    pub fn new() -> FsSource {
66        let mut fonts = vec![];
67        for font_directory in default_font_directories() {
68            fonts.extend(Self::discover_fonts(&font_directory));
69        }
70
71        FsSource {
72            mem_source: MemSource::from_fonts(fonts.into_iter()).unwrap(),
73        }
74    }
75
76    fn discover_fonts(path: &Path) -> Vec<Handle> {
77        let mut fonts = vec![];
78        for directory_entry in WalkDir::new(path).into_iter() {
79            let directory_entry = match directory_entry {
80                Ok(directory_entry) => directory_entry,
81                Err(_) => continue,
82            };
83            let path = directory_entry.path();
84            let mut file = match File::open(path) {
85                Err(_) => continue,
86                Ok(file) => file,
87            };
88            match Font::analyze_file(&mut file) {
89                Err(_) => continue,
90                Ok(FileType::Single) => fonts.push(Handle::from_path(path.to_owned(), 0)),
91                Ok(FileType::Collection(font_count)) => {
92                    for font_index in 0..font_count {
93                        fonts.push(Handle::from_path(path.to_owned(), font_index))
94                    }
95                }
96            }
97        }
98        fonts
99    }
100
101    /// Indexes all fonts found in `path`
102    pub fn in_path<P>(path: P) -> FsSource
103    where
104        P: AsRef<Path>,
105    {
106        let fonts = Self::discover_fonts(path.as_ref());
107        FsSource {
108            mem_source: MemSource::from_fonts(fonts.into_iter()).unwrap(),
109        }
110    }
111
112    /// Returns paths of all fonts installed on the system.
113    pub fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
114        self.mem_source.all_fonts()
115    }
116
117    /// Returns the names of all families installed on the system.
118    pub fn all_families(&self) -> Result<Vec<String>, SelectionError> {
119        self.mem_source.all_families()
120    }
121
122    /// Looks up a font family by name and returns the handles of all the fonts in that family.
123    pub fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
124        self.mem_source.select_family_by_name(family_name)
125    }
126
127    /// Selects a font by PostScript name, which should be a unique identifier.
128    ///
129    /// This implementation does a brute-force search of installed fonts to find the one that
130    /// matches.
131    pub fn select_by_postscript_name(
132        &self,
133        postscript_name: &str,
134    ) -> Result<Handle, SelectionError> {
135        self.mem_source.select_by_postscript_name(postscript_name)
136    }
137
138    /// Performs font matching according to the CSS Fonts Level 3 specification and returns the
139    /// handle.
140    #[inline]
141    pub fn select_best_match(
142        &self,
143        family_names: &[FamilyName],
144        properties: &Properties,
145    ) -> Result<Handle, SelectionError> {
146        <Self as Source>::select_best_match(self, family_names, properties)
147    }
148}
149
150impl Source for FsSource {
151    #[inline]
152    fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
153        self.all_fonts()
154    }
155
156    #[inline]
157    fn all_families(&self) -> Result<Vec<String>, SelectionError> {
158        self.all_families()
159    }
160
161    fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
162        self.select_family_by_name(family_name)
163    }
164
165    fn select_by_postscript_name(&self, postscript_name: &str) -> Result<Handle, SelectionError> {
166        self.select_by_postscript_name(postscript_name)
167    }
168
169    #[inline]
170    fn as_any(&self) -> &dyn Any {
171        self
172    }
173
174    #[inline]
175    fn as_mut_any(&mut self) -> &mut dyn Any {
176        self
177    }
178}
179
180#[cfg(any(target_os = "android", target_env = "ohos"))]
181fn default_font_directories() -> Vec<PathBuf> {
182    vec![PathBuf::from("/system/fonts")]
183}
184
185#[cfg(target_family = "windows")]
186fn default_font_directories() -> Vec<PathBuf> {
187    unsafe {
188        let mut buffer = vec![0; MAX_PATH];
189        let len = sysinfoapi::GetWindowsDirectoryW(buffer.as_mut_ptr(), buffer.len() as UINT);
190        assert!(len != 0);
191        buffer.truncate(len as usize);
192
193        let mut path = PathBuf::from(OsString::from_wide(&buffer));
194        path.push("Fonts");
195        vec![path]
196    }
197}
198
199#[cfg(target_os = "macos")]
200fn default_font_directories() -> Vec<PathBuf> {
201    let mut directories = vec![
202        PathBuf::from("/System/Library/Fonts"),
203        PathBuf::from("/Library/Fonts"),
204        PathBuf::from("/Network/Library/Fonts"),
205    ];
206    if let Some(mut path) = dirs::home_dir() {
207        path.push("Library");
208        path.push("Fonts");
209        directories.push(path);
210    }
211    directories
212}
213
214#[cfg(not(any(
215    target_os = "android",
216    target_family = "windows",
217    target_os = "macos",
218    target_env = "ohos"
219)))]
220fn default_font_directories() -> Vec<PathBuf> {
221    let mut directories = vec![
222        PathBuf::from("/usr/share/fonts"),
223        PathBuf::from("/usr/local/share/fonts"),
224        PathBuf::from("/var/run/host/usr/share/fonts"), // Flatpak specific
225        PathBuf::from("/var/run/host/usr/local/share/fonts"),
226    ];
227    if let Some(path) = dirs::home_dir() {
228        directories.push(path.join(".fonts")); // ~/.fonts is deprecated
229        directories.push(path.join("local").join("share").join("fonts")); // Flatpak specific
230    }
231    if let Some(mut path) = dirs::data_dir() {
232        path.push("fonts");
233        directories.push(path);
234    }
235    directories
236}