diff --git a/examples/lorem-ipsum.rs b/examples/lorem-ipsum.rs index 994ce388..2b9461db 100644 --- a/examples/lorem-ipsum.rs +++ b/examples/lorem-ipsum.rs @@ -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 = 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 = 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) -> (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 diff --git a/src/otf/glyf.rs b/src/otf/glyf.rs index bd31d272..b570e1fd 100644 --- a/src/otf/glyf.rs +++ b/src/otf/glyf.rs @@ -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, @@ -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::().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(&self, mut reader: &[u8], mut callback: F) + -> Result<(), Error> where F: FnMut(&Point) { + let number_of_contours = try!(reader.read_i16::().map_err(Error::eof)); try!(reader.jump(mem::size_of::() * 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(&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::() * 5).map_err(Error::eof)); + + loop { + let flags = try!(reader.read_u16::().map_err(Error::eof)); + let flags = CompositeFlags::from_bits_truncate(flags); + let glyph_index = try!(reader.read_u16::().map_err(Error::eof)); + + let (arg0, arg1); + if flags.contains(ARG_1_AND_2_ARE_WORDS) { + arg0 = try!(reader.read_i16::().map_err(Error::eof)); + arg1 = try!(reader.read_i16::().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::().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::().map_err(Error::eof))); + transform.m11 = F2Dot14(try!(reader.read_i16::().map_err(Error::eof))); + } else if flags.contains(WE_HAVE_A_TWO_BY_TWO) { + transform.m00 = F2Dot14(try!(reader.read_i16::().map_err(Error::eof))); + transform.m01 = F2Dot14(try!(reader.read_i16::().map_err(Error::eof))); + transform.m10 = F2Dot14(try!(reader.read_i16::().map_err(Error::eof))); + transform.m11 = F2Dot14(try!(reader.read_i16::().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 { 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 { 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 for F2Dot14 { + type Output = i16; + + #[inline] + fn mul(self, other: i16) -> i16 { + ((self.0 as i32 * other as i32) >> 14) as i16 + } +} +