Implement partial support for the CFF format
Curves are not yet supported.
This commit is contained in:
parent
4ffc3b9e41
commit
e71dd1241f
|
@ -0,0 +1,628 @@
|
||||||
|
// 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, LittleEndian, ReadBytesExt};
|
||||||
|
use euclid::Point2D;
|
||||||
|
use otf::glyf::Point;
|
||||||
|
use otf::head::HeadTable;
|
||||||
|
use otf::{Error, FontTable};
|
||||||
|
use outline::GlyphBounds;
|
||||||
|
use std::cmp;
|
||||||
|
use std::u16;
|
||||||
|
use util::Jump;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct CffTable<'a> {
|
||||||
|
// The offset of the char strings INDEX.
|
||||||
|
char_strings: u32,
|
||||||
|
table: FontTable<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CffTable<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(table: FontTable) -> Result<CffTable, Error> {
|
||||||
|
let mut reader = table.bytes;
|
||||||
|
|
||||||
|
// Check version.
|
||||||
|
let major = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
let minor = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
if major != 1 || minor != 0 {
|
||||||
|
return Err(Error::UnsupportedCffVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the header.
|
||||||
|
let hdr_size = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
try!(reader.jump(hdr_size as usize - 3).map_err(Error::eof));
|
||||||
|
|
||||||
|
// Skip the name INDEX.
|
||||||
|
//
|
||||||
|
// TODO(pcwalton): What to do if there are multiple fonts here?
|
||||||
|
try!(skip_index(&mut reader));
|
||||||
|
|
||||||
|
// Get the top DICT for our font.
|
||||||
|
if try!(find_in_index(&mut reader, 0)).is_none() {
|
||||||
|
return Err(Error::CffTopDictNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the CharStrings offset within the top DICT.
|
||||||
|
let char_strings = try!(get_integer_in_dict(&mut reader, 17));
|
||||||
|
|
||||||
|
// Skip the string INDEX.
|
||||||
|
try!(skip_index(&mut reader));
|
||||||
|
|
||||||
|
// Ignore the global subr INDEX for now.
|
||||||
|
//
|
||||||
|
// TODO(pcwalton): Support global subroutines.
|
||||||
|
|
||||||
|
Ok(CffTable {
|
||||||
|
char_strings: char_strings as u32,
|
||||||
|
table: table,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn for_each_point<F>(&self, glyph_id: u16, mut callback: F)
|
||||||
|
-> Result<(), Error> where F: FnMut(&Point) {
|
||||||
|
let mut reader = self.table.bytes;
|
||||||
|
try!(reader.jump(self.char_strings as usize).map_err(Error::eof));
|
||||||
|
|
||||||
|
let char_string_length = match try!(find_in_index(&mut reader, glyph_id)) {
|
||||||
|
Some(char_string_length) => char_string_length,
|
||||||
|
None => return Err(Error::UnexpectedEof),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = &reader[0..char_string_length as usize];
|
||||||
|
let mut stack = EvaluationStack::new();
|
||||||
|
let (mut start, mut pos) = (Point2D::new(0, 0), Point2D::new(0, 0));
|
||||||
|
let mut index_in_contour = 0;
|
||||||
|
let mut hint_count = 0;
|
||||||
|
|
||||||
|
// FIXME(pcwalton): This shouldn't panic on stack bounds check failures.
|
||||||
|
while let Ok(b0) = reader.read_u8() {
|
||||||
|
match b0 {
|
||||||
|
32...246 => try!(stack.push(b0 as i32 - 139)),
|
||||||
|
247...250 => {
|
||||||
|
let b1 = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
try!(stack.push((b0 as i32 - 247) * 256 + b1 as i32 + 108))
|
||||||
|
}
|
||||||
|
251...254 => {
|
||||||
|
let b1 = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
try!(stack.push((b0 as i32 - 251) * -256 - b1 as i32 - 108))
|
||||||
|
}
|
||||||
|
255 => {
|
||||||
|
// FIXME(pcwalton): Don't truncate the lower 16 bits.
|
||||||
|
try!(stack.push(try!(reader.read_i32::<BigEndian>().map_err(Error::eof)) >>
|
||||||
|
16))
|
||||||
|
}
|
||||||
|
28 => {
|
||||||
|
let number = try!(reader.read_i16::<BigEndian>().map_err(Error::eof)) as i32;
|
||||||
|
try!(stack.push(number))
|
||||||
|
}
|
||||||
|
|
||||||
|
4 => {
|
||||||
|
// |- dy1 vmoveto
|
||||||
|
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
|
||||||
|
pos.y += stack.array[0] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: 0,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
start = pos;
|
||||||
|
index_in_contour = 1;
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
// |- {dxa dya}+ rlineto
|
||||||
|
for points in stack.array[0..stack.size as usize].chunks(2) {
|
||||||
|
pos = pos + Point2D::new(points[0] as i16, points[1] as i16);
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
index_in_contour += 1
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
6 => {
|
||||||
|
// |- dx1 {dya dxb}* hlineto
|
||||||
|
// |- {dxa dyb}* hlineto
|
||||||
|
for (i, length) in stack.array[0..stack.size as usize].iter().enumerate() {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
pos.x += *length as i16
|
||||||
|
} else {
|
||||||
|
pos.y += *length as i16
|
||||||
|
}
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
index_in_contour += 1
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
7 => {
|
||||||
|
// |- dy1 {dxa dyb}* vlineto
|
||||||
|
// |- {dya dxb}* vlineto
|
||||||
|
for (i, length) in stack.array[0..stack.size as usize].iter().enumerate() {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
pos.y += *length as i16
|
||||||
|
} else {
|
||||||
|
pos.x += *length as i16
|
||||||
|
}
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
index_in_contour += 1
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
8 => {
|
||||||
|
// |- {dxa dya dxb dyb dxc dyc}+ rrcurveto (8)
|
||||||
|
for chunk in stack.array[0..stack.size as usize].chunks(6) {
|
||||||
|
pos = pos + Point2D::new(chunk[0] as i16, chunk[1] as i16);
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour + 0,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos = pos + Point2D::new(chunk[2] as i16, chunk[3] as i16);
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour + 1,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos = pos + Point2D::new(chunk[4] as i16, chunk[5] as i16);
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour + 2,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
index_in_contour += 3
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
30 => {
|
||||||
|
// |- dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30)
|
||||||
|
// |- {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto (30)
|
||||||
|
for (i, chunk) in stack.array[0..stack.size as usize].chunks(4).enumerate() {
|
||||||
|
if chunk.len() != 4 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let dxyf = if i * 4 + 5 == stack.size as usize {
|
||||||
|
stack.array[stack.size as usize - 1]
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if i % 2 == 0 {
|
||||||
|
process_hvcurveto_v(chunk,
|
||||||
|
&mut pos,
|
||||||
|
dxyf,
|
||||||
|
&mut index_in_contour,
|
||||||
|
&mut callback);
|
||||||
|
} else {
|
||||||
|
process_hvcurveto_h(chunk,
|
||||||
|
&mut pos,
|
||||||
|
dxyf,
|
||||||
|
&mut index_in_contour,
|
||||||
|
&mut callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
31 => {
|
||||||
|
// |- dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? hvcurveto (31)
|
||||||
|
// |- {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? hvcurveto (31)
|
||||||
|
for (i, chunk) in stack.array[0..stack.size as usize].chunks(4).enumerate() {
|
||||||
|
if chunk.len() != 4 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let dxyf = if i * 4 + 5 == stack.size as usize {
|
||||||
|
stack.array[stack.size as usize - 1]
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if i % 2 == 0 {
|
||||||
|
process_hvcurveto_h(chunk,
|
||||||
|
&mut pos,
|
||||||
|
dxyf,
|
||||||
|
&mut index_in_contour,
|
||||||
|
&mut callback);
|
||||||
|
} else {
|
||||||
|
process_hvcurveto_v(chunk,
|
||||||
|
&mut pos,
|
||||||
|
dxyf,
|
||||||
|
&mut index_in_contour,
|
||||||
|
&mut callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
26 => {
|
||||||
|
// |- dx1? {dya dxb dyb dyc}+ vvcurveto (26)
|
||||||
|
let start;
|
||||||
|
if stack.size % 2 == 0 {
|
||||||
|
start = 0
|
||||||
|
} else {
|
||||||
|
pos.x += stack.array[0] as i16;
|
||||||
|
start = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, chunk) in stack.array[start..stack.size as usize]
|
||||||
|
.chunks(4)
|
||||||
|
.enumerate() {
|
||||||
|
pos.y += chunk[0] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.x += chunk[1] as i16;
|
||||||
|
pos.y += chunk[2] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour + 1,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.y += chunk[3] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour + 2,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
index_in_contour += 3
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
27 => {
|
||||||
|
// |- dy1? {dxa dxb dyb dxc}+ hhcurveto (27)
|
||||||
|
let start;
|
||||||
|
if stack.size % 2 == 0 {
|
||||||
|
start = 0
|
||||||
|
} else {
|
||||||
|
pos.y += stack.array[0] as i16;
|
||||||
|
start = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, chunk) in stack.array[start..stack.size as usize]
|
||||||
|
.chunks(4)
|
||||||
|
.enumerate() {
|
||||||
|
pos.x += chunk[0] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.x += chunk[1] as i16;
|
||||||
|
pos.y += chunk[2] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour + 1,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.x += chunk[3] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: index_in_contour + 2,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
index_in_contour += 3
|
||||||
|
}
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
14 => {
|
||||||
|
// endchar
|
||||||
|
break
|
||||||
|
}
|
||||||
|
1 | 18 => {
|
||||||
|
// hstem hint (ignored)
|
||||||
|
hint_count += stack.size as u16 / 2;
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
3 | 23 => {
|
||||||
|
// vstem hint (ignored)
|
||||||
|
hint_count += stack.size as u16 / 2;
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
19 => {
|
||||||
|
// hintmask (ignored)
|
||||||
|
//
|
||||||
|
// First, process an implicit vstem hint.
|
||||||
|
//
|
||||||
|
// FIXME(pcwalton): Should only do that if we're in the header.
|
||||||
|
hint_count += stack.size as u16 / 2;
|
||||||
|
stack.clear();
|
||||||
|
|
||||||
|
// Now skip ⌈hint_count / 8⌉ bytes.
|
||||||
|
let hint_byte_count = (hint_count as usize + 7) / 8;
|
||||||
|
try!(reader.jump(hint_byte_count).map_err(Error::eof));
|
||||||
|
}
|
||||||
|
21 => {
|
||||||
|
// |- dx1 dy1 rmoveto
|
||||||
|
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
|
||||||
|
pos = pos + Point2D::new(stack.array[0] as i16, stack.array[1] as i16);
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: 0,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
start = pos;
|
||||||
|
index_in_contour = 1;
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
22 => {
|
||||||
|
// |- dx1 hmoveto
|
||||||
|
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
|
||||||
|
pos.x += stack.array[0] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: pos,
|
||||||
|
index_in_contour: 0,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
start = pos;
|
||||||
|
index_in_contour = 1;
|
||||||
|
stack.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
12 => {
|
||||||
|
// TODO(pcwalton): Support these extended operators.
|
||||||
|
let _operator = (12 << 8) |
|
||||||
|
(try!(reader.read_u8().map_err(Error::eof)) as u32);
|
||||||
|
stack.clear();
|
||||||
|
return Err(Error::CffUnimplementedOperator)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
stack.clear();
|
||||||
|
return Err(Error::CffUnimplementedOperator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close_path_if_necessary(&mut pos, &start, index_in_contour, &mut callback);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(pcwalton): Do some caching, perhaps?
|
||||||
|
// TODO(pcwalton): Compute this at the same time as `for_each_point`, perhaps?
|
||||||
|
pub fn glyph_bounds(&self, glyph_id: u16) -> Result<GlyphBounds, Error> {
|
||||||
|
let mut bounds = GlyphBounds::default();
|
||||||
|
self.for_each_point(glyph_id, |point| {
|
||||||
|
bounds.left = cmp::min(bounds.left, point.position.x as i32);
|
||||||
|
bounds.bottom = cmp::min(bounds.bottom, point.position.y as i32);
|
||||||
|
bounds.right = cmp::max(bounds.right, point.position.x as i32);
|
||||||
|
bounds.top = cmp::max(bounds.top, point.position.y as i32);
|
||||||
|
});
|
||||||
|
Ok(bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moves the reader to the location of the given element in the index. Returns the length of the
|
||||||
|
// element if the element was found or `None` otherwise.
|
||||||
|
fn find_in_index(reader: &mut &[u8], index: u16) -> Result<Option<u32>, Error> {
|
||||||
|
let count = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
|
||||||
|
if count == 0 {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
let off_size = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
|
||||||
|
let mut offset_reader = *reader;
|
||||||
|
try!(offset_reader.jump(off_size as usize * cmp::min(index, count) as usize)
|
||||||
|
.map_err(Error::eof));
|
||||||
|
let offset = try!(read_offset(&mut offset_reader, off_size));
|
||||||
|
|
||||||
|
let next_offset = if index < count {
|
||||||
|
Some(try!(read_offset(&mut offset_reader, off_size)) - offset)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
try!(reader.jump(off_size as usize * (count as usize + 1) + offset as usize - 1)
|
||||||
|
.map_err(Error::eof));
|
||||||
|
return Ok(next_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skips over an INDEX by reading the last element in the offset array and seeking the appropriate
|
||||||
|
// number of bytes forward.
|
||||||
|
fn skip_index(reader: &mut &[u8]) -> Result<(), Error> {
|
||||||
|
find_in_index(reader, u16::MAX).map(drop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the integer with the given operator.
|
||||||
|
fn get_integer_in_dict(reader: &mut &[u8], operator: u16) -> Result<i32, Error> {
|
||||||
|
let mut last_integer_operand = None;
|
||||||
|
loop {
|
||||||
|
let b0 = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
match b0 {
|
||||||
|
32...246 => last_integer_operand = Some(b0 as i32 - 139),
|
||||||
|
247...250 => {
|
||||||
|
let b1 = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
last_integer_operand = Some((b0 as i32 - 247) * 256 + b1 as i32 + 108)
|
||||||
|
}
|
||||||
|
251...254 => {
|
||||||
|
let b1 = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
last_integer_operand = Some(-(b0 as i32 - 251) * 256 - b1 as i32 - 108)
|
||||||
|
}
|
||||||
|
28 => {
|
||||||
|
last_integer_operand =
|
||||||
|
Some(try!(reader.read_i16::<BigEndian>().map_err(Error::eof)) as i32)
|
||||||
|
}
|
||||||
|
29 => {
|
||||||
|
last_integer_operand =
|
||||||
|
Some(try!(reader.read_i32::<BigEndian>().map_err(Error::eof)) as i32)
|
||||||
|
}
|
||||||
|
30 => {
|
||||||
|
// TODO(pcwalton): Real numbers.
|
||||||
|
while (try!(reader.read_u8().map_err(Error::eof)) & 0xf) != 0xf {}
|
||||||
|
}
|
||||||
|
12 => {
|
||||||
|
let b1 = try!(reader.read_u8().map_err(Error::eof));
|
||||||
|
if operator == (((b1 as u16) << 8) | (b0 as u16)) {
|
||||||
|
match last_integer_operand {
|
||||||
|
Some(last_integer_operand) => return Ok(last_integer_operand),
|
||||||
|
None => return Err(Error::CffIntegerNotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_integer_operand = None
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if operator == b0 as u16 {
|
||||||
|
match last_integer_operand {
|
||||||
|
Some(last_integer_operand) => return Ok(last_integer_operand),
|
||||||
|
None => return Err(Error::CffIntegerNotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_integer_operand = None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads an Offset with the given size.
|
||||||
|
fn read_offset(reader: &mut &[u8], size: u8) -> Result<u32, Error> {
|
||||||
|
match size {
|
||||||
|
1 => Ok(try!(reader.read_u8().map_err(Error::eof)) as u32),
|
||||||
|
2 => Ok(try!(reader.read_u16::<BigEndian>().map_err(Error::eof)) as u32),
|
||||||
|
3 => {
|
||||||
|
let hi = try!(reader.read_u8().map_err(Error::eof)) as u32;
|
||||||
|
let lo = try!(reader.read_u16::<BigEndian>().map_err(Error::eof)) as u32;
|
||||||
|
Ok((hi << 16) | lo)
|
||||||
|
}
|
||||||
|
4 => Ok(try!(reader.read_u32::<BigEndian>().map_err(Error::eof))),
|
||||||
|
_ => Err(Error::CffBadOffset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The CFF evaluation stack used during CharString reading.
|
||||||
|
struct EvaluationStack {
|
||||||
|
array: [i32; 48],
|
||||||
|
size: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EvaluationStack {
|
||||||
|
fn new() -> EvaluationStack {
|
||||||
|
EvaluationStack {
|
||||||
|
array: [0; 48],
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, value: i32) -> Result<(), Error> {
|
||||||
|
if (self.size as usize) < self.array.len() {
|
||||||
|
self.array[self.size as usize] = value;
|
||||||
|
self.size += 1;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::CffStackOverflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.size = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_path_if_necessary<F>(pos: &mut Point2D<i16>,
|
||||||
|
start: &Point2D<i16>,
|
||||||
|
index_in_contour: u16,
|
||||||
|
mut callback: F)
|
||||||
|
where F: FnMut(&Point) {
|
||||||
|
if index_in_contour == 0 {
|
||||||
|
// No path to close.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(&Point {
|
||||||
|
position: *start,
|
||||||
|
index_in_contour: index_in_contour,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_hvcurveto_h<F>(chunk: &[i32],
|
||||||
|
pos: &mut Point2D<i16>,
|
||||||
|
dxf: i32,
|
||||||
|
index_in_contour: &mut u16,
|
||||||
|
mut callback: F)
|
||||||
|
where F: FnMut(&Point) {
|
||||||
|
pos.x += chunk[0] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: *pos,
|
||||||
|
index_in_contour: *index_in_contour + 0,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.x += chunk[1] as i16;
|
||||||
|
pos.y += chunk[2] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: *pos,
|
||||||
|
index_in_contour: *index_in_contour + 1,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.x += dxf as i16;
|
||||||
|
pos.y += chunk[3] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: *pos,
|
||||||
|
index_in_contour: *index_in_contour + 2,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
*index_in_contour += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_hvcurveto_v<F>(chunk: &[i32],
|
||||||
|
pos: &mut Point2D<i16>,
|
||||||
|
dyf: i32,
|
||||||
|
index_in_contour: &mut u16,
|
||||||
|
mut callback: F)
|
||||||
|
where F: FnMut(&Point) {
|
||||||
|
pos.y += chunk[0] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: *pos,
|
||||||
|
index_in_contour: *index_in_contour + 0,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.x += chunk[1] as i16;
|
||||||
|
pos.y += chunk[2] as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: *pos,
|
||||||
|
index_in_contour: *index_in_contour + 1,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
pos.x += chunk[3] as i16;
|
||||||
|
pos.y += dyf as i16;
|
||||||
|
callback(&Point {
|
||||||
|
position: *pos,
|
||||||
|
index_in_contour: *index_in_contour + 2,
|
||||||
|
on_curve: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
*index_in_contour += 3
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt};
|
||||||
use charmap::{CodepointRange, GlyphMapping};
|
use charmap::{CodepointRange, GlyphMapping};
|
||||||
|
use otf::cff::CffTable;
|
||||||
use otf::cmap::CmapTable;
|
use otf::cmap::CmapTable;
|
||||||
use otf::glyf::{GlyfTable, Point};
|
use otf::glyf::{GlyfTable, Point};
|
||||||
use otf::head::HeadTable;
|
use otf::head::HeadTable;
|
||||||
|
@ -25,6 +26,7 @@ use std::mem;
|
||||||
use std::u16;
|
use std::u16;
|
||||||
use util::Jump;
|
use util::Jump;
|
||||||
|
|
||||||
|
mod cff;
|
||||||
mod cmap;
|
mod cmap;
|
||||||
mod glyf;
|
mod glyf;
|
||||||
mod head;
|
mod head;
|
||||||
|
@ -34,6 +36,10 @@ mod kern;
|
||||||
mod loca;
|
mod loca;
|
||||||
mod os_2;
|
mod os_2;
|
||||||
|
|
||||||
|
const CFF: u32 = ((b'C' as u32) << 24) |
|
||||||
|
((b'F' as u32) << 16) |
|
||||||
|
((b'F' as u32) << 8) |
|
||||||
|
(b' ' as u32);
|
||||||
const CMAP: u32 = ((b'c' as u32) << 24) |
|
const CMAP: u32 = ((b'c' as u32) << 24) |
|
||||||
((b'm' as u32) << 16) |
|
((b'm' as u32) << 16) |
|
||||||
((b'a' as u32) << 8) |
|
((b'a' as u32) << 8) |
|
||||||
|
@ -80,9 +86,10 @@ const SFNT: u32 = ((b's' as u32) << 24) |
|
||||||
((b'n' as u32) << 8) |
|
((b'n' as u32) << 8) |
|
||||||
(b't' as u32);
|
(b't' as u32);
|
||||||
|
|
||||||
static SFNT_VERSIONS: [u32; 2] = [
|
static SFNT_VERSIONS: [u32; 3] = [
|
||||||
0x10000,
|
0x10000,
|
||||||
((b't' as u32) << 24) | ((b'r' as u32) << 16) | ((b'u' as u32) << 8) | (b'e' as u32),
|
((b't' as u32) << 24) | ((b'r' as u32) << 16) | ((b'u' as u32) << 8) | (b'e' as u32),
|
||||||
|
OTTO,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// A handle to a font backed by a byte buffer containing the contents of the file (`.ttf`,
|
/// A handle to a font backed by a byte buffer containing the contents of the file (`.ttf`,
|
||||||
|
@ -98,6 +105,7 @@ pub struct Font<'a> {
|
||||||
hmtx: HmtxTable<'a>,
|
hmtx: HmtxTable<'a>,
|
||||||
os_2: Os2Table,
|
os_2: Os2Table,
|
||||||
|
|
||||||
|
cff: Option<CffTable<'a>>,
|
||||||
glyf: Option<GlyfTable<'a>>,
|
glyf: Option<GlyfTable<'a>>,
|
||||||
loca: Option<LocaTable<'a>>,
|
loca: Option<LocaTable<'a>>,
|
||||||
kern: Option<KernTable<'a>>,
|
kern: Option<KernTable<'a>>,
|
||||||
|
@ -153,10 +161,6 @@ impl<'a> Font<'a> {
|
||||||
}
|
}
|
||||||
magic_number if SFNT_VERSIONS.contains(&magic_number) => Font::from_otf(bytes, 0),
|
magic_number if SFNT_VERSIONS.contains(&magic_number) => Font::from_otf(bytes, 0),
|
||||||
0x0100 => Font::from_dfont_index(bytes, index),
|
0x0100 => Font::from_dfont_index(bytes, index),
|
||||||
OTTO => {
|
|
||||||
// TODO(pcwalton): Support CFF outlines.
|
|
||||||
Err(Error::UnsupportedCffOutlines)
|
|
||||||
}
|
|
||||||
_ => Err(Error::UnknownFormat),
|
_ => Err(Error::UnknownFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,20 +172,18 @@ impl<'a> Font<'a> {
|
||||||
let mut magic_number = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
|
let mut magic_number = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
|
||||||
|
|
||||||
// Check version.
|
// Check version.
|
||||||
if magic_number == OTTO {
|
if !SFNT_VERSIONS.contains(&magic_number) {
|
||||||
// TODO(pcwalton): Support CFF outlines.
|
|
||||||
return Err(Error::UnsupportedCffOutlines)
|
|
||||||
} else if !SFNT_VERSIONS.contains(&magic_number) {
|
|
||||||
return Err(Error::UnknownFormat)
|
return Err(Error::UnknownFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
|
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(Error::eof));
|
||||||
try!(reader.jump(mem::size_of::<u16>() * 3).map_err(Error::eof));
|
try!(reader.jump(mem::size_of::<u16>() * 3).map_err(Error::eof));
|
||||||
|
|
||||||
let (mut cmap_table, mut head_table) = (None, None);
|
let (mut cff_table, mut cmap_table) = (None, None);
|
||||||
|
let (mut glyf_table, mut head_table) = (None, None);
|
||||||
let (mut hhea_table, mut hmtx_table) = (None, None);
|
let (mut hhea_table, mut hmtx_table) = (None, None);
|
||||||
let (mut glyf_table, mut kern_table) = (None, None);
|
let (mut kern_table, mut loca_table) = (None, None);
|
||||||
let (mut loca_table, mut os_2_table) = (None, None);
|
let mut os_2_table = None;
|
||||||
|
|
||||||
for _ in 0..num_tables {
|
for _ in 0..num_tables {
|
||||||
let table_id = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
|
let table_id = try!(reader.read_u32::<BigEndian>().map_err(Error::eof));
|
||||||
|
@ -193,6 +195,7 @@ impl<'a> Font<'a> {
|
||||||
let length = try!(reader.read_u32::<BigEndian>().map_err(Error::eof)) as usize;
|
let length = try!(reader.read_u32::<BigEndian>().map_err(Error::eof)) as usize;
|
||||||
|
|
||||||
let mut slot = match table_id {
|
let mut slot = match table_id {
|
||||||
|
CFF => &mut cff_table,
|
||||||
CMAP => &mut cmap_table,
|
CMAP => &mut cmap_table,
|
||||||
HEAD => &mut head_table,
|
HEAD => &mut head_table,
|
||||||
HHEA => &mut hhea_table,
|
HHEA => &mut hhea_table,
|
||||||
|
@ -214,6 +217,11 @@ impl<'a> Font<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cff_table = match cff_table {
|
||||||
|
None => None,
|
||||||
|
Some(cff_table) => Some(try!(CffTable::new(cff_table))),
|
||||||
|
};
|
||||||
|
|
||||||
let loca_table = match loca_table {
|
let loca_table = match loca_table {
|
||||||
None => None,
|
None => None,
|
||||||
Some(loca_table) => Some(try!(LocaTable::new(loca_table))),
|
Some(loca_table) => Some(try!(LocaTable::new(loca_table))),
|
||||||
|
@ -228,6 +236,7 @@ impl<'a> Font<'a> {
|
||||||
hmtx: HmtxTable::new(try!(hmtx_table.ok_or(Error::RequiredTableMissing))),
|
hmtx: HmtxTable::new(try!(hmtx_table.ok_or(Error::RequiredTableMissing))),
|
||||||
os_2: try!(Os2Table::new(try!(os_2_table.ok_or(Error::RequiredTableMissing)))),
|
os_2: try!(Os2Table::new(try!(os_2_table.ok_or(Error::RequiredTableMissing)))),
|
||||||
|
|
||||||
|
cff: cff_table,
|
||||||
glyf: glyf_table.map(GlyfTable::new),
|
glyf: glyf_table.map(GlyfTable::new),
|
||||||
loca: loca_table,
|
loca: loca_table,
|
||||||
kern: kern_table.and_then(|table| KernTable::new(table).ok()),
|
kern: kern_table.and_then(|table| KernTable::new(table).ok()),
|
||||||
|
@ -320,8 +329,8 @@ impl<'a> Font<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn for_each_point<F>(&self, glyph_id: u16, callback: F) -> Result<(), Error>
|
pub fn for_each_point<F>(&self, glyph_id: u16, callback: F) -> Result<(), Error>
|
||||||
where F: FnMut(&Point) {
|
where F: FnMut(&Point) {
|
||||||
match self.glyf {
|
match (self.glyf, self.cff) {
|
||||||
Some(glyf) => {
|
(Some(glyf), None) => {
|
||||||
let loca = match self.loca {
|
let loca = match self.loca {
|
||||||
Some(ref loca) => loca,
|
Some(ref loca) => loca,
|
||||||
None => return Err(Error::RequiredTableMissing),
|
None => return Err(Error::RequiredTableMissing),
|
||||||
|
@ -329,15 +338,17 @@ impl<'a> Font<'a> {
|
||||||
|
|
||||||
glyf.for_each_point(&self.head, loca, glyph_id, callback)
|
glyf.for_each_point(&self.head, loca, glyph_id, callback)
|
||||||
}
|
}
|
||||||
None => Ok(()),
|
(None, Some(cff)) => cff.for_each_point(glyph_id, callback),
|
||||||
|
(Some(_), Some(_)) => Err(Error::Failed),
|
||||||
|
(None, None) => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the boundaries of the given glyph in font units.
|
/// Returns the boundaries of the given glyph in font units.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn glyph_bounds(&self, glyph_id: u16) -> Result<GlyphBounds, Error> {
|
pub fn glyph_bounds(&self, glyph_id: u16) -> Result<GlyphBounds, Error> {
|
||||||
match self.glyf {
|
match (self.glyf, self.cff) {
|
||||||
Some(glyf) => {
|
(Some(glyf), None) => {
|
||||||
let loca = match self.loca {
|
let loca = match self.loca {
|
||||||
Some(ref loca) => loca,
|
Some(ref loca) => loca,
|
||||||
None => return Err(Error::RequiredTableMissing),
|
None => return Err(Error::RequiredTableMissing),
|
||||||
|
@ -345,7 +356,9 @@ impl<'a> Font<'a> {
|
||||||
|
|
||||||
glyf.glyph_bounds(&self.head, loca, glyph_id)
|
glyf.glyph_bounds(&self.head, loca, glyph_id)
|
||||||
}
|
}
|
||||||
None => Err(Error::RequiredTableMissing),
|
(None, Some(cff)) => cff.glyph_bounds(glyph_id),
|
||||||
|
(Some(_), Some(_)) => Err(Error::Failed),
|
||||||
|
(None, None) => Err(Error::RequiredTableMissing),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,10 +447,10 @@ pub enum Error {
|
||||||
UnsupportedVersion,
|
UnsupportedVersion,
|
||||||
/// The file was of a format we don't support.
|
/// The file was of a format we don't support.
|
||||||
UnknownFormat,
|
UnknownFormat,
|
||||||
/// The font has CFF outlines, which we don't yet support.
|
|
||||||
UnsupportedCffOutlines,
|
|
||||||
/// The font had a glyph format we don't support.
|
/// The font had a glyph format we don't support.
|
||||||
UnsupportedGlyphFormat,
|
UnsupportedGlyphFormat,
|
||||||
|
/// We don't support the declared version of the font's CFF outlines.
|
||||||
|
UnsupportedCffVersion,
|
||||||
/// We don't support the declared version of the font's character map.
|
/// We don't support the declared version of the font's character map.
|
||||||
UnsupportedCmapVersion,
|
UnsupportedCmapVersion,
|
||||||
/// The font character map has an unsupported platform/encoding ID.
|
/// The font character map has an unsupported platform/encoding ID.
|
||||||
|
@ -452,10 +465,16 @@ pub enum Error {
|
||||||
UnsupportedOs2Version,
|
UnsupportedOs2Version,
|
||||||
/// A required table is missing.
|
/// A required table is missing.
|
||||||
RequiredTableMissing,
|
RequiredTableMissing,
|
||||||
/// The glyph is a composite glyph.
|
/// An integer in a CFF DICT was not found.
|
||||||
///
|
CffIntegerNotFound,
|
||||||
/// TODO(pcwalton): Support these.
|
/// The CFF Top DICT was not found.
|
||||||
CompositeGlyph,
|
CffTopDictNotFound,
|
||||||
|
/// A CFF `Offset` value was formatted incorrectly.
|
||||||
|
CffBadOffset,
|
||||||
|
/// The CFF evaluation stack overflowed.
|
||||||
|
CffStackOverflow,
|
||||||
|
/// An unimplemented CFF CharString operator was encountered.
|
||||||
|
CffUnimplementedOperator,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|
|
@ -279,7 +279,7 @@ impl GlyphPixelBounds {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The boundaries of a glyph in font units.
|
/// The boundaries of a glyph in font units.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Default, Debug)]
|
||||||
pub struct GlyphBounds {
|
pub struct GlyphBounds {
|
||||||
pub left: i32,
|
pub left: i32,
|
||||||
pub bottom: i32,
|
pub bottom: i32,
|
||||||
|
|
Loading…
Reference in New Issue