font_kit/sources/
core_text.rs

1// font-kit/src/sources/core_text.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 contains the installed fonts on macOS.
12
13use core_foundation::array::CFArray;
14use core_foundation::base::{CFType, TCFType};
15use core_foundation::dictionary::CFDictionary;
16use core_foundation::string::CFString;
17use core_text::font_collection::{self, CTFontCollection};
18use core_text::font_descriptor::{self, CTFontDescriptor};
19use core_text::font_manager;
20use std::any::Any;
21use std::collections::HashMap;
22use std::f32;
23use std::fs::File;
24use std::path::PathBuf;
25use std::sync::Arc;
26
27use crate::error::SelectionError;
28use crate::family_handle::FamilyHandle;
29use crate::family_name::FamilyName;
30use crate::file_type::FileType;
31use crate::font::Font;
32use crate::handle::Handle;
33use crate::loaders::core_text::{self as core_text_loader, FONT_WEIGHT_MAPPING};
34use crate::properties::{Properties, Stretch, Weight};
35use crate::source::Source;
36use crate::utils;
37
38/// A source that contains the installed fonts on macOS.
39#[allow(missing_debug_implementations)]
40#[allow(missing_copy_implementations)]
41pub struct CoreTextSource;
42
43impl CoreTextSource {
44    /// Opens a new connection to the system font source.
45    ///
46    /// (Note that this doesn't actually do any Mach communication to the font server; that is done
47    /// lazily on demand by the Core Text/Core Graphics API.)
48    #[inline]
49    pub fn new() -> CoreTextSource {
50        CoreTextSource
51    }
52
53    /// Returns paths of all fonts installed on the system.
54    pub fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
55        let collection = font_collection::create_for_all_families();
56        create_handles_from_core_text_collection(collection)
57    }
58
59    /// Returns the names of all families installed on the system.
60    pub fn all_families(&self) -> Result<Vec<String>, SelectionError> {
61        let core_text_family_names = font_manager::copy_available_font_family_names();
62        let mut families = Vec::with_capacity(core_text_family_names.len() as usize);
63        for core_text_family_name in core_text_family_names.iter() {
64            families.push(core_text_family_name.to_string())
65        }
66        Ok(families)
67    }
68
69    /// Looks up a font family by name and returns the handles of all the fonts in that family.
70    pub fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
71        let attributes: CFDictionary<CFString, CFType> = CFDictionary::from_CFType_pairs(&[(
72            CFString::new("NSFontFamilyAttribute"),
73            CFString::new(family_name).as_CFType(),
74        )]);
75
76        let descriptor = font_descriptor::new_from_attributes(&attributes);
77        let descriptors = CFArray::from_CFTypes(&[descriptor]);
78        let collection = font_collection::new_from_descriptors(&descriptors);
79        let handles = create_handles_from_core_text_collection(collection)?;
80        Ok(FamilyHandle::from_font_handles(handles.into_iter()))
81    }
82
83    /// Selects a font by PostScript name, which should be a unique identifier.
84    pub fn select_by_postscript_name(
85        &self,
86        postscript_name: &str,
87    ) -> Result<Handle, SelectionError> {
88        let attributes: CFDictionary<CFString, CFType> = CFDictionary::from_CFType_pairs(&[(
89            CFString::new("NSFontNameAttribute"),
90            CFString::new(postscript_name).as_CFType(),
91        )]);
92
93        let descriptor = font_descriptor::new_from_attributes(&attributes);
94        let descriptors = CFArray::from_CFTypes(&[descriptor]);
95        let collection = font_collection::new_from_descriptors(&descriptors);
96        match collection.get_descriptors() {
97            None => Err(SelectionError::NotFound),
98            Some(descriptors) => create_handle_from_descriptor(&*descriptors.get(0).unwrap()),
99        }
100    }
101
102    /// Performs font matching according to the CSS Fonts Level 3 specification and returns the
103    /// handle.
104    #[inline]
105    pub fn select_best_match(
106        &self,
107        family_names: &[FamilyName],
108        properties: &Properties,
109    ) -> Result<Handle, SelectionError> {
110        <Self as Source>::select_best_match(self, family_names, properties)
111    }
112}
113
114impl Source for CoreTextSource {
115    fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
116        self.all_fonts()
117    }
118
119    fn all_families(&self) -> Result<Vec<String>, SelectionError> {
120        self.all_families()
121    }
122
123    fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
124        self.select_family_by_name(family_name)
125    }
126
127    fn select_by_postscript_name(&self, postscript_name: &str) -> Result<Handle, SelectionError> {
128        self.select_by_postscript_name(postscript_name)
129    }
130
131    #[inline]
132    fn as_any(&self) -> &dyn Any {
133        self
134    }
135
136    #[inline]
137    fn as_mut_any(&mut self) -> &mut dyn Any {
138        self
139    }
140}
141
142#[allow(dead_code)]
143fn css_to_core_text_font_weight(css_weight: Weight) -> f32 {
144    core_text_loader::piecewise_linear_lookup(
145        f32::max(100.0, css_weight.0) / 100.0 - 1.0,
146        &FONT_WEIGHT_MAPPING,
147    )
148}
149
150#[allow(dead_code)]
151fn css_stretchiness_to_core_text_width(css_stretchiness: Stretch) -> f32 {
152    let css_stretchiness = utils::clamp(css_stretchiness.0, 0.5, 2.0);
153    0.25 * core_text_loader::piecewise_linear_find_index(css_stretchiness, &Stretch::MAPPING) - 1.0
154}
155
156#[derive(Clone)]
157struct FontDataInfo {
158    data: Arc<Vec<u8>>,
159    file_type: FileType,
160}
161
162fn create_handles_from_core_text_collection(
163    collection: CTFontCollection,
164) -> Result<Vec<Handle>, SelectionError> {
165    let mut fonts = vec![];
166    if let Some(descriptors) = collection.get_descriptors() {
167        let mut font_data_info_cache: HashMap<PathBuf, FontDataInfo> = HashMap::new();
168
169        'outer: for index in 0..descriptors.len() {
170            let descriptor = descriptors.get(index).unwrap();
171            let font_path = descriptor.font_path().unwrap();
172
173            let data_info = if let Some(data_info) = font_data_info_cache.get(&font_path) {
174                data_info.clone()
175            } else {
176                let mut file = if let Ok(file) = File::open(&font_path) {
177                    file
178                } else {
179                    continue;
180                };
181                let data = if let Ok(data) = utils::slurp_file(&mut file) {
182                    Arc::new(data)
183                } else {
184                    continue;
185                };
186
187                let file_type = match Font::analyze_bytes(Arc::clone(&data)) {
188                    Ok(file_type) => file_type,
189                    Err(_) => continue,
190                };
191
192                let data_info = FontDataInfo { data, file_type };
193
194                font_data_info_cache.insert(font_path.clone(), data_info.clone());
195
196                data_info
197            };
198
199            match data_info.file_type {
200                FileType::Collection(font_count) => {
201                    let postscript_name = descriptor.font_name();
202                    for font_index in 0..font_count {
203                        if let Ok(font) = Font::from_bytes(Arc::clone(&data_info.data), font_index)
204                        {
205                            if let Some(font_postscript_name) = font.postscript_name() {
206                                if postscript_name == font_postscript_name {
207                                    fonts.push(Handle::from_memory(data_info.data, font_index));
208                                    continue 'outer;
209                                }
210                            }
211                        }
212                    }
213                }
214                FileType::Single => {
215                    fonts.push(Handle::from_memory(data_info.data, 0));
216                }
217            }
218        }
219    }
220    if fonts.is_empty() {
221        Err(SelectionError::NotFound)
222    } else {
223        Ok(fonts)
224    }
225}
226
227fn create_handle_from_descriptor(descriptor: &CTFontDescriptor) -> Result<Handle, SelectionError> {
228    let font_path = descriptor.font_path().unwrap();
229
230    let mut file = if let Ok(file) = File::open(&font_path) {
231        file
232    } else {
233        return Err(SelectionError::CannotAccessSource { reason: None });
234    };
235
236    let font_data = if let Ok(font_data) = utils::slurp_file(&mut file) {
237        Arc::new(font_data)
238    } else {
239        return Err(SelectionError::CannotAccessSource { reason: None });
240    };
241
242    match Font::analyze_bytes(Arc::clone(&font_data)) {
243        Ok(FileType::Collection(font_count)) => {
244            let postscript_name = descriptor.font_name();
245            for font_index in 0..font_count {
246                if let Ok(font) = Font::from_bytes(Arc::clone(&font_data), font_index) {
247                    if let Some(font_postscript_name) = font.postscript_name() {
248                        if postscript_name == font_postscript_name {
249                            return Ok(Handle::from_memory(font_data, font_index));
250                        }
251                    }
252                }
253            }
254
255            Err(SelectionError::NotFound)
256        }
257        Ok(FileType::Single) => Ok(Handle::from_memory(font_data, 0)),
258        Err(e) => Err(SelectionError::CannotAccessSource {
259            reason: Some(format!("{:?} error on path {:?}", e, font_path).into()),
260        }),
261    }
262}
263
264#[cfg(test)]
265mod test {
266    use crate::properties::{Stretch, Weight};
267
268    #[test]
269    fn test_css_to_core_text_font_weight() {
270        // Exact matches
271        assert_eq!(super::css_to_core_text_font_weight(Weight(100.0)), -0.7);
272        assert_eq!(super::css_to_core_text_font_weight(Weight(400.0)), 0.0);
273        assert_eq!(super::css_to_core_text_font_weight(Weight(700.0)), 0.4);
274        assert_eq!(super::css_to_core_text_font_weight(Weight(900.0)), 0.8);
275
276        // Linear interpolation
277        assert_eq!(super::css_to_core_text_font_weight(Weight(450.0)), 0.1);
278    }
279
280    #[test]
281    fn test_css_to_core_text_font_stretch() {
282        // Exact matches
283        assert_eq!(
284            super::css_stretchiness_to_core_text_width(Stretch(1.0)),
285            0.0
286        );
287        assert_eq!(
288            super::css_stretchiness_to_core_text_width(Stretch(0.5)),
289            -1.0
290        );
291        assert_eq!(
292            super::css_stretchiness_to_core_text_width(Stretch(2.0)),
293            1.0
294        );
295
296        // Linear interpolation
297        assert_eq!(
298            super::css_stretchiness_to_core_text_width(Stretch(1.7)),
299            0.85
300        );
301    }
302}