Implement basic support for WOFF 1.0

This commit is contained in:
Patrick Walton 2017-02-22 14:19:27 -08:00
parent 9490fa2d3b
commit 5e9b8c9423
13 changed files with 201 additions and 46 deletions

View File

@ -7,6 +7,7 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
bitflags = "0.7" bitflags = "0.7"
byteorder = "1" byteorder = "1"
euclid = "0.10" euclid = "0.10"
flate2 = "0.2"
gl = "0.6" gl = "0.6"
memmap = "0.5" memmap = "0.5"
time = "0.1" time = "0.1"

View File

@ -66,6 +66,7 @@ fn main() {
let shelf_height = point_size * 2; let shelf_height = point_size * 2;
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();
let mut buffer = vec![];
let mut results = vec![]; let mut results = vec![];
let start = time::precise_time_ns(); let start = time::precise_time_ns();
@ -75,7 +76,7 @@ fn main() {
loop { loop {
glyph_count = 0; glyph_count = 0;
unsafe { unsafe {
let font = Font::new(file.as_slice()).unwrap(); let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)]; let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges) let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges)

View File

@ -13,8 +13,9 @@ 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();
let mut buffer = vec![];
unsafe { unsafe {
let font = Font::new(file.as_slice()).unwrap(); let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)]; let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap(); let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap();
for (glyph_index, (_, glyph_id)) in glyph_mapping.iter().enumerate() { for (glyph_index, (_, glyph_id)) in glyph_mapping.iter().enumerate() {

View File

@ -72,6 +72,7 @@ fn main() {
let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap(); let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap();
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap(); let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
let mut buffer = vec![];
let point_size = match matches.value_of("POINT-SIZE") { let point_size = match matches.value_of("POINT-SIZE") {
Some(point_size) => point_size.parse().unwrap(), Some(point_size) => point_size.parse().unwrap(),
@ -85,7 +86,7 @@ fn main() {
let (outlines, atlas); let (outlines, atlas);
unsafe { unsafe {
let font = Font::from_collection_index(file.as_slice(), font_index).unwrap(); let font = Font::from_collection_index(file.as_slice(), font_index, &mut buffer).unwrap();
let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)]; let codepoint_ranges = [CodepointRange::new(' ' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap(); let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap();

View File

@ -102,8 +102,9 @@ fn main() {
}; };
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap(); let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
let mut buffer = vec![];
let font = unsafe { let font = unsafe {
Font::from_collection_index(file.as_slice(), font_index).unwrap() Font::from_collection_index(file.as_slice(), font_index, &mut buffer).unwrap()
}; };
let units_per_em = font.units_per_em() as f32; let units_per_em = font.units_per_em() as f32;

View File

@ -18,6 +18,8 @@ use font::Font;
use std::mem; use std::mem;
use util::Jump; use util::Jump;
pub const MAGIC_NUMBER: u32 = 0x0100;
const SFNT: u32 = ((b's' as u32) << 24) | const SFNT: u32 = ((b's' as u32) << 24) |
((b'f' as u32) << 16) | ((b'f' as u32) << 16) |
((b'n' as u32) << 8) | ((b'n' as u32) << 8) |

View File

@ -13,4 +13,5 @@
pub mod dfont; pub mod dfont;
pub mod otf; pub mod otf;
pub mod ttc; pub mod ttc;
pub mod woff;

View File

@ -32,6 +32,31 @@ const OTTO: u32 = ((b'O' as u32) << 24) |
((b'T' as u32) << 8) | ((b'T' as u32) << 8) |
(b'O' as u32); (b'O' as u32);
pub const KNOWN_TABLE_COUNT: usize = 9;
pub static KNOWN_TABLES: [u32; KNOWN_TABLE_COUNT] = [
cff::TAG,
os_2::TAG,
cmap::TAG,
glyf::TAG,
head::TAG,
hhea::TAG,
hmtx::TAG,
kern::TAG,
loca::TAG,
];
// This must agree with the above.
const TABLE_INDEX_CFF: usize = 0;
const TABLE_INDEX_OS_2: usize = 1;
const TABLE_INDEX_CMAP: usize = 2;
const TABLE_INDEX_GLYF: usize = 3;
const TABLE_INDEX_HEAD: usize = 4;
const TABLE_INDEX_HHEA: usize = 5;
const TABLE_INDEX_HMTX: usize = 6;
const TABLE_INDEX_KERN: usize = 7;
const TABLE_INDEX_LOCA: usize = 8;
pub static SFNT_VERSIONS: [u32; 3] = [ pub 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),
@ -65,32 +90,18 @@ impl<'a> Font<'a> {
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof)); let num_tables = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
try!(reader.jump(mem::size_of::<u16>() * 3).map_err(FontError::eof)); try!(reader.jump(mem::size_of::<u16>() * 3).map_err(FontError::eof));
let (mut cff_table, mut cmap_table) = (None, None); let mut tables = [None; KNOWN_TABLE_COUNT];
let (mut glyf_table, mut head_table) = (None, None);
let (mut hhea_table, mut hmtx_table) = (None, None);
let (mut kern_table, mut loca_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(FontError::eof)); let table_id = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let _checksum = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
// Skip over the checksum.
try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)) as usize; let offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)) as usize;
let length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)) as usize; let length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)) as usize;
let mut slot = match table_id { // Find the table ID in our list of known IDs, which must be sorted.
cff::TAG => &mut cff_table, debug_assert!(KNOWN_TABLES.windows(2).all(|w| w[0] < w[1]));
cmap::TAG => &mut cmap_table, let slot = match KNOWN_TABLES.binary_search(&table_id) {
glyf::TAG => &mut glyf_table, Err(_) => continue,
head::TAG => &mut head_table, Ok(table_index) => &mut tables[table_index],
hhea::TAG => &mut hhea_table,
hmtx::TAG => &mut hmtx_table,
kern::TAG => &mut kern_table,
loca::TAG => &mut loca_table,
os_2::TAG => &mut os_2_table,
_ => continue,
}; };
// Make sure there isn't more than one copy of the table. // Make sure there isn't more than one copy of the table.
@ -103,27 +114,37 @@ impl<'a> Font<'a> {
}) })
} }
let cff_table = match cff_table { Font::from_table_list(bytes, &tables)
}
#[doc(hidden)]
pub fn from_table_list<'b>(bytes: &'b [u8],
tables: &[Option<FontTable<'b>>; KNOWN_TABLE_COUNT])
-> Result<Font<'b>, FontError> {
let cff_table = match tables[TABLE_INDEX_CFF] {
None => None, None => None,
Some(cff_table) => Some(try!(CffTable::new(cff_table))), Some(cff_table) => Some(try!(CffTable::new(cff_table))),
}; };
let loca_table = match loca_table { let loca_table = match tables[TABLE_INDEX_LOCA] {
None => None, None => None,
Some(loca_table) => Some(try!(LocaTable::new(loca_table))), Some(loca_table) => Some(try!(LocaTable::new(loca_table))),
}; };
// For brevity below…
let missing = FontError::RequiredTableMissing;
let tables = FontTables { let tables = FontTables {
cmap: CmapTable::new(try!(cmap_table.ok_or(FontError::RequiredTableMissing))), cmap: CmapTable::new(try!(tables[TABLE_INDEX_CMAP].ok_or(missing))),
head: try!(HeadTable::new(try!(head_table.ok_or(FontError::RequiredTableMissing)))), head: try!(HeadTable::new(try!(tables[TABLE_INDEX_HEAD].ok_or(missing)))),
hhea: try!(HheaTable::new(try!(hhea_table.ok_or(FontError::RequiredTableMissing)))), hhea: try!(HheaTable::new(try!(tables[TABLE_INDEX_HHEA].ok_or(missing)))),
hmtx: HmtxTable::new(try!(hmtx_table.ok_or(FontError::RequiredTableMissing))), hmtx: HmtxTable::new(try!(tables[TABLE_INDEX_HMTX].ok_or(missing))),
os_2: try!(Os2Table::new(try!(os_2_table.ok_or(FontError::RequiredTableMissing)))), os_2: try!(Os2Table::new(try!(tables[TABLE_INDEX_OS_2].ok_or(missing)))),
cff: cff_table, cff: cff_table,
glyf: glyf_table.map(GlyfTable::new), glyf: tables[TABLE_INDEX_GLYF].map(GlyfTable::new),
loca: loca_table, loca: loca_table,
kern: kern_table.and_then(|table| KernTable::new(table).ok()), kern: tables[TABLE_INDEX_KERN].and_then(|table| KernTable::new(table).ok()),
}; };
Ok(Font::from_tables(bytes, tables)) Ok(Font::from_tables(bytes, tables))

