use crate::binary::read::{ReadArray, ReadBinary, ReadBuf, ReadCtxt, ReadFrom, ReadScope};
use crate::binary::U32Be;
use crate::error::ParseError;
use crate::tables::FontTableProvider;
use alloc::borrow::Cow;
use alloc::string::String;
use core::convert::TryFrom;
pub const MAGIC: u32 = 0x774F4646;
#[derive(Clone)]
pub struct WoffFont<'a> {
pub scope: ReadScope<'a>,
pub woff_header: WoffHeader,
pub table_directory: ReadArray<'a, TableDirectoryEntry>,
}
#[derive(Clone, Debug)]
pub struct WoffHeader {
pub flavor: u32,
pub length: u32,
pub num_tables: u16,
pub total_sfnt_size: u32,
pub _major_version: u16,
pub _minor_version: u16,
pub meta_offset: u32,
pub meta_length: u32,
pub meta_orig_length: u32,
pub priv_offset: u32,
pub priv_length: u32,
}
#[derive(Debug, Clone)]
pub struct TableDirectoryEntry {
pub tag: u32,
pub offset: u32,
pub comp_length: u32,
pub orig_length: u32,
pub orig_checksum: u32,
}
impl<'a> WoffFont<'a> {
pub fn flavor(&self) -> u32 {
self.woff_header.flavor
}
pub fn extended_metadata(&self) -> Result<Option<String>, ParseError> {
use miniz_oxide::inflate::decompress_to_vec;
let offset = usize::try_from(self.woff_header.meta_offset)?;
let length = usize::try_from(self.woff_header.meta_length)?;
if offset == 0 || length == 0 {
return Ok(None);
}
let compressed_metadata = self.scope.offset_length(offset, length)?;
let metadata_uncompressed = decompress_to_vec(compressed_metadata.data()).map_err(|_err| ParseError::CompressionError)?;
let metadata_string = String::from_utf8(metadata_uncompressed).map_err(|_err| ParseError::CompressionError)?;
Ok(Some(metadata_string))
}
pub fn find_table_directory_entry(&self, tag: u32) -> Option<TableDirectoryEntry> {
self.table_directory
.iter()
.find(|table_entry| table_entry.tag == tag)
}
}
impl<'a> ReadBinary<'a> for WoffFont<'a> {
type HostType = Self;
fn read(ctxt: &mut ReadCtxt<'a>) -> Result<Self, ParseError> {
let scope = ctxt.scope();
let mut peek = ctxt.clone();
let magic = peek.read_u32be()?;
match magic {
MAGIC => {
let woff_header = ctxt.read::<WoffHeader>()?;
let table_directory =
ctxt.read_array::<TableDirectoryEntry>(usize::from(woff_header.num_tables))?;
Ok(WoffFont {
scope,
woff_header,
table_directory,
})
}
_ => Err(ParseError::BadVersion),
}
}
}
impl<'a> FontTableProvider for WoffFont<'a> {
fn table_data<'b>(&'b self, tag: u32) -> Result<Option<Cow<'b, [u8]>>, ParseError> {
self.find_table_directory_entry(tag)
.map(|table_entry| {
table_entry
.read_table(&self.scope)
.map(|table| table.into_data())
})
.transpose()
}
fn has_table(&self, tag: u32) -> bool {
self.find_table_directory_entry(tag).is_some()
}
}
impl<'a> ReadBinary<'a> for WoffHeader {
type HostType = Self;
fn read(ctxt: &mut ReadCtxt<'a>) -> Result<Self, ParseError> {
let signature = ctxt.read_u32be()?;
match signature {
MAGIC => {
let flavor = ctxt.read_u32be()?;
let length = ctxt.read_u32be()?;
let num_tables = ctxt.read_u16be()?;
let reserved = ctxt.read_u16be()?;
ctxt.check(reserved == 0)?;
let total_sfnt_size = ctxt.read_u32be()?;
let _major_version = ctxt.read_u16be()?;
let _minor_version = ctxt.read_u16be()?;
let meta_offset = ctxt.read_u32be()?;
let meta_length = ctxt.read_u32be()?;
let meta_orig_length = ctxt.read_u32be()?;
let priv_offset = ctxt.read_u32be()?;
let priv_length = ctxt.read_u32be()?;
Ok(WoffHeader {
flavor,
length,
num_tables,
total_sfnt_size,
_major_version,
_minor_version,
meta_offset,
meta_length,
meta_orig_length,
priv_offset,
priv_length,
})
}
_ => Err(ParseError::BadVersion),
}
}
}
impl<'a> ReadFrom<'a> for TableDirectoryEntry {
type ReadType = ((U32Be, U32Be, U32Be), (U32Be, U32Be));
fn from(
((tag, offset, comp_length), (orig_length, orig_checksum)): ((u32, u32, u32), (u32, u32)),
) -> Self {
TableDirectoryEntry {
tag,
offset,
comp_length,
orig_length,
orig_checksum,
}
}
}
impl TableDirectoryEntry {
fn is_compressed(&self) -> bool {
self.comp_length != self.orig_length
}
pub fn read_table<'a>(&self, scope: &ReadScope<'a>) -> Result<ReadBuf<'a>, ParseError> {
let offset = usize::try_from(self.offset)?;
let length = usize::try_from(self.comp_length)?;
let table_data = scope.offset_length(offset, length)?;
if self.is_compressed() {
use miniz_oxide::inflate::decompress_to_vec;
let table_uncompressed = decompress_to_vec(table_data.data())
.map_err(|_err| ParseError::CompressionError)?;
Ok(ReadBuf::from(table_uncompressed))
} else {
Ok(ReadBuf::from(table_data.data()))
}
}
}