Add support for composite glyphs

This commit is contained in:
Patrick Walton 2017-02-06 11:44:26 -08:00
parent 5453afa7a1
commit 23f7f88f43
2 changed files with 163 additions and 14 deletions

View File

@ -20,17 +20,21 @@ use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode};
use memmap::{Mmap, Protection};
use pathfinder::atlas::AtlasBuilder;
use pathfinder::charmap::CodepointRange;
use pathfinder::charmap::CodepointRanges;
use pathfinder::coverage::CoverageBuffer;
use pathfinder::glyph_range::GlyphRanges;
use pathfinder::otf::Font;
use pathfinder::outline::{OutlineBuffers, OutlineBuilder};
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
use pathfinder::shaper;
use std::char;
use std::env;
use std::fs::File;
use std::io::Read;
use std::mem;
use std::os::raw::c_void;
use std::path::Path;
use std::process;
const ATLAS_SIZE: u32 = 2048;
const WIDTH: u32 = 640;
@ -70,16 +74,32 @@ fn main() {
let (width, height) = window.get_framebuffer_size();
let mut device_pixel_size = Size2D::new(width as u32, height as u32);
let mut chars: Vec<char> = TEXT.chars().collect();
chars.sort();
let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)];
let mut args = env::args();
args.next();
let font_path = args.next().unwrap_or_else(|| usage());
let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap();
let mut text = "".to_string();
match args.next() {
Some(path) => drop(File::open(path).unwrap().read_to_string(&mut text).unwrap()),
None => text.push_str(TEXT),
}
text = text.replace(&['\n', '\r', '\t'][..], " ");
// Make sure the characters include `[A-Za-z0-9 ./,]`, for the FPS display.
let mut chars: Vec<char> = text.chars().collect();
chars.extend(" ./,:()".chars());
chars.extend(('A' as u32..('Z' as u32 + 1)).flat_map(char::from_u32));
chars.extend(('a' as u32..('z' as u32 + 1)).flat_map(char::from_u32));
chars.extend(('0' as u32..('9' as u32 + 1)).flat_map(char::from_u32));
chars.sort();
let codepoint_ranges = CodepointRanges::from_sorted_chars(&chars);
let file = Mmap::open_path(font_path, Protection::Read).unwrap();
let (font, shaped_glyph_positions, glyph_ranges);
unsafe {
font = Font::new(file.as_slice()).unwrap();
glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges).unwrap();
shaped_glyph_positions = shaper::shape_text(&font, &glyph_ranges, TEXT)
glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges.ranges).unwrap();
shaped_glyph_positions = shaper::shape_text(&font, &glyph_ranges, &text)
}
let paragraph_width = (device_pixel_size.width as f32 * UNITS_PER_EM as f32 /
@ -724,6 +744,11 @@ fn create_image(rasterizer: &Rasterizer, atlas_size: &Size2D<u32>) -> (Image, GL
(compute_image, gl_texture)
}
fn usage() -> ! {
println!("usage: lorem-ipsum /path/to/font.ttf [/path/to/text.txt]");
process::exit(0)
}
static COMPOSITE_VERTEX_SHADER: &'static str = "\
#version 330

View File

@ -15,10 +15,14 @@ use otf::loca::LocaTable;
use otf::{Error, FontTable};
use outline::GlyphBounds;
use std::mem;
use std::ops::Mul;
use util::Jump;
const F2DOT14_ZERO: F2Dot14 = F2Dot14(0);
const F2DOT14_ONE: F2Dot14 = F2Dot14(0b0100_0000_0000_0000);
bitflags! {
flags Flags: u8 {
flags SimpleFlags: u8 {
const ON_CURVE = 1 << 0,
const X_SHORT_VECTOR = 1 << 1,
const Y_SHORT_VECTOR = 1 << 2,
@ -28,6 +32,18 @@ bitflags! {
}
}
bitflags! {
flags CompositeFlags: u16 {
const ARG_1_AND_2_ARE_WORDS = 1 << 0,
const ARGS_ARE_XY_VALUES = 1 << 1,
const ROUND_XY_TO_GRID = 1 << 2,
const WE_HAVE_A_SCALE = 1 << 3,
const MORE_COMPONENTS = 1 << 5,
const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6,
const WE_HAVE_A_TWO_BY_TWO = 1 << 7,
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Point {
pub position: Point2D<i16>,
@ -65,11 +81,18 @@ impl<'a> GlyfTable<'a> {
Some(offset) => try!(reader.jump(offset as usize).map_err(Error::eof)),
}
let glyph_start = reader;
let number_of_contours = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
if number_of_contours < 0 {
// TODO(pcwalton): Composite glyphs.
return Err(Error::CompositeGlyph)
if number_of_contours >= 0 {
self.for_each_point_in_simple_glyph(glyph_start, callback)
} else {
self.for_each_point_in_composite_glyph(glyph_start, head_table, loca_table, callback)
}
}
fn for_each_point_in_simple_glyph<F>(&self, mut reader: &[u8], mut callback: F)
-> Result<(), Error> where F: FnMut(&Point) {
let number_of_contours = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
try!(reader.jump(mem::size_of::<i16>() * 4).map_err(Error::eof));
// Find out how many points we have.
@ -105,7 +128,7 @@ impl<'a> GlyfTable<'a> {
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);
let flags = SimpleFlags::from_bits_truncate(*flag_parser.current);
try!(flag_parser.next());
let mut delta = Point2D::new(0, 0);
@ -189,6 +212,66 @@ impl<'a> GlyfTable<'a> {
Ok(())
}
// TODO(pcwalton): Consider rasterizing pieces of composite glyphs independently and
// compositing them together.
fn for_each_point_in_composite_glyph<F>(&self,
mut reader: &[u8],
head_table: &HeadTable,
loca_table: &LocaTable,
mut callback: F)
-> Result<(), Error> where F: FnMut(&Point) {
try!(reader.jump(mem::size_of::<i16>() * 5).map_err(Error::eof));
loop {
let flags = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let flags = CompositeFlags::from_bits_truncate(flags);
let glyph_index = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
let (arg0, arg1);
if flags.contains(ARG_1_AND_2_ARE_WORDS) {
arg0 = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
arg1 = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
} else {
arg0 = try!(reader.read_i8().map_err(Error::eof)) as i16;
arg1 = try!(reader.read_i8().map_err(Error::eof)) as i16;
}
let mut transform = Mat3x2::identity();
if flags.contains(ARGS_ARE_XY_VALUES) {
transform.m02 = arg0;
transform.m12 = arg1;
}
if flags.contains(WE_HAVE_A_SCALE) {
let scale = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)));
transform.m00 = scale;
transform.m11 = scale;
} else if flags.contains(WE_HAVE_AN_X_AND_Y_SCALE) {
transform.m00 = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)));
transform.m11 = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)));
} else if flags.contains(WE_HAVE_A_TWO_BY_TWO) {
transform.m00 = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)));
transform.m01 = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)));
transform.m10 = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)));
transform.m11 = F2Dot14(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)));
}
if let Some(offset) = try!(loca_table.location_of(head_table, glyph_index)) {
let mut reader = self.table.bytes;
try!(reader.jump(offset as usize).map_err(Error::eof));
self.for_each_point_in_simple_glyph(reader, |point| {
callback(&transform.transform(&point))
});
}
if !flags.contains(MORE_COMPONENTS) {
break
}
}
Ok(())
}
pub fn glyph_bounds(&self, head_table: &HeadTable, loca_table: &LocaTable, glyph_id: u16)
-> Result<GlyphBounds, Error> {
let mut reader = self.table.bytes;
@ -229,7 +312,7 @@ fn calculate_size_of_x_coordinates<'a, 'b>(reader: &'a mut &'b [u8], number_of_p
-> Result<u16, Error> {
let (mut x_coordinate_length, mut points_left) = (0, number_of_points);
while points_left > 0 {
let flags = Flags::from_bits_truncate(try!(reader.read_u8().map_err(Error::eof)));
let flags = SimpleFlags::from_bits_truncate(try!(reader.read_u8().map_err(Error::eof)));
let repeat_count = if !flags.contains(REPEAT) {
1
} else {
@ -278,7 +361,7 @@ impl<'a> FlagParser<'a> {
None => return Err(Error::UnexpectedEof),
};
let flags = Flags::from_bits_truncate(*self.current);
let flags = SimpleFlags::from_bits_truncate(*self.current);
self.next = &self.next[1..];
if flags.contains(REPEAT) {
@ -296,3 +379,44 @@ impl<'a> FlagParser<'a> {
}
}
#[derive(Copy, Clone, Debug)]
struct Mat3x2 {
m00: F2Dot14,
m01: F2Dot14,
m02: i16,
m10: F2Dot14,
m11: F2Dot14,
m12: i16,
}
impl Mat3x2 {
fn identity() -> Mat3x2 {
Mat3x2 {
m00: F2DOT14_ONE, m01: F2DOT14_ZERO, m02: 0,
m10: F2DOT14_ZERO, m11: F2DOT14_ONE, m12: 0,
}
}
// TODO(pcwalton): SIMD/FMA.
fn transform(&self, point: &Point) -> Point {
let p = point.position;
Point {
position: Point2D::new(self.m00 * p.x + self.m01 * p.y + self.m02,
self.m10 * p.x + self.m11 * p.y + self.m12),
..*point
}
}
}
#[derive(Copy, Clone, Debug)]
struct F2Dot14(i16);
impl Mul<i16> for F2Dot14 {
type Output = i16;
#[inline]
fn mul(self, other: i16) -> i16 {
((self.0 as i32 * other as i32) >> 14) as i16
}
}