From 775ee2b5262f6a7f2d00183467ffc79682c2d0c6 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 26 Oct 2017 17:55:17 -0700 Subject: [PATCH] Add basic native rasterization for Core Graphics and match the WebRender `FontContext` API more --- demo/server/src/main.rs | 8 +- font-renderer/Cargo.toml | 4 +- font-renderer/src/core_graphics.rs | 160 +++++++++++++++++++++++---- font-renderer/src/directwrite/mod.rs | 6 +- font-renderer/src/freetype/mod.rs | 12 +- font-renderer/src/lib.rs | 63 +++++++++-- 6 files changed, 209 insertions(+), 44 deletions(-) diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index 199b14f7..d131deb8 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -31,7 +31,7 @@ extern crate serde_derive; use app_units::Au; use euclid::{Point2D, Transform2D}; use lru_cache::LruCache; -use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey}; +use pathfinder_font_renderer::{FontContext, FontInstance, FontKey, GlyphKey, SubpixelOffset}; use pathfinder_partitioner::mesh_library::MeshLibrary; use pathfinder_partitioner::partitioner::Partitioner; use pathfinder_path_utils::cubic::CubicCurve; @@ -270,7 +270,7 @@ fn partition_font(request: Json) // Parse glyph data. let font_key = FontKey::new(); - let font_instance_key = FontInstanceKey { + let font_instance = FontInstance { font_key: font_key, size: Au::from_f64_px(request.point_size), }; @@ -289,12 +289,12 @@ fn partition_font(request: Json) // Read glyph info. let mut path_buffer = PathBuffer::new(); let subpath_indices: Vec<_> = request.glyphs.iter().map(|glyph| { - let glyph_key = GlyphKey::new(glyph.id); + let glyph_key = GlyphKey::new(glyph.id, SubpixelOffset(0)); let first_subpath_index = path_buffer.subpaths.len(); // This might fail; if so, just leave it blank. - if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance_key, &glyph_key) { + if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance, &glyph_key) { let stream = Transform2DPathStream::new(glyph_outline.into_iter(), &glyph.transform); let stream = MonotonicPathCommandStream::new(stream); path_buffer.add_stream(stream) diff --git a/font-renderer/Cargo.toml b/font-renderer/Cargo.toml index 0b6a52e4..5f410819 100644 --- a/font-renderer/Cargo.toml +++ b/font-renderer/Cargo.toml @@ -12,6 +12,8 @@ app_units = "0.5" euclid = "0.15" libc = "0.2" log = "0.3" +serde = "1.0" +serde_derive = "1.0" [dependencies.freetype-sys] version = "0.6" @@ -27,11 +29,9 @@ freetype-sys = "0.6" [target.'cfg(target_os = "macos")'.dependencies.core-graphics] git = "https://github.com/pcwalton/rust-core-graphics.git" -branch = "cg-data-provider-fix" [target.'cfg(target_os = "macos")'.dependencies.core-text] git = "https://github.com/pcwalton/rust-core-text.git" -branch = "cg-data-provider-fix" [target.'cfg(target_os = "windows")'.dependencies] dwrite-sys = "0.2" diff --git a/font-renderer/src/core_graphics.rs b/font-renderer/src/core_graphics.rs index 5d344658..8d1416a3 100644 --- a/font-renderer/src/core_graphics.rs +++ b/font-renderer/src/core_graphics.rs @@ -8,6 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use core_graphics_sys::base::{kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Little}; +use core_graphics_sys::color_space::CGColorSpace; +use core_graphics_sys::context::{CGContext, CGTextDrawingMode}; use core_graphics_sys::data_provider::CGDataProvider; use core_graphics_sys::font::{CGFont, CGGlyph}; use core_graphics_sys::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGPoint, CGRect}; @@ -15,21 +18,36 @@ use core_graphics_sys::geometry::{CGSize, CG_ZERO_POINT}; use core_graphics_sys::path::CGPathElementType; use core_text::font::CTFont; use core_text; -use euclid::{Point2D, Size2D}; +use euclid::{Point2D, Size2D, Vector2D}; use pathfinder_path_utils::cubic::{CubicPathCommand, CubicPathCommandApproxStream}; use pathfinder_path_utils::PathCommand; use std::collections::BTreeMap; use std::collections::btree_map::Entry; use std::sync::Arc; -use {FontInstanceKey, FontKey, GlyphDimensions, GlyphKey}; +use {FontInstance, FontKey, GlyphDimensions, GlyphKey}; + +const CG_ZERO_RECT: CGRect = CGRect { + origin: CG_ZERO_POINT, + size: CGSize { + width: 0.0, + height: 0.0, + }, +}; const CURVE_APPROX_ERROR_BOUND: f32 = 0.1; +// A conservative overestimate of the amount of font dilation that Core Graphics performs, as a +// fraction of ppem. +// +// The actual amount as of High Sierra is 0.0121 in the X direction and 0.015125 in the Y +// direction. +const FONT_DILATION_AMOUNT: f32 = 0.02; + pub type GlyphOutline = Vec; pub struct FontContext { core_graphics_fonts: BTreeMap, - core_text_fonts: BTreeMap, + core_text_fonts: BTreeMap, } impl FontContext { @@ -66,49 +84,68 @@ impl FontContext { } } - fn ensure_core_text_font(&mut self, font_instance_key: &FontInstanceKey) - -> Result { - match self.core_text_fonts.entry(*font_instance_key) { + fn ensure_core_text_font(&mut self, font_instance: &FontInstance) -> Result { + match self.core_text_fonts.entry(*font_instance) { Entry::Occupied(entry) => Ok((*entry.get()).clone()), Entry::Vacant(entry) => { let core_graphics_font = match self.core_graphics_fonts - .get(&font_instance_key.font_key) { + .get(&font_instance.font_key) { None => return Err(()), Some(core_graphics_font) => core_graphics_font, }; - let core_text_font = try!(font_instance_key.instantiate(&core_graphics_font)); + let core_text_font = try!(font_instance.instantiate(&core_graphics_font)); entry.insert(core_text_font.clone()); Ok(core_text_font) } } } - pub fn glyph_dimensions(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) - -> Option { + pub fn glyph_dimensions(&self, font_instance: &FontInstance, glyph_key: &GlyphKey) + -> Result { let core_graphics_font = match self.core_graphics_fonts.get(&font_instance.font_key) { - None => return None, + None => return Err(()), Some(core_graphics_font) => core_graphics_font, }; let glyph = glyph_key.glyph_index as CGGlyph; - let mut bounding_boxes = [CGRect::new(&CG_ZERO_POINT, &CGSize::new(0.0, 0.0))]; + let mut bounding_boxes = [CG_ZERO_RECT]; let mut advances = [0]; if !core_graphics_font.get_glyph_b_boxes(&[glyph], &mut bounding_boxes) || !core_graphics_font.get_glyph_advances(&[glyph], &mut advances) { - return None + return Err(()) } - Some(GlyphDimensions { - origin: Point2D::new(bounding_boxes[0].origin.x.round() as i32, - bounding_boxes[0].origin.y.round() as i32), - size: Size2D::new(bounding_boxes[0].size.width.round() as u32, - bounding_boxes[0].size.height.round() as u32), + // FIXME(pcwalton): Vertical subpixel offsets. + let subpixel_offset = Point2D::new(glyph_key.subpixel_offset.into(), 0.0); + + // Round out to pixel boundaries. + let bounding_box = &bounding_boxes[0]; + let mut lower_left = Point2D::new(bounding_box.origin.x.floor() as i32, + bounding_box.origin.y.floor() as i32); + let mut upper_right = Point2D::new((bounding_box.origin.x + bounding_box.size.width + + subpixel_offset.x).ceil() as i32, + (bounding_box.origin.y + bounding_box.size.height + + subpixel_offset.y).ceil() as i32); + + // Core Graphics performs font dilation to expand the outlines a bit. As of High Sierra, + // the values seem to be 1.21% in the X direction and 1.5125% in the Y direction. Make sure + // that there's enough room to account for this. We round the values up to 2% to account + // for the possibility that Apple might tweak this later. + let font_dilation_radius = (font_instance.size.to_f32_px() * FONT_DILATION_AMOUNT * + 0.5).ceil() as i32; + lower_left += Vector2D::new(-font_dilation_radius, -font_dilation_radius); + upper_right += Vector2D::new(font_dilation_radius, font_dilation_radius); + + Ok(GlyphDimensions { + origin: lower_left, + size: Size2D::new((upper_right.x - lower_left.x) as u32, + (upper_right.y - lower_left.y) as u32), advance: advances[0] as f32, }) } - pub fn glyph_outline(&mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) + pub fn glyph_outline(&mut self, font_instance: &FontInstance, glyph_key: &GlyphKey) -> Result { let core_text_font = try!(self.ensure_core_text_font(font_instance)); let path = try!(core_text_font.create_path_for_glyph(glyph_key.glyph_index as CGGlyph, @@ -147,10 +184,93 @@ impl FontContext { Point2D::new(core_graphics_point.x as f32, core_graphics_point.y as f32) } } + + /// Uses the native Core Graphics library to rasterize a glyph on CPU. + pub fn rasterize_glyph_with_native_rasterizer(&self, + font_instance: &FontInstance, + glyph_key: &GlyphKey) + -> Result { + let core_graphics_font = match self.core_graphics_fonts.get(&font_instance.font_key) { + None => return Err(()), + Some(core_graphics_font) => core_graphics_font, + }; + + let dimensions = try!(self.glyph_dimensions(font_instance, glyph_key)); + + // TODO(pcwalton): Add support for non-subpixel render modes. + let bitmap_context_flags = kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst; + + let mut core_graphics_context = + CGContext::create_bitmap_context(None, + dimensions.size.width as usize, + dimensions.size.height as usize, + 8, + dimensions.size.width as usize * 4, + &CGColorSpace::create_device_rgb(), + bitmap_context_flags); + + // TODO(pcwalton): Add support for non-subpixel render modes. + let (antialias, smooth, bg_color) = (true, true, 1.0); + + // Use subpixel positioning. But don't let Core Graphics quantize, because we do that + // ourselves. + core_graphics_context.set_allows_font_subpixel_positioning(true); + core_graphics_context.set_should_subpixel_position_fonts(true); + core_graphics_context.set_allows_font_subpixel_quantization(false); + core_graphics_context.set_should_subpixel_quantize_fonts(false); + + // Set up antialiasing flags. + core_graphics_context.set_allows_font_smoothing(smooth); + core_graphics_context.set_should_smooth_fonts(smooth); + core_graphics_context.set_allows_antialiasing(antialias); + core_graphics_context.set_should_antialias(antialias); + + // Set up the background. + core_graphics_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_color); + core_graphics_context.fill_rect(CGRect { + origin: CG_ZERO_POINT, + size: CGSize { + width: dimensions.size.width as f64, + height: dimensions.size.height as f64, + }, + }); + + // Set up the text color. + core_graphics_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0); + core_graphics_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); + + // Set up the font. + core_graphics_context.set_font(core_graphics_font); + core_graphics_context.set_font_size(font_instance.size.to_f64_px()); + + // Compute the rasterization origin. + // TODO(pcwalton): Vertical subpixel positioning. + let subpixel_offset = Point2D::new(glyph_key.subpixel_offset.into(), 0.0); + let origin = CGPoint { + x: -dimensions.origin.x as f64 + subpixel_offset.x, + y: -dimensions.origin.y as f64, + }; + + // Draw the glyph, and extract the pixels. + core_graphics_context.show_glyphs_at_positions(&[glyph_key.glyph_index as CGGlyph], + &[origin]); + let pixels = core_graphics_context.data().to_vec(); + + // Return the image. + Ok(GlyphImage { + dimensions: dimensions, + pixels: pixels, + }) + } } -impl FontInstanceKey { +impl FontInstance { fn instantiate(&self, core_graphics_font: &CGFont) -> Result { Ok(core_text::font::new_from_CGFont(core_graphics_font, self.size.to_f64_px())) } } + +pub struct GlyphImage { + pub dimensions: GlyphDimensions, + pub pixels: Vec, +} diff --git a/font-renderer/src/directwrite/mod.rs b/font-renderer/src/directwrite/mod.rs index bcaef015..44dc31dc 100644 --- a/font-renderer/src/directwrite/mod.rs +++ b/font-renderer/src/directwrite/mod.rs @@ -34,7 +34,7 @@ use winapi::{IDWriteFontFileStreamVtbl, IDWriteGdiInterop, IDWriteGeometrySink, use winapi::{IUnknownVtbl, TRUE, UINT16, UINT32, UINT64, UINT}; use self::com::{PathfinderCoclass, PathfinderComObject, PathfinderComPtr}; -use {FontInstanceKey, FontKey, GlyphDimensions, GlyphKey}; +use {FontInstance, FontKey, GlyphDimensions, GlyphKey}; mod com; @@ -182,7 +182,7 @@ impl FontContext { self.dwrite_font_faces.remove(font_key); } - pub fn glyph_dimensions(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) + pub fn glyph_dimensions(&self, font_instance: &FontInstance, glyph_key: &GlyphKey) -> Option { unsafe { let font_face = match self.dwrite_font_faces.get(&font_instance.font_key) { @@ -207,7 +207,7 @@ impl FontContext { } } - pub fn glyph_outline(&mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) + pub fn glyph_outline(&mut self, font_instance: &FontInstance, glyph_key: &GlyphKey) -> Result { unsafe { let font_face = match self.dwrite_font_faces.get(&font_instance.font_key) { diff --git a/font-renderer/src/freetype/mod.rs b/font-renderer/src/freetype/mod.rs index b033a3ae..503f1b46 100644 --- a/font-renderer/src/freetype/mod.rs +++ b/font-renderer/src/freetype/mod.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use {FontKey, FontInstanceKey, GlyphDimensions, GlyphKey}; +use {FontKey, FontInstance, GlyphDimensions, GlyphKey}; use app_units::Au; use euclid::{Point2D, Size2D}; use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_OUTLINE}; @@ -90,14 +90,14 @@ impl FontContext { self.faces.remove(font_key); } - pub fn glyph_dimensions(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) + pub fn glyph_dimensions(&self, font_instance: &FontInstance, glyph_key: &GlyphKey) -> Option { self.load_glyph(font_instance, glyph_key).and_then(|glyph_slot| { self.glyph_dimensions_from_slot(font_instance, glyph_key, glyph_slot) }) } - pub fn glyph_outline<'a>(&'a mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) + pub fn glyph_outline<'a>(&'a mut self, font_instance: &FontInstance, glyph_key: &GlyphKey) -> Result, ()> { self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| { unsafe { @@ -109,7 +109,7 @@ impl FontContext { }) } - fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) + fn load_glyph(&self, font_instance: &FontInstance, glyph_key: &GlyphKey) -> Option { let face = match self.faces.get(&font_instance.font_key) { None => return None, @@ -134,7 +134,7 @@ impl FontContext { } fn glyph_dimensions_from_slot(&self, - font_instance: &FontInstanceKey, + font_instance: &FontInstance, glyph_key: &GlyphKey, glyph_slot: FT_GlyphSlot) -> Option { @@ -160,7 +160,7 @@ impl FontContext { // Returns the bounding box for a glyph, accounting for subpixel positioning as appropriate. // // TODO(pcwalton): Subpixel positioning. - fn bounding_box_from_slot(&self, _: &FontInstanceKey, _: &GlyphKey, glyph_slot: FT_GlyphSlot) + fn bounding_box_from_slot(&self, _: &FontInstance, _: &GlyphKey, glyph_slot: FT_GlyphSlot) -> FT_BBox { let mut bounding_box: FT_BBox; unsafe { diff --git a/font-renderer/src/lib.rs b/font-renderer/src/lib.rs index 5b53a12d..2e6d39a0 100644 --- a/font-renderer/src/lib.rs +++ b/font-renderer/src/lib.rs @@ -12,11 +12,15 @@ extern crate app_units; extern crate euclid; extern crate libc; extern crate pathfinder_path_utils; +extern crate serde; #[allow(unused_imports)] #[macro_use] extern crate log; +#[macro_use] +extern crate serde_derive; + #[cfg(test)] extern crate env_logger; @@ -57,7 +61,9 @@ mod directwrite; #[cfg(any(target_os = "linux", feature = "freetype"))] mod freetype; -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +const SUBPIXEL_GRANULARITY: u8 = 4; + +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct FontKey { id: usize, } @@ -71,39 +77,78 @@ impl FontKey { } } -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct FontInstanceKey { - pub font_key: FontKey, - pub size: Au, + id: usize, } impl FontInstanceKey { #[inline] - pub fn new(font_key: &FontKey, size: Au) -> FontInstanceKey { + pub fn new() -> FontInstanceKey { + static NEXT_FONT_INSTANCE_KEY_ID: AtomicUsize = ATOMIC_USIZE_INIT; FontInstanceKey { + id: NEXT_FONT_INSTANCE_KEY_ID.fetch_add(1, Ordering::Relaxed), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] +pub struct FontInstance { + pub font_key: FontKey, + + // The font size is in *device* pixels, not logical pixels. + // It is stored as an Au since we need sub-pixel sizes, but + // we can't store an f32 due to use of this type as a hash key. + // TODO(gw): Perhaps consider having LogicalAu and DeviceAu + // or something similar to that. + pub size: Au, +} + +impl FontInstance { + #[inline] + pub fn new(font_key: &FontKey, size: Au) -> FontInstance { + FontInstance { font_key: *font_key, size: size, } } } -// FIXME(pcwalton): Subpixel offsets? -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct SubpixelOffset(pub u8); + +impl Into for SubpixelOffset { + #[inline] + fn into(self) -> f32 { + self.0 as f32 / SUBPIXEL_GRANULARITY as f32 + } +} + +impl Into for SubpixelOffset { + #[inline] + fn into(self) -> f64 { + self.0 as f64 / SUBPIXEL_GRANULARITY as f64 + } +} + +#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct GlyphKey { pub glyph_index: u32, + pub subpixel_offset: SubpixelOffset, } impl GlyphKey { #[inline] - pub fn new(glyph_index: u32) -> GlyphKey { + pub fn new(glyph_index: u32, subpixel_offset: SubpixelOffset) -> GlyphKey { GlyphKey { glyph_index: glyph_index, + subpixel_offset: subpixel_offset, } } } #[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub struct GlyphDimensions { pub origin: Point2D, pub size: Size2D,