//! the [VARC (Variable Composite/Component)](https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md) table pub use super::layout::{Condition, CoverageTable}; use super::variations::PackedDeltas; pub use crate::ps::cff::index::Index2; use crate::types::{F2Dot14, Matrix}; #[cfg(feature = "libm")] #[allow(unused_imports)] use core_maths::*; include!("../../generated/generated_varc.rs"); /// Let's us call self.something().get(i) instead of get(self.something(), i) trait Get<'a> { fn get(self, nth: usize) -> Result<&'a [u8], ReadError>; } impl<'a> Get<'a> for Option, ReadError>> { fn get(self, nth: usize) -> Result<&'a [u8], ReadError> { self.transpose()? .ok_or(ReadError::NullOffset) .and_then(|index| index.get(nth).map_err(|_| ReadError::OutOfBounds)) } } impl Varc<'_> { /// Friendlier accessor than directly using raw data via [Index2] pub fn axis_indices(&self, nth: usize) -> Result, ReadError> { let raw = self.axis_indices_list().get(nth)?; Ok(PackedDeltas::consume_all(raw.into())) } /// Friendlier accessor than directly using raw data via [Index2] /// /// nth would typically be obtained by looking up a [GlyphId] in [Self::coverage]. pub fn glyph(&self, nth: usize) -> Result, ReadError> { let raw = Some(self.var_composite_glyphs()).get(nth)?; Ok(VarcGlyph { table: self, data: raw.into(), }) } } impl SparseVariationRegion<'_> { /// Computes a floating point scalar value for this sparse region and the /// specified normalized variation coordinates. pub fn compute_scalar_f32(&self, coords: &[F2Dot14]) -> f32 { let mut scalar = 1.0f32; for axis in self.region_axes() { let peak = axis.peak(); if peak == F2Dot14::ZERO { continue; } let axis_index = axis.axis_index() as usize; let coord = coords.get(axis_index).copied().unwrap_or(F2Dot14::ZERO); if coord == peak { continue; } if coord == F2Dot14::ZERO { return 0.0; } let start = axis.start(); let end = axis.end(); if start > peak || peak > end || (start < F2Dot14::ZERO && end > F2Dot14::ZERO) { continue; } if coord < start || coord > end { return 0.0; } else if coord < peak { // Use raw bits - scale factors cancel in the ratio let numerat = coord.to_bits() - start.to_bits(); if numerat == 0 { return 0.0; } let denom = peak.to_bits() - start.to_bits(); scalar *= numerat as f32 / denom as f32; } else { // Use raw bits - scale factors cancel in the ratio let numerat = end.to_bits() - coord.to_bits(); if numerat == 0 { return 0.0; } let denom = end.to_bits() - peak.to_bits(); scalar *= numerat as f32 / denom as f32; } } scalar } } /// A VARC glyph doesn't have any root level attributes, it's just a list of components /// /// pub struct VarcGlyph<'a> { table: &'a Varc<'a>, data: FontData<'a>, } impl<'a> VarcGlyph<'a> { /// pub fn components(&self) -> impl Iterator, ReadError>> { VarcComponentIter { table: self.table, cursor: self.data.cursor(), } } } struct VarcComponentIter<'a> { table: &'a Varc<'a>, cursor: Cursor<'a>, } impl<'a> Iterator for VarcComponentIter<'a> { type Item = Result, ReadError>; fn next(&mut self) -> Option { if self.cursor.is_empty() { return None; } Some(VarcComponent::parse(self.table, &mut self.cursor)) } } pub struct VarcComponent<'a> { flags: VarcFlags, gid: GlyphId, condition_index: Option, axis_indices_index: Option, axis_values: Option>, axis_values_var_index: Option, transform_var_index: Option, transform: DecomposedTransform, } impl<'a> VarcComponent<'a> { /// Requires access to VARC fields to fully parse. /// /// * HarfBuzz [VarComponent::get_path_at](https://github.com/harfbuzz/harfbuzz/blob/0c2f5ecd51d11e32836ee136a1bc765d650a4ec0/src/OT/Var/VARC/VARC.cc#L132) fn parse(table: &Varc, cursor: &mut Cursor<'a>) -> Result { let raw_flags = cursor.read_u32_var()?; let flags = VarcFlags::from_bits_truncate(raw_flags); // Ref https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-component-record // This is a GlyphID16 if GID_IS_24BIT bit of flags is clear, else GlyphID24. let gid = if flags.contains(VarcFlags::GID_IS_24BIT) { GlyphId::new(cursor.read::()?.to_u32()) } else { GlyphId::from(cursor.read::()?) }; let condition_index = if flags.contains(VarcFlags::HAVE_CONDITION) { Some(cursor.read_u32_var()?) } else { None }; let (axis_indices_index, axis_values) = if flags.contains(VarcFlags::HAVE_AXES) { // let axis_indices_index = cursor.read_u32_var()?; let num_axis_values = table .axis_indices(axis_indices_index as usize)? .count_or_compute(); // we need to consume num_axis_values entries in packed delta format let deltas = if num_axis_values > 0 { let Some(data) = cursor.remaining() else { return Err(ReadError::OutOfBounds); }; let deltas = PackedDeltas::new(data, num_axis_values); *cursor = deltas.iter().end(); // jump past the packed deltas Some(deltas) } else { None }; (Some(axis_indices_index), deltas) } else { (None, None) }; let axis_values_var_index = if flags.contains(VarcFlags::AXIS_VALUES_HAVE_VARIATION) { Some(cursor.read_u32_var()?) } else { None }; let transform_var_index = if flags.contains(VarcFlags::TRANSFORM_HAS_VARIATION) { Some(cursor.read_u32_var()?) } else { None }; let mut transform = DecomposedTransform::default(); if flags.intersects(VarcFlags::HAVE_TRANSLATE_X | VarcFlags::HAVE_TRANSLATE_Y) { if flags.contains(VarcFlags::HAVE_TRANSLATE_X) { transform.translate_x = cursor.read::()?.to_i16() as f32 } if flags.contains(VarcFlags::HAVE_TRANSLATE_Y) { transform.translate_y = cursor.read::()?.to_i16() as f32 } } if flags.contains(VarcFlags::HAVE_ROTATION) { transform.rotation = cursor.read::()?.to_f32() } if flags.intersects(VarcFlags::HAVE_SCALE_X | VarcFlags::HAVE_SCALE_Y) { if flags.contains(VarcFlags::HAVE_SCALE_X) { transform.scale_x = cursor.read::()?.to_f32() } transform.scale_y = if flags.contains(VarcFlags::HAVE_SCALE_Y) { cursor.read::()?.to_f32() } else { transform.scale_x }; } // Per the spec, SkewX/SkewY are encoded before TCenterX/TCenterY. // https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-component-record if flags.intersects(VarcFlags::HAVE_SKEW_X | VarcFlags::HAVE_SKEW_Y) { if flags.contains(VarcFlags::HAVE_SKEW_X) { transform.skew_x = cursor.read::()?.to_f32() } if flags.contains(VarcFlags::HAVE_SKEW_Y) { transform.skew_y = cursor.read::()?.to_f32() } } if flags.intersects(VarcFlags::HAVE_TCENTER_X | VarcFlags::HAVE_TCENTER_Y) { if flags.contains(VarcFlags::HAVE_TCENTER_X) { transform.center_x = cursor.read::()?.to_i16() as f32 } if flags.contains(VarcFlags::HAVE_TCENTER_Y) { transform.center_y = cursor.read::()?.to_i16() as f32 } } // Optional, process and discard one uint32var per each set bit in RESERVED_MASK. let reserved = raw_flags & VarcFlags::RESERVED_MASK.bits; if reserved != 0 { let num_reserved = reserved.count_ones(); for _ in 0..num_reserved { cursor.read_u32_var()?; } } Ok(VarcComponent { flags, gid, condition_index, axis_indices_index, axis_values, axis_values_var_index, transform_var_index, transform, }) } pub fn flags(&self) -> VarcFlags { self.flags } pub fn gid(&self) -> GlyphId { self.gid } pub fn condition_index(&self) -> Option { self.condition_index } pub fn transform(&self) -> &DecomposedTransform { &self.transform } pub fn axis_indices_index(&self) -> Option { self.axis_indices_index } pub fn axis_values(&self) -> Option<&PackedDeltas<'a>> { self.axis_values.as_ref() } pub fn axis_values_var_index(&self) -> Option { self.axis_values_var_index } pub fn transform_var_index(&self) -> Option { self.transform_var_index } } /// #[derive(Clone, Copy)] pub struct DecomposedTransform { translate_x: f32, translate_y: f32, rotation: f32, // multiples of Pi, counter-clockwise scale_x: f32, scale_y: f32, skew_x: f32, // multiples of Pi, clockwise skew_y: f32, // multiples of Pi, counter-clockwise center_x: f32, center_y: f32, } impl Default for DecomposedTransform { fn default() -> Self { Self { translate_x: 0.0, translate_y: 0.0, rotation: 0.0, scale_x: 1.0, scale_y: 1.0, skew_x: 0.0, skew_y: 0.0, center_x: 0.0, center_y: 0.0, } } } impl DecomposedTransform { pub fn translate_x(&self) -> f32 { self.translate_x } pub fn translate_y(&self) -> f32 { self.translate_y } pub fn rotation(&self) -> f32 { self.rotation } pub fn scale_x(&self) -> f32 { self.scale_x } pub fn scale_y(&self) -> f32 { self.scale_y } pub fn skew_x(&self) -> f32 { self.skew_x } pub fn skew_y(&self) -> f32 { self.skew_y } pub fn center_x(&self) -> f32 { self.center_x } pub fn center_y(&self) -> f32 { self.center_y } pub fn set_translate_x(&mut self, value: f32) { self.translate_x = value; } pub fn set_translate_y(&mut self, value: f32) { self.translate_y = value; } pub fn set_rotation(&mut self, value: f32) { self.rotation = value; } pub fn set_scale_x(&mut self, value: f32) { self.scale_x = value; } pub fn set_scale_y(&mut self, value: f32) { self.scale_y = value; } pub fn set_skew_x(&mut self, value: f32) { self.skew_x = value; } pub fn set_skew_y(&mut self, value: f32) { self.skew_y = value; } pub fn set_center_x(&mut self, value: f32) { self.center_x = value; } pub fn set_center_y(&mut self, value: f32) { self.center_y = value; } /// Convert decomposed form to 2x3 matrix form. /// /// The first two values are x,y x-basis vector, /// the second 2 values are x,y y-basis vector, and the third 2 are translation. /// /// In augmented matrix /// form, if this method returns `[a, b, c, d, e, f]` that is taken as: /// /// ```text /// | a c e | /// | b d f | /// | 0 0 1 | /// ``` /// /// References: /// FontTools Python implementation /// * Wikipedia [affine transformation](https://en.wikipedia.org/wiki/Affine_transformation) pub fn matrix(&self) -> Matrix { // Python: t.translate(self.translateX + self.tCenterX, self.translateY + self.tCenterY) let mut transform = Matrix::IDENTITY; transform.dx = self.translate_x + self.center_x; transform.dy = self.translate_y + self.center_y; // TODO: this produces very small floats for rotations, e.g. 90 degree rotation a basic scale // puts 1.2246467991473532e-16 into [0]. Should we special case? Round? // Python: t = t.rotate(self.rotation * math.pi) if self.rotation != 0.0 { let (s, c) = (self.rotation * core::f32::consts::PI).sin_cos(); transform *= Matrix::from_elements([c, s, -s, c, 0.0, 0.0]); } // Python: t = t.scale(self.scaleX, self.scaleY) if (self.scale_x, self.scale_y) != (1.0, 1.0) { transform *= Matrix::from_elements([self.scale_x, 0.0, 0.0, self.scale_y, 0.0, 0.0]); } // Python: t = t.skew(-self.skewX * math.pi, self.skewY * math.pi) if (self.skew_x, self.skew_y) != (0.0, 0.0) { transform *= Matrix::from_elements([ 1.0, (self.skew_y * core::f32::consts::PI).tan(), (-self.skew_x * core::f32::consts::PI).tan(), 1.0, 0.0, 0.0, ]) } // Python: t = t.translate(-self.tCenterX, -self.tCenterY) if (self.center_x, self.center_y) != (0.0, 0.0) { transform *= Matrix::from_elements([1.0, 0.0, 0.0, 1.0, -self.center_x, -self.center_y]); } transform } } impl<'a> MultiItemVariationData<'a> { /// An [Index2] where each item is a [PackedDeltas] pub fn delta_sets(&self) -> Result, ReadError> { Index2::read(self.raw_delta_sets().into()) } /// Read a specific delta set. /// /// Equivalent to calling [Self::delta_sets], fetching item i, and parsing as [PackedDeltas] pub fn delta_set(&self, i: usize) -> Result, ReadError> { let index = self.delta_sets()?; let raw_deltas = index.get(i).map_err(|_| ReadError::OutOfBounds)?; Ok(PackedDeltas::consume_all(raw_deltas.into())) } } #[cfg(test)] mod tests { use types::GlyphId16; use crate::types::F2Dot14; use crate::FontData; use crate::{FontRef, ReadError, TableProvider}; use super::{Condition, DecomposedTransform, Varc}; impl Varc<'_> { fn conditions(&self) -> impl Iterator> { self.condition_list() .expect("A condition list is present") .expect("We could read the condition list") .conditions() .iter() .enumerate() .map(|(i, c)| c.unwrap_or_else(|e| panic!("condition {i} {e}"))) } fn axis_indices_count(&self) -> Result { let Some(axis_indices_list) = self.axis_indices_list() else { return Ok(0); }; let axis_indices_list = axis_indices_list?; Ok(axis_indices_list.count() as usize) } } fn round6(v: f32) -> f32 { (v * 1_000_000.0).round() / 1_000_000.0 } fn coord(value: f32) -> F2Dot14 { F2Dot14::from_f32(value) } fn assert_close(actual: f32, expected: f32) { let diff = (actual - expected).abs(); assert!( diff <= 1e-6, "expected {expected}, got {actual}, diff {diff}" ); } fn sfnt_table_range(data: &[u8], tag: [u8; 4]) -> (usize, usize) { let font = FontRef::new(data).unwrap(); if let Some(rec) = font .table_directory() .table_records() .iter() .find(|rec| rec.tag() == tag) { return (rec.offset() as usize, rec.length() as usize); } panic!( "missing table {:?}", core::str::from_utf8(&tag).unwrap_or("????") ); } fn write_be_u32(dst: &mut [u8], value: u32) { dst.copy_from_slice(&value.to_be_bytes()); } fn read_be_u32(src: &[u8]) -> u32 { u32::from_be_bytes([src[0], src[1], src[2], src[3]]) } fn encode_u32_var(value: u32) -> Vec { if value < 0x80 { vec![value as u8] } else if value < 0x4000 { vec![0x80 | ((value >> 8) as u8), value as u8] } else if value < 0x20_0000 { vec![ 0xC0 | ((value >> 16) as u8), (value >> 8) as u8, value as u8, ] } else if value < 0x1000_0000 { vec![ 0xE0 | ((value >> 24) as u8), (value >> 16) as u8, (value >> 8) as u8, value as u8, ] } else { vec![ 0xF0, (value >> 24) as u8, (value >> 16) as u8, (value >> 8) as u8, value as u8, ] } } #[test] fn read_cjk_0x6868() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); table.coverage().unwrap(); // should have coverage } #[test] fn identify_all_conditional_types() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); // We should have all 5 condition types in order assert_eq!( (1..=5).collect::>(), table.conditions().map(|c| c.format()).collect::>() ); } #[test] fn read_condition_format1_axis_range() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); let Some(Condition::Format1AxisRange(condition)) = table.conditions().find(|c| c.format() == 1) else { panic!("No such item"); }; assert_eq!( (0, 0.5, 1.0), ( condition.axis_index(), condition.filter_range_min_value().to_f32(), condition.filter_range_max_value().to_f32(), ) ); } #[test] fn read_condition_format2_variable_value() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); let Some(Condition::Format2VariableValue(condition)) = table.conditions().find(|c| c.format() == 2) else { panic!("No such item"); }; assert_eq!((1, 2), (condition.default_value(), condition.var_index(),)); } #[test] fn read_condition_format3_and() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); let Some(Condition::Format3And(condition)) = table.conditions().find(|c| c.format() == 3) else { panic!("No such item"); }; // Should reference a format 1 and a format 2 assert_eq!( vec![1, 2], condition .conditions() .iter() .map(|c| c.unwrap().format()) .collect::>() ); } #[test] fn read_condition_format4_or() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); let Some(Condition::Format4Or(condition)) = table.conditions().find(|c| c.format() == 4) else { panic!("No such item"); }; // Should reference a format 1 and a format 2 assert_eq!( vec![1, 2], condition .conditions() .iter() .map(|c| c.unwrap().format()) .collect::>() ); } #[test] fn read_condition_format5_negate() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); let Some(Condition::Format5Negate(condition)) = table.conditions().find(|c| c.format() == 5) else { panic!("No such item"); }; // Should reference a format 1 assert_eq!(1, condition.condition().unwrap().format(),); } #[test] fn read_axis_indices_list() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); assert_eq!(table.axis_indices_count().unwrap(), 2); assert_eq!( vec![2, 3, 4, 5, 6], table.axis_indices(1).unwrap().iter().collect::>() ); } #[test] fn compute_sparse_region_scalar_handles_boundaries_and_products() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let varc = font.varc().unwrap(); let store = varc.multi_var_store().unwrap().unwrap(); let regions = store.region_list().unwrap(); let region_list = regions.regions(); let axis0_region = region_list.get(0).unwrap(); assert_close(axis0_region.compute_scalar_f32(&[coord(1.0)]), 1.0); assert_close(axis0_region.compute_scalar_f32(&[coord(0.5)]), 0.5); assert_close(axis0_region.compute_scalar_f32(&[F2Dot14::ZERO]), 0.0); assert_close(axis0_region.compute_scalar_f32(&[]), 0.0); assert_close(axis0_region.compute_scalar_f32(&[coord(-0.25)]), 0.0); let axis0_axis1_region = region_list.get(2).unwrap(); assert_close( axis0_axis1_region.compute_scalar_f32(&[coord(0.5), coord(0.25)]), 0.125, ); } #[test] fn axis_indices_offset_out_of_bounds_errors() { let mut bytes = font_test_data::varc::CJK_6868.to_vec(); let (varc_offset, varc_len) = sfnt_table_range(&bytes, *b"VARC"); // axis_indices_list_offset is the 5th u32 in the VARC header. let axis_indices_offset = varc_offset + 16; write_be_u32( &mut bytes[axis_indices_offset..axis_indices_offset + 4], (varc_len as u32).saturating_add(8), ); let font = FontRef::new(&bytes).unwrap(); let table = font.varc().unwrap(); assert!(matches!(table.axis_indices(0), Err(ReadError::OutOfBounds))); } #[test] fn var_composite_glyphs_offset_out_of_bounds_errors() { let mut bytes = font_test_data::varc::CJK_6868.to_vec(); let (varc_offset, varc_len) = sfnt_table_range(&bytes, *b"VARC"); // var_composite_glyphs_offset is the 6th u32 in the VARC header. let glyphs_offset = varc_offset + 20; write_be_u32( &mut bytes[glyphs_offset..glyphs_offset + 4], (varc_len as u32).saturating_add(8), ); let font = FontRef::new(&bytes).unwrap(); let table = font.varc().unwrap(); assert!(matches!(table.glyph(0), Err(ReadError::OutOfBounds))); } #[test] fn parse_component_with_missing_translate_data_errors() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); // flags=HAVE_TRANSLATE_X, gid=1, missing FWORD payload. let data = FontData::new(&[0x10, 0x00, 0x01]); let mut cursor = data.cursor(); assert!(matches!( super::VarcComponent::parse(&table, &mut cursor), Err(ReadError::OutOfBounds) )); } #[test] fn parse_component_with_invalid_axis_indices_index_errors() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); // flags=HAVE_AXES, gid=1, axis_indices_index=127 (out of range). let data = FontData::new(&[0x02, 0x00, 0x01, 0x7F]); let mut cursor = data.cursor(); assert!(matches!( super::VarcComponent::parse(&table, &mut cursor), Err(ReadError::OutOfBounds) )); } #[test] fn parse_component_reserved_fields_are_consumed() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); // flags = HAVE_TRANSLATE_X plus two reserved bits. let flags = 0x0001_8010_u32; let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(flags)); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&[0x00, 0x07]); // translate_x = 7 bytes.extend_from_slice(&encode_u32_var(1)); // reserved payload 1 bytes.extend_from_slice(&encode_u32_var(2)); // reserved payload 2 bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.transform().translate_x(), 7.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_reserved_fields_truncation_errors() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); // flags = HAVE_TRANSLATE_X plus two reserved bits, but only one reserved payload follows. let flags = 0x0001_8010_u32; let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(flags)); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&[0x00, 0x07]); // translate_x bytes.extend_from_slice(&encode_u32_var(1)); // only one reserved payload let data = FontData::new(&bytes); let mut cursor = data.cursor(); assert!(matches!( super::VarcComponent::parse(&table, &mut cursor), Err(ReadError::OutOfBounds) )); } #[test] fn parse_component_gid_is_24bit_path() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::GID_IS_24BIT.bits())); bytes.extend_from_slice(&[0x12, 0x34, 0x56]); bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.gid().to_u32(), 0x12_34_56); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_gid_is_24bit_truncation_errors() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::GID_IS_24BIT.bits())); bytes.extend_from_slice(&[0x12, 0x34]); // truncated uint24 let data = FontData::new(&bytes); let mut cursor = data.cursor(); assert!(matches!( super::VarcComponent::parse(&table, &mut cursor), Err(ReadError::OutOfBounds) )); } #[test] fn parse_component_with_axes_zero_count_does_not_consume_axis_values() { let mut bytes = font_test_data::varc::CJK_6868.to_vec(); let (varc_offset, _) = sfnt_table_range(&bytes, *b"VARC"); let axis_indices_rel = read_be_u32(&bytes[varc_offset + 16..varc_offset + 20]) as usize; let axis_indices_abs = varc_offset + axis_indices_rel; // Overwrite axis_indices_list with Index2 { count = 1, off_size = 1, offsets = [1, 1] }. // That single item is empty, so count_or_compute() must be zero. bytes[axis_indices_abs..axis_indices_abs + 7] .copy_from_slice(&[0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01]); let font = FontRef::new(&bytes).unwrap(); let table = font.varc().unwrap(); assert_eq!(table.axis_indices(0).unwrap().count_or_compute(), 0); // flags = HAVE_AXES | HAVE_TRANSLATE_X let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(0x12)); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(0)); // axis_indices_index, count is zero bytes.extend_from_slice(&[0x00, 0x05]); // translate_x bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.transform().translate_x(), 5.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_with_axes_nonzero_count_consumes_axis_values() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5); // flags = HAVE_AXES | HAVE_TRANSLATE_X let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(0x12)); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index, count is five bytes.push(0x04); // one I8 run, count = 5 bytes.extend_from_slice(&[1, 2, 3, 4, 5]); // packed axis values bytes.extend_from_slice(&[0x00, 0x09]); // translate_x bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.transform().translate_x(), 9.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_scale_x_only_applies_to_scale_y() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_SCALE_X.bits())); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&[0x08, 0x00]); // scale_x = 2.0 in F6Dot10 bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.transform().scale_x(), 2.0); assert_eq!(component.transform().scale_y(), 2.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_scale_x_and_scale_y_are_independent() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let flags = super::VarcFlags::HAVE_SCALE_X.bits() | super::VarcFlags::HAVE_SCALE_Y.bits(); let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(flags)); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&[0x08, 0x00]); // scale_x = 2.0 bytes.extend_from_slice(&[0x0C, 0x00]); // scale_y = 3.0 bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.transform().scale_x(), 2.0); assert_eq!(component.transform().scale_y(), 3.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_multibyte_condition_and_var_indices() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let flags = super::VarcFlags::HAVE_CONDITION.bits() | super::VarcFlags::AXIS_VALUES_HAVE_VARIATION.bits() | super::VarcFlags::TRANSFORM_HAS_VARIATION.bits() | super::VarcFlags::HAVE_TRANSLATE_X.bits(); let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(flags)); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(0x2345)); // condition_index bytes.extend_from_slice(&encode_u32_var(0x0123)); // axis_values_var_index bytes.extend_from_slice(&encode_u32_var(0x0222)); // transform_var_index bytes.extend_from_slice(&[0x00, 0x07]); // translate_x = 7 bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.condition_index(), Some(0x2345)); assert_eq!(component.axis_values_var_index(), Some(0x0123)); assert_eq!(component.transform_var_index(), Some(0x0222)); assert_eq!(component.transform().translate_x(), 7.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_truncated_multibyte_condition_index_errors() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_CONDITION.bits())); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.push(0x81); // truncated uint32var (needs one more byte) let data = FontData::new(&bytes); let mut cursor = data.cursor(); assert!(matches!( super::VarcComponent::parse(&table, &mut cursor), Err(ReadError::OutOfBounds) )); } #[test] fn parse_component_axes_with_var_index_and_zero_axis_count() { let mut bytes = font_test_data::varc::CJK_6868.to_vec(); let (varc_offset, _) = sfnt_table_range(&bytes, *b"VARC"); let axis_indices_rel = read_be_u32(&bytes[varc_offset + 16..varc_offset + 20]) as usize; let axis_indices_abs = varc_offset + axis_indices_rel; // One empty axis-indices entry at index 0. bytes[axis_indices_abs..axis_indices_abs + 7] .copy_from_slice(&[0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01]); let font = FontRef::new(&bytes).unwrap(); let table = font.varc().unwrap(); let flags = super::VarcFlags::HAVE_AXES.bits() | super::VarcFlags::AXIS_VALUES_HAVE_VARIATION.bits() | super::VarcFlags::HAVE_TRANSLATE_X.bits(); let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(flags)); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(0)); // axis_indices_index => zero-length axis list bytes.extend_from_slice(&encode_u32_var(0x0123)); // axis_values_var_index bytes.extend_from_slice(&[0x00, 0x06]); // translate_x = 6 bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.axis_indices_index(), Some(0)); assert!(component.axis_values().is_none()); assert_eq!(component.axis_values_var_index(), Some(0x0123)); assert_eq!(component.transform().translate_x(), 6.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } #[test] fn parse_component_axes_truncated_i16_payload_detected_on_fetch() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5); // flags = HAVE_AXES. let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_AXES.bits())); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => expects 5 values bytes.push(0x44); // I16 run, count = 5 bytes.extend_from_slice(&[0x00, 0x01, 0x00, 0x02, 0x00, 0x03]); // only 3 i16 values let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); let mut out = [0.0; 5]; assert!(matches!( component .axis_values() .unwrap() .fetcher() .add_to_f32_scaled(&mut out, 1.0), Err(ReadError::OutOfBounds) )); } #[test] fn parse_component_axes_truncated_i32_payload_detected_on_fetch() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5); // flags = HAVE_AXES. let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_AXES.bits())); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => expects 5 values bytes.push(0xC4); // I32 run, count = 5 bytes.extend_from_slice(&[ 0x00, 0x00, 0x00, 0x01, // only 1 i32 value 0x00, 0x00, 0x00, 0x02, ]); let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); let mut out = [0.0; 5]; assert!(matches!( component .axis_values() .unwrap() .fetcher() .add_to_f32_scaled(&mut out, 1.0), Err(ReadError::OutOfBounds) )); } #[test] fn parse_component_axes_payload_overshoot_causes_following_field_error() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5); // flags = HAVE_AXES | HAVE_TRANSLATE_X. let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var( super::VarcFlags::HAVE_AXES.bits() | super::VarcFlags::HAVE_TRANSLATE_X.bits(), )); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => expects 5 values bytes.push(0x44); // I16 run, count = 5 (needs 10 bytes) bytes.extend_from_slice(&[0x00, 0x01, 0x00, 0x02]); // too short bytes.extend_from_slice(&[0x00, 0x09]); // intended translate_x (should be unreachable) let data = FontData::new(&bytes); let mut cursor = data.cursor(); assert!(matches!( super::VarcComponent::parse(&table, &mut cursor), Err(ReadError::OutOfBounds) )); } #[test] fn generated_condition_varints_roundtrip() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let mut state = 0x1234_5678_u32; for _ in 0..256 { // xorshift32 state ^= state << 13; state ^= state >> 17; state ^= state << 5; let value = state; let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_CONDITION.bits())); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(value)); bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); assert_eq!(component.condition_index(), Some(value)); assert_eq!(cursor.read::().unwrap(), 0xAA); } } #[test] fn generated_axis_values_single_run_roundtrip() { let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap(); let table = font.varc().unwrap(); assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5); let mut state = 0xA5A5_5A5A_u32; for _ in 0..128 { // xorshift32 state ^= state << 13; state ^= state >> 17; state ^= state << 5; let run_kind = state & 3; let mut payload = Vec::new(); let mut expected = [0f32; 5]; match run_kind { 0 => { // I8, count = 5 payload.push(0x04); for out in &mut expected { state = state.wrapping_mul(1664525).wrapping_add(1013904223); let v = ((state >> 24) as i8) % 64; payload.push(v as u8); *out = v as f32; } } 1 => { // I16, count = 5 payload.push(0x44); for out in &mut expected { state = state.wrapping_mul(1664525).wrapping_add(1013904223); let v = ((state >> 16) as i16) % 2048; payload.extend_from_slice(&v.to_be_bytes()); *out = v as f32; } } 2 => { // I32, count = 5 payload.push(0xC4); for out in &mut expected { state = state.wrapping_mul(1664525).wrapping_add(1013904223); let v = (state as i32) % 1_000_000; payload.extend_from_slice(&v.to_be_bytes()); *out = v as f32; } } _ => { // Zero, count = 5 payload.push(0x84); } } let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var( super::VarcFlags::HAVE_AXES.bits() | super::VarcFlags::HAVE_TRANSLATE_X.bits(), )); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => 5 values bytes.extend_from_slice(&payload); bytes.extend_from_slice(&[0x00, 0x07]); // translate_x bytes.push(0xAA); // sentinel let data = FontData::new(&bytes); let mut cursor = data.cursor(); let component = super::VarcComponent::parse(&table, &mut cursor).unwrap(); let mut out = [0.0f32; 5]; component .axis_values() .unwrap() .fetcher() .add_to_f32_scaled(&mut out, 1.0) .unwrap(); assert_eq!(out, expected); assert_eq!(component.transform().translate_x(), 7.0); assert_eq!(cursor.read::().unwrap(), 0xAA); } } #[test] fn parse_component_truncated_condition_varint_boundaries_error() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let values = [0x80_u32, 0x4000_u32, 0x20_0000_u32, 0x1000_0000_u32]; for value in values { let encoded = encode_u32_var(value); assert!(encoded.len() > 1); let truncated = &encoded[..encoded.len() - 1]; let mut bytes = Vec::new(); bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_CONDITION.bits())); bytes.extend_from_slice(&[0x00, 0x01]); // gid bytes.extend_from_slice(truncated); let data = FontData::new(&bytes); let mut cursor = data.cursor(); assert!(matches!( super::VarcComponent::parse(&table, &mut cursor), Err(ReadError::OutOfBounds) )); } } #[test] fn read_glyph_6868() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let gid = font.cmap().unwrap().map_codepoint(0x6868_u32).unwrap(); let table = font.varc().unwrap(); let idx = table.coverage().unwrap().get(gid).unwrap(); let glyph = table.glyph(idx as usize).unwrap(); assert_eq!( vec![GlyphId16::new(2), GlyphId16::new(5), GlyphId16::new(7)], glyph .components() .map(|c| c.unwrap().gid) .collect::>() ); } // Expected created using the Python DecomposedTransform #[test] fn decomposed_scale_to_matrix() { let scale_x = 2.0; let scale_y = 3.0; assert_eq!( [scale_x, 0.0, 0.0, scale_y, 0.0, 0.0], DecomposedTransform { scale_x, scale_y, ..Default::default() } .matrix() .map(round6) .elements() ); } // Expected created using the Python DecomposedTransform #[test] fn decomposed_rotate_to_matrix() { assert_eq!( [0.0, 1.0, -1.0, 0.0, 0.0, 0.0], DecomposedTransform { // Rotation is in multiples of Pi (90 degrees = 0.5 * Pi). rotation: 0.5, ..Default::default() } .matrix() .map(round6) .elements() ); } // Expected created using the Python DecomposedTransform #[test] fn decomposed_skew_to_matrix() { // Skew is in multiples of Pi. let skew_x: f32 = 1.0 / 6.0; // 30 degrees let skew_y: f32 = -1.0 / 3.0; // -60 degrees assert_eq!( [ 1.0, round6((skew_y * core::f32::consts::PI).tan()), round6((-skew_x * core::f32::consts::PI).tan()), 1.0, 0.0, 0.0 ], DecomposedTransform { skew_x, skew_y, ..Default::default() } .matrix() .map(round6) .elements() ); } // Expected created using the Python DecomposedTransform #[test] fn decomposed_scale_rotate_to_matrix() { let scale_x = 2.0; let scale_y = 3.0; assert_eq!( [0.0, scale_x, -scale_y, 0.0, 0.0, 0.0], DecomposedTransform { scale_x, scale_y, // 90 degrees = 0.5 * Pi. rotation: 0.5, ..Default::default() } .matrix() .map(round6) .elements() ); } // Expected created using the Python DecomposedTransform #[test] fn decomposed_scale_rotate_translate_to_matrix() { assert_eq!( [0.0, 2.0, -1.0, 0.0, 10.0, 20.0], DecomposedTransform { scale_x: 2.0, // 90 degrees = 0.5 * Pi. rotation: 0.5, translate_x: 10.0, translate_y: 20.0, ..Default::default() } .matrix() .map(round6) .elements() ); } // Expected created using the Python DecomposedTransform #[test] fn decomposed_scale_skew_translate_to_matrix() { assert_eq!( [-0.866026, 5.5, -2.5, 2.020726, 10.0, 20.0], DecomposedTransform { scale_x: 2.0, scale_y: 3.0, // Angles are in multiples of Pi. rotation: 1.0 / 6.0, // 30 degrees skew_x: 1.0 / 6.0, // 30 degrees skew_y: 1.0 / 3.0, // 60 degrees translate_x: 10.0, translate_y: 20.0, ..Default::default() } .matrix() .map(round6) .elements() ); } // Expected created using the Python DecomposedTransform #[test] fn decomposed_rotate_around_to_matrix() { assert_eq!( [1.732051, 1.0, -0.5, 0.866025, 10.267949, 19.267949], DecomposedTransform { scale_x: 2.0, // 30 degrees = 1/6 * Pi. rotation: 1.0 / 6.0, translate_x: 10.0, translate_y: 20.0, center_x: 1.0, center_y: 2.0, ..Default::default() } .matrix() .map(round6) .elements() ); } #[test] fn read_multivar_store_region_list() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let varstore = table.multi_var_store().unwrap().unwrap(); let regions = varstore.region_list().unwrap().regions(); let sparse_regions = regions .iter() .map(|r| { r.unwrap() .region_axes() .iter() .map(|a| { ( a.axis_index(), a.start().to_f32(), a.peak().to_f32(), a.end().to_f32(), ) }) .collect::>() }) .collect::>(); // Check a sampling of the regions assert_eq!( vec![ vec![(0, 0.0, 1.0, 1.0),], vec![(0, 0.0, 1.0, 1.0), (1, 0.0, 1.0, 1.0),], vec![(6, -1.0, -1.0, 0.0),], ], [0, 2, 38] .into_iter() .map(|i| sparse_regions[i].clone()) .collect::>() ); } #[test] fn read_multivar_store_delta_sets() { let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap(); let table = font.varc().unwrap(); let varstore = table.multi_var_store().unwrap().unwrap(); assert_eq!( vec![(3, 6), (33, 6), (10, 5), (25, 8),], varstore .variation_data() .iter() .map(|d| d.unwrap()) .map(|d| (d.region_index_count(), d.delta_sets().unwrap().count())) .collect::>() ); assert_eq!( vec![-1, 33, 0, 0, 0, 0], varstore .variation_data() .get(0) .unwrap() .delta_set(5) .unwrap() .iter() .collect::>() ) } }