Handle glyph outlines in which the first point is off-curve correctly.

These show up in Helvetica.dfont and Times.dfont.
This commit is contained in:
Patrick Walton 2017-02-03 15:18:07 -08:00
parent ce383385df
commit da7c70f035
3 changed files with 70 additions and 30 deletions

View File

@ -25,10 +25,9 @@ use pathfinder::rasterizer::{Rasterizer, RasterizerOptions};
use std::env; use std::env;
use std::os::raw::c_void; use std::os::raw::c_void;
const POINT_SIZE: f32 = 24.0; const DEFAULT_POINT_SIZE: f32 = 24.0;
const WIDTH: u32 = 512; const WIDTH: u32 = 512;
const HEIGHT: u32 = 384; const HEIGHT: u32 = 384;
const SHELF_HEIGHT: u32 = 32;
fn main() { fn main() {
let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap(); let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap();
@ -49,10 +48,19 @@ fn main() {
let rasterizer_options = RasterizerOptions::from_env().unwrap(); let rasterizer_options = RasterizerOptions::from_env().unwrap();
let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap(); let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap();
let mut glyph_buffer_builder = GlyphBufferBuilder::new();
let mut batch_builder = BatchBuilder::new(device_pixel_width as GLuint, SHELF_HEIGHT);
let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap(); let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap();
let point_size = match env::args().nth(2) {
None => DEFAULT_POINT_SIZE,
Some(point_size) => point_size.parse().unwrap()
};
// FIXME(pcwalton)
let shelf_height = (point_size * 2.0).ceil() as u32;
let mut glyph_buffer_builder = GlyphBufferBuilder::new();
let mut batch_builder = BatchBuilder::new(device_pixel_width as GLuint, shelf_height);
unsafe { unsafe {
let font = Font::new(file.as_slice()).unwrap(); let font = Font::new(file.as_slice()).unwrap();
let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)]; let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)];
@ -60,7 +68,7 @@ fn main() {
let glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges).unwrap(); let glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges).unwrap();
for (glyph_index, glyph_id) in glyph_ranges.iter().enumerate() { for (glyph_index, glyph_id) in glyph_ranges.iter().enumerate() {
glyph_buffer_builder.add_glyph(&font, glyph_id).unwrap(); glyph_buffer_builder.add_glyph(&font, glyph_id).unwrap();
batch_builder.add_glyph(&glyph_buffer_builder, glyph_index as u32, POINT_SIZE).unwrap() batch_builder.add_glyph(&glyph_buffer_builder, glyph_index as u32, point_size).unwrap()
} }
} }

View File

@ -52,11 +52,11 @@ impl GlyphBufferBuilder {
glyph_index: glyph_index, glyph_index: glyph_index,
}); });
if !point.first_point_in_contour && point.on_curve { if point.index_in_contour > 0 && point.on_curve {
let indices = if last_point_on_curve { let indices = if !last_point_on_curve {
[point_index - 1, 0, point_index]
} else {
[point_index - 2, point_index - 1, point_index] [point_index - 2, point_index - 1, point_index]
} else {
[point_index - 1, 0, point_index]
}; };
self.indices.extend(indices.iter().cloned()); self.indices.extend(indices.iter().cloned());
} }

View File

@ -28,11 +28,11 @@ bitflags! {
} }
} }
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq, Debug)]
pub struct Point { pub struct Point {
pub position: Point2D<i16>, pub position: Point2D<i16>,
pub index_in_contour: u16,
pub on_curve: bool, pub on_curve: bool,
pub first_point_in_contour: bool,
} }
/// TODO(pcwalton): Add some caching so we don't keep going to the `loca` table all the time. /// TODO(pcwalton): Add some caching so we don't keep going to the `loca` table all the time.
@ -97,7 +97,12 @@ impl<'a> GlyfTable<'a> {
for _ in 0..number_of_contours { for _ in 0..number_of_contours {
let contour_point_count = let contour_point_count =
try!(endpoints_reader.read_u16::<BigEndian>().map_err(drop)) - point_index + 1; try!(endpoints_reader.read_u16::<BigEndian>().map_err(drop)) - point_index + 1;
let (mut starting_point, mut last_point_was_off_curve) = (Point2D::new(0, 0), false);
let mut first_on_curve_point = None;
let mut initial_off_curve_point = None;
let mut last_point_was_off_curve = false;
let mut point_index_in_contour = 0;
for contour_point_index in 0..contour_point_count { for contour_point_index in 0..contour_point_count {
let flags = Flags::from_bits_truncate(*flag_parser.current); let flags = Flags::from_bits_truncate(*flag_parser.current);
try!(flag_parser.next()); try!(flag_parser.next());
@ -121,36 +126,63 @@ impl<'a> GlyfTable<'a> {
} }
if last_point_was_off_curve && !flags.contains(ON_CURVE) { if last_point_was_off_curve && !flags.contains(ON_CURVE) {
let position = position + delta / 2;
// An important edge case!
if first_on_curve_point.is_none() {
first_on_curve_point = Some(position)
}
callback(&Point { callback(&Point {
position: position + delta / 2, position: position,
index_in_contour: point_index_in_contour,
on_curve: true, on_curve: true,
first_point_in_contour: false, });
}) point_index_in_contour += 1
} }
position = position + delta; position = position + delta;
let first_point_in_contour = contour_point_index == 0; if flags.contains(ON_CURVE) && first_on_curve_point.is_none() {
if first_point_in_contour { first_on_curve_point = Some(position)
starting_point = position
} }
callback(&Point { // Sometimes the initial point is an off curve point. In that case, save it so we
position: position, // can emit it later when closing the path.
on_curve: flags.contains(ON_CURVE), if !flags.contains(ON_CURVE) && first_on_curve_point.is_none() {
first_point_in_contour: first_point_in_contour, debug_assert!(initial_off_curve_point.is_none());
}); initial_off_curve_point = Some(position)
} else {
callback(&Point {
position: position,
on_curve: flags.contains(ON_CURVE),
index_in_contour: point_index_in_contour,
});
point_index_in_contour += 1
}
last_point_was_off_curve = !flags.contains(ON_CURVE); last_point_was_off_curve = !flags.contains(ON_CURVE);
point_index += 1 point_index += 1;
}
// We're about to close the path. Emit the initial off curve point if there was one.
if let Some(initial_off_curve_point) = initial_off_curve_point {
callback(&Point {
position: initial_off_curve_point,
on_curve: false,
index_in_contour: point_index_in_contour,
});
point_index_in_contour += 1
} }
// Close the path. // Close the path.
callback(&Point { if let Some(first_on_curve_point) = first_on_curve_point {
position: starting_point, callback(&Point {
on_curve: true, position: first_on_curve_point,
first_point_in_contour: false, on_curve: true,
}) index_in_contour: point_index_in_contour,
})
}
} }
Ok(()) Ok(())