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 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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue