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::os::raw::c_void;
const POINT_SIZE: f32 = 24.0;
const DEFAULT_POINT_SIZE: f32 = 24.0;
const WIDTH: u32 = 512;
const HEIGHT: u32 = 384;
const SHELF_HEIGHT: u32 = 32;
fn main() {
let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap();
@ -49,10 +48,19 @@ fn main() {
let rasterizer_options = RasterizerOptions::from_env().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 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 {
let font = Font::new(file.as_slice()).unwrap();
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();
for (glyph_index, glyph_id) in glyph_ranges.iter().enumerate() {
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,
});
if !point.first_point_in_contour && point.on_curve {
let indices = if last_point_on_curve {
[point_index - 1, 0, point_index]
} else {
if point.index_in_contour > 0 && point.on_curve {
let indices = if !last_point_on_curve {
[point_index - 2, point_index - 1, point_index]
} else {
[point_index - 1, 0, point_index]
};
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 position: Point2D<i16>,
pub index_in_contour: u16,
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.
@ -97,7 +97,12 @@ impl<'a> GlyfTable<'a> {
for _ in 0..number_of_contours {
let contour_point_count =
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 {
let flags = Flags::from_bits_truncate(*flag_parser.current);
try!(flag_parser.next());
@ -121,37 +126,64 @@ impl<'a> GlyfTable<'a> {
}
if last_point_was_off_curve && !flags.contains(ON_CURVE) {
callback(&Point {
position: position + delta / 2,
on_curve: true,
first_point_in_contour: false,
})
}
let position = position + delta / 2;
position = position + delta;
let first_point_in_contour = contour_point_index == 0;
if first_point_in_contour {
starting_point = position
// An important edge case!
if first_on_curve_point.is_none() {
first_on_curve_point = Some(position)
}
callback(&Point {
position: position,
on_curve: flags.contains(ON_CURVE),
first_point_in_contour: first_point_in_contour,
index_in_contour: point_index_in_contour,
on_curve: true,
});
point_index_in_contour += 1
}
position = position + delta;
if flags.contains(ON_CURVE) && first_on_curve_point.is_none() {
first_on_curve_point = Some(position)
}
// Sometimes the initial point is an off curve point. In that case, save it so we
// can emit it later when closing the path.
if !flags.contains(ON_CURVE) && first_on_curve_point.is_none() {
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);
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.
if let Some(first_on_curve_point) = first_on_curve_point {
callback(&Point {
position: starting_point,
position: first_on_curve_point,
on_curve: true,
first_point_in_contour: false,
index_in_contour: point_index_in_contour,
})
}
}
Ok(())
}