Expand description
A TrueType/OpenType font parsing/viewing library and a CLI tool.
§Features
Not much is implemented yet, but here’s a few features that ttf-parser and read-fonts
don’t have:
- Two-byte codepoint mappings (other crates only support Unicode).
- Supports more character encodings (such as Shift JIS, GB 18030, Big5, EUC-KR).
- Provides raw low-level access to all structures (with &, and not just getters).
- Faster and easier access to metrics in the ‘hmtx’ table.
- Actually implements the ‘cmap’ format 2 subtable (deprecated, but still used by some fonts).
Just like ttf-parser and read-fonts, this is zero-copy and no-alloc too. Also, validation of
the table data only happens when you access the table (dir.hmtx()), instead of doing bounds
checks on every single field access like ttf-parser and read-fonts do, for some reason.
The documentation will also be better, more descriptive and with examples. I want the entire crate to be comprehensible through documentation alone, without the need to look through what the language server shows you a type can do.
§Reading a font file
TableDirectory is the entry point and main hub for OpenType tables.
Use TableDirectory::new(bytes) to read a font file. Note that
the validation of specific tables happens separately and is not cached.
use ttf_view::tables::TableDirectory;
let data = std::fs::read("MyFont.ttf").unwrap();
let dir = TableDirectory::new(&data).expect("font should be well-formed");
if let Ok(cmap) = dir.cmap() {
// ...
}See the tables module for more information about tables that you can access.
§Notes on how to access tables
A table should generally be accessed through a type like Os_2<'_>. This
type automatically dereferences to Os_2Base, which contains fields
that are available in all versions of 'OS/2' table. You can access specific versions of the
table through methods like v0, v1, v4 on it, which return references to the raw data with
fields exactly as specified by OpenType. Additionally, each version of the table automatically
dereferences to the previous version, e.g. Os_2V1 to Os_2V0, Os_2V0 to Os_2Base.
let os_2: Os_2<'_> = dir.os_2().unwrap();
println!("Vendor ID: {}", os_2.ach_vend_id); // a field in Os_2Base
if let Some(v4) = os_2.v4() { // v4 is &Os_2V4
println!("Last char idx: {}", v4.us_last_char_index); // a field in Os_2Base
println!("Default char: {}", v4.us_default_char); // a field in Os_2V4
}Note about types: if a type is suffixed with Raw, then it means it doesn’t provide much info
on its own, and you should generally use a higher-level type without this suffix. For example,
TableRecordRaw only specifies an offset from the font’s root, so it
can’t provide the actual table’s data. But TableRecord is a
higher-level wrapper which does have a reference to the font, and can provide its table’s data.
let record_raw: &TableRecordRaw = dir.table_record_raw(tags::head).unwrap();
// can access fields just fine
println!("'head' length: {}", record_raw.length);
println!("'head' offset: {}", record_raw.offset);
// but can't access the actual table
let record: TableRecord<'_> = dir.table_record(tags::head).unwrap();
// auto-derefs to &TableRecordRaw for field access
println!("'head' length: {}", record_raw.length);
println!("'head' offset: {}", record_raw.offset);
// and can also access the actual table
let bytes = record.table_as_bytes();
let head = record.table_as::<Head<'_>>().unwrap();