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 face_index: u16,
45        /// The index of the named instance within the font.
46        ///
47        /// For non-variable fonts, this is ignored.
48        pub named_instance_index: u16,
49    }
50
51    impl LocalFontIdentifier {
52        pub fn index(&self) -> u32 {
53            self.face_index as u32
54        }
55
56        pub fn named_instance_index(&self) -> u32 {
57            self.named_instance_index as u32
58        }
59
60        pub fn native_font_handle(&self) -> NativeFontHandle {
61            NativeFontHandle {
62                path: PathBuf::from(&*self.path),
63                index: self.face_index as u32,
64            }
65        }
66
67        #[allow(unsafe_code)]
68        pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
69            let file = File::open(Path::new(&*self.path)).ok()?;
70            let mmap = unsafe { Mmap::map(&file).ok()? };
71            let data = FontData::from_bytes(&mmap);
72
73            Some(FontDataAndIndex {
74                data,
75                index: self.face_index as u32,
76            })
77        }
78
79        /// Fontconfig and FreeType use a packed format to represent face and
80        /// named instance indexes in a single integer. The first 16 bits make
81        /// up the named instance index and the second 16 bits make up the
82        /// face index.
83        ///
84        /// See <https://freetype.org/freetype2/docs/reference/ft2-face_creation.html#ft_open_face>
85        /// for more information.
86        pub fn face_index_for_freetype(&self) -> u32 {
87            ((self.named_instance_index()) << 16) | self.index()
88        }
89    }
90}
91
92#[cfg(target_os = "macos")]
93mod platform {
94    use std::fs::File;
95    use std::path::Path;
96
97    use log::warn;
98    use malloc_size_of_derive::MallocSizeOf;
99    use memmap2::Mmap;
100    use read_fonts::types::NameId;
101    use read_fonts::{FileRef, TableProvider};
102    use serde::{Deserialize, Serialize};
103    use style::Atom;
104    use webrender_api::NativeFontHandle;
105
106    use crate::{FontData, FontDataAndIndex};
107
108    /// An identifier for a local font on a MacOS system. These values comes from the CoreText
109    /// CTFontCollection. Note that `path` here is required. We do not load fonts that do not
110    /// have paths.
111    #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
112    pub struct LocalFontIdentifier {
113        pub postscript_name: Atom,
114        pub path: Atom,
115    }
116
117    impl LocalFontIdentifier {
118        pub fn native_font_handle(&self) -> NativeFontHandle {
119            NativeFontHandle {
120                name: self.postscript_name.to_string(),
121                path: self.path.to_string(),
122            }
123        }
124
125        pub(crate) fn index(&self) -> u32 {
126            0
127        }
128
129        #[allow(unsafe_code)]
130        pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
131            let file = File::open(Path::new(&*self.path)).ok()?;
132            let mmap = unsafe { Mmap::map(&file).ok()? };
133
134            // Determine index
135            let file_ref = FileRef::new(mmap.as_ref()).ok()?;
136            let index = ttc_index_from_postscript_name(file_ref, &self.postscript_name);
137
138            Some(FontDataAndIndex {
139                data: FontData::from_bytes(&mmap),
140                index,
141            })
142        }
143    }
144
145    /// CoreText font enumeration gives us a Postscript name rather than an index.
146    /// This functions maps from a Postscript name to an index.
147    ///
148    /// This mapping works for single-font files and for simple TTC files, but may not work in all cases.
149    /// We are not 100% sure which cases (if any) will not work. But we suspect that variable fonts may cause
150    /// issues due to the Postscript names corresponding to instances not being straightforward, and the possibility
151    /// that CoreText may return a non-standard in that scenerio.
152    fn ttc_index_from_postscript_name(font_file: FileRef<'_>, postscript_name: &str) -> u32 {
153        match font_file {
154            // File only contains one font: simply return 0
155            FileRef::Font(_) => 0,
156            // File is a collection: iterate through each font in the collection and check
157            // whether the name matches
158            FileRef::Collection(collection) => {
159                for i in 0..collection.len() {
160                    let font = collection.get(i).unwrap();
161                    let name_table = font.name().unwrap();
162                    if name_table
163                        .name_record()
164                        .iter()
165                        .filter(|record| record.name_id() == NameId::POSTSCRIPT_NAME)
166                        .any(|record| {
167                            record
168                                .string(name_table.string_data())
169                                .unwrap()
170                                .chars()
171                                .eq(postscript_name.chars())
172                        })
173                    {
174                        return i;
175                    }
176                }
177
178                // If we fail to find a font, just use the first font in the file.
179                warn!(
180                    "Font with postscript_name {} not found in collection",
181                    postscript_name
182                );
183                0
184            },
185        }
186    }
187}
188
189#[cfg(target_os = "windows")]
190mod platform {
191    use std::hash::Hash;
192    use std::sync::Arc;
193
194    use dwrote::{FontCollection, FontDescriptor};
195    use malloc_size_of_derive::MallocSizeOf;
196    use serde::{Deserialize, Serialize};
197    use webrender_api::NativeFontHandle;
198
199    use crate::{FontData, FontDataAndIndex};
200
201    /// An identifier for a local font on a Windows system.
202    #[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
203    pub struct LocalFontIdentifier {
204        /// The FontDescriptor of this font.
205        #[ignore_malloc_size_of = "dwrote does not support MallocSizeOf"]
206        pub font_descriptor: Arc<FontDescriptor>,
207    }
208
209    impl LocalFontIdentifier {
210        pub fn index(&self) -> u32 {
211            FontCollection::system()
212                .font_from_descriptor(&self.font_descriptor)
213                .ok()
214                .flatten()
215                .map_or(0, |font| font.create_font_face().get_index())
216        }
217
218        pub fn native_font_handle(&self) -> NativeFontHandle {
219            let face = FontCollection::system()
220                .font_from_descriptor(&self.font_descriptor)
221                .ok()
222                .flatten()
223                .expect("Could not create Font from FontDescriptor")
224                .create_font_face();
225            let path = face
226                .files()
227                .ok()
228                .and_then(|files| files.first().cloned())
229                .expect("Could not get FontFace files")
230                .font_file_path()
231                .ok()
232                .expect("Could not get FontFace files path");
233            NativeFontHandle {
234                path,
235                index: face.get_index(),
236            }
237        }
238
239        pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
240            let font = FontCollection::system()
241                .font_from_descriptor(&self.font_descriptor)
242                .ok()??;
243            let face = font.create_font_face();
244            let index = face.get_index();
245            let files = face.files().ok()?;
246            assert!(!files.is_empty());
247
248            let data = files[0].font_file_bytes().ok()?;
249            let data = FontData::from_bytes(&data);
250
251            Some(FontDataAndIndex { data, index })
252        }
253    }
254
255    impl Eq for LocalFontIdentifier {}
256
257    impl Hash for LocalFontIdentifier {
258        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
259            self.font_descriptor.family_name.hash(state);
260            self.font_descriptor.weight.to_u32().hash(state);
261            self.font_descriptor.stretch.to_u32().hash(state);
262            self.font_descriptor.style.to_u32().hash(state);
263        }
264    }
265}