diff --git a/components/fonts/platform/freetype/font.rs b/components/fonts/platform/freetype/font.rs index 82fa19cdd17..2d721183ab6 100644 --- a/components/fonts/platform/freetype/font.rs +++ b/components/fonts/platform/freetype/font.rs @@ -4,17 +4,13 @@ use std::ffi::CString; use std::fs::File; -use std::os::raw::c_long; -use std::{mem, ptr}; use app_units::Au; use euclid::default::{Point2D, Rect, Size2D}; use freetype_sys::{ - FT_Done_Face, FT_F26Dot6, FT_FACE_FLAG_COLOR, FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE, - FT_Face, FT_Get_Char_Index, FT_Get_Kerning, FT_GlyphSlot, FT_Int32, FT_KERNING_DEFAULT, - FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_NO_HINTING, FT_Load_Glyph, FT_Long, FT_New_Face, - FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_Size_Metrics, FT_SizeRec, - FT_UInt, FT_ULong, FT_Vector, + FT_F26Dot6, FT_Get_Char_Index, FT_Get_Kerning, FT_GlyphSlot, FT_KERNING_DEFAULT, + FT_LOAD_DEFAULT, FT_LOAD_NO_HINTING, FT_Load_Glyph, FT_Size_Metrics, FT_SizeRec, FT_UInt, + FT_ULong, FT_Vector, }; use log::debug; use memmap2::Mmap; @@ -37,13 +33,9 @@ use crate::font::{ }; use crate::font_template::FontTemplateDescriptor; use crate::glyph::GlyphId; +use crate::platform::freetype::freetype_face::FreeTypeFace; use crate::system_font_service::FontIdentifier; -// This constant is not present in the freetype -// bindings due to bindgen not handling the way -// the macro is defined. -const FT_LOAD_TARGET_LIGHT: FT_UInt = 1 << 16; - /// Convert FreeType-style 26.6 fixed point to an [`f64`]. fn fixed_26_dot_6_to_float(fixed: FT_F26Dot6) -> f64 { fixed as f64 / 64.0 @@ -68,7 +60,7 @@ impl FontTableMethods for FontTable { #[derive(Debug)] #[allow(unused)] pub struct PlatformFont { - face: ReentrantMutex, + face: ReentrantMutex, requested_face_size: Au, actual_face_size: Au, @@ -76,28 +68,6 @@ pub struct PlatformFont { table_provider_data: FreeTypeFaceTableProviderData, } -// FT_Face can be used in multiple threads, but from only one thread at a time. -// It's protected with a ReentrantMutex for PlatformFont. -// See https://freetype.org/freetype2/docs/reference/ft2-face_creation.html#ft_face. -unsafe impl Sync for PlatformFont {} -unsafe impl Send for PlatformFont {} - -impl Drop for PlatformFont { - fn drop(&mut self) { - let face = self.face.lock(); - assert!(!face.is_null()); - unsafe { - // The FreeType documentation says that both `FT_New_Face` and `FT_Done_Face` - // should be protected by a mutex. - // See https://freetype.org/freetype2/docs/reference/ft2-library_setup.html. - let _guard = FreeTypeLibraryHandle::get().lock(); - if FT_Done_Face(*face) != 0 { - panic!("FT_Done_Face failed"); - } - } - } -} - impl PlatformFontMethods for PlatformFont { fn new_from_data( _font_identifier: FontIdentifier, @@ -106,20 +76,7 @@ impl PlatformFontMethods for PlatformFont { ) -> Result { let library = FreeTypeLibraryHandle::get().lock(); let data: &[u8] = font_data.as_ref(); - let mut face: FT_Face = ptr::null_mut(); - let result = unsafe { - FT_New_Memory_Face( - library.freetype_library, - data.as_ptr(), - data.len() as FT_Long, - 0, /* face_index */ - &mut face, - ) - }; - - if 0 != result || face.is_null() { - return Err("Could not create FreeType face"); - } + let face = FreeTypeFace::new_from_memory(&library, data)?; let (requested_face_size, actual_face_size) = match requested_size { Some(requested_size) => (requested_size, face.set_size(requested_size)?), @@ -138,22 +95,10 @@ impl PlatformFontMethods for PlatformFont { font_identifier: LocalFontIdentifier, requested_size: Option, ) -> Result { - let mut face: FT_Face = ptr::null_mut(); let library = FreeTypeLibraryHandle::get().lock(); let filename = CString::new(&*font_identifier.path).expect("filename contains NUL byte!"); - let result = unsafe { - FT_New_Face( - library.freetype_library, - filename.as_ptr(), - font_identifier.index() as FT_Long, - &mut face, - ) - }; - - if 0 != result || face.is_null() { - return Err("Could not create FreeType face"); - } + let face = FreeTypeFace::new_from_file(&library, &filename, font_identifier.index())?; let (requested_face_size, actual_face_size) = match requested_size { Some(requested_size) => (requested_size, face.set_size(requested_size)?), @@ -210,10 +155,9 @@ impl PlatformFontMethods for PlatformFont { fn glyph_index(&self, codepoint: char) -> Option { let face = self.face.lock(); - assert!(!face.is_null()); unsafe { - let idx = FT_Get_Char_Index(*face, codepoint as FT_ULong); + let idx = FT_Get_Char_Index(face.as_ptr(), codepoint as FT_ULong); if idx != 0 as FT_UInt { Some(idx as GlyphId) } else { @@ -228,12 +172,11 @@ impl PlatformFontMethods for PlatformFont { fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel { let face = self.face.lock(); - assert!(!face.is_null()); let mut delta = FT_Vector { x: 0, y: 0 }; unsafe { FT_Get_Kerning( - *face, + face.as_ptr(), first_glyph, second_glyph, FT_KERNING_DEFAULT, @@ -245,16 +188,15 @@ impl PlatformFontMethods for PlatformFont { fn glyph_h_advance(&self, glyph: GlyphId) -> Option { let face = self.face.lock(); - assert!(!face.is_null()); let load_flags = face.glyph_load_flags(); - let result = unsafe { FT_Load_Glyph(*face, glyph as FT_UInt, load_flags) }; + let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph as FT_UInt, load_flags) }; if 0 != result { debug!("Unable to load glyph {}. reason: {:?}", glyph, result); return None; } - let void_glyph = unsafe { (**face).glyph }; + let void_glyph = face.as_ref().glyph; let slot: FT_GlyphSlot = void_glyph; assert!(!slot.is_null()); @@ -263,12 +205,11 @@ impl PlatformFontMethods for PlatformFont { } fn metrics(&self) -> FontMetrics { - let face_ptr = *self.face.lock(); - let face = unsafe { &*face_ptr }; + let face = self.face.lock(); let font_ref = self.table_provider_data.font_ref(); // face.size is a *c_void in the bindings, presumably to avoid recursive structural types - let freetype_size: &FT_SizeRec = unsafe { mem::transmute(&(*face.size)) }; + let freetype_size: &FT_SizeRec = unsafe { &*face.as_ref().size }; let freetype_metrics: &FT_Size_Metrics = &(freetype_size).metrics; let mut max_advance; @@ -277,7 +218,7 @@ impl PlatformFontMethods for PlatformFont { let mut line_height; let mut y_scale = 0.0; let mut em_height; - if face_ptr.scalable() { + if face.scalable() { // Prefer FT_Size_Metrics::y_scale to y_ppem as y_ppem does not have subpixel accuracy. // // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its (fractional) value is a @@ -287,11 +228,11 @@ impl PlatformFontMethods for PlatformFont { // This converts the value to a float without losing precision. y_scale = freetype_metrics.y_scale as f64 / 65535.0 / 64.0; - max_advance = (face.max_advance_width as f64) * y_scale; - max_ascent = (face.ascender as f64) * y_scale; - max_descent = -(face.descender as f64) * y_scale; - line_height = (face.height as f64) * y_scale; - em_height = (face.units_per_EM as f64) * y_scale; + max_advance = (face.as_ref().max_advance_width as f64) * y_scale; + max_ascent = (face.as_ref().ascender as f64) * y_scale; + max_descent = -(face.as_ref().descender as f64) * y_scale; + line_height = (face.as_ref().height as f64) * y_scale; + em_height = (face.as_ref().units_per_EM as f64) * y_scale; } else { max_advance = fixed_26_dot_6_to_float(freetype_metrics.max_advance); max_ascent = fixed_26_dot_6_to_float(freetype_metrics.ascender); @@ -307,7 +248,7 @@ impl PlatformFontMethods for PlatformFont { // Bug 1267909 - Even if the font is not explicitly scalable, if the face has color // bitmaps, it should be treated as scalable and scaled to the desired size. Metrics // based on y_ppem need to be rescaled for the adjusted size. - if face_ptr.color() { + if face.color() { em_height = self.requested_face_size.to_f64_px(); let adjust_scale = em_height / (freetype_metrics.y_ppem as f64); max_advance *= adjust_scale; @@ -327,8 +268,8 @@ impl PlatformFontMethods for PlatformFont { // Convert using a formula similar to what CTFont returns for consistency. let leading = line_height - (max_ascent + max_descent); - let underline_size = face.underline_thickness as f64 * y_scale; - let underline_offset = face.underline_position as f64 * y_scale + 0.5; + let underline_size = face.as_ref().underline_thickness as f64 * y_scale; + let underline_offset = face.as_ref().underline_position as f64 * y_scale + 0.5; // The default values for strikeout size and offset. Use OpenType spec's suggested position // for Roman font as the default for offset. @@ -411,16 +352,15 @@ impl PlatformFontMethods for PlatformFont { fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect { let face = self.face.lock(); - assert!(!face.is_null()); let load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING; - let result = unsafe { FT_Load_Glyph(*face, glyph_id as FT_UInt, load_flags) }; + let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph_id as FT_UInt, load_flags) }; if 0 != result { debug!("Unable to load glyph {}. reason: {:?}", glyph_id, result); return Rect::default(); } - let metrics = unsafe { &(*(**face).glyph).metrics }; + let metrics = unsafe { &(*face.as_ref().glyph).metrics }; Rect::new( Point2D::new( @@ -448,85 +388,6 @@ impl PlatformFont { } } -trait FreeTypeFaceHelpers { - fn scalable(self) -> bool; - fn color(self) -> bool; - fn set_size(self, pt_size: Au) -> Result; - fn glyph_load_flags(self) -> FT_Int32; -} - -impl FreeTypeFaceHelpers for FT_Face { - fn scalable(self) -> bool { - unsafe { (*self).face_flags & FT_FACE_FLAG_SCALABLE as c_long != 0 } - } - - fn color(self) -> bool { - unsafe { (*self).face_flags & FT_FACE_FLAG_COLOR as c_long != 0 } - } - - fn set_size(self, requested_size: Au) -> Result { - if self.scalable() { - let size_in_fixed_point = (requested_size.to_f64_px() * 64.0 + 0.5) as FT_F26Dot6; - let result = unsafe { FT_Set_Char_Size(self, size_in_fixed_point, 0, 72, 72) }; - if 0 != result { - return Err("FT_Set_Char_Size failed"); - } - return Ok(requested_size); - } - - let requested_size = (requested_size.to_f64_px() * 64.0) as FT_Pos; - let get_size_at_index = |index| unsafe { - ( - (*(*self).available_sizes.offset(index as isize)).x_ppem, - (*(*self).available_sizes.offset(index as isize)).y_ppem, - ) - }; - - let mut best_index = 0; - let mut best_size = get_size_at_index(0); - let mut best_dist = best_size.1 - requested_size; - for strike_index in 1..unsafe { (*self).num_fixed_sizes } { - let new_scale = get_size_at_index(strike_index); - let new_distance = new_scale.1 - requested_size; - - // Distance is positive if strike is larger than desired size, - // or negative if smaller. If previously a found smaller strike, - // then prefer a larger strike. Otherwise, minimize distance. - if (best_dist < 0 && new_distance >= best_dist) || new_distance.abs() <= best_dist { - best_dist = new_distance; - best_size = new_scale; - best_index = strike_index; - } - } - - if 0 == unsafe { FT_Select_Size(self, best_index) } { - Ok(Au::from_f64_px(best_size.1 as f64 / 64.0)) - } else { - Err("FT_Select_Size failed") - } - } - - fn glyph_load_flags(self) -> FT_Int32 { - let mut load_flags = FT_LOAD_DEFAULT; - - // Default to slight hinting, which is what most - // Linux distros use by default, and is a better - // default than no hinting. - // TODO(gw): Make this configurable. - load_flags |= FT_LOAD_TARGET_LIGHT as i32; - - let face_flags = unsafe { (*self).face_flags }; - if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 { - // We only set FT_LOAD_COLOR if there are bitmap strikes; COLR (color-layer) fonts - // will be handled internally in Servo. In that case WebRender will just be asked to - // paint individual layers. - load_flags |= FT_LOAD_COLOR; - } - - load_flags as FT_Int32 - } -} - #[derive(Clone)] enum FreeTypeFaceTableProviderData { Web(FontData), diff --git a/components/fonts/platform/freetype/freetype_face.rs b/components/fonts/platform/freetype/freetype_face.rs new file mode 100644 index 00000000000..3683a062ba0 --- /dev/null +++ b/components/fonts/platform/freetype/freetype_face.rs @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::ffi::{CStr, c_long}; +use std::ptr; + +use app_units::Au; +use freetype_sys::{ + FT_Done_Face, FT_F26Dot6, FT_FACE_FLAG_COLOR, FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE, + FT_Face, FT_FaceRec, FT_Int32, FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_Long, FT_New_Face, + FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_UInt, +}; + +use crate::platform::freetype::library_handle::FreeTypeLibraryHandle; + +// This constant is not present in the freetype +// bindings due to bindgen not handling the way +// the macro is defined. +const FT_LOAD_TARGET_LIGHT: FT_UInt = 1 << 16; + +/// A safe wrapper around [FT_Face]. +#[derive(Debug)] +pub(crate) struct FreeTypeFace { + /// ## Safety Invariant + /// The pointer must have been returned from [FT_New_Face] or [FT_New_Memory_Face] + /// and must not be freed before `FreetypeFace::drop` is called. + face: ptr::NonNull, +} + +impl FreeTypeFace { + pub(crate) fn new_from_memory( + library: &FreeTypeLibraryHandle, + data: &[u8], + ) -> Result { + let mut face = ptr::null_mut(); + let result = unsafe { + FT_New_Memory_Face( + library.freetype_library, + data.as_ptr(), + data.len() as FT_Long, + 0, + &mut face, + ) + }; + + if 0 != result { + return Err("Could not create FreeType face"); + } + let Some(face) = ptr::NonNull::new(face) else { + return Err("Could not create FreeType face"); + }; + + Ok(Self { face }) + } + + pub(crate) fn new_from_file( + library: &FreeTypeLibraryHandle, + filename: &CStr, + index: u32, + ) -> Result { + let mut face = ptr::null_mut(); + let result = unsafe { + FT_New_Face( + library.freetype_library, + filename.as_ptr(), + index as FT_Long, + &mut face, + ) + }; + + if 0 != result { + return Err("Could not create FreeType face"); + } + let Some(face) = ptr::NonNull::new(face) else { + return Err("Could not create FreeType face"); + }; + + Ok(Self { face }) + } + + pub(crate) fn as_ref(&self) -> &FT_FaceRec { + unsafe { self.face.as_ref() } + } + + pub(crate) fn as_ptr(&self) -> FT_Face { + self.face.as_ptr() + } + + /// Return true iff the font face flags contain [FT_FACE_FLAG_SCALABLE]. + pub(crate) fn scalable(&self) -> bool { + self.as_ref().face_flags & FT_FACE_FLAG_SCALABLE as c_long != 0 + } + + /// Return true iff the font face flags contain [FT_FACE_FLAG_COLOR]. + pub(crate) fn color(&self) -> bool { + self.as_ref().face_flags & FT_FACE_FLAG_COLOR as c_long != 0 + } + + /// Scale the font to the given size if it is scalable, or select the closest + /// available size if it is not, preferring larger sizes over smaller ones. + /// + /// Returns the selected size on success and a error message on failure + pub(crate) fn set_size(&self, requested_size: Au) -> Result { + if self.scalable() { + let size_in_fixed_point = (requested_size.to_f64_px() * 64.0 + 0.5) as FT_F26Dot6; + let result = + unsafe { FT_Set_Char_Size(self.face.as_ptr(), size_in_fixed_point, 0, 72, 72) }; + if 0 != result { + return Err("FT_Set_Char_Size failed"); + } + return Ok(requested_size); + } + + let requested_size = (requested_size.to_f64_px() * 64.0) as FT_Pos; + let get_size_at_index = |index| unsafe { + ( + (*self.as_ref().available_sizes.offset(index as isize)).x_ppem, + (*self.as_ref().available_sizes.offset(index as isize)).y_ppem, + ) + }; + + let mut best_index = 0; + let mut best_size = get_size_at_index(0); + let mut best_dist = best_size.1 - requested_size; + for strike_index in 1..self.as_ref().num_fixed_sizes { + let new_scale = get_size_at_index(strike_index); + let new_distance = new_scale.1 - requested_size; + + // Distance is positive if strike is larger than desired size, + // or negative if smaller. If previously a found smaller strike, + // then prefer a larger strike. Otherwise, minimize distance. + if (best_dist < 0 && new_distance >= best_dist) || new_distance.abs() <= best_dist { + best_dist = new_distance; + best_size = new_scale; + best_index = strike_index; + } + } + + if 0 == unsafe { FT_Select_Size(self.face.as_ptr(), best_index) } { + Ok(Au::from_f64_px(best_size.1 as f64 / 64.0)) + } else { + Err("FT_Select_Size failed") + } + } + + /// Select a reasonable set of glyph loading flags for the font. + pub(crate) fn glyph_load_flags(&self) -> FT_Int32 { + let mut load_flags = FT_LOAD_DEFAULT; + + // Default to slight hinting, which is what most + // Linux distros use by default, and is a better + // default than no hinting. + // TODO(gw): Make this configurable. + load_flags |= FT_LOAD_TARGET_LIGHT as i32; + + let face_flags = self.as_ref().face_flags; + if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 { + // We only set FT_LOAD_COLOR if there are bitmap strikes; COLR (color-layer) fonts + // will be handled internally in Servo. In that case WebRender will just be asked to + // paint individual layers. + load_flags |= FT_LOAD_COLOR; + } + + load_flags as FT_Int32 + } +} + +/// FT_Face can be used in multiple threads, but from only one thread at a time. +/// See . +unsafe impl Send for FreeTypeFace {} + +impl Drop for FreeTypeFace { + fn drop(&mut self) { + // The FreeType documentation says that both `FT_New_Face` and `FT_Done_Face` + // should be protected by a mutex. + // See https://freetype.org/freetype2/docs/reference/ft2-library_setup.html. + let _guard = FreeTypeLibraryHandle::get().lock(); + if unsafe { FT_Done_Face(self.face.as_ptr()) } != 0 { + panic!("FT_Done_Face failed"); + } + } +} diff --git a/components/fonts/platform/freetype/library_handle.rs b/components/fonts/platform/freetype/library_handle.rs index 4c162cfac96..caadc280941 100644 --- a/components/fonts/platform/freetype/library_handle.rs +++ b/components/fonts/platform/freetype/library_handle.rs @@ -11,12 +11,12 @@ use freetype_sys::{ FT_Add_Default_Modules, FT_Done_Library, FT_Library, FT_Memory, FT_MemoryRec, FT_New_Library, }; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use parking_lot::Mutex; +use parking_lot::ReentrantMutex; use servo_allocator::libc_compat::{free, malloc, realloc}; use servo_allocator::usable_size; static FREETYPE_MEMORY_USAGE: AtomicUsize = AtomicUsize::new(0); -static FREETYPE_LIBRARY_HANDLE: OnceLock> = OnceLock::new(); +static FREETYPE_LIBRARY_HANDLE: OnceLock> = OnceLock::new(); extern "C" fn ft_alloc(_: FT_Memory, req_size: c_long) -> *mut c_void { unsafe { @@ -90,7 +90,7 @@ impl FreeTypeLibraryHandle { /// > also, as long as a mutex lock is used around FT_New_Face and FT_Done_Face. /// /// See . - pub(crate) fn get() -> &'static Mutex { + pub(crate) fn get() -> &'static ReentrantMutex { FREETYPE_LIBRARY_HANDLE.get_or_init(|| { let freetype_memory = Box::into_raw(Box::new(FT_MemoryRec { user: ptr::null_mut(), @@ -105,7 +105,7 @@ impl FreeTypeLibraryHandle { panic!("Unable to initialize FreeType library"); } FT_Add_Default_Modules(freetype_library); - Mutex::new(FreeTypeLibraryHandle { + ReentrantMutex::new(FreeTypeLibraryHandle { freetype_library, freetype_memory, }) diff --git a/components/fonts/platform/freetype/mod.rs b/components/fonts/platform/freetype/mod.rs index c507cf08184..feea468008e 100644 --- a/components/fonts/platform/freetype/mod.rs +++ b/components/fonts/platform/freetype/mod.rs @@ -14,6 +14,7 @@ use style::Atom; use webrender_api::NativeFontHandle; pub mod font; +mod freetype_face; #[cfg(all(target_os = "linux", not(target_env = "ohos"), not(ohos_mock)))] pub mod font_list;