View File

@ -18,10 +18,10 @@ use font::Font;
use std::mem; use std::mem;
use util::Jump; use util::Jump;
pub const TTCF: u32 = ((b't' as u32) << 24) | pub const MAGIC_NUMBER: u32 = ((b't' as u32) << 24) |
((b't' as u32) << 16) | ((b't' as u32) << 16) |
((b'c' as u32) << 8) | ((b'c' as u32) << 8) |
(b'f' as u32); (b'f' as u32);
impl<'a> Font<'a> { impl<'a> Font<'a> {
/// Creates a new font from a single font within a byte buffer containing the contents of a /// Creates a new font from a single font within a byte buffer containing the contents of a
@ -29,7 +29,7 @@ impl<'a> Font<'a> {
pub fn from_ttc_index<'b>(bytes: &'b [u8], index: u32) -> Result<Font<'b>, FontError> { pub fn from_ttc_index<'b>(bytes: &'b [u8], index: u32) -> Result<Font<'b>, FontError> {
let mut reader = bytes; let mut reader = bytes;
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)); let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if magic_number != TTCF { if magic_number != MAGIC_NUMBER {
return Err(FontError::UnknownFormat) return Err(FontError::UnknownFormat)
} }

114
src/containers/woff.rs Normal file
View File

@ -0,0 +1,114 @@
// 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.
//! Web Open Font Format 1.0 (`.woff`) files.
//!
//! See the specification: https://www.w3.org/TR/WOFF/
//!
//! TODO(pcwalton): WOFF 2.0.
use byteorder::{BigEndian, ReadBytesExt};
use containers::otf::{KNOWN_TABLES, KNOWN_TABLE_COUNT, SFNT_VERSIONS};
use error::FontError;
use flate2::FlateReadExt;
use font::{Font, FontTable};
use std::io::Read;
use std::iter;
use std::mem;
use util::Jump;
pub const MAGIC_NUMBER: u32 = ((b'w' as u32) << 24) |
((b'O' as u32) << 16) |
((b'F' as u32) << 8) |
(b'F' as u32);
impl<'a> Font<'a> {
/// Creates a new font from a buffer containing data in the WOFF format.
///
/// The given buffer will be used to decompress data.
///
/// Decompresses eagerly.
pub fn from_woff<'b>(bytes: &'b [u8], buffer: &'b mut Vec<u8>) -> Result<Font<'b>, FontError> {
let mut reader = bytes;
// Check magic number.
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if magic_number != MAGIC_NUMBER {
return Err(FontError::UnknownFormat)
}
// Check the flavor.
let flavor = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
if !SFNT_VERSIONS.contains(&flavor) {
return Err(FontError::UnknownFormat)
}
// Get the number of tables.
let _length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let num_tables = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
let _reserved = try!(reader.read_u16::<BigEndian>().map_err(FontError::eof));
// Allocate size for uncompressed tables.
let total_sfnt_size = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
try!(reader.jump(mem::size_of::<u32>() * 6).map_err(FontError::eof));
let buffer_start = buffer.len();
buffer.extend(iter::repeat(0).take(total_sfnt_size as usize));
let mut buffer = &mut buffer[buffer_start..];
// Decompress and load tables as necessary.
let mut tables = [None; KNOWN_TABLE_COUNT];
for _ in 0..num_tables {
let tag = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let offset = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let comp_length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let orig_length = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
let _orig_checksum = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
// Find the table ID in our list of known IDs, which must be sorted.
debug_assert!(KNOWN_TABLES.windows(2).all(|w| w[0] < w[1]));
let slot = match KNOWN_TABLES.binary_search(&tag) {
Err(_) => continue,
Ok(table_index) => &mut tables[table_index],
};
// Make sure there isn't more than one copy of the table.
if slot.is_some() {
return Err(FontError::Failed)
}
// Allocate space in the buffer.
let comp_end = offset as usize + comp_length as usize;
let mut temp = buffer; // borrow check black magic
let (mut dest, mut rest) = temp.split_at_mut(orig_length as usize);
buffer = rest;
// Decompress or copy as applicable.
//
// FIXME(pcwalton): Errors here may be zlib errors, not EOFs.
if comp_length != orig_length {
let mut table_reader = bytes;
try!(table_reader.jump(offset as usize).map_err(FontError::eof));
let mut table_reader = table_reader.zlib_decode();
try!(table_reader.read_exact(dest).map_err(FontError::eof));
} else if comp_end <= bytes.len() {
dest.clone_from_slice(&bytes[offset as usize..comp_end])
} else {
return Err(FontError::UnexpectedEof)
}
*slot = Some(FontTable {
bytes: dest,
})
}
Font::from_table_list(bytes, &tables)
}
}

View File

@ -12,8 +12,10 @@
use byteorder::{BigEndian, ReadBytesExt}; use byteorder::{BigEndian, ReadBytesExt};
use charmap::{CodepointRange, GlyphMapping}; use charmap::{CodepointRange, GlyphMapping};
use containers::dfont;
use containers::otf::{FontTables, SFNT_VERSIONS}; use containers::otf::{FontTables, SFNT_VERSIONS};
use containers::ttc::TTCF; use containers::ttc;
use containers::woff;
use error::FontError; use error::FontError;
use euclid::Point2D; use euclid::Point2D;
use outline::GlyphBounds; use outline::GlyphBounds;
@ -49,9 +51,12 @@ impl<'a> Font<'a> {
/// If this is a `.ttc` or `.dfont` collection, this returns the first font within it. If you /// If this is a `.ttc` or `.dfont` collection, this returns the first font within it. If you
/// want to read another one, use the `Font::from_collection_index` API. /// want to read another one, use the `Font::from_collection_index` API.
/// ///
/// The supplied `buffer` is an arbitrary vector that may or may not be used as a temporary
/// storage space. Typically you will want to just pass an empty vector here.
///
/// Returns the font on success or an error on failure. /// Returns the font on success or an error on failure.
pub fn new<'b>(bytes: &'b [u8]) -> Result<Font<'b>, FontError> { pub fn new<'b>(bytes: &'b [u8], buffer: &'b mut Vec<u8>) -> Result<Font<'b>, FontError> {
Font::from_collection_index(bytes, 0) Font::from_collection_index(bytes, 0, buffer)
} }
/// Creates a new font from a single font within a byte buffer containing the contents of a /// Creates a new font from a single font within a byte buffer containing the contents of a
@ -59,15 +64,20 @@ impl<'a> Font<'a> {
/// ///
/// If this is a `.ttc` or `.dfont` collection, this returns the appropriate font within it. /// If this is a `.ttc` or `.dfont` collection, this returns the appropriate font within it.
/// ///
/// The supplied `buffer` is an arbitrary vector that may or may not be used as a temporary
/// storage space. Typically you will want to just pass an empty vector here.
///
/// Returns the font on success or an error on failure. /// Returns the font on success or an error on failure.
pub fn from_collection_index<'b>(bytes: &'b [u8], index: u32) -> Result<Font<'b>, FontError> { pub fn from_collection_index<'b>(bytes: &'b [u8], index: u32, buffer: &'b mut Vec<u8>)
-> Result<Font<'b>, FontError> {
// Check the magic number. // Check the magic number.
let mut reader = bytes; let mut reader = bytes;
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof)); let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
match magic_number { match magic_number {
TTCF => Font::from_ttc_index(bytes, index), ttc::MAGIC_NUMBER => Font::from_ttc_index(bytes, index),
woff::MAGIC_NUMBER => Font::from_woff(bytes, buffer),
dfont::MAGIC_NUMBER => Font::from_dfont_index(bytes, index),
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),
_ => Err(FontError::UnknownFormat), _ => Err(FontError::UnknownFormat),
} }
} }

