diff --git a/gfx/2d/ScaledFontMac.cpp b/gfx/2d/ScaledFontMac.cpp index d34d6b3ed68f..e42aed1c3640 100644 --- a/gfx/2d/ScaledFontMac.cpp +++ b/gfx/2d/ScaledFontMac.cpp @@ -450,7 +450,7 @@ bool ScaledFontMac::GetWRFontInstanceOptions( options.flags |= wr::FontInstanceFlags::FONT_SMOOTHING; } if (mApplySyntheticBold) { - options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; + options.flags |= wr::FontInstanceFlags::MULTISTRIKE_BOLD; } if (mHasColorGlyphs) { options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; @@ -472,7 +472,7 @@ ScaledFontMac::InstanceData::InstanceData( if (!(aOptions->flags & wr::FontInstanceFlags::FONT_SMOOTHING)) { mUseFontSmoothing = false; } - if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) { + if (aOptions->flags & wr::FontInstanceFlags::MULTISTRIKE_BOLD) { mApplySyntheticBold = true; } if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) { diff --git a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs index 17a0f1a79049..1437f3b2926e 100644 --- a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs +++ b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs @@ -605,7 +605,7 @@ impl FontInstance { #[allow(dead_code)] pub fn get_extra_strikes(&self, x_scale: f64) -> usize { - if self.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { + if self.flags.contains(FontInstanceFlags::MULTISTRIKE_BOLD) { let mut bold_offset = self.size.to_f64_px() / 48.0; if bold_offset < 1.0 { bold_offset = 0.25 + 0.75 * bold_offset; @@ -772,6 +772,119 @@ impl GlyphFormat { } } +#[allow(dead_code)] +#[inline] +fn blend_strike_pixel(dest: u8, src: u32, src_alpha: u32) -> u8 { + // Assume premultiplied alpha such that src and dest are already multiplied + // by their respective alpha values and in range 0..=255. The rounded over + // blend is then (src * 255 + dest * (255 - src_alpha) + 128) / 255. + // We approximate (x + 128) / 255 as (x + 128 + ((x + 128) >> 8)) >> 8. + let x = src * 255 + dest as u32 * (255 - src_alpha) + 128; + ((x + (x >> 8)) >> 8) as u8 +} + +// Blends a single strike at a given offset into a destination buffer, assuming +// the destination has been allocated with enough extra space to accommodate the +// offset. +#[allow(dead_code)] +fn blend_strike( + dest_bitmap: &mut [u8], + src_bitmap: &[u8], + width: usize, + height: usize, + subpixel_mask: bool, + offset: f64, +) { + let dest_stride = dest_bitmap.len() / height; + let src_stride = width * 4; + let offset_integer = offset.floor() as usize * 4; + let offset_fract = (offset.fract() * 256.0) as u32; + for (src_row, dest_row) in src_bitmap.chunks(src_stride).zip(dest_bitmap.chunks_mut(dest_stride)) { + let mut prev_px = [0u32; 4]; + let dest_row_offset = &mut dest_row[offset_integer .. offset_integer + src_stride]; + for (src, dest) in src_row.chunks(4).zip(dest_row_offset.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 fractional offset. + let next_px = [px[0] * offset_fract, + px[1] * offset_fract, + px[2] * offset_fract, + px[3] * offset_fract]; + let offset_px = [(((px[0] << 8) - next_px[0]) + prev_px[0] + 128) >> 8, + (((px[1] << 8) - next_px[1]) + prev_px[1] + 128) >> 8, + (((px[2] << 8) - next_px[2]) + prev_px[2] + 128) >> 8, + (((px[3] << 8) - next_px[3]) + prev_px[3] + 128) >> 8]; + if subpixel_mask { + // Subpixel masks assume each component is an independent weight. + dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[0]); + dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[1]); + dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[2]); + dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]); + } else { + // Otherwise assume we have a premultiplied alpha BGRA value. + dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[3]); + dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[3]); + dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[3]); + dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]); + } + // Save the remainder for blending onto the next pixel. + prev_px = next_px; + } + if offset_fract > 0 { + // When there is fractional offset, there will be a remaining value + // from the previous pixel but no next pixel, so just use that. + let dest = &mut dest_row[offset_integer + src_stride .. ]; + let offset_px = [(prev_px[0] + 128) >> 8, + (prev_px[1] + 128) >> 8, + (prev_px[2] + 128) >> 8, + (prev_px[3] + 128) >> 8]; + if subpixel_mask { + dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[0]); + dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[1]); + dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[2]); + dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]); + } else { + dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[3]); + dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[3]); + dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[3]); + dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]); + } + } + } +} + +// Applies multistrike bold to a source bitmap. This assumes the source bitmap +// is a tighly packed slice of BGRA pixel values of exactly the specified width +// and height. The specified extra strikes and pixel step control where to put +// each strike. The pixel step is allowed to have a fractional offset and does +// not strictly need to be integer. +#[allow(dead_code)] +pub fn apply_multistrike_bold( + src_bitmap: &[u8], + width: usize, + height: usize, + subpixel_mask: bool, + extra_strikes: usize, + pixel_step: f64, +) -> (Vec, usize) { + let src_stride = width * 4; + // The amount of extra width added to the bitmap from the extra strikes. + let extra_width = (extra_strikes as f64 * pixel_step).ceil() as usize; + let dest_width = width + extra_width; + let dest_stride = dest_width * 4; + // Zero out the initial bitmap so any extra width is cleared. + let mut dest_bitmap = vec![0u8; dest_stride * height]; + for (src_row, dest_row) in src_bitmap.chunks(src_stride).zip(dest_bitmap.chunks_mut(dest_stride)) { + // Copy the initial bitmap strike rows directly from the source. + dest_row[0 .. src_stride].copy_from_slice(src_row); + } + // Finally blend each extra strike in turn. + for i in 1 ..= extra_strikes { + let offset = i as f64 * pixel_step; + blend_strike(&mut dest_bitmap, src_bitmap, width, height, subpixel_mask, offset); + } + (dest_bitmap, dest_width) +} + pub struct RasterizedGlyph { pub top: f32, pub left: f32, diff --git a/gfx/wr/webrender/src/platform/windows/font.rs b/gfx/wr/webrender/src/platform/windows/font.rs index 3e4ef10b1722..21fa423ed532 100644 --- a/gfx/wr/webrender/src/platform/windows/font.rs +++ b/gfx/wr/webrender/src/platform/windows/font.rs @@ -5,11 +5,11 @@ use api::{FontInstanceFlags, FontKey, FontRenderMode, FontVariation}; use api::{ColorU, GlyphDimensions, NativeFontHandle}; use dwrote; -use crate::gamma_lut::ColorLut; +use crate::gamma_lut::{ColorLut, GammaLut}; use crate::glyph_rasterizer::{FontInstance, FontTransform, GlyphKey}; -use crate::internal_types::{FastHashMap, FastHashSet, ResourceCacheError}; use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; -use crate::gamma_lut::GammaLut; +use crate::glyph_rasterizer::apply_multistrike_bold; +use crate::internal_types::{FastHashMap, FastHashSet, ResourceCacheError}; use std::borrow::Borrow; use std::collections::hash_map::Entry; use std::hash::{Hash, Hasher}; @@ -377,7 +377,7 @@ impl FontContext { font: &FontInstance, key: &GlyphKey, ) -> Option { - let (size, _, bitmaps, transform) = Self::get_glyph_parameters(font, key); + 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; @@ -389,6 +389,14 @@ impl FontContext { 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(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() @@ -401,9 +409,9 @@ impl FontContext { GlyphDimensions { left: bounds.left, top: -bounds.top, - width, + width: width + extra_width.ceil() as i32, height, - advance, + advance: advance + extra_width as f32, } }) } @@ -418,14 +426,9 @@ impl FontContext { render_mode: FontRenderMode, bitmaps: bool, subpixel_bgr: bool, - texture_padding: bool, - ) -> Vec { - let (buffer_width, buffer_height, padding) = if texture_padding { - (width + 2, height + 2, 1) - } else { - (width, height, 0) - }; - + padding: usize, + ) -> (Vec, 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 = vec![0; buffer_length]; @@ -445,6 +448,7 @@ impl FontContext { bgra_pixels[offset + 3] = alpha; } } + (bgra_pixels, false) } (_, FontRenderMode::Subpixel, false) => { assert!(width * height * 3 == pixels.len()); @@ -464,6 +468,7 @@ impl FontContext { bgra_pixels[offset + 3] = 0xff; } } + (bgra_pixels, true) } _ => { assert!(width * height * 3 == pixels.len()); @@ -481,9 +486,9 @@ impl FontContext { bgra_pixels[offset + 3] = alpha; } } + (bgra_pixels, false) } - }; - bgra_pixels + } } pub fn prepare_font(font: &mut FontInstance) { @@ -503,8 +508,9 @@ impl FontContext { } } - fn get_glyph_parameters(font: &FontInstance, key: &GlyphKey) -> (f32, f64, bool, Option) { - let (_, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + fn get_glyph_parameters(font: &FontInstance, key: &GlyphKey) + -> (f32, f64, f64, bool, Option) { + 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 { @@ -542,14 +548,14 @@ impl FontContext { } else { None }; - (scaled_size as f32, y_scale, bitmaps, transform) + (scaled_size as f32, x_scale, y_scale, bitmaps, transform) } pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { - let (size, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key); + 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 width = (bounds.right - bounds.left) as i32; + 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 @@ -558,10 +564,37 @@ impl FontContext { } let pixels = analysis.create_alpha_texture(texture_type, bounds).or(Err(GlyphRasterError::LoadFailed))?; - let mut bgra_pixels = self.convert_to_bgra(&pixels, width as usize, height as usize, - texture_type, font.render_mode, bitmaps, - font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR), - font.use_texture_padding()); + 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(strike_scale); + if extra_strikes > 0 { + let (bold_pixels, bold_width) = apply_multistrike_bold( + &bgra_pixels, + (width + padding * 2) as usize, + height 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(); @@ -573,11 +606,10 @@ impl FontContext { gamma as f32 / 100.0, gamma as f32 / 100.0, )); - if bitmaps || texture_type == dwrote::DWRITE_TEXTURE_ALIASED_1x1 || - font.render_mode != FontRenderMode::Subpixel { - gamma_lut.preblend(&mut bgra_pixels, font.color); - } else { + 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 { @@ -588,7 +620,6 @@ impl FontContext { font.get_glyph_format() }; - let padding = if font.use_texture_padding() { 1 } else { 0 }; Ok(RasterizedGlyph { left: (bounds.left - padding) as f32, top: (-bounds.top + padding) as f32, diff --git a/gfx/wr/webrender_api/src/font.rs b/gfx/wr/webrender_api/src/font.rs index 3052db8f9e13..cf805432c4d6 100644 --- a/gfx/wr/webrender_api/src/font.rs +++ b/gfx/wr/webrender_api/src/font.rs @@ -359,6 +359,7 @@ bitflags! { #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)] pub struct FontInstanceFlags: u32 { // Common flags + // Use native synthetic bold, if supported. const SYNTHETIC_BOLD = 1 << 1; const EMBEDDED_BITMAPS = 1 << 2; const SUBPIXEL_BGR = 1 << 3; @@ -367,6 +368,8 @@ bitflags! { const FLIP_Y = 1 << 6; const SUBPIXEL_POSITION = 1 << 7; const VERTICAL = 1 << 8; + // Explicitly use multi-strike bold emulation. + const MULTISTRIKE_BOLD = 1 << 9; // Internal flags const TRANSFORM_GLYPHS = 1 << 12;