use super::*;
use crate::{dump_table, from_obj::ToOwnedTable, tables::layout::LookupFlag};
use read_fonts::{tables::gpos as raw, FontData, FontRead};
fn coverage() -> CoverageTable {
[GlyphId::new(65536)].into_iter().collect()
}
fn value() -> ValueRecord {
ValueRecord::new()
.with_x_advance(-123)
.with_x_placement_device(VariationIndex::new(3, 7))
}
fn anchor() -> AnchorTable {
AnchorFormat1::new(123, -456).into()
}
fn marks() -> MarkArray2 {
MarkArray2::new(vec![MarkRecord2::new(2, anchor())])
}
fn anchors() -> Vec<Option<AnchorTable>> {
vec![None, None, Some(anchor())]
}
macro_rules! roundtrip {
($table:expr, $type:ident, $format:literal) => {{
let table = $table;
let bytes = dump_table(&table).unwrap();
assert_eq!(&bytes[..2], &$format.to_be_bytes());
let read = raw::$type::read(FontData::new(&bytes)).unwrap();
let owned: $type = read.to_owned_table();
assert_eq!(dump_table(&owned).unwrap(), bytes);
bytes
}};
}
#[test]
fn all_extended_positioning_formats_roundtrip() {
roundtrip!(
SinglePosFormat3::new(coverage(), value()),
SinglePosFormat3,
3u16
);
roundtrip!(
SinglePosFormat4::new(coverage(), vec![value()]),
SinglePosFormat4,
4u16
);
roundtrip!(
PairPosFormat3::new(
coverage(),
vec![PairSet2::new(vec![PairValueRecord2::new(
GlyphId24::new(65536),
value(),
ValueRecord::new()
)])]
),
PairPosFormat3,
3u16
);
let class: ClassDef = [(GlyphId::new(65536), 2u32)].into_iter().collect();
let matrix = vec![Class1Record::new(vec![Class2Record::new(value(), ValueRecord::new())]); 3];
let bytes = roundtrip!(
PairPosFormat4::new(coverage(), class, ClassDef::default(), matrix),
PairPosFormat4,
4u16
);
assert_eq!(&bytes[18..22], &[0, 3, 0, 1]);
roundtrip!(
CursivePosFormat2::new(
coverage(),
vec![EntryExitRecord2::new(Some(anchor()), None)]
),
CursivePosFormat2,
2u16
);
let bytes = roundtrip!(
MarkBasePosFormat2::new(
coverage(),
coverage(),
marks(),
BaseArray2::new(vec![BaseRecord2::new(anchors())])
),
MarkBasePosFormat2,
2u16
);
assert_eq!(&bytes[10..12], &[0, 3]);
let bytes = roundtrip!(
MarkLigPosFormat2::new(
coverage(),
coverage(),
marks(),
LigatureArray2::new(vec![LigatureAttach2::new(vec![ComponentRecord2::new(
anchors()
)])])
),
MarkLigPosFormat2,
2u16
);
assert_eq!(&bytes[10..12], &[0, 3]);
let bytes = roundtrip!(
MarkMarkPosFormat2::new(
coverage(),
coverage(),
marks(),
Mark2Array2::new(vec![Mark2Record2::new(anchors())])
),
MarkMarkPosFormat2,
2u16
);
assert_eq!(&bytes[10..12], &[0, 3]);
}
#[test]
fn extended_anchor_arrays_keep_the_specified_count_widths_and_offset_bases() {
let pair = PairSet2::new(vec![PairValueRecord2::new(
GlyphId24::new(65536),
ValueRecord::new().with_x_advance(-123),
ValueRecord::new(),
)]);
assert_eq!(dump_table(&pair).unwrap(), [0, 0, 1, 1, 0, 0, 0xFF, 0x85]);
let bytes = dump_table(&marks()).unwrap();
assert_eq!(&bytes[..8], &[0, 0, 1, 0, 2, 0, 0, 8]);
let read = raw::MarkArray2::read(FontData::new(&bytes)).unwrap();
assert_eq!(
read.mark_records()[0]
.mark_anchor(read.offset_data())
.unwrap()
.x_coordinate(),
123
);
let bytes = dump_table(&BaseArray2::new(vec![BaseRecord2::new(vec![Some(
anchor(),
)])]))
.unwrap();
assert_eq!(&bytes[..6], &[0, 0, 1, 0, 0, 6]);
let read = raw::BaseArray2::read_with_args(FontData::new(&bytes), 1).unwrap();
assert_eq!(
read.base_records()
.get(0)
.unwrap()
.base_anchors(read.offset_data())
.get(0)
.unwrap()
.unwrap()
.y_coordinate(),
-456
);
let bytes = dump_table(&LigatureAttach2::new(vec![ComponentRecord2::new(vec![
Some(anchor()),
])]))
.unwrap();
assert_eq!(&bytes[..5], &[0, 1, 0, 0, 5]);
let read = raw::LigatureAttach2::read_with_args(FontData::new(&bytes), 1).unwrap();
assert_eq!(
read.component_records()
.get(0)
.unwrap()
.ligature_anchors(read.offset_data())
.get(0)
.unwrap()
.unwrap()
.x_coordinate(),
123
);
let bytes = dump_table(&Mark2Array2::new(vec![Mark2Record2::new(vec![Some(
anchor(),
)])]))
.unwrap();
assert_eq!(&bytes[..5], &[0, 1, 0, 0, 5]);
let read = raw::Mark2Array2::read_with_args(FontData::new(&bytes), 1).unwrap();
assert_eq!(
read.mark2_records()
.get(0)
.unwrap()
.mark2_anchors(read.offset_data())
.get(0)
.unwrap()
.unwrap()
.x_coordinate(),
123
);
}
#[test]
fn full_width_coverage_and_anchor_offsets() {
let mut bytes = vec![0; 65542];
bytes[..9].copy_from_slice(&[0, 2, 0, 0, 0, 15, 0, 0, 1]);
bytes[9..15].copy_from_slice(&[1, 0, 0, 0, 0, 0]);
bytes[15..23].copy_from_slice(&[0, 3, 0, 0, 1, 1, 0, 0]);
bytes[65536..].copy_from_slice(&[0, 1, 0, 123, 0xFE, 0x38]);
let read = raw::CursivePosFormat2::read(FontData::new(&bytes)).unwrap();
let record = &read.entry_exit_record()[0];
assert_eq!(
record
.entry_anchor(read.offset_data())
.unwrap()
.unwrap()
.y_coordinate(),
-456
);
assert!(record.exit_anchor(read.offset_data()).is_none());
let mut bytes = vec![0; 65544];
bytes[..8].copy_from_slice(&[0, 3, 0, 1, 0, 0, 0, 0]);
bytes[65536..].copy_from_slice(&[0, 3, 0, 0, 1, 1, 0, 0]);
let read = raw::SinglePosFormat3::read(FontData::new(&bytes)).unwrap();
assert_eq!(read.coverage().unwrap().get(GlyphId::new(65536)), Some(0));
}
#[test]
fn extended_value_records_resolve_devices_against_the_containing_table() {
let single = SinglePosFormat3::new(coverage(), value());
let bytes = dump_table(&single).unwrap();
let read = raw::SinglePosFormat3::read(FontData::new(&bytes)).unwrap();
let raw::DeviceOrVariationIndex::VariationIndex(index) =
read.value_record().x_placement_device().unwrap().unwrap()
else {
panic!("wrong device")
};
assert_eq!(
(index.delta_set_outer_index(), index.delta_set_inner_index()),
(3, 7)
);
let pair = PairPosFormat3::new(
coverage(),
vec![PairSet2::new(vec![PairValueRecord2::new(
GlyphId24::new(65536),
value(),
ValueRecord::new(),
)])],
);
let bytes = dump_table(&pair).unwrap();
let read = raw::PairPosFormat3::read(FontData::new(&bytes)).unwrap();
let record = read
.pair_sets()
.get(0)
.unwrap()
.pair_value_records()
.get(0)
.unwrap();
let raw::DeviceOrVariationIndex::VariationIndex(index) = record
.value_record1()
.x_placement_device()
.unwrap()
.unwrap()
else {
panic!("wrong device")
};
assert_eq!(
(index.delta_set_outer_index(), index.delta_set_inner_index()),
(3, 7)
);
}
#[test]
fn extended_positioning_validation_rejects_bad_dimensions_and_formats() {
let class: ClassDef = [(GlyphId::new(65536), 65536u32)].into_iter().collect();
let pair = PairPosFormat4::new(
coverage(),
class,
ClassDef::default(),
vec![Class1Record::new(vec![Class2Record::default()])],
);
assert!(dump_table(&pair).is_err());
let mark = MarkBasePosFormat2::new(
coverage(),
coverage(),
marks(),
BaseArray2::new(vec![BaseRecord2::new(vec![Some(anchor())])]),
);
assert!(dump_table(&mark).is_err());
let marks = MarkArray2::new(vec![MarkRecord2::new(u16::MAX, anchor())]);
assert!(dump_table(&marks).is_err());
let single = SinglePosFormat4::new(coverage(), vec![ValueRecord::new(), value()]);
assert!(dump_table(&single).is_err());
let mark2 = Mark2Array2::new(vec![Mark2Record2::default(); 65536]);
assert!(dump_table(&mark2).is_err());
}
#[test]
fn extended_positioning_count_and_lookup_dispatch() {
let single = SinglePosFormat4::new(
coverage(),
vec![ValueRecord::new().with_x_advance(1); 65537],
);
let bytes = dump_table(&single).unwrap();
assert_eq!(&bytes[8..11], &[1, 0, 1]);
let read = raw::SinglePosFormat4::read(FontData::new(&bytes)).unwrap();
assert_eq!(
read.value_records().get(65536).unwrap().x_advance(),
Some(1)
);
let table: CursivePos = CursivePosFormat2::new(
coverage(),
vec![EntryExitRecord2::new(Some(anchor()), None)],
)
.into();
for lookup in [
PositionLookup::Cursive(Lookup::new(LookupFlag::empty(), vec![table.clone()])),
PositionLookup::Extension(Lookup::new(
LookupFlag::empty(),
vec![ExtensionSubtable::Cursive(ExtensionPosFormat1::new(
3, table,
))],
)),
] {
let bytes = dump_table(&lookup).unwrap();
let read = raw::PositionLookup::read(FontData::new(&bytes)).unwrap();
let raw::PositionSubtables::Cursive(tables) = read.subtables().unwrap() else {
panic!("wrong lookup type")
};
assert!(matches!(
tables.get(0).unwrap(),
raw::CursivePos::Format2(_)
));
}
}