use std::collections::{BTreeSet, HashMap};
use crate::graph::{Graph, ObjectId, ObjectStore, OffsetLen};
use crate::validate::{Validate, ValidationReport};
use types::Uint24;
pub trait FontWrite {
fn write_into(&self, writer: &mut TableWriter);
}
#[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>, ValidationReport> {
table.validate()?;
let mut writer = TableWriter::default();
table.write_into(&mut writer);
let mut graph = writer.finish();
if !graph.topological_sort() {
panic!("could not find a graph packing that handles all offsets, aborting");
}
Ok(dump_impl(&graph.order, &graph.objects))
}
fn dump_impl(order: &[ObjectId], nodes: &HashMap<ObjectId, TableData>) -> Vec<u8> {
let mut offsets = HashMap::new();
let mut out = Vec::new();
let mut off = 0;
for id in order {
let node = nodes.get(id).unwrap();
offsets.insert(*id, off);
off += node.bytes.len() as u32;
out.extend_from_slice(&node.bytes);
}
let mut table_head = 0;
for id in order {
let node = nodes.get(id).unwrap();
for offset in &node.offsets {
let abs_off = *offsets
.get(&offset.object)
.expect("all offsets visited in first pass");
let rel_off = abs_off - (table_head + offset.adjustment);
let buffer_pos = table_head + offset.pos;
let write_over = out.get_mut(buffer_pos as usize..).unwrap();
write_offset(write_over, offset.len, rel_off);
}
table_head += node.bytes.len() as u32;
}
out
}
fn write_offset(at: &mut [u8], len: OffsetLen, resolved: u32) {
let at = &mut at[..len as u8 as usize];
match len {
OffsetLen::Offset16 => at.copy_from_slice(
u16::try_from(resolved)
.expect("offset overflow should be checked before now")
.to_be_bytes()
.as_slice(),
),
OffsetLen::Offset24 => at.copy_from_slice(
Uint24::checked_new(resolved)
.expect("offset overflow should be checked before now")
.to_be_bytes()
.as_slice(),
),
OffsetLen::Offset32 => at.copy_from_slice(resolved.to_be_bytes().as_slice()),
}
}
impl TableWriter {
fn add_table(&mut self, table: &dyn FontWrite) -> ObjectId {
self.stack.push(TableData::default());
table.write_into(self);
self.tables.add(self.stack.pop().unwrap())
}
fn finish(mut self) -> Graph {
let id = self.tables.add(self.stack.pop().unwrap());
Graph::from_obj_store(self.tables, id)
}
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()
.bytes
.extend_from_slice(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) -> Vec<u8> {
assert_eq!(self.stack.len(), 1);
let result = self.stack.pop().unwrap();
assert!(result.offsets.is_empty());
result.bytes
}
}
impl Default for TableWriter {
fn default() -> Self {
TableWriter {
tables: ObjectStore::default(),
stack: vec![TableData::default()],
offset_adjustment: 0,
}
}
}
#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
pub(crate) struct TableData {
pub(crate) bytes: Vec<u8>,
pub(crate) offsets: Vec<OffsetRecord>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct OffsetRecord {
pos: u32,
pub(crate) len: OffsetLen,
pub(crate) object: ObjectId,
pub(crate) adjustment: u32,
}
impl TableData {
fn add_offset(&mut self, object: ObjectId, width: usize, adjustment: u32) {
self.offsets.push(OffsetRecord {
pos: self.bytes.len() as u32,
len: match width {
2 => OffsetLen::Offset16,
3 => OffsetLen::Offset24,
_ => OffsetLen::Offset32,
},
object,
adjustment,
});
let null_bytes = &[0u8, 0, 0, 0].get(..width.min(4)).unwrap();
self.write(null_bytes);
}
fn write(&mut self, bytes: &[u8]) {
self.bytes.extend(bytes)
}
#[cfg(test)]
pub fn make_mock(size: usize) -> Self {
TableData {
bytes: vec![0xca; size], offsets: Vec::new(),
}
}
#[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::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::GlyphId);
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) {
match self {
Some(obj) => obj.write_into(writer),
None => (),
}
}
}