diff --git a/font-test-data/src/ift.rs b/font-test-data/src/ift.rs index 6088f6f17..c070ae5cd 100644 --- a/font-test-data/src/ift.rs +++ b/font-test-data/src/ift.rs @@ -62,8 +62,9 @@ pub fn u16_entries_format1() -> BeBuffer { {0u32: "feature_map_offset"}, // applied entry bitmap (38 bytes) + {0u8: "applied_entry_bitmap"}, [ - 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -200,26 +201,26 @@ pub fn codepoints_only_format2() -> BeBuffer { /* ### Entries Array ### */ // Entry id = 1 - {0b00010000u8: "entries"}, // format = CODEPOINT_BIT_1 + {0b00010000u8: "entries[0]"}, // format = CODEPOINT_BIT_1 [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [0..17] // Entry id = 2 - 0b01100000u8, // format = IGNORED | CODEPOINT_BIT_2 + {0b01100000u8: "entries[1]"}, // format = IGNORED | CODEPOINT_BIT_2 5u16, // bias [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [5..22] // Entry id = 3 - 0b00100000u8, // format = CODEPOINT_BIT_2 - 5u16, // bias + {0b00100000u8: "entries[2]"}, // format = CODEPOINT_BIT_2 + 5u16, // bias [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [5..22] // Entry id = 4 - 0b00110000u8, // format = CODEPOINT_BIT_1 | CODEPOINT_BIT_2 - (Uint24::new(80_000)), // bias - [0b00001101, 0b00000011, 0b00110001u8] // codepoints = [80_005..80_022] + {0b00110000u8: "entries[3]"}, // format = CODEPOINT_BIT_1 | CODEPOINT_BIT_2 + (Uint24::new(80_000)), // bias + [0b00001101, 0b00000011, 0b00110001u8] // codepoints = [80_005..80_022] }; - let offset = buffer.offset_for("entries") as u32; + let offset = buffer.offset_for("entries[0]") as u32; buffer.write_at("entries_offset", offset); buffer @@ -243,7 +244,7 @@ pub fn features_and_design_space_format2() -> BeBuffer { /* ### Entries Array ### */ // Entry id = 1 - {0b00010001u8: "entries"}, // format = CODEPOINT_BIT_1 | FEATURES_AND_DESIGN_SPACE + {0b00010001u8: "entries[0]"}, // format = CODEPOINT_BIT_1 | FEATURES_AND_DESIGN_SPACE 2u8, // feature count = 2 (Tag::new(b"liga")), // feature[0] = liga @@ -256,9 +257,8 @@ pub fn features_and_design_space_format2() -> BeBuffer { [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [0..17] - // Entries Array // Entry id = 2 - 0b00010001u8, // format = CODEPOINT_BIT_1 | FEATURES_AND_DESIGN_SPACE + {0b00010001u8: "entries[1]"}, // format = CODEPOINT_BIT_1 | FEATURES_AND_DESIGN_SPACE 1u8, // feature count (Tag::new(b"rlig")), // feature[0] @@ -268,7 +268,7 @@ pub fn features_and_design_space_format2() -> BeBuffer { [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [0..17] // Entry id = 3 - 0b000100001u8, // format = CODEPOINT_BIT_2 | FEATURES_AND_DESIGN_SPACE + {0b000100001u8: "entries[2]"}, // format = CODEPOINT_BIT_2 | FEATURES_AND_DESIGN_SPACE 1u8, // feature count = 1 (Tag::new(b"smcp")), // feature[0] = smcp @@ -290,7 +290,7 @@ pub fn features_and_design_space_format2() -> BeBuffer { 0b00001101, 0b00000011, 0b00110001 // codepoints = [5..22] }; - let offset = buffer.offset_for("entries") as u32; + let offset = buffer.offset_for("entries[0]") as u32; buffer.write_at("entries_offset", offset); buffer @@ -315,17 +315,17 @@ pub fn copy_indices_format2() -> BeBuffer { // Entries Array // Entry id = 1 - {0b01100000u8: "entries"}, // format = CODEPOINT_BIT_2 | IGNORED + {0b01100000u8: "entries[0]"}, // format = CODEPOINT_BIT_2 | IGNORED 5u16, // bias = 5 [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [5..22] // Entry id = 2 - 0b00100000u8, // format = CODEPOINT_BIT_2 + {0b00100000u8: "entries[1]"}, // format = CODEPOINT_BIT_2 50u16, // bias [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [50..67] // Entry id = 3 - 0b00000001u8, // format = FEATURES_AND_DESIGN_SPACE + {0b00000001u8: "entries[2]"}, // format = FEATURES_AND_DESIGN_SPACE 1u8, // feature count = 1 (Tag::new(b"rlig")), // feature[0] = rlig @@ -336,7 +336,7 @@ pub fn copy_indices_format2() -> BeBuffer { 0x02BC_0000u32, // end = 700 // Entry id = 4 - 0b00000001u8, // format = FEATURES_AND_DESIGN_SPACE + {0b00000001u8: "entries[3]"}, // format = FEATURES_AND_DESIGN_SPACE 1u8, // feature count (Tag::new(b"liga")), // feature[0] = liga @@ -347,17 +347,17 @@ pub fn copy_indices_format2() -> BeBuffer { 0x0064_0000, // end = 100 // Entry id = 5 - 0b00000010u8, // format = COPY_INDICES + {0b00000010u8: "entries[4]"}, // format = COPY_INDICES 1u8, // copy count (Uint24::new(0)), // copy // Entry id = 6 - 0b00000010u8, // format = COPY_INDICES + {0b00000010u8: "entries[5]"}, // format = COPY_INDICES 1u8, // copy count (Uint24::new(2)), // copy // Entry id = 7 - 0b00000010u8, // format = COPY_INDICES + {0b00000010u8: "entries[6]"}, // format = COPY_INDICES 4u8, // copy count (Uint24::new(3)), // copy[0] (Uint24::new(2)), // copy[1] @@ -365,20 +365,20 @@ pub fn copy_indices_format2() -> BeBuffer { (Uint24::new(0)), // copy[3] // Entry id = 8 - 0b00000010u8, // format = COPY_INDICES + {0b00000010u8: "entries[7]"}, // format = COPY_INDICES 2u8, // copy count (Uint24::new(4)), // copy[0] (Uint24::new(5)), // copy[1] // Entry id = 9 - 0b00100010u8, // format = CODEPOINT_BIT_2 | COPY_INDICES + {0b00100010u8: "entries[8]"}, // format = CODEPOINT_BIT_2 | COPY_INDICES 1u8, // copy count (Uint24::new(0)), // copy[0] 100u16, // bias [0b00001101, 0b00000011, 0b00110001u8] // codepoints = [100..117] }; - let offset = buffer.offset_for("entries") as u32; + let offset = buffer.offset_for("entries[0]") as u32; buffer.write_at("entries_offset", offset); buffer @@ -403,28 +403,28 @@ pub fn custom_ids_format2() -> BeBuffer { // Entries Array // Entry id = 0 - {0b00010100u8: "entries"}, // format = CODEPOINT_BIT_1 | ID_DELTA + {0b00010100u8: "entries[0]"}, // format = CODEPOINT_BIT_1 | ID_DELTA (Int24::new(-1)), // id delta [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [0..17] // Entry id = 6 - 0b00100100u8, // format = CODEPOINT_BIT_2 | ID_DELTA - {(Int24::new(5)): "id delta"}, // id delta + {0b00100100u8: "entries[1]"}, // format = CODEPOINT_BIT_2 | ID_DELTA + {(Int24::new(5)): "id delta"}, // id delta 5u16, // bias [0b00001101, 0b00000011, 0b00110001u8], // codepoints = [5..22] // Entry id = 14 - 0b01000100u8, // format = ID_DELTA | IGNORED + {0b01000100u8: "entries[2]"}, // format = ID_DELTA | IGNORED {(Int24::new(7)): "id delta - ignored entry"}, // id delta // Entry id = 15 - 0b00101000u8, // format = CODEPOINT_BIT_2 | PATCH_ENCODING + {0b00101000u8: "entries[3]"}, // format = CODEPOINT_BIT_2 | PATCH_ENCODING {3u8: "entry[4] encoding"}, // patch encoding = Glyph Keyed 10u16, // bias [0b00001101, 0b00000011, 0b00110001u8] // codepoints = [10..27] }; - let offset = buffer.offset_for("entries") as u32; + let offset = buffer.offset_for("entries[0]") as u32; buffer.write_at("entries_offset", offset); buffer @@ -654,7 +654,10 @@ where } pub fn test_font_for_patching() -> Vec { - test_font_for_patching_with_loca_mod(|_| {}, Default::default()) + test_font_for_patching_with_loca_mod( + |_| {}, + HashMap::from([(Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice())]), + ) } // Format specification: https://w3c.github.io/IFT/Overview.html#glyph-keyed diff --git a/incremental-font-transfer/src/font_patch.rs b/incremental-font-transfer/src/font_patch.rs index b3c42366c..e8079a844 100644 --- a/incremental-font-transfer/src/font_patch.rs +++ b/incremental-font-transfer/src/font_patch.rs @@ -142,9 +142,9 @@ impl IncrementalFontPatchBase for FontRef<'_> { let mut cached_compat_ids: HashMap> = Default::default(); - let mut raw_patches: Vec> = vec![]; - for (patch, patch_data) in patches { - let tag = patch.tag(); + let mut raw_patches: Vec<(&PatchInfo, GlyphKeyedPatch<'_>)> = vec![]; + for (patch_info, patch_data) in patches { + let tag = patch_info.tag(); let font_compat_id = cached_compat_ids .entry(tag.tag()) .or_insert_with(|| { @@ -164,7 +164,7 @@ impl IncrementalFontPatchBase for FontRef<'_> { return Err(PatchingError::IncompatiblePatch); } - raw_patches.push(patch); + raw_patches.push((patch_info, patch)); } apply_glyph_keyed_patches(&raw_patches, self) @@ -220,7 +220,8 @@ mod tests { let info: PatchInfo = PatchUri::from_index( "foo.bar/{id}", 0, - &IftTableTag::Ift(CompatibilityId::from_u32s([1, 2, 3, 4])), + IftTableTag::Ift(CompatibilityId::from_u32s([1, 2, 3, 4])), + 0, TableKeyed { fully_invalidating: false, }, @@ -252,7 +253,8 @@ mod tests { let info: PatchInfo = PatchUri::from_index( "foo.bar/{id}", 0, - &IftTableTag::Ift(CompatibilityId::from_u32s([2, 2, 3, 4])), + IftTableTag::Ift(CompatibilityId::from_u32s([2, 2, 3, 4])), + 0, TableKeyed { fully_invalidating: false, }, @@ -277,7 +279,8 @@ mod tests { let info: PatchInfo = PatchUri::from_index( "foo.bar/{id}", 0, - &IftTableTag::Ift(CompatibilityId::from_u32s([1, 2, 3, 4])), + IftTableTag::Ift(CompatibilityId::from_u32s([1, 2, 3, 4])), + 0, GlyphKeyed, ) .into(); @@ -303,7 +306,8 @@ mod tests { let info: PatchInfo = PatchUri::from_index( "foo.bar/{id}", 0, - &IftTableTag::Ift(CompatibilityId::from_u32s([6, 7, 9, 9])), + IftTableTag::Ift(CompatibilityId::from_u32s([6, 7, 9, 9])), + 0, GlyphKeyed, ) .into(); diff --git a/incremental-font-transfer/src/glyph_keyed.rs b/incremental-font-transfer/src/glyph_keyed.rs index ae82fcee2..0881522ab 100644 --- a/incremental-font-transfer/src/glyph_keyed.rs +++ b/incremental-font-transfer/src/glyph_keyed.rs @@ -6,8 +6,9 @@ //! //! Glyph Keyed patches are specified here: //! -use crate::font_patch::PatchingError; +use crate::patchmap::IftTableTag; use crate::table_keyed::copy_unprocessed_tables; +use crate::{font_patch::PatchingError, patch_group::PatchInfo}; use font_types::Scalar; use read_fonts::{ @@ -28,12 +29,12 @@ use std::ops::RangeInclusive; use write_fonts::FontBuilder; pub(crate) fn apply_glyph_keyed_patches( - patches: &[GlyphKeyedPatch<'_>], + patches: &[(&PatchInfo, GlyphKeyedPatch<'_>)], font: &FontRef, ) -> Result, PatchingError> { let mut decompression_buffer: Vec> = Vec::with_capacity(patches.len()); - for patch in patches { + for (_, patch) in patches { if patch.format() != Tag::new(b"ifgk") { return Err(PatchingError::InvalidPatch("Patch file tag is not 'ifgk'")); } @@ -51,7 +52,7 @@ pub(crate) fn apply_glyph_keyed_patches( let mut glyph_patches: Vec> = vec![]; for (raw_data, patch) in decompression_buffer.iter().zip(patches) { glyph_patches.push( - GlyphPatches::read(FontData::new(raw_data), patch.flags()) + GlyphPatches::read(FontData::new(raw_data), patch.1.flags()) .map_err(PatchingError::PatchParsingFailed)?, ); } @@ -65,7 +66,8 @@ pub(crate) fn apply_glyph_keyed_patches( PatchingError::FontParsingFailed(ReadError::MalformedData("Font has no glyphs.")), )? as u32); - let mut processed_tables = BTreeSet::::new(); + // IFT and IFTX tables will be modified and then copied below. + let mut processed_tables = BTreeSet::from([Tag::new(b"IFT "), Tag::new(b"IFTX")]); let mut font_builder = FontBuilder::new(); for table_tag in table_tag_list(&glyph_patches)? { @@ -100,7 +102,32 @@ pub(crate) fn apply_glyph_keyed_patches( } } - // TODO(garretrieger): mark the patch applied in the appropriate IFT table. + // Mark patches applied in IFT and IFTX as needed, copy the modified tables into the font builder. + let mut new_itf_data = font + .table_data(Tag::new(b"IFT ")) + .map(|data| data.as_bytes().to_vec()); + let mut new_itfx_data = font + .table_data(Tag::new(b"IFTX")) + .map(|data| data.as_bytes().to_vec()); + for (info, _) in patches { + let data = match info.tag() { + IftTableTag::Ift(_) => new_itf_data.as_mut().ok_or(PatchingError::InternalError)?, + IftTableTag::Iftx(_) => new_itfx_data.as_mut().ok_or(PatchingError::InternalError)?, + }; + let byte_index = info.application_flag_bit_index() / 8; + let bit_index = (info.application_flag_bit_index() % 8) as u8; + let byte = data + .get_mut(byte_index) + .ok_or(PatchingError::InternalError)?; + *byte |= 1 << bit_index; + } + + if let Some(data) = new_itf_data { + font_builder.add_raw(Tag::new(b"IFT "), data); + } + if let Some(data) = new_itfx_data { + font_builder.add_raw(Tag::new(b"IFTX"), data); + } copy_unprocessed_tables(font, processed_tables, &mut font_builder); @@ -418,12 +445,16 @@ fn patch_glyf_and_loca<'a>( #[cfg(test)] pub(crate) mod tests { - use std::{collections::BTreeSet, io::Write}; + use std::{ + collections::{BTreeSet, HashMap}, + io::Write, + }; use brotlic::CompressorWriter; use read_fonts::{ - tables::ift::GlyphKeyedPatch, test_helpers::BeBuffer, FontData, FontRead, ReadError, - TableProvider, + tables::ift::{CompatibilityId, GlyphKeyedPatch}, + test_helpers::BeBuffer, + FontData, FontRead, ReadError, TableProvider, }; use font_test_data::ift::{ @@ -433,7 +464,13 @@ pub(crate) mod tests { }; use skrifa::{FontRef, Tag}; - use crate::{font_patch::PatchingError, glyph_keyed::apply_glyph_keyed_patches}; + use crate::{ + font_patch::PatchingError, + glyph_keyed::apply_glyph_keyed_patches, + patchmap::{PatchEncoding, PatchUri}, + }; + + use super::{IftTableTag, PatchInfo}; pub(crate) fn assemble_glyph_keyed_patch(mut header: BeBuffer, payload: BeBuffer) -> BeBuffer { let payload_data: &[u8] = &payload; @@ -467,6 +504,15 @@ pub(crate) mod tests { } } + fn patch_info(tag: Tag, bit_index: usize) -> PatchInfo { + let source = match &tag.to_be_bytes() { + b"IFT " => IftTableTag::Ift(CompatibilityId::from_u32s([0, 0, 0, 0])), + b"IFTX" => IftTableTag::Iftx(CompatibilityId::from_u32s([0, 0, 0, 0])), + _ => panic!("Unexpected tag value."), + }; + PatchUri::from_index("", 0, source, bit_index, PatchEncoding::GlyphKeyed).into() + } + #[test] fn noop_glyph_keyed() { let patch = @@ -477,10 +523,18 @@ pub(crate) mod tests { let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); - let patched = apply_glyph_keyed_patches(&[patch], &font).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 4); + + let patched = apply_glyph_keyed_patches(&[(&patch_info, patch)], &font).unwrap(); let patched = FontRef::new(&patched).unwrap(); - check_tables_equal(&font, &patched, BTreeSet::default()); + // Application bit will be set in the patched font. + let expected_font = test_font_for_patching_with_loca_mod( + |_| {}, + HashMap::from([(Tag::new(b"IFT "), vec![0b0001_0000, 0, 0, 0].as_slice())]), + ); + let expected_font = FontRef::new(&expected_font).unwrap(); + check_tables_equal(&expected_font, &patched, BTreeSet::default()); } #[test] @@ -493,9 +547,13 @@ pub(crate) mod tests { let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); - let patched = apply_glyph_keyed_patches(&[patch], &font).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 28); + let patched = apply_glyph_keyed_patches(&[(&patch_info, patch)], &font).unwrap(); let patched = FontRef::new(&patched).unwrap(); + let new_ift: &[u8] = patched.table_data(Tag::new(b"IFT ")).unwrap().as_bytes(); + assert_eq!(&[0, 0, 0, 0b0001_0000], new_ift); + let new_glyf: &[u8] = patched.table_data(Tag::new(b"glyf")).unwrap().as_bytes(); assert_eq!( &[ @@ -537,7 +595,7 @@ pub(crate) mod tests { check_tables_equal( &font, &patched, - [Tag::new(b"glyf"), Tag::new(b"loca")].into(), + [Tag::new(b"IFT "), Tag::new(b"glyf"), Tag::new(b"loca")].into(), ); } @@ -547,18 +605,28 @@ pub(crate) mod tests { assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches()); let patch: &[u8] = &patch; let patch1 = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info_1 = patch_info(Tag::new(b"IFTX"), 13); let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches_2()); let patch: &[u8] = &patch; let patch2 = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info_2 = patch_info(Tag::new(b"IFTX"), 28); - let font = test_font_for_patching(); + let font = test_font_for_patching_with_loca_mod( + |_| {}, + HashMap::from([(Tag::new(b"IFTX"), vec![0, 0, 0, 0].as_slice())]), + ); let font = FontRef::new(&font).unwrap(); - let patched = apply_glyph_keyed_patches(&[patch2, patch1], &font).unwrap(); + let patched = + apply_glyph_keyed_patches(&[(&patch_info_2, patch2), (&patch_info_1, patch1)], &font) + .unwrap(); let patched = FontRef::new(&patched).unwrap(); + let new_ift: &[u8] = patched.table_data(Tag::new(b"IFTX")).unwrap().as_bytes(); + assert_eq!(&[0, 0b0010_0000, 0, 0b0001_0000], new_ift); + let new_glyf: &[u8] = patched.table_data(Tag::new(b"glyf")).unwrap().as_bytes(); assert_eq!( &[ @@ -602,7 +670,7 @@ pub(crate) mod tests { check_tables_equal( &font, &patched, - [Tag::new(b"glyf"), Tag::new(b"loca")].into(), + [Tag::new(b"glyf"), Tag::new(b"loca"), Tag::new(b"IFTX")].into(), ); } @@ -613,12 +681,13 @@ pub(crate) mod tests { let patch = assemble_glyph_keyed_patch(header_builder, glyf_u16_glyph_patches()); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::InvalidPatch("Patch file tag is not 'ifgk'")) ); } @@ -631,12 +700,13 @@ pub(crate) mod tests { ); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::InvalidPatch( "CFF, CFF2, and gvar patches are not yet supported." )) @@ -651,11 +721,12 @@ pub(crate) mod tests { let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); - let patched = apply_glyph_keyed_patches(&[patch], &font).unwrap(); + let patched = apply_glyph_keyed_patches(&[(&patch_info, patch)], &font).unwrap(); let patched = FontRef::new(&patched).unwrap(); let new_glyf: &[u8] = patched.table_data(Tag::new(b"glyf")).unwrap().as_bytes(); @@ -698,7 +769,7 @@ pub(crate) mod tests { check_tables_equal( &font, &patched, - [Tag::new(b"glyf"), Tag::new(b"loca")].into(), + [Tag::new(b"glyf"), Tag::new(b"loca"), Tag::new(b"IFT ")].into(), ); } @@ -709,12 +780,13 @@ pub(crate) mod tests { let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::InvalidPatch( "Duplicate or unsorted table tag." )) @@ -728,12 +800,13 @@ pub(crate) mod tests { let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::InvalidPatch( "Duplicate or unsorted table tag." )) @@ -747,12 +820,13 @@ pub(crate) mod tests { let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::PatchParsingFailed(ReadError::MalformedData( "Glyph IDs are unsorted or duplicated." ))), @@ -766,12 +840,13 @@ pub(crate) mod tests { let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::PatchParsingFailed(ReadError::MalformedData( "Glyph IDs are unsorted or duplicated." ))), @@ -786,12 +861,13 @@ pub(crate) mod tests { patch.write_at("max_uncompressed_length", len as u32 - 1); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::InvalidPatch("Max size exceeded.")), ); } @@ -803,12 +879,13 @@ pub(crate) mod tests { let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); let font = test_font_for_patching(); let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::InvalidPatch( "Patch would add a glyph beyond this fonts maximum." )), @@ -821,6 +898,7 @@ pub(crate) mod tests { assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches()); let patch: &[u8] = &patch; let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap(); + let patch_info = patch_info(Tag::new(b"IFT "), 0); // unorder offsets related to a glyph not being replaced let font = test_font_for_patching_with_loca_mod( @@ -836,7 +914,7 @@ pub(crate) mod tests { let font = FontRef::new(&font).unwrap(); assert_eq!( - apply_glyph_keyed_patches(&[patch], &font), + apply_glyph_keyed_patches(&[(&patch_info, patch)], &font), Err(PatchingError::FontParsingFailed(ReadError::MalformedData( "loca contains unordered offsets." ))), diff --git a/incremental-font-transfer/src/patch_group.rs b/incremental-font-transfer/src/patch_group.rs index c52abc478..16f5d99a3 100644 --- a/incremental-font-transfer/src/patch_group.rs +++ b/incremental-font-transfer/src/patch_group.rs @@ -37,6 +37,7 @@ impl<'a> PatchGroup<'a> { }); } + // TODO(garretrieger): following the spec disallow cases where both tables have the same compat id. let compat_group = Self::select_next_patches_from_candidates( candidates, ift_font.ift().ok().map(|t| t.compatibility_id()), @@ -121,6 +122,9 @@ impl<'a> PatchGroup<'a> { ift_compat_id: Option, iftx_compat_id: Option, ) -> Result { + // TODO(garretrieger): disallow the case where IFT and IFTX have the same compat id. Make it an error in the + // specification. + // Some notes about this implementation: // - From candidates we need to form the largest possible group of patches which follow the selection criteria // from: https://w3c.github.io/IFT/Overview.html#extend-font-subset and won't invalidate each other. @@ -306,7 +310,7 @@ pub enum UriStatus { pub(crate) struct PatchInfo { uri: String, source_table: IftTableTag, - // TODO: details for how to mark the patch applied in the mapping table (ie. bit index to flip). + application_flag_bit_index: usize, // TODO: Signals for heuristic patch selection: } @@ -314,12 +318,17 @@ impl PatchInfo { pub(crate) fn tag(&self) -> &IftTableTag { &self.source_table } + + pub(crate) fn application_flag_bit_index(&self) -> usize { + self.application_flag_bit_index + } } impl From for PatchInfo { fn from(value: PatchUri) -> Self { PatchInfo { uri: value.uri_string(), + application_flag_bit_index: value.application_flag_bit_index(), source_table: value.source_table(), } } @@ -439,7 +448,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 1, - &IftTableTag::Ift(cid_1()), + IftTableTag::Ift(cid_1()), + 42, PatchEncoding::TableKeyed { fully_invalidating: true, }, @@ -450,7 +460,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 2, - &IftTableTag::Ift(cid_1()), + IftTableTag::Ift(cid_1()), + 42, PatchEncoding::TableKeyed { fully_invalidating: false, }, @@ -461,7 +472,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 2, - &IftTableTag::Iftx(cid_2()), + IftTableTag::Iftx(cid_2()), + 42, PatchEncoding::TableKeyed { fully_invalidating: false, }, @@ -472,7 +484,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 2, - &IftTableTag::Iftx(cid_2()), + IftTableTag::Iftx(cid_2()), + 42, PatchEncoding::GlyphKeyed, ) } @@ -481,7 +494,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 2, - &IftTableTag::Ift(cid_2()), + IftTableTag::Ift(cid_2()), + 42, PatchEncoding::TableKeyed { fully_invalidating: false, }, @@ -492,7 +506,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 3, - &IftTableTag::Iftx(cid_2()), + IftTableTag::Iftx(cid_2()), + 42, PatchEncoding::TableKeyed { fully_invalidating: false, }, @@ -503,7 +518,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 3, - &IftTableTag::Ift(cid_1()), + IftTableTag::Ift(cid_1()), + 42, PatchEncoding::GlyphKeyed, ) } @@ -512,7 +528,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 4, - &IftTableTag::Ift(cid_1()), + IftTableTag::Ift(cid_1()), + 42, PatchEncoding::GlyphKeyed, ) } @@ -521,7 +538,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 4, - &IftTableTag::Iftx(cid_2()), + IftTableTag::Iftx(cid_2()), + 42, PatchEncoding::GlyphKeyed, ) } @@ -530,7 +548,8 @@ mod tests { PatchUri::from_index( "//foo.bar/{id}", 5, - &IftTableTag::Iftx(cid_2()), + IftTableTag::Iftx(cid_2()), + 42, PatchEncoding::GlyphKeyed, ) } @@ -538,6 +557,7 @@ mod tests { fn patch_info_ift(uri: &str) -> PatchInfo { PatchInfo { uri: uri.to_string(), + application_flag_bit_index: 42, source_table: IftTableTag::Ift(cid_1()), } } @@ -545,6 +565,7 @@ mod tests { fn patch_info_ift_c2(uri: &str) -> PatchInfo { PatchInfo { uri: uri.to_string(), + application_flag_bit_index: 42, source_table: IftTableTag::Ift(cid_2()), } } @@ -552,6 +573,7 @@ mod tests { fn patch_info_iftx(uri: &str) -> PatchInfo { PatchInfo { uri: uri.to_string(), + application_flag_bit_index: 42, source_table: IftTableTag::Iftx(cid_2()), } } @@ -1258,7 +1280,9 @@ mod tests { ("foo/08".to_string(), UriStatus::Applied,), ]) ); - } - // TODO(garretrieger): test that previously applied patches are ignored. + // there should be no more applicable patches left now. + let g = PatchGroup::select_next_patches(new_font, &s).unwrap(); + assert!(!g.has_uris()); + } } diff --git a/incremental-font-transfer/src/patchmap.rs b/incremental-font-transfer/src/patchmap.rs index e6b6eddba..eaa83b627 100644 --- a/incremental-font-transfer/src/patchmap.rs +++ b/incremental-font-transfer/src/patchmap.rs @@ -112,13 +112,22 @@ fn add_intersecting_format1_patches( intersect_format1_feature_map(map, features, &mut entries)?; // Step 3: produce final output. + let applied_entries_start_bit_index = map.shape().applied_entries_bitmap_byte_range().start * 8; patches.extend( entries .iter() // Entry 0 is the entry for codepoints already in the font, so it's always considered applied and skipped. .filter(|index| *index > 0) .filter(|index| !map.is_entry_applied(*index)) - .map(|index| PatchUri::from_index(uri_template, index as u32, source_table, encoding)), + .map(|index| { + PatchUri::from_index( + uri_template, + index as u32, + source_table.clone(), + applied_entries_start_bit_index + index as usize, + encoding, + ) + }), ); Ok(()) } @@ -298,20 +307,25 @@ fn decode_format2_entries( let mut entries_data = FontData::new(entries_data); let mut entries: Vec = vec![]; + let mut entry_start_byte = map.entries_offset().to_u32() as usize; + let mut id_string_data = map .entry_id_string_data() .transpose()? .map(|table| table.id_data()) .map(Cursor::new); while entry_count > 0 { - entries_data = decode_format2_entry( + let consumed_bytes; + (entries_data, consumed_bytes) = decode_format2_entry( entries_data, + entry_start_byte, source_table, uri_template, &default_encoding, &mut id_string_data, &mut entries, )?; + entry_start_byte += consumed_bytes; entry_count -= 1; } @@ -320,17 +334,27 @@ fn decode_format2_entries( fn decode_format2_entry<'a>( data: FontData<'a>, + data_start_index: usize, source_table: &IftTableTag, uri_template: &str, default_encoding: &PatchEncoding, id_string_data: &mut Option>, entries: &mut Vec, -) -> Result, ReadError> { +) -> Result<(FontData<'a>, usize), ReadError> { let entry_data = EntryData::read( data, Offset32::new(if id_string_data.is_none() { 0 } else { 1 }), )?; - let mut entry = Entry::new(uri_template, source_table, default_encoding); + + // Record the index of the bit which when set causes this entry to be ignored. + // See: https://w3c.github.io/IFT/Overview.html#mapping-entry-formatflags + let ignored_bit_index = (data_start_index * 8) + 6; + let mut entry = Entry::new( + uri_template, + source_table, + ignored_bit_index, + default_encoding, + ); // Features if let Some(features) = entry_data.feature_tags() { @@ -393,7 +417,9 @@ fn decode_format2_entry<'a>( .contains(EntryFormatFlags::IGNORED); entries.push(entry); - Ok(FontData::new(remaining_data)) + + let consumed_bytes = entry_data.shape().codepoint_data_byte_range().end - remaining_data.len(); + Ok((FontData::new(remaining_data), consumed_bytes)) } fn format2_new_entry_id( @@ -575,8 +601,7 @@ pub struct PatchUri { id: PatchId, encoding: PatchEncoding, source_table: IftTableTag, - // TODO(garretrieger): add a resolve method which when supplied the bytes associated with the URL - // produces an object suitable for passing to the font_patch API. + application_flag_bit_index: usize, } impl PatchUri { @@ -622,6 +647,10 @@ impl PatchUri { self.source_table } + pub(crate) fn application_flag_bit_index(&self) -> usize { + self.application_flag_bit_index + } + fn count_leading_zeroes(id: &[u8]) -> usize { let mut leading_bytes = 0; for b in id { @@ -644,13 +673,15 @@ impl PatchUri { pub(crate) fn from_index( uri_template: &str, entry_index: u32, - source_table: &IftTableTag, + source_table: IftTableTag, + application_flag_bit_index: usize, encoding: PatchEncoding, ) -> PatchUri { PatchUri { template: uri_template.to_string(), id: PatchId::Numeric(entry_index), - source_table: source_table.clone(), + source_table, + application_flag_bit_index, encoding, } } @@ -713,7 +744,12 @@ struct Entry { } impl Entry { - fn new(template: &str, source_table: &IftTableTag, default_encoding: &PatchEncoding) -> Entry { + fn new( + template: &str, + source_table: &IftTableTag, + application_flag_bit_index: usize, + default_encoding: &PatchEncoding, + ) -> Entry { Entry { subset_definition: SubsetDefinition { codepoints: IntSet::empty(), @@ -722,7 +758,13 @@ impl Entry { }, ignored: false, - uri: PatchUri::from_index(template, 0, source_table, *default_encoding), + uri: PatchUri::from_index( + template, + 0, + source_table.clone(), + application_flag_bit_index, + *default_encoding, + ), } } @@ -795,12 +837,14 @@ mod tests { uri_template: &str, entry_id: &str, source_table: &IftTableTag, + application_flag_bit_index: usize, encoding: PatchEncoding, ) -> PatchUri { PatchUri { template: uri_template.to_string(), id: PatchId::String(entry_id.as_bytes().to_vec()), source_table: source_table.clone(), + application_flag_bit_index, encoding, } } @@ -835,11 +879,31 @@ mod tests { // TODO(garretrieger): fuzzer to check consistency vs intersecting "*" subset def. + #[derive(Copy, Clone)] + struct ExpectedEntry { + index: u32, + application_bit_index: usize, + } + + fn f1(index: u32) -> ExpectedEntry { + ExpectedEntry { + index, + application_bit_index: (index as usize) + 36 * 8, + } + } + + fn f2(index: u32, entry_start: usize) -> ExpectedEntry { + ExpectedEntry { + index, + application_bit_index: entry_start * 8 + 6, + } + } + fn test_intersection( font: &FontRef, codepoints: [u32; M], tags: [Tag; N], - expected_entries: [u32; O], + expected_entries: [ExpectedEntry; O], ) { test_design_space_intersection(font, codepoints, tags, [], expected_entries) } @@ -854,7 +918,7 @@ mod tests { codepoints: [u32; M], tags: [Tag; N], design_space: [(Tag, Vec>); O], - expected_entries: [u32; P], + expected_entries: [ExpectedEntry; P], ) { let patches = intersecting_patches( font, @@ -868,14 +932,20 @@ mod tests { let expected: Vec = expected_entries .iter() - .map(|index| { - PatchUri::from_index( - "ABCDEFɤ", - *index, - &IftTableTag::Ift(compat_id()), - PatchEncoding::GlyphKeyed, - ) - }) + .map( + |ExpectedEntry { + index, + application_bit_index, + }| { + PatchUri::from_index( + "ABCDEFɤ", + *index, + IftTableTag::Ift(compat_id()), + *application_bit_index, + PatchEncoding::GlyphKeyed, + ) + }, + ) .collect(); assert_eq!(patches, expected); @@ -884,7 +954,7 @@ mod tests { fn test_intersection_with_all( font: &FontRef, tags: [Tag; M], - expected_entries: [u32; N], + expected_entries: [ExpectedEntry; N], ) { let patches = intersecting_patches( font, @@ -898,14 +968,20 @@ mod tests { let expected: Vec = expected_entries .iter() - .map(|index| { - PatchUri::from_index( - "ABCDEFɤ", - *index, - &IftTableTag::Ift(compat_id()), - PatchEncoding::GlyphKeyed, - ) - }) + .map( + |ExpectedEntry { + index, + application_bit_index, + }| { + PatchUri::from_index( + "ABCDEFɤ", + *index, + IftTableTag::Ift(compat_id()), + *application_bit_index, + PatchEncoding::GlyphKeyed, + ) + }, + ) .collect(); assert_eq!(expected, patches); @@ -916,7 +992,8 @@ mod tests { PatchUri::from_index( template, value, - &IftTableTag::Ift(Default::default()), + IftTableTag::Ift(Default::default()), + 0, PatchEncoding::GlyphKeyed, ) .uri_string(), @@ -930,6 +1007,7 @@ mod tests { template, value, &IftTableTag::Ift(Default::default()), + 0, PatchEncoding::GlyphKeyed, ) .uri_string(), @@ -988,10 +1066,10 @@ mod tests { test_intersection(&font, [0x123], [], []); // 0x123 is not in the mapping test_intersection(&font, [0x13], [], []); // 0x13 maps to entry 0 test_intersection(&font, [0x12], [], []); // 0x12 maps to entry 1 which is applied - test_intersection(&font, [0x11], [], [2]); // 0x11 maps to entry 2 - test_intersection(&font, [0x11, 0x12, 0x123], [], [2]); + test_intersection(&font, [0x11], [], [f1(2)]); // 0x11 maps to entry 2 + test_intersection(&font, [0x11, 0x12, 0x123], [], [f1(2)]); - test_intersection_with_all(&font, [], [2]); + test_intersection_with_all(&font, [], [f1(2)]); } #[test] @@ -1133,10 +1211,10 @@ mod tests { test_intersection(&font, [], [], []); test_intersection(&font, [0x11], [], []); - test_intersection(&font, [0x12], [], [0x50]); - test_intersection(&font, [0x13, 0x15], [], [0x51, 0x12c]); + test_intersection(&font, [0x12], [], [f1(0x50)]); + test_intersection(&font, [0x13, 0x15], [], [f1(0x51), f1(0x12c)]); - test_intersection_with_all(&font, [], [0x50, 0x51, 0x12c]); + test_intersection_with_all(&font, [], [f1(0x50), f1(0x51), f1(0x12c)]); } #[test] @@ -1155,36 +1233,40 @@ mod tests { [Tag::new(b"liga"), Tag::new(b"dlig"), Tag::new(b"null")], [], ); - test_intersection(&font, [0x12], [], [0x50]); - test_intersection(&font, [0x12], [Tag::new(b"liga")], [0x50, 0x180]); + test_intersection(&font, [0x12], [], [f1(0x50)]); + test_intersection(&font, [0x12], [Tag::new(b"liga")], [f1(0x50), f1(0x180)]); test_intersection( &font, [0x13, 0x14], [Tag::new(b"liga")], - [0x51, 0x12c, 0x180, 0x181], + [f1(0x51), f1(0x12c), f1(0x180), f1(0x181)], ); test_intersection( &font, [0x13, 0x14], [Tag::new(b"dlig")], - [0x51, 0x12c, 0x190], + [f1(0x51), f1(0x12c), f1(0x190)], ); test_intersection( &font, [0x13, 0x14], [Tag::new(b"dlig"), Tag::new(b"liga")], - [0x51, 0x12c, 0x180, 0x181, 0x190], + [f1(0x51), f1(0x12c), f1(0x180), f1(0x181), f1(0x190)], ); - test_intersection(&font, [0x11], [Tag::new(b"null")], [0x12D]); - test_intersection(&font, [0x15], [Tag::new(b"liga")], [0x181]); + test_intersection(&font, [0x11], [Tag::new(b"null")], [f1(0x12D)]); + test_intersection(&font, [0x15], [Tag::new(b"liga")], [f1(0x181)]); - test_intersection_with_all(&font, [], [0x50, 0x51, 0x12c]); + test_intersection_with_all(&font, [], [f1(0x50), f1(0x51), f1(0x12c)]); test_intersection_with_all( &font, [Tag::new(b"liga")], - [0x50, 0x51, 0x12c, 0x180, 0x181], + [f1(0x50), f1(0x51), f1(0x12c), f1(0x180), f1(0x181)], + ); + test_intersection_with_all( + &font, + [Tag::new(b"dlig")], + [f1(0x50), f1(0x51), f1(0x12c), f1(0x190)], ); - test_intersection_with_all(&font, [Tag::new(b"dlig")], [0x50, 0x51, 0x12c, 0x190]); } #[test] @@ -1204,15 +1286,15 @@ mod tests { &font, [0x13, 0x14], [Tag::new(b"liga")], - [0x51, 0x12c, 0x190], + [f1(0x51), f1(0x12c), f1(0x190)], ); test_intersection( &font, [0x13, 0x14], [Tag::new(b"dlig")], - [0x51, 0x12c], // dlig is ignored since it's out of order. + [f1(0x51), f1(0x12c)], // dlig is ignored since it's out of order. ); - test_intersection(&font, [0x11], [Tag::new(b"null")], [0x12D]); + test_intersection(&font, [0x11], [Tag::new(b"null")], [f1(0x12D)]); } #[test] @@ -1232,9 +1314,9 @@ mod tests { &font, [0x13, 0x14], [Tag::new(b"liga")], - [0x51, 0x12c, 0x190], + [f1(0x51), f1(0x12c), f1(0x190)], ); - test_intersection(&font, [0x11], [Tag::new(b"null")], [0x12D]); + test_intersection(&font, [0x11], [Tag::new(b"null")], [f1(0x12D)]); } #[test] @@ -1322,13 +1404,16 @@ mod tests { ); let font = FontRef::new(&font_bytes).unwrap(); + let e1 = f2(1, codepoints_only_format2().offset_for("entries[0]")); + let e3 = f2(3, codepoints_only_format2().offset_for("entries[2]")); + let e4 = f2(4, codepoints_only_format2().offset_for("entries[3]")); test_intersection(&font, [], [], []); - test_intersection(&font, [0x02], [], [1]); - test_intersection(&font, [0x15], [], [3]); - test_intersection(&font, [0x07], [], [1, 3]); - test_intersection(&font, [80_007], [], [4]); + test_intersection(&font, [0x02], [], [e1]); + test_intersection(&font, [0x15], [], [e3]); + test_intersection(&font, [0x07], [], [e1, e3]); + test_intersection(&font, [80_007], [], [e4]); - test_intersection_with_all(&font, [], [1, 3, 4]); + test_intersection_with_all(&font, [], [e1, e3, e4]); } #[test] @@ -1340,17 +1425,30 @@ mod tests { ); let font = FontRef::new(&font_bytes).unwrap(); + let e1 = f2( + 1, + features_and_design_space_format2().offset_for("entries[0]"), + ); + let e2 = f2( + 2, + features_and_design_space_format2().offset_for("entries[1]"), + ); + let e3 = f2( + 3, + features_and_design_space_format2().offset_for("entries[2]"), + ); + test_intersection(&font, [], [], []); test_intersection(&font, [0x02], [], []); test_intersection(&font, [0x50], [Tag::new(b"rlig")], []); - test_intersection(&font, [0x02], [Tag::new(b"rlig")], [2]); + test_intersection(&font, [0x02], [Tag::new(b"rlig")], [e2]); test_design_space_intersection( &font, [0x02], [Tag::new(b"rlig")], [(Tag::new(b"wdth"), vec![0.7..=0.8])], - [2], + [e2], ); test_design_space_intersection( @@ -1358,14 +1456,14 @@ mod tests { [0x05], [Tag::new(b"smcp")], [(Tag::new(b"wdth"), vec![0.7..=0.8])], - [1], + [e1], ); test_design_space_intersection( &font, [0x05], [Tag::new(b"smcp")], [(Tag::new(b"wdth"), vec![0.2..=0.3])], - [3], + [e3], ); test_design_space_intersection( &font, @@ -1380,21 +1478,21 @@ mod tests { [0x05], [Tag::new(b"smcp")], [(Tag::new(b"wdth"), vec![0.2..=0.3, 0.7..=0.8])], - [1, 3], + [e1, e3], ); test_design_space_intersection( &font, [0x05], [Tag::new(b"smcp")], [(Tag::new(b"wdth"), vec![2.2..=2.3])], - [3], + [e3], ); test_design_space_intersection( &font, [0x05], [Tag::new(b"smcp")], [(Tag::new(b"wdth"), vec![2.2..=2.3, 1.2..=1.3])], - [3], + [e3], ); } @@ -1407,16 +1505,22 @@ mod tests { ); let font = FontRef::new(&font_bytes).unwrap(); + let e3 = f2(3, copy_indices_format2().offset_for("entries[2]")); + let e5 = f2(5, copy_indices_format2().offset_for("entries[4]")); + let e6 = f2(6, copy_indices_format2().offset_for("entries[5]")); + let e7 = f2(7, copy_indices_format2().offset_for("entries[6]")); + let e8 = f2(8, copy_indices_format2().offset_for("entries[7]")); + let e9 = f2(9, copy_indices_format2().offset_for("entries[8]")); test_intersection(&font, [], [], []); - test_intersection(&font, [0x05], [], [5, 9]); - test_intersection(&font, [0x65], [], [9]); + test_intersection(&font, [0x05], [], [e5, e9]); + test_intersection(&font, [0x65], [], [e9]); test_design_space_intersection( &font, [], [Tag::new(b"rlig")], [(Tag::new(b"wght"), vec![500.0..=500.0])], - [3, 6], + [e3, e6], ); test_design_space_intersection( @@ -1424,7 +1528,7 @@ mod tests { [0x05], [Tag::new(b"rlig")], [(Tag::new(b"wght"), vec![500.0..=500.0])], - [3, 5, 6, 7, 8, 9], + [e3, e5, e6, e7, e8, e9], ); } @@ -1437,7 +1541,11 @@ mod tests { ); let font = FontRef::new(&font_bytes).unwrap(); - test_intersection_with_all(&font, [], [0, 6, 15]); + let e0 = f2(0, custom_ids_format2().offset_for("entries[0]")); + let e6 = f2(6, custom_ids_format2().offset_for("entries[1]")); + let e15 = f2(15, custom_ids_format2().offset_for("entries[3]")); + + test_intersection_with_all(&font, [], [e0, e6, e15]); } #[test]