use alloc::vec::Vec;
use bytes::{Buf, BufMut};
use four_cc::FourCC;
use thiserror::Error;
use crate::buffer::{BufExt, SafeBuf, TruncatedError};
use crate::sfnt::{TableDirectory, TableRecord};
#[derive(Debug, Error)]
pub enum CollectionHeaderError {
#[error("Invalid collection header version")]
InvalidCollectionVersion,
#[error("Truncated collection header")]
Truncated,
#[error("No tables in font")]
NoTables,
#[error("Invalid table index")]
InvalidTableIndex,
}
impl From<TruncatedError> for CollectionHeaderError {
fn from(TruncatedError: TruncatedError) -> CollectionHeaderError {
CollectionHeaderError::Truncated
}
}
#[derive(Clone, Copy)]
#[repr(u32)]
pub enum CollectionHeaderVersion {
V1 = 0x0001_0000,
V2 = 0x0002_0000,
}
impl TryFrom<u32> for CollectionHeaderVersion {
type Error = CollectionHeaderError;
fn try_from(value: u32) -> Result<Self, CollectionHeaderError> {
match value {
value if value == Self::V1 as u32 => Ok(Self::V1),
value if value == Self::V2 as u32 => Ok(Self::V2),
_ => Err(CollectionHeaderError::InvalidCollectionVersion),
}
}
}
#[allow(dead_code)]
pub struct CollectionHeader {
pub version: CollectionHeaderVersion,
pub fonts: Vec<CollectionFontEntry>,
}
impl CollectionHeader {
pub fn from_buf(
buf: &mut impl Buf,
total_num_tables: u16,
) -> Result<Self, CollectionHeaderError> {
let version = SafeBuf::try_get_u32(buf)?.try_into()?;
let num_fonts = buf.try_get_255_u16()?;
let fonts = (0..num_fonts)
.map(|_| {
let num_tables = buf.try_get_255_u16()?;
if num_tables == 0 {
return Err(CollectionHeaderError::NoTables);
}
let flavor = buf.try_get_four_cc()?;
let table_indices = (0..num_tables)
.map(|_| {
let table_idx = buf.try_get_255_u16()?;
if table_idx >= total_num_tables {
Err(CollectionHeaderError::InvalidTableIndex)
} else {
Ok(table_idx)
}
})
.collect::<Result<_, _>>()?;
Ok(CollectionFontEntry {
flavor,
table_indices,
})
})
.collect::<Result<_, _>>()?;
Ok(CollectionHeader { version, fonts })
}
pub fn calculate_header_size(&self) -> usize {
12 + self
.fonts
.iter()
.map(|font| 4 + font.calculate_directory_size())
.sum::<usize>()
}
pub fn write_to_buf(&self, buffer: &mut impl BufMut, tables: &[TableRecord]) {
assert!(buffer.remaining_mut() >= self.calculate_header_size());
buffer.put_slice(&crate::magic::TTF_COLLECTION_FLAVOR.0);
buffer.put_u32(CollectionHeaderVersion::V1 as u32);
buffer.put_u32(self.fonts.len() as u32);
let font_directory_len = self.fonts.len() * core::mem::size_of::<u32>();
let mut table_directory_offset = 12 + font_directory_len;
for font in &self.fonts {
buffer.put_u32(table_directory_offset as u32);
table_directory_offset += font.calculate_directory_size();
}
for font in &self.fonts {
let font_tables = font
.table_indices
.iter()
.map(|&idx| tables[idx as usize])
.collect();
let table_directory = TableDirectory::new(font.flavor, font_tables);
table_directory.write_to_buf(buffer);
}
}
}
pub struct CollectionFontEntry {
pub flavor: FourCC,
pub table_indices: Vec<u16>,
}
impl CollectionFontEntry {
pub fn calculate_directory_size(&self) -> usize {
12 + self.table_indices.len() * core::mem::size_of::<TableRecord>()
}
}