Add support for composite glyphs
This commit is contained in:
parent
5453afa7a1
commit
23f7f88f43
|
@ -20,17 +20,21 @@ use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
|
||||||
use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode};
|
use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode};
|
||||||
use memmap::{Mmap, Protection};
|
use memmap::{Mmap, Protection};
|
||||||
use pathfinder::atlas::AtlasBuilder;
|
use pathfinder::atlas::AtlasBuilder;
|
||||||
use pathfinder::charmap::CodepointRange;
|
use pathfinder::charmap::CodepointRanges;
|
||||||
use pathfinder::coverage::CoverageBuffer;
|
use pathfinder::coverage::CoverageBuffer;
|
||||||
use pathfinder::glyph_range::GlyphRanges;
|
use pathfinder::glyph_range::GlyphRanges;
|
||||||
use pathfinder::otf::Font;
|
use pathfinder::otf::Font;
|
||||||
use pathfinder::outline::{OutlineBuffers, OutlineBuilder};
|
use pathfinder::outline::{OutlineBuffers, OutlineBuilder};
|
||||||
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
|
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
|
||||||
use pathfinder::shaper;
|
use pathfinder::shaper;
|
||||||
|
use std::char;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
const ATLAS_SIZE: u32 = 2048;
|
const ATLAS_SIZE: u32 = 2048;
|
||||||
const WIDTH: u32 = 640;
|
const WIDTH: u32 = 640;
|
||||||
|
@ -70,16 +74,32 @@ fn main() {
|
||||||
let (width, height) = window.get_framebuffer_size();
|
let (width, height) = window.get_framebuffer_size();
|
||||||
let mut device_pixel_size = Size2D::new(width as u32, height as u32);
|
let mut device_pixel_size = Size2D::new(width as u32, height as u32);
|
||||||
|
|
||||||
let mut chars: Vec<char> = TEXT.chars().collect();
|
let mut args = env::args();
|
||||||
chars.sort();
|
args.next();
|
||||||
let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)];
|
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);
|
let (font, shaped_glyph_positions, glyph_ranges);
|
||||||
unsafe {
|
unsafe {
|
||||||
font = Font::new(file.as_slice()).unwrap();
|
font = Font::new(file.as_slice()).unwrap();
|
||||||
glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges).unwrap();
|
glyph_ranges = font.glyph_ranges_for_codepoint_ranges(&codepoint_ranges.ranges).unwrap();
|
||||||
shaped_glyph_positions = shaper::shape_text(&font, &glyph_ranges, TEXT)
|
shaped_glyph_positions = shaper::shape_text(&font, &glyph_ranges, &text)
|
||||||
}
|
}
|
||||||
|
|
||||||
let paragraph_width = (device_pixel_size.width as f32 * UNITS_PER_EM as f32 /
|
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)
|
(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 = "\
|
static COMPOSITE_VERTEX_SHADER: &'static str = "\
|
||||||
#version 330
|
#version 330
|
||||||
|
|
||||||
|
|
138
src/otf/glyf.rs
138
src/otf/glyf.rs
|
@ -15,10 +15,14 @@ use otf::loca::LocaTable;
|
||||||
use otf::{Error, FontTable};
|
use otf::{Error, FontTable};
|
||||||
use outline::GlyphBounds;
|
use outline::GlyphBounds;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::ops::Mul;
|
||||||
use util::Jump;
|
use util::Jump;
|
||||||
|
|
||||||
|
const F2DOT14_ZERO: F2Dot14 = F2Dot14(0);
|
||||||
|
const F2DOT14_ONE: F2Dot14 = F2Dot14(0b0100_0000_0000_0000);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
flags Flags: u8 {
|
flags SimpleFlags: u8 {
|
||||||
const ON_CURVE = 1 << 0,
|
const ON_CURVE = 1 << 0,
|
||||||
const X_SHORT_VECTOR = 1 << 1,
|
const X_SHORT_VECTOR = 1 << 1,
|
||||||
const Y_SHORT_VECTOR = 1 << 2,
|
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)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
pub struct Point {
|
pub struct Point {
|
||||||
pub position: Point2D<i16>,
|
pub position: Point2D<i16>,
|
||||||
|
@ -65,11 +81,18 @@ impl<'a> GlyfTable<'a> {
|
||||||
Some(offset) => try!(reader.jump(offset as usize).map_err(Error::eof)),
|
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));
|
let number_of_contours = try!(reader.read_i16::<BigEndian>().map_err(Error::eof));
|
||||||
if number_of_contours < 0 {
|
if number_of_contours >= 0 {
|
||||||
// TODO(pcwalton): Composite glyphs.
|
self.for_each_point_in_simple_glyph(glyph_start, callback)
|
||||||
return Err(Error::CompositeGlyph)
|
} 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));
|
try!(reader.jump(mem::size_of::<i16>() * 4).map_err(Error::eof));
|
||||||
|
|
||||||
// Find out how many points we have.
|
// Find out how many points we have.
|
||||||
|
@ -105,7 +128,7 @@ impl<'a> GlyfTable<'a> {
|
||||||
let mut point_index_in_contour = 0;
|
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 = SimpleFlags::from_bits_truncate(*flag_parser.current);
|
||||||
try!(flag_parser.next());
|
try!(flag_parser.next());
|
||||||
|
|
||||||
let mut delta = Point2D::new(0, 0);
|
let mut delta = Point2D::new(0, 0);
|
||||||
|
@ -189,6 +212,66 @@ impl<'a> GlyfTable<'a> {
|
||||||
Ok(())
|
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)
|
pub fn glyph_bounds(&self, head_table: &HeadTable, loca_table: &LocaTable, glyph_id: u16)
|
||||||
-> Result<GlyphBounds, Error> {
|
-> Result<GlyphBounds, Error> {
|
||||||
let mut reader = self.table.bytes;
|
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> {
|
-> Result<u16, Error> {
|
||||||
let (mut x_coordinate_length, mut points_left) = (0, number_of_points);
|
let (mut x_coordinate_length, mut points_left) = (0, number_of_points);
|
||||||
while points_left > 0 {
|
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) {
|
let repeat_count = if !flags.contains(REPEAT) {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
|
@ -278,7 +361,7 @@ impl<'a> FlagParser<'a> {
|
||||||
None => return Err(Error::UnexpectedEof),
|
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..];
|
self.next = &self.next[1..];
|
||||||
|
|
||||||
if flags.contains(REPEAT) {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue