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