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