use super::*;
use crate::model::Error;
use crate::tables::layout::FeatureVariations;
use crate::tables::variations::ItemVariationStore;
use alloc::vec;
use alloc::vec::Vec;
#[test]
fn delta_callback_errors_survive_negation_and_no_variation_skips_callback() {
let bytes = [0, 5, 0, 0, 5, 0, 2, 0, 1, 0, 0, 0, 0];
let condition = Condition::read(FontData::new(&bytes)).unwrap();
assert_eq!(
condition.evaluate(&[], |_| Err::<f64, _>(Error::InvalidOperation)),
Err(Error::InvalidOperation)
);
let bytes = [0, 2, 0, 1, 255, 255, 255, 255];
let condition = Condition::read(FontData::new(&bytes)).unwrap();
assert_eq!(
condition.evaluate(&[], |_| Err::<f64, _>(Error::InvalidOperation)),
Ok(true)
);
}
#[test]
fn condition_sets_and_lookup_records_reject_bad_offsets() {
let bytes = [0, 1, 0, 0, 0, 6, 0, 5, 0, 0, 0];
let set = ConditionSet::read(FontData::new(&bytes)).unwrap();
assert_eq!(set.evaluate(&[], |_| Ok::<_, Error>(0.0)), Ok(false));
let truncated = ConditionSet::read(FontData::new(&bytes[..2])).unwrap();
assert_eq!(
truncated.evaluate(&[], |_| Ok::<_, Error>(0.0)),
Err(Error::Malformed)
);
let record = LookupConditionRecord {
condition_offset: crate::types::BigEndian::new([0, 0, 0, 6]),
lookup_index_list_offset: crate::types::Offset32::new(0).into(),
};
assert_eq!(
record.evaluate(FontData::new(&bytes), &[], |_| Ok::<_, Error>(0.0)),
Ok(false)
);
assert!(record
.evaluate(FontData::new(&bytes[..2]), &[], |_| Ok::<_, Error>(0.0))
.is_err());
let null = LookupConditionRecord {
condition_offset: crate::types::BigEndian::new([0; 4]),
lookup_index_list_offset: crate::types::Offset32::new(0).into(),
};
assert_eq!(
null.evaluate(FontData::new(&[]), &[], |_| Ok::<_, Error>(0.0)),
Ok(true)
);
}
fn evaluate(bytes: &[u8], coords: &[F2Dot14]) -> Option<bool> {
Condition::read(FontData::new(bytes))
.ok()?
.evaluate(coords, |_| Ok::<_, Error>(0.0))
.ok()
}
fn axis_range(axis: u16, min: f32, max: f32) -> Vec<u8> {
let mut bytes = vec![0, 1];
bytes.extend_from_slice(&axis.to_be_bytes());
bytes.extend_from_slice(&F2Dot14::from_f32(min).to_be_bytes());
bytes.extend_from_slice(&F2Dot14::from_f32(max).to_be_bytes());
bytes
}
#[test]
fn axis_ranges_include_the_endpoints_and_missing_axes_are_zero() {
let condition = axis_range(0, 0.25, 0.75);
for (coord, matches) in [
(0.0, false),
(0.25, true),
(0.5, true),
(0.75, true),
(1.0, false),
] {
assert_eq!(
evaluate(&condition, &[F2Dot14::from_f32(coord)]),
Some(matches)
);
}
assert_eq!(evaluate(&condition, &[]), Some(false));
assert_eq!(evaluate(&axis_range(5, 0.0, 0.0), &[]), Some(true));
}
#[test]
fn variable_values_keep_fractional_deltas_and_use_a_strict_positive_test() {
let bytes = [
0, 1, 0, 0, 0, 12, 0, 1, 0, 0, 0, 22, 0, 1, 0, 1, 0, 0, 0x40, 0, 0x40, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, ];
let store = ItemVariationStore::read(FontData::new(&bytes)).unwrap();
let condition = Condition::read(FontData::new(&[0, 2, 0, 0, 0, 0, 0, 0])).unwrap();
for (coord, matches) in [(-1.0, false), (0.0, false), (0.5, true), (1.0, true)] {
let coords = [F2Dot14::from_f32(coord)];
assert_eq!(
condition
.evaluate(&coords, store_deltas(Some(&store), &coords))
.ok(),
Some(matches)
);
}
for default_value in [-1i16, 0, 1] {
let mut bytes = vec![0, 2];
bytes.extend_from_slice(&default_value.to_be_bytes());
bytes.extend_from_slice(&u32::MAX.to_be_bytes());
assert_eq!(evaluate(&bytes, &[]), Some(default_value > 0));
}
}
#[test]
fn compound_conditions_and_null_children() {
let mut condition = vec![0, 3, 2, 0, 0, 9, 0, 0, 17];
condition.extend_from_slice(&axis_range(0, 0.25, 0.75));
condition.extend_from_slice(&axis_range(1, -1.0, -0.25));
assert_eq!(
evaluate(
&condition,
&[F2Dot14::from_f32(0.5), F2Dot14::from_f32(-0.5)]
),
Some(true)
);
assert_eq!(evaluate(&condition, &[F2Dot14::from_f32(0.5)]), Some(false));
condition[1] = 4;
assert_eq!(evaluate(&condition, &[F2Dot14::from_f32(0.5)]), Some(true));
assert_eq!(evaluate(&condition, &[]), Some(false));
let mut negated = vec![0, 5, 0, 0, 5];
negated.extend_from_slice(&condition);
assert_eq!(evaluate(&negated, &[]), Some(true));
assert_eq!(evaluate(&negated, &[F2Dot14::from_f32(0.5)]), Some(false));
assert_eq!(evaluate(&[0, 3, 1, 0, 0, 0], &[]), Some(true));
assert_eq!(evaluate(&[0, 4, 1, 0, 0, 0], &[]), Some(true));
assert_eq!(evaluate(&[0, 5, 0, 0, 0], &[]), Some(false));
assert_eq!(evaluate(&[0, 3, 0], &[]), Some(true));
assert_eq!(evaluate(&[0, 4, 0], &[]), Some(false));
}
#[test]
fn malformed_and_expensive_conditions_fail_closed() {
assert_eq!(evaluate(&[0, 3, 1], &[]), None);
assert_eq!(evaluate(&[0, 5, 0xFF, 0xFF, 0xFF], &[]), None);
let mut deep = Vec::new();
for _ in 0..MAX_RECURSION_DEPTH {
deep.extend_from_slice(&[0, 5, 0, 0, 5]);
}
deep.extend_from_slice(&axis_range(0, 0.0, 0.0));
assert_eq!(evaluate(&deep, &[]), None);
let mut repeated = Vec::new();
for _ in 0..17 {
repeated.extend_from_slice(&[0, 3, 2, 0, 0, 9, 0, 0, 9]);
}
repeated.extend_from_slice(&axis_range(0, 0.0, 0.0));
assert_eq!(evaluate(&repeated, &[]), None);
}
#[test]
fn feature_variations_select_the_first_matching_record_at_default_too() {
let mut bytes = vec![0, 1, 0, 0, 0, 0, 0, 2];
bytes.extend_from_slice(&[0, 0, 0, 24, 0, 0, 0, 0]);
bytes.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]);
bytes.extend_from_slice(&[0, 1, 0, 0, 0, 6]);
bytes.extend_from_slice(&[0, 5, 0, 0, 5]);
bytes.extend_from_slice(&axis_range(0, 0.25, 0.75));
let variations = FeatureVariations::read(FontData::new(&bytes)).unwrap();
assert_eq!(variations.index_for_coords(&[]), Some(0));
assert_eq!(
variations.index_for_coords(&[F2Dot14::from_f32(0.5)]),
Some(1)
);
}
#[test]
fn unknown_condition_formats_are_false() {
let bytes = [
0, 4, 2, 0, 0, 9, 0, 0, 11, 0, 99, 0, 1, 0, 0, 0xC0, 0, 0x40, 0, ];
assert_eq!(evaluate(&bytes, &[]), Some(true));
let bytes = [0, 5, 0, 0, 5, 0, 99];
assert_eq!(evaluate(&bytes, &[]), Some(true));
}