diff options
Diffstat (limited to 'gfx/wr/wr_glyph_rasterizer/src/platform')
-rw-r--r-- | gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs | 899 | ||||
-rw-r--r-- | gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs | 1099 | ||||
-rw-r--r-- | gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs | 639 |
3 files changed, 2637 insertions, 0 deletions
diff --git a/gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs b/gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs new file mode 100644 index 0000000000..804dbc93e8 --- /dev/null +++ b/gfx/wr/wr_glyph_rasterizer/src/platform/macos/font.rs @@ -0,0 +1,899 @@ +/* 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::{ColorF, ColorU, FontKey, FontRenderMode, FontSize, GlyphDimensions}; +use api::{FontInstanceFlags, FontVariation, NativeFontHandle}; +use core_foundation::data::CFData; +use core_foundation::base::TCFType; +use core_foundation::dictionary::CFDictionary; +use core_foundation::number::{CFNumber}; +use core_foundation::string::CFString; +use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst}; +use core_graphics::base::{kCGBitmapByteOrder32Little}; +use core_graphics::color_space::CGColorSpace; +use core_graphics::context::CGContext; +use core_graphics::context::{CGBlendMode, CGTextDrawingMode}; +use core_graphics::font::{CGFont, CGGlyph}; +use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize}; +use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGRect}; +use core_text; +use core_text::font::CTFont; +use core_text::font_descriptor::{CTFontDescriptor, kCTFontDefaultOrientation, kCTFontVariationAttribute}; +use core_text::font_manager; +use euclid::default::Size2D; +use crate::gamma_lut::{ColorLut, GammaLut}; +use crate::rasterizer::{FontInstance, FontTransform, GlyphKey}; +use crate::rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; +use crate::types::FastHashMap; +use std::collections::hash_map::Entry; +use std::sync::Arc; + +const INITIAL_CG_CONTEXT_SIDE_LENGTH: u32 = 32; + +pub struct FontContext { + ct_font_descs: FastHashMap<FontKey, CTFontDescriptor>, + // Table mapping a sized font key with variations to its instantiated CoreText font. + ct_fonts: FastHashMap<(FontKey, FontSize, Vec<FontVariation>), CTFont>, + #[allow(dead_code)] + graphics_context: GraphicsContext, + #[allow(dead_code)] + gamma_lut: GammaLut, +} + +// core text is safe to use on multiple threads and non-shareable resources are +// all hidden inside their font context. +unsafe impl Send for FontContext {} + +struct GlyphMetrics { + rasterized_left: i32, + #[allow(dead_code)] + rasterized_descent: i32, + rasterized_ascent: i32, + rasterized_width: i32, + rasterized_height: i32, + advance: f32, +} + +// There are a number of different OS prefs that control whether or not +// requesting font smoothing actually results in subpixel AA. This gets even +// murkier in newer macOS versions that deprecate subpixel AA, with the prefs +// potentially interacting and overriding each other. In an attempt to future- +// proof things against any new prefs or interpretation of those prefs in +// future macOS versions, we do a check here to request font smoothing and see +// what result it actually gives us much like Skia does. We need to check for +// each of three potential results and process them in the font backend in +// distinct ways: +// 1) subpixel AA (differing RGB channels) with dilation +// 2) grayscale AA (matching RGB channels) with dilation, a compatibility mode +// 3) grayscale AA without dilation as if font smoothing was not requested +// We can discern between case 1 and the rest by checking if the subpixels differ. +// We can discern between cases 2 and 3 by rendering with and without smoothing +// and comparing the two to determine if there was some dilation. +// This returns the actual FontRenderMode needed to support each case, if any. +fn determine_font_smoothing_mode() -> Option<FontRenderMode> { + let mut smooth_context = CGContext::create_bitmap_context( + None, + 12, + 12, + 8, + 12 * 4, + &CGColorSpace::create_device_rgb(), + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, + ); + smooth_context.set_should_smooth_fonts(true); + smooth_context.set_should_antialias(true); + smooth_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); + let mut gray_context = CGContext::create_bitmap_context( + None, + 12, + 12, + 8, + 12 * 4, + &CGColorSpace::create_device_rgb(), + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, + ); + gray_context.set_should_smooth_fonts(false); + gray_context.set_should_antialias(true); + gray_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); + + // Autorelease pool for CTFont + objc::rc::autoreleasepool(|| { + // Lucida Grande 12 is the default fallback font in Firefox + let ct_font = core_text::font::new_from_name("Lucida Grande", 12.).unwrap(); + let point = CGPoint { x: 0., y: 0. }; + let glyph = 'X' as CGGlyph; + ct_font.draw_glyphs(&[glyph], &[point], smooth_context.clone()); + ct_font.draw_glyphs(&[glyph], &[point], gray_context.clone()); + }); + + let mut mode = None; + for (smooth, gray) in smooth_context.data().chunks(4).zip(gray_context.data().chunks(4)) { + if smooth[0] != smooth[1] || smooth[1] != smooth[2] { + return Some(FontRenderMode::Subpixel); + } + if smooth[0] != gray[0] || smooth[1] != gray[1] || smooth[2] != gray[2] { + mode = Some(FontRenderMode::Alpha); + } + } + return mode; +} + +// We cache the font smoothing mode globally, rather than storing it in each FontContext, +// to avoid having to determine this redundantly in each context and to avoid needing to +// lock them to access this setting in prepare_font. +lazy_static! { + static ref FONT_SMOOTHING_MODE: Option<FontRenderMode> = determine_font_smoothing_mode(); +} + +fn should_use_white_on_black(color: ColorU) -> bool { + let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32); + // These thresholds were determined on 10.12 by observing what CG does. + r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255 +} + +fn get_glyph_metrics( + ct_font: &CTFont, + transform: Option<&CGAffineTransform>, + glyph: CGGlyph, + x_offset: f64, + y_offset: f64, + extra_width: f64, +) -> GlyphMetrics { + let mut bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]); + + if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() || bounds.size.width.is_nan() || + bounds.size.height.is_nan() + { + // If an unexpected glyph index is requested, core text will return NaN values + // which causes us to do bad thing as the value is cast into an integer and + // overflow when expanding the bounds a few lines below. + // Instead we are better off returning zero-sized metrics because this special + // case is handled by the callers of this method. + return GlyphMetrics { + rasterized_left: 0, + rasterized_width: 0, + rasterized_height: 0, + rasterized_ascent: 0, + rasterized_descent: 0, + advance: 0.0, + }; + } + + let mut advance = CGSize { width: 0.0, height: 0.0 }; + unsafe { + ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1); + } + + if bounds.size.width > 0.0 { + bounds.size.width += extra_width; + } + if advance.width > 0.0 { + advance.width += extra_width; + } + + if let Some(transform) = transform { + bounds = bounds.apply_transform(transform); + } + + // First round out to pixel boundaries + // CG Origin is bottom left + let mut left = bounds.origin.x.floor() as i32; + let mut bottom = bounds.origin.y.floor() as i32; + let mut right = (bounds.origin.x + bounds.size.width + x_offset).ceil() as i32; + let mut top = (bounds.origin.y + bounds.size.height + y_offset).ceil() as i32; + + // Expand the bounds by 1 pixel, to give CG room for anti-aliasing. + // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset + // is not currently known, as CG dilates the outlines by some percentage. + // This is taken from Skia. + left -= 1; + bottom -= 1; + right += 1; + top += 1; + + let width = right - left; + let height = top - bottom; + + GlyphMetrics { + rasterized_left: left, + rasterized_width: width, + rasterized_height: height, + rasterized_ascent: top, + rasterized_descent: -bottom, + advance: advance.width as f32, + } +} + +fn new_ct_font_with_variations(ct_font_desc: &CTFontDescriptor, size: f64, variations: &[FontVariation]) -> CTFont { + let ct_font = core_text::font::new_from_descriptor(ct_font_desc, size); + if variations.is_empty() { + return ct_font; + } + let mut vals: Vec<(CFNumber, CFNumber)> = Vec::with_capacity(variations.len() as usize); + for variation in variations { + vals.push((CFNumber::from(variation.tag as i64), CFNumber::from(variation.value as f64))); + } + if vals.is_empty() { + return ct_font; + } + let vals_dict = CFDictionary::from_CFType_pairs(&vals); + let variation_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontVariationAttribute) }; + let attrs_dict = CFDictionary::from_CFType_pairs(&[(variation_attribute, vals_dict)]); + let ct_var_font_desc = ct_font.copy_descriptor().create_copy_with_attributes(attrs_dict.to_untyped()).unwrap(); + core_text::font::new_from_descriptor(&ct_var_font_desc, size) + +} + +// We rely on Gecko to determine whether the font may have color glyphs to avoid +// needing to load the font ahead of time to query its symbolic traits. +fn is_bitmap_font(font: &FontInstance) -> bool { + font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) +} + +impl FontContext { + pub fn distribute_across_threads() -> bool { + true + } + + pub fn new() -> FontContext { + debug!("Test for subpixel AA support: {:?}", *FONT_SMOOTHING_MODE); + + // Force CG to use sRGB color space to gamma correct. + let contrast = 0.0; + let gamma = 0.0; + + FontContext { + ct_font_descs: FastHashMap::default(), + ct_fonts: FastHashMap::default(), + graphics_context: GraphicsContext::new(), + gamma_lut: GammaLut::new(contrast, gamma, gamma), + } + } + + pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) { + if self.ct_font_descs.contains_key(font_key) { + return; + } + + assert_eq!(index, 0); + let data = CFData::from_arc(bytes); + let ct_font_desc = match font_manager::create_font_descriptor_with_data(data) { + Err(_) => return, + Ok(cg_font) => cg_font, + }; + self.ct_font_descs.insert(*font_key, ct_font_desc); + } + + pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) { + if self.ct_font_descs.contains_key(font_key) { + return; + } + + // There's no way great way to go from a CGFont to a CTFontDescriptor + // We could use the postscript name but that doesn't work for the + // system UI fonts on newer macOS versions. Instead we create a CTFont + // and use the descriptor for that. Normally we'd try to avoid new_from_CGFont + // because that adds the CGFont to the descriptor cache which can keep the CGFont + // around for a long time, but that should be ok for non-web (native) fonts. + let cf_name = CFString::new(&native_font_handle.name); + + // For "hidden" system fonts, whose names start with a period, + // we can't instantiate CTFonts via a descriptor. We're really + // supposed to use CTFontCreateUIFontForLanguage, but for now + // we just use the CGFont. + let desc = if native_font_handle.name.starts_with('.') { + let cg_font = match CGFont::from_name(&cf_name) { + Ok(cg_font) => cg_font, + Err(_) => { + // If for some reason we failed to load a font descriptor, then our + // only options are to either abort or substitute a fallback font. + // It is preferable to use a fallback font instead so that rendering + // can at least still proceed in some fashion without erroring. + // Lucida Grande is the fallback font in Gecko, so use that here. + CGFont::from_name(&CFString::from_static_string("Lucida Grande")) + .expect("couldn't find font with postscript name and couldn't load fallback font") + } + }; + core_text::font::new_from_CGFont(&cg_font, 0.).copy_descriptor() + } else { + core_text::font_descriptor::new_from_postscript_name(&cf_name) + }; + + self.ct_font_descs + .insert(*font_key, desc); + } + + pub fn delete_font(&mut self, font_key: &FontKey) { + if let Some(_) = self.ct_font_descs.remove(font_key) { + self.ct_fonts.retain(|k, _| k.0 != *font_key); + } + } + + pub fn delete_font_instance(&mut self, instance: &FontInstance) { + // Remove the CoreText font corresponding to this instance. + let size = FontSize::from_f64_px(instance.get_transformed_size()); + self.ct_fonts.remove(&(instance.font_key, size, instance.variations.clone())); + } + + fn get_ct_font( + &mut self, + font_key: FontKey, + size: f64, + variations: &[FontVariation], + ) -> Option<CTFont> { + // Interacting with CoreText can create autorelease garbage. + objc::rc::autoreleasepool(|| { + match self.ct_fonts.entry((font_key, FontSize::from_f64_px(size), variations.to_vec())) { + Entry::Occupied(entry) => Some((*entry.get()).clone()), + Entry::Vacant(entry) => { + let ct_font_desc = self.ct_font_descs.get(&font_key)?; + let ct_font = new_ct_font_with_variations(ct_font_desc, size, variations); + entry.insert(ct_font.clone()); + Some(ct_font) + } + } + }) + } + + pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> { + let character = ch as u16; + let mut glyph = 0; + + self.get_ct_font(font_key, 16.0, &[]) + .and_then(|ct_font| { + unsafe { + let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1); + + if result { + Some(glyph as u32) + } else { + None + } + } + }) + } + + pub fn get_glyph_dimensions( + &mut self, + font: &FontInstance, + key: &GlyphKey, + ) -> Option<GlyphDimensions> { + let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + let size = font.size.to_f64_px() * y_scale; + self.get_ct_font(font.font_key, size, &font.variations) + .and_then(|ct_font| { + let glyph = key.index() as CGGlyph; + let bitmap = is_bitmap_font(font); + let (mut shape, (x_offset, y_offset)) = if bitmap { + (FontTransform::identity(), (0.0, 0.0)) + } else { + (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key)) + }; + 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, size); + shape = shape_; + tx = tx_; + ty = ty_; + } + let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) { + Some(CGAffineTransform { + a: shape.scale_x as f64, + b: -shape.skew_y as f64, + c: -shape.skew_x as f64, + d: shape.scale_y as f64, + tx: tx, + ty: -ty, + }) + } else { + None + }; + let (strike_scale, pixel_step) = if bitmap { + (y_scale, 1.0) + } else { + (x_scale, y_scale / x_scale) + }; + let extra_strikes = font.get_extra_strikes( + FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD, + strike_scale, + ); + let metrics = get_glyph_metrics( + &ct_font, + transform.as_ref(), + glyph, + x_offset, + y_offset, + extra_strikes as f64 * pixel_step, + ); + if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { + None + } else { + Some(GlyphDimensions { + left: metrics.rasterized_left, + top: metrics.rasterized_ascent, + width: metrics.rasterized_width, + height: metrics.rasterized_height, + advance: metrics.advance, + }) + } + }) + } + + // Assumes the pixels here are linear values from CG + fn gamma_correct_pixels( + &self, + pixels: &mut Vec<u8>, + render_mode: FontRenderMode, + color: ColorU, + ) { + // Then convert back to gamma corrected values. + match render_mode { + FontRenderMode::Alpha => { + self.gamma_lut.preblend_grayscale(pixels, color); + } + FontRenderMode::Subpixel => { + self.gamma_lut.preblend(pixels, color); + } + _ => {} // Again, give mono untouched since only the alpha matters. + } + } + + #[allow(dead_code)] + fn print_glyph_data(&mut self, data: &[u8], width: usize, height: usize) { + // Rust doesn't have step_by support on stable :( + debug!("Width is: {:?} height: {:?}", width, height); + for i in 0 .. height { + let current_height = i * width * 4; + + for pixel in data[current_height .. current_height + (width * 4)].chunks(4) { + let b = pixel[0]; + let g = pixel[1]; + let r = pixel[2]; + let a = pixel[3]; + debug!("({}, {}, {}, {}) ", r, g, b, a); + } + } + } + + pub fn prepare_font(font: &mut FontInstance) { + if is_bitmap_font(font) { + // Render mode is ignored for bitmap fonts. Also, avoid normalizing the color + // in case CoreText needs the current color for rendering glyph color layers. + font.render_mode = FontRenderMode::Mono; + font.disable_subpixel_position(); + return; + } + // Sanitize the render mode for font smoothing. If font smoothing is supported, + // then we just need to ensure the render mode is limited to what is supported. + // If font smoothing is actually disabled, then we need to fall back to grayscale. + if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) || + font.render_mode == FontRenderMode::Subpixel { + match *FONT_SMOOTHING_MODE { + Some(mode) => { + font.render_mode = font.render_mode.limit_by(mode); + font.flags.insert(FontInstanceFlags::FONT_SMOOTHING); + } + None => { + font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha); + font.flags.remove(FontInstanceFlags::FONT_SMOOTHING); + } + } + } + match font.render_mode { + FontRenderMode::Mono => { + // In mono mode the color of the font is irrelevant. + font.color = ColorU::new(255, 255, 255, 255); + // Subpixel positioning is disabled in mono mode. + font.disable_subpixel_position(); + } + FontRenderMode::Alpha => { + font.color = if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) { + // Only the G channel is used to index grayscale tables, + // so use R and B to preserve light/dark determination. + let ColorU { g, a, .. } = font.color.luminance_color().quantized_ceil(); + let rb = if should_use_white_on_black(font.color) { 255 } else { 0 }; + ColorU::new(rb, g, rb, a) + } else { + ColorU::new(255, 255, 255, 255) + }; + } + FontRenderMode::Subpixel => { + // Quantization may change the light/dark determination, so quantize in the + // direction necessary to respect the threshold. + font.color = if should_use_white_on_black(font.color) { + font.color.quantized_ceil() + } else { + font.color.quantized_floor() + }; + } + } + } + + pub fn begin_rasterize(_font: &FontInstance) { + } + + pub fn end_rasterize(_font: &FontInstance) { + } + + pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { + objc::rc::autoreleasepool(|| { + let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + let size = font.size.to_f64_px() * y_scale; + let ct_font = + self.get_ct_font(font.font_key, size, &font.variations).ok_or(GlyphRasterError::LoadFailed)?; + let glyph_type = if is_bitmap_font(font) { + GlyphType::Bitmap + } else { + GlyphType::Vector + }; + + let (mut shape, (x_offset, y_offset)) = match glyph_type { + GlyphType::Bitmap => (FontTransform::identity(), (0.0, 0.0)), + GlyphType::Vector => { + (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key)) + } + }; + 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, size); + shape = shape_; + tx = tx_; + ty = ty_; + } + let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) { + Some(CGAffineTransform { + a: shape.scale_x as f64, + b: -shape.skew_y as f64, + c: -shape.skew_x as f64, + d: shape.scale_y as f64, + tx: tx, + ty: -ty, + }) + } else { + None + }; + + let glyph = key.index() as CGGlyph; + let (strike_scale, pixel_step) = if glyph_type == GlyphType::Bitmap { + (y_scale, 1.0) + } else { + (x_scale, y_scale / x_scale) + }; + let extra_strikes = font.get_extra_strikes( + FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD, + strike_scale, + ); + let metrics = get_glyph_metrics( + &ct_font, + transform.as_ref(), + glyph, + x_offset, + y_offset, + extra_strikes as f64 * pixel_step, + ); + if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { + return Err(GlyphRasterError::LoadFailed); + } + + let raster_size = Size2D::new( + metrics.rasterized_width as u32, + metrics.rasterized_height as u32 + ); + + // If the font render mode is Alpha, we support two different ways to + // compute the grayscale mask, depending on the value of the platform + // options' font_smoothing flag: + // - Alpha + smoothing: + // We will recover a grayscale mask from a subpixel rasterization, in + // such a way that the result looks as close to subpixel text + // blending as we can make it. This involves gamma correction, + // luminance computations and preblending based on the text color, + // just like with the Subpixel render mode. + // - Alpha without smoothing: + // We will ask CoreGraphics to rasterize the text with font_smoothing + // off. This will cause it to use grayscale anti-aliasing with + // comparatively thin text. This method of text rendering is not + // gamma-aware. + // + // For subpixel rasterization, starting with macOS 10.11, CoreGraphics + // uses different glyph dilation based on the text color. Bright text + // uses less font dilation (looks thinner) than dark text. + // As a consequence, when we ask CG to rasterize with subpixel AA, we + // will render white-on-black text as opposed to black-on-white text if + // the text color brightness exceeds a certain threshold. This applies + // to both the Subpixel and the "Alpha + smoothing" modes, but not to + // the "Alpha without smoothing" and Mono modes. + // + // Fonts with color glyphs may, depending on the state within per-glyph + // table data, require the current font color to determine the output + // color. For such fonts we must thus supply the current font color just + // in case it is necessary. + let use_white_on_black = should_use_white_on_black(font.color); + let use_font_smoothing = font.flags.contains(FontInstanceFlags::FONT_SMOOTHING); + let (antialias, smooth, text_color, bg_color, invert) = match glyph_type { + GlyphType::Bitmap => (true, false, ColorF::from(font.color), ColorF::TRANSPARENT, false), + GlyphType::Vector => { + match (font.render_mode, use_font_smoothing) { + (FontRenderMode::Subpixel, _) | + (FontRenderMode::Alpha, true) => if use_white_on_black { + (true, true, ColorF::WHITE, ColorF::BLACK, false) + } else { + (true, true, ColorF::BLACK, ColorF::WHITE, true) + }, + (FontRenderMode::Alpha, false) => (true, false, ColorF::BLACK, ColorF::WHITE, true), + (FontRenderMode::Mono, _) => (false, false, ColorF::BLACK, ColorF::WHITE, true), + } + } + }; + + { + let cg_context = self.graphics_context.get_context(&raster_size, glyph_type); + + // These are always true in Gecko, even for non-AA fonts + cg_context.set_allows_font_subpixel_positioning(true); + cg_context.set_should_subpixel_position_fonts(true); + + // Don't quantize because we're doing it already. + cg_context.set_allows_font_subpixel_quantization(false); + cg_context.set_should_subpixel_quantize_fonts(false); + + cg_context.set_should_smooth_fonts(smooth); + cg_context.set_should_antialias(antialias); + + // Fill the background. This could be opaque white, opaque black, or + // transparency. + cg_context.set_rgb_fill_color( + bg_color.r.into(), + bg_color.g.into(), + bg_color.b.into(), + bg_color.a.into(), + ); + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + width: metrics.rasterized_width as f64, + height: metrics.rasterized_height as f64, + }, + }; + + // Make sure we use the Copy blend mode, or else we'll get the Porter-Duff OVER + // operator, which can't clear to the transparent color! + cg_context.set_blend_mode(CGBlendMode::Copy); + cg_context.fill_rect(rect); + cg_context.set_blend_mode(CGBlendMode::Normal); + + // Set the text color and draw the glyphs. + cg_context.set_rgb_fill_color( + text_color.r.into(), + text_color.g.into(), + text_color.b.into(), + 1.0, + ); + cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); + + // CG Origin is bottom left, WR is top left. Need -y offset + let mut draw_origin = CGPoint { + x: -metrics.rasterized_left as f64 + x_offset + tx, + y: metrics.rasterized_descent as f64 - y_offset - ty, + }; + + if let Some(transform) = transform { + cg_context.set_text_matrix(&transform); + + draw_origin = draw_origin.apply_transform(&transform.invert()); + } else { + // Make sure to reset this because some previous glyph rasterization might have + // changed it. + cg_context.set_text_matrix(&CG_AFFINE_TRANSFORM_IDENTITY); + } + + ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone()); + + // We'd like to render all the strikes in a single ct_font.draw_glyphs call, + // passing an array of glyph IDs and an array of origins, but unfortunately + // with some fonts, Core Text may inappropriately pixel-snap the rasterization, + // such that the strikes overprint instead of being offset. Rendering the + // strikes with individual draw_glyphs calls avoids this. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1633397 for details.) + for i in 1 ..= extra_strikes { + let origin = CGPoint { + x: draw_origin.x + i as f64 * pixel_step, + y: draw_origin.y, + }; + ct_font.draw_glyphs(&[glyph], &[origin], cg_context.clone()); + } + } + + let mut rasterized_pixels = self.graphics_context + .get_rasterized_pixels(&raster_size, glyph_type); + + if glyph_type == GlyphType::Vector { + // We rendered text into an opaque surface. The code below needs to + // ignore the current value of each pixel's alpha channel. But it's + // allowed to write to the alpha channel, because we're done calling + // CG functions now. + + if smooth { + // Convert to linear space for subpixel AA. + // We explicitly do not do this for grayscale AA ("Alpha without + // smoothing" or Mono) because those rendering modes are not + // gamma-aware in CoreGraphics. + self.gamma_lut.coregraphics_convert_to_linear( + &mut rasterized_pixels, + ); + } + + for pixel in rasterized_pixels.chunks_mut(4) { + if invert { + pixel[0] = 255 - pixel[0]; + pixel[1] = 255 - pixel[1]; + pixel[2] = 255 - pixel[2]; + } + + // Set alpha to the value of the green channel. For grayscale + // text, all three channels have the same value anyway. + // For subpixel text, the mask's alpha only makes a difference + // when computing the destination alpha on destination pixels + // that are not completely opaque. Picking an alpha value + // that's somehow based on the mask at least ensures that text + // blending doesn't modify the destination alpha on pixels where + // the mask is entirely zero. + pixel[3] = pixel[1]; + } + + if smooth { + // Convert back from linear space into device space, and perform + // some "preblending" based on the text color. + // In Alpha + smoothing mode, this will also convert subpixel AA + // into grayscale AA. + self.gamma_correct_pixels( + &mut rasterized_pixels, + font.render_mode, + font.color, + ); + } + } + + Ok(RasterizedGlyph { + left: metrics.rasterized_left as f32, + top: metrics.rasterized_ascent as f32, + width: metrics.rasterized_width, + height: metrics.rasterized_height, + scale: match glyph_type { + GlyphType::Bitmap => y_scale.recip() as f32, + GlyphType::Vector => 1.0, + }, + format: match glyph_type { + GlyphType::Bitmap => GlyphFormat::ColorBitmap, + GlyphType::Vector => font.get_glyph_format(), + }, + bytes: rasterized_pixels, + })}) + } +} + +// Avoids taking locks by recycling Core Graphics contexts. +#[allow(dead_code)] +struct GraphicsContext { + vector_context: CGContext, + vector_context_size: Size2D<u32>, + bitmap_context: CGContext, + bitmap_context_size: Size2D<u32>, +} + +impl GraphicsContext { + fn new() -> GraphicsContext { + let size = Size2D::new(INITIAL_CG_CONTEXT_SIDE_LENGTH, INITIAL_CG_CONTEXT_SIDE_LENGTH); + GraphicsContext { + vector_context: GraphicsContext::create_cg_context(&size, GlyphType::Vector), + vector_context_size: size, + bitmap_context: GraphicsContext::create_cg_context(&size, GlyphType::Bitmap), + bitmap_context_size: size, + } + } + + #[allow(dead_code)] + fn get_context(&mut self, size: &Size2D<u32>, glyph_type: GlyphType) + -> &mut CGContext { + let (cached_context, cached_size) = match glyph_type { + GlyphType::Vector => { + (&mut self.vector_context, &mut self.vector_context_size) + } + GlyphType::Bitmap => { + (&mut self.bitmap_context, &mut self.bitmap_context_size) + } + }; + let rounded_size = Size2D::new(size.width.next_power_of_two(), + size.height.next_power_of_two()); + if rounded_size.width > cached_size.width || rounded_size.height > cached_size.height { + *cached_size = Size2D::new(u32::max(cached_size.width, rounded_size.width), + u32::max(cached_size.height, rounded_size.height)); + *cached_context = GraphicsContext::create_cg_context(cached_size, glyph_type); + } + cached_context + } + + #[allow(dead_code)] + fn get_rasterized_pixels(&mut self, size: &Size2D<u32>, glyph_type: GlyphType) + -> Vec<u8> { + let (cached_context, cached_size) = match glyph_type { + GlyphType::Vector => (&mut self.vector_context, &self.vector_context_size), + GlyphType::Bitmap => (&mut self.bitmap_context, &self.bitmap_context_size), + }; + let cached_data = cached_context.data(); + let cached_stride = cached_size.width as usize * 4; + + let result_len = size.width as usize * size.height as usize * 4; + let mut result = Vec::with_capacity(result_len); + for y in (cached_size.height - size.height)..cached_size.height { + let cached_start = y as usize * cached_stride; + let cached_end = cached_start + size.width as usize * 4; + result.extend_from_slice(&cached_data[cached_start..cached_end]); + } + debug_assert_eq!(result.len(), result_len); + result + } + + fn create_cg_context(size: &Size2D<u32>, glyph_type: GlyphType) -> CGContext { + // The result of rasterization, in all render modes, is going to be a + // BGRA surface with white text on transparency using premultiplied + // alpha. For subpixel text, the RGB values will be the mask value for + // the individual components. For bitmap glyphs, the RGB values will be + // the (premultiplied) color of the pixel. For Alpha and Mono, each + // pixel will have R==G==B==A at the end of this function. + // We access the color channels in little-endian order. + // The CGContext will create and own our pixel buffer. + // In the non-Bitmap cases, we will ask CoreGraphics to draw text onto + // an opaque background. In order to hit the most efficient path in CG + // for this, we will tell CG that the CGContext is opaque, by passing + // an "[...]AlphaNone[...]" context flag. This creates a slight + // contradiction to the way we use the buffer after CG is done with it, + // because we will convert it into text-on-transparency. But that's ok; + // we still get four bytes per pixel and CG won't mess with the alpha + // channel after we've stopped calling CG functions. We just need to + // make sure that we don't look at the alpha values of the pixels that + // we get from CG, and compute our own alpha value only from RGB. + // Note that CG requires kCGBitmapByteOrder32Little in order to do + // subpixel AA at all (which we need it to do in both Subpixel and + // Alpha+smoothing mode). But little-endian is what we want anyway, so + // this works out nicely. + let color_type = match glyph_type { + GlyphType::Vector => kCGImageAlphaNoneSkipFirst, + GlyphType::Bitmap => kCGImageAlphaPremultipliedFirst, + }; + + CGContext::create_bitmap_context(None, + size.width as usize, + size.height as usize, + 8, + size.width as usize * 4, + &CGColorSpace::create_device_rgb(), + kCGBitmapByteOrder32Little | color_type) + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +enum GlyphType { + Vector, + Bitmap, +} + diff --git a/gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs b/gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs new file mode 100644 index 0000000000..5885d8f9a2 --- /dev/null +++ b/gfx/wr/wr_glyph_rasterizer/src/platform/unix/font.rs @@ -0,0 +1,1099 @@ +/* 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<FontVariation>, +} + +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<FontTemplate, Arc<Mutex<CachedFont>>>, + // 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<Arc<Mutex<CachedFont>>, 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<Mutex<CachedFont>>) { + 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<FontCache> = Mutex::new(FontCache::new()); + static ref LCD_FILTER_UNUSED: Condvar = Condvar::new(); +} + +pub struct FontContext { + fonts: FastHashMap<FontKey, Arc<Mutex<CachedFont>>>, +} + +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<u8>, 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<u8> { + 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<Vec<u8>>, 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<CachedFont>, 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<FT_Fixed> = 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<GlyphDimensions> { + 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<u32> { + 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<GlyphDimensions> { + 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, + }) + } +} + diff --git a/gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs b/gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs new file mode 100644 index 0000000000..81232d70c4 --- /dev/null +++ b/gfx/wr/wr_glyph_rasterizer/src/platform/windows/font.rs @@ -0,0 +1,639 @@ +/* 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::{FontInstanceFlags, FontKey, FontRenderMode, FontVariation}; +use api::{ColorU, GlyphDimensions, NativeFontHandle}; +use dwrote; +use crate::gamma_lut::{ColorLut, GammaLut}; +use crate::rasterizer::{FontInstance, FontTransform, GlyphKey}; +use crate::rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; +use crate::rasterizer::apply_multistrike_bold; +use crate::types::{FastHashMap, FastHashSet}; +use std::borrow::Borrow; +use std::collections::hash_map::Entry; +use std::hash::{Hash, Hasher}; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use api::FontInstancePlatformOptions; +use std::mem; + +lazy_static! { + static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor { + family_name: "Arial".to_owned(), + weight: dwrote::FontWeight::Regular, + stretch: dwrote::FontStretch::Normal, + style: dwrote::FontStyle::Normal, + }; +} + +type CachedFontKey = Arc<Path>; + +// A cached dwrote font file that is shared among all faces. +// Each face holds a CachedFontKey to keep track of how many users of the font there are. +struct CachedFont { + key: CachedFontKey, + file: dwrote::FontFile, +} + +// FontFile contains a ComPtr<IDWriteFontFile>, but DWrite font files are threadsafe. +unsafe impl Send for CachedFont {} + +impl PartialEq for CachedFont { + fn eq(&self, other: &CachedFont) -> bool { + self.key == other.key + } +} +impl Eq for CachedFont {} + +impl Hash for CachedFont { + fn hash<H: Hasher>(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl Borrow<Path> for CachedFont { + fn borrow(&self) -> &Path { + &*self.key + } +} + +lazy_static! { + // This is effectively a weak map of dwrote FontFiles. CachedFonts are entered into the + // cache when there are any FontFaces using them. CachedFonts are removed from the cache + // when there are no more FontFaces using them at all. + static ref FONT_CACHE: Mutex<FastHashSet<CachedFont>> = Mutex::new(FastHashSet::default()); +} + +struct FontFace { + cached: Option<CachedFontKey>, + file: dwrote::FontFile, + index: u32, + face: dwrote::FontFace, +} + +pub struct FontContext { + fonts: FastHashMap<FontKey, FontFace>, + variations: FastHashMap<(FontKey, dwrote::DWRITE_FONT_SIMULATIONS, Vec<FontVariation>), dwrote::FontFace>, + gamma_luts: FastHashMap<(u16, u8), GammaLut>, +} + +// DirectWrite is safe to use on multiple threads and non-shareable resources are +// all hidden inside their font context. +unsafe impl Send for FontContext {} + +fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE { + match render_mode { + FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1, + FontRenderMode::Alpha | + FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1, + } +} + +fn dwrite_measure_mode( + font: &FontInstance, + bitmaps: bool, +) -> dwrote::DWRITE_MEASURING_MODE { + if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) { + dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC + } else { + match font.render_mode { + FontRenderMode::Mono => dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC, + FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL, + } + } +} + +fn dwrite_render_mode( + font_face: &dwrote::FontFace, + font: &FontInstance, + em_size: f32, + measure_mode: dwrote::DWRITE_MEASURING_MODE, + bitmaps: bool, +) -> dwrote::DWRITE_RENDERING_MODE { + let dwrite_render_mode = match font.render_mode { + FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED, + FontRenderMode::Alpha | FontRenderMode::Subpixel => { + if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) { + dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC + } else if font.flags.contains(FontInstanceFlags::FORCE_SYMMETRIC) { + dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC + } else if font.flags.contains(FontInstanceFlags::NO_SYMMETRIC) { + dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL + } else { + font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode) + } + } + }; + + if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE { + // Outline mode is not supported + return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC; + } + + dwrite_render_mode +} + +fn is_bitmap_font(font: &FontInstance) -> bool { + // If bitmaps are requested, then treat as a bitmap font to disable transforms. + // If mono AA is requested, let that take priority over using bitmaps. + font.render_mode != FontRenderMode::Mono && + font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) +} + +impl FontContext { + pub fn distribute_across_threads() -> bool { + true + } + + pub fn new() -> FontContext { + FontContext { + fonts: FastHashMap::default(), + variations: FastHashMap::default(), + gamma_luts: FastHashMap::default(), + } + } + + fn add_font_descriptor(&mut self, font_key: &FontKey, desc: &dwrote::FontDescriptor) { + let system_fc = dwrote::FontCollection::get_system(false); + if let Some(font) = system_fc.get_font_from_descriptor(desc) { + let face = font.create_font_face(); + let file = face.get_files().pop().unwrap(); + let index = face.get_index(); + self.fonts.insert(*font_key, FontFace { cached: None, file, index, face }); + } + } + + pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc<Vec<u8>>, index: u32) { + if self.fonts.contains_key(font_key) { + return; + } + + if let Some(file) = dwrote::FontFile::new_from_data(data) { + if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) { + self.fonts.insert(*font_key, FontFace { cached: None, file, index, face }); + return; + } + } + // XXX add_raw_font needs to have a way to return an error + debug!("DWrite WR failed to load font from data, using Arial instead"); + self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR); + } + + pub fn add_native_font(&mut self, font_key: &FontKey, font_handle: NativeFontHandle) { + if self.fonts.contains_key(font_key) { + return; + } + + let index = font_handle.index; + let mut cache = FONT_CACHE.lock().unwrap(); + // Check to see if the font is already in the cache. If so, reuse it. + if let Some(font) = cache.get(font_handle.path.as_path()) { + if let Ok(face) = font.file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) { + self.fonts.insert( + *font_key, + FontFace { cached: Some(font.key.clone()), file: font.file.clone(), index, face }, + ); + return; + } + } + if let Some(file) = dwrote::FontFile::new_from_path(&font_handle.path) { + // The font is not in the cache yet, so try to create the font and insert it in the cache. + if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) { + let key: CachedFontKey = font_handle.path.into(); + self.fonts.insert( + *font_key, + FontFace { cached: Some(key.clone()), file: file.clone(), index, face }, + ); + cache.insert(CachedFont { key, file }); + return; + } + } + + // XXX add_native_font needs to have a way to return an error + debug!("DWrite WR failed to load font from path, using Arial instead"); + self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR); + } + + pub fn delete_font(&mut self, font_key: &FontKey) { + if let Some(face) = self.fonts.remove(font_key) { + self.variations.retain(|k, _| k.0 != *font_key); + // Check if this was a cached font. + if let Some(key) = face.cached { + let mut cache = FONT_CACHE.lock().unwrap(); + // If there are only two references left, that means only this face and + // the cache are using the font. So remove it from the cache. + if Arc::strong_count(&key) == 2 { + cache.remove(&*key); + } + } + } + } + + pub fn delete_font_instance(&mut self, instance: &FontInstance) { + // Ensure we don't keep around excessive amounts of stale variations. + if !instance.variations.is_empty() { + let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { + dwrote::DWRITE_FONT_SIMULATIONS_BOLD + } else { + dwrote::DWRITE_FONT_SIMULATIONS_NONE + }; + self.variations.remove(&(instance.font_key, sims, instance.variations.clone())); + } + } + + // Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite + // doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture + #[allow(dead_code)] + fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) { + // Rust doesn't have step_by support on stable :( + for i in 0 .. height { + let current_height = i * width * 3; + + for pixel in data[current_height .. current_height + (width * 3)].chunks(3) { + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + debug!("({}, {}, {}) ", r, g, b,); + } + } + } + + fn get_font_face( + &mut self, + font: &FontInstance, + ) -> &dwrote::FontFace { + if !font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) && + font.variations.is_empty() { + return &self.fonts.get(&font.font_key).unwrap().face; + } + let sims = if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { + dwrote::DWRITE_FONT_SIMULATIONS_BOLD + } else { + dwrote::DWRITE_FONT_SIMULATIONS_NONE + }; + match self.variations.entry((font.font_key, sims, font.variations.clone())) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let normal_face = self.fonts.get(&font.font_key).unwrap(); + if !font.variations.is_empty() { + if let Some(var_face) = normal_face.face.create_font_face_with_variations( + sims, + &font.variations.iter().map(|var| { + dwrote::DWRITE_FONT_AXIS_VALUE { + // OpenType tags are big-endian, but DWrite wants little-endian. + axisTag: var.tag.swap_bytes(), + value: var.value, + } + }).collect::<Vec<_>>(), + ) { + return entry.insert(var_face); + } + } + let var_face = normal_face.file + .create_face(normal_face.index, sims) + .unwrap_or_else(|_| normal_face.face.clone()); + entry.insert(var_face) + } + } + } + + fn create_glyph_analysis( + &mut self, + font: &FontInstance, + key: &GlyphKey, + size: f32, + transform: Option<dwrote::DWRITE_MATRIX>, + bitmaps: bool, + ) -> Result<(dwrote::GlyphRunAnalysis, dwrote::DWRITE_TEXTURE_TYPE, dwrote::RECT), dwrote::HRESULT> { + let face = self.get_font_face(font); + let glyph = key.index() as u16; + let advance = 0.0f32; + let offset = dwrote::GlyphOffset { + advanceOffset: 0.0, + ascenderOffset: 0.0, + }; + + let glyph_run = dwrote::DWRITE_GLYPH_RUN { + fontFace: unsafe { face.as_ptr() }, + fontEmSize: size, // size in DIPs (1/96", same as CSS pixels) + glyphCount: 1, + glyphIndices: &glyph, + glyphAdvances: &advance, + glyphOffsets: &offset, + isSideways: 0, + bidiLevel: 0, + }; + + let dwrite_measure_mode = dwrite_measure_mode(font, bitmaps); + let dwrite_render_mode = dwrite_render_mode( + face, + font, + size, + dwrite_measure_mode, + bitmaps, + ); + + let analysis = dwrote::GlyphRunAnalysis::create( + &glyph_run, + 1.0, + transform, + dwrite_render_mode, + dwrite_measure_mode, + 0.0, + 0.0, + )?; + let texture_type = dwrite_texture_type(font.render_mode); + let bounds = analysis.get_alpha_texture_bounds(texture_type)?; + // If the bounds are empty, then we might not be able to render the glyph with cleartype. + // Try again with aliased rendering to check if that works instead. + if font.render_mode != FontRenderMode::Mono && + (bounds.left == bounds.right || bounds.top == bounds.bottom) { + let analysis2 = dwrote::GlyphRunAnalysis::create( + &glyph_run, + 1.0, + transform, + dwrote::DWRITE_RENDERING_MODE_ALIASED, + dwrite_measure_mode, + 0.0, + 0.0, + )?; + let bounds2 = analysis2.get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_ALIASED_1x1)?; + if bounds2.left != bounds2.right && bounds2.top != bounds2.bottom { + return Ok((analysis2, dwrote::DWRITE_TEXTURE_ALIASED_1x1, bounds2)); + } + } + Ok((analysis, texture_type, bounds)) + } + + pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> { + let face = &self.fonts.get(&font_key).unwrap().face; + let indices = face.get_glyph_indices(&[ch as u32]); + indices.first().map(|idx| *idx as u32) + } + + pub fn get_glyph_dimensions( + &mut self, + font: &FontInstance, + key: &GlyphKey, + ) -> Option<GlyphDimensions> { + let (size, x_scale, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key); + let (_, _, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps).ok()?; + + let width = (bounds.right - bounds.left) as i32; + let height = (bounds.bottom - bounds.top) as i32; + + // Alpha texture bounds can sometimes return an empty rect + // Such as for spaces + if width == 0 || height == 0 { + return None; + } + + let (strike_scale, pixel_step) = if bitmaps { + (y_scale, 1.0) + } else { + (x_scale, y_scale / x_scale) + }; + let extra_strikes = font.get_extra_strikes(FontInstanceFlags::MULTISTRIKE_BOLD, strike_scale); + let extra_width = extra_strikes as f64 * pixel_step; + + let face = self.get_font_face(font); + face.get_design_glyph_metrics(&[key.index() as u16], false) + .first() + .map(|metrics| { + let em_size = size / 16.; + let design_units_per_pixel = face.metrics().metrics0().designUnitsPerEm as f32 / 16. as f32; + let scaled_design_units_to_pixels = em_size / design_units_per_pixel; + let advance = metrics.advanceWidth as f32 * scaled_design_units_to_pixels; + + GlyphDimensions { + left: bounds.left, + top: -bounds.top, + width: width + extra_width.ceil() as i32, + height, + advance: advance + extra_width as f32, + } + }) + } + + // DWrite ClearType gives us values in RGB, but WR expects BGRA. + fn convert_to_bgra( + &self, + pixels: &[u8], + width: usize, + height: usize, + texture_type: dwrote::DWRITE_TEXTURE_TYPE, + render_mode: FontRenderMode, + bitmaps: bool, + subpixel_bgr: bool, + padding: usize, + ) -> (Vec<u8>, bool) { + let (buffer_width, buffer_height) = (width + padding * 2, height + padding * 2); + let buffer_length = buffer_width * buffer_height * 4; + let mut bgra_pixels: Vec<u8> = vec![0; buffer_length]; + + match (texture_type, render_mode, bitmaps) { + (dwrote::DWRITE_TEXTURE_ALIASED_1x1, _, _) => { + assert!(width * height == pixels.len()); + let mut i = 0; + for row in padding .. height + padding { + let row_offset = row * buffer_width; + for col in padding .. width + padding { + let offset = (row_offset + col) * 4; + let alpha = pixels[i]; + i += 1; + bgra_pixels[offset + 0] = alpha; + bgra_pixels[offset + 1] = alpha; + bgra_pixels[offset + 2] = alpha; + bgra_pixels[offset + 3] = alpha; + } + } + (bgra_pixels, false) + } + (_, FontRenderMode::Subpixel, false) => { + assert!(width * height * 3 == pixels.len()); + let mut i = 0; + for row in padding .. height + padding { + let row_offset = row * buffer_width; + for col in padding .. width + padding { + let offset = (row_offset + col) * 4; + let (mut r, g, mut b) = (pixels[i + 0], pixels[i + 1], pixels[i + 2]); + if subpixel_bgr { + mem::swap(&mut r, &mut b); + } + i += 3; + bgra_pixels[offset + 0] = b; + bgra_pixels[offset + 1] = g; + bgra_pixels[offset + 2] = r; + bgra_pixels[offset + 3] = 0xff; + } + } + (bgra_pixels, true) + } + _ => { + assert!(width * height * 3 == pixels.len()); + let mut i = 0; + for row in padding .. height + padding { + let row_offset = row * buffer_width; + for col in padding .. width + padding { + let offset = (row_offset + col) * 4; + // Only take the G channel, as its closest to D2D + let alpha = pixels[i + 1] as u8; + i += 3; + bgra_pixels[offset + 0] = alpha; + bgra_pixels[offset + 1] = alpha; + bgra_pixels[offset + 2] = alpha; + bgra_pixels[offset + 3] = alpha; + } + } + (bgra_pixels, false) + } + } + } + + 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(255, 255, 255, 255); + // Subpixel positioning is disabled in mono mode. + font.disable_subpixel_position(); + } + FontRenderMode::Alpha => { + font.color = font.color.luminance_color().quantize(); + } + FontRenderMode::Subpixel => { + font.color = font.color.quantize(); + } + } + } + + fn get_glyph_parameters(font: &FontInstance, key: &GlyphKey) + -> (f32, f64, f64, bool, Option<dwrote::DWRITE_MATRIX>) { + let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + let scaled_size = font.size.to_f64_px() * y_scale; + let bitmaps = is_bitmap_font(font); + let (mut shape, (mut x_offset, mut y_offset)) = if bitmaps { + (FontTransform::identity(), (0.0, 0.0)) + } else { + (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key)) + }; + 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, scaled_size); + shape = shape_; + tx = tx_; + ty = ty_; + }; + x_offset += tx; + y_offset += ty; + let transform = if !shape.is_identity() || (x_offset, y_offset) != (0.0, 0.0) { + Some(dwrote::DWRITE_MATRIX { + m11: shape.scale_x, + m12: shape.skew_y, + m21: shape.skew_x, + m22: shape.scale_y, + dx: x_offset as f32, + dy: y_offset as f32, + }) + } else { + None + }; + (scaled_size as f32, x_scale, y_scale, bitmaps, transform) + } + + pub fn begin_rasterize(_font: &FontInstance) { + } + + pub fn end_rasterize(_font: &FontInstance) { + } + + pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { + let (size, x_scale, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key); + let (analysis, texture_type, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps) + .or(Err(GlyphRasterError::LoadFailed))?; + let mut width = (bounds.right - bounds.left) as i32; + let height = (bounds.bottom - bounds.top) as i32; + // Alpha texture bounds can sometimes return an empty rect + // Such as for spaces + if width == 0 || height == 0 { + return Err(GlyphRasterError::LoadFailed); + } + + let pixels = analysis.create_alpha_texture(texture_type, bounds).or(Err(GlyphRasterError::LoadFailed))?; + let padding = if font.use_texture_padding() { 1 } else { 0 }; + let (mut bgra_pixels, is_subpixel) = self.convert_to_bgra( + &pixels, + width as usize, + height as usize, + texture_type, + font.render_mode, + bitmaps, + font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR), + padding as usize, + ); + + // Apply multistrike bold, if necessary, and replace the current pixels with it. + let (strike_scale, pixel_step) = if bitmaps { + (y_scale, 1.0) + } else { + (x_scale, y_scale / x_scale) + }; + let extra_strikes = font.get_extra_strikes(FontInstanceFlags::MULTISTRIKE_BOLD, strike_scale); + if extra_strikes > 0 { + let (bold_pixels, bold_width) = apply_multistrike_bold( + &bgra_pixels, + (width + padding * 2) as usize, + (height + padding * 2) as usize, + is_subpixel, + extra_strikes, + pixel_step, + ); + width = bold_width as i32 - padding * 2; + bgra_pixels = bold_pixels; + } + + let FontInstancePlatformOptions { gamma, contrast, cleartype_level, .. } = + font.platform_options.unwrap_or_default(); + let gamma_lut = self.gamma_luts + .entry((gamma, contrast)) + .or_insert_with(|| + GammaLut::new( + contrast as f32 / 100.0, + gamma as f32 / 100.0, + gamma as f32 / 100.0, + )); + if is_subpixel { + gamma_lut.preblend_scaled(&mut bgra_pixels, font.color, cleartype_level); + } else { + gamma_lut.preblend(&mut bgra_pixels, font.color); + } + + let format = if bitmaps { + GlyphFormat::Bitmap + } else if texture_type == dwrote::DWRITE_TEXTURE_ALIASED_1x1 { + font.get_alpha_glyph_format() + } else { + font.get_glyph_format() + }; + + Ok(RasterizedGlyph { + left: (bounds.left - padding) as f32, + top: (-bounds.top + padding) as f32, + width: width + padding * 2, + height: height + padding * 2, + scale: (if bitmaps { y_scale.recip() } else { 1.0 }) as f32, + format, + bytes: bgra_pixels, + }) + } +} |