Add basic native rasterization for Core Graphics and match the WebRender

`FontContext` API more
This commit is contained in:
Patrick Walton 2017-10-26 17:55:17 -07:00
parent f8c950d39a
commit 775ee2b526
6 changed files with 209 additions and 44 deletions

View File

@ -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<PartitionFontRequest>)
// 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<PartitionFontRequest>)
// 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)

View File

@ -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"

View File

@ -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<PathCommand>;
pub struct FontContext {
core_graphics_fonts: BTreeMap<FontKey, CGFont>,
core_text_fonts: BTreeMap<FontInstanceKey, CTFont>,
core_text_fonts: BTreeMap<FontInstance, CTFont>,
}
impl FontContext {
@ -66,49 +84,68 @@ impl FontContext {
}
}
fn ensure_core_text_font(&mut self, font_instance_key: &FontInstanceKey)
-> Result<CTFont, ()> {
match self.core_text_fonts.entry(*font_instance_key) {
fn ensure_core_text_font(&mut self, font_instance: &FontInstance) -> Result<CTFont, ()> {
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<GlyphDimensions> {
pub fn glyph_dimensions(&self, font_instance: &FontInstance, glyph_key: &GlyphKey)
-> Result<GlyphDimensions, ()> {
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<GlyphOutline, ()> {
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<GlyphImage, ()> {
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<CTFont, ()> {
Ok(core_text::font::new_from_CGFont(core_graphics_font, self.size.to_f64_px()))
}
}
pub struct GlyphImage {
pub dimensions: GlyphDimensions,
pub pixels: Vec<u8>,
}

View File

@ -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<GlyphDimensions> {
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<GlyphOutline, ()> {
unsafe {
let font_face = match self.dwrite_font_faces.get(&font_instance.font_key) {

View File

@ -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<GlyphDimensions> {
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<GlyphOutline<'a>, ()> {
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<FT_GlyphSlot> {
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<GlyphDimensions> {
@ -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 {

View File

@ -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<f32> for SubpixelOffset {
#[inline]
fn into(self) -> f32 {
self.0 as f32 / SUBPIXEL_GRANULARITY as f32
}
}
impl Into<f64> 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<i32>,
pub size: Size2D<u32>,