use std::collections::BTreeSet;
use crate::error::{Error, PackingError};
use crate::graph::{Graph, ObjectId, ObjectStore, OffsetLen};
use crate::table_type::TableType;
use crate::validate::Validate;
use fontcull_font_types::{FixedSize, Scalar};
use fontcull_read_fonts::{FontData, FontRead, FontReadWithArgs, ReadError};
pub trait FontWrite {
fn write_into(&self, writer: &mut TableWriter);
fn table_type(&self) -> TableType {
TableType::Unknown
}
}
#[derive(Debug)]
pub struct TableWriter {
tables: ObjectStore,
stack: Vec<TableData>,
offset_adjustment: u32,
}
pub fn dump_table<T: FontWrite + Validate>(table: &T) -> Result<Vec<u8>, Error> {
log::trace!("writing table '{}'", table.table_type());
table.validate()?;
let mut graph = TableWriter::make_graph(table);
if !graph.pack_objects() {
return Err(Error::PackingFailed(PackingError {
graph: graph.into(),
}));
}
Ok(graph.serialize())
}
impl TableWriter {
pub(crate) fn make_graph(root: &impl FontWrite) -> Graph {
let mut writer = TableWriter::default();
let root_id = writer.add_table(root);
Graph::from_obj_store(writer.tables, root_id)
}
fn add_table(&mut self, table: &dyn FontWrite) -> ObjectId {
self.stack.push(TableData::default());
table.write_into(self);
let mut table_data = self.stack.pop().unwrap();
table_data.type_ = table.table_type();
self.tables.add(table_data)
}
pub(crate) fn adjust_offsets(&mut self, adjustment: u32, f: impl FnOnce(&mut TableWriter)) {
self.offset_adjustment = adjustment;
f(self);
self.offset_adjustment = 0;
}
#[inline]
pub fn write_slice(&mut self, bytes: &[u8]) {
self.stack.last_mut().unwrap().write_bytes(bytes)
}
pub fn write_offset(&mut self, obj: &dyn FontWrite, width: usize) {
let obj_id = self.add_table(obj);
let data = self.stack.last_mut().unwrap();
data.add_offset(obj_id, width, self.offset_adjustment);
}
pub fn pad_to_2byte_aligned(&mut self) {
if self.stack.last().unwrap().bytes.len() % 2 != 0 {
self.write_slice(&[0]);
}
}
pub(crate) fn into_data(mut self) -> TableData {
assert_eq!(self.stack.len(), 1);
let result = self.stack.pop().unwrap();
assert!(result.offsets.is_empty());
result
}
pub(crate) fn current_data(&self) -> &TableData {
self.stack.last().unwrap() }
}
impl Default for TableWriter {
fn default() -> Self {
TableWriter {
tables: ObjectStore::default(),
stack: vec![TableData::default()],
offset_adjustment: 0,
}
}
}
#[derive(Debug, Default, Clone)] pub(crate) struct TableData {
pub(crate) type_: TableType,
pub(crate) bytes: Vec<u8>,
pub(crate) offsets: Vec<OffsetRecord>,
}
impl std::hash::Hash for TableData {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.bytes.hash(state);
self.offsets.hash(state);
}
}
impl PartialEq for TableData {
fn eq(&self, other: &Self) -> bool {
self.bytes == other.bytes && self.offsets == other.offsets
}
}
impl Eq for TableData {}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct OffsetRecord {
pub(crate) pos: u32,
pub(crate) len: OffsetLen,
pub(crate) object: ObjectId,
pub(crate) adjustment: u32,
}
impl TableData {
pub(crate) fn new(type_: TableType) -> Self {
TableData {
type_,
..Default::default()
}
}
pub(crate) fn add_offset(&mut self, object: ObjectId, width: usize, adjustment: u32) {
const PLACEHOLDER_BYTES: &[u8] = &[0xff; 4];
self.offsets.push(OffsetRecord {
pos: self.bytes.len() as u32,
len: match width {
2 => OffsetLen::Offset16,
3 => OffsetLen::Offset24,
_ => OffsetLen::Offset32,
},
object,
adjustment,
});
let placeholder = PLACEHOLDER_BYTES.get(..width.min(4)).unwrap();
self.write_bytes(placeholder);
}
pub(crate) fn write<T: Scalar>(&mut self, value: T) {
self.write_bytes(value.to_raw().as_ref())
}
pub(crate) fn write_over<T: Scalar>(&mut self, value: T, pos: usize) {
let raw = value.to_raw();
let len = raw.as_ref().len();
self.bytes[pos..pos + len].copy_from_slice(raw.as_ref());
}
fn write_bytes(&mut self, bytes: &[u8]) {
self.bytes.extend_from_slice(bytes)
}
pub(crate) fn reparse<'a, T: FontRead<'a>>(&'a self) -> Result<T, ReadError> {
let data = FontData::new(&self.bytes);
T::read(data)
}
pub(crate) fn reparse_with_args<'a, A, T: FontReadWithArgs<'a, Args = A>>(
&'a self,
args: &A,
) -> Result<T, ReadError> {
let data = FontData::new(&self.bytes);
T::read_with_args(data, args)
}
pub(crate) fn read_at<T: Scalar>(&self, pos: usize) -> Option<T> {
let len = T::RAW_BYTE_LEN;
self.bytes.get(pos..pos + len).and_then(T::read)
}
#[cfg(test)]
pub fn make_mock(size: usize) -> Self {
TableData {
bytes: vec![0xca; size], offsets: Vec::new(),
type_: TableType::MockTable,
}
}
#[cfg(test)]
pub fn add_mock_offset(&mut self, object: ObjectId, len: OffsetLen) {
let pos = self.offsets.iter().map(|off| off.len as u8 as u32).sum();
self.offsets.push(OffsetRecord {
pos,
len,
object,
adjustment: 0,
});
}
}
macro_rules! write_be_bytes {
($ty:ty) => {
impl FontWrite for $ty {
#[inline]
fn write_into(&self, writer: &mut TableWriter) {
writer.write_slice(&self.to_be_bytes())
}
}
};
}
write_be_bytes!(u8);
write_be_bytes!(i8);
write_be_bytes!(u16);
write_be_bytes!(i16);
write_be_bytes!(u32);
write_be_bytes!(i32);
write_be_bytes!(i64);
write_be_bytes!(types::Uint24);
write_be_bytes!(types::Int24);
write_be_bytes!(types::F2Dot14);
write_be_bytes!(types::Fixed);
write_be_bytes!(types::FWord);
write_be_bytes!(types::UfWord);
write_be_bytes!(types::LongDateTime);
write_be_bytes!(types::Tag);
write_be_bytes!(types::Version16Dot16);
write_be_bytes!(types::MajorMinor);
write_be_bytes!(types::GlyphId16);
write_be_bytes!(types::NameId);
impl<T: FontWrite> FontWrite for [T] {
fn write_into(&self, writer: &mut TableWriter) {
self.iter().for_each(|item| item.write_into(writer))
}
}
impl<T: FontWrite> FontWrite for BTreeSet<T> {
fn write_into(&self, writer: &mut TableWriter) {
self.iter().for_each(|item| item.write_into(writer))
}
}
impl<T: FontWrite> FontWrite for Vec<T> {
fn write_into(&self, writer: &mut TableWriter) {
self.iter().for_each(|item| item.write_into(writer))
}
}
impl<T: FontWrite> FontWrite for Option<T> {
fn write_into(&self, writer: &mut TableWriter) {
if let Some(obj) = self {
obj.write_into(writer)
}
}
}