Parse enough of the OTF format to be able to dump outlines

This commit is contained in:
Patrick Walton 2017-01-09 18:29:41 -08:00
parent ce811c0e48
commit abd8fb8e7b
10 changed files with 790 additions and 332 deletions

View File

@ -1,23 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
extern crate euclid;
extern crate memmap;
extern crate pathfinder;
use euclid::Point2D;
use memmap::{Mmap, Protection};
use pathfinder::batch::{CodepointBatch, CodepointRange, GlyphBatch};
use pathfinder::batch::GlyphRange;
use pathfinder::charmap::CodepointRange;
use pathfinder::otf::FontData;
use std::char;
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();
let cmap = font.cmap_table().unwrap();
let glyf = font.glyf_table().unwrap();
let head = font.head_table().unwrap();
let loca = font.loca_table(&head).unwrap();
let codepoint_ranges = [CodepointRange::new('A' as u32, 'Z' as u32)];
let glyph_ranges = cmap.glyph_ranges_for_codepoint_ranges(&codepoint_ranges).unwrap();
for (codepoint, glyph_id) in
codepoint_ranges.iter()
.flat_map(CodepointRange::iter)
.zip(glyph_ranges.iter().flat_map(GlyphRange::iter)) {
println!("Glyph {}: codepoint {} '{}':",
glyph_id,
codepoint,
char::from_u32(codepoint).unwrap_or('?'));
let mut last_point: Option<Point2D<i16>> = None;
let mut last_point_was_off_curve = false;
glyf.for_each_point(&loca, glyph_id as u32, |point| {
if point.first_point_in_contour {
println!("M {},{}", point.position.x, point.position.y);
} else {
let last = last_point.unwrap();
if point.on_curve {
if last_point_was_off_curve {
println!("Q {},{} {},{}",
last.x,
last.y,
point.position.x,
point.position.y);
} else {
println!("L {},{}", point.position.x, point.position.y);
}
}
}
last_point_was_off_curve = !point.on_curve;
last_point = Some(point.position);
}).unwrap()
}
}
}

View File

