Skip to main content

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