#![deny(missing_debug_implementations)]
#![warn(rust_2018_idioms)]
pub mod cff;
pub mod outline;
pub mod parser;
pub mod tables;
pub use outline::{BBox, CubicContour, CubicOutline, CubicSegment, Point};
use crate::cff::Cff;
use crate::parser::TableDirectory;
use crate::tables::{
cmap::CmapTable, head::HeadTable, hhea::HheaTable, hmtx::HmtxTable, maxp::MaxpTable,
name::NameTable,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
UnexpectedEof,
BadMagic,
BadHeader,
BadOffset,
MissingTable(&'static str),
MissingCff,
Cff2NotImplemented,
GlyphOutOfRange(u16),
UnsupportedCmapFormat(u16),
Cff(&'static str),
BadStructure(&'static str),
CharstringStackOverflow,
CharstringStackUnderflow,
CharstringBadSubrIndex(i32),
CharstringNoLocalSubrs,
CharstringTooDeep,
CharstringTooLong,
CharstringUnsupportedOp(u16),
#[doc(hidden)]
CharstringEnd,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnexpectedEof => f.write_str("unexpected end of font data"),
Self::BadMagic => f.write_str("not a TrueType / OpenType font (bad magic)"),
Self::BadHeader => f.write_str("malformed sfnt header"),
Self::BadOffset => f.write_str("table offset out of range"),
Self::MissingTable(t) => write!(f, "required table missing: {t}"),
Self::MissingCff => f.write_str("font has no CFF/CFF2 table"),
Self::Cff2NotImplemented => f.write_str("CFF2 (variable) not implemented in round 1"),
Self::GlyphOutOfRange(g) => write!(f, "glyph index {g} out of range"),
Self::UnsupportedCmapFormat(fmt) => {
write!(f, "cmap format {fmt} not implemented in round 1")
}
Self::Cff(s) => write!(f, "CFF: {s}"),
Self::BadStructure(s) => write!(f, "malformed structure: {s}"),
Self::CharstringStackOverflow => {
f.write_str("Type 2 charstring: operand stack overflow")
}
Self::CharstringStackUnderflow => {
f.write_str("Type 2 charstring: operand stack underflow")
}
Self::CharstringBadSubrIndex(i) => {
write!(f, "Type 2 charstring: subr index {i} out of range")
}
Self::CharstringNoLocalSubrs => {
f.write_str("Type 2 charstring: callsubr but no local subrs INDEX")
}
Self::CharstringTooDeep => {
f.write_str("Type 2 charstring: subroutine recursion too deep")
}
Self::CharstringTooLong => f.write_str("Type 2 charstring: too many bytes processed"),
Self::CharstringUnsupportedOp(op) => {
write!(f, "Type 2 charstring: unsupported operator {op:#06x}")
}
Self::CharstringEnd => f.write_str("Type 2 charstring: end (internal)"),
}
}
}
impl std::error::Error for Error {}
#[derive(Debug)]
pub struct Font<'a> {
bytes: &'a [u8],
head: HeadTable,
hhea: HheaTable,
maxp: MaxpTable,
cmap: CmapTable<'a>,
name: NameTable<'a>,
hmtx: HmtxTable<'a>,
cff: Cff<'a>,
}
impl<'a> Font<'a> {
pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
let dir = TableDirectory::parse(bytes)?;
let cff_tag = dir.cff_tag.ok_or(Error::MissingCff)?;
if cff_tag == *b"CFF2" {
return Err(Error::Cff2NotImplemented);
}
let head = HeadTable::parse(dir.required(b"head", bytes)?)?;
let hhea = HheaTable::parse(dir.required(b"hhea", bytes)?)?;
let maxp = MaxpTable::parse(dir.required(b"maxp", bytes)?)?;
let cmap = CmapTable::parse(dir.required(b"cmap", bytes)?)?;
let name = NameTable::parse(dir.required(b"name", bytes)?)?;
let hmtx = HmtxTable::parse(
dir.required(b"hmtx", bytes)?,
hhea.num_long_hor_metrics,
maxp.num_glyphs,
)?;
let cff_bytes = dir.required(b"CFF ", bytes)?;
let cff = Cff::parse(cff_bytes)?;
Ok(Self {
bytes,
head,
hhea,
maxp,
cmap,
name,
hmtx,
cff,
})
}
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
pub fn family_name(&self) -> Option<&str> {
self.name.find(1)
}
pub fn full_name(&self) -> Option<&str> {
self.name.find(4)
}
pub fn units_per_em(&self) -> u16 {
self.head.units_per_em
}
pub fn glyph_count(&self) -> u16 {
self.maxp.num_glyphs
}
pub fn ascent(&self) -> i16 {
self.hhea.ascent
}
pub fn descent(&self) -> i16 {
self.hhea.descent
}
pub fn line_gap(&self) -> i16 {
self.hhea.line_gap
}
pub fn ps_name(&self) -> Option<&str> {
std::str::from_utf8(self.cff.ps_name()).ok()
}
pub fn glyph_index(&self, codepoint: char) -> Option<u16> {
self.cmap.lookup(codepoint as u32)
}
pub fn glyph_outline(&self, glyph_id: u16) -> Result<CubicOutline, Error> {
if glyph_id >= self.maxp.num_glyphs {
return Err(Error::GlyphOutOfRange(glyph_id));
}
self.cff.glyph_outline(glyph_id)
}
pub fn glyph_advance(&self, glyph_id: u16) -> i16 {
self.hmtx.advance(glyph_id) as i16
}
pub fn glyph_lsb(&self, glyph_id: u16) -> i16 {
self.hmtx.lsb(glyph_id)
}
pub fn glyph_name(&self, glyph_id: u16) -> Option<&str> {
let sid = self.cff.charset().sid_of(glyph_id)?;
self.cff.strings().get(sid)
}
pub fn cff(&self) -> &Cff<'a> {
&self.cff
}
}