Make the FreeType API match the Core Graphics one

This commit is contained in:
Patrick Walton 2018-03-05 16:31:48 -08:00
parent fad7f2f343
commit f718aa9ce3
4 changed files with 76 additions and 31 deletions

View File

@ -41,12 +41,16 @@ const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING;
const DPI: u32 = 72;
const STEM_DARKENING_AMOUNT: f32 = 0.02;
/// An object that loads and renders fonts using the FreeType library.
pub struct FontContext<FK> where FK: Clone + Hash + Eq + Ord {
library: FT_Library,
faces: BTreeMap<FK, Face>,
}
unsafe impl<FK> Send for FontContext<FK> where FK: Clone + Hash + Eq + Ord + Send {}
impl<FK> FontContext<FK> where FK: Clone + Hash + Eq + Ord {
/// Creates a new font context instance.
pub fn new() -> Result<FontContext<FK>, ()> {
@ -112,14 +116,17 @@ impl<FK> FontContext<FK> where FK: Clone + Hash + Eq + Ord {
/// libraries (including Pathfinder) apply modifications to the outlines: for example, to
/// dilate them for easier reading. To retrieve extents that account for these modifications,
/// set `exact` to false.
pub fn glyph_dimensions(&self, font_instance: &FontInstance<FK>, glyph_key: &GlyphKey)
pub fn glyph_dimensions(&self,
font_instance: &FontInstance<FK>,
glyph_key: &GlyphKey,
exact: bool)
-> Result<GlyphDimensions, ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).and_then(|glyph_slot| {
self.glyph_dimensions_from_slot(font_instance, glyph_key, glyph_slot)
self.glyph_dimensions_from_slot(font_instance, glyph_key, glyph_slot, exact)
})
}
pub fn glyph_outline<'a>(&'a mut self, font_instance: &FontInstance<FK>, glyph_key: &GlyphKey)
pub fn glyph_outline<'a>(&'a self, font_instance: &FontInstance<FK>, glyph_key: &GlyphKey)
-> Result<GlyphOutline<'a>, ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
unsafe {
@ -130,11 +137,11 @@ impl<FK> FontContext<FK> where FK: Clone + Hash + Eq + Ord {
/// Uses the FreeType library to rasterize a glyph on CPU.
///
/// Pathfinder uses this for reference testing.
///
/// If `exact` is true, then the glyph image will have precisely the size specified by the font
/// designer. Because some font libraries, such as Core Graphics, perform modifications to the
/// glyph outlines, to ensure the entire outline fits it is best to pass false for `exact`.
/// If `exact` is true, then the raw outline extents as specified by the font designer are
/// returned. These may differ from the extents when rendered on screen, because some font
/// libraries (including Pathfinder) apply modifications to the outlines: for example, to
/// dilate them for easier reading. To retrieve extents that account for these modifications,
/// set `exact` to false.
pub fn rasterize_glyph_with_native_rasterizer(&self,
font_instance: &FontInstance<FK>,
glyph_key: &GlyphKey,
@ -248,7 +255,8 @@ impl<FK> FontContext<FK> where FK: Clone + Hash + Eq + Ord {
fn glyph_dimensions_from_slot(&self,
font_instance: &FontInstance<FK>,
glyph_key: &GlyphKey,
glyph_slot: FT_GlyphSlot)
glyph_slot: FT_GlyphSlot,
exact: bool)
-> Result<GlyphDimensions, ()> {
unsafe {
let metrics = &(*glyph_slot).metrics;
@ -259,12 +267,27 @@ impl<FK> FontContext<FK> where FK: Clone + Hash + Eq + Ord {
}
let bounding_box = self.bounding_box_from_slot(font_instance, glyph_key, glyph_slot);
let mut lower_left = Point2D::new(f26dot6_to_i32_rounding_up(bounding_box.xMin),
f26dot6_to_i32_rounding_up(bounding_box.yMin));
let mut upper_right = Point2D::new(f26dot6_to_i32_rounding_up(bounding_box.xMax),
f26dot6_to_i32_rounding_up(bounding_box.yMax));
// Account for stem darkening. Round up to be conservative.
if !exact {
let stem_darkening_radius = (font_instance.size.to_f32_px() *
STEM_DARKENING_AMOUNT * 0.5).ceil() as i32;
lower_left += Vector2D::new(-stem_darkening_radius, -stem_darkening_radius);
upper_right += Vector2D::new(stem_darkening_radius, stem_darkening_radius);
}
Ok(GlyphDimensions {
origin: Point2D::new((bounding_box.xMin >> 6) as i32,
(bounding_box.yMax >> 6) as i32),
size: Size2D::new(((bounding_box.xMax - bounding_box.xMin) >> 6) as u32,
((bounding_box.yMax - bounding_box.yMin) >> 6) as u32),
advance: f32::from_ft_f26dot6(metrics.horiAdvance),
origin: lower_left,
size: Size2D::new((upper_right.x - lower_left.x) as u32,
(upper_right.y - lower_left.y) as u32),
advance: f32::from_ft_f26dot6(metrics.horiAdvance) /
(*(*glyph_slot).face).units_per_EM as f32 *
font_instance.size.to_f32_px(),
})
}
}
@ -308,3 +331,7 @@ unsafe fn convert_vec_u32_to_vec_u8(mut input: Vec<u32>) -> Vec<u8> {
mem::forget(input);
Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4)
}
fn f26dot6_to_i32_rounding_up(x: FT_F26Dot6) -> i32 {
((x + (1 << 5) - 1) >> 6) as i32
}

View File

@ -15,6 +15,7 @@ use lyon_path::{PathEvent, PathState};
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
#[derive(Clone)]
pub struct Outline<'a> {
outline: &'a FT_Outline,
}

View File

@ -292,7 +292,7 @@ impl MeshLibrary {
/// Computes vertex normals necessary for emboldening and/or stem darkening. This is intended
/// for stencil-and-cover.
pub fn push_stencil_normals<I>(&mut self, _path_id: u16, stream: I)
where I: Iterator<Item = PathEvent> + Clone {
where I: Iterator<Item = PathEvent> {
let mut normals = PathNormals::new();
normals.add_path(stream);
self.stencil_normals.extend(normals.normals().iter().map(|normals| {

View File

@ -40,9 +40,10 @@ impl PathNormals {
self.normals.clear()
}
pub fn add_path<I>(&mut self, mut stream: I) where I: Iterator<Item = PathEvent> + Clone {
let (mut path_stream, mut path_points) = (stream.clone(), vec![]);
pub fn add_path<I>(&mut self, mut stream: I) where I: Iterator<Item = PathEvent> {
let (mut path_ops, mut path_points) = (vec![], vec![]);
while let Some(event) = stream.next() {
path_ops.push(PathOp::from_path_event(&event));
match event {
PathEvent::MoveTo(to) => path_points.push(to),
PathEvent::LineTo(to) => path_points.push(to),
@ -53,18 +54,15 @@ impl PathNormals {
PathEvent::Arc(..) => {
panic!("PathNormals::add_path(): Convert arcs to quadratics first!")
}
PathEvent::Close => {
self.flush(path_stream, &mut path_points);
path_stream = stream.clone();
}
PathEvent::Close => self.flush(path_ops.drain(..), &mut path_points),
}
}
self.flush(path_stream, &mut path_points);
self.flush(path_ops.into_iter(), &mut path_points);
}
fn flush<I>(&mut self, path_stream: I, path_points: &mut Vec<Point2D<f32>>)
where I: Iterator<Item = PathEvent> + Clone {
where I: Iterator<Item = PathOp> {
match path_points.len() {
0 | 1 => path_points.clear(),
2 => {
@ -80,7 +78,7 @@ impl PathNormals {
}
fn flush_slow<I>(&mut self, path_stream: I, path_points: &mut Vec<Point2D<f32>>)
where I: Iterator<Item = PathEvent> + Clone {
where I: Iterator<Item = PathOp> {
let mut normals = vec![Vector2D::zero(); path_points.len()];
*normals.last_mut().unwrap() = compute_normal(&path_points[path_points.len() - 2],
&path_points[path_points.len() - 1],
@ -95,10 +93,10 @@ impl PathNormals {
path_points.clear();
let mut next_normal_index = 0;
for event in path_stream {
match event {
PathEvent::MoveTo(_) => next_normal_index += 1,
PathEvent::LineTo(_) => {
for op in path_stream {
match op {
PathOp::MoveTo => next_normal_index += 1,
PathOp::LineTo => {
next_normal_index += 1;
self.normals.push(SegmentNormals {
from: normals[next_normal_index - 2],
@ -106,7 +104,7 @@ impl PathNormals {
to: normals[next_normal_index - 1],
});
}
PathEvent::QuadraticTo(..) => {
PathOp::QuadraticTo => {
next_normal_index += 2;
self.normals.push(SegmentNormals {
from: normals[next_normal_index - 3],
@ -114,8 +112,7 @@ impl PathNormals {
to: normals[next_normal_index - 1],
})
}
PathEvent::CubicTo(..) | PathEvent::Arc(..) => unreachable!(),
PathEvent::Close => {
PathOp::Close => {
self.normals.push(SegmentNormals {
from: normals[next_normal_index - 1],
ctrl: Vector2D::zero(),
@ -133,3 +130,23 @@ fn compute_normal(prev: &Point2D<f32>, current: &Point2D<f32>, next: &Point2D<f3
let vector = ((*current - *prev) + (*next - *current)).normalize();
Vector2D::new(vector.y, -vector.x)
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum PathOp {
MoveTo,
LineTo,
QuadraticTo,
Close,
}
impl PathOp {
fn from_path_event(event: &PathEvent) -> PathOp {
match *event {
PathEvent::MoveTo(..) => PathOp::MoveTo,
PathEvent::LineTo(..) => PathOp::LineTo,
PathEvent::QuadraticTo(..) => PathOp::QuadraticTo,
PathEvent::Close => PathOp::Close,
PathEvent::Arc(..) | PathEvent::CubicTo(..) => unreachable!(),
}
}
}