commit 34cd1ef9b0659c2a525b5297d442d6b19c43114f Author: Patrick Walton Date: Fri Jan 6 23:49:45 2017 -0800 Initial commit diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..3706cce7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pathfinder" +version = "0.1.0" +authors = ["Patrick Walton "] + +[dependencies] +bitflags = "0.7" +byteorder = "1" +euclid = "0.10" +memmap = "0.5" + diff --git a/examples/dump-outlines.rs b/examples/dump-outlines.rs new file mode 100644 index 00000000..cb75f237 --- /dev/null +++ b/examples/dump-outlines.rs @@ -0,0 +1,20 @@ +extern crate memmap; +extern crate pathfinder; + +use memmap::{Mmap, Protection}; +use pathfinder::batch::{CodepointBatch, CodepointRange, GlyphBatch}; +use pathfinder::otf::FontData; +use std::env; + +fn main() { + let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap(); + unsafe { + let font = FontData::new(file.as_slice()); + let mut glyph_batch = GlyphBatch::new(); + glyph_batch.find_glyph_ranges_for_codepoint_ranges(&CodepointBatch { + ranges: vec![CodepointRange::new('A' as u32, 'Z' as u32, 0)], + fonts: vec![font], + }).unwrap(); + } +} + diff --git a/src/batch.rs b/src/batch.rs new file mode 100644 index 00000000..9e371854 --- /dev/null +++ b/src/batch.rs @@ -0,0 +1,58 @@ +// Copyright 2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use otf::FontData; + +#[derive(Clone, Copy, Debug)] +pub struct CodepointRange { + pub start: u32, + pub end: u32, + pub font_index: u32, +} + +impl CodepointRange { + #[inline] + pub fn new(start: u32, end: u32, font_index: u32) -> CodepointRange { + CodepointRange { + start: start, + end: end, + font_index: font_index, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct GlyphRange { + pub start: u32, + pub end: u32, + pub font_index: u32, +} + +#[derive(Clone)] +pub struct CodepointBatch<'a> { + pub ranges: Vec, + pub fonts: Vec>, +} + +#[derive(Clone)] +pub struct GlyphBatch<'a> { + pub ranges: Vec, + pub fonts: Vec>, +} + +impl<'a> GlyphBatch<'a> { + pub fn new<'b>() -> GlyphBatch<'b> { + GlyphBatch { + ranges: vec![], + fonts: vec![], + } + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..6ed8e6dd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,19 @@ +// Copyright 2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[macro_use] +extern crate bitflags; +extern crate byteorder; +extern crate euclid; + +pub mod batch; +pub mod otf; +mod util; + diff --git a/src/otf.rs b/src/otf.rs new file mode 100644 index 00000000..e91f0754 --- /dev/null +++ b/src/otf.rs @@ -0,0 +1,296 @@ +// Copyright 2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use batch::{CodepointBatch, GlyphBatch}; +use byteorder::{BigEndian, ReadBytesExt}; +use euclid::Point2D; +use std::mem; +use util::Jump; + +const CMAP: u32 = ((b'c' as u32) << 24)|((b'm' as u32) << 16)|((b'a' as u32) << 8)|(b'p' as u32); + +bitflags! { + flags Flags: u8 { + const ON_CURVE = 1 << 0, + const X_SHORT_VECTOR = 1 << 1, + const Y_SHORT_VECTOR = 1 << 2, + const REPEAT = 1 << 3, + const THIS_X_IS_SAME = 1 << 4, + const THIS_Y_IS_SAME = 1 << 5, + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct Point { + position: Point2D, + on_curve: bool, + first_point_in_contour: bool, +} + +pub fn apply_glyph(glyf_table: &[u8], mut applier: F) -> Result<(), ()> where F: FnMut(&Point) { + let mut reader = glyf_table; + + let number_of_contours = try!(reader.read_i16::().map_err(drop)); + if number_of_contours < 0 { + // TODO(pcwalton): Composite glyphs. + return Err(()) + } + try!(reader.jump(mem::size_of::() * 4)); + + // Find out how many points we have. + let mut endpoints_reader = reader; + try!(reader.jump(mem::size_of::() as usize * (number_of_contours as usize - 1))); + let number_of_points = try!(reader.read_u16::().map_err(drop)); + + // Skip over hinting instructions. + let instruction_length = try!(reader.read_u16::().map_err(drop)); + try!(reader.jump(instruction_length as usize)); + + // Find the offsets of the X and Y coordinates. + let flags_reader = reader.clone(); + let x_coordinate_length = try!(calculate_size_of_x_coordinates(&mut reader, number_of_points)); + + // Set up the streams. + let mut flag_parser = try!(FlagParser::new(flags_reader)); + let mut x_coordinate_reader = reader.clone(); + try!(reader.jump(x_coordinate_length as usize)); + let mut y_coordinate_reader = reader; + + // Now parse the contours. + let (mut position, mut point_index) = (Point2D::new(0, 0), 0); + for _ in 0..number_of_contours { + let contour_point_count = try!(endpoints_reader.read_u16::().map_err(drop)) - + point_index + 1; + let (mut starting_point, mut last_point_was_off_curve) = (Point2D::new(0, 0), false); + for contour_point_index in 0..contour_point_count { + let flags = Flags::from_bits_truncate(*flag_parser.current); + try!(flag_parser.next()); + + let mut delta = Point2D::new(0, 0); + if flags.contains(X_SHORT_VECTOR) { + delta.x = try!(x_coordinate_reader.read_u8().map_err(drop)) as i16; + if !flags.contains(THIS_X_IS_SAME) { + delta.x = -delta.x + } + } else if !flags.contains(THIS_X_IS_SAME) { + delta.x = try!(x_coordinate_reader.read_i16::().map_err(drop)) + } + if flags.contains(Y_SHORT_VECTOR) { + delta.y = try!(y_coordinate_reader.read_u8().map_err(drop)) as i16; + if !flags.contains(THIS_Y_IS_SAME) { + delta.y = -delta.y + } + } else if !flags.contains(THIS_Y_IS_SAME) { + delta.y = try!(y_coordinate_reader.read_i16::().map_err(drop)) + } + + if last_point_was_off_curve && flags.contains(ON_CURVE) { + applier(&Point { + position: position + delta / 2, + on_curve: true, + first_point_in_contour: false, + }) + } + + position = position + delta; + + let first_point_in_contour = contour_point_index == 0; + if first_point_in_contour { + starting_point = position + } + + applier(&Point { + position: position, + on_curve: flags.contains(ON_CURVE), + first_point_in_contour: first_point_in_contour, + }); + + last_point_was_off_curve = !flags.contains(ON_CURVE); + point_index += 1 + } + + // Close the path. + applier(&Point { + position: starting_point, + on_curve: true, + first_point_in_contour: false, + }) + } + + Ok(()) +} + +// Given a reader pointing to the start of the list of flags, returns the size in bytes of the list +// of X coordinates and positions the reader at the start of that list. +#[inline] +fn calculate_size_of_x_coordinates<'a, 'b>(reader: &'a mut &'b [u8], number_of_points: u16) + -> 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(drop))); + let repeat_count = if !flags.contains(REPEAT) { + 1 + } else { + try!(reader.read_u8().map_err(drop)) as u16 + }; + + if flags.contains(X_SHORT_VECTOR) { + x_coordinate_length += repeat_count + } else if !flags.contains(THIS_X_IS_SAME) { + x_coordinate_length += repeat_count * 2 + } + + points_left -= repeat_count + } + + Ok(x_coordinate_length) +} + +struct FlagParser<'a> { + next: &'a [u8], + current: &'a u8, + repeats_left: u8, +} + +impl<'a> FlagParser<'a> { + #[inline] + fn new(buffer: &[u8]) -> Result { + let mut parser = FlagParser { + next: buffer, + current: &buffer[0], + repeats_left: 0, + }; + try!(parser.next()); + Ok(parser) + } + + #[inline] + fn next(&mut self) -> Result<(), ()> { + if self.repeats_left > 0 { + self.repeats_left -= 1; + return Ok(()) + } + + self.current = try!(self.next.get(0).ok_or(())); + let flags = Flags::from_bits_truncate(*self.current); + self.next = &self.next[1..]; + + if flags.contains(REPEAT) { + self.repeats_left = *try!(self.next.get(0).ok_or(())); + self.next = &self.next[1..]; + } else { + self.repeats_left = 0 + } + + Ok(()) + } +} + +impl<'a> GlyphBatch<'a> { + pub fn find_glyph_ranges_for_codepoint_ranges(&mut self, codepoint_batch: &CodepointBatch<'a>) + -> Result<(), ()> { + let mut cached_cmap: Option = None; + for codepoint_range in &codepoint_batch.ranges { + let font_index = codepoint_range.font_index; + + match cached_cmap { + Some(ref cached_cmap) if cached_cmap.font_index == font_index => {} + _ => { + let cmap_table = try!(codepoint_batch.fonts[font_index as usize].get_table(CMAP)); + let cmap_table = try!(cmap_table.ok_or(())); + cached_cmap = Some(CachedCmap { + font_index: font_index, + cmap_table: cmap_table, + encoding_record: None, + }) + } + } + + let cached_cmap = cached_cmap.as_mut().unwrap(); + let mut cmap_table = cached_cmap.cmap_table; + + // Check version. + if try!(cmap_table.read_u16::().map_err(drop)) != 0 { + return Err(()) + } + + let num_tables = try!(cmap_table.read_u16::().map_err(drop)); + + let platform_id = try!(cmap_table.read_u16::().map_err(drop)); + let encoding_id = try!(cmap_table.read_u16::().map_err(drop)); + println!("platform_id={} encoding_id={}", platform_id, encoding_id); + } + + Ok(()) + } +} + +struct CachedCmap<'a> { + font_index: u32, + cmap_table: &'a [u8], + encoding_record: Option<&'a [u8]>, +} + +#[derive(Clone, Copy, Debug)] +pub struct FontData<'a> { + pub bytes: &'a [u8], +} + +impl<'a> FontData<'a> { + #[inline] + pub fn new<'b>(bytes: &'b [u8]) -> FontData<'b> { + FontData { + bytes: bytes, + } + } + + pub fn get_table(&self, table_id: u32) -> Result, ()> { + let mut reader = self.bytes; + let sfnt_version = try!(reader.read_u32::().map_err(drop)); + if sfnt_version != 0x10000 { + return Err(()) + } + + let num_tables = try!(reader.read_u16::().map_err(drop)); + try!(reader.jump(mem::size_of::() * 3)); + + let (mut low, mut high) = (0, num_tables); + while low < high { + let mut reader = reader; + let mid = (low + high) / 2; + try!(reader.jump(mid as usize * mem::size_of::() * 4)); + + let current_table_id = try!(reader.read_u32::().map_err(drop)); + if table_id < current_table_id { + high = mid; + continue + } + if table_id > current_table_id { + low = mid + 1; + continue + } + + // Skip the checksum, and slurp the offset and length. + try!(reader.read_u32::().map_err(drop)); + let offset = try!(reader.read_u32::().map_err(drop)) as usize; + let length = try!(reader.read_u32::().map_err(drop)) as usize; + + let end = offset + length; + if end > self.bytes.len() { + return Err(()) + } + return Ok(Some(&self.bytes[offset..end])) + } + + Ok(None) + } + +} + diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000..f56e60bd --- /dev/null +++ b/src/util.rs @@ -0,0 +1,28 @@ +// Copyright 2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// A faster version of `Seek` that supports only forward motion from the current position. +pub trait Jump { + /// Moves the pointer forward `n` bytes from the *current* position. + fn jump(&mut self, n: usize) -> Result<(), ()>; +} + +impl<'a> Jump for &'a [u8] { + #[inline] + fn jump(&mut self, n: usize) -> Result<(), ()> { + if n <= self.len() { + *self = &(*self)[n..]; + Ok(()) + } else { + Err(()) + } + } +} +