Make the FreeType API match the Core Graphics one
This commit is contained in:
parent
fad7f2f343
commit
f718aa9ce3
|
@ -41,12 +41,16 @@ const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING;
|
||||||
|
|
||||||
const DPI: u32 = 72;
|
const DPI: u32 = 72;
|
||||||
|
|
||||||
|
const STEM_DARKENING_AMOUNT: f32 = 0.02;
|
||||||
|
|
||||||
/// An object that loads and renders fonts using the FreeType library.
|
/// An object that loads and renders fonts using the FreeType library.
|
||||||
pub struct FontContext<FK> where FK: Clone + Hash + Eq + Ord {
|
pub struct FontContext<FK> where FK: Clone + Hash + Eq + Ord {
|
||||||
library: FT_Library,
|
library: FT_Library,
|
||||||
faces: BTreeMap<FK, Face>,
|
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 {
|
impl<FK> FontContext<FK> where FK: Clone + Hash + Eq + Ord {
|
||||||
/// Creates a new font context instance.
|
/// Creates a new font context instance.
|
||||||
pub fn new() -> Result<FontContext<FK>, ()> {
|
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
|
/// libraries (including Pathfinder) apply modifications to the outlines: for example, to
|
||||||
/// dilate them for easier reading. To retrieve extents that account for these modifications,
|
/// dilate them for easier reading. To retrieve extents that account for these modifications,
|
||||||
/// set `exact` to false.
|
/// 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, ()> {
|
-> Result<GlyphDimensions, ()> {
|
||||||
self.load_glyph(font_instance, glyph_key).ok_or(()).and_then(|glyph_slot| {
|
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>, ()> {
|
-> Result<GlyphOutline<'a>, ()> {
|
||||||
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
|
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
|
||||||
unsafe {
|
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.
|
/// Uses the FreeType library to rasterize a glyph on CPU.
|
||||||
///
|
///
|
||||||
/// Pathfinder uses this for reference testing.
|
/// 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
|
||||||
/// If `exact` is true, then the glyph image will have precisely the size specified by the font
|
/// libraries (including Pathfinder) apply modifications to the outlines: for example, to
|
||||||
/// designer. Because some font libraries, such as Core Graphics, perform modifications to the
|
/// dilate them for easier reading. To retrieve extents that account for these modifications,
|
||||||
/// glyph outlines, to ensure the entire outline fits it is best to pass false for `exact`.
|
/// set `exact` to false.
|
||||||
pub fn rasterize_glyph_with_native_rasterizer(&self,
|
pub fn rasterize_glyph_with_native_rasterizer(&self,
|
||||||
font_instance: &FontInstance<FK>,
|
font_instance: &FontInstance<FK>,
|
||||||
glyph_key: &GlyphKey,
|
glyph_key: &GlyphKey,
|
||||||
|
@ -248,7 +255,8 @@ impl<FK> FontContext<FK> where FK: Clone + Hash + Eq + Ord {
|
||||||
fn glyph_dimensions_from_slot(&self,
|
fn glyph_dimensions_from_slot(&self,
|
||||||
font_instance: &FontInstance<FK>,
|
font_instance: &FontInstance<FK>,
|
||||||
glyph_key: &GlyphKey,
|
glyph_key: &GlyphKey,
|
||||||
glyph_slot: FT_GlyphSlot)
|
glyph_slot: FT_GlyphSlot,
|
||||||
|
exact: bool)
|
||||||
-> Result<GlyphDimensions, ()> {
|
-> Result<GlyphDimensions, ()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let metrics = &(*glyph_slot).metrics;
|
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 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 {
|
Ok(GlyphDimensions {
|
||||||
origin: Point2D::new((bounding_box.xMin >> 6) as i32,
|
origin: lower_left,
|
||||||
(bounding_box.yMax >> 6) as i32),
|
size: Size2D::new((upper_right.x - lower_left.x) as u32,
|
||||||
size: Size2D::new(((bounding_box.xMax - bounding_box.xMin) >> 6) as u32,
|
(upper_right.y - lower_left.y) as u32),
|
||||||
((bounding_box.yMax - bounding_box.yMin) >> 6) as u32),
|
advance: f32::from_ft_f26dot6(metrics.horiAdvance) /
|
||||||
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);
|
mem::forget(input);
|
||||||
Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use lyon_path::{PathEvent, PathState};
|
||||||
|
|
||||||
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
|
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Outline<'a> {
|
pub struct Outline<'a> {
|
||||||
outline: &'a FT_Outline,
|
outline: &'a FT_Outline,
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,7 +292,7 @@ impl MeshLibrary {
|
||||||
/// Computes vertex normals necessary for emboldening and/or stem darkening. This is intended
|
/// Computes vertex normals necessary for emboldening and/or stem darkening. This is intended
|
||||||
/// for stencil-and-cover.
|
/// for stencil-and-cover.
|
||||||
pub fn push_stencil_normals<I>(&mut self, _path_id: u16, stream: I)
|
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();
|
let mut normals = PathNormals::new();
|
||||||
normals.add_path(stream);
|
normals.add_path(stream);
|
||||||
self.stencil_normals.extend(normals.normals().iter().map(|normals| {
|
self.stencil_normals.extend(normals.normals().iter().map(|normals| {
|
||||||
|
|
|
@ -40,9 +40,10 @@ impl PathNormals {
|
||||||
self.normals.clear()
|
self.normals.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_path<I>(&mut self, mut stream: I) where I: Iterator<Item = PathEvent> + Clone {
|
pub fn add_path<I>(&mut self, mut stream: I) where I: Iterator<Item = PathEvent> {
|
||||||
let (mut path_stream, mut path_points) = (stream.clone(), vec![]);
|
let (mut path_ops, mut path_points) = (vec![], vec![]);
|
||||||
while let Some(event) = stream.next() {
|
while let Some(event) = stream.next() {
|
||||||
|
path_ops.push(PathOp::from_path_event(&event));
|
||||||
match event {
|
match event {
|
||||||
PathEvent::MoveTo(to) => path_points.push(to),
|
PathEvent::MoveTo(to) => path_points.push(to),
|
||||||
PathEvent::LineTo(to) => path_points.push(to),
|
PathEvent::LineTo(to) => path_points.push(to),
|
||||||
|
@ -53,18 +54,15 @@ impl PathNormals {
|
||||||
PathEvent::Arc(..) => {
|
PathEvent::Arc(..) => {
|
||||||
panic!("PathNormals::add_path(): Convert arcs to quadratics first!")
|
panic!("PathNormals::add_path(): Convert arcs to quadratics first!")
|
||||||
}
|
}
|
||||||
PathEvent::Close => {
|
PathEvent::Close => self.flush(path_ops.drain(..), &mut path_points),
|
||||||
self.flush(path_stream, &mut path_points);
|
|
||||||
path_stream = stream.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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>>)
|
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() {
|
match path_points.len() {
|
||||||
0 | 1 => path_points.clear(),
|
0 | 1 => path_points.clear(),
|
||||||
2 => {
|
2 => {
|
||||||
|
@ -80,7 +78,7 @@ impl PathNormals {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush_slow<I>(&mut self, path_stream: I, path_points: &mut Vec<Point2D<f32>>)
|
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()];
|
let mut normals = vec![Vector2D::zero(); path_points.len()];
|
||||||
*normals.last_mut().unwrap() = compute_normal(&path_points[path_points.len() - 2],
|
*normals.last_mut().unwrap() = compute_normal(&path_points[path_points.len() - 2],
|
||||||
&path_points[path_points.len() - 1],
|
&path_points[path_points.len() - 1],
|
||||||
|
@ -95,10 +93,10 @@ impl PathNormals {
|
||||||
path_points.clear();
|
path_points.clear();
|
||||||
|
|
||||||
let mut next_normal_index = 0;
|
let mut next_normal_index = 0;
|
||||||
for event in path_stream {
|
for op in path_stream {
|
||||||
match event {
|
match op {
|
||||||
PathEvent::MoveTo(_) => next_normal_index += 1,
|
PathOp::MoveTo => next_normal_index += 1,
|
||||||
PathEvent::LineTo(_) => {
|
PathOp::LineTo => {
|
||||||
next_normal_index += 1;
|
next_normal_index += 1;
|
||||||
self.normals.push(SegmentNormals {
|
self.normals.push(SegmentNormals {
|
||||||
from: normals[next_normal_index - 2],
|
from: normals[next_normal_index - 2],
|
||||||
|
@ -106,7 +104,7 @@ impl PathNormals {
|
||||||
to: normals[next_normal_index - 1],
|
to: normals[next_normal_index - 1],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
PathEvent::QuadraticTo(..) => {
|
PathOp::QuadraticTo => {
|
||||||
next_normal_index += 2;
|
next_normal_index += 2;
|
||||||
self.normals.push(SegmentNormals {
|
self.normals.push(SegmentNormals {
|
||||||
from: normals[next_normal_index - 3],
|
from: normals[next_normal_index - 3],
|
||||||
|
@ -114,8 +112,7 @@ impl PathNormals {
|
||||||
to: normals[next_normal_index - 1],
|
to: normals[next_normal_index - 1],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
PathEvent::CubicTo(..) | PathEvent::Arc(..) => unreachable!(),
|
PathOp::Close => {
|
||||||
PathEvent::Close => {
|
|
||||||
self.normals.push(SegmentNormals {
|
self.normals.push(SegmentNormals {
|
||||||
from: normals[next_normal_index - 1],
|
from: normals[next_normal_index - 1],
|
||||||
ctrl: Vector2D::zero(),
|
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();
|
let vector = ((*current - *prev) + (*next - *current)).normalize();
|
||||||
Vector2D::new(vector.y, -vector.x)
|
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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue