/* 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 http://mozilla.org/MPL/2.0/. */ use api::{ColorU, GlyphDimensions, FontKey, FontRenderMode}; use api::{FontInstancePlatformOptions, FontLCDFilter, FontHinting}; use api::{FontInstanceFlags, FontTemplate, FontVariation, NativeFontHandle}; use freetype::freetype::{FT_BBox, FT_Outline_Translate, FT_Pixel_Mode, FT_Render_Mode}; use freetype::freetype::{FT_Done_Face, FT_Error, FT_Get_Char_Index, FT_Int32}; use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter, FT_Pos}; use freetype::freetype::{FT_F26Dot6, FT_Face, FT_Glyph_Format, FT_Long, FT_UInt}; use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Face, FT_New_Memory_Face}; use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph}; use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size}; use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform, FT_String, FT_ULong, FT_Vector}; use freetype::freetype::{FT_Err_Unimplemented_Feature, FT_MulFix, FT_Outline_Embolden}; use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT}; use freetype::freetype::{FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, FT_LOAD_NO_AUTOHINT}; use freetype::freetype::{FT_LOAD_NO_BITMAP, FT_LOAD_NO_HINTING}; use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES}; use freetype::freetype::{FT_FACE_FLAG_MULTIPLE_MASTERS}; use freetype::succeeded; use crate::rasterizer::{FontInstance, GlyphFormat, GlyphKey}; use crate::rasterizer::{GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; use crate::types::FastHashMap; #[cfg(any(not(target_os = "android"), feature = "dynamic_freetype"))] use libc::{dlsym, RTLD_DEFAULT}; use libc::free; use std::{cmp, mem, ptr, slice}; use std::cmp::max; use std::ffi::CString; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; // These constants are not present in the freetype // bindings due to bindgen not handling the way // the macros are defined. //const FT_LOAD_TARGET_NORMAL: FT_UInt = 0 << 16; const FT_LOAD_TARGET_LIGHT: FT_UInt = 1 << 16; const FT_LOAD_TARGET_MONO: FT_UInt = 2 << 16; const FT_LOAD_TARGET_LCD: FT_UInt = 3 << 16; const FT_LOAD_TARGET_LCD_V: FT_UInt = 4 << 16; #[repr(C)] struct FT_Var_Axis { pub name: *mut FT_String, pub minimum: FT_Fixed, pub def: FT_Fixed, pub maximum: FT_Fixed, pub tag: FT_ULong, pub strid: FT_UInt, } #[repr(C)] struct FT_Var_Named_Style { pub coords: *mut FT_Fixed, pub strid: FT_UInt, pub psid: FT_UInt, } #[repr(C)] struct FT_MM_Var { pub num_axis: FT_UInt, pub num_designs: FT_UInt, pub num_namedstyles: FT_UInt, pub axis: *mut FT_Var_Axis, pub namedstyle: *mut FT_Var_Named_Style, } #[inline] pub fn unimplemented(error: FT_Error) -> bool { error == FT_Err_Unimplemented_Feature as FT_Error } // Use dlsym to check for symbols. If not available. just return an unimplemented error. #[cfg(any(not(target_os = "android"), feature = "dynamic_freetype"))] macro_rules! ft_dyn_fn { ($func_name:ident($($arg_name:ident:$arg_type:ty),*) -> FT_Error) => { #[allow(non_snake_case)] unsafe fn $func_name($($arg_name:$arg_type),*) -> FT_Error { extern "C" fn unimpl_func($(_:$arg_type),*) -> FT_Error { FT_Err_Unimplemented_Feature as FT_Error } lazy_static! { static ref FUNC: unsafe extern "C" fn($($arg_type),*) -> FT_Error = { unsafe { let cname = CString::new(stringify!($func_name)).unwrap(); let ptr = dlsym(RTLD_DEFAULT, cname.as_ptr()); if !ptr.is_null() { mem::transmute(ptr) } else { unimpl_func } } }; } (*FUNC)($($arg_name),*) } } } // On Android, just statically link in the symbols... #[cfg(all(target_os = "android", not(feature = "dynamic_freetype")))] macro_rules! ft_dyn_fn { ($($proto:tt)+) => { extern "C" { fn $($proto)+; } } } ft_dyn_fn!(FT_Get_MM_Var(face: FT_Face, desc: *mut *mut FT_MM_Var) -> FT_Error); ft_dyn_fn!(FT_Done_MM_Var(library: FT_Library, desc: *mut FT_MM_Var) -> FT_Error); ft_dyn_fn!(FT_Set_Var_Design_Coordinates(face: FT_Face, num_vals: FT_UInt, vals: *mut FT_Fixed) -> FT_Error); ft_dyn_fn!(FT_Get_Var_Design_Coordinates(face: FT_Face, num_vals: FT_UInt, vals: *mut FT_Fixed) -> FT_Error); extern "C" { fn FT_GlyphSlot_Embolden(slot: FT_GlyphSlot); } // Custom version of FT_GlyphSlot_Embolden to be less aggressive with outline // fonts than the default implementation in FreeType. #[no_mangle] pub extern "C" fn mozilla_glyphslot_embolden_less(slot: FT_GlyphSlot) { if slot.is_null() { return; } let slot_ = unsafe { &mut *slot }; let format = slot_.format; if format != FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE { // For non-outline glyphs, just fall back to FreeType's function. unsafe { FT_GlyphSlot_Embolden(slot) }; return; } let face_ = unsafe { *slot_.face }; // FT_GlyphSlot_Embolden uses a divisor of 24 here; we'll be only half as // bold. let size_ = unsafe { *face_.size }; let strength = unsafe { FT_MulFix(face_.units_per_EM as FT_Long, size_.metrics.y_scale) / 48 }; unsafe { FT_Outline_Embolden(&mut slot_.outline, strength) }; // Adjust metrics to suit the fattened glyph. if slot_.advance.x != 0 { slot_.advance.x += strength; } if slot_.advance.y != 0 { slot_.advance.y += strength; } slot_.metrics.width += strength; slot_.metrics.height += strength; slot_.metrics.horiAdvance += strength; slot_.metrics.vertAdvance += strength; slot_.metrics.horiBearingY += strength; } struct CachedFont { template: FontTemplate, face: FT_Face, mm_var: *mut FT_MM_Var, variations: Vec, } impl Drop for CachedFont { fn drop(&mut self) { unsafe { if !self.mm_var.is_null() && unimplemented(FT_Done_MM_Var((*(*self.face).glyph).library, self.mm_var)) { free(self.mm_var as _); } FT_Done_Face(self.face); } } } struct FontCache { lib: FT_Library, // Maps a template to a cached font that may be used across all threads. fonts: FastHashMap>>, // The current LCD filter installed in the library. lcd_filter: FontLCDFilter, // The number of threads currently relying on the LCD filter state. lcd_filter_uses: usize, } // FreeType resources are safe to move between threads as long as they // are not concurrently accessed. In our case, everything is behind a // Mutex so it is safe to move them between threads. unsafe impl Send for CachedFont {} unsafe impl Send for FontCache {} impl FontCache { fn new() -> Self { let mut lib: FT_Library = ptr::null_mut(); let result = unsafe { FT_Init_FreeType(&mut lib) }; if succeeded(result) { // Ensure the library uses the default LCD filter initially. unsafe { FT_Library_SetLcdFilter(lib, FT_LcdFilter::FT_LCD_FILTER_DEFAULT) }; } else { panic!("Failed to initialize FreeType - {}", result) } FontCache { lib, fonts: FastHashMap::default(), lcd_filter: FontLCDFilter::Default, lcd_filter_uses: 0, } } fn add_font(&mut self, template: FontTemplate) -> Result>, FT_Error> { if let Some(cached) = self.fonts.get(&template) { return Ok(cached.clone()); } unsafe { let mut face: FT_Face = ptr::null_mut(); let result = match template { FontTemplate::Raw(ref bytes, index) => { FT_New_Memory_Face( self.lib, bytes.as_ptr(), bytes.len() as FT_Long, index as FT_Long, &mut face, ) } FontTemplate::Native(NativeFontHandle { ref path, index }) => { let str = path.as_os_str().to_str().unwrap(); let cstr = CString::new(str).unwrap(); FT_New_Face( self.lib, cstr.as_ptr(), index as FT_Long, &mut face, ) } }; if !succeeded(result) || face.is_null() { return Err(result); } let mut mm_var = ptr::null_mut(); if ((*face).face_flags & (FT_FACE_FLAG_MULTIPLE_MASTERS as FT_Long)) != 0 && succeeded(FT_Get_MM_Var(face, &mut mm_var)) { // Calling this before FT_Set_Var_Design_Coordinates avoids a bug with font variations // not initialized properly in the font face, even if we ignore the result. // See bug 1647035. let mut tmp = [0; 16]; let res = FT_Get_Var_Design_Coordinates( face, (*mm_var).num_axis.min(16), tmp.as_mut_ptr() ); debug_assert!(succeeded(res)); } let cached = Arc::new(Mutex::new(CachedFont { template: template.clone(), face, mm_var, variations: Vec::new(), })); self.fonts.insert(template, cached.clone()); Ok(cached) } } fn delete_font(&mut self, cached: Arc>) { self.fonts.remove(&cached.lock().unwrap().template); } } impl Drop for FontCache { fn drop(&mut self) { self.fonts.clear(); unsafe { FT_Done_FreeType(self.lib); } } } lazy_static! { static ref FONT_CACHE: Mutex = Mutex::new(FontCache::new()); static ref LCD_FILTER_UNUSED: Condvar = Condvar::new(); } pub struct FontContext { fonts: FastHashMap>>, } fn get_skew_bounds(bottom: i32, top: i32, skew_factor: f32, _vertical: bool) -> (f32, f32) { let skew_min = (bottom as f32 + 0.5) * skew_factor; let skew_max = (top as f32 - 0.5) * skew_factor; // Negative skew factor may switch the sense of skew_min and skew_max. (skew_min.min(skew_max).floor(), skew_min.max(skew_max).ceil()) } fn skew_bitmap( bitmap: &[u8], width: usize, height: usize, left: i32, top: i32, skew_factor: f32, vertical: bool, // TODO: vertical skew not yet implemented! ) -> (Vec, usize, i32) { let stride = width * 4; // Calculate the skewed horizontal offsets of the bottom and top of the glyph. let (skew_min, skew_max) = get_skew_bounds(top - height as i32, top, skew_factor, vertical); // Allocate enough extra width for the min/max skew offsets. let skew_width = width + (skew_max - skew_min) as usize; let mut skew_buffer = vec![0u8; skew_width * height * 4]; for y in 0 .. height { // Calculate a skew offset at the vertical center of the current row. let offset = (top as f32 - y as f32 - 0.5) * skew_factor - skew_min; // Get a blend factor in 0..256 constant across all pixels in the row. let blend = (offset.fract() * 256.0) as u32; let src_row = y * stride; let dest_row = (y * skew_width + offset.floor() as usize) * 4; let mut prev_px = [0u32; 4]; for (src, dest) in bitmap[src_row .. src_row + stride].chunks(4).zip( skew_buffer[dest_row .. dest_row + stride].chunks_mut(4) ) { let px = [src[0] as u32, src[1] as u32, src[2] as u32, src[3] as u32]; // Blend current pixel with previous pixel based on blend factor. let next_px = [px[0] * blend, px[1] * blend, px[2] * blend, px[3] * blend]; dest[0] = ((((px[0] << 8) - next_px[0]) + prev_px[0] + 128) >> 8) as u8; dest[1] = ((((px[1] << 8) - next_px[1]) + prev_px[1] + 128) >> 8) as u8; dest[2] = ((((px[2] << 8) - next_px[2]) + prev_px[2] + 128) >> 8) as u8; dest[3] = ((((px[3] << 8) - next_px[3]) + prev_px[3] + 128) >> 8) as u8; // Save the remainder for blending onto the next pixel. prev_px = next_px; } // If the skew misaligns the final pixel, write out the remainder. if blend > 0 { let dest = &mut skew_buffer[dest_row + stride .. dest_row + stride + 4]; dest[0] = ((prev_px[0] + 128) >> 8) as u8; dest[1] = ((prev_px[1] + 128) >> 8) as u8; dest[2] = ((prev_px[2] + 128) >> 8) as u8; dest[3] = ((prev_px[3] + 128) >> 8) as u8; } } (skew_buffer, skew_width, left + skew_min as i32) } fn transpose_bitmap(bitmap: &[u8], width: usize, height: usize) -> Vec { let mut transposed = vec![0u8; width * height * 4]; for (y, row) in bitmap.chunks(width * 4).enumerate() { let mut offset = y * 4; for src in row.chunks(4) { transposed[offset .. offset + 4].copy_from_slice(src); offset += height * 4; } } transposed } fn flip_bitmap_x(bitmap: &mut [u8], width: usize, height: usize) { assert!(bitmap.len() == width * height * 4); let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) }; for row in pixels.chunks_mut(width) { row.reverse(); } } fn flip_bitmap_y(bitmap: &mut [u8], width: usize, height: usize) { assert!(bitmap.len() == width * height * 4); let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) }; for y in 0 .. height / 2 { let low_row = y * width; let high_row = (height - 1 - y) * width; for x in 0 .. width { pixels.swap(low_row + x, high_row + x); } } } impl FontContext { pub fn distribute_across_threads() -> bool { false } pub fn new() -> FontContext { FontContext { fonts: FastHashMap::default(), } } pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc>, index: u32) { if !self.fonts.contains_key(font_key) { let len = bytes.len(); match FONT_CACHE.lock().unwrap().add_font(FontTemplate::Raw(bytes, index)) { Ok(font) => self.fonts.insert(*font_key, font), Err(result) => panic!("adding raw font failed: {} bytes, err={:?}", len, result), }; } } pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) { if !self.fonts.contains_key(font_key) { let path = native_font_handle.path.to_string_lossy().into_owned(); match FONT_CACHE.lock().unwrap().add_font(FontTemplate::Native(native_font_handle)) { Ok(font) => self.fonts.insert(*font_key, font), Err(result) => panic!("adding native font failed: file={} err={:?}", path, result), }; } } pub fn delete_font(&mut self, font_key: &FontKey) { if let Some(cached) = self.fonts.remove(font_key) { // If the only references to this font are the FontCache and this FontContext, // then delete the font as there are no other existing users. if Arc::strong_count(&cached) <= 2 { FONT_CACHE.lock().unwrap().delete_font(cached); } } } pub fn delete_font_instance(&mut self, _instance: &FontInstance) { } fn load_glyph(&mut self, font: &FontInstance, glyph: &GlyphKey) -> Option<(MutexGuard, FT_GlyphSlot, f32)> { let mut cached = self.fonts.get(&font.font_key)?.lock().ok()?; let face = cached.face; let mm_var = cached.mm_var; if !mm_var.is_null() && font.variations != cached.variations { cached.variations.clear(); cached.variations.extend_from_slice(&font.variations); unsafe { let num_axis = (*mm_var).num_axis; let mut coords: Vec = Vec::with_capacity(num_axis as usize); for i in 0 .. num_axis { let axis = (*mm_var).axis.offset(i as isize); let mut value = (*axis).def; for var in &font.variations { if var.tag as FT_ULong == (*axis).tag { value = (var.value * 65536.0 + 0.5) as FT_Fixed; value = cmp::min(value, (*axis).maximum); value = cmp::max(value, (*axis).minimum); break; } } coords.push(value); } let res = FT_Set_Var_Design_Coordinates(face, num_axis, coords.as_mut_ptr()); debug_assert!(succeeded(res)); } } let mut load_flags = FT_LOAD_DEFAULT; let FontInstancePlatformOptions { mut hinting, .. } = font.platform_options.unwrap_or_default(); // Disable hinting if there is a non-axis-aligned transform. if font.synthetic_italics.is_enabled() || ((font.transform.scale_x != 0.0 || font.transform.scale_y != 0.0) && (font.transform.skew_x != 0.0 || font.transform.skew_y != 0.0)) { hinting = FontHinting::None; } match (hinting, font.render_mode) { (FontHinting::None, _) => load_flags |= FT_LOAD_NO_HINTING, (FontHinting::Mono, _) => load_flags = FT_LOAD_TARGET_MONO, (FontHinting::Light, _) => load_flags = FT_LOAD_TARGET_LIGHT, (FontHinting::LCD, FontRenderMode::Subpixel) => { load_flags = if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) { FT_LOAD_TARGET_LCD_V } else { FT_LOAD_TARGET_LCD }; if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) { load_flags |= FT_LOAD_FORCE_AUTOHINT; } } _ => { if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) { load_flags |= FT_LOAD_FORCE_AUTOHINT; } } } if font.flags.contains(FontInstanceFlags::NO_AUTOHINT) { load_flags |= FT_LOAD_NO_AUTOHINT; } if !font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) { load_flags |= FT_LOAD_NO_BITMAP; } let face_flags = unsafe { (*face).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 are handled internally by Gecko, and // WebRender is just asked to paint individual layers. load_flags |= FT_LOAD_COLOR; } load_flags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); let req_size = font.size.to_f64_px(); let mut result = if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 && (face_flags & (FT_FACE_FLAG_SCALABLE as FT_Long)) == 0 && (load_flags & FT_LOAD_NO_BITMAP) == 0 { unsafe { FT_Set_Transform(face, ptr::null_mut(), ptr::null_mut()) }; self.choose_bitmap_size(face, req_size * y_scale) } else { let mut shape = font.transform.invert_scale(x_scale, y_scale); if font.flags.contains(FontInstanceFlags::FLIP_X) { shape = shape.flip_x(); } if font.flags.contains(FontInstanceFlags::FLIP_Y) { shape = shape.flip_y(); } if font.flags.contains(FontInstanceFlags::TRANSPOSE) { shape = shape.swap_xy(); } let (mut tx, mut ty) = (0.0, 0.0); if font.synthetic_italics.is_enabled() { let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, y_scale * req_size); shape = shape_; tx = tx_; ty = ty_; }; let mut ft_shape = FT_Matrix { xx: (shape.scale_x * 65536.0) as FT_Fixed, xy: (shape.skew_x * -65536.0) as FT_Fixed, yx: (shape.skew_y * -65536.0) as FT_Fixed, yy: (shape.scale_y * 65536.0) as FT_Fixed, }; // The delta vector for FT_Set_Transform is in units of 1/64 pixel. let mut ft_delta = FT_Vector { x: (tx * 64.0) as FT_F26Dot6, y: (ty * -64.0) as FT_F26Dot6, }; unsafe { FT_Set_Transform(face, &mut ft_shape, &mut ft_delta); FT_Set_Char_Size( face, (req_size * x_scale * 64.0 + 0.5) as FT_F26Dot6, (req_size * y_scale * 64.0 + 0.5) as FT_F26Dot6, 0, 0, ) } }; if !succeeded(result) { error!("Unable to set glyph size and transform: {}", result); //let raw_error = unsafe { FT_Error_String(result) }; //if !raw_error.is_ptr() { // error!("\tcode {:?}", CStr::from_ptr(raw_error)); //} debug!( "\t[{}] for size {:?} and scale {:?} from font {:?}", glyph.index(), req_size, (x_scale, y_scale), font.font_key, ); return None; } result = unsafe { FT_Load_Glyph(face, glyph.index() as FT_UInt, load_flags as FT_Int32) }; if !succeeded(result) { error!("Unable to load glyph: {}", result); //let raw_error = unsafe { FT_Error_String(result) }; //if !raw_error.is_ptr() { // error!("\tcode {:?}", CStr::from_ptr(raw_error)); //} debug!( "\t[{}] with flags {:?} from font {:?}", glyph.index(), load_flags, font.font_key, ); return None; } let slot = unsafe { (*face).glyph }; assert!(slot != ptr::null_mut()); if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { mozilla_glyphslot_embolden_less(slot); } let format = unsafe { (*slot).format }; match format { FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => { let bitmap_size = unsafe { (*(*(*slot).face).size).metrics.y_ppem }; Some((cached, slot, req_size as f32 / bitmap_size as f32)) } FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => Some((cached, slot, 1.0)), _ => { error!("Unsupported format"); debug!("format={:?}", format); None } } } fn pad_bounding_box(font: &FontInstance, cbox: &mut FT_BBox) { // Apply extra pixel of padding for subpixel AA, due to the filter. if font.render_mode == FontRenderMode::Subpixel { // Using an LCD filter may add one full pixel to each side if support is built in. // As of FreeType 2.8.1, an LCD filter is always used regardless of settings // if support for the patent-encumbered LCD filter algorithms is not built in. // Thus, the only reasonable way to guess padding is to unconditonally add it if // subpixel AA is used. let lcd_extra_pixels = 1; let padding = (lcd_extra_pixels * 64) as FT_Pos; if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) { cbox.yMin -= padding; cbox.yMax += padding; } else { cbox.xMin -= padding; cbox.xMax += padding; } } } // Get the bounding box for a glyph, accounting for sub-pixel positioning. fn get_bounding_box( slot: FT_GlyphSlot, font: &FontInstance, glyph: &GlyphKey, scale: f32, ) -> FT_BBox { // Get the estimated bounding box from FT (control points). let mut cbox = FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 }; unsafe { FT_Outline_Get_CBox(&(*slot).outline, &mut cbox); } // For spaces and other non-printable characters, early out. if unsafe { (*slot).outline.n_contours } == 0 { return cbox; } Self::pad_bounding_box(font, &mut cbox); // Offset the bounding box by subpixel positioning. // Convert to 26.6 fixed point format for FT. let (dx, dy) = font.get_subpx_offset(glyph); let (dx, dy) = ( (dx / scale as f64 * 64.0 + 0.5) as FT_Pos, -(dy / scale as f64 * 64.0 + 0.5) as FT_Pos, ); cbox.xMin += dx; cbox.xMax += dx; cbox.yMin += dy; cbox.yMax += dy; // Outset the box to device pixel boundaries cbox.xMin &= !63; cbox.yMin &= !63; cbox.xMax = (cbox.xMax + 63) & !63; cbox.yMax = (cbox.yMax + 63) & !63; cbox } fn get_glyph_dimensions_impl( slot: FT_GlyphSlot, font: &FontInstance, glyph: &GlyphKey, scale: f32, use_transform: bool, ) -> Option { let format = unsafe { (*slot).format }; let (mut left, mut top, mut width, mut height) = match format { FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => { unsafe { ( (*slot).bitmap_left as i32, (*slot).bitmap_top as i32, (*slot).bitmap.width as i32, (*slot).bitmap.rows as i32, ) } } FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => { let cbox = Self::get_bounding_box(slot, font, glyph, scale); ( (cbox.xMin >> 6) as i32, (cbox.yMax >> 6) as i32, ((cbox.xMax - cbox.xMin) >> 6) as i32, ((cbox.yMax - cbox.yMin) >> 6) as i32, ) } _ => return None, }; let mut advance = unsafe { (*slot).metrics.horiAdvance as f32 / 64.0 }; if use_transform { if scale != 1.0 { let x0 = left as f32 * scale; let x1 = width as f32 * scale + x0; let y1 = top as f32 * scale; let y0 = y1 - height as f32 * scale; left = x0.round() as i32; top = y1.round() as i32; width = (x1.ceil() - x0.floor()) as i32; height = (y1.ceil() - y0.floor()) as i32; advance *= scale; } // An outline glyph's cbox would have already been transformed inside FT_Load_Glyph, // so only handle bitmap glyphs which are not handled by FT_Load_Glyph. if format == FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP { if font.synthetic_italics.is_enabled() { let (skew_min, skew_max) = get_skew_bounds( top - height as i32, top, font.synthetic_italics.to_skew(), font.flags.contains(FontInstanceFlags::VERTICAL), ); left += skew_min as i32; width += (skew_max - skew_min) as i32; } if font.flags.contains(FontInstanceFlags::TRANSPOSE) { mem::swap(&mut width, &mut height); mem::swap(&mut left, &mut top); left -= width as i32; top += height as i32; } if font.flags.contains(FontInstanceFlags::FLIP_X) { left = -(left + width as i32); } if font.flags.contains(FontInstanceFlags::FLIP_Y) { top = -(top - height as i32); } } } Some(GlyphDimensions { left, top, width, height, advance, }) } pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option { let cached = self.fonts.get(&font_key)?.lock().ok()?; let face = cached.face; unsafe { let idx = FT_Get_Char_Index(face, ch as _); if idx != 0 { Some(idx) } else { None } } } pub fn get_glyph_dimensions( &mut self, font: &FontInstance, key: &GlyphKey, ) -> Option { let (_cached, slot, scale) = self.load_glyph(font, key)?; Self::get_glyph_dimensions_impl(slot, &font, key, scale, true) } fn choose_bitmap_size(&self, face: FT_Face, requested_size: f64) -> FT_Error { let mut best_dist = unsafe { *(*face).available_sizes.offset(0) }.y_ppem as f64 / 64.0 - requested_size; let mut best_size = 0; let num_fixed_sizes = unsafe { (*face).num_fixed_sizes }; for i in 1 .. num_fixed_sizes { // 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. let dist = unsafe { *(*face).available_sizes.offset(i as isize) }.y_ppem as f64 / 64.0 - requested_size; if (best_dist < 0.0 && dist >= best_dist) || dist.abs() <= best_dist { best_dist = dist; best_size = i; } } unsafe { FT_Select_Size(face, best_size) } } pub fn prepare_font(font: &mut FontInstance) { match font.render_mode { FontRenderMode::Mono => { // In mono mode the color of the font is irrelevant. font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF); // Subpixel positioning is disabled in mono mode. font.disable_subpixel_position(); } FontRenderMode::Alpha | FontRenderMode::Subpixel => { // We don't do any preblending with FreeType currently, so the color is not used. font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF); } } } fn rasterize_glyph_outline( slot: FT_GlyphSlot, font: &FontInstance, key: &GlyphKey, scale: f32, ) -> bool { // Get the subpixel offsets in FT 26.6 format. let (dx, dy) = font.get_subpx_offset(key); let (dx, dy) = ( (dx / scale as f64 * 64.0 + 0.5) as FT_Pos, -(dy / scale as f64 * 64.0 + 0.5) as FT_Pos, ); // Move the outline curves to be at the origin, taking // into account the subpixel positioning. unsafe { let outline = &(*slot).outline; let mut cbox = FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 }; FT_Outline_Get_CBox(outline, &mut cbox); Self::pad_bounding_box(font, &mut cbox); FT_Outline_Translate( outline, dx - ((cbox.xMin + dx) & !63), dy - ((cbox.yMin + dy) & !63), ); } let render_mode = match font.render_mode { FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO, FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL, FontRenderMode::Subpixel => if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) { FT_Render_Mode::FT_RENDER_MODE_LCD_V } else { FT_Render_Mode::FT_RENDER_MODE_LCD }, }; let result = unsafe { FT_Render_Glyph(slot, render_mode) }; if !succeeded(result) { error!("Unable to rasterize"); debug!( "{:?} with {:?}, {:?}", key, render_mode, result ); false } else { true } } pub fn begin_rasterize(font: &FontInstance) { // The global LCD filter state is only used in subpixel rendering modes. if font.render_mode == FontRenderMode::Subpixel { let mut cache = FONT_CACHE.lock().unwrap(); let FontInstancePlatformOptions { lcd_filter, .. } = font.platform_options.unwrap_or_default(); // Check if the current LCD filter matches the requested one. if cache.lcd_filter != lcd_filter { // If the filter doesn't match, we have to wait for all other currently rasterizing threads // that may use the LCD filter state to finish before we can override it. while cache.lcd_filter_uses != 0 { cache = LCD_FILTER_UNUSED.wait(cache).unwrap(); } // Finally set the LCD filter to the requested one now that the library is unused. cache.lcd_filter = lcd_filter; let filter = match lcd_filter { FontLCDFilter::None => FT_LcdFilter::FT_LCD_FILTER_NONE, FontLCDFilter::Default => FT_LcdFilter::FT_LCD_FILTER_DEFAULT, FontLCDFilter::Light => FT_LcdFilter::FT_LCD_FILTER_LIGHT, FontLCDFilter::Legacy => FT_LcdFilter::FT_LCD_FILTER_LEGACY, }; unsafe { let result = FT_Library_SetLcdFilter(cache.lib, filter); // Setting the legacy filter may fail, so just use the default filter instead. if !succeeded(result) { FT_Library_SetLcdFilter(cache.lib, FT_LcdFilter::FT_LCD_FILTER_DEFAULT); } } } cache.lcd_filter_uses += 1; } } pub fn end_rasterize(font: &FontInstance) { if font.render_mode == FontRenderMode::Subpixel { let mut cache = FONT_CACHE.lock().unwrap(); // If this is the last use of the LCD filter, then signal that the LCD filter isn't used. cache.lcd_filter_uses -= 1; if cache.lcd_filter_uses == 0 { LCD_FILTER_UNUSED.notify_all(); } } } pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { let (_cached, slot, scale) = self.load_glyph(font, key) .ok_or(GlyphRasterError::LoadFailed)?; // Get dimensions of the glyph, to see if we need to rasterize it. // Don't apply scaling to the dimensions, as the glyph cache needs to know the actual // footprint of the glyph. let dimensions = Self::get_glyph_dimensions_impl(slot, font, key, scale, false) .ok_or(GlyphRasterError::LoadFailed)?; let GlyphDimensions { mut left, mut top, width, height, .. } = dimensions; // For spaces and other non-printable characters, early out. if width == 0 || height == 0 { return Err(GlyphRasterError::LoadFailed); } let format = unsafe { (*slot).format }; match format { FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {} FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => { if !Self::rasterize_glyph_outline(slot, font, key, scale) { return Err(GlyphRasterError::LoadFailed); } } _ => { error!("Unsupported format"); debug!("format={:?}", format); return Err(GlyphRasterError::LoadFailed); } }; debug!( "Rasterizing {:?} as {:?} with dimensions {:?}", key, font.render_mode, dimensions ); let bitmap = unsafe { &(*slot).bitmap }; let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) }; let (mut actual_width, mut actual_height) = match pixel_mode { FT_Pixel_Mode::FT_PIXEL_MODE_LCD => { assert!(bitmap.width % 3 == 0); ((bitmap.width / 3) as usize, bitmap.rows as usize) } FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => { assert!(bitmap.rows % 3 == 0); (bitmap.width as usize, (bitmap.rows / 3) as usize) } FT_Pixel_Mode::FT_PIXEL_MODE_MONO | FT_Pixel_Mode::FT_PIXEL_MODE_GRAY | FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => { (bitmap.width as usize, bitmap.rows as usize) } _ => panic!("Unsupported mode"), }; // If we need padding, we will need to expand the buffer size. let (buffer_width, buffer_height, padding) = if font.use_texture_padding() { (actual_width + 2, actual_height + 2, 1) } else { (actual_width, actual_height, 0) }; let mut final_buffer = vec![0u8; buffer_width * buffer_height * 4]; // Extract the final glyph from FT format into BGRA8 format, which is // what WR expects. let subpixel_bgr = font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR); let mut src_row = bitmap.buffer; let mut dest = 4 * padding * (padding + buffer_width); let actual_end = final_buffer.len() - 4 * padding * (buffer_width + 1); while dest < actual_end { let mut src = src_row; let row_end = dest + actual_width * 4; match pixel_mode { FT_Pixel_Mode::FT_PIXEL_MODE_MONO => { while dest < row_end { // Cast the byte to signed so that we can left shift each bit into // the top bit, then right shift to fill out the bits with 0s or 1s. let mut byte: i8 = unsafe { *src as i8 }; src = unsafe { src.offset(1) }; let byte_end = cmp::min(row_end, dest + 8 * 4); while dest < byte_end { let alpha = (byte >> 7) as u8; final_buffer[dest + 0] = alpha; final_buffer[dest + 1] = alpha; final_buffer[dest + 2] = alpha; final_buffer[dest + 3] = alpha; dest += 4; byte <<= 1; } } } FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => { while dest < row_end { let alpha = unsafe { *src }; final_buffer[dest + 0] = alpha; final_buffer[dest + 1] = alpha; final_buffer[dest + 2] = alpha; final_buffer[dest + 3] = alpha; src = unsafe { src.offset(1) }; dest += 4; } } FT_Pixel_Mode::FT_PIXEL_MODE_LCD => { while dest < row_end { let (mut r, g, mut b) = unsafe { (*src, *src.offset(1), *src.offset(2)) }; if subpixel_bgr { mem::swap(&mut r, &mut b); } final_buffer[dest + 0] = b; final_buffer[dest + 1] = g; final_buffer[dest + 2] = r; final_buffer[dest + 3] = max(max(b, g), r); src = unsafe { src.offset(3) }; dest += 4; } } FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => { while dest < row_end { let (mut r, g, mut b) = unsafe { (*src, *src.offset(bitmap.pitch as isize), *src.offset((2 * bitmap.pitch) as isize)) }; if subpixel_bgr { mem::swap(&mut r, &mut b); } final_buffer[dest + 0] = b; final_buffer[dest + 1] = g; final_buffer[dest + 2] = r; final_buffer[dest + 3] = max(max(b, g), r); src = unsafe { src.offset(1) }; dest += 4; } src_row = unsafe { src_row.offset((2 * bitmap.pitch) as isize) }; } FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => { // The source is premultiplied BGRA data. let dest_slice = &mut final_buffer[dest .. row_end]; let src_slice = unsafe { slice::from_raw_parts(src, dest_slice.len()) }; dest_slice.copy_from_slice(src_slice); } _ => panic!("Unsupported mode"), } src_row = unsafe { src_row.offset(bitmap.pitch as isize) }; dest = row_end + 8 * padding; } if font.use_texture_padding() { left -= padding as i32; top += padding as i32; actual_width = buffer_width; actual_height = buffer_height; } match format { FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => { if font.synthetic_italics.is_enabled() { let (skew_buffer, skew_width, skew_left) = skew_bitmap( &final_buffer, actual_width, actual_height, left, top, font.synthetic_italics.to_skew(), font.flags.contains(FontInstanceFlags::VERTICAL), ); final_buffer = skew_buffer; actual_width = skew_width; left = skew_left; } if font.flags.contains(FontInstanceFlags::TRANSPOSE) { final_buffer = transpose_bitmap(&final_buffer, actual_width, actual_height); mem::swap(&mut actual_width, &mut actual_height); mem::swap(&mut left, &mut top); left -= actual_width as i32; top += actual_height as i32; } if font.flags.contains(FontInstanceFlags::FLIP_X) { flip_bitmap_x(&mut final_buffer, actual_width, actual_height); left = -(left + actual_width as i32); } if font.flags.contains(FontInstanceFlags::FLIP_Y) { flip_bitmap_y(&mut final_buffer, actual_width, actual_height); top = -(top - actual_height as i32); } } FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => { unsafe { left += (*slot).bitmap_left; top += (*slot).bitmap_top - height as i32; } } _ => {} } let glyph_format = match (pixel_mode, format) { (FT_Pixel_Mode::FT_PIXEL_MODE_LCD, _) | (FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V, _) => font.get_subpixel_glyph_format(), (FT_Pixel_Mode::FT_PIXEL_MODE_BGRA, _) => GlyphFormat::ColorBitmap, (_, FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP) => GlyphFormat::Bitmap, _ => font.get_alpha_glyph_format(), }; Ok(RasterizedGlyph { left: left as f32, top: top as f32, width: actual_width as i32, height: actual_height as i32, scale, format: glyph_format, bytes: final_buffer, }) } }