blob: c4e0b865bd5483230c2d736618cdbffa16c2c60b [file] [edit]
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 font_types::{FixedSize, Scalar};
use read_fonts::{FontData, FontRead, FontReadWithArgs, ReadError};
/// A type that that can be written out as part of a font file.
///
/// This both handles writing big-endian bytes as well as describing the
/// relationship between tables and their subtables.
pub trait FontWrite {
/// Write our data and information about offsets into this [TableWriter].
fn write_into(&self, writer: &mut TableWriter);
/// The type of this table.
///
/// This only matters in cases where a table may require additional processing
/// after initial compilation, such as with GPOS/GSUB lookups.
fn table_type(&self) -> TableType {
TableType::Unknown
}
}
/// An object that manages a collection of serialized tables.
///
/// This handles deduplicating objects and tracking offsets.
#[derive(Debug)]
pub struct TableWriter {
/// Finished tables, associated with an ObjectId; duplicate tables share an id.
tables: ObjectStore,
/// Tables currently being written.
///
/// Tables are processed as they are encountered (as subtables)
stack: Vec<TableData>,
/// An adjustment factor subtracted from written offsets.
///
/// This is '0', unless a particular offset is expected to be relative some
/// position *other* than the start of the table.
///
/// This should only ever be non-zero in the body of a closure passed to
/// [adjust_offsets](Self::adjust_offsets)
offset_adjustment: u32,
}
/// Attempt to serialize a table.
///
/// Returns an error if the table is malformed or cannot otherwise be serialized,
/// otherwise it will return the bytes encoding the table.
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 {
/// A convenience method for generating a graph with the provided root object.
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)
}
/// Call the provided closure, adjusting any written offsets by `adjustment`.
pub(crate) fn adjust_offsets(&mut self, adjustment: u32, f: impl FnOnce(&mut TableWriter)) {
self.offset_adjustment = adjustment;
f(self);
self.offset_adjustment = 0;
}
/// Write raw bytes into this table.
///
/// The caller is responsible for ensuring bytes are in big-endian order.
#[inline]
pub fn write_slice(&mut self, bytes: &[u8]) {
self.stack.last_mut().unwrap().write_bytes(bytes)
}
/// Create an offset to another table.
///
/// The `width` argument is the size in bytes of the offset, e.g. 2 for
/// an `Offset16`, and 4 for an `Offset32`.
///
/// The provided table will be serialized immediately, and the position
/// of the offset within the current table will be recorded. Offsets
/// are resolved when the root table object is serialized, at which point
/// we overwrite each recorded offset position with the final offset of the
/// appropriate table.
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);
}
/// Add a padding byte of necessary to ensure the table length is an even number.
///
/// This is necessary for things like the glyph table, which require offsets
/// to be aligned on 2-byte boundaries.
pub fn pad_to_2byte_aligned(&mut self) {
if self.stack.last().unwrap().bytes.len() % 2 != 0 {
self.write_slice(&[0]);
}
}
/// used when writing top-level font objects, which are done more manually.
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
}
/// A reference to the current table data.
///
/// This is currently only used to figure out the glyph positions when
/// compiling the glyf table.
pub(crate) fn current_data(&self) -> &TableData {
self.stack.last().unwrap() // there is always at least one
}
}
impl Default for TableWriter {
fn default() -> Self {
TableWriter {
tables: ObjectStore::default(),
stack: vec![TableData::default()],
offset_adjustment: 0,
}
}
}
/// The encoded data for a given table, along with info on included offsets
#[derive(Debug, Default, Clone)] // DO NOT DERIVE MORE TRAITS! we want to ignore name field
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 {}
/// The position and type of an offset, along with the id of the pointed-to entity
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct OffsetRecord {
/// the position of the offset within the parent table
pub(crate) pos: u32,
/// the offset length in bytes
pub(crate) len: OffsetLen,
/// The object pointed to by the offset
pub(crate) object: ObjectId,
/// a value subtracted from the resolved offset before writing.
///
/// In general we assume that offsets are relative to the start of the parent
/// table, but in some cases this is not true (for instance, offsets to
/// strings in the name table are relative to the end of the table.)
pub(crate) adjustment: u32,
}
impl TableData {
pub(crate) fn new(type_: TableType) -> Self {
TableData {
type_,
..Default::default()
}
}
/// the 'adjustment' param is used to modify the written position.
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,
});
// we don't want to use zeros as our placeholder, since we want to
// distinguish from a null offset during splitting/promotion.
// we write all ones, since maybe it will stand out in debugging
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())
}
/// Write the value over existing data at the provided position.
///
/// Only used in very special cases. The caller is responsible for knowing
/// what they are doing.
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)
}
/// A helper function to reparse this table data as some type.
///
/// Used internally when modifying the graph after initial compilation,
/// such as during table splitting.
pub(crate) fn reparse<'a, T: FontRead<'a>>(&'a self) -> Result<T, ReadError> {
let data = FontData::new(&self.bytes);
T::read(data)
}
// see above
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)
}
/// A helper function to read a value out of this data.
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], // has no special meaning
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())
}
}
};
}
//NOTE: not implemented for offsets! it would be too easy to accidentally write them.
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) {
match self {
Some(obj) => obj.write_into(writer),
None => (),
}
}
}