View File

@ -78,6 +78,7 @@ extern crate bitflags;
extern crate byteorder; extern crate byteorder;
extern crate compute_shader; extern crate compute_shader;
extern crate euclid; extern crate euclid;
extern crate flate2;
extern crate gl; extern crate gl;
#[cfg(test)] #[cfg(test)]
extern crate memmap; extern crate memmap;

View File

@ -12,8 +12,9 @@ static TEST_FONT_PATH: &'static str = "resources/tests/nimbus-sans/NimbusSanL-Re
#[bench] #[bench]
fn bench_add_glyphs(bencher: &mut Bencher) { fn bench_add_glyphs(bencher: &mut Bencher) {
let file = Mmap::open_path(TEST_FONT_PATH, Protection::Read).expect("Couldn't open test font"); let file = Mmap::open_path(TEST_FONT_PATH, Protection::Read).expect("Couldn't open test font");
let mut buffer = vec![];
unsafe { unsafe {
let font = Font::new(file.as_slice()).unwrap(); let font = Font::new(file.as_slice(), &mut buffer).unwrap();
let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)]; let codepoint_ranges = [CodepointRange::new('!' as u32, '~' as u32)];
let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges) let glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges)
.expect("Couldn't find glyph ranges"); .expect("Couldn't find glyph ranges");