font-renderer: Pull hinted outlines out of FreeType
This commit is contained in:
parent
2ddf95bd70
commit
92c5014ec6
|
@ -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"
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue