diff --git a/examples/dump-outlines.rs b/examples/dump-outlines.rs index 118219d6..3b745d60 100644 --- a/examples/dump-outlines.rs +++ b/examples/dump-outlines.rs @@ -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> = 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() + } } } diff --git a/src/batch.rs b/src/batch.rs index 9e371854..b9532447 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -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, +pub struct GlyphRange { + pub start: u16, + pub end: u16, } -impl CodepointRange { +impl GlyphRange { #[inline] - pub fn new(start: u32, end: u32, font_index: u32) -> CodepointRange { - CodepointRange { - start: start, - end: end, - font_index: font_index, + pub fn iter(&self) -> GlyphRangeIter { + GlyphRangeIter { + start: self.start, + end: self.end, } } } -#[derive(Clone, Copy, Debug)] -pub struct GlyphRange { - pub start: u32, - pub end: u32, - pub font_index: u32, +#[derive(Clone)] +pub struct GlyphRangeIter { + start: u16, + end: u16, } -#[derive(Clone)] -pub struct CodepointBatch<'a> { - pub ranges: Vec, - pub fonts: Vec>, +impl Iterator for GlyphRangeIter { + type Item = u16; + + #[inline] + fn next(&mut self) -> Option { + if self.start > self.end { + None + } else { + let item = self.start; + self.start += 1; + Some(item) + } + } } -#[derive(Clone)] -pub struct GlyphBatch<'a> { +#[derive(Clone, Debug)] +pub struct FontGlyphRange { + pub font_id: u32, pub ranges: Vec, - pub fonts: Vec>, } -impl<'a> GlyphBatch<'a> { - pub fn new<'b>() -> GlyphBatch<'b> { - GlyphBatch { - ranges: vec![], +#[derive(Clone)] +pub struct FontGlyphRanges { + pub fonts: Vec, +} + +impl FontGlyphRanges { + pub fn new() -> FontGlyphRanges { + FontGlyphRanges { fonts: vec![], } } diff --git a/src/charmap.rs b/src/charmap.rs new file mode 100644 index 00000000..d1a04c00 --- /dev/null +++ b/src/charmap.rs @@ -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 or the MIT license +// , 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 { + if self.start > self.end { + None + } else { + let item = self.start; + self.start += 1; + Some(item) + } + } +} + diff --git a/src/lib.rs b/src/lib.rs index d63957ca..b4c0962c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/otf.rs b/src/otf.rs deleted file mode 100644 index e91f0754..00000000 --- a/src/otf.rs +++ /dev/null @@ -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 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/otf/cmap.rs b/src/otf/cmap.rs new file mode 100644 index 00000000..9a5c8d15 --- /dev/null +++ b/src/otf/cmap.rs @@ -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 or the MIT license +// , 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, ()> { + let mut cmap_reader = self.table.bytes; + + // Check version. + if try!(cmap_reader.read_u16::().map_err(drop)) != 0 { + return Err(()) + } + + let num_tables = try!(cmap_reader.read_u16::().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::().map_err(drop)); + let encoding_id = try!(cmap_reader.read_u16::().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::().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::().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::().map_err(drop)); + let language = try!(cmap_reader.read_u16::().map_err(drop)); + let seg_count = try!(cmap_reader.read_u16::().map_err(drop)) / 2; + let search_range = try!(cmap_reader.read_u16::().map_err(drop)); + let entry_selector = try!(cmap_reader.read_u16::().map_err(drop)); + let range_shift = try!(cmap_reader.read_u16::().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::())); + let mut id_deltas = start_codes; + try!(id_deltas.jump(seg_count as usize * mem::size_of::())); + let mut id_range_offsets = id_deltas; + try!(id_range_offsets.jump(seg_count as usize * mem::size_of::())); + let mut glyph_ids = id_range_offsets; + try!(glyph_ids.jump(seg_count as usize * mem::size_of::())); + + // 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::().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::().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::().map_err(drop)); + let end_code = try!(end_code.read_u16::().map_err(drop)); + let id_range_offset = try!(id_range_offset.read_u16::().map_err(drop)); + let id_delta = try!(id_delta.read_i16::().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::().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) + } +} + diff --git a/src/otf/glyf.rs b/src/otf/glyf.rs new file mode 100644 index 00000000..ee68c5cb --- /dev/null +++ b/src/otf/glyf.rs @@ -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 or the MIT license +// , 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, + 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(&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::().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)) + 1; + + // 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; + 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::().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) { + 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 { + 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 { + 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(()) + } +} + diff --git a/src/otf/head.rs b/src/otf/head.rs new file mode 100644 index 00000000..0bbe4452 --- /dev/null +++ b/src/otf/head.rs @@ -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 or the MIT license +// , 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 { + let mut reader = table.bytes; + + // Check the version. + let major_version = try!(reader.read_u16::().map_err(drop)); + let minor_version = try!(reader.read_u16::().map_err(drop)); + if (major_version, minor_version) != (1, 0) { + return Err(()) + } + + // Check the magic number. + try!(reader.jump(mem::size_of::() * 2)); + let magic_number = try!(reader.read_u32::().map_err(drop)); + if magic_number != MAGIC_NUMBER { + return Err(()) + } + + // Read the units per em. + try!(reader.jump(mem::size_of::())); + let units_per_em = try!(reader.read_u16::().map_err(drop)); + + // Read the index-to-location format. + try!(reader.jump(mem::size_of::() * 2 + + mem::size_of::() * 4 + + mem::size_of::() * 2 + + mem::size_of::())); + let index_to_loc_format = try!(reader.read_i16::().map_err(drop)); + + // Check the glyph data format. + let glyph_data_format = try!(reader.read_i16::().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, + }) + } +} diff --git a/src/otf/loca.rs b/src/otf/loca.rs new file mode 100644 index 00000000..43051c8f --- /dev/null +++ b/src/otf/loca.rs @@ -0,0 +1,43 @@ +// +// 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 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, ()> { + 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 { + let mut reader = self.table.bytes; + if !self.long { + try!(reader.jump(glyph_id as usize * 2)); + Ok(try!(reader.read_u16::().map_err(drop)) as u32 * 2) + } else { + try!(reader.jump(glyph_id as usize * 4)); + reader.read_u32::().map_err(drop) + } + } +} + diff --git a/src/otf/mod.rs b/src/otf/mod.rs new file mode 100644 index 00000000..d3d14ed1 --- /dev/null +++ b/src/otf/mod.rs @@ -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 or the MIT license +// , 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, ()> { + 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(FontTable { + bytes: &self.bytes[offset..end], + })) + } + + Ok(None) + } + + #[inline] + pub fn cmap_table(&self) -> Result { + self.table(CMAP).and_then(|table| table.ok_or(()).map(CmapTable::new)) + } + + #[inline] + pub fn glyf_table(&self) -> Result { + self.table(GLYF).and_then(|table| table.ok_or(()).map(GlyfTable::new)) + } + + #[inline] + pub fn head_table(&self) -> Result { + self.table(HEAD).and_then(|table| table.ok_or(()).and_then(HeadTable::new)) + } + + #[inline] + pub fn loca_table(&self, head_table: &HeadTable) -> Result { + let loca_table = try!(self.table(LOCA).and_then(|table| table.ok_or(()))); + LocaTable::new(loca_table, head_table) + } +} +