diff --git a/font-codegen/src/fields.rs b/font-codegen/src/fields.rs index e59f41226..9c4aff78b 100644 --- a/font-codegen/src/fields.rs +++ b/font-codegen/src/fields.rs @@ -605,6 +605,13 @@ impl Field { self.attrs.conditional.is_some() } + fn skip_compile(&self) -> bool { + if let Some(compile) = self.attrs.compile.as_ref() { + return matches!(&compile.attr, CustomCompile::Skip); + } + false + } + /// Sanity check we are in a sane state for the end of phase fn sanity_check(&self, phase: Phase) -> syn::Result<()> { check_resolution(phase, &self.typ)?; @@ -1398,7 +1405,7 @@ impl Field { #[allow(clippy::wrong_self_convention)] fn from_obj_requires_offset_data(&self, in_record: bool) -> bool { match &self.typ { - _ if self.attrs.to_owned.is_some() => false, + _ if self.attrs.to_owned.is_some() || self.skip_compile() => false, FieldType::Offset { target: OffsetTarget::Array(_), .. diff --git a/read-fonts/generated/generated_postscript.rs b/read-fonts/generated/generated_postscript.rs index f037a29fa..ec4c51f84 100644 --- a/read-fonts/generated/generated_postscript.rs +++ b/read-fonts/generated/generated_postscript.rs @@ -46,8 +46,8 @@ impl<'a> FontRead<'a> for Index1<'a> { let mut cursor = data.cursor(); let count: u16 = cursor.read()?; let off_size: u8 = cursor.read()?; - let offsets_byte_len = (transforms::add_multiply(count, 1_usize, off_size)) - .checked_mul(u8::RAW_BYTE_LEN) + let offsets_byte_len = (transforms::add(count, 1_usize)) + .checked_mul(::compute_size(&off_size)?) .ok_or(ReadError::OutOfBounds)?; cursor.advance_by(offsets_byte_len); let data_byte_len = cursor.remaining_bytes() / u8::RAW_BYTE_LEN * u8::RAW_BYTE_LEN; @@ -77,9 +77,9 @@ impl<'a> Index1<'a> { } /// Bytes containing `count + 1` offsets each of `off_size`. - pub fn offsets(&self) -> &'a [u8] { + pub fn offsets(&self) -> ComputedArray<'a, VarOffset> { let range = self.shape.offsets_byte_range(); - self.data.read_array(range).unwrap() + self.data.read_with_args(range, &self.off_size()).unwrap() } /// Array containing the object data. @@ -98,7 +98,14 @@ impl<'a> SomeTable<'a> for Index1<'a> { match idx { 0usize => Some(Field::new("count", self.count())), 1usize => Some(Field::new("off_size", self.off_size())), - 2usize => Some(Field::new("offsets", self.offsets())), + 2usize => Some(Field::new( + "offsets", + traversal::FieldType::computed_array( + "VarOffset", + self.offsets(), + self.offset_data(), + ), + )), 3usize => Some(Field::new("data", self.data())), _ => None, } @@ -154,8 +161,8 @@ impl<'a> FontRead<'a> for Index2<'a> { let mut cursor = data.cursor(); let count: u32 = cursor.read()?; let off_size: u8 = cursor.read()?; - let offsets_byte_len = (transforms::add_multiply(count, 1_usize, off_size)) - .checked_mul(u8::RAW_BYTE_LEN) + let offsets_byte_len = (transforms::add(count, 1_usize)) + .checked_mul(::compute_size(&off_size)?) .ok_or(ReadError::OutOfBounds)?; cursor.advance_by(offsets_byte_len); let data_byte_len = cursor.remaining_bytes() / u8::RAW_BYTE_LEN * u8::RAW_BYTE_LEN; @@ -185,9 +192,9 @@ impl<'a> Index2<'a> { } /// Bytes containing `count + 1` offsets each of `off_size`. - pub fn offsets(&self) -> &'a [u8] { + pub fn offsets(&self) -> ComputedArray<'a, VarOffset> { let range = self.shape.offsets_byte_range(); - self.data.read_array(range).unwrap() + self.data.read_with_args(range, &self.off_size()).unwrap() } /// Array containing the object data. @@ -206,7 +213,14 @@ impl<'a> SomeTable<'a> for Index2<'a> { match idx { 0usize => Some(Field::new("count", self.count())), 1usize => Some(Field::new("off_size", self.off_size())), - 2usize => Some(Field::new("offsets", self.offsets())), + 2usize => Some(Field::new( + "offsets", + traversal::FieldType::computed_array( + "VarOffset", + self.offsets(), + self.offset_data(), + ), + )), 3usize => Some(Field::new("data", self.data())), _ => None, } diff --git a/read-fonts/src/tables/postscript.rs b/read-fonts/src/tables/postscript.rs index ef47a58f9..32244b132 100644 --- a/read-fonts/src/tables/postscript.rs +++ b/read-fonts/src/tables/postscript.rs @@ -18,7 +18,7 @@ include!("../../generated/generated_postscript.rs"); pub use blend::BlendState; pub use charset::{Charset, CharsetIter}; -pub use index::Index; +pub use index::{Index, VarOffset}; pub use stack::{Number, Stack}; pub use string::{Latin1String, StringId, STANDARD_STRINGS}; diff --git a/read-fonts/src/tables/postscript/index.rs b/read-fonts/src/tables/postscript/index.rs index 96d475c57..448fc2f3a 100644 --- a/read-fonts/src/tables/postscript/index.rs +++ b/read-fonts/src/tables/postscript/index.rs @@ -110,6 +110,65 @@ impl Default for Index<'_> { } } +/// An offset that can be encoded using 1-4 bytes. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct VarOffset(u32); + +impl ReadArgs for VarOffset { + type Args = u8; +} + +impl ComputeSize for VarOffset { + fn compute_size(args: &Self::Args) -> Result { + Ok(*args as usize) + } +} + +impl FontReadWithArgs<'_> for VarOffset { + fn read_with_args(data: FontData<'_>, args: &Self::Args) -> Result { + // There are actually count + 1 entries in the offset array. + // + // "Offsets in the offset array are relative to the byte that precedes + // the object data. Therefore the first element of the offset array is + // always 1. (This ensures that every object has a corresponding offset + // which is always nonzero and permits the efficient implementation of + // dynamic object loading.)" + // + // See + let raw = match *args { + 1 => data.read_at::(0).map(|x| x as _), + 2 => data.read_at::(0).map(|x| x as _), + 3 => data.read_at::(0).map(|x| x.to_u32()), + 4 => data.read_at::(0), + _ => Err(ReadError::MalformedData("invalid cff index offset len")), + }?; + + raw.checked_sub(1) + .ok_or(ReadError::OutOfBounds) + .map(VarOffset) + } +} + +impl VarOffset { + pub fn to_u32(self) -> u32 { + self.0 + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for VarOffset { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "VarOffset", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("offset", self.0)), + _ => None, + }), + data, + } + } +} + impl<'a> Index1<'a> { /// Returns the total size in bytes of the index table. pub fn size_in_bytes(&self) -> Result { @@ -130,12 +189,10 @@ impl<'a> Index1<'a> { /// Returns the offset of the object at the given index. pub fn get_offset(&self, index: usize) -> Result { - read_offset( - index, - self.count() as usize, - self.off_size(), - self.offsets(), - ) + self.offsets() + .get(index) + .map(|idx| idx.0 as usize) + .map_err(|_| Error::ZeroOffsetInIndex) } /// Returns the data for the object at the given index. @@ -166,12 +223,10 @@ impl<'a> Index2<'a> { /// Returns the offset of the object at the given index. pub fn get_offset(&self, index: usize) -> Result { - read_offset( - index, - self.count() as usize, - self.off_size(), - self.offsets(), - ) + self.offsets() + .get(index) + .map(|idx| idx.0 as usize) + .map_err(|_| Error::ZeroOffsetInIndex) } /// Returns the data for the object at the given index. @@ -182,39 +237,6 @@ impl<'a> Index2<'a> { } } -/// Reads an offset which is encoded as a variable sized integer. -fn read_offset( - index: usize, - count: usize, - offset_size: u8, - offset_data: &[u8], -) -> Result { - // There are actually count + 1 entries in the offset array. - // - // "Offsets in the offset array are relative to the byte that precedes - // the object data. Therefore the first element of the offset array is - // always 1. (This ensures that every object has a corresponding offset - // which is always nonzero and permits the efficient implementation of - // dynamic object loading.)" - // - // See - if index > count { - Err(ReadError::OutOfBounds)?; - } - let data_offset = index * offset_size as usize; - let offset_data = FontData::new(offset_data); - match offset_size { - 1 => offset_data.read_at::(data_offset)? as usize, - 2 => offset_data.read_at::(data_offset)? as usize, - 3 => offset_data.read_at::(data_offset)?.to_u32() as usize, - 4 => offset_data.read_at::(data_offset)? as usize, - _ => return Err(Error::InvalidIndexOffsetSize(offset_size)), - } - // As above, subtract one to get the actual offset. - .checked_sub(1) - .ok_or(Error::ZeroOffsetInIndex) -} - #[cfg(test)] mod tests { use font_test_data::bebuffer::BeBuffer; diff --git a/resources/codegen_inputs/postscript.rs b/resources/codegen_inputs/postscript.rs index 8e3ea9922..00f0e2e85 100644 --- a/resources/codegen_inputs/postscript.rs +++ b/resources/codegen_inputs/postscript.rs @@ -1,30 +1,44 @@ #![parse_module(read_fonts::tables::postscript)] /// An array of variable-sized objects in a `CFF` table. +#[skip_font_write] table Index1 { /// Number of objects stored in INDEX. + #[compile(skip)] count: u16, /// Object array element size. + #[compile(skip)] off_size: u8, /// Bytes containing `count + 1` offsets each of `off_size`. - #[count(add_multiply($count, 1, $off_size))] - offsets: [u8], + #[count(add($count, 1))] + #[read_with($off_size)] + #[compile(skip)] + offsets: ComputedArray, /// Array containing the object data. #[count(..)] + #[compile_type(Vec>)] + #[to_owned(convert_objects_f1(obj))] data: [u8], } /// An array of variable-sized objects in a `CFF2` table. +#[skip_font_write] table Index2 { /// Number of objects stored in INDEX. + #[compile(skip)] count: u32, /// Object array element size. + #[compile(skip)] off_size: u8, /// Bytes containing `count + 1` offsets each of `off_size`. - #[count(add_multiply($count, 1, $off_size))] - offsets: [u8], + #[count(add($count, 1))] + #[read_with($off_size)] + #[compile(skip)] + offsets: ComputedArray, /// Array containing the object data. #[count(..)] + #[compile_type(Vec>)] + #[to_owned(convert_objects_f2(obj))] data: [u8], } diff --git a/write-fonts/generated/generated_ift.rs b/write-fonts/generated/generated_ift.rs index 3c6f9224c..4a6e75608 100644 --- a/write-fonts/generated/generated_ift.rs +++ b/write-fonts/generated/generated_ift.rs @@ -345,7 +345,6 @@ impl Validate for GlyphMap { impl<'a> FromObjRef> for GlyphMap { fn from_obj_ref(obj: &read_fonts::tables::ift::GlyphMap<'a>, _: FontData) -> Self { - let offset_data = obj.offset_data(); GlyphMap { first_mapped_glyph: obj.first_mapped_glyph(), } @@ -428,7 +427,7 @@ impl Validate for FeatureRecord { } impl FromObjRef for FeatureRecord { - fn from_obj_ref(obj: &read_fonts::tables::ift::FeatureRecord, offset_data: FontData) -> Self { + fn from_obj_ref(obj: &read_fonts::tables::ift::FeatureRecord, _: FontData) -> Self { FeatureRecord { feature_tag: obj.feature_tag(), } @@ -459,7 +458,7 @@ impl Validate for EntryMapRecord { } impl FromObjRef for EntryMapRecord { - fn from_obj_ref(obj: &read_fonts::tables::ift::EntryMapRecord, offset_data: FontData) -> Self { + fn from_obj_ref(obj: &read_fonts::tables::ift::EntryMapRecord, _: FontData) -> Self { EntryMapRecord {} } } diff --git a/write-fonts/generated/generated_postscript.rs b/write-fonts/generated/generated_postscript.rs index 5b7b20921..1c359711d 100644 --- a/write-fonts/generated/generated_postscript.rs +++ b/write-fonts/generated/generated_postscript.rs @@ -9,37 +9,15 @@ use crate::codegen_prelude::*; #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Index1 { - /// Number of objects stored in INDEX. - pub count: u16, - /// Object array element size. - pub off_size: u8, - /// Bytes containing `count + 1` offsets each of `off_size`. - pub offsets: Vec, /// Array containing the object data. - pub data: Vec, + pub data: Vec>, } impl Index1 { /// Construct a new `Index1` - pub fn new(count: u16, off_size: u8, offsets: Vec, data: Vec) -> Self { - Self { - count, - off_size, - offsets, - data, - } - } -} - -impl FontWrite for Index1 { - fn write_into(&self, writer: &mut TableWriter) { - self.count.write_into(writer); - self.off_size.write_into(writer); - self.offsets.write_into(writer); - self.data.write_into(writer); - } - fn table_type(&self) -> TableType { - TableType::Named("Index1") + #[allow(clippy::useless_conversion)] + pub fn new(data: Vec>) -> Self { + Self { data } } } @@ -49,12 +27,8 @@ impl Validate for Index1 { impl<'a> FromObjRef> for Index1 { fn from_obj_ref(obj: &read_fonts::tables::postscript::Index1<'a>, _: FontData) -> Self { - let offset_data = obj.offset_data(); Index1 { - count: obj.count(), - off_size: obj.off_size(), - offsets: obj.offsets().to_owned_obj(offset_data), - data: obj.data().to_owned_obj(offset_data), + data: convert_objects_f1(obj), } } } @@ -72,37 +46,15 @@ impl<'a> FontRead<'a> for Index1 { #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Index2 { - /// Number of objects stored in INDEX. - pub count: u32, - /// Object array element size. - pub off_size: u8, - /// Bytes containing `count + 1` offsets each of `off_size`. - pub offsets: Vec, /// Array containing the object data. - pub data: Vec, + pub data: Vec>, } impl Index2 { /// Construct a new `Index2` - pub fn new(count: u32, off_size: u8, offsets: Vec, data: Vec) -> Self { - Self { - count, - off_size, - offsets, - data, - } - } -} - -impl FontWrite for Index2 { - fn write_into(&self, writer: &mut TableWriter) { - self.count.write_into(writer); - self.off_size.write_into(writer); - self.offsets.write_into(writer); - self.data.write_into(writer); - } - fn table_type(&self) -> TableType { - TableType::Named("Index2") + #[allow(clippy::useless_conversion)] + pub fn new(data: Vec>) -> Self { + Self { data } } } @@ -112,12 +64,8 @@ impl Validate for Index2 { impl<'a> FromObjRef> for Index2 { fn from_obj_ref(obj: &read_fonts::tables::postscript::Index2<'a>, _: FontData) -> Self { - let offset_data = obj.offset_data(); Index2 { - count: obj.count(), - off_size: obj.off_size(), - offsets: obj.offsets().to_owned_obj(offset_data), - data: obj.data().to_owned_obj(offset_data), + data: convert_objects_f2(obj), } } } diff --git a/write-fonts/src/tables/postscript.rs b/write-fonts/src/tables/postscript.rs index 57cb2e3b1..49ca793a0 100644 --- a/write-fonts/src/tables/postscript.rs +++ b/write-fonts/src/tables/postscript.rs @@ -5,52 +5,85 @@ include!("../../generated/generated_postscript.rs"); impl Index2 { /// Construct an `Index2` from a list of byte items. pub fn from_items(items: Vec>) -> Self { - if items.is_empty() { - return Index2::new(0, 1, vec![1], vec![]); + Self { data: items } + } +} + +impl FontWrite for Index1 { + fn write_into(&self, writer: &mut TableWriter) { + IndexWriter { + format: IndexFormat::Format1, + objects: self.data.as_slice(), + } + .write_into(writer); + } +} + +impl FontWrite for Index2 { + fn write_into(&self, writer: &mut TableWriter) { + IndexWriter { + format: IndexFormat::Format2, + objects: self.data.as_slice(), } + .write_into(writer); + } +} + +fn convert_objects_f1(from: &read_fonts::tables::postscript::Index1) -> Vec> { + (0..from.count()) + .map(|i| from.get(i as usize).map(Vec::from).unwrap_or_default()) + .collect() +} + +fn convert_objects_f2(from: &read_fonts::tables::postscript::Index2) -> Vec> { + (0..from.count()) + .map(|i| from.get(i as usize).map(Vec::from).unwrap_or_default()) + .collect() +} + +enum IndexFormat { + Format1, + Format2, +} +struct IndexWriter<'a> { + format: IndexFormat, + objects: &'a [Vec], +} - let count = items.len() as u32; +impl FontWrite for IndexWriter<'_> { + fn write_into(&self, writer: &mut TableWriter) { + let count = self.objects.len(); + match self.format { + IndexFormat::Format1 => (count as u16).write_into(writer), + IndexFormat::Format2 => (count as u32).write_into(writer), + } // Calculate offsets (1-based per CFF2 spec) - let mut offset_values = Vec::with_capacity(items.len() + 1); + let mut offset_values = Vec::with_capacity(count); let mut current_offset = 1u32; + // always start with 1 offset_values.push(current_offset); - for item in &items { + for item in self.objects { current_offset += item.len() as u32; offset_values.push(current_offset); } - // Determine off_size (minimum bytes needed for largest offset) - let max_offset = *offset_values.last().unwrap(); - let off_size = if max_offset <= 0xFF { - 1u8 - } else if max_offset <= 0xFFFF { - 2u8 - } else if max_offset <= 0xFFFFFF { - 3u8 - } else { - 4u8 - }; + let off_size = (4 - current_offset.leading_zeros() / 8).max(1) as u8; + off_size.write_into(writer); - // Pack offsets based on off_size - let mut offsets = Vec::with_capacity(offset_values.len() * off_size as usize); - for offset in &offset_values { + for offset in offset_values.iter().copied() { match off_size { - 1 => offsets.push(*offset as u8), - 2 => offsets.extend((*offset as u16).to_be_bytes()), - 3 => { - let bytes = offset.to_be_bytes(); - offsets.extend(&bytes[1..4]); - } - 4 => offsets.extend(offset.to_be_bytes()), + 1 => (offset as u8).write_into(writer), + 2 => (offset as u16).write_into(writer), + 3 => Uint24::new(offset).write_into(writer), + 4 => offset.write_into(writer), _ => unreachable!(), } } - // Concatenate item data - let data: Vec = items.into_iter().flatten().collect(); - - Index2::new(count, off_size, offsets, data) + for data in self.objects { + data.write_into(writer); + } } } @@ -62,6 +95,9 @@ mod tests { fn test_index2_from_items() { let items = vec![vec![1, 2, 3], vec![4, 5]]; let index = Index2::from_items(items); - assert_eq!(index.count, 2); + let bytes = crate::dump_table(&index).unwrap(); + let read_back = Index2::read(bytes.as_slice().into()).unwrap(); + + assert_eq!(index, read_back); } }