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