Initial commit

This commit is contained in:
Patrick Walton 2017-01-06 23:49:45 -08:00
commit 34cd1ef9b0
6 changed files with 432 additions and 0 deletions

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "pathfinder"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
bitflags = "0.7"
byteorder = "1"
euclid = "0.10"
memmap = "0.5"

20
examples/dump-outlines.rs Normal file
View File

@ -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();
}
}

58
src/batch.rs Normal file
View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<CodepointRange>,
pub fonts: Vec<FontData<'a>>,
}
#[derive(Clone)]
pub struct GlyphBatch<'a> {
pub ranges: Vec<GlyphRange>,
pub fonts: Vec<FontData<'a>>,
}
impl<'a> GlyphBatch<'a> {
pub fn new<'b>() -> GlyphBatch<'b> {
GlyphBatch {
ranges: vec![],
fonts: vec![],
}
}
}

19
src/lib.rs Normal file
View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;

296
src/otf.rs Normal file
View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<i16>,
on_curve: bool,
first_point_in_contour: bool,
}
pub fn apply_glyph<F>(glyf_table: &[u8], mut applier: F) -> Result<(), ()> where F: FnMut(&Point) {
let mut reader = glyf_table;
let number_of_contours = try!(reader.read_i16::<BigEndian>().map_err(drop));
if number_of_contours < 0 {
// TODO(pcwalton): Composite glyphs.
return Err(())
}
try!(reader.jump(mem::size_of::<u16>() * 4));
// Find out how many points we have.
let mut endpoints_reader = reader;
try!(reader.jump(mem::size_of::<u16>() as usize * (number_of_contours as usize - 1)));
let number_of_points = try!(reader.read_u16::<BigEndian>().map_err(drop));
// Skip over hinting instructions.
let instruction_length = try!(reader.read_u16::<BigEndian>().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::<BigEndian>().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::<BigEndian>().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::<BigEndian>().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<u16, ()> {
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<FlagParser, ()> {
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<CachedCmap> = 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::<BigEndian>().map_err(drop)) != 0 {
return Err(())
}
let num_tables = try!(cmap_table.read_u16::<BigEndian>().map_err(drop));
let platform_id = try!(cmap_table.read_u16::<BigEndian>().map_err(drop));
let encoding_id = try!(cmap_table.read_u16::<BigEndian>().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<Option<&[u8]>, ()> {
let mut reader = self.bytes;
let sfnt_version = try!(reader.read_u32::<BigEndian>().map_err(drop));
if sfnt_version != 0x10000 {
return Err(())
}
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(drop));
try!(reader.jump(mem::size_of::<u16>() * 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::<u32>() * 4));
let current_table_id = try!(reader.read_u32::<BigEndian>().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::<BigEndian>().map_err(drop));
let offset = try!(reader.read_u32::<BigEndian>().map_err(drop)) as usize;
let length = try!(reader.read_u32::<BigEndian>().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)
}
}

28
src/util.rs Normal file
View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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(())
}
}
}