Parse enough of the OTF format to be able to dump outlines
This commit is contained in:
parent
ce811c0e48
commit
abd8fb8e7b
|
@ -1,23 +1,61 @@
|
||||||
/* Any copyright is dedicated to the Public Domain.
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
extern crate euclid;
|
||||||
extern crate memmap;
|
extern crate memmap;
|
||||||
extern crate pathfinder;
|
extern crate pathfinder;
|
||||||
|
|
||||||
|
use euclid::Point2D;
|
||||||
use memmap::{Mmap, Protection};
|
use memmap::{Mmap, Protection};
|
||||||
use pathfinder::batch::{CodepointBatch, CodepointRange, GlyphBatch};
|
use pathfinder::batch::GlyphRange;
|
||||||
|
use pathfinder::charmap::CodepointRange;
|
||||||
use pathfinder::otf::FontData;
|
use pathfinder::otf::FontData;
|
||||||
|
use std::char;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap();
|
let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap();
|
||||||
unsafe {
|
unsafe {
|
||||||
let font = FontData::new(file.as_slice());
|
let font = FontData::new(file.as_slice());
|
||||||
let mut glyph_batch = GlyphBatch::new();
|
let cmap = font.cmap_table().unwrap();
|
||||||
glyph_batch.find_glyph_ranges_for_codepoint_ranges(&CodepointBatch {
|
let glyf = font.glyf_table().unwrap();
|
||||||
ranges: vec![CodepointRange::new('A' as u32, 'Z' as u32, 0)],
|
let head = font.head_table().unwrap();
|
||||||
fonts: vec![font],
|
let loca = font.loca_table(&head).unwrap();
|
||||||
}).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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
64
src/batch.rs
64
src/batch.rs
|
@ -8,49 +8,57 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use otf::FontData;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct CodepointRange {
|
pub struct GlyphRange {
|
||||||
pub start: u32,
|
pub start: u16,
|
||||||
pub end: u32,
|
pub end: u16,
|
||||||
pub font_index: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodepointRange {
|
impl GlyphRange {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(start: u32, end: u32, font_index: u32) -> CodepointRange {
|
pub fn iter(&self) -> GlyphRangeIter {
|
||||||
CodepointRange {
|
GlyphRangeIter {
|
||||||
start: start,
|
start: self.start,
|
||||||
end: end,
|
end: self.end,
|
||||||
font_index: font_index,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct GlyphRange {
|
pub struct GlyphRangeIter {
|
||||||
pub start: u32,
|
start: u16,
|
||||||
pub end: u32,
|
end: u16,
|
||||||
pub font_index: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
impl Iterator for GlyphRangeIter {
|
||||||
pub struct CodepointBatch<'a> {
|
type Item = u16;
|
||||||
pub ranges: Vec<CodepointRange>,
|
|
||||||
pub fonts: Vec<FontData<'a>>,
|
#[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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GlyphBatch<'a> {
|
pub struct FontGlyphRange {
|
||||||
|
pub font_id: u32,
|
||||||
pub ranges: Vec<GlyphRange>,
|
pub ranges: Vec<GlyphRange>,
|
||||||
pub fonts: Vec<FontData<'a>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GlyphBatch<'a> {
|
#[derive(Clone)]
|
||||||
pub fn new<'b>() -> GlyphBatch<'b> {
|
pub struct FontGlyphRanges {
|
||||||
GlyphBatch {
|
pub fonts: Vec<FontGlyphRange>,
|
||||||
ranges: vec![],
|
}
|
||||||
|
|
||||||
|
impl FontGlyphRanges {
|
||||||
|
pub fn new() -> FontGlyphRanges {
|
||||||
|
FontGlyphRanges {
|
||||||
fonts: vec![],
|
fonts: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
#![feature(iter_min_by)]
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
@ -20,6 +18,7 @@ extern crate quickcheck;
|
||||||
|
|
||||||
pub mod atlas;
|
pub mod atlas;
|
||||||
pub mod batch;
|
pub mod batch;
|
||||||
|
pub mod charmap;
|
||||||
pub mod otf;
|
pub mod otf;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
|
296
src/otf.rs
296
src/otf.rs
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue