| 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 => (), |
| } |
| } |
| } |