Initial commit
This commit is contained in:
commit
34cd1ef9b0
|
@ -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"
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue