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");
trait Get<'a> {
fn get(self, nth: usize) -> Result<&'a [u8], ReadError>;
}
impl<'a> Get<'a> for Option<Result<Index2<'a>, 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<'_> {
pub fn axis_indices(&self, nth: usize) -> Result<PackedDeltas<'_>, ReadError> {
let raw = self.axis_indices_list().get(nth)?;
Ok(PackedDeltas::consume_all(raw.into()))
}
pub fn glyph(&self, nth: usize) -> Result<VarcGlyph<'_>, ReadError> {
let raw = Some(self.var_composite_glyphs()).get(nth)?;
Ok(VarcGlyph {
table: self,
data: raw.into(),
})
}
}
impl SparseVariationRegion<'_> {
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 {
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 {
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
}
}
pub struct VarcGlyph<'a> {
table: &'a Varc<'a>,
data: FontData<'a>,
}
impl<'a> VarcGlyph<'a> {
pub fn components(&self) -> impl Iterator<Item = Result<VarcComponent<'a>, 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<VarcComponent<'a>, ReadError>;
fn next(&mut self) -> Option<Self::Item> {
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<u32>,
axis_indices_index: Option<u32>,
axis_values: Option<PackedDeltas<'a>>,
axis_values_var_index: Option<u32>,
transform_var_index: Option<u32>,
transform: DecomposedTransform,
}
impl<'a> VarcComponent<'a> {
fn parse(table: &Varc, cursor: &mut Cursor<'a>) -> Result<Self, ReadError> {
let raw_flags = cursor.read_u32_var()?;
let flags = VarcFlags::from_bits_truncate(raw_flags);
let gid = if flags.contains(VarcFlags::GID_IS_24BIT) {
GlyphId::new(cursor.read::<Uint24>()?.to_u32())
} else {
GlyphId::from(cursor.read::<u16>()?)
};
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();
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(); 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::<FWord>()?.to_i16() as f32
}
if flags.contains(VarcFlags::HAVE_TRANSLATE_Y) {
transform.translate_y = cursor.read::<FWord>()?.to_i16() as f32
}
}
if flags.contains(VarcFlags::HAVE_ROTATION) {
transform.rotation = cursor.read::<F4Dot12>()?.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::<F6Dot10>()?.to_f32()
}
transform.scale_y = if flags.contains(VarcFlags::HAVE_SCALE_Y) {
cursor.read::<F6Dot10>()?.to_f32()
} else {
transform.scale_x
};
}
if flags.intersects(VarcFlags::HAVE_TCENTER_X | VarcFlags::HAVE_TCENTER_Y) {
if flags.contains(VarcFlags::HAVE_TCENTER_X) {
transform.center_x = cursor.read::<FWord>()?.to_i16() as f32
}
if flags.contains(VarcFlags::HAVE_TCENTER_Y) {
transform.center_y = cursor.read::<FWord>()?.to_i16() as f32
}
}
if flags.intersects(VarcFlags::HAVE_SKEW_X | VarcFlags::HAVE_SKEW_Y) {
if flags.contains(VarcFlags::HAVE_SKEW_X) {
transform.skew_x = cursor.read::<F4Dot12>()?.to_f32()
}
if flags.contains(VarcFlags::HAVE_SKEW_Y) {
transform.skew_y = cursor.read::<F4Dot12>()?.to_f32()
}
}
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<u32> {
self.condition_index
}
pub fn transform(&self) -> &DecomposedTransform {
&self.transform
}
pub fn axis_indices_index(&self) -> Option<u32> {
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<u32> {
self.axis_values_var_index
}
pub fn transform_var_index(&self) -> Option<u32> {
self.transform_var_index
}
}
#[derive(Clone, Copy)]
pub struct DecomposedTransform {
translate_x: f32,
translate_y: f32,
rotation: f32, scale_x: f32,
scale_y: f32,
skew_x: f32, skew_y: f32, 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;
}
pub fn matrix(&self) -> Matrix<f32> {
let mut transform = Matrix::IDENTITY;
transform.dx = self.translate_x + self.center_x;
transform.dy = self.translate_y + self.center_y;
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]);
}
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]);
}
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,
])
}
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> {
pub fn delta_sets(&self) -> Result<Index2<'a>, ReadError> {
Index2::read(self.raw_delta_sets().into())
}
pub fn delta_set(&self, i: usize) -> Result<PackedDeltas<'a>, 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<Item = Condition<'_>> {
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<usize, ReadError> {
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<u8> {
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(); }
#[test]
fn identify_all_conditional_types() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
assert_eq!(
(1..=5).collect::<Vec<_>>(),
table.conditions().map(|c| c.format()).collect::<Vec<_>>()
);
}
#[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");
};
assert_eq!(
vec![1, 2],
condition
.conditions()
.iter()
.map(|c| c.unwrap().format())
.collect::<Vec<_>>()
);
}
#[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");
};
assert_eq!(
vec![1, 2],
condition
.conditions()
.iter()
.map(|c| c.unwrap().format())
.collect::<Vec<_>>()
);
}
#[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");
};
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::<Vec<_>>()
);
}
#[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");
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");
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();
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();
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();
let flags = 0x0001_8010_u32;
let mut bytes = Vec::new();
bytes.extend_from_slice(&encode_u32_var(flags));
bytes.extend_from_slice(&[0x00, 0x01]); bytes.extend_from_slice(&[0x00, 0x07]); bytes.extend_from_slice(&encode_u32_var(1)); bytes.extend_from_slice(&encode_u32_var(2)); bytes.push(0xAA); 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::<u8>().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();
let flags = 0x0001_8010_u32;
let mut bytes = Vec::new();
bytes.extend_from_slice(&encode_u32_var(flags));
bytes.extend_from_slice(&[0x00, 0x01]); bytes.extend_from_slice(&[0x00, 0x07]); bytes.extend_from_slice(&encode_u32_var(1)); 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); 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::<u8>().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]); 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;
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);
let mut bytes = Vec::new();
bytes.extend_from_slice(&encode_u32_var(0x12));
bytes.extend_from_slice(&[0x00, 0x01]); bytes.extend_from_slice(&encode_u32_var(0)); bytes.extend_from_slice(&[0x00, 0x05]); bytes.push(0xAA); 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::<u8>().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);
let mut bytes = Vec::new();
bytes.extend_from_slice(&encode_u32_var(0x12));
bytes.extend_from_slice(&[0x00, 0x01]); bytes.extend_from_slice(&encode_u32_var(1)); bytes.push(0x04); bytes.extend_from_slice(&[1, 2, 3, 4, 5]); bytes.extend_from_slice(&[0x00, 0x09]); bytes.push(0xAA); 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::<u8>().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]); bytes.extend_from_slice(&[0x08, 0x00]); bytes.push(0xAA); 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::<u8>().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]); bytes.extend_from_slice(&[0x08, 0x00]); bytes.extend_from_slice(&[0x0C, 0x00]); bytes.push(0xAA); 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::<u8>().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]); bytes.extend_from_slice(&encode_u32_var(0x2345)); bytes.extend_from_slice(&encode_u32_var(0x0123)); bytes.extend_from_slice(&encode_u32_var(0x0222)); bytes.extend_from_slice(&[0x00, 0x07]); bytes.push(0xAA); 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::<u8>().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]); bytes.push(0x81); 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;
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]); bytes.extend_from_slice(&encode_u32_var(0)); bytes.extend_from_slice(&encode_u32_var(0x0123)); bytes.extend_from_slice(&[0x00, 0x06]); bytes.push(0xAA); 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::<u8>().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);
let mut bytes = Vec::new();
bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_AXES.bits()));
bytes.extend_from_slice(&[0x00, 0x01]); bytes.extend_from_slice(&encode_u32_var(1)); bytes.push(0x44); bytes.extend_from_slice(&[0x00, 0x01, 0x00, 0x02, 0x00, 0x03]); 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);
let mut bytes = Vec::new();
bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_AXES.bits()));
bytes.extend_from_slice(&[0x00, 0x01]); bytes.extend_from_slice(&encode_u32_var(1)); bytes.push(0xC4); bytes.extend_from_slice(&[
0x00, 0x00, 0x00, 0x01, 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);
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]); bytes.extend_from_slice(&encode_u32_var(1)); bytes.push(0x44); bytes.extend_from_slice(&[0x00, 0x01, 0x00, 0x02]); bytes.extend_from_slice(&[0x00, 0x09]); 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 {
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]); bytes.extend_from_slice(&encode_u32_var(value));
bytes.push(0xAA); 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::<u8>().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 {
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 => {
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 => {
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 => {
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;
}
}
_ => {
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]); bytes.extend_from_slice(&encode_u32_var(1)); bytes.extend_from_slice(&payload);
bytes.extend_from_slice(&[0x00, 0x07]); bytes.push(0xAA);
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::<u8>().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]); 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::<Vec<_>>()
);
}
#[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()
);
}
#[test]
fn decomposed_rotate_to_matrix() {
assert_eq!(
[0.0, 1.0, -1.0, 0.0, 0.0, 0.0],
DecomposedTransform {
rotation: 0.5,
..Default::default()
}
.matrix()
.map(round6)
.elements()
);
}
#[test]
fn decomposed_skew_to_matrix() {
let skew_x: f32 = 1.0 / 6.0; let skew_y: f32 = -1.0 / 3.0; 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()
);
}
#[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,
rotation: 0.5,
..Default::default()
}
.matrix()
.map(round6)
.elements()
);
}
#[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,
rotation: 0.5,
translate_x: 10.0,
translate_y: 20.0,
..Default::default()
}
.matrix()
.map(round6)
.elements()
);
}
#[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,
rotation: 1.0 / 6.0, skew_x: 1.0 / 6.0, skew_y: 1.0 / 3.0, translate_x: 10.0,
translate_y: 20.0,
..Default::default()
}
.matrix()
.map(round6)
.elements()
);
}
#[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,
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::<Vec<_>>()
})
.collect::<Vec<_>>();
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::<Vec<_>>()
);
}
#[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::<Vec<_>>()
);
assert_eq!(
vec![-1, 33, 0, 0, 0, 0],
varstore
.variation_data()
.get(0)
.unwrap()
.delta_set(5)
.unwrap()
.iter()
.collect::<Vec<_>>()
)
}
}