use core::mem::size_of;
use crate::tables::cmap::CmapTable;
use crate::tables::glyf::Glyph;
use crate::tables::head::HeadTable;
use crate::tables::hhea::HheaTable;
use crate::tables::hmtx::HmtxTable;
use crate::tables::loca::LocaTable;
use crate::tables::maxp::MaxpTable;
use crate::Map;
use crate::Vec;
#[derive(Copy, Clone, Debug)]
pub(crate) struct OffsetTable {
pub(crate) _scaler_type: u32,
pub(crate) num_tables: u16,
pub(crate) _search_range: u16,
pub(crate) _entry_selector: u16,
pub(crate) _range_shift: u16,
}
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub(crate) struct TableRecord {
pub(crate) table_tag: [u8; 4],
pub(crate) check_sum: u32,
pub(crate) offset: u32,
pub(crate) length: u32,
}
impl TableRecord {
pub(crate) fn new() -> TableRecord {
TableRecord {
table_tag: [0; 4],
check_sum: 0,
offset: 0,
length: 0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum FontError {
InvalidFile,
TableNotFound(&'static str),
UnexpectedEndOfFile,
}
impl core::fmt::Display for FontError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
FontError::InvalidFile => write!(f, "Invalid font file"),
FontError::TableNotFound(t) => write!(f, "Table not found: {}", t),
FontError::UnexpectedEndOfFile => write!(f, "Unexpected end of file"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FontError {}
pub struct TrueTypeFont {
pub(crate) offset_table: OffsetTable,
pub(crate) tables: Vec<TableRecord>,
pub(crate) cmap: CmapTable,
pub(crate) head: HeadTable,
pub(crate) loca: LocaTable,
pub(crate) maxp: MaxpTable,
pub(crate) glyf: TableRecord,
pub(crate) hhea: HheaTable,
pub(crate) hmtx: HmtxTable,
pub(crate) glyph_data_table: Vec<Option<Glyph>>,
pub(crate) glyph_id_table: Map<char, u32>,
pub kern_table: Map<(u32, u32), i16>,
pub cache: crate::cache::Cache,
pub dpi: f32,
pub(crate) rasterizer: crate::rasterizer::dda::Rasterizer,
pub(crate) lines_scratch: crate::geometry::lines::GlyphLines,
pub(crate) segments_scratch: Vec<(f32, f32, f32, f32)>,
}
impl OffsetTable {
pub(crate) fn new() -> OffsetTable {
OffsetTable {
_scaler_type: 0,
num_tables: 0,
_search_range: 0,
_entry_selector: 0,
_range_shift: 0,
}
}
}
impl TrueTypeFont {
pub(crate) fn new() -> Self {
TrueTypeFont {
offset_table: OffsetTable::new(),
tables: Vec::new(),
cmap: CmapTable::new(),
head: HeadTable::new(),
loca: LocaTable::Short(Vec::new()),
maxp: MaxpTable::new(),
glyf: TableRecord::new(),
hhea: HheaTable::new(),
hmtx: HmtxTable::new(),
glyph_data_table: Vec::new(),
glyph_id_table: Map::new(),
kern_table: Map::new(),
cache: crate::cache::Cache::new(),
dpi: 72.0,
rasterizer: crate::rasterizer::dda::Rasterizer::with_capacity(0, 0),
lines_scratch: crate::geometry::lines::GlyphLines::new(),
segments_scratch: Vec::new(),
}
}
pub fn set_dpi(&mut self, dpi: f32) {
self.dpi = dpi;
}
pub(crate) fn load_offset_table(&mut self, font_bytes: &[u8]) -> Result<(), FontError> {
if font_bytes.len() >= 12 {
self.offset_table = OffsetTable {
_scaler_type: get_u32_be(font_bytes, 0),
num_tables: get_u16_be(font_bytes, 4),
_search_range: get_u16_be(font_bytes, 6),
_entry_selector: get_u16_be(font_bytes, 8),
_range_shift: get_u16_be(font_bytes, 10),
};
Ok(())
} else {
Err(FontError::InvalidFile)
}
}
pub(crate) fn load_tables(&mut self, font_bytes: &[u8]) -> Result<(), FontError> {
let mut offset = size_of::<OffsetTable>();
for _i in 0..self.offset_table.num_tables {
if offset + 16 > font_bytes.len() {
return Err(FontError::UnexpectedEndOfFile);
}
let table = TableRecord {
table_tag: [font_bytes[offset], font_bytes[offset + 1], font_bytes[offset + 2], font_bytes[offset + 3]],
check_sum: get_u32_be(font_bytes, offset + 4),
offset: get_u32_be(font_bytes, offset + 8),
length: get_u32_be(font_bytes, offset + 12),
};
self.tables.push(table);
offset += size_of::<TableRecord>();
}
Ok(())
}
pub fn load_font(font_bytes: &[u8]) -> Result<Self, FontError> {
let mut font = Self::new();
font.load_offset_table(&font_bytes)?;
font.load_tables(&font_bytes)?;
font.load_cmap(&font_bytes)?;
font.load_cmap_encodings(&font_bytes);
font.load_cmap_subtable_formats(&font_bytes);
font.load_cmap_subtables(&font_bytes);
font.load_hhea(&font_bytes)?;
font.load_head(&font_bytes)?;
font.load_maxp(&font_bytes)?;
font.load_loca(&font_bytes)?;
font.load_glyf()?;
font.load_hmtx(&font_bytes)?;
font.cache_all_glyphs(&font_bytes);
font.load_kerning_pairs(&font_bytes);
font.offset_table = OffsetTable::new();
font.tables.clear();
font.cmap = CmapTable::new();
font.loca = LocaTable::Short(Vec::new());
font.maxp = MaxpTable::new();
Ok(font)
}
}
#[inline]
pub fn get_u32_be(base: &[u8], offset: usize) -> u32 {
if offset + 4 > base.len() { return 0; }
let bytes = &base[offset..offset + 4];
u32::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
}
#[inline]
pub fn get_u16_be(base: &[u8], offset: usize) -> u16 {
if offset + 2 > base.len() { return 0; }
let bytes = &base[offset..offset + 2];
u16::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
}
#[inline]
pub fn get_i16_be(base: &[u8], offset: usize) -> i16 {
if offset + 2 > base.len() { return 0; }
let bytes = &base[offset..offset + 2];
i16::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
}
#[inline]
pub fn get_i64_be(base: &[u8], offset: usize) -> i64 {
if offset + 8 > base.len() { return 0; }
let bytes = &base[offset..offset + 8];
i64::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
}