@ -8,49 +8,57 @@
// 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,
pub start: u16,
pub end: u16,
}
impl GlyphRange {
#[inline]
pub fn iter(&self) -> GlyphRangeIter {
GlyphRangeIter {
start: self.start,
end: self.end,
}
}
}
#[derive(Clone)]
pub struct CodepointBatch<'a> {
pub ranges: Vec<CodepointRange>,
pub fonts: Vec<FontData<'a>>,
pub struct GlyphRangeIter {
start: u16,
end: u16,
}
#[derive(Clone)]
pub struct GlyphBatch<'a> {
impl Iterator for GlyphRangeIter {
type Item = u16;
#[inline]
fn next(&mut self) -> Option<u16> {
if self.start > self.end {
None
} else {
let item = self.start;
self.start += 1;
Some(item)
}
}
}
#[derive(Clone, Debug)]
pub struct FontGlyphRange {
pub font_id: u32,
pub ranges: Vec<GlyphRange>,
pub fonts: Vec<FontData<'a>>,
}
impl<'a> GlyphBatch<'a> {
pub fn new<'b>() -> GlyphBatch<'b> {
GlyphBatch {
ranges: vec![],
#[derive(Clone)]
pub struct FontGlyphRanges {
pub fonts: Vec<FontGlyphRange>,
}
impl FontGlyphRanges {
pub fn new() -> FontGlyphRanges {
FontGlyphRanges {
fonts: vec![],
}
}

55
src/charmap.rs Normal file
View File

@ -0,0 +1,55 @@
// 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.
/// An inclusive codepoint range.
#[derive(Clone, Copy, Debug)]
pub struct CodepointRange {
pub start: u32,
pub end: u32,
}
impl CodepointRange {
#[inline]
pub fn new(start: u32, end: u32) -> CodepointRange {
CodepointRange {
start: start,
end: end,
}
}
#[inline]
pub fn iter(&self) -> CodepointRangeIter {
CodepointRangeIter {
start: self.start,
end: self.end,
}
}
}
pub struct CodepointRangeIter {
start: u32,
end: u32,
}
impl Iterator for CodepointRangeIter {
type Item = u32;
#[inline]
fn next(&mut self) -> Option<u32> {
if self.start > self.end {
None
} else {
let item = self.start;
self.start += 1;
Some(item)
}
}
}

View File

@ -8,8 +8,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(iter_min_by)]
#[macro_use]
extern crate bitflags;
extern crate byteorder;
@ -20,6 +18,7 @@ extern crate quickcheck;
pub mod atlas;
pub mod batch;
pub mod charmap;
pub mod otf;
mod util;

View File

@ -1,296 +0,0 @@
// 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)
}
}

212
src/otf/cmap.rs Normal file
View File

@ -0,0 +1,212 @@
// 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::GlyphRange;
use byteorder::{BigEndian, ReadBytesExt};
use charmap::CodepointRange;
use otf::FontTable;
use std::cmp;
use std::mem;
use std::u16;
use util::Jump;
const PLATFORM_ID_UNICODE: u16 = 0;
const PLATFORM_ID_MICROSOFT: u16 = 3;
const MICROSOFT_ENCODING_ID_UNICODE_BMP: u16 = 1;
const MICROSOFT_ENCODING_ID_UNICODE_UCS4: u16 = 10;
const FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES: u16 = 4;
const MISSING_GLYPH: u16 = 0;
#[derive(Clone, Copy)]
pub struct CmapTable<'a> {
table: FontTable<'a>,
}
impl<'a> CmapTable<'a> {
pub fn new(table: FontTable) -> CmapTable {
CmapTable {
table: table,
}
}
pub fn glyph_ranges_for_codepoint_ranges(&self, codepoint_ranges: &[CodepointRange])
-> Result<Vec<GlyphRange>, ()> {
let mut cmap_reader = self.table.bytes;
// Check version.
if try!(cmap_reader.read_u16::<BigEndian>().map_err(drop)) != 0 {
return Err(())
}
let num_tables = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
// Check platform ID and encoding.
// TODO(pcwalton): Handle more.
// TODO(pcwalton): Search for one that we can handle.
let platform_id = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
let encoding_id = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
match (platform_id, encoding_id) {
(PLATFORM_ID_UNICODE, _) |
(PLATFORM_ID_MICROSOFT, MICROSOFT_ENCODING_ID_UNICODE_BMP) |
(PLATFORM_ID_MICROSOFT, MICROSOFT_ENCODING_ID_UNICODE_UCS4) => {}
_ => return Err(())
}
// Move to the mapping table.
let offset = try!(cmap_reader.read_u32::<BigEndian>().map_err(drop));
cmap_reader = self.table.bytes;
try!(cmap_reader.jump(offset as usize));
// Check the mapping table format.
let format = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
if format != FORMAT_SEGMENT_MAPPING_TO_DELTA_VALUES {
println!("bad format, {:?}", format);
return Err(())
}
// Read the mapping table header.
let length = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
let language = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
let seg_count = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop)) / 2;
let search_range = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
let entry_selector = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
let range_shift = try!(cmap_reader.read_u16::<BigEndian>().map_err(drop));
// Set up parallel array pointers.
//
// NB: Microsoft's spec refers to `startCode` and `endCode` as `startCount` and `endCount`
// respectively in a few places. I believe this is a mistake, and `startCode` and `endCode`
// are the correct names.
let (end_codes, mut start_codes) = (cmap_reader, cmap_reader);
try!(start_codes.jump((seg_count as usize + 1) * mem::size_of::<u16>()));
let mut id_deltas = start_codes;
try!(id_deltas.jump(seg_count as usize * mem::size_of::<u16>()));
let mut id_range_offsets = id_deltas;
try!(id_range_offsets.jump(seg_count as usize * mem::size_of::<u16>()));
let mut glyph_ids = id_range_offsets;
try!(glyph_ids.jump(seg_count as usize * mem::size_of::<u16>()));
// Now perform the lookups.
let mut glyph_ranges = vec![];
for codepoint_range in codepoint_ranges {
let mut codepoint_range = *codepoint_range;
while codepoint_range.end >= codepoint_range.start {
if codepoint_range.start > u16::MAX as u32 {
codepoint_range.start += 1;
glyph_ranges.push(GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
});
continue
}
let start_codepoint_range = codepoint_range.start as u16;
let mut end_codepoint_range = codepoint_range.end as u16;
// Binary search to find the segment.
let (mut low, mut high) = (0, seg_count);
let mut segment_index = None;
while low < high {
let mid = (low + high) / 2;
let mut end_code = end_codes;
try!(end_code.jump(mid as usize * 2));
let end_code = try!(end_code.read_u16::<BigEndian>().map_err(drop));
if start_codepoint_range > end_code {
low = mid + 1;
continue
}
let mut start_code = start_codes;
try!(start_code.jump(mid as usize * 2));
let start_code = try!(start_code.read_u16::<BigEndian>().map_err(drop));
if start_codepoint_range < start_code {
high = mid;
continue
}
segment_index = Some(mid);
break
}
let segment_index = match segment_index {
Some(segment_index) => segment_index,
None => {
codepoint_range.start += 1;
glyph_ranges.push(GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
});
continue
}
};
// Read out the segment info.
let mut start_code = start_codes;
let mut end_code = end_codes;
let mut id_range_offset = id_range_offsets;
let mut id_delta = id_deltas;
try!(start_code.jump(segment_index as usize * 2));
try!(end_code.jump(segment_index as usize * 2));
try!(id_range_offset.jump(segment_index as usize * 2));
try!(id_delta.jump(segment_index as usize * 2));
let start_code = try!(start_code.read_u16::<BigEndian>().map_err(drop));
let end_code = try!(end_code.read_u16::<BigEndian>().map_err(drop));
let id_range_offset = try!(id_range_offset.read_u16::<BigEndian>().map_err(drop));
let id_delta = try!(id_delta.read_i16::<BigEndian>().map_err(drop));
end_codepoint_range = cmp::min(end_codepoint_range, end_code);
codepoint_range.start = (end_codepoint_range + 1) as u32;
let start_code_offset = start_codepoint_range - start_code;
let end_code_offset = end_codepoint_range - start_code;
// If we're direct-mapped (`idRangeOffset` = 0), then try to convert as much of the
// codepoint range as possible to a contiguous glyph range.
if id_range_offset == 0 {
// Microsoft's documentation is contradictory as to whether the code offset or
// the actual code is added to the ID delta here. In reality it seems to be the
// latter.
glyph_ranges.push(GlyphRange {
start: (start_codepoint_range as i16).wrapping_add(id_delta) as u16,
end: (end_codepoint_range as i16).wrapping_add(id_delta) as u16,
});
continue
}
// Otherwise, look up the glyphs individually.
for code_offset in start_code_offset..(end_code_offset + 1) {
println!("individual lookup");
let mut glyph_id = glyph_ids;
try!(glyph_id.jump((id_range_offset as usize + code_offset as usize) * 2));
let mut glyph_id = try!(glyph_id.read_u16::<BigEndian>().map_err(drop));
if glyph_id == 0 {
glyph_ranges.push(GlyphRange {
start: MISSING_GLYPH,
end: MISSING_GLYPH,
})
} else {
glyph_id = (glyph_id as i16).wrapping_add(id_delta) as u16;
glyph_ranges.push(GlyphRange {
start: glyph_id,
end: glyph_id,
})
}
}
}
}
Ok(glyph_ranges)
}
}

