font-renderer: Pull hinted outlines out of FreeType

This commit is contained in:
Patrick Walton 2017-08-09 15:36:41 -07:00
parent 2ddf95bd70
commit 92c5014ec6
3 changed files with 228 additions and 6 deletions

View File

@ -6,7 +6,14 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
app_units = "0.5"
euclid = "0.15"
log = "0.3"
[dependencies.freetype-sys]
git = "https://github.com/pcwalton/freetype-sys.git"
branch = "outline-get-cbox"
[dependencies.pathfinder_partitioner]
path = "../partitioner"
[dev-dependencies]
env_logger = "0.4"

View File

@ -3,6 +3,14 @@
extern crate app_units;
extern crate euclid;
extern crate freetype_sys;
extern crate pathfinder_partitioner;
#[allow(unused_imports)]
#[macro_use]
extern crate log;
#[cfg(test)]
extern crate env_logger;
use app_units::Au;
use euclid::{Point2D, Size2D};
@ -10,6 +18,7 @@ use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_O
use freetype_sys::{FT_GlyphSlot, FT_Init_FreeType, FT_Int32, FT_LOAD_TARGET_LIGHT, FT_Library};
use freetype_sys::{FT_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox};
use freetype_sys::{FT_Set_Char_Size, FT_UInt};
use pathfinder_partitioner::{Endpoint, Subpath};
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::mem;
@ -24,6 +33,8 @@ mod tests;
// TODO(pcwalton): Make this configurable.
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_TARGET_LIGHT;
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
pub struct FontContext {
library: FT_Library,
faces: BTreeMap<FontKey, Face>,
@ -79,6 +90,19 @@ impl FontContext {
})
}
pub fn push_glyph_outline(&self,
font_instance: &FontInstanceKey,
glyph_key: &GlyphKey,
glyph_outline_buffer: &mut GlyphOutlineBuffer)
-> Result<(), ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
self.push_glyph_outline_from_glyph_slot(font_instance,
glyph_key,
glyph_slot,
glyph_outline_buffer)
})
}
fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Option<FT_GlyphSlot> {
let face = match self.faces.get(&font_instance.font_key) {
@ -145,6 +169,51 @@ impl FontContext {
bounding_box
}
fn push_glyph_outline_from_glyph_slot(&self,
_: &FontInstanceKey,
_: &GlyphKey,
glyph_slot: FT_GlyphSlot,
glyph_outline_buffer: &mut GlyphOutlineBuffer) {
unsafe {
let outline = &(*glyph_slot).outline;
let mut first_point_index = 0 as u32;
let mut first_endpoint_index = glyph_outline_buffer.endpoints.len() as u32;
for contour_index in 0..outline.n_contours as usize {
let current_subpath_index = glyph_outline_buffer.subpaths.len() as u32;
let mut current_control_point_index = None;
let last_point_index = *outline.contours.offset(contour_index as isize) as u32 + 1;
for point_index in first_point_index..last_point_index {
// FIXME(pcwalton): Use `units_per_EM` to do this conversion?
// TODO(pcwalton): Approximate cubic Béziers with quadratics.
// FIXME(pcwalton): Does FreeType produce multiple consecutive off-curve points
// in a row like raw TrueType does?
let point = *outline.points.offset(point_index as isize);
let point_position = Point2D::new(point.x as f32, point.y as f32);
if (*outline.tags.offset(point_index as isize) & FREETYPE_POINT_ON_CURVE) != 0 {
glyph_outline_buffer.endpoints.push(Endpoint {
position: point_position,
control_point_index: current_control_point_index.take().unwrap_or(!0),
subpath_index: current_subpath_index,
});
} else {
let mut control_points = &mut glyph_outline_buffer.control_points;
current_control_point_index = Some(control_points.len() as u32);
control_points.push(point_position)
}
}
let last_endpoint_index = glyph_outline_buffer.endpoints.len() as u32;
glyph_outline_buffer.subpaths.push(Subpath {
first_endpoint_index: first_endpoint_index,
last_endpoint_index: last_endpoint_index,
});
first_endpoint_index = last_endpoint_index;
first_point_index = last_point_index;
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
@ -200,6 +269,23 @@ pub struct GlyphDimensions {
pub advance: f32,
}
pub struct GlyphOutlineBuffer {
pub endpoints: Vec<Endpoint>,
pub control_points: Vec<Point2D<f32>>,
pub subpaths: Vec<Subpath>,
}
impl GlyphOutlineBuffer {
#[inline]
pub fn new() -> GlyphOutlineBuffer {
GlyphOutlineBuffer {
endpoints: vec![],
control_points: vec![],
subpaths: vec![],
}
}
}
struct Face {
face: FT_Face,
bytes: Vec<u8>,

View File

@ -1,20 +1,102 @@
// pathfinder/font-renderer/src/tests.rs
use app_units::Au;
use env_logger;
use euclid::Size2D;
use euclid::approxeq::ApproxEq;
use pathfinder_partitioner::Subpath;
use std::fs::File;
use std::io::Read;
use {FontContext, FontInstanceKey, FontKey, GlyphDimensions, GlyphKey};
use {FontContext, FontInstanceKey, FontKey, GlyphDimensions, GlyphKey, GlyphOutlineBuffer};
static TEST_FONT_PATH: &'static str = "resources/tests/nimbus-sans/NimbusSanL-Regu.ttf";
const TEST_FONT_SIZE: Au = Au(60 * 16);
const TEST_FIRST_GLYPH_INDEX: u32 = 0x1f;
const TEST_GLYPH_ID: u32 = 68; // 'a'
// Nimbus Sans Regular 16pt., 'A'
const EXPECTED_GLYPH_ORIGIN: [i32; 2] = [1, 12];
const EXPECTED_GLYPH_SIZE: [u32; 2] = [8, 12];
const EXPECTED_GLYPH_ORIGIN: [i32; 2] = [0, 9];
const EXPECTED_GLYPH_SIZE: [u32; 2] = [9, 9];
const EXPECTED_GLYPH_ADVANCE: f32 = 9.0;
static EXPECTED_GLYPH_ENDPOINTS: [[f32; 2]; 34] = [
[ 548.0, 77.0 ],
[ 548.0, 10.0 ],
[ 490.0, 0.0 ],
[ 402.0, 82.0 ],
[ 323.0, 24.0 ],
[ 219.0, 0.0 ],
[ 89.0, 44.0 ],
[ 43.0, 158.0 ],
[ 138.0, 301.0 ],
[ 233.0, 324.0 ],
[ 310.0, 333.0 ],
[ 381.0, 353.0 ],
[ 399.0, 392.0 ],
[ 399.0, 414.0 ],
[ 362.0, 476.0 ],
[ 278.0, 494.0 ],
[ 153.0, 400.0 ],
[ 67.0, 400.0 ],
[ 104.0, 512.0 ],
[ 282.0, 576.0 ],
[ 444.0, 529.0 ],
[ 484.0, 430.0 ],
[ 484.0, 117.0 ],
[ 530.0, 75.0 ],
[ 399.0, 289.0 ],
[ 349.0, 273.0 ],
[ 261.0, 258.0 ],
[ 165.0, 228.0 ],
[ 132.0, 161.0 ],
[ 157.0, 101.0 ],
[ 238.0, 75.0 ],
[ 365.0, 124.0 ],
[ 396.0, 169.0 ],
[ 399.0, 193.0 ],
];
static EXPECTED_GLYPH_CONTROL_POINTS: [[f32; 2]; 29] = [
[ 512.0, 0.0 ],
[ 410.0, 0.0 ],
[ 362.0, 43.0 ],
[ 276.0, 0.0 ],
[ 137.0, 0.0 ],
[ 43.0, 86.0 ],
[ 43.0, 262.0 ],
[ 169.0, 314.0 ],
[ 241.0, 325.0 ],
[ 365.0, 340.0 ],
[ 398.0, 366.0 ],
[ 399.0, 457.0 ],
[ 330.0, 494.0 ],
[ 163.0, 494.0 ],
[ 70.0, 474.0 ],
[ 160.0, 576.0 ],
[ 394.0, 576.0 ],
[ 484.0, 493.0 ],
[ 484.0, 75.0 ],
[ 537.0, 75.0 ],
[ 377.0, 278.0 ],
[ 325.0, 268.0 ],
[ 195.0, 249.0 ],
[ 132.0, 205.0 ],
[ 132.0, 125.0 ],
[ 183.0, 75.0 ],
[ 313.0, 75.0 ],
[ 390.0, 147.0 ],
[ 399.0, 177.0 ],
];
static EXPECTED_GLYPH_SUBPATHS: [Subpath; 2] = [
Subpath {
first_endpoint_index: 0,
last_endpoint_index: 24,
},
Subpath {
first_endpoint_index: 24,
last_endpoint_index: 34,
},
];
#[test]
fn test_font_context_glyph_dimensions() {
let mut font_context = FontContext::new();
@ -25,7 +107,7 @@ fn test_font_context_glyph_dimensions() {
font_context.add_font_from_memory(&font_key, bytes, 0).unwrap();
let font_instance = FontInstanceKey::new(&font_key, TEST_FONT_SIZE);
let glyph_key = GlyphKey::new('A' as u32 - TEST_FIRST_GLYPH_INDEX);
let glyph_key = GlyphKey::new(TEST_GLYPH_ID);
let glyph_dimensions = font_context.glyph_dimensions(&font_instance, &glyph_key).unwrap();
assert_eq!(glyph_dimensions, GlyphDimensions {
@ -34,3 +116,50 @@ fn test_font_context_glyph_dimensions() {
advance: EXPECTED_GLYPH_ADVANCE,
})
}
#[test]
fn test_font_context_glyph_outline() {
drop(env_logger::init());
let mut font_context = FontContext::new();
let font_key = FontKey::new();
let mut bytes = vec![];
File::open(TEST_FONT_PATH).unwrap().read_to_end(&mut bytes).unwrap();
font_context.add_font_from_memory(&font_key, bytes, 0).unwrap();
let font_instance = FontInstanceKey::new(&font_key, TEST_FONT_SIZE);
let glyph_key = GlyphKey::new(TEST_GLYPH_ID);
let mut glyph_outline_buffer = GlyphOutlineBuffer::new();
font_context.push_glyph_outline(&font_instance, &glyph_key, &mut glyph_outline_buffer)
.unwrap();
info!("endpoints: {:#?}", glyph_outline_buffer.endpoints);
info!("control points: {:#?}", glyph_outline_buffer.control_points);
assert_eq!(glyph_outline_buffer.endpoints.len(), EXPECTED_GLYPH_ENDPOINTS.len());
for (expected_position, endpoint) in
EXPECTED_GLYPH_ENDPOINTS.iter().zip(glyph_outline_buffer.endpoints.iter()) {
let actual_position = endpoint.position;
info!("expected endpoint: {:?} actual endpoint: {:?}", expected_position, actual_position);
assert!(expected_position[0].approx_eq(&actual_position.x) &&
expected_position[1].approx_eq(&actual_position.y));
}
assert_eq!(glyph_outline_buffer.control_points.len(), EXPECTED_GLYPH_CONTROL_POINTS.len());
for (expected_position, actual_position) in
EXPECTED_GLYPH_CONTROL_POINTS.iter().zip(glyph_outline_buffer.control_points.iter()) {
info!("expected control point: {:?} actual control point: {:?}",
expected_position,
actual_position);
assert!(expected_position[0].approx_eq(&actual_position.x) &&
expected_position[1].approx_eq(&actual_position.y));
}
assert_eq!(glyph_outline_buffer.subpaths.len(), EXPECTED_GLYPH_SUBPATHS.len());
for (expected_subpath, actual_subpath) in
EXPECTED_GLYPH_SUBPATHS.iter().zip(glyph_outline_buffer.subpaths.iter()) {
assert_eq!(expected_subpath.first_endpoint_index, actual_subpath.first_endpoint_index);
assert_eq!(expected_subpath.last_endpoint_index, actual_subpath.last_endpoint_index);
}
}