diff --git a/docs/codegen-tour.md b/docs/codegen-tour.md index 5bdf73e1d..3bb57296d 100644 --- a/docs/codegen-tour.md +++ b/docs/codegen-tour.md @@ -621,7 +621,7 @@ has a size which can be computed at runtime from some set of arguments. Records, like tables, can contain offsets. Unlike tables, records do not have access to the raw data against which those offsets should be resolved. For the -purpose of consistency across our geneerated code, however, it *is* important +purpose of consistency across our generated code, however, it *is* important that we have a consistent way of resolving offsets contained in records, and we do: you have to pass it in. @@ -637,15 +637,33 @@ The equivalent getter on a record looks like, fn coverage(&self, data: FontData<'a>) -> Result, ReadError>; ``` -This... honestly, this is not great ergonomics. It is, however, simple, and is -relied on by codegen in various places, and when we're generating code we aren't -too bothered by how ergonomic it is. We might want to revisit this at some -point; one simple improvement would be to have the caller pass in the parent -table, but I'm not sure how this would work in cases where a type might be -referenced by multiple parents. Another option would be to have some kind of -fancy `RecordData` struct that would be a thin wrapper around a record plus the -parent data, and which would implement the record getters, but deref to the -record otherwise.... I'm really not sure. +This is not great ergonomics, and it is easy to misuse: nothing stops you from +passing the *wrong* table's data, in which case you silently get a +wrong-but-plausible answer. To address this, table fields containing arrays of +offset-bearing fixed-size records do not return a bare `&[T]`; they return the +`ArrayOfRecordsWithOffsetData` type, which pairs the records with the data of +the enclosing table. This is a vec-like type (`len`/`get`/`iter`, with an +`as_slice` escape hatch) whose items are `OffsetResolving`: a thin wrapper +around a record plus the parent data. For each offset field in the record, we +generate a no-argument getter on `OffsetResolving` that resolves the offset +against the stored data; for everything else it derefs to the record. + +```rust +impl<'a> OffsetResolving<'a, ScriptRecord> { + pub fn script(&self) -> Result, ReadError> { + self.record().script(self.offset_data()) + } +} +``` + +The data-taking getters on the record itself remain (codegen and some callers +rely on them), but most code should never need to supply `FontData` by hand. +Records whose offsets resolve against something other than the parent table +(those using the `#[offset_getter]` attribute, like `NameRecord`) are excluded +and keep the plain `&[T]` representation. This mechanism covers *fixed-size* +records, which are lazily byte-cast and so cannot store the parent data +themselves; for non-fixed-size records (which are instantiated on read) the +plan is for the record to hold the parent data internally. ### arrays @@ -655,7 +673,9 @@ the size and contents of the array: - if the contents of an array have a fixed uniform size, known at compile time, then we represent the array as a rust slice: `&[T]`. This is true for all scalars (including offsets) as well as records that are composed of a fixed number of - scalars. + scalars. (For fixed-size records that contain offsets, the table getter wraps + the slice in `ArrayOfRecordsWithOffsetData`; see [offsets in + records](#offsets-in-records).) - if the contents of an array have a uniform size, but the size can only be determined at runtime, we represent the array using the [`ComputedArray`][] type. This requires the inner type to implement [`FontRead`][] with a non-empty diff --git a/font-codegen/src/fields.rs b/font-codegen/src/fields.rs index 65038df40..f5930f4cc 100644 --- a/font-codegen/src/fields.rs +++ b/font-codegen/src/fields.rs @@ -375,11 +375,15 @@ fn traversal_arm_for_field( let offset_data = pass_data .cloned() .unwrap_or_else(|| fld.offset_getter_data_src()); + // the wrapper is not a slice, but carries one + let maybe_as_slice = fld + .array_of_offset_bearing_records + .then(|| quote!(.as_slice())); quote!(Field::new( #name_str, traversal::FieldType::array_of_records( stringify!(#typ), - self.#name()#maybe_try, + self.#name()#maybe_try #maybe_as_slice, #offset_data, ) )) @@ -663,6 +667,9 @@ impl Field { let be = big_endian(typ); quote!(&'a [#be]) } + FieldType::Struct { typ } if self.array_of_offset_bearing_records => { + quote!(ArrayOfRecordsWithOffsetData<'a, #typ>) + } FieldType::Struct { typ } => quote!(&'a [#typ]), FieldType::PendingResolution { typ } => quote!( &'a [#typ] ), _ => unreachable!("An array should never contain {:#?}", inner_typ), @@ -732,7 +739,18 @@ impl Field { } else if is_var_array { quote!( self.data.split_off(range.start).and_then(|d| VarLenArray::read(d).ok()) #maybe_unwrap_or_def ) } else if is_array { - quote!(self.data.read_array(range).ok() #maybe_unwrap #maybe_unwrap_or_def) + if self.array_of_offset_bearing_records { + // pair the records with the table's data, so that they can + // resolve their offsets without being passed it + let data_src = self.offset_getter_data_src(); + quote! { + self.data.read_array(range).ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, #data_src)) + #maybe_unwrap #maybe_unwrap_or_def + } + } else { + quote!(self.data.read_array(range).ok() #maybe_unwrap #maybe_unwrap_or_def) + } } else { quote!(self.data.read_at(range.start).ok() #maybe_unwrap #maybe_unwrap_or_def) }; @@ -924,6 +942,39 @@ impl Field { } } + /// A getter on `OffsetResolving` that resolves this offset field + /// against the stored data. + /// + /// This is the same getter generated by [`typed_offset_field_getter`] for + /// the record itself, minus the `data` argument (to which it delegates, + /// passing the stored data). + /// + /// [`typed_offset_field_getter`]: Self::typed_offset_field_getter + pub(crate) fn offset_resolving_getter(&self, record_name: &syn::Ident) -> Option { + let target = match &self.typ { + _ if self.attrs.offset_getter.is_some() => return None, + FieldType::Offset { target, .. } => target, + // a zerocopy record cannot contain an array + _ => return None, + }; + let getter_name = self.offset_getter_name().unwrap(); + let mut return_type = target.getter_return_type(false); + if self.is_nullable() { + return_type = quote!(Option<#return_type>); + } + let raw_name = &self.name; + let docs = format!( + " Attempt to resolve [`{raw_name}`][{record_name}::{raw_name}] \ + against the data of the enclosing table." + ); + Some(quote! { + #[doc = #docs] + pub fn #getter_name(&self) -> #return_type { + self.record().#getter_name(self.offset_data()) + } + }) + } + fn is_count(&self) -> bool { self.attrs.count.is_some() } @@ -1305,7 +1356,11 @@ impl Field { } FieldType::Array { inner_typ } => match inner_typ.as_ref() { FieldType::Scalar { .. } | FieldType::Struct { .. } => { - quote!(obj.#name().to_owned_obj(offset_data)) + // the wrapper is not a slice, but carries one + let maybe_as_slice = self + .array_of_offset_bearing_records + .then(|| quote!(.as_slice())); + quote!(obj.#name()#maybe_as_slice.to_owned_obj(offset_data)) } FieldType::Offset { .. } => { let offset_getter = self.offset_getter_name().unwrap(); diff --git a/font-codegen/src/parsing.rs b/font-codegen/src/parsing.rs index 3f2ffeae6..4d5bc6e18 100644 --- a/font-codegen/src/parsing.rs +++ b/font-codegen/src/parsing.rs @@ -483,6 +483,38 @@ impl Items { } } + // Now that struct references are resolved, find the table fields that + // are arrays of records with generated offset getters; these are + // wrapped in `ArrayOfRecordsWithOffsetData` so that the records can + // resolve their offsets without being passed the table's data. + // + // Records defined in another module (`extern record`) are opaque to + // us, so their arrays stay bare slices. + let offset_bearing_records = self + .items + .values() + .filter_map(|item| match item { + Item::Record(record) if record.has_offset_resolving_getters() => { + Some(record.name.clone()) + } + _ => None, + }) + .collect::>(); + for item in self.iter_mut() { + // only table fields: a record has no data of its own to pair + // an array of records with + let Item::Table(table) = item else { continue }; + for field in table.fields.fields.iter_mut() { + if let FieldType::Array { inner_typ } = &field.typ { + if matches!(inner_typ.as_ref(), + FieldType::Struct { typ } if offset_bearing_records.contains(typ)) + { + field.array_of_offset_bearing_records = true; + } + } + } + } + Ok(()) } diff --git a/font-codegen/src/parsing/fields.rs b/font-codegen/src/parsing/fields.rs index a7110ab55..a65038ea0 100644 --- a/font-codegen/src/parsing/fields.rs +++ b/font-codegen/src/parsing/fields.rs @@ -35,6 +35,15 @@ pub(crate) struct Field { /// These fields must be present, which means reads can unwrap (and could even /// be unsafe.) pub(crate) validated_at_parse: bool, + /// `true` for a table field that is an array of records with generated + /// offset getters. + /// + /// Such fields are wrapped in `ArrayOfRecordsWithOffsetData`, which pairs + /// the records with the table's data so that their offsets can be resolved + /// without the caller needing to pass that data in. + /// + /// Like `validated_at_parse`, this is computed during resolution. + pub(crate) array_of_offset_bearing_records: bool, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -168,6 +177,7 @@ impl Parse for Field { typ, // computed later validated_at_parse: false, + array_of_offset_bearing_records: false, }) } } diff --git a/font-codegen/src/record.rs b/font-codegen/src/record.rs index 3d8e97f85..20bde85dd 100644 --- a/font-codegen/src/record.rs +++ b/font-codegen/src/record.rs @@ -48,6 +48,7 @@ pub(crate) fn generate(item: &Record, all_items: &Items) -> syn::Result syn::Result` +/// that resolve those offsets against the stored data. +fn generate_offset_resolving_impl(item: &Record) -> Option { + if !item.has_offset_resolving_getters() { + return None; + } + let name = &item.name; + let getters = item + .fields + .iter() + .filter_map(|fld| fld.offset_resolving_getter(name)) + .collect::>(); + Some(quote! { + impl<'a> OffsetResolving<'a, #name> { + #( #getters )* + } + }) +} + fn generate_read_with_args(item: &Record) -> TokenStream { assert!(item.attrs.read_args.is_some()); // expected this to be checked already // @@ -393,6 +414,22 @@ impl Record { self.fields.iter().all(Field::is_zerocopy_compatible) } + /// `true` if this is a fixed-size record with at least one generated + /// offset getter. + /// + /// Arrays of such records are wrapped in `ArrayOfRecordsWithOffsetData`, + /// and the record gets resolving getters on `OffsetResolving`. + /// Offsets with an `#[offset_getter]` attribute don't count: their getters + /// are written by hand, and codegen cannot know what data they resolve + /// against (`NameRecord` resolves against a separate storage area, for + /// instance). + pub(crate) fn has_offset_resolving_getters(&self) -> bool { + self.is_zerocopy() + && self.fields.iter().any(|fld| { + matches!(fld.typ, FieldType::Offset { .. }) && fld.attrs.offset_getter.is_none() + }) + } + fn gets_extra_traits(&self, all_items: &Items) -> bool { self.fields .iter() diff --git a/read-fonts/generated/generated_base.rs b/read-fonts/generated/generated_base.rs index 60cf12b29..86a4993fe 100644 --- a/read-fonts/generated/generated_base.rs +++ b/read-fonts/generated/generated_base.rs @@ -411,9 +411,13 @@ impl<'a> BaseScriptList<'a> { /// Array of BaseScriptRecords, in alphabetical order by /// baseScriptTag - pub fn base_script_records(&self) -> &'a [BaseScriptRecord] { + pub fn base_script_records(&self) -> ArrayOfRecordsWithOffsetData<'a, BaseScriptRecord> { let range = self.base_script_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn base_script_count_byte_range(&self) -> Range { @@ -454,7 +458,7 @@ impl<'a> SomeTable<'a> for BaseScriptList<'a> { "base_script_records", traversal::FieldType::array_of_records( stringify!(BaseScriptRecord), - self.base_script_records(), + self.base_script_records().as_slice(), self.offset_data(), ), )), @@ -506,6 +510,13 @@ impl FixedSize for BaseScriptRecord { const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, BaseScriptRecord> { + /// Attempt to resolve [`base_script_offset`][BaseScriptRecord::base_script_offset] against the data of the enclosing table. + pub fn base_script(&self) -> Result, ReadError> { + self.record().base_script(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for BaseScriptRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -592,9 +603,13 @@ impl<'a> BaseScript<'a> { /// Array of BaseLangSysRecords, in alphabetical order by /// BaseLangSysTag - pub fn base_lang_sys_records(&self) -> &'a [BaseLangSysRecord] { + pub fn base_lang_sys_records(&self) -> ArrayOfRecordsWithOffsetData<'a, BaseLangSysRecord> { let range = self.base_lang_sys_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn base_values_offset_byte_range(&self) -> Range { @@ -658,7 +673,7 @@ impl<'a> SomeTable<'a> for BaseScript<'a> { "base_lang_sys_records", traversal::FieldType::array_of_records( stringify!(BaseLangSysRecord), - self.base_lang_sys_records(), + self.base_lang_sys_records().as_slice(), self.offset_data(), ), )), @@ -710,6 +725,13 @@ impl FixedSize for BaseLangSysRecord { const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, BaseLangSysRecord> { + /// Attempt to resolve [`min_max_offset`][BaseLangSysRecord::min_max_offset] against the data of the enclosing table. + pub fn min_max(&self) -> Result, ReadError> { + self.record().min_max(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for BaseLangSysRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -923,9 +945,13 @@ impl<'a> MinMax<'a> { /// Array of FeatMinMaxRecords, in alphabetical order by /// featureTableTag - pub fn feat_min_max_records(&self) -> &'a [FeatMinMaxRecord] { + pub fn feat_min_max_records(&self) -> ArrayOfRecordsWithOffsetData<'a, FeatMinMaxRecord> { let range = self.feat_min_max_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn min_coord_offset_byte_range(&self) -> Range { @@ -986,7 +1012,7 @@ impl<'a> SomeTable<'a> for MinMax<'a> { "feat_min_max_records", traversal::FieldType::array_of_records( stringify!(FeatMinMaxRecord), - self.feat_min_max_records(), + self.feat_min_max_records().as_slice(), self.offset_data(), ), )), @@ -1061,6 +1087,18 @@ impl FixedSize for FeatMinMaxRecord { const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, FeatMinMaxRecord> { + /// Attempt to resolve [`min_coord_offset`][FeatMinMaxRecord::min_coord_offset] against the data of the enclosing table. + pub fn min_coord(&self) -> Option, ReadError>> { + self.record().min_coord(self.offset_data()) + } + + /// Attempt to resolve [`max_coord_offset`][FeatMinMaxRecord::max_coord_offset] against the data of the enclosing table. + pub fn max_coord(&self) -> Option, ReadError>> { + self.record().max_coord(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for FeatMinMaxRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_bitmap.rs b/read-fonts/generated/generated_bitmap.rs index 94b60d6ba..d948721f8 100644 --- a/read-fonts/generated/generated_bitmap.rs +++ b/read-fonts/generated/generated_bitmap.rs @@ -792,9 +792,13 @@ impl<'a> IndexSubtableList<'a> { basic_table_impls!(impl_the_methods); /// Array of IndexSubtableRecords. - pub fn index_subtable_records(&self) -> &'a [IndexSubtableRecord] { + pub fn index_subtable_records(&self) -> ArrayOfRecordsWithOffsetData<'a, IndexSubtableRecord> { let range = self.index_subtable_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub(crate) fn number_of_index_subtables(&self) -> u32 { @@ -836,7 +840,7 @@ impl<'a> SomeTable<'a> for IndexSubtableList<'a> { "index_subtable_records", traversal::FieldType::array_of_records( stringify!(IndexSubtableRecord), - self.index_subtable_records(), + self.index_subtable_records().as_slice(), self.offset_data(), ), )), @@ -896,6 +900,13 @@ impl FixedSize for IndexSubtableRecord { GlyphId16::RAW_BYTE_LEN + GlyphId16::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, IndexSubtableRecord> { + /// Attempt to resolve [`index_subtable_offset`][IndexSubtableRecord::index_subtable_offset] against the data of the enclosing table. + pub fn index_subtable(&self) -> Result, ReadError> { + self.record().index_subtable(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for IndexSubtableRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_cmap.rs b/read-fonts/generated/generated_cmap.rs index 89c7bcaef..23bb5dd2e 100644 --- a/read-fonts/generated/generated_cmap.rs +++ b/read-fonts/generated/generated_cmap.rs @@ -57,9 +57,13 @@ impl<'a> Cmap<'a> { self.data.read_at(range.start).ok().unwrap() } - pub fn encoding_records(&self) -> &'a [EncodingRecord] { + pub fn encoding_records(&self) -> ArrayOfRecordsWithOffsetData<'a, EncodingRecord> { let range = self.encoding_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn version_byte_range(&self) -> Range { @@ -106,7 +110,7 @@ impl<'a> SomeTable<'a> for Cmap<'a> { "encoding_records", traversal::FieldType::array_of_records( stringify!(EncodingRecord), - self.encoding_records(), + self.encoding_records().as_slice(), self.offset_data(), ), )), @@ -169,6 +173,13 @@ impl FixedSize for EncodingRecord { PlatformId::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, EncodingRecord> { + /// Attempt to resolve [`subtable_offset`][EncodingRecord::subtable_offset] against the data of the enclosing table. + pub fn subtable(&self) -> Result, ReadError> { + self.record().subtable(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for EncodingRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -1827,9 +1838,13 @@ impl<'a> Cmap14<'a> { } /// Array of VariationSelector records. - pub fn var_selector(&self) -> &'a [VariationSelector] { + pub fn var_selector(&self) -> ArrayOfRecordsWithOffsetData<'a, VariationSelector> { let range = self.var_selector_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn format_byte_range(&self) -> Range { @@ -1877,7 +1892,7 @@ impl<'a> SomeTable<'a> for Cmap14<'a> { "var_selector", traversal::FieldType::array_of_records( stringify!(VariationSelector), - self.var_selector(), + self.var_selector().as_slice(), self.offset_data(), ), )), @@ -1954,6 +1969,18 @@ impl FixedSize for VariationSelector { Uint24::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, VariationSelector> { + /// Attempt to resolve [`default_uvs_offset`][VariationSelector::default_uvs_offset] against the data of the enclosing table. + pub fn default_uvs(&self) -> Option, ReadError>> { + self.record().default_uvs(self.offset_data()) + } + + /// Attempt to resolve [`non_default_uvs_offset`][VariationSelector::non_default_uvs_offset] against the data of the enclosing table. + pub fn non_default_uvs(&self) -> Option, ReadError>> { + self.record().non_default_uvs(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for VariationSelector { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_colr.rs b/read-fonts/generated/generated_colr.rs index 038ce33f0..1e71bfaa5 100644 --- a/read-fonts/generated/generated_colr.rs +++ b/read-fonts/generated/generated_colr.rs @@ -455,9 +455,13 @@ impl<'a> BaseGlyphList<'a> { self.data.read_at(range.start).ok().unwrap() } - pub fn base_glyph_paint_records(&self) -> &'a [BaseGlyphPaint] { + pub fn base_glyph_paint_records(&self) -> ArrayOfRecordsWithOffsetData<'a, BaseGlyphPaint> { let range = self.base_glyph_paint_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn num_base_glyph_paint_records_byte_range(&self) -> Range { @@ -501,7 +505,7 @@ impl<'a> SomeTable<'a> for BaseGlyphList<'a> { "base_glyph_paint_records", traversal::FieldType::array_of_records( stringify!(BaseGlyphPaint), - self.base_glyph_paint_records(), + self.base_glyph_paint_records().as_slice(), self.offset_data(), ), )), @@ -553,6 +557,13 @@ impl FixedSize for BaseGlyphPaint { const RAW_BYTE_LEN: usize = GlyphId16::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, BaseGlyphPaint> { + /// Attempt to resolve [`paint_offset`][BaseGlyphPaint::paint_offset] against the data of the enclosing table. + pub fn paint(&self) -> Result, ReadError> { + self.record().paint(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for BaseGlyphPaint { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -718,9 +729,13 @@ impl<'a> ClipList<'a> { } /// Clip records. Sorted by startGlyphID. - pub fn clips(&self) -> &'a [Clip] { + pub fn clips(&self) -> ArrayOfRecordsWithOffsetData<'a, Clip> { let range = self.clips_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn format_byte_range(&self) -> Range { @@ -766,7 +781,7 @@ impl<'a> SomeTable<'a> for ClipList<'a> { "clips", traversal::FieldType::array_of_records( stringify!(Clip), - self.clips(), + self.clips().as_slice(), self.offset_data(), ), )), @@ -826,6 +841,13 @@ impl FixedSize for Clip { GlyphId16::RAW_BYTE_LEN + GlyphId16::RAW_BYTE_LEN + Offset24::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, Clip> { + /// Attempt to resolve [`clip_box_offset`][Clip::clip_box_offset] against the data of the enclosing table. + pub fn clip_box(&self) -> Result, ReadError> { + self.record().clip_box(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for Clip { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_feat.rs b/read-fonts/generated/generated_feat.rs index 438f187ae..92b967b7e 100644 --- a/read-fonts/generated/generated_feat.rs +++ b/read-fonts/generated/generated_feat.rs @@ -60,9 +60,13 @@ impl<'a> Feat<'a> { } /// The feature name array, sorted by feature type. - pub fn names(&self) -> &'a [FeatureName] { + pub fn names(&self) -> ArrayOfRecordsWithOffsetData<'a, FeatureName> { let range = self.names_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn version_byte_range(&self) -> Range { @@ -121,7 +125,7 @@ impl<'a> SomeTable<'a> for Feat<'a> { "names", traversal::FieldType::array_of_records( stringify!(FeatureName), - self.names(), + self.names().as_slice(), self.offset_data(), ), )), @@ -205,6 +209,13 @@ impl FixedSize for FeatureName { + NameId::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, FeatureName> { + /// Attempt to resolve [`setting_table_offset`][FeatureName::setting_table_offset] against the data of the enclosing table. + pub fn setting_table(&self) -> Result, ReadError> { + self.record().setting_table(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for FeatureName { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_gpos.rs b/read-fonts/generated/generated_gpos.rs index 05c1ff20a..1d3bb0b0b 100644 --- a/read-fonts/generated/generated_gpos.rs +++ b/read-fonts/generated/generated_gpos.rs @@ -1154,9 +1154,13 @@ impl<'a> MarkArray<'a> { /// Array of MarkRecords, ordered by corresponding glyphs in the /// associated mark Coverage table. - pub fn mark_records(&self) -> &'a [MarkRecord] { + pub fn mark_records(&self) -> ArrayOfRecordsWithOffsetData<'a, MarkRecord> { let range = self.mark_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn mark_count_byte_range(&self) -> Range { @@ -1196,7 +1200,7 @@ impl<'a> SomeTable<'a> for MarkArray<'a> { "mark_records", traversal::FieldType::array_of_records( stringify!(MarkRecord), - self.mark_records(), + self.mark_records().as_slice(), self.offset_data(), ), )), @@ -1248,6 +1252,13 @@ impl FixedSize for MarkRecord { const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, MarkRecord> { + /// Attempt to resolve [`mark_anchor_offset`][MarkRecord::mark_anchor_offset] against the data of the enclosing table. + pub fn mark_anchor(&self) -> Result, ReadError> { + self.record().mark_anchor(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for MarkRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -2682,9 +2693,13 @@ impl<'a> CursivePosFormat1<'a> { } /// Array of EntryExit records, in Coverage index order. - pub fn entry_exit_record(&self) -> &'a [EntryExitRecord] { + pub fn entry_exit_record(&self) -> ArrayOfRecordsWithOffsetData<'a, EntryExitRecord> { let range = self.entry_exit_record_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn pos_format_byte_range(&self) -> Range { @@ -2744,7 +2759,7 @@ impl<'a> SomeTable<'a> for CursivePosFormat1<'a> { "entry_exit_record", traversal::FieldType::array_of_records( stringify!(EntryExitRecord), - self.entry_exit_record(), + self.entry_exit_record().as_slice(), self.offset_data(), ), )), @@ -2816,6 +2831,18 @@ impl FixedSize for EntryExitRecord { const RAW_BYTE_LEN: usize = Offset16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, EntryExitRecord> { + /// Attempt to resolve [`entry_anchor_offset`][EntryExitRecord::entry_anchor_offset] against the data of the enclosing table. + pub fn entry_anchor(&self) -> Option, ReadError>> { + self.record().entry_anchor(self.offset_data()) + } + + /// Attempt to resolve [`exit_anchor_offset`][EntryExitRecord::exit_anchor_offset] against the data of the enclosing table. + pub fn exit_anchor(&self) -> Option, ReadError>> { + self.record().exit_anchor(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for EntryExitRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_layout.rs b/read-fonts/generated/generated_layout.rs index 53ff5d4da..78a61d6be 100644 --- a/read-fonts/generated/generated_layout.rs +++ b/read-fonts/generated/generated_layout.rs @@ -47,9 +47,13 @@ impl<'a> ScriptList<'a> { } /// Array of ScriptRecords, listed alphabetically by script tag - pub fn script_records(&self) -> &'a [ScriptRecord] { + pub fn script_records(&self) -> ArrayOfRecordsWithOffsetData<'a, ScriptRecord> { let range = self.script_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn script_count_byte_range(&self) -> Range { @@ -89,7 +93,7 @@ impl<'a> SomeTable<'a> for ScriptList<'a> { "script_records", traversal::FieldType::array_of_records( stringify!(ScriptRecord), - self.script_records(), + self.script_records().as_slice(), self.offset_data(), ), )), @@ -141,6 +145,13 @@ impl FixedSize for ScriptRecord { const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, ScriptRecord> { + /// Attempt to resolve [`script_offset`][ScriptRecord::script_offset] against the data of the enclosing table. + pub fn script(&self) -> Result, ReadError> { + self.record().script(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for ScriptRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -215,9 +226,13 @@ impl<'a> Script<'a> { } /// Array of LangSysRecords, listed alphabetically by LangSys tag - pub fn lang_sys_records(&self) -> &'a [LangSysRecord] { + pub fn lang_sys_records(&self) -> ArrayOfRecordsWithOffsetData<'a, LangSysRecord> { let range = self.lang_sys_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn default_lang_sys_offset_byte_range(&self) -> Range { @@ -267,7 +282,7 @@ impl<'a> SomeTable<'a> for Script<'a> { "lang_sys_records", traversal::FieldType::array_of_records( stringify!(LangSysRecord), - self.lang_sys_records(), + self.lang_sys_records().as_slice(), self.offset_data(), ), )), @@ -318,6 +333,13 @@ impl FixedSize for LangSysRecord { const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, LangSysRecord> { + /// Attempt to resolve [`lang_sys_offset`][LangSysRecord::lang_sys_offset] against the data of the enclosing table. + pub fn lang_sys(&self) -> Result, ReadError> { + self.record().lang_sys(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for LangSysRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -500,9 +522,13 @@ impl<'a> FeatureList<'a> { /// Array of FeatureRecords — zero-based (first feature has /// FeatureIndex = 0), listed alphabetically by feature tag - pub fn feature_records(&self) -> &'a [FeatureRecord] { + pub fn feature_records(&self) -> ArrayOfRecordsWithOffsetData<'a, FeatureRecord> { let range = self.feature_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn feature_count_byte_range(&self) -> Range { @@ -542,7 +568,7 @@ impl<'a> SomeTable<'a> for FeatureList<'a> { "feature_records", traversal::FieldType::array_of_records( stringify!(FeatureRecord), - self.feature_records(), + self.feature_records().as_slice(), self.offset_data(), ), )), @@ -595,6 +621,13 @@ impl FixedSize for FeatureRecord { const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, FeatureRecord> { + /// Attempt to resolve [`feature_offset`][FeatureRecord::feature_offset] against the data of the enclosing table. + pub fn feature(&self) -> Result, ReadError> { + self.record().feature(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for FeatureRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -4616,9 +4649,15 @@ impl<'a> FeatureVariations<'a> { } /// Array of feature variation records. - pub fn feature_variation_records(&self) -> &'a [FeatureVariationRecord] { + pub fn feature_variation_records( + &self, + ) -> ArrayOfRecordsWithOffsetData<'a, FeatureVariationRecord> { let range = self.feature_variation_records_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn version_byte_range(&self) -> Range { @@ -4671,7 +4710,7 @@ impl<'a> SomeTable<'a> for FeatureVariations<'a> { "feature_variation_records", traversal::FieldType::array_of_records( stringify!(FeatureVariationRecord), - self.feature_variation_records(), + self.feature_variation_records().as_slice(), self.offset_data(), ), )), @@ -4743,6 +4782,20 @@ impl FixedSize for FeatureVariationRecord { const RAW_BYTE_LEN: usize = Offset32::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, FeatureVariationRecord> { + /// Attempt to resolve [`condition_set_offset`][FeatureVariationRecord::condition_set_offset] against the data of the enclosing table. + pub fn condition_set(&self) -> Option, ReadError>> { + self.record().condition_set(self.offset_data()) + } + + /// Attempt to resolve [`feature_table_substitution_offset`][FeatureVariationRecord::feature_table_substitution_offset] against the data of the enclosing table. + pub fn feature_table_substitution( + &self, + ) -> Option, ReadError>> { + self.record().feature_table_substitution(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for FeatureVariationRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_meta.rs b/read-fonts/generated/generated_meta.rs index e0076843d..ff5ee522a 100644 --- a/read-fonts/generated/generated_meta.rs +++ b/read-fonts/generated/generated_meta.rs @@ -65,9 +65,13 @@ impl<'a> Meta<'a> { } /// Array of data map records. - pub fn data_maps(&self) -> &'a [DataMapRecord] { + pub fn data_maps(&self) -> ArrayOfRecordsWithOffsetData<'a, DataMapRecord> { let range = self.data_maps_byte_range(); - self.data.read_array(range).ok().unwrap_or_default() + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() } pub fn version_byte_range(&self) -> Range { @@ -127,7 +131,7 @@ impl<'a> SomeTable<'a> for Meta<'a> { "data_maps", traversal::FieldType::array_of_records( stringify!(DataMapRecord), - self.data_maps(), + self.data_maps().as_slice(), self.offset_data(), ), )), @@ -187,6 +191,13 @@ impl FixedSize for DataMapRecord { const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN + u32::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, DataMapRecord> { + /// Attempt to resolve [`data_offset`][DataMapRecord::data_offset] against the data of the enclosing table. + pub fn data(&self) -> Result, ReadError> { + self.record().data(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for DataMapRecord { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { diff --git a/read-fonts/generated/generated_test_records.rs b/read-fonts/generated/generated_test_records.rs index e9bf69420..bde49f1a3 100644 --- a/read-fonts/generated/generated_test_records.rs +++ b/read-fonts/generated/generated_test_records.rs @@ -317,6 +317,18 @@ impl FixedSize for ContainsOffsets { const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN; } +impl<'a> OffsetResolving<'a, ContainsOffsets> { + /// Attempt to resolve [`array_offset`][ContainsOffsets::array_offset] against the data of the enclosing table. + pub fn array(&self) -> Result<&'a [SimpleRecord], ReadError> { + self.record().array(self.offset_data()) + } + + /// Attempt to resolve [`other_offset`][ContainsOffsets::other_offset] against the data of the enclosing table. + pub fn other(&self) -> Result, ReadError> { + self.record().other(self.offset_data()) + } +} + #[cfg(feature = "experimental_traverse")] impl<'a> SomeRecord<'a> for ContainsOffsets { fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { @@ -344,6 +356,110 @@ impl<'a> SomeRecord<'a> for ContainsOffsets { } } +impl<'a> MinByteRange<'a> for ContainsOffsetRecords<'a> { + fn min_byte_range(&self) -> Range { + 0..self.offset_records_byte_range().end + } + fn min_table_bytes(&self) -> &'a [u8] { + let range = self.min_byte_range(); + self.data.as_bytes().get(range).unwrap_or_default() + } +} + +impl ReadArgs for ContainsOffsetRecords<'_> { + type Args = (); +} + +impl<'a> FontRead<'a> for ContainsOffsetRecords<'a> { + fn read_with_args(data: FontData<'a>, _: ()) -> Result { + #[allow(clippy::absurd_extreme_comparisons)] + if data.len() < Self::MIN_SIZE { + return Err(ReadError::OutOfBounds); + } + Ok(Self { data }) + } +} + +#[derive(Clone)] +pub struct ContainsOffsetRecords<'a> { + data: FontData<'a>, +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> ContainsOffsetRecords<'a> { + pub const MIN_SIZE: usize = u16::RAW_BYTE_LEN; + basic_table_impls!(impl_the_methods); + + pub fn record_count(&self) -> u16 { + let range = self.record_count_byte_range(); + self.data.read_at(range.start).ok().unwrap() + } + + pub fn offset_records(&self) -> ArrayOfRecordsWithOffsetData<'a, ContainsOffsets> { + let range = self.offset_records_byte_range(); + self.data + .read_array(range) + .ok() + .map(|records| ArrayOfRecordsWithOffsetData::new(records, self.offset_data())) + .unwrap_or_default() + } + + pub fn record_count_byte_range(&self) -> Range { + let start = 0; + let end = start + u16::RAW_BYTE_LEN; + start..end + } + + pub fn offset_records_byte_range(&self) -> Range { + let record_count = self.record_count(); + let start = self.record_count_byte_range().end; + let end = start + + (transforms::to_usize(record_count)).saturating_mul(ContainsOffsets::RAW_BYTE_LEN); + start..end + } +} + +const _: () = assert!(FontData::default_data_long_enough( + ContainsOffsetRecords::MIN_SIZE +)); + +impl Default for ContainsOffsetRecords<'_> { + fn default() -> Self { + Self { + data: FontData::default_table_data(), + } + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for ContainsOffsetRecords<'a> { + fn type_name(&self) -> &str { + "ContainsOffsetRecords" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("record_count", self.record_count())), + 1usize => Some(Field::new( + "offset_records", + traversal::FieldType::array_of_records( + stringify!(ContainsOffsets), + self.offset_records().as_slice(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +#[allow(clippy::needless_lifetimes)] +impl<'a> std::fmt::Debug for ContainsOffsetRecords<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + impl<'a> MinByteRange<'a> for VarLenItem<'a> { fn min_byte_range(&self) -> Range { 0..self.data_byte_range().end diff --git a/read-fonts/src/array.rs b/read-fonts/src/array.rs index 26290aee5..e125437e9 100644 --- a/read-fonts/src/array.rs +++ b/read-fonts/src/array.rs @@ -223,6 +223,194 @@ pub(crate) fn get_pair(slice: &[T], idx: usize) -> Result<&[T; 2], ReadError> .ok_or(ReadError::OutOfBounds) } +/// An array of records paired with the data of the enclosing table. +/// +/// Records may contain offsets, and these offsets are resolved against the +/// data of the table that contains the record; a bare record has no way of +/// getting at that data, which is why the record's generated offset getters +/// require it to be passed in. Bundling the data up with the records lets +/// the items of this array (each an [`OffsetResolving`]) resolve their own +/// offsets, unburdening the user from needing to determine the appropriate +/// input data (and making it impossible to pass the *wrong* data). +/// +/// This is the analog, for arrays of records containing offsets, of +/// [`ArrayOfOffsets`][crate::offset_array::ArrayOfOffsets]. +pub struct ArrayOfRecordsWithOffsetData<'a, T> { + data: FontData<'a>, + records: &'a [T], +} + +impl<'a, T> ArrayOfRecordsWithOffsetData<'a, T> { + pub(crate) fn new(records: &'a [T], data: FontData<'a>) -> Self { + Self { data, records } + } + + /// The number of records in the array. + pub fn len(&self) -> usize { + self.records.len() + } + + pub fn is_empty(&self) -> bool { + self.records.is_empty() + } + + /// Return the record at `idx`, or `None` if it is out of bounds. + pub fn get(&self, idx: usize) -> Option> { + self.records.get(idx).map(|record| OffsetResolving { + data: self.data, + record, + }) + } + + /// Return an iterator over the records in this array. + pub fn iter(&self) -> ArrayOfRecordsIter<'a, T> { + ArrayOfRecordsIter { + data: self.data, + inner: self.records.iter(), + } + } + + /// The records, as a plain slice. + /// + /// This is an escape hatch for slice-only APIs like `binary_search_by`; + /// pair the resulting index with [`get`][Self::get] to recover an + /// offset-resolving record. + pub fn as_slice(&self) -> &'a [T] { + self.records + } + + /// The data of the enclosing table, against which record offsets resolve. + pub fn offset_data(&self) -> FontData<'a> { + self.data + } +} + +impl Clone for ArrayOfRecordsWithOffsetData<'_, T> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for ArrayOfRecordsWithOffsetData<'_, T> {} + +impl Default for ArrayOfRecordsWithOffsetData<'_, T> { + fn default() -> Self { + Self { + data: FontData::default(), + records: &[], + } + } +} + +impl std::fmt::Debug for ArrayOfRecordsWithOffsetData<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_list().entries(self.records).finish() + } +} + +impl<'a, T> IntoIterator for ArrayOfRecordsWithOffsetData<'a, T> { + type Item = OffsetResolving<'a, T>; + type IntoIter = ArrayOfRecordsIter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> IntoIterator for &ArrayOfRecordsWithOffsetData<'a, T> { + type Item = OffsetResolving<'a, T>; + type IntoIter = ArrayOfRecordsIter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// An iterator over an [`ArrayOfRecordsWithOffsetData`]. +pub struct ArrayOfRecordsIter<'a, T> { + data: FontData<'a>, + inner: std::slice::Iter<'a, T>, +} + +impl<'a, T> Iterator for ArrayOfRecordsIter<'a, T> { + type Item = OffsetResolving<'a, T>; + + fn next(&mut self) -> Option { + let record = self.inner.next()?; + Some(OffsetResolving { + data: self.data, + record, + }) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl DoubleEndedIterator for ArrayOfRecordsIter<'_, T> { + fn next_back(&mut self) -> Option { + let record = self.inner.next_back()?; + Some(OffsetResolving { + data: self.data, + record, + }) + } +} + +impl ExactSizeIterator for ArrayOfRecordsIter<'_, T> {} + +impl Clone for ArrayOfRecordsIter<'_, T> { + fn clone(&self) -> Self { + Self { + data: self.data, + inner: self.inner.clone(), + } + } +} + +/// A record paired with the data of the enclosing table. +/// +/// This derefs to the record itself, so all of the record's methods are +/// available; in addition, for each offset in the record, codegen provides +/// a getter *on this type* that resolves the offset without requiring the +/// caller to pass in the enclosing table's data. +pub struct OffsetResolving<'a, T> { + data: FontData<'a>, + record: &'a T, +} + +impl<'a, T> OffsetResolving<'a, T> { + /// The underlying record. + pub fn record(&self) -> &'a T { + self.record + } + + /// The data of the enclosing table, against which offsets resolve. + pub fn offset_data(&self) -> FontData<'a> { + self.data + } +} + +impl std::ops::Deref for OffsetResolving<'_, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + self.record + } +} + +impl Clone for OffsetResolving<'_, T> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for OffsetResolving<'_, T> {} + +impl std::fmt::Debug for OffsetResolving<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.record.fmt(f) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/read-fonts/src/codegen_test.rs b/read-fonts/src/codegen_test.rs index 147e94780..05b8881e0 100644 --- a/read-fonts/src/codegen_test.rs +++ b/read-fonts/src/codegen_test.rs @@ -10,6 +10,89 @@ pub mod records { include!("../generated/generated_test_records.rs"); + + #[cfg(test)] + mod tests { + use super::*; + use font_test_data::bebuffer::BeBuffer; + + fn contains_offset_records_data() -> BeBuffer { + BeBuffer::new() + .push(2u16) // record_count + // two ContainsOffsets records + .extend([1u16, 18]) // off_array_count, array_offset + .push(36u32) // other_offset + .extend([2u16, 24]) + .push(36u32) + // SimpleRecord array at 18: [(1, 2)] + .push(1u16) + .push(2u32) + // SimpleRecord array at 24: [(3, 4), (5, 6)] + .push(3u16) + .push(4u32) + .push(5u16) + .push(6u32) + // empty BasicTable at 36 + .extend([0u16, 0]) + .push(0u32) + } + + #[test] + fn array_of_offset_records_resolvers() { + let buf = contains_offset_records_data(); + let table = ContainsOffsetRecords::read(buf.data().into()).unwrap(); + let records = table.offset_records(); + assert_eq!(records.len(), 2); + assert!(!records.is_empty()); + + // scalar getters pass through via Deref + let counts = records + .iter() + .map(|rec| rec.off_array_count()) + .collect::>(); + assert_eq!(counts, [1, 2]); + + // no-arg resolvers match the old data-taking form + for rec in &records { + assert_eq!( + rec.array().unwrap(), + rec.record().array(table.offset_data()).unwrap() + ); + } + let vals = records + .iter() + .map(|rec| { + rec.array() + .unwrap() + .iter() + .map(|simple| simple.val1()) + .collect::>() + }) + .collect::>(); + assert_eq!(vals, [vec![1], vec![3, 5]]); + + assert_eq!(records.get(1).unwrap().other().unwrap().simple_count(), 0); + assert!(records.get(2).is_none()); + } + + #[test] + fn array_of_offset_records_slice_consistency() { + let buf = contains_offset_records_data(); + let table = ContainsOffsetRecords::read(buf.data().into()).unwrap(); + let records = table.offset_records(); + + let slice = records.as_slice(); + assert_eq!(slice.len(), records.len()); + for (i, raw) in slice.iter().enumerate() { + let wrapped = records.get(i).unwrap(); + assert_eq!(wrapped.record().off_array_count(), raw.off_array_count()); + } + + assert_eq!(records.iter().len(), 2); + let last = records.iter().next_back().unwrap(); + assert_eq!(last.off_array_count(), 2); + } + } } pub mod formats { diff --git a/read-fonts/src/lib.rs b/read-fonts/src/lib.rs index bdb466289..20122254e 100644 --- a/read-fonts/src/lib.rs +++ b/read-fonts/src/lib.rs @@ -103,7 +103,9 @@ pub extern crate font_types as types; /// All the types that may be referenced in auto-generated code. #[doc(hidden)] pub(crate) mod codegen_prelude { - pub use crate::array::{ComputedArray, VarLenArray}; + pub use crate::array::{ + ArrayOfRecordsWithOffsetData, ComputedArray, OffsetResolving, VarLenArray, + }; pub use crate::font_data::{Cursor, FontData}; pub use crate::offset::{Offset, ResolveNullableOffset, ResolveOffset}; pub use crate::offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets}; diff --git a/read-fonts/src/model/font/instance.rs b/read-fonts/src/model/font/instance.rs index f8f2a38ac..d5023c6d0 100644 --- a/read-fonts/src/model/font/instance.rs +++ b/read-fonts/src/model/font/instance.rs @@ -501,7 +501,7 @@ pub(crate) fn feature_variation_index( if rec.condition_set_offset().is_null() { return Some(index as u32); } - let Some(Ok(condition_set)) = rec.condition_set(feature_vars.offset_data()) else { + let Some(Ok(condition_set)) = rec.condition_set() else { continue; }; // Otherwise, all conditions must be satisfied. diff --git a/read-fonts/src/tables/base.rs b/read-fonts/src/tables/base.rs index 3c5a86bdf..9a9cf7704 100644 --- a/read-fonts/src/tables/base.rs +++ b/read-fonts/src/tables/base.rs @@ -48,7 +48,11 @@ mod tests { assert_eq!(base_tag.min_byte_range().end, 14); let base_script = horiz.base_script_list().unwrap(); assert_eq!( - base_script.base_script_records()[3].base_script_tag(), + base_script + .base_script_records() + .get(3) + .unwrap() + .base_script_tag(), Tag::new(b"latn") ); } diff --git a/read-fonts/src/tables/bitmap.rs b/read-fonts/src/tables/bitmap.rs index a2d1486af..390e82fb9 100644 --- a/read-fonts/src/tables/bitmap.rs +++ b/read-fonts/src/tables/bitmap.rs @@ -26,7 +26,7 @@ impl BitmapSize { ..BitmapLocation::default() }; for record in subtable_list.index_subtable_records() { - let subtable = record.index_subtable(subtable_list.offset_data())?; + let subtable = record.index_subtable()?; if !(record.first_glyph_index()..=record.last_glyph_index()).contains(&glyph_id) { continue; } diff --git a/read-fonts/src/tables/cmap.rs b/read-fonts/src/tables/cmap.rs index 5da589584..40046b2de 100644 --- a/read-fonts/src/tables/cmap.rs +++ b/read-fonts/src/tables/cmap.rs @@ -43,7 +43,7 @@ impl<'a> Cmap<'a> { pub fn map_codepoint(&self, codepoint: impl Into) -> Option { let codepoint = codepoint.into(); for record in self.encoding_records() { - if let Ok(subtable) = record.subtable(self.offset_data()) { + if let Ok(subtable) = record.subtable() { if let Some(gid) = subtable.map_codepoint(codepoint) { return Some(gid); } @@ -63,14 +63,13 @@ impl<'a> Cmap<'a> { pub fn best_subtable(&self) -> Option<(u16, EncodingRecord, CmapSubtable<'a>)> { // Follows the HarfBuzz approach // See - let offset_data = self.offset_data(); let records = self.encoding_records(); let find = |platform_id, encoding_id| { for (index, record) in records.iter().enumerate() { if record.platform_id() != platform_id || record.encoding_id() != encoding_id { continue; } - if let Ok(subtable) = record.subtable(offset_data) { + if let Ok(subtable) = record.subtable() { match subtable { CmapSubtable::Format0(_) | CmapSubtable::Format4(_) @@ -110,9 +109,8 @@ impl<'a> Cmap<'a> { /// This is always a [format 14](https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences) /// subtable. pub fn uvs_subtable(&self) -> Option<(u16, Cmap14<'a>)> { - let offset_data = self.offset_data(); for (index, record) in self.encoding_records().iter().enumerate() { - if let Ok(CmapSubtable::Format14(cmap14)) = record.subtable(offset_data) { + if let Ok(CmapSubtable::Format14(cmap14)) = record.subtable() { return Some((index as u16, cmap14)); }; } @@ -124,13 +122,13 @@ impl<'a> Cmap<'a> { self.encoding_records() .get(index as usize) .ok_or(ReadError::OutOfBounds) - .and_then(|encoding| encoding.subtable(self.offset_data())) + .and_then(|encoding| encoding.subtable()) } #[cfg(feature = "std")] pub fn closure_glyphs(&self, unicodes: &IntSet, glyph_set: &mut IntSet) { for record in self.encoding_records() { - if let Ok(subtable) = record.subtable(self.offset_data()) { + if let Ok(subtable) = record.subtable() { match subtable { CmapSubtable::Format14(format14) => { format14.closure_glyphs(unicodes, glyph_set); @@ -763,6 +761,7 @@ impl<'a> Cmap14<'a> { // Variation selector records are sorted in order of var_selector. Binary search to find // the appropriate record. let selector_record = selector_records + .as_slice() .binary_search_by(|rec| { let rec_selector: u32 = rec.var_selector().into(); rec_selector.cmp(&selector) @@ -773,7 +772,7 @@ impl<'a> Cmap14<'a> { // (start_unicode_value, start_unicode_value + additional_count) to find the requested codepoint. // If found, ignore the selector and return a value indicating that the default cmap mapping // should be used. - if let Some(Ok(default_uvs)) = selector_record.default_uvs(self.offset_data()) { + if let Some(Ok(default_uvs)) = selector_record.default_uvs() { use core::cmp::Ordering; let found_default_uvs = default_uvs .ranges() @@ -793,7 +792,7 @@ impl<'a> Cmap14<'a> { } } // Binary search the non-default UVS table if present. This maps codepoint+selector to a variant glyph. - let non_default_uvs = selector_record.non_default_uvs(self.offset_data())?.ok()?; + let non_default_uvs = selector_record.non_default_uvs()?.ok()?; let mapping = non_default_uvs.uvs_mapping(); let ix = mapping .binary_search_by(|map| { @@ -828,12 +827,7 @@ impl<'a> Cmap14<'a> { if !unicodes.contains(selector.var_selector().to_u32()) { continue; } - if let Some(non_default_uvs) = selector - .non_default_uvs(self.offset_data()) - .transpose() - .ok() - .flatten() - { + if let Some(non_default_uvs) = selector.non_default_uvs().transpose().ok().flatten() { glyph_set.extend( non_default_uvs .uvs_mapping() @@ -850,8 +844,7 @@ impl<'a> Cmap14<'a> { /// in the subtable. #[derive(Clone)] pub struct Cmap14Iter<'a> { - offset_data: FontData<'a>, - records: core::slice::Iter<'a, VariationSelector>, + records: crate::array::ArrayOfRecordsIter<'a, VariationSelector>, cur_selector: Option, default_uvs: Option>, non_default_uvs: Option>, @@ -867,7 +860,6 @@ impl<'a> Cmap14Iter<'a> { (u32::MAX, u32::MAX) }; Self { - offset_data: subtable.offset_data(), records: subtable.var_selector().iter(), cur_selector: None, default_uvs: None, @@ -894,13 +886,13 @@ impl<'a> Cmap14Iter<'a> { } self.cur_selector = Some(selector); self.default_uvs = record - .default_uvs(self.offset_data) + .default_uvs() .transpose() .ok() .flatten() .map(DefaultUvsIter::new); self.non_default_uvs = record - .non_default_uvs(self.offset_data) + .non_default_uvs() .transpose() .ok() .flatten() @@ -1680,7 +1672,7 @@ mod tests { fn find_cmap4<'a>(cmap: &Cmap<'a>) -> Option> { cmap.encoding_records() .iter() - .filter_map(|record| record.subtable(cmap.offset_data()).ok()) + .filter_map(|record| record.subtable().ok()) .find_map(|subtable| match subtable { CmapSubtable::Format4(cmap4) => Some(cmap4), _ => None, @@ -1690,7 +1682,7 @@ mod tests { fn find_cmap12<'a>(cmap: &Cmap<'a>) -> Option> { cmap.encoding_records() .iter() - .filter_map(|record| record.subtable(cmap.offset_data()).ok()) + .filter_map(|record| record.subtable().ok()) .find_map(|subtable| match subtable { CmapSubtable::Format12(cmap12) => Some(cmap12), _ => None, @@ -1700,7 +1692,7 @@ mod tests { fn find_cmap14<'a>(cmap: &Cmap<'a>) -> Option> { cmap.encoding_records() .iter() - .filter_map(|record| record.subtable(cmap.offset_data()).ok()) + .filter_map(|record| record.subtable().ok()) .find_map(|subtable| match subtable { CmapSubtable::Format14(cmap14) => Some(cmap14), _ => None, diff --git a/read-fonts/src/tables/colr.rs b/read-fonts/src/tables/colr.rs index c480bdddb..de34711c0 100644 --- a/read-fonts/src/tables/colr.rs +++ b/read-fonts/src/tables/colr.rs @@ -53,15 +53,18 @@ impl<'a> Colr<'a> { }; let list = self.base_glyph_list().ok_or(ReadError::NullOffset)??; let records = list.base_glyph_paint_records(); - let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) { - Ok(ix) => &records[ix], + let record = match records + .as_slice() + .binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) + { + Ok(ix) => records.get(ix).ok_or(ReadError::OutOfBounds)?, _ => return Ok(None), }; - let offset_data = list.offset_data(); // Use the address of the paint as an identifier for the recursion // blacklist. - let id = record.paint_offset().to_u32() as usize + offset_data.as_ref().as_ptr() as usize; - Ok(Some((record.paint(offset_data)?, id))) + let id = record.paint_offset().to_u32() as usize + + record.offset_data().as_ref().as_ptr() as usize; + Ok(Some((record.paint()?, id))) } /// Returns the COLRv1 layer at the given index. @@ -90,7 +93,7 @@ impl<'a> Colr<'a> { }; let list = self.clip_list().ok_or(ReadError::NullOffset)??; let clips = list.clips(); - let clip = match clips.binary_search_by(|clip| { + let clip = match clips.as_slice().binary_search_by(|clip| { if glyph_id < clip.start_glyph_id() { Ordering::Greater } else if glyph_id > clip.end_glyph_id() { @@ -99,9 +102,9 @@ impl<'a> Colr<'a> { Ordering::Equal } }) { - Ok(ix) => &clips[ix], + Ok(ix) => clips.get(ix).ok_or(ReadError::OutOfBounds)?, _ => return Ok(None), }; - Ok(Some(clip.clip_box(list.offset_data())?)) + Ok(Some(clip.clip_box()?)) } } diff --git a/read-fonts/src/tables/colr/closure.rs b/read-fonts/src/tables/colr/closure.rs index 935becf11..2290e3ced 100644 --- a/read-fonts/src/tables/colr/closure.rs +++ b/read-fonts/src/tables/colr/closure.rs @@ -4,7 +4,7 @@ use font_types::{GlyphId, GlyphId16}; use crate::{collections::IntSet, tables::variations::NO_VARIATION_INDEX, ResolveOffset}; use super::{ - Clip, ClipBox, ClipBoxFormat2, ClipList, ColorLine, ColorStop, Colr, Paint, PaintColrGlyph, + Clip, ClipBox, ClipBoxFormat2, ColorLine, ColorStop, Colr, Paint, PaintColrGlyph, PaintColrLayers, PaintComposite, PaintGlyph, PaintLinearGradient, PaintRadialGradient, PaintRotate, PaintRotateAroundCenter, PaintScale, PaintScaleAroundCenter, PaintScaleUniform, PaintScaleUniformAroundCenter, PaintSkew, PaintSkewAroundCenter, PaintSolid, @@ -81,7 +81,6 @@ impl Colr<'_> { Colrv1ClosureContext::new(layer_indices, palette_indices, variation_indices, self); if let Some(Ok(base_glyph_list)) = self.base_glyph_list() { let base_glyph_records = base_glyph_list.base_glyph_paint_records(); - let offset_data = base_glyph_list.offset_data(); let num_records = base_glyph_records.len() as u32; let bit_storage = u32::BITS - num_records.leading_zeros(); if glyph_set.is_inverted() || num_records <= glyph_set.len() as u32 * bit_storage { @@ -90,7 +89,7 @@ impl Colr<'_> { if !glyph_set.contains(GlyphId::from(gid)) { continue; } - if let Ok(paint) = record.paint(offset_data) { + if let Ok(paint) = record.paint() { c.dispatch(&paint); } } @@ -99,16 +98,18 @@ impl Colr<'_> { let Ok(glyph_id) = glyph_id.try_into() else { continue; }; - let record = match base_glyph_records + let Some(record) = base_glyph_records + .as_slice() .binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) - { - Ok(idx) => &base_glyph_records[idx], - _ => continue, + .ok() + .and_then(|idx| base_glyph_records.get(idx)) + else { + continue; }; if record.paint_offset().is_null() { continue; } - if let Ok(paint) = record.paint(offset_data) { + if let Ok(paint) = record.paint() { c.dispatch(&paint); } } @@ -119,7 +120,7 @@ impl Colr<'_> { if let Some(Ok(clip_list)) = self.clip_list() { c.glyph_set.union(glyph_set); for clip_record in clip_list.clips() { - clip_record.v1_closure(&mut c, &clip_list); + clip_record.v1_closure(&mut c); } } } @@ -430,11 +431,15 @@ impl PaintColrGlyph<'_> { return; }; let records = list.base_glyph_paint_records(); - let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) { - Ok(ix) => &records[ix], - _ => return, + let Some(record) = records + .as_slice() + .binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) + .ok() + .and_then(|ix| records.get(ix)) + else { + return; }; - if let Ok(paint) = record.paint(list.offset_data()) { + if let Ok(paint) = record.paint() { c.add_glyph_id(glyph_id); c.dispatch(&paint); } @@ -631,9 +636,9 @@ impl PaintComposite<'_> { } } -impl Clip { - fn v1_closure(&self, c: &mut Colrv1ClosureContext, clip_list: &ClipList) { - let Ok(clip_box) = self.clip_box(clip_list.offset_data()) else { +impl crate::array::OffsetResolving<'_, Clip> { + fn v1_closure(&self, c: &mut Colrv1ClosureContext) { + let Ok(clip_box) = self.clip_box() else { return; }; let start_id = GlyphId::from(self.start_glyph_id()); diff --git a/read-fonts/src/tables/feat.rs b/read-fonts/src/tables/feat.rs index 1a09777f4..071e7a4fc 100644 --- a/read-fonts/src/tables/feat.rs +++ b/read-fonts/src/tables/feat.rs @@ -5,7 +5,7 @@ include!("../../generated/generated_feat.rs"); impl Feat<'_> { /// Returns the name for the given feature code. pub fn find(&self, feature: u16) -> Option { - let names = self.names(); + let names = self.names().as_slice(); let ix = names .binary_search_by(|name| name.feature().cmp(&feature)) .ok()?; @@ -74,7 +74,7 @@ mod tests { let setting_names = names .iter() .map(|name| { - let settings = name.setting_table(feat.offset_data()).unwrap(); + let settings = name.setting_table().unwrap(); settings .settings() .iter() diff --git a/read-fonts/src/tables/layout/closure.rs b/read-fonts/src/tables/layout/closure.rs index c79a52561..04fb678fb 100644 --- a/read-fonts/src/tables/layout/closure.rs +++ b/read-fonts/src/tables/layout/closure.rs @@ -109,23 +109,24 @@ impl ScriptList<'_> { let mut c = CollectFeaturesContext::new(features, layout_table_head, feature_list, &mut out); let script_records = self.script_records(); - let font_data = self.offset_data(); if scripts.is_inverted() { for record in script_records { let tag = record.script_tag(); if !scripts.contains(tag) || record.script_offset().is_null() { continue; } - let script = record.script(font_data)?; + let script = record.script()?; script.collect_features(&mut c, languages)?; } } else { for idx in scripts.iter().filter_map(|tag| self.index_for_tag(tag)) { - let record = script_records[idx as usize]; + let Some(record) = script_records.get(idx as usize) else { + continue; + }; if record.script_offset().is_null() { continue; } - let script = record.script(font_data)?; + let script = record.script()?; script.collect_features(&mut c, languages)?; } } @@ -144,7 +145,6 @@ impl Script<'_> { } let lang_sys_records = self.lang_sys_records(); - let font_data = self.offset_data(); if let Some(default_lang_sys) = self.default_lang_sys().transpose()? { default_lang_sys.collect_features(c); @@ -156,7 +156,7 @@ impl Script<'_> { if !languages.contains(tag) || record.lang_sys_offset().is_null() { continue; } - let lang_sys = record.lang_sys(font_data)?; + let lang_sys = record.lang_sys()?; lang_sys.collect_features(c); } } else { @@ -164,11 +164,13 @@ impl Script<'_> { .iter() .filter_map(|tag| self.lang_sys_index_for_tag(tag)) { - let record = lang_sys_records[idx as usize]; + let Some(record) = lang_sys_records.get(idx as usize) else { + continue; + }; if record.lang_sys_offset().is_null() { continue; } - let lang_sys = record.lang_sys(font_data)?; + let lang_sys = record.lang_sys()?; lang_sys.collect_features(c); } } @@ -225,7 +227,6 @@ impl FeatureList<'_> { ) -> Result, ReadError> { let features_records = self.feature_records(); let num_features = self.feature_count(); - let font_data = self.offset_data(); let mut lookup_idxes = IntSet::empty(); if feature_indices.is_inverted() { @@ -238,7 +239,7 @@ impl FeatureList<'_> { if feature_rec.feature_offset().is_null() { continue; } - lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups()); + lookup_idxes.extend_unsorted(feature_rec.feature()?.collect_lookups()); } } else { for feature_rec in feature_indices @@ -248,7 +249,7 @@ impl FeatureList<'_> { if feature_rec.feature_offset().is_null() { continue; } - lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups()); + lookup_idxes.extend_unsorted(feature_rec.feature()?.collect_lookups()); } } Ok(lookup_idxes) @@ -263,10 +264,7 @@ impl FeatureVariations<'_> { let mut out = IntSet::empty(); for variation_rec in self.feature_variation_records() { - let Some(subs) = variation_rec - .feature_table_substitution(self.offset_data()) - .transpose()? - else { + let Some(subs) = variation_rec.feature_table_substitution().transpose()? else { continue; }; diff --git a/read-fonts/src/tables/layout/feature.rs b/read-fonts/src/tables/layout/feature.rs index 4ba733179..4c4b6996f 100644 --- a/read-fonts/src/tables/layout/feature.rs +++ b/read-fonts/src/tables/layout/feature.rs @@ -8,12 +8,7 @@ impl<'a> FeatureList<'a> { self.feature_records() .get(index as usize) .ok_or(ReadError::OutOfBounds) - .and_then(|rec| { - Ok(TaggedElement::new( - rec.feature_tag(), - rec.feature(self.offset_data())?, - )) - }) + .and_then(|rec| Ok(TaggedElement::new(rec.feature_tag(), rec.feature()?))) } } diff --git a/read-fonts/src/tables/layout/script.rs b/read-fonts/src/tables/layout/script.rs index 196b1e77d..ca5f2ab1f 100644 --- a/read-fonts/src/tables/layout/script.rs +++ b/read-fonts/src/tables/layout/script.rs @@ -20,6 +20,7 @@ impl<'a> ScriptList<'a> { /// Returns the index of the script with the given tag. pub fn index_for_tag(&self, tag: Tag) -> Option { self.script_records() + .as_slice() .binary_search_by_key(&tag, |rec| rec.script_tag()) .map(|index| index as u16) .ok() @@ -30,12 +31,7 @@ impl<'a> ScriptList<'a> { self.script_records() .get(index as usize) .ok_or(ReadError::OutOfBounds) - .and_then(|rec| { - Ok(TaggedElement::new( - rec.script_tag(), - rec.script(self.offset_data())?, - )) - }) + .and_then(|rec| Ok(TaggedElement::new(rec.script_tag(), rec.script()?))) } /// Finds the first available script that matches one of the given tags. @@ -85,6 +81,7 @@ impl<'a> Script<'a> { /// the index. pub fn lang_sys_index_for_tag(&self, tag: Tag) -> Option { self.lang_sys_records() + .as_slice() .binary_search_by_key(&tag, |rec| rec.lang_sys_tag()) .map(|index| index as u16) .ok() @@ -95,12 +92,7 @@ impl<'a> Script<'a> { self.lang_sys_records() .get(index as usize) .ok_or(ReadError::OutOfBounds) - .and_then(|rec| { - Ok(TaggedElement::new( - rec.lang_sys_tag(), - rec.lang_sys(self.offset_data())?, - )) - }) + .and_then(|rec| Ok(TaggedElement::new(rec.lang_sys_tag(), rec.lang_sys()?))) } } diff --git a/read-fonts/src/tables/layout/spec_tests.rs b/read-fonts/src/tables/layout/spec_tests.rs index 3a2dbb541..309b0172b 100644 --- a/read-fonts/src/tables/layout/spec_tests.rs +++ b/read-fonts/src/tables/layout/spec_tests.rs @@ -7,9 +7,18 @@ fn example_1_scripts() { let table = ScriptList::read(test_data::SCRIPTS.into()).unwrap(); assert_eq!(table.script_count(), 3); - assert_eq!(table.script_records()[0].script_tag(), Tag::new(b"hani")); - assert_eq!(table.script_records()[1].script_tag(), Tag::new(b"kana")); - assert_eq!(table.script_records()[2].script_tag(), Tag::new(b"latn")); + assert_eq!( + table.script_records().get(0).unwrap().script_tag(), + Tag::new(b"hani") + ); + assert_eq!( + table.script_records().get(1).unwrap().script_tag(), + Tag::new(b"kana") + ); + assert_eq!( + table.script_records().get(2).unwrap().script_tag(), + Tag::new(b"latn") + ); } #[test] @@ -22,9 +31,9 @@ fn example_2_scripts_and_langs() { assert_eq!(def_sys.feature_index_count(), 3); assert_eq!(table.lang_sys_count(), 1); - let urdu_record = &table.lang_sys_records()[0]; + let urdu_record = table.lang_sys_records().get(0).unwrap(); assert_eq!(urdu_record.lang_sys_tag(), Tag::new(b"URD ")); - let urdu_sys = urdu_record.lang_sys(table.offset_data()).unwrap(); + let urdu_sys = urdu_record.lang_sys().unwrap(); assert_eq!(urdu_sys.required_feature_index(), 3); assert_eq!(urdu_sys.feature_index_count(), 3); } @@ -34,8 +43,8 @@ fn example_3_featurelist_and_feature() { // https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#example-3-featurelist-table-and-feature-table let table = FeatureList::read(test_data::FEATURELIST_AND_FEATURE.into()).unwrap(); assert_eq!(table.feature_count(), 3); - let turkish_liga_record = &table.feature_records()[0]; - let feature = turkish_liga_record.feature(table.offset_data()).unwrap(); + let turkish_liga_record = table.feature_records().get(0).unwrap(); + let feature = turkish_liga_record.feature().unwrap(); assert!(feature.feature_params_offset().is_null()); assert_eq!(feature.lookup_list_indices().len(), 1); } diff --git a/read-fonts/src/tables/meta.rs b/read-fonts/src/tables/meta.rs index 5490cb5c1..f582d5b10 100644 --- a/read-fonts/src/tables/meta.rs +++ b/read-fonts/src/tables/meta.rs @@ -103,18 +103,15 @@ mod tests { #[test] fn parse_simple() { let table = Meta::read(test_data::SIMPLE_META_TABLE.into()).unwrap(); - let rec1 = table.data_maps()[0]; - let rec2 = table.data_maps()[1]; + let rec1 = table.data_maps().get(0).unwrap(); + let rec2 = table.data_maps().get(1).unwrap(); assert_eq!(rec1.tag(), Tag::new(b"dlng")); assert_eq!(rec2.tag(), Tag::new(b"slng")); assert!(expect_script_lang_tags( - rec1.data(table.offset_data()).unwrap(), + rec1.data().unwrap(), &["en-latn", "latn"] )); - assert!(expect_script_lang_tags( - rec2.data(table.offset_data()).unwrap(), - &["latn"] - )); + assert!(expect_script_lang_tags(rec2.data().unwrap(), &["latn"])); } } diff --git a/read-fonts/src/tests/test_gpos.rs b/read-fonts/src/tests/test_gpos.rs index 2ed36917a..5bc906eb6 100644 --- a/read-fonts/src/tests/test_gpos.rs +++ b/read-fonts/src/tests/test_gpos.rs @@ -93,7 +93,7 @@ fn cursiveposformat1() { assert_eq!(table.entry_exit_count(), 2); assert_eq!(table.entry_exit_record().len(), 2); - let record2 = &table.entry_exit_record()[1]; + let record2 = table.entry_exit_record().get(1).unwrap(); let entry2: AnchorFormat1 = record2 .entry_anchor_offset() .resolve(table.offset_data()) diff --git a/resources/codegen_inputs/test_records.rs b/resources/codegen_inputs/test_records.rs index eec59a75f..9eac2fad4 100644 --- a/resources/codegen_inputs/test_records.rs +++ b/resources/codegen_inputs/test_records.rs @@ -43,6 +43,13 @@ record ContainsOffsets { other_offset: Offset32, } +table ContainsOffsetRecords { + #[compile(array_len($offset_records))] + record_count: u16, + #[count($record_count)] + offset_records: [ContainsOffsets], +} + #[skip_constructor] table VarLenItem { length: u32, diff --git a/skera/src/base.rs b/skera/src/base.rs index db2daac7f..4dd541439 100644 --- a/skera/src/base.rs +++ b/skera/src/base.rs @@ -499,7 +499,7 @@ impl CollectVariationIndices for BaseScriptList<'_> { continue; } - let Ok(base_script) = script_record.base_script(self.offset_data()) else { + let Ok(base_script) = script_record.base_script() else { return; }; base_script.collect_variation_indices(plan, varidx_set); @@ -521,7 +521,7 @@ impl CollectVariationIndices for BaseScript<'_> { if record.min_max_offset().is_null() { continue; } - if let Ok(min_max) = record.min_max(self.offset_data()) { + if let Ok(min_max) = record.min_max() { min_max.collect_variation_indices(plan, varidx_set); } } @@ -552,11 +552,11 @@ impl CollectVariationIndices for MinMax<'_> { continue; } - if let Some(Ok(min_coord)) = record.min_coord(self.offset_data()) { + if let Some(Ok(min_coord)) = record.min_coord() { min_coord.collect_variation_indices(plan, varidx_set); } - if let Some(Ok(max_coord)) = record.max_coord(self.offset_data()) { + if let Some(Ok(max_coord)) = record.max_coord() { max_coord.collect_variation_indices(plan, varidx_set); } } diff --git a/skera/src/cblc.rs b/skera/src/cblc.rs index 8579207bd..adef22638 100644 --- a/skera/src/cblc.rs +++ b/skera/src/cblc.rs @@ -159,15 +159,15 @@ impl<'a> SubsetTable<'a> for IndexSubtableList<'a> { let init_len = s.length(); // serialize subtables in reverse order for idx in (0..src_num_records).rev() { - let record = records[idx]; + let record = records.get(idx).unwrap(); if record.index_subtable_offset().is_null() { continue; } - let Ok(subtable) = record.index_subtable(self.offset_data()) else { + let Ok(subtable) = record.index_subtable() else { return Err(s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR)); }; s.push()?; - match subset_index_subtable(&subtable, plan, s, &record, args.0, args.1) { + match subset_index_subtable(&subtable, plan, s, record.record(), args.0, args.1) { Ok((start_gid, end_gid, table_size)) => { let Some(obj_idx) = s.pop_pack(true) else { return Err(s.error()); @@ -192,7 +192,7 @@ impl<'a> SubsetTable<'a> for IndexSubtableList<'a> { let mut min_start_gid = GlyphId::from(u16::MAX); let mut max_end_gid = GlyphId::NOTDEF; for (record_idx, objidx, start_gid, end_gid) in obj_idxes.iter().rev() { - let record = records[*record_idx]; + let record = records.get(*record_idx).unwrap(); record.subset(plan, s, (*objidx, *start_gid, *end_gid))?; min_start_gid = min_start_gid.min(*start_gid); diff --git a/skera/src/cmap.rs b/skera/src/cmap.rs index d3469b16f..58e70b864 100644 --- a/skera/src/cmap.rs +++ b/skera/src/cmap.rs @@ -11,6 +11,7 @@ use crate::fnv::FnvHashMap; use skrifa::raw::tables::cmap::UnicodeRange; use write_fonts::{ read::{ + array::OffsetResolving, collections::IntSet, tables::cmap::{ Cmap, Cmap12, Cmap14, Cmap4, CmapSubtable, DefaultUvs, EncodingRecord, NonDefaultUvs, @@ -36,7 +37,7 @@ impl Subset for Cmap<'_> { s: &mut Serializer, _builder: &mut FontBuilder, ) -> Result<(), SubsetError> { - let retained_encoding_records: Vec<(usize, &EncodingRecord)> = self + let retained_encoding_records: Vec<(usize, OffsetResolving<'_, EncodingRecord>)> = self .encoding_records() .iter() .enumerate() @@ -50,10 +51,7 @@ impl Subset for Cmap<'_> { let mut has_format12 = false; for (_, record) in retained_encoding_records.iter() { - if record - .subtable(self.offset_data()) - .is_ok_and(|t| t.format() == 12) - { + if record.subtable().is_ok_and(|t| t.format() == 12) { has_format12 = true; } @@ -95,7 +93,7 @@ fn can_drop_format12( cmap12_record: &EncodingRecord, cmap12_subset_unicodes: &IntSet, cmap: &Cmap, - retained_encoding_records: &[(usize, &EncodingRecord)], + retained_encoding_records: &[(usize, OffsetResolving<'_, EncodingRecord>)], unicodes_cache: &mut SubtableUnicodeCache, subset_unicodes: &IntSet, num_glyphs: usize, @@ -123,7 +121,7 @@ fn can_drop_format12( .language(); for (rec_idx, rec) in retained_encoding_records.iter() { - let Ok(subtable) = rec.subtable(cmap.offset_data()) else { + let Ok(subtable) = rec.subtable() else { continue; }; if rec.platform_id() != target_platform @@ -150,7 +148,7 @@ fn serialize_cmap( cmap: &Cmap, s: &mut Serializer, plan: &Plan, - retained_encoding_records: &[(usize, &EncodingRecord)], + retained_encoding_records: &[(usize, OffsetResolving<'_, EncodingRecord>)], ) -> Result<(), SerializeErrorFlags> { // allocate header: version + numTables s.allocate_size(HEADER_SIZE, false)?; @@ -166,7 +164,7 @@ fn serialize_cmap( return Err(s.error()); } - let Ok(subtable) = record.subtable(cmap.offset_data()) else { + let Ok(subtable) = record.subtable() else { continue; }; @@ -763,7 +761,7 @@ impl Serialize for Cmap14<'_> { // numVarSelectorRecords, initialized to 0, update later let num_records_pos = s.embed(0_u32)?; - let retained_records: Vec<&VariationSelector> = self + let retained_records: Vec> = self .var_selector() .iter() .filter(|r| plan.unicodes.contains(r.var_selector().to_u32())) diff --git a/skera/src/gpos.rs b/skera/src/gpos.rs index 1740212e1..f3fa2b6c0 100644 --- a/skera/src/gpos.rs +++ b/skera/src/gpos.rs @@ -45,7 +45,7 @@ impl NameIdClosure for Gpos<'_> { if !plan.gpos_features.contains_key(&(i as u16)) { continue; } - let Ok(feature) = feature_record.feature(feature_list.offset_data()) else { + let Ok(feature) = feature_record.feature() else { continue; }; feature.collect_name_ids(plan); diff --git a/skera/src/gpos/cursive_pos.rs b/skera/src/gpos/cursive_pos.rs index de019b6b5..04876cf82 100644 --- a/skera/src/gpos/cursive_pos.rs +++ b/skera/src/gpos/cursive_pos.rs @@ -108,7 +108,6 @@ impl CollectVariationIndices for CursivePosFormat1<'_> { return; }; - let font_data = self.offset_data(); let glyph_set = &plan.glyphset_gsub; let entry_exit_records = self.entry_exit_record(); let record_idxes = intersected_coverage_indices(&coverage, glyph_set); @@ -116,10 +115,10 @@ impl CollectVariationIndices for CursivePosFormat1<'_> { let Some(rec) = entry_exit_records.get(i as usize) else { return; }; - if let Some(Ok(entry_anchor)) = rec.entry_anchor(font_data) { + if let Some(Ok(entry_anchor)) = rec.entry_anchor() { entry_anchor.collect_variation_indices(plan, varidx_set); } - if let Some(Ok(exit_anchor)) = rec.exit_anchor(font_data) { + if let Some(Ok(exit_anchor)) = rec.exit_anchor() { exit_anchor.collect_variation_indices(plan, varidx_set); } } diff --git a/skera/src/gpos/mark_array.rs b/skera/src/gpos/mark_array.rs index 940cc291d..5d175eb49 100644 --- a/skera/src/gpos/mark_array.rs +++ b/skera/src/gpos/mark_array.rs @@ -7,6 +7,7 @@ use crate::{ }; use write_fonts::{ read::{ + array::OffsetResolving, collections::IntSet, tables::{ gpos::{MarkArray, MarkRecord}, @@ -19,12 +20,11 @@ use write_fonts::{ }; pub(crate) fn collect_mark_record_varidx( - mark_record: &MarkRecord, + mark_record: OffsetResolving<'_, MarkRecord>, plan: &Plan, varidx_set: &mut IntSet, - font_data: FontData, ) { - if let Ok(mark_anchor) = mark_record.mark_anchor(font_data) { + if let Ok(mark_anchor) = mark_record.mark_anchor() { mark_anchor.collect_variation_indices(plan, varidx_set); }; } diff --git a/skera/src/gpos/mark_base_pos.rs b/skera/src/gpos/mark_base_pos.rs index c090deac2..1a2160453 100644 --- a/skera/src/gpos/mark_base_pos.rs +++ b/skera/src/gpos/mark_base_pos.rs @@ -29,7 +29,6 @@ impl CollectVariationIndices for MarkBasePosFormat1<'_> { }; let glyph_set = &plan.glyphset_gsub; - let mark_array_data = mark_array.offset_data(); let mark_records = mark_array.mark_records(); let mark_record_idxes = intersected_coverage_indices(&mark_coverage, glyph_set); @@ -39,7 +38,7 @@ impl CollectVariationIndices for MarkBasePosFormat1<'_> { return; }; let class = mark_record.mark_class(); - collect_mark_record_varidx(mark_record, plan, varidx_set, mark_array_data); + collect_mark_record_varidx(mark_record, plan, varidx_set); retained_mark_classes.insert(class); } diff --git a/skera/src/gpos/mark_lig_pos.rs b/skera/src/gpos/mark_lig_pos.rs index e02d95e71..2441947b5 100644 --- a/skera/src/gpos/mark_lig_pos.rs +++ b/skera/src/gpos/mark_lig_pos.rs @@ -28,7 +28,6 @@ impl CollectVariationIndices for MarkLigPosFormat1<'_> { }; let glyph_set = &plan.glyphset_gsub; - let mark_array_data = mark_array.offset_data(); let mark_records = mark_array.mark_records(); let mark_record_idxes = intersected_coverage_indices(&mark_coverage, glyph_set); @@ -38,7 +37,7 @@ impl CollectVariationIndices for MarkLigPosFormat1<'_> { return; }; let class = mark_record.mark_class(); - collect_mark_record_varidx(mark_record, plan, varidx_set, mark_array_data); + collect_mark_record_varidx(mark_record, plan, varidx_set); retained_mark_classes.insert(class); } diff --git a/skera/src/gpos/mark_mark_pos.rs b/skera/src/gpos/mark_mark_pos.rs index a0c3f91d6..4251ce373 100644 --- a/skera/src/gpos/mark_mark_pos.rs +++ b/skera/src/gpos/mark_mark_pos.rs @@ -27,7 +27,6 @@ impl CollectVariationIndices for MarkMarkPosFormat1<'_> { }; let glyph_set = &plan.glyphset_gsub; - let mark1_array_data = mark1_array.offset_data(); let mark1_records = mark1_array.mark_records(); let mark1_record_idxes = intersected_coverage_indices(&mark1_coverage, glyph_set); @@ -37,7 +36,7 @@ impl CollectVariationIndices for MarkMarkPosFormat1<'_> { return; }; let class = mark1_record.mark_class(); - collect_mark_record_varidx(mark1_record, plan, varidx_set, mark1_array_data); + collect_mark_record_varidx(mark1_record, plan, varidx_set); retained_mark_classes.insert(class); } diff --git a/skera/src/gsub.rs b/skera/src/gsub.rs index 0b6ea78d5..55af0f321 100644 --- a/skera/src/gsub.rs +++ b/skera/src/gsub.rs @@ -37,7 +37,7 @@ impl NameIdClosure for Gsub<'_> { if !plan.gsub_features.contains_key(&(i as u16)) { continue; } - let Ok(feature) = feature_record.feature(feature_list.offset_data()) else { + let Ok(feature) = feature_record.feature() else { continue; }; feature.collect_name_ids(plan); diff --git a/skera/src/layout.rs b/skera/src/layout.rs index 0fbf2a40a..33e6c7dc4 100644 --- a/skera/src/layout.rs +++ b/skera/src/layout.rs @@ -808,12 +808,11 @@ pub(crate) fn collect_features_with_retained_subs( feature_variations: &FeatureVariations, lookup_indices: &IntSet, ) -> IntSet { - let font_data = feature_variations.offset_data(); let mut out = IntSet::empty(); for subs in feature_variations .feature_variation_records() .iter() - .filter_map(|rec| rec.feature_table_substitution(font_data)) + .filter_map(|rec| rec.feature_table_substitution()) { let Ok(subs) = subs else { return IntSet::empty(); @@ -864,7 +863,7 @@ pub(crate) fn prune_features( continue; } - let Ok(feature) = feature_rec.feature(feature_list.offset_data()) else { + let Ok(feature) = feature_rec.feature() else { return out; }; // always keep "size" feature even if it's empty @@ -900,7 +899,7 @@ pub(crate) fn find_duplicate_features( continue; }; - let Ok(f) = rec.feature(feature_list.offset_data()) else { + let Ok(f) = rec.feature() else { return out; }; @@ -918,7 +917,7 @@ pub(crate) fn find_duplicate_features( continue; }; - let Ok(other_f) = other_rec.feature(feature_list.offset_data()) else { + let Ok(other_f) = other_rec.feature() else { return out; }; @@ -1044,7 +1043,7 @@ impl<'a> PruneLangSysContext<'a> { if langsys_rec.lang_sys_offset().is_null() { continue; } - let Ok(l) = langsys_rec.lang_sys(script.offset_data()) else { + let Ok(l) = langsys_rec.lang_sys() else { return; }; if !self.visit_langsys(l.feature_index_count()) { @@ -1062,7 +1061,7 @@ impl<'a> PruneLangSysContext<'a> { if langsys_rec.lang_sys_offset().is_null() { continue; } - let Ok(l) = langsys_rec.lang_sys(script.offset_data()) else { + let Ok(l) = langsys_rec.lang_sys() else { return; }; if !self.visit_langsys(l.feature_index_count()) { @@ -1096,7 +1095,7 @@ impl<'a> PruneLangSysContext<'a> { continue; } - let Ok(script) = script_rec.script(script_list.offset_data()) else { + let Ok(script) = script_rec.script() else { return (self.script_langsys_map(), self.feature_indices()); }; self.prune_script_langsys(i as u16, &script); @@ -1574,7 +1573,8 @@ impl<'a> SubsetTable<'a> for FeatureVariations<'_> { let variation_records = self.feature_variation_records(); for i in 0..num_retained_records { - variation_records[i as usize].subset(plan, s, (font_data, feature_index_map, c))?; + let record = variation_records.get(i as usize).unwrap(); + record.subset(plan, s, (font_data, feature_index_map, c))?; } Ok(()) } @@ -1589,11 +1589,11 @@ fn num_variation_record_to_retain( ) -> Result { let num_records = feature_variations.feature_variation_record_count(); let variation_records = feature_variations.feature_variation_records(); - let font_data = feature_variations.offset_data(); for i in (0..num_records).rev() { - let Some(feature_substitution) = variation_records[i as usize] - .feature_table_substitution(font_data) + let record = variation_records.get(i as usize).unwrap(); + let Some(feature_substitution) = record + .feature_table_substitution() .transpose() .map_err(|_| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))? else { diff --git a/skera/src/lib.rs b/skera/src/lib.rs index 6d34b7aa1..1cf0651e9 100644 --- a/skera/src/lib.rs +++ b/skera/src/lib.rs @@ -526,7 +526,7 @@ impl Plan { fn collect_variation_selectors(&mut self, font: &FontRef, input_unicodes: &IntSet) { if let Ok(cmap) = font.cmap() { let encoding_records = cmap.encoding_records(); - if let Ok(i) = encoding_records.binary_search_by(|r| { + if let Ok(i) = encoding_records.as_slice().binary_search_by(|r| { if r.platform_id() != PlatformId::Unicode { r.platform_id().cmp(&PlatformId::Unicode) } else if r.encoding_id() != 5 { @@ -535,10 +535,8 @@ impl Plan { std::cmp::Ordering::Equal } }) { - if let Ok(CmapSubtable::Format14(cmap14)) = encoding_records - .get(i) - .unwrap() - .subtable(cmap.offset_data()) + if let Ok(CmapSubtable::Format14(cmap14)) = + encoding_records.get(i).unwrap().subtable() { self.unicodes.extend( cmap14 diff --git a/skrifa/src/charmap.rs b/skrifa/src/charmap.rs index c3831b9f8..21794ea7e 100644 --- a/skrifa/src/charmap.rs +++ b/skrifa/src/charmap.rs @@ -20,7 +20,7 @@ use read_fonts::{ CmapIterLimits, CmapSubtable, EncodingRecord, PlatformId, }, types::GlyphId, - FontData, FontRef, TableProvider, + FontRef, TableProvider, }; pub use read_fonts::tables::cmap::MapVariant; @@ -178,11 +178,10 @@ impl MappingIndex { return Default::default(); }; let records = cmap.encoding_records(); - let data = cmap.offset_data(); Charmap { codepoint_subtable: self .codepoint_subtable - .and_then(|index| get_subtable(data, records, index)) + .and_then(|index| get_subtable(records, index)) .and_then(SupportedSubtable::new) .map(|subtable| CodepointSubtable { subtable, @@ -190,7 +189,7 @@ impl MappingIndex { }), variant_subtable: self .variant_subtable - .and_then(|index| get_subtable(data, records, index)) + .and_then(|index| get_subtable(records, index)) .and_then(|subtable| match subtable { CmapSubtable::Format14(cmap14) => Some(cmap14), _ => None, @@ -248,14 +247,13 @@ impl Iterator for VariantMappings<'_> { } } -fn get_subtable<'a>( - data: FontData<'a>, - records: &[EncodingRecord], +fn get_subtable( + records: read_fonts::array::ArrayOfRecordsWithOffsetData<'_, EncodingRecord>, index: u16, -) -> Option> { +) -> Option> { records .get(index as usize) - .and_then(|record| record.subtable(data).ok()) + .and_then(|record| record.subtable().ok()) } #[derive(Clone)] @@ -310,8 +308,10 @@ impl<'a> SupportedSubtable<'a> { }) } - fn from_cmap_record(cmap: &Cmap<'a>, record: &cmap::EncodingRecord) -> Option { - Self::new(record.subtable(cmap.offset_data()).ok()?) + fn from_cmap_record( + record: read_fonts::array::OffsetResolving<'a, cmap::EncodingRecord>, + ) -> Option { + Self::new(record.subtable().ok()?) } } @@ -374,9 +374,7 @@ impl<'a> MappingSelection<'a> { match (record.platform_id(), record.encoding_id()) { (PlatformId::Unicode, ENCODING_APPLE_ID_VARIANT_SELECTOR) => { // Unicode variation sequences - if let Ok(CmapSubtable::Format14(subtable)) = - record.subtable(cmap.offset_data()) - { + if let Ok(CmapSubtable::Format14(subtable)) = record.subtable() { if variant_subtable.is_none() { mapping_index.variant_subtable = Some(i as u16); variant_subtable = Some(subtable); @@ -385,14 +383,14 @@ impl<'a> MappingSelection<'a> { } (PlatformId::Windows, ENCODING_MS_SYMBOL) => { // Symbol - if let Some(subtable) = SupportedSubtable::from_cmap_record(cmap, record) { + if let Some(subtable) = SupportedSubtable::from_cmap_record(record) { maybe_choose_subtable(MappingKind::Symbol, i, subtable); } } (PlatformId::Windows, ENCODING_MS_ID_UCS_4) | (PlatformId::Unicode, ENCODING_APPLE_ID_UNICODE_32) => { // Unicode full repertoire - if let Some(subtable) = SupportedSubtable::from_cmap_record(cmap, record) { + if let Some(subtable) = SupportedSubtable::from_cmap_record(record) { maybe_choose_subtable(MappingKind::UnicodeFull, i, subtable); } } @@ -400,7 +398,7 @@ impl<'a> MappingSelection<'a> { | (PlatformId::Unicode, _) | (PlatformId::Windows, ENCODING_MS_UNICODE_CS) => { // Unicode BMP only - if let Some(subtable) = SupportedSubtable::from_cmap_record(cmap, record) { + if let Some(subtable) = SupportedSubtable::from_cmap_record(record) { maybe_choose_subtable(MappingKind::UnicodeBmp, i, subtable); } } diff --git a/skrifa/src/outline/autohint/shape.rs b/skrifa/src/outline/autohint/shape.rs index 9040673b9..92d7e1d91 100644 --- a/skrifa/src/outline/autohint/shape.rs +++ b/skrifa/src/outline/autohint/shape.rs @@ -211,13 +211,13 @@ impl<'a> Shaper<'a> { for script in script_tags.iter().filter_map(|tag| { tag.and_then(|tag| script_list.index_for_tag(tag)) .and_then(|ix| script_list.script_records().get(ix as usize)) - .and_then(|rec| rec.script(script_list.offset_data()).ok()) + .and_then(|rec| rec.script().ok()) }) { // And all language systems for each script for langsys in script .lang_sys_records() .iter() - .filter_map(|rec| rec.lang_sys(script.offset_data()).ok()) + .filter_map(|rec| rec.lang_sys().ok()) .chain(script.default_lang_sys().transpose().ok().flatten()) { for feature_ix in langsys.feature_indices() { @@ -228,7 +228,7 @@ impl<'a> Shaper<'a> { // If our style has a feature tag, we only look at that specific // feature; otherwise, handle all of them if style.feature == Some(rec.feature_tag()) || style.feature.is_none() { - rec.feature(feature_list.offset_data()).ok() + rec.feature().ok() } else { None } diff --git a/write-fonts/generated/generated_base.rs b/write-fonts/generated/generated_base.rs index 5edf80642..081f534de 100644 --- a/write-fonts/generated/generated_base.rs +++ b/write-fonts/generated/generated_base.rs @@ -260,7 +260,10 @@ impl<'a> FromObjRef> for BaseScript fn from_obj_ref(obj: &read_fonts::tables::base::BaseScriptList<'a>, _: FontData) -> Self { let offset_data = obj.offset_data(); BaseScriptList { - base_script_records: obj.base_script_records().to_owned_obj(offset_data), + base_script_records: obj + .base_script_records() + .as_slice() + .to_owned_obj(offset_data), } } } @@ -397,7 +400,10 @@ impl<'a> FromObjRef> for BaseScript { BaseScript { base_values: obj.base_values().to_owned_table(), default_min_max: obj.default_min_max().to_owned_table(), - base_lang_sys_records: obj.base_lang_sys_records().to_owned_obj(offset_data), + base_lang_sys_records: obj + .base_lang_sys_records() + .as_slice() + .to_owned_obj(offset_data), } } } @@ -606,7 +612,10 @@ impl<'a> FromObjRef> for MinMax { MinMax { min_coord: obj.min_coord().to_owned_table(), max_coord: obj.max_coord().to_owned_table(), - feat_min_max_records: obj.feat_min_max_records().to_owned_obj(offset_data), + feat_min_max_records: obj + .feat_min_max_records() + .as_slice() + .to_owned_obj(offset_data), } } } diff --git a/write-fonts/generated/generated_cmap.rs b/write-fonts/generated/generated_cmap.rs index 003ed90c3..ecedcbb5d 100644 --- a/write-fonts/generated/generated_cmap.rs +++ b/write-fonts/generated/generated_cmap.rs @@ -54,7 +54,7 @@ impl<'a> FromObjRef> for Cmap { fn from_obj_ref(obj: &read_fonts::tables::cmap::Cmap<'a>, _: FontData) -> Self { let offset_data = obj.offset_data(); Cmap { - encoding_records: obj.encoding_records().to_owned_obj(offset_data), + encoding_records: obj.encoding_records().as_slice().to_owned_obj(offset_data), } } } @@ -1225,7 +1225,7 @@ impl<'a> FromObjRef> for Cmap14 { Cmap14 { length: obj.length(), num_var_selector_records: obj.num_var_selector_records(), - var_selector: obj.var_selector().to_owned_obj(offset_data), + var_selector: obj.var_selector().as_slice().to_owned_obj(offset_data), } } } diff --git a/write-fonts/generated/generated_colr.rs b/write-fonts/generated/generated_colr.rs index ae4d19346..98f90e223 100644 --- a/write-fonts/generated/generated_colr.rs +++ b/write-fonts/generated/generated_colr.rs @@ -281,7 +281,10 @@ impl<'a> FromObjRef> for BaseGlyphLi let offset_data = obj.offset_data(); BaseGlyphList { num_base_glyph_paint_records: obj.num_base_glyph_paint_records(), - base_glyph_paint_records: obj.base_glyph_paint_records().to_owned_obj(offset_data), + base_glyph_paint_records: obj + .base_glyph_paint_records() + .as_slice() + .to_owned_obj(offset_data), } } } @@ -466,7 +469,7 @@ impl<'a> FromObjRef> for ClipList { ClipList { format: obj.format(), num_clips: obj.num_clips(), - clips: obj.clips().to_owned_obj(offset_data), + clips: obj.clips().as_slice().to_owned_obj(offset_data), } } } diff --git a/write-fonts/generated/generated_gpos.rs b/write-fonts/generated/generated_gpos.rs index 94caf812d..88ebd607e 100644 --- a/write-fonts/generated/generated_gpos.rs +++ b/write-fonts/generated/generated_gpos.rs @@ -628,7 +628,7 @@ impl<'a> FromObjRef> for MarkArray { fn from_obj_ref(obj: &read_fonts::tables::gpos::MarkArray<'a>, _: FontData) -> Self { let offset_data = obj.offset_data(); MarkArray { - mark_records: obj.mark_records().to_owned_obj(offset_data), + mark_records: obj.mark_records().as_slice().to_owned_obj(offset_data), } } } @@ -1456,7 +1456,7 @@ impl<'a> FromObjRef> for Cursive let offset_data = obj.offset_data(); CursivePosFormat1 { coverage: obj.coverage().to_owned_table(), - entry_exit_record: obj.entry_exit_record().to_owned_obj(offset_data), + entry_exit_record: obj.entry_exit_record().as_slice().to_owned_obj(offset_data), } } } diff --git a/write-fonts/generated/generated_layout.rs b/write-fonts/generated/generated_layout.rs index 17e5bbd86..e344e22ce 100644 --- a/write-fonts/generated/generated_layout.rs +++ b/write-fonts/generated/generated_layout.rs @@ -50,7 +50,7 @@ impl<'a> FromObjRef> for ScriptList { fn from_obj_ref(obj: &read_fonts::tables::layout::ScriptList<'a>, _: FontData) -> Self { let offset_data = obj.offset_data(); ScriptList { - script_records: obj.script_records().to_owned_obj(offset_data), + script_records: obj.script_records().as_slice().to_owned_obj(offset_data), } } } @@ -171,7 +171,7 @@ impl<'a> FromObjRef> for Script { let offset_data = obj.offset_data(); Script { default_lang_sys: obj.default_lang_sys().to_owned_table(), - lang_sys_records: obj.lang_sys_records().to_owned_obj(offset_data), + lang_sys_records: obj.lang_sys_records().as_slice().to_owned_obj(offset_data), } } } @@ -362,7 +362,7 @@ impl<'a> FromObjRef> for FeatureList fn from_obj_ref(obj: &read_fonts::tables::layout::FeatureList<'a>, _: FontData) -> Self { let offset_data = obj.offset_data(); FeatureList { - feature_records: obj.feature_records().to_owned_obj(offset_data), + feature_records: obj.feature_records().as_slice().to_owned_obj(offset_data), } } } @@ -2966,7 +2966,10 @@ impl<'a> FromObjRef> for Featu fn from_obj_ref(obj: &read_fonts::tables::layout::FeatureVariations<'a>, _: FontData) -> Self { let offset_data = obj.offset_data(); FeatureVariations { - feature_variation_records: obj.feature_variation_records().to_owned_obj(offset_data), + feature_variation_records: obj + .feature_variation_records() + .as_slice() + .to_owned_obj(offset_data), } } } diff --git a/write-fonts/generated/generated_meta.rs b/write-fonts/generated/generated_meta.rs index 1877aec0c..a18f1b064 100644 --- a/write-fonts/generated/generated_meta.rs +++ b/write-fonts/generated/generated_meta.rs @@ -55,7 +55,7 @@ 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: obj.data_maps().to_owned_obj(offset_data), + data_maps: obj.data_maps().as_slice().to_owned_obj(offset_data), } } } diff --git a/write-fonts/generated/generated_test_records.rs b/write-fonts/generated/generated_test_records.rs index fb940b0e5..796587620 100644 --- a/write-fonts/generated/generated_test_records.rs +++ b/write-fonts/generated/generated_test_records.rs @@ -229,6 +229,74 @@ impl FromObjRef for Contains } } +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ContainsOffsetRecords { + pub offset_records: Vec, +} + +impl ContainsOffsetRecords { + /// Construct a new `ContainsOffsetRecords` + pub fn new(offset_records: Vec) -> Self { + Self { offset_records } + } +} + +impl FontWrite for ContainsOffsetRecords { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + (u16::try_from(array_len(&self.offset_records)).unwrap()).write_into(writer); + self.offset_records.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("ContainsOffsetRecords") + } +} + +impl Validate for ContainsOffsetRecords { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("ContainsOffsetRecords", |ctx| { + ctx.in_field("offset_records", |ctx| { + if self.offset_records.len() > to_usize(u16::MAX) { + ctx.report("array exceeds max length"); + } + self.offset_records.validate_impl(ctx); + }); + }) + } +} + +impl<'a> FromObjRef> + for ContainsOffsetRecords +{ + fn from_obj_ref( + obj: &read_fonts::codegen_test::records::ContainsOffsetRecords<'a>, + _: FontData, + ) -> Self { + let offset_data = obj.offset_data(); + ContainsOffsetRecords { + offset_records: obj.offset_records().as_slice().to_owned_obj(offset_data), + } + } +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> FromTableRef> + for ContainsOffsetRecords +{ +} + +impl ReadArgs for ContainsOffsetRecords { + type Args = (); +} + +impl<'a> FontRead<'a> for ContainsOffsetRecords { + fn read_with_args(data: FontData<'a>, _: ()) -> Result { + ::read(data) + .map(|x| x.to_owned_table()) + } +} + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct VarLenItem { diff --git a/write-fonts/src/codegen_test.rs b/write-fonts/src/codegen_test.rs index d029f2ebd..f127713c7 100644 --- a/write-fonts/src/codegen_test.rs +++ b/write-fonts/src/codegen_test.rs @@ -46,6 +46,8 @@ mod records { let basic = BasicTable::new(simple.clone(), vec![contains_arrays]); let contains_offsets = ContainsOffsets::new(simple, basic); assert_eq!(contains_offsets.other.simple_records.len(), 1); + let contains_offset_records = ContainsOffsetRecords::new(vec![contains_offsets]); + assert_eq!(contains_offset_records.offset_records.len(), 1); } } diff --git a/write-fonts/src/graph/splitting/mark2base.rs b/write-fonts/src/graph/splitting/mark2base.rs index 28b775c88..d65dc6882 100644 --- a/write-fonts/src/graph/splitting/mark2base.rs +++ b/write-fonts/src/graph/splitting/mark2base.rs @@ -460,10 +460,11 @@ mod tests { let new_mark_idx = new_subtable.mark_coverage().unwrap().get(mark_gid).unwrap(); let new_base_idx = new_subtable.base_coverage().unwrap().get(base_gid).unwrap(); let new_mark_array = new_subtable.mark_array().unwrap(); - let new_mark_record = &new_mark_array.mark_records()[new_mark_idx as usize]; - let new_mark_anchor = new_mark_record - .mark_anchor(new_mark_array.offset_data()) + let new_mark_record = new_mark_array + .mark_records() + .get(new_mark_idx as usize) .unwrap(); + let new_mark_anchor = new_mark_record.mark_anchor().unwrap(); let new_base_array = new_subtable.base_array().unwrap(); let new_base_anchor = new_base_array .base_records() @@ -562,8 +563,8 @@ mod tests { .expect("should exist in some subtable"); let new_cov_idx = subtable.mark_coverage().unwrap().get(gid).unwrap(); let mark_array = subtable.mark_array().unwrap(); - let mark_record = &mark_array.mark_records()[new_cov_idx as usize]; - let mark_anchor = mark_record.mark_anchor(mark_array.offset_data()).unwrap(); + let mark_record = mark_array.mark_records().get(new_cov_idx as usize).unwrap(); + let mark_anchor = mark_record.mark_anchor().unwrap(); let rgpos::AnchorTable::Format3(mark_anchor) = mark_anchor else { panic!("wrong format") }; diff --git a/write-fonts/src/tables/cmap.rs b/write-fonts/src/tables/cmap.rs index c5248297b..7bca59a02 100644 --- a/write-fonts/src/tables/cmap.rs +++ b/write-fonts/src/tables/cmap.rs @@ -588,7 +588,7 @@ mod tests { ); for encoding_record in cmap.encoding_records() { - let CmapSubtable::Format4(cmap4) = encoding_record.subtable(font_data).unwrap() else { + let CmapSubtable::Format4(cmap4) = encoding_record.subtable().unwrap() else { panic!("Expected a cmap4 in {encoding_record:?}"); }; @@ -698,14 +698,9 @@ mod tests { mappings } - fn assert_cmap12_groups( - font_data: FontData, - cmap: &Cmap, - record_index: usize, - expected: &[(u32, u32, u32)], - ) { - let rec = &cmap.encoding_records()[record_index]; - let CmapSubtable::Format12(subtable) = rec.subtable(font_data).unwrap() else { + fn assert_cmap12_groups(cmap: &Cmap, record_index: usize, expected: &[(u32, u32, u32)]) { + let rec = cmap.encoding_records().get(record_index).unwrap(); + let CmapSubtable::Format12(subtable) = rec.subtable().unwrap() else { panic!("Expected a cmap12 in {rec:?}"); }; let groups = subtable @@ -743,12 +738,9 @@ mod tests { ); let encoding_records = cmap.encoding_records(); - let first_rec = &encoding_records[0]; + let first_rec = encoding_records.get(0).unwrap(); assert!( - matches!( - first_rec.subtable(font_data).unwrap(), - CmapSubtable::Format4(_) - ), + matches!(first_rec.subtable().unwrap(), CmapSubtable::Format4(_)), "Expected a cmap4 in {first_rec:?}" ); @@ -762,8 +754,8 @@ mod tests { (0x1f134, 0x1f134, 486), (0x1f136, 0x1f136, 488), ]; - assert_cmap12_groups(font_data, &cmap, 1, &expected_groups); - assert_cmap12_groups(font_data, &cmap, 3, &expected_groups); + assert_cmap12_groups(&cmap, 1, &expected_groups); + assert_cmap12_groups(&cmap, 3, &expected_groups); } #[test] @@ -796,8 +788,8 @@ mod tests { (0x1f134, 0x1f134, 486), (0x1f136, 0x1f136, 488), ]; - assert_cmap12_groups(font_data, &cmap, 0, &expected_groups); - assert_cmap12_groups(font_data, &cmap, 1, &expected_groups); + assert_cmap12_groups(&cmap, 0, &expected_groups); + assert_cmap12_groups(&cmap, 1, &expected_groups); } #[test]