212
src/otf/glyf.rs Normal file
View File

@ -0,0 +1,212 @@
// 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 byteorder::{BigEndian, ReadBytesExt};
use euclid::Point2D;
use otf::FontTable;
use otf::loca::LocaTable;
use std::mem;
use util::Jump;
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)]
pub struct Point {
pub position: Point2D<i16>,
pub on_curve: bool,
pub first_point_in_contour: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct GlyfTable<'a> {
pub table: FontTable<'a>,
}
impl<'a> GlyfTable<'a> {
#[inline]
pub fn new(table: FontTable) -> GlyfTable {
GlyfTable {
table: table,
}
}
pub fn for_each_point<F>(&self, loca_table: &LocaTable, glyph_id: u32, mut callback: F)
-> Result<(), ()> where F: FnMut(&Point) {
let mut reader = self.table.bytes;
let offset = try!(loca_table.location_of(glyph_id));
try!(reader.jump(offset as usize));
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::<i16>() * 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)) + 1;
// 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;
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;
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) {
callback(&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
}
callback(&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.
callback(&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 + 1
};
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(())
}
}

63
src/otf/head.rs Normal file
View File

@ -0,0 +1,63 @@
// 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 byteorder::{BigEndian, ReadBytesExt};
use otf::FontTable;
use std::mem;
use util::Jump;
const MAGIC_NUMBER: u32 = 0x5f0f3cf5;
pub struct HeadTable {
pub units_per_em: u16,
pub index_to_loc_format: i16,
}
impl HeadTable {
pub fn new(table: FontTable) -> Result<HeadTable, ()> {
let mut reader = table.bytes;
// Check the version.
let major_version = try!(reader.read_u16::<BigEndian>().map_err(drop));
let minor_version = try!(reader.read_u16::<BigEndian>().map_err(drop));
if (major_version, minor_version) != (1, 0) {
return Err(())
}
// Check the magic number.
try!(reader.jump(mem::size_of::<u32>() * 2));
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(drop));
if magic_number != MAGIC_NUMBER {
return Err(())
}
// Read the units per em.
try!(reader.jump(mem::size_of::<u16>()));
let units_per_em = try!(reader.read_u16::<BigEndian>().map_err(drop));
// Read the index-to-location format.
try!(reader.jump(mem::size_of::<i64>() * 2 +
mem::size_of::<i16>() * 4 +
mem::size_of::<u16>() * 2 +
mem::size_of::<i16>()));
let index_to_loc_format = try!(reader.read_i16::<BigEndian>().map_err(drop));
// Check the glyph data format.
let glyph_data_format = try!(reader.read_i16::<BigEndian>().map_err(drop));
if glyph_data_format != 0 {
return Err(())
}
Ok(HeadTable {
units_per_em: units_per_em,
index_to_loc_format: index_to_loc_format,
})
}
}

