From ac6a598d6a96997382eca6d326be38d2f555b9f7 Mon Sep 17 00:00:00 2001 From: Hoolean Date: Mon, 11 Nov 2024 23:14:08 +0000 Subject: [PATCH 1/2] Add initial implementation of `meta` table, with read and write This commit adds the code necessary to read and write the `meta` table, including its codegen definition, manual extras to handle the variable length data fields, and plumbing to surface it within `read-fonts` and `write-fonts`. Where possible, this commit borrows liberally from the implementation of other tables, especially `name` (for its handling of variable length data). This is an early implementation, and as such requires manual and automated testing. Additionally, there is an implementation of the FontWrite trait for &[u8] that may be redundant. --- read-fonts/generated/generated_meta.rs | 168 ++++++++++++++++++++++++ read-fonts/src/table_provider.rs | 4 + read-fonts/src/tables.rs | 1 + read-fonts/src/tables/meta.rs | 15 +++ resources/codegen_inputs/meta.rs | 34 +++++ resources/codegen_plan.toml | 10 ++ write-fonts/generated/generated_meta.rs | 119 +++++++++++++++++ write-fonts/src/tables.rs | 2 + write-fonts/src/tables/meta.rs | 29 ++++ 9 files changed, 382 insertions(+) create mode 100644 read-fonts/generated/generated_meta.rs create mode 100644 read-fonts/src/tables/meta.rs create mode 100644 resources/codegen_inputs/meta.rs create mode 100644 write-fonts/generated/generated_meta.rs create mode 100644 write-fonts/src/tables/meta.rs diff --git a/read-fonts/generated/generated_meta.rs b/read-fonts/generated/generated_meta.rs new file mode 100644 index 000000000..6ce660519 --- /dev/null +++ b/read-fonts/generated/generated_meta.rs @@ -0,0 +1,168 @@ +// THIS FILE IS AUTOGENERATED. +// Any changes to this file will be overwritten. +// For more information about how codegen works, see font-codegen/README.md + +#[allow(unused_imports)] +use crate::codegen_prelude::*; + +/// [`meta`](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct MetaMarker { + data_maps_byte_len: usize, +} + +impl MetaMarker { + fn version_byte_range(&self) -> Range { + let start = 0; + start..start + u32::RAW_BYTE_LEN + } + fn flags_byte_range(&self) -> Range { + let start = self.version_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn reserved_byte_range(&self) -> Range { + let start = self.flags_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn data_maps_count_byte_range(&self) -> Range { + let start = self.reserved_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn data_maps_byte_range(&self) -> Range { + let start = self.data_maps_count_byte_range().end; + start..start + self.data_maps_byte_len + } +} + +impl TopLevelTable for Meta<'_> { + /// `meta` + const TAG: Tag = Tag::new(b"meta"); +} + +impl<'a> FontRead<'a> for Meta<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let data_maps_count: u32 = cursor.read()?; + let data_maps_byte_len = (data_maps_count as usize) + .checked_mul(DataMapRecord::RAW_BYTE_LEN) + .ok_or(ReadError::OutOfBounds)?; + cursor.advance_by(data_maps_byte_len); + cursor.finish(MetaMarker { data_maps_byte_len }) + } +} + +/// [`meta`](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) +pub type Meta<'a> = TableRef<'a, MetaMarker>; + +impl<'a> Meta<'a> { + /// Version number of the metadata table — set to 1. + pub fn version(&self) -> u32 { + let range = self.shape.version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Flags — currently unused; set to 0. + pub fn flags(&self) -> u32 { + let range = self.shape.flags_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// The number of data maps in the table. + pub fn data_maps_count(&self) -> u32 { + let range = self.shape.data_maps_count_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Array of data map records. + pub fn data_maps(&self) -> &'a [DataMapRecord] { + let range = self.shape.data_maps_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Meta<'a> { + fn type_name(&self) -> &str { + "Meta" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("version", self.version())), + 1usize => Some(Field::new("flags", self.flags())), + 2usize => Some(Field::new("data_maps_count", self.data_maps_count())), + 3usize => Some(Field::new( + "data_maps", + traversal::FieldType::array_of_records( + stringify!(DataMapRecord), + self.data_maps(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> std::fmt::Debug for Meta<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +/// https://learn.microsoft.com/en-us/typography/opentype/spec/meta#table-formats +#[derive(Clone, Debug, Copy, bytemuck :: AnyBitPattern)] +#[repr(C)] +#[repr(packed)] +pub struct DataMapRecord { + /// A tag indicating the type of metadata. + pub tag: BigEndian, + /// Offset in bytes from the beginning of the metadata table to the data for this tag. + pub data_offset: BigEndian, + /// Length of the data, in bytes. The data is not required to be padded to any byte boundary. + pub data_length: BigEndian, +} + +impl DataMapRecord { + /// A tag indicating the type of metadata. + pub fn tag(&self) -> Tag { + self.tag.get() + } + + /// Offset in bytes from the beginning of the metadata table to the data for this tag. + pub fn data_offset(&self) -> Offset32 { + self.data_offset.get() + } + + /// Length of the data, in bytes. The data is not required to be padded to any byte boundary. + pub fn data_length(&self) -> u32 { + self.data_length.get() + } +} + +impl FixedSize for DataMapRecord { + const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN + u32::RAW_BYTE_LEN; +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for DataMapRecord { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "DataMapRecord", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("tag", self.tag())), + 1usize => Some(Field::new( + "data_offset", + FieldType::offset_to_array_of_scalars(self.data_offset(), self.data(_data)), + )), + 2usize => Some(Field::new("data_length", self.data_length())), + _ => None, + }), + data, + } + } +} diff --git a/read-fonts/src/table_provider.rs b/read-fonts/src/table_provider.rs index efb043911..6a9191a86 100644 --- a/read-fonts/src/table_provider.rs +++ b/read-fonts/src/table_provider.rs @@ -212,6 +212,10 @@ pub trait TableProvider<'a> { self.expect_data_for_tag(tables::ift::IFTX_TAG) .and_then(FontRead::read) } + + fn meta(&self) -> Result, ReadError> { + self.expect_table() + } } #[cfg(test)] diff --git a/read-fonts/src/tables.rs b/read-fonts/src/tables.rs index 9825cb332..9153ef8d1 100644 --- a/read-fonts/src/tables.rs +++ b/read-fonts/src/tables.rs @@ -33,6 +33,7 @@ pub mod layout; pub mod loca; pub mod ltag; pub mod maxp; +pub mod meta; pub mod mvar; pub mod name; pub mod os2; diff --git a/read-fonts/src/tables/meta.rs b/read-fonts/src/tables/meta.rs new file mode 100644 index 000000000..d5ba62f16 --- /dev/null +++ b/read-fonts/src/tables/meta.rs @@ -0,0 +1,15 @@ +//! The [meta (Metadata)](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) table + +include!("../../generated/generated_meta.rs"); + +impl DataMapRecord { + /// The data under this record, interpreted from length and offset. + pub fn data<'a>(&self, data: FontData<'a>) -> Result<&'a [u8], ReadError> { + let start = self.data_offset().to_usize(); + let end = start + self.data_length() as usize; + + data.as_bytes() + .get(start..end) + .ok_or(ReadError::OutOfBounds) + } +} diff --git a/resources/codegen_inputs/meta.rs b/resources/codegen_inputs/meta.rs new file mode 100644 index 000000000..2b5a13c42 --- /dev/null +++ b/resources/codegen_inputs/meta.rs @@ -0,0 +1,34 @@ +#![parse_module(read_fonts::tables::meta)] + +/// [`meta`](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) +#[tag = "meta"] +table Meta { + /// Version number of the metadata table — set to 1. + #[compile(1)] + version: u32, + /// Flags — currently unused; set to 0. + #[compile(0)] + flags: u32, + /// Not used; set to 0. + #[skip_getter] + #[compile(0)] + reserved: u32, + /// The number of data maps in the table. + data_maps_count: u32, + /// Array of data map records. + #[count($data_maps_count)] + data_maps: [DataMapRecord], +} + +/// https://learn.microsoft.com/en-us/typography/opentype/spec/meta#table-formats +record DataMapRecord { + /// A tag indicating the type of metadata. + tag: Tag, + /// Offset in bytes from the beginning of the metadata table to the data for this tag. + #[offset_getter(data)] + #[compile_with(compile_map_value)] + data_offset: Offset32<[u8]>, + /// Length of the data, in bytes. The data is not required to be padded to any byte boundary. + #[compile(skip)] + data_length: u32, +} diff --git a/resources/codegen_plan.toml b/resources/codegen_plan.toml index ee74d746c..533c2ff1e 100644 --- a/resources/codegen_plan.toml +++ b/resources/codegen_plan.toml @@ -228,6 +228,16 @@ mode = "compile" source = "resources/codegen_inputs/maxp.rs" target = "write-fonts/generated/generated_maxp.rs" +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/meta.rs" +target = "read-fonts/generated/generated_meta.rs" + +[[generate]] +mode = "compile" +source = "resources/codegen_inputs/meta.rs" +target = "write-fonts/generated/generated_meta.rs" + [[generate]] mode = "parse" source = "resources/codegen_inputs/stat.rs" diff --git a/write-fonts/generated/generated_meta.rs b/write-fonts/generated/generated_meta.rs new file mode 100644 index 000000000..41fae6693 --- /dev/null +++ b/write-fonts/generated/generated_meta.rs @@ -0,0 +1,119 @@ +// THIS FILE IS AUTOGENERATED. +// Any changes to this file will be overwritten. +// For more information about how codegen works, see font-codegen/README.md + +#[allow(unused_imports)] +use crate::codegen_prelude::*; + +/// [`meta`](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Meta { + /// The number of data maps in the table. + pub data_maps_count: u32, + /// Array of data map records. + pub data_maps: Vec, +} + +impl Meta { + /// Construct a new `Meta` + pub fn new(data_maps_count: u32, data_maps: Vec) -> Self { + Self { + data_maps_count, + data_maps: data_maps.into_iter().map(Into::into).collect(), + } + } +} + +impl FontWrite for Meta { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + (1 as u32).write_into(writer); + (0 as u32).write_into(writer); + (0 as u32).write_into(writer); + self.data_maps_count.write_into(writer); + self.data_maps.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::TopLevel(Meta::TAG) + } +} + +impl Validate for Meta { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Meta", |ctx| { + ctx.in_field("data_maps", |ctx| { + if self.data_maps.len() > (u32::MAX as usize) { + ctx.report("array exceeds max length"); + } + self.data_maps.validate_impl(ctx); + }); + }) + } +} + +impl TopLevelTable for Meta { + const TAG: Tag = Tag::new(b"meta"); +} + +impl<'a> FromObjRef> for Meta { + fn from_obj_ref(obj: &read_fonts::tables::meta::Meta<'a>, _: FontData) -> Self { + let offset_data = obj.offset_data(); + Meta { + data_maps_count: obj.data_maps_count(), + data_maps: obj.data_maps().to_owned_obj(offset_data), + } + } +} + +impl<'a> FromTableRef> for Meta {} + +impl<'a> FontRead<'a> for Meta { + fn read(data: FontData<'a>) -> Result { + ::read(data).map(|x| x.to_owned_table()) + } +} + +/// https://learn.microsoft.com/en-us/typography/opentype/spec/meta#table-formats +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DataMapRecord { + /// A tag indicating the type of metadata. + pub tag: Tag, + /// Offset in bytes from the beginning of the metadata table to the data for this tag. + pub data: OffsetMarker, WIDTH_32>, +} + +impl DataMapRecord { + /// Construct a new `DataMapRecord` + pub fn new(tag: Tag, data: Vec) -> Self { + Self { + tag, + data: data.into(), + } + } +} + +impl FontWrite for DataMapRecord { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + self.tag.write_into(writer); + (self.compile_map_value()).write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("DataMapRecord") + } +} + +impl Validate for DataMapRecord { + fn validate_impl(&self, _ctx: &mut ValidationCtx) {} +} + +impl FromObjRef for DataMapRecord { + fn from_obj_ref(obj: &read_fonts::tables::meta::DataMapRecord, offset_data: FontData) -> Self { + DataMapRecord { + tag: obj.tag(), + data: obj.data(offset_data).to_owned_obj(offset_data), + } + } +} diff --git a/write-fonts/src/tables.rs b/write-fonts/src/tables.rs index 1444b797d..5ec0b99fc 100644 --- a/write-fonts/src/tables.rs +++ b/write-fonts/src/tables.rs @@ -20,6 +20,7 @@ pub mod ift; pub mod layout; pub mod loca; pub mod maxp; +pub mod meta; pub mod mvar; pub mod name; pub mod os2; @@ -51,6 +52,7 @@ fn do_we_even_serde() { hvar: hvar::Hvar, loca: loca::Loca, maxp: maxp::Maxp, + meta: meta::Meta, name: name::Name, os2: os2::Os2, post: post::Post, diff --git a/write-fonts/src/tables/meta.rs b/write-fonts/src/tables/meta.rs new file mode 100644 index 000000000..7f4f4c23d --- /dev/null +++ b/write-fonts/src/tables/meta.rs @@ -0,0 +1,29 @@ +//! The [meta (Metadata)](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) table + +include!("../../generated/generated_meta.rs"); + +impl DataMapRecord { + /// Required to append a variable length slice of bytes at the end of the + /// table, referenced by length and offset in this record. + fn compile_map_value(&self) -> MapValueAndLenWriter { + MapValueAndLenWriter(self.data.as_slice()) + } +} + +struct MapValueAndLenWriter<'a>(&'a [u8]); + +impl FontWrite for MapValueAndLenWriter<'_> { + fn write_into(&self, writer: &mut TableWriter) { + let length = u32::try_from(self.0.len()).expect("meta record data too long: exceeds u32"); + + writer.write_offset(&self.0, 4); + length.write_into(writer); + } +} + +// TODO: is this necessary? +impl FontWrite for &[u8] { + fn write_into(&self, writer: &mut TableWriter) { + writer.write_slice(self); + } +} From ef7f1f2551170bfb0533ca05f6ca184863d18680 Mon Sep 17 00:00:00 2001 From: Hoolean Date: Tue, 12 Nov 2024 22:13:14 +0000 Subject: [PATCH 2/2] Derive `data_maps_count` from length of `data_maps` on write --- resources/codegen_inputs/meta.rs | 1 + write-fonts/generated/generated_meta.rs | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/resources/codegen_inputs/meta.rs b/resources/codegen_inputs/meta.rs index 2b5a13c42..ec43b5d75 100644 --- a/resources/codegen_inputs/meta.rs +++ b/resources/codegen_inputs/meta.rs @@ -14,6 +14,7 @@ table Meta { #[compile(0)] reserved: u32, /// The number of data maps in the table. + #[compile(array_len($data_maps))] data_maps_count: u32, /// Array of data map records. #[count($data_maps_count)] diff --git a/write-fonts/generated/generated_meta.rs b/write-fonts/generated/generated_meta.rs index 41fae6693..edef6fe5d 100644 --- a/write-fonts/generated/generated_meta.rs +++ b/write-fonts/generated/generated_meta.rs @@ -9,17 +9,14 @@ use crate::codegen_prelude::*; #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Meta { - /// The number of data maps in the table. - pub data_maps_count: u32, /// Array of data map records. pub data_maps: Vec, } impl Meta { /// Construct a new `Meta` - pub fn new(data_maps_count: u32, data_maps: Vec) -> Self { + pub fn new(data_maps: Vec) -> Self { Self { - data_maps_count, data_maps: data_maps.into_iter().map(Into::into).collect(), } } @@ -31,7 +28,7 @@ impl FontWrite for Meta { (1 as u32).write_into(writer); (0 as u32).write_into(writer); (0 as u32).write_into(writer); - self.data_maps_count.write_into(writer); + (array_len(&self.data_maps).unwrap() as u32).write_into(writer); self.data_maps.write_into(writer); } fn table_type(&self) -> TableType { @@ -60,7 +57,6 @@ impl<'a> FromObjRef> for Meta { fn from_obj_ref(obj: &read_fonts::tables::meta::Meta<'a>, _: FontData) -> Self { let offset_data = obj.offset_data(); Meta { - data_maps_count: obj.data_maps_count(), data_maps: obj.data_maps().to_owned_obj(offset_data), } }