Implement basic support for WOFF 1.0
This commit is contained in:
parent
9490fa2d3b
commit
5e9b8c9423
|
@ -7,6 +7,7 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
|||
bitflags = "0.7"
|
||||
byteorder = "1"
|
||||
euclid = "0.10"
|
||||
flate2 = "0.2"
|
||||
gl = "0.6"
|
||||
memmap = "0.5"
|
||||
time = "0.1"
|
||||
|
|
|
@ -66,6 +66,7 @@ fn main() {
|
|||
let shelf_height = point_size * 2;
|
||||
|
||||
let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap();
|
||||
let mut buffer = vec![];
|
||||
|
||||
let mut results = vec![];
|
||||
let start = time::precise_time_ns();
|
||||
|
@ -75,7 +76,7 @@ fn main() {
|
|||
loop {
|
||||
glyph_count = 0;
|
||||
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 glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges)
|
||||
|
|
|
@ -13,8 +13,9 @@ use std::env;
|
|||
|
||||
fn main() {
|
||||
let file = Mmap::open_path(env::args().nth(1).unwrap(), Protection::Read).unwrap();
|
||||
let mut buffer = vec![];
|
||||
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 glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap();
|
||||
for (glyph_index, (_, glyph_id)) in glyph_mapping.iter().enumerate() {
|
||||
|
|
|
@ -72,6 +72,7 @@ fn main() {
|
|||
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 mut buffer = vec![];
|
||||
|
||||
let point_size = match matches.value_of("POINT-SIZE") {
|
||||
Some(point_size) => point_size.parse().unwrap(),
|
||||
|
@ -85,7 +86,7 @@ fn main() {
|
|||
|
||||
let (outlines, atlas);
|
||||
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 glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges).unwrap();
|
||||
|
||||
|
|
|
@ -102,8 +102,9 @@ fn main() {
|
|||
};
|
||||
|
||||
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
|
||||
let mut buffer = vec![];
|
||||
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;
|
||||
|
|
|
@ -18,6 +18,8 @@ use font::Font;
|
|||
use std::mem;
|
||||
use util::Jump;
|
||||
|
||||
pub const MAGIC_NUMBER: u32 = 0x0100;
|
||||
|
||||
const SFNT: u32 = ((b's' as u32) << 24) |
|
||||
((b'f' as u32) << 16) |
|
||||
((b'n' as u32) << 8) |
|
||||
|
|
|
@ -13,4 +13,5 @@
|
|||
pub mod dfont;
|
||||
pub mod otf;
|
||||
pub mod ttc;
|
||||
pub mod woff;
|
||||
|
||||
|
|
|
@ -32,6 +32,31 @@ const OTTO: u32 = ((b'O' as u32) << 24) |
|
|||
((b'T' as u32) << 8) |
|
||||
(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] = [
|
||||
0x10000,
|
||||
((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));
|
||||
try!(reader.jump(mem::size_of::<u16>() * 3).map_err(FontError::eof));
|
||||
|
||||
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 kern_table, mut loca_table) = (None, None);
|
||||
let mut os_2_table = None;
|
||||
|
||||
let mut tables = [None; KNOWN_TABLE_COUNT];
|
||||
for _ in 0..num_tables {
|
||||
let table_id = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
|
||||
|
||||
// Skip over the checksum.
|
||||
try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
|
||||
|
||||
let _checksum = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
|
||||
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 mut slot = match table_id {
|
||||
cff::TAG => &mut cff_table,
|
||||
cmap::TAG => &mut cmap_table,
|
||||
glyf::TAG => &mut glyf_table,
|
||||
head::TAG => &mut head_table,
|
||||
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,
|
||||
// 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(&table_id) {
|
||||
Err(_) => continue,
|
||||
Ok(table_index) => &mut tables[table_index],
|
||||
};
|
||||
|
||||
// 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,
|
||||
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,
|
||||
Some(loca_table) => Some(try!(LocaTable::new(loca_table))),
|
||||
};
|
||||
|
||||
// For brevity below…
|
||||
let missing = FontError::RequiredTableMissing;
|
||||
|
||||
let tables = FontTables {
|
||||
cmap: CmapTable::new(try!(cmap_table.ok_or(FontError::RequiredTableMissing))),
|
||||
head: try!(HeadTable::new(try!(head_table.ok_or(FontError::RequiredTableMissing)))),
|
||||
hhea: try!(HheaTable::new(try!(hhea_table.ok_or(FontError::RequiredTableMissing)))),
|
||||
hmtx: HmtxTable::new(try!(hmtx_table.ok_or(FontError::RequiredTableMissing))),
|
||||
os_2: try!(Os2Table::new(try!(os_2_table.ok_or(FontError::RequiredTableMissing)))),
|
||||
cmap: CmapTable::new(try!(tables[TABLE_INDEX_CMAP].ok_or(missing))),
|
||||
head: try!(HeadTable::new(try!(tables[TABLE_INDEX_HEAD].ok_or(missing)))),
|
||||
hhea: try!(HheaTable::new(try!(tables[TABLE_INDEX_HHEA].ok_or(missing)))),
|
||||
hmtx: HmtxTable::new(try!(tables[TABLE_INDEX_HMTX].ok_or(missing))),
|
||||
os_2: try!(Os2Table::new(try!(tables[TABLE_INDEX_OS_2].ok_or(missing)))),
|
||||
|
||||
cff: cff_table,
|
||||
glyf: glyf_table.map(GlyfTable::new),
|
||||
glyf: tables[TABLE_INDEX_GLYF].map(GlyfTable::new),
|
||||
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))
|
||||
|
|
|
@ -18,10 +18,10 @@ use font::Font;
|
|||
use std::mem;
|
||||
use util::Jump;
|
||||
|
||||
pub const TTCF: u32 = ((b't' as u32) << 24) |
|
||||
((b't' as u32) << 16) |
|
||||
((b'c' as u32) << 8) |
|
||||
(b'f' as u32);
|
||||
pub const MAGIC_NUMBER: u32 = ((b't' as u32) << 24) |
|
||||
((b't' as u32) << 16) |
|
||||
((b'c' as u32) << 8) |
|
||||
(b'f' as u32);
|
||||
|
||||
impl<'a> Font<'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> {
|
||||
let mut reader = bytes;
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
22
src/font.rs
22
src/font.rs
|
@ -12,8 +12,10 @@
|
|||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use charmap::{CodepointRange, GlyphMapping};
|
||||
use containers::dfont;
|
||||
use containers::otf::{FontTables, SFNT_VERSIONS};
|
||||
use containers::ttc::TTCF;
|
||||
use containers::ttc;
|
||||
use containers::woff;
|
||||
use error::FontError;
|
||||
use euclid::Point2D;
|
||||
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
|
||||
/// 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.
|
||||
pub fn new<'b>(bytes: &'b [u8]) -> Result<Font<'b>, FontError> {
|
||||
Font::from_collection_index(bytes, 0)
|
||||
pub fn new<'b>(bytes: &'b [u8], buffer: &'b mut Vec<u8>) -> Result<Font<'b>, FontError> {
|
||||
Font::from_collection_index(bytes, 0, buffer)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
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.
|
||||
let mut reader = bytes;
|
||||
let magic_number = try!(reader.read_u32::<BigEndian>().map_err(FontError::eof));
|
||||
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),
|
||||
0x0100 => Font::from_dfont_index(bytes, index),
|
||||
_ => Err(FontError::UnknownFormat),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ extern crate bitflags;
|
|||
extern crate byteorder;
|
||||
extern crate compute_shader;
|
||||
extern crate euclid;
|
||||
extern crate flate2;
|
||||
extern crate gl;
|
||||
#[cfg(test)]
|
||||
extern crate memmap;
|
||||
|
|
|
@ -12,8 +12,9 @@ static TEST_FONT_PATH: &'static str = "resources/tests/nimbus-sans/NimbusSanL-Re
|
|||
#[bench]
|
||||
fn bench_add_glyphs(bencher: &mut Bencher) {
|
||||
let file = Mmap::open_path(TEST_FONT_PATH, Protection::Read).expect("Couldn't open test font");
|
||||
let mut buffer = vec![];
|
||||
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 glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges)
|
||||
.expect("Couldn't find glyph ranges");
|
||||
|
|
Loading…
Reference in New Issue