fonts_traits/
font_identifier.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use malloc_size_of_derive::MallocSizeOf;
6pub use platform::LocalFontIdentifier;
7use serde::{Deserialize, Serialize};
8use servo_url::ServoUrl;
9
10#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
11pub enum FontIdentifier {
12    Local(LocalFontIdentifier),
13    Web(ServoUrl),
14}
15
16impl FontIdentifier {
17    pub fn index(&self) -> u32 {
18        match *self {
19            Self::Local(ref local_font_identifier) => local_font_identifier.index(),
20            Self::Web(_) => 0,
21        }
22    }
23}
24
25#[cfg(any(target_os = "linux", target_os = "android"))]
26mod platform {
27    use std::fs::File;
28    use std::path::{Path, PathBuf};
29
30    use malloc_size_of_derive::MallocSizeOf;
31    use memmap2::Mmap;
32    use serde::{Deserialize, Serialize};
33    use style::Atom;
34    use webrender_api::NativeFontHandle;
35
36    use crate::{FontData, FontDataAndIndex};
37
38    /// An identifier for a local font on systems using Freetype.
39    #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
40    pub struct LocalFontIdentifier {
41        /// The path to the font.
42        pub path: Atom,
43        /// The variation index within the font.
44        pub variation_index: i32,
45    }
46
47    impl LocalFontIdentifier {
48        pub fn index(&self) -> u32 {
49            self.variation_index.try_into().unwrap()
50        }
51
52        pub fn native_font_handle(&self) -> NativeFontHandle {
53            NativeFontHandle {
54                path: PathBuf::from(&*self.path),
55                index: self.variation_index as u32,
56            }
57        }
58
59        #[allow(unsafe_code)]
60        pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
61            let file = File::open(Path::new(&*self.path)).ok()?;
62            let mmap = unsafe { Mmap::map(&file).ok()? };
63            let data = FontData::from_bytes(&mmap);
64
65            Some(FontDataAndIndex {
66                data,
67                index: self.variation_index as u32,
68            })
69        }
70    }
71}
72
73#[cfg(target_os = "macos")]
74mod platform {
75    use std::fs::File;
76    use std::path::Path;
77
78    use log::warn;
79    use malloc_size_of_derive::MallocSizeOf;
80    use memmap2::Mmap;
81    use read_fonts::types::NameId;
82    use read_fonts::{FileRef, TableProvider};
83    use serde::{Deserialize, Serialize};
84    use style::Atom;
85    use webrender_api::NativeFontHandle;
86
87    use crate::{FontData, FontDataAndIndex};
88
89    /// An identifier for a local font on a MacOS system. These values comes from the CoreText
90    /// CTFontCollection. Note that `path` here is required. We do not load fonts that do not
91    /// have paths.
92    #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
93    pub struct LocalFontIdentifier {
94        pub postscript_name: Atom,
95        pub path: Atom,
96    }
97
98    impl LocalFontIdentifier {
99        pub fn native_font_handle(&self) -> NativeFontHandle {
100            NativeFontHandle {
101                name: self.postscript_name.to_string(),
102                path: self.path.to_string(),
103            }
104        }
105
106        pub(crate) fn index(&self) -> u32 {
107            0
108        }
109
110        #[allow(unsafe_code)]
111        pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
112            let file = File::open(Path::new(&*self.path)).ok()?;
113            let mmap = unsafe { Mmap::map(&file).ok()? };
114
115            // Determine index
116            let file_ref = FileRef::new(mmap.as_ref()).ok()?;
117            let index = ttc_index_from_postscript_name(file_ref, &self.postscript_name);
118
119            Some(FontDataAndIndex {
120                data: FontData::from_bytes(&mmap),
121                index,
122            })
123        }
124    }
125
126    /// CoreText font enumeration gives us a Postscript name rather than an index.
127    /// This functions maps from a Postscript name to an index.
128    ///
129    /// This mapping works for single-font files and for simple TTC files, but may not work in all cases.
130    /// We are not 100% sure which cases (if any) will not work. But we suspect that variable fonts may cause
131    /// issues due to the Postscript names corresponding to instances not being straightforward, and the possibility
132    /// that CoreText may return a non-standard in that scenerio.
133    fn ttc_index_from_postscript_name(font_file: FileRef<'_>, postscript_name: &str) -> u32 {
134        match font_file {
135            // File only contains one font: simply return 0
136            FileRef::Font(_) => 0,
137            // File is a collection: iterate through each font in the collection and check
138            // whether the name matches
139            FileRef::Collection(collection) => {
140                for i in 0..collection.len() {
141                    let font = collection.get(i).unwrap();
142                    let name_table = font.name().unwrap();
143                    if name_table
144                        .name_record()
145                        .iter()
146                        .filter(|record| record.name_id() == NameId::POSTSCRIPT_NAME)
147                        .any(|record| {
148                            record
149                                .string(name_table.string_data())
150                                .unwrap()
151                                .chars()
152                                .eq(postscript_name.chars())
153                        })
154                    {
155                        return i;
156                    }
157                }
158
159                // If we fail to find a font, just use the first font in the file.
160                warn!(
161                    "Font with postscript_name {} not found in collection",
162                    postscript_name
163                );
164                0
165            },
166        }
167    }
168}
169
170#[cfg(target_os = "windows")]
171mod platform {
172    use std::hash::Hash;
173    use std::sync::Arc;
174
175    use dwrote::{FontCollection, FontDescriptor};
176    use malloc_size_of_derive::MallocSizeOf;
177    use serde::{Deserialize, Serialize};
178    use webrender_api::NativeFontHandle;
179
180    use crate::{FontData, FontDataAndIndex};
181
182    /// An identifier for a local font on a Windows system.
183    #[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
184    pub struct LocalFontIdentifier {
185        /// The FontDescriptor of this font.
186        #[ignore_malloc_size_of = "dwrote does not support MallocSizeOf"]
187        pub font_descriptor: Arc<FontDescriptor>,
188    }
189
190    impl LocalFontIdentifier {
191        pub fn index(&self) -> u32 {
192            FontCollection::system()
193                .font_from_descriptor(&self.font_descriptor)
194                .ok()
195                .flatten()
196                .map_or(0, |font| font.create_font_face().get_index())
197        }
198
199        pub fn native_font_handle(&self) -> NativeFontHandle {
200            let face = FontCollection::system()
201                .font_from_descriptor(&self.font_descriptor)
202                .ok()
203                .flatten()
204                .expect("Could not create Font from FontDescriptor")
205                .create_font_face();
206            let path = face
207                .files()
208                .ok()
209                .and_then(|files| files.first().cloned())
210                .expect("Could not get FontFace files")
211                .font_file_path()
212                .ok()
213                .expect("Could not get FontFace files path");
214            NativeFontHandle {
215                path,
216                index: face.get_index(),
217            }
218        }
219
220        pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
221            let font = FontCollection::system()
222                .font_from_descriptor(&self.font_descriptor)
223                .ok()??;
224            let face = font.create_font_face();
225            let index = face.get_index();
226            let files = face.files().ok()?;
227            assert!(!files.is_empty());
228
229            let data = files[0].font_file_bytes().ok()?;
230            let data = FontData::from_bytes(&data);
231
232            Some(FontDataAndIndex { data, index })
233        }
234    }
235
236    impl Eq for LocalFontIdentifier {}
237
238    impl Hash for LocalFontIdentifier {
239        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
240            self.font_descriptor.family_name.hash(state);
241            self.font_descriptor.weight.to_u32().hash(state);
242            self.font_descriptor.stretch.to_u32().hash(state);
243            self.font_descriptor.style.to_u32().hash(state);
244        }
245    }
246}