43
src/otf/loca.rs Normal file
View File

@ -0,0 +1,43 @@
//
// 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 byteorder::{BigEndian, ReadBytesExt};
use otf::FontTable;
use otf::head::HeadTable;
use util::Jump;
pub struct LocaTable<'a> {
table: FontTable<'a>,
pub long: bool,
}
impl<'a> LocaTable<'a> {
pub fn new(loca_table: FontTable<'a>, head_table: &HeadTable) -> Result<LocaTable<'a>, ()> {
let long = match head_table.index_to_loc_format {
0 => false,
1 => true,
_ => return Err(()),
};
Ok(LocaTable {
table: loca_table,
long: long,
})
}
pub fn location_of(&self, glyph_id: u32) -> Result<u32, ()> {
let mut reader = self.table.bytes;
if !self.long {
try!(reader.jump(glyph_id as usize * 2));
Ok(try!(reader.read_u16::<BigEndian>().map_err(drop)) as u32 * 2)
} else {
try!(reader.jump(glyph_id as usize * 4));
reader.read_u32::<BigEndian>().map_err(drop)
}
}
}

124
src/otf/mod.rs Normal file
View File

@ -0,0 +1,124 @@
// 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 byteorder::{BigEndian, ReadBytesExt};
use otf::cmap::CmapTable;
use otf::glyf::GlyfTable;
use otf::head::HeadTable;
use otf::loca::LocaTable;
use std::mem;
use std::u16;
use util::Jump;
pub mod cmap;
pub mod glyf;
pub mod head;
pub mod loca;
const CMAP: u32 = ((b'c' as u32) << 24) |
((b'm' as u32) << 16) |
((b'a' as u32) << 8) |
(b'p' as u32);
const GLYF: u32 = ((b'g' as u32) << 24) |
((b'l' as u32) << 16) |
((b'y' as u32) << 8) |
(b'f' as u32);
const HEAD: u32 = ((b'h' as u32) << 24) |
((b'e' as u32) << 16) |
((b'a' as u32) << 8) |
(b'd' as u32);
const LOCA: u32 = ((b'l' as u32) << 24) |
((b'o' as u32) << 16) |
((b'c' as u32) << 8) |
(b'a' as u32);
#[derive(Clone, Copy, Debug)]
pub struct FontData<'a> {
pub bytes: &'a [u8],
}
#[derive(Clone, Copy, Debug)]
pub struct FontTable<'a> {
pub bytes: &'a [u8],
}
impl<'a> FontData<'a> {
#[inline]
pub fn new<'b>(bytes: &'b [u8]) -> FontData<'b> {
FontData {
bytes: bytes,
}
}
fn table(&self, table_id: u32) -> Result<Option<FontTable>, ()> {
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(FontTable {
bytes: &self.bytes[offset..end],
}))
}
Ok(None)
}
#[inline]
pub fn cmap_table(&self) -> Result<CmapTable, ()> {
self.table(CMAP).and_then(|table| table.ok_or(()).map(CmapTable::new))
}
#[inline]
pub fn glyf_table(&self) -> Result<GlyfTable, ()> {
self.table(GLYF).and_then(|table| table.ok_or(()).map(GlyfTable::new))
}
#[inline]
pub fn head_table(&self) -> Result<HeadTable, ()> {
self.table(HEAD).and_then(|table| table.ok_or(()).and_then(HeadTable::new))
}
#[inline]
pub fn loca_table(&self, head_table: &HeadTable) -> Result<LocaTable, ()> {
let loca_table = try!(self.table(LOCA).and_then(|table| table.ok_or(())));
LocaTable::new(loca_table, head_table)
}
}