Skip to main content

fonts/platform/freetype/
freetype_face.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 std::ffi::c_long;
6use std::fmt::Debug;
7use std::ptr;
8
9use app_units::Au;
10use fonts_traits::FontData;
11use freetype_sys::{
12    FT_Done_Face, FT_Done_MM_Var, FT_F26Dot6, FT_FACE_FLAG_COLOR, FT_FACE_FLAG_FIXED_SIZES,
13    FT_FACE_FLAG_SCALABLE, FT_Face, FT_FaceRec, FT_Fixed, FT_Get_MM_Var, FT_HAS_MULTIPLE_MASTERS,
14    FT_Int32, FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_TARGET_LIGHT, FT_Long, FT_MM_Var,
15    FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_Set_Var_Design_Coordinates,
16    FTErrorMethods,
17};
18use memmap2::Mmap;
19use servo_arc::Arc;
20use webrender_api::FontVariation;
21
22use crate::platform::freetype::library_handle::FreeTypeLibraryHandle;
23
24/// A safe wrapper around [FT_Face].
25#[derive(Debug)]
26pub(crate) struct FreeTypeFace {
27    /// ## Safety Invariant
28    /// The pointer must have been returned from [FT_New_Memory_Face]
29    /// backed by `_data`.
30    face: ptr::NonNull<FT_FaceRec>,
31    _data: FontBackingStore,
32}
33
34pub(crate) enum FontBackingStore {
35    Web(FontData),
36    /// Memory-mapped file of a system font.
37    Local(Arc<Mmap>),
38}
39
40impl AsRef<[u8]> for FontBackingStore {
41    fn as_ref(&self) -> &[u8] {
42        match self {
43            Self::Web(font_data) => font_data.as_ref(),
44            Self::Local(mmap) => mmap.as_ref(),
45        }
46    }
47}
48
49impl Debug for FontBackingStore {
50    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
51        f.debug_struct("FontBackingStore")
52            .field("len", &self.as_ref().len())
53            .finish()
54    }
55}
56
57impl FreeTypeFace {
58    pub(crate) fn new_from_memory(
59        library: &FreeTypeLibraryHandle,
60        font_backing_store: FontBackingStore,
61        face_index: u32,
62    ) -> Result<Self, &'static str> {
63        let mut face = ptr::null_mut();
64        let data = font_backing_store.as_ref();
65        // SAFETY: By storing the font_backing_store in Self below, we ensure that the
66        // data referenced by the face created here is kept alive for the duration of
67        // this instance. Freetype will not mutate this memory.
68        let result = unsafe {
69            FT_New_Memory_Face(
70                library.freetype_library,
71                data.as_ptr(),
72                data.len() as FT_Long,
73                face_index as FT_Long,
74                &mut face,
75            )
76        };
77
78        if 0 != result {
79            return Err("Could not create FreeType face");
80        }
81        let Some(face) = ptr::NonNull::new(face) else {
82            return Err("Could not create FreeType face");
83        };
84
85        Ok(Self {
86            face,
87            _data: font_backing_store,
88        })
89    }
90
91    pub(crate) fn as_ref(&self) -> &FT_FaceRec {
92        unsafe { self.face.as_ref() }
93    }
94
95    pub(crate) fn as_ptr(&self) -> FT_Face {
96        self.face.as_ptr()
97    }
98
99    /// Return true iff the font face flags contain [FT_FACE_FLAG_SCALABLE].
100    pub(crate) fn scalable(&self) -> bool {
101        self.as_ref().face_flags & FT_FACE_FLAG_SCALABLE as c_long != 0
102    }
103
104    /// Return true iff the font face flags contain [FT_FACE_FLAG_COLOR].
105    pub(crate) fn color(&self) -> bool {
106        self.as_ref().face_flags & FT_FACE_FLAG_COLOR as c_long != 0
107    }
108
109    /// Scale the font to the given size if it is scalable, or select the closest
110    /// available size if it is not, preferring larger sizes over smaller ones.
111    ///
112    /// Returns the selected size on success and a error message on failure
113    pub(crate) fn set_size(&self, requested_size: Au) -> Result<Au, &'static str> {
114        if self.scalable() {
115            let size_in_fixed_point = (requested_size.to_f64_px() * 64.0 + 0.5) as FT_F26Dot6;
116            let result =
117                unsafe { FT_Set_Char_Size(self.face.as_ptr(), size_in_fixed_point, 0, 72, 72) };
118            if 0 != result {
119                return Err("FT_Set_Char_Size failed");
120            }
121            return Ok(requested_size);
122        }
123
124        let face = self.as_ref();
125        if face.num_fixed_sizes <= 0 || face.available_sizes.is_null() {
126            return Err("No fixed sizes available");
127        }
128
129        let requested_size = (requested_size.to_f64_px() * 64.0) as FT_Pos;
130        let get_size_at_index = |index| {
131            assert!(index < face.num_fixed_sizes);
132            // SAFETY: We checked `available_sizes` is not NULL and that index is in bounds.
133            unsafe {
134                (
135                    (*face.available_sizes.offset(index as isize)).x_ppem,
136                    (*face.available_sizes.offset(index as isize)).y_ppem,
137                )
138            }
139        };
140
141        let mut best_index = 0;
142        let mut best_size = get_size_at_index(0);
143        let mut best_dist = best_size.1 - requested_size;
144        for strike_index in 1..face.num_fixed_sizes {
145            let new_scale = get_size_at_index(strike_index);
146            let new_distance = new_scale.1 - requested_size;
147
148            // Distance is positive if strike is larger than desired size,
149            // or negative if smaller. If previously a found smaller strike,
150            // then prefer a larger strike. Otherwise, minimize distance.
151            if (best_dist < 0 && new_distance >= best_dist) || new_distance.abs() <= best_dist {
152                best_dist = new_distance;
153                best_size = new_scale;
154                best_index = strike_index;
155            }
156        }
157
158        if 0 == unsafe { FT_Select_Size(self.face.as_ptr(), best_index) } {
159            Ok(Au::from_f64_px(best_size.1 as f64 / 64.0))
160        } else {
161            Err("FT_Select_Size failed")
162        }
163    }
164
165    /// Select a reasonable set of glyph loading flags for the font.
166    pub(crate) fn glyph_load_flags(&self) -> FT_Int32 {
167        let mut load_flags = FT_LOAD_DEFAULT;
168
169        // Default to slight hinting, which is what most
170        // Linux distros use by default, and is a better
171        // default than no hinting.
172        // TODO(gw): Make this configurable.
173        load_flags |= FT_LOAD_TARGET_LIGHT;
174
175        let face_flags = self.as_ref().face_flags;
176        if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 {
177            // We only set FT_LOAD_COLOR if there are bitmap strikes; COLR (color-layer) fonts
178            // will be handled internally in Servo. In that case WebRender will just be asked to
179            // paint individual layers.
180            load_flags |= FT_LOAD_COLOR;
181        }
182
183        load_flags as FT_Int32
184    }
185
186    /// Applies to provided variations to the font face.
187    ///
188    /// Returns the normalized font variations, which are clamped
189    /// to fit within the range of their respective axis. Variation
190    /// values for nonexistent axes are not included.
191    pub(crate) fn set_variations_for_font(
192        &self,
193        variations: &[FontVariation],
194        library: &FreeTypeLibraryHandle,
195    ) -> Result<Vec<FontVariation>, &'static str> {
196        if !unsafe { FT_HAS_MULTIPLE_MASTERS(self.as_ptr()) } ||
197            variations.is_empty() ||
198            !servo_config::pref!(layout_variable_fonts_enabled)
199        {
200            // Nothing to do
201            return Ok(vec![]);
202        }
203
204        // Query variation axis of font
205        let mut mm_var: *mut FT_MM_Var = ptr::null_mut();
206        let result = unsafe { FT_Get_MM_Var(self.as_ptr(), &mut mm_var as *mut _) };
207        if !result.succeeded() {
208            return Err("Failed to query font variations");
209        }
210
211        // Prepare values for each axis. These are either the provided values (if any) or the default
212        // ones for the axis.
213        let num_axis = unsafe { (*mm_var).num_axis } as usize;
214        let mut normalized_axis_values = Vec::with_capacity(variations.len());
215        let mut coords = vec![0; num_axis];
216        for (index, coord) in coords.iter_mut().enumerate() {
217            let axis_data = unsafe { &*(*mm_var).axis.add(index) };
218            let Some(variation) = variations
219                .iter()
220                .find(|variation| variation.tag == axis_data.tag as u32)
221            else {
222                *coord = axis_data.def;
223                continue;
224            };
225
226            // Freetype uses a 16.16 fixed point format for variation values
227            let shift_factor = 16.0_f32.exp2();
228            let min_value = axis_data.minimum as f32 / shift_factor;
229            let max_value = axis_data.maximum as f32 / shift_factor;
230            normalized_axis_values.push(FontVariation {
231                tag: variation.tag,
232                value: variation.value.min(max_value).max(min_value),
233            });
234
235            *coord = (variation.value * shift_factor) as FT_Fixed;
236        }
237
238        // Free the MM_Var structure
239        unsafe {
240            FT_Done_MM_Var(library.freetype_library, mm_var);
241        }
242
243        // Set the values for each variation axis
244        let result = unsafe {
245            FT_Set_Var_Design_Coordinates(self.as_ptr(), coords.len() as u32, coords.as_ptr())
246        };
247        if !result.succeeded() {
248            return Err("Could not set variations for font face");
249        }
250
251        Ok(normalized_axis_values)
252    }
253}
254
255/// FT_Face can be used in multiple threads, but from only one thread at a time.
256/// See <https://freetype.org/freetype2/docs/reference/ft2-face_creation.html#ft_face>.
257unsafe impl Send for FreeTypeFace {}
258
259impl Drop for FreeTypeFace {
260    fn drop(&mut self) {
261        // The FreeType documentation says that both `FT_New_Face` and `FT_Done_Face`
262        // should be protected by a mutex.
263        // See https://freetype.org/freetype2/docs/reference/ft2-library_setup.html.
264        let result_code = {
265            let _guard = FreeTypeLibraryHandle::get().lock();
266            // SAFETY: This is the same pointer we allocated with, and we kept the
267            // underlying memory alive via Self._data.
268            unsafe { FT_Done_Face(self.face.as_ptr()) }
269        };
270        if result_code != 0 {
271            log::error!("FT_Done_Face failed: {result_code}");
272        }
273    }
274}