From 82b93a5b43bf8fa632d06b159504bb6a25dbe503 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 23 Oct 2023 16:36:58 +0100 Subject: [PATCH] [{read,write}-fonts] Allow DeltaSetIndexMap repeated trailing entries to be omitted Fixes #667 --- read-fonts/src/tables/variations.rs | 14 +++++++++++--- write-fonts/src/tables/variations.rs | 19 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/read-fonts/src/tables/variations.rs b/read-fonts/src/tables/variations.rs index 41c37c7eb..8a1816287 100644 --- a/read-fonts/src/tables/variations.rs +++ b/read-fonts/src/tables/variations.rs @@ -516,12 +516,20 @@ impl EntryFormat { impl<'a> DeltaSetIndexMap<'a> { /// Returns the delta set index for the specified value. pub fn get(&self, index: u32) -> Result { - let (entry_format, data) = match self { - Self::Format0(fmt) => (fmt.entry_format(), fmt.map_data()), - Self::Format1(fmt) => (fmt.entry_format(), fmt.map_data()), + let (entry_format, map_count, data) = match self { + Self::Format0(fmt) => (fmt.entry_format(), fmt.map_count() as u32, fmt.map_data()), + Self::Format1(fmt) => (fmt.entry_format(), fmt.map_count(), fmt.map_data()), }; let entry_size = entry_format.entry_size(); let data = FontData::new(data); + // "if an index into the mapping array is used that is greater than or equal to + // mapCount, then the last logical entry of the mapping array is used." + // https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats + // #associating-target-items-to-variation-data + let mut index = index; + if index >= map_count { + index = map_count.saturating_sub(1); + } let offset = index as usize * entry_size as usize; let entry = match entry_size { 1 => data.read_at::(offset)? as u32, diff --git a/write-fonts/src/tables/variations.rs b/write-fonts/src/tables/variations.rs index d9f0951af..f90b63a94 100644 --- a/write-fonts/src/tables/variations.rs +++ b/write-fonts/src/tables/variations.rs @@ -411,12 +411,21 @@ impl DeltaSetIndexMap { let outer_shift = 16 - inner_bits; let entry_size = fmt.entry_size(); assert!((1..=4).contains(&entry_size)); - let mut map_data: Vec = Vec::with_capacity(mapping.len() * entry_size as usize); - for idx in mapping { + + // omit trailing entries that are the same as the previous one; + // the last entry is assumed when index is >= map_count + let mut map_count = mapping.len(); + while map_count > 1 && mapping[map_count - 1] == mapping[map_count - 2] { + map_count -= 1; + } + + let mut map_data: Vec = Vec::with_capacity(map_count * entry_size as usize); + for idx in mapping.iter().take(map_count) { let idx = ((idx & 0xFFFF0000) >> outer_shift) | (idx & inner_mask); // append entry_size bytes to map_data in BigEndian order map_data.extend_from_slice(&idx.to_be_bytes()[4 - entry_size as usize..]); } + assert_eq!(map_data.len(), map_count * entry_size as usize); (fmt, map_data) } } @@ -429,7 +438,7 @@ where fn from_iter>(iter: T) -> Self { let mapping: Vec = iter.into_iter().map(|v| v.into()).collect(); let (fmt, map_data) = DeltaSetIndexMap::pack_map_data(&mapping); - let map_count = mapping.len(); + let map_count = map_data.len() / fmt.entry_size() as usize; let delta_set_index_map: DeltaSetIndexMap = if map_count <= u16::MAX as usize { DeltaSetIndexMap::format_0(fmt, map_count as u16, map_data) } else { @@ -591,8 +600,10 @@ mod tests { } #[rstest] + // Note how the packed data below is b"\x00\x01" and not b"\x00\x01\x01", for the + // repeated trailing values can be omitted #[case::one_byte_one_inner_bit( - vec![0, 1, 1], 0b00_0000, 1, 1, b"\x00\x01\x01", + vec![0, 1, 1], 0b00_0000, 1, 1, b"\x00\x01", )] #[case::one_byte_two_inner_bits( vec![0, 1, 2], 0b00_0001, 1, 2, b"\x00\x01\x02",