use crate::parser::{read_i16, read_u16, read_u32};
use crate::tables::gdef::{ClassDef, Coverage};
use crate::tables::layout::{FeatureList, LayoutHeader, Lookup, LookupList, Script, ScriptList};
use crate::Error;
pub const GPOS_LOOKUP_TYPE_SINGLE: u16 = 1;
pub const GPOS_LOOKUP_TYPE_PAIR: u16 = 2;
pub const GPOS_LOOKUP_TYPE_CURSIVE: u16 = 3;
pub const GPOS_LOOKUP_TYPE_MARK_TO_BASE: u16 = 4;
pub const GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE: u16 = 5;
pub const GPOS_LOOKUP_TYPE_MARK_TO_MARK: u16 = 6;
pub const GPOS_LOOKUP_TYPE_CONTEXT: u16 = 7;
pub const GPOS_LOOKUP_TYPE_CHAINED_CONTEXT: u16 = 8;
pub const GPOS_LOOKUP_TYPE_EXTENSION: u16 = 9;
const VF_X_PLACEMENT: u16 = 0x0001;
const VF_Y_PLACEMENT: u16 = 0x0002;
const VF_X_ADVANCE: u16 = 0x0004;
const VF_Y_ADVANCE: u16 = 0x0008;
const VF_X_PLACEMENT_DEVICE: u16 = 0x0010;
const VF_Y_PLACEMENT_DEVICE: u16 = 0x0020;
const VF_X_ADVANCE_DEVICE: u16 = 0x0040;
const VF_Y_ADVANCE_DEVICE: u16 = 0x0080;
const VF_RESERVED: u16 = 0xFF00;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValueFormat(pub u16);
impl ValueFormat {
pub fn bits(self) -> u16 {
self.0
}
pub fn is_valid(self) -> bool {
self.0 & VF_RESERVED == 0
}
pub fn has_x_placement(self) -> bool {
self.0 & VF_X_PLACEMENT != 0
}
pub fn has_y_placement(self) -> bool {
self.0 & VF_Y_PLACEMENT != 0
}
pub fn has_x_advance(self) -> bool {
self.0 & VF_X_ADVANCE != 0
}
pub fn has_y_advance(self) -> bool {
self.0 & VF_Y_ADVANCE != 0
}
pub fn has_x_placement_device(self) -> bool {
self.0 & VF_X_PLACEMENT_DEVICE != 0
}
pub fn has_y_placement_device(self) -> bool {
self.0 & VF_Y_PLACEMENT_DEVICE != 0
}
pub fn has_x_advance_device(self) -> bool {
self.0 & VF_X_ADVANCE_DEVICE != 0
}
pub fn has_y_advance_device(self) -> bool {
self.0 & VF_Y_ADVANCE_DEVICE != 0
}
pub fn record_size(self) -> usize {
2 * (self.0 & !VF_RESERVED).count_ones() as usize
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ValueRecord {
pub x_placement: i16,
pub y_placement: i16,
pub x_advance: i16,
pub y_advance: i16,
pub x_placement_device_offset: u16,
pub y_placement_device_offset: u16,
pub x_advance_device_offset: u16,
pub y_advance_device_offset: u16,
}
impl ValueRecord {
pub fn parse(data: &[u8], off: usize, format: ValueFormat) -> Result<(Self, usize), Error> {
let mut cur = off;
let mut rec = ValueRecord::default();
macro_rules! take_i16 {
($cond:expr, $field:ident) => {
if $cond {
rec.$field = read_i16(data, cur)?;
cur += 2;
}
};
}
macro_rules! take_off {
($cond:expr, $field:ident) => {
if $cond {
rec.$field = read_u16(data, cur)?;
cur += 2;
}
};
}
take_i16!(format.has_x_placement(), x_placement);
take_i16!(format.has_y_placement(), y_placement);
take_i16!(format.has_x_advance(), x_advance);
take_i16!(format.has_y_advance(), y_advance);
take_off!(format.has_x_placement_device(), x_placement_device_offset);
take_off!(format.has_y_placement_device(), y_placement_device_offset);
take_off!(format.has_x_advance_device(), x_advance_device_offset);
take_off!(format.has_y_advance_device(), y_advance_device_offset);
Ok((rec, cur - off))
}
}
#[derive(Debug, Clone, Copy)]
pub struct SinglePos<'a> {
bytes: &'a [u8],
coverage: Coverage<'a>,
value_format: ValueFormat,
inner: SinglePosInner,
}
#[derive(Debug, Clone, Copy)]
enum SinglePosInner {
Format1 { value_off: usize },
Format2 { values_off: usize, count: u16 },
}
impl<'a> SinglePos<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
let coverage_off = read_u16(bytes, 2)? as usize;
let value_format = ValueFormat(read_u16(bytes, 4)?);
if !value_format.is_valid() {
return Err(Error::BadStructure(
"GPOS/SinglePos: reserved valueFormat bit set",
));
}
if coverage_off == 0 || coverage_off >= bytes.len() {
return Err(Error::BadStructure(
"GPOS/SinglePos: coverageOffset out of range",
));
}
let coverage = Coverage::parse(&bytes[coverage_off..])?;
match format {
1 => {
let value_off = 6usize;
ValueRecord::parse(bytes, value_off, value_format)?;
Ok(Self {
bytes,
coverage,
value_format,
inner: SinglePosInner::Format1 { value_off },
})
}
2 => {
let count = read_u16(bytes, 6)?;
let values_off = 8usize;
let rec_size = value_format.record_size();
let need = values_off
.checked_add(
rec_size
.checked_mul(count as usize)
.ok_or(Error::BadStructure("GPOS/SinglePos: valueCount overflow"))?,
)
.ok_or(Error::BadStructure("GPOS/SinglePos: length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
coverage,
value_format,
inner: SinglePosInner::Format2 { values_off, count },
})
}
_ => Err(Error::BadStructure("GPOS/SinglePos: unknown format")),
}
}
pub fn format(&self) -> u16 {
match self.inner {
SinglePosInner::Format1 { .. } => 1,
SinglePosInner::Format2 { .. } => 2,
}
}
pub fn value_format(&self) -> ValueFormat {
self.value_format
}
pub fn coverage(&self) -> Coverage<'a> {
self.coverage
}
pub fn value_count(&self) -> u16 {
match self.inner {
SinglePosInner::Format1 { .. } => 1,
SinglePosInner::Format2 { count, .. } => count,
}
}
pub fn value(&self, glyph_id: u16) -> Option<Result<ValueRecord, Error>> {
let idx = self.coverage.index_of(glyph_id)?;
match self.inner {
SinglePosInner::Format1 { value_off } => {
Some(ValueRecord::parse(self.bytes, value_off, self.value_format).map(|(r, _)| r))
}
SinglePosInner::Format2 { values_off, count } => {
if idx >= count {
return Some(Err(Error::BadStructure(
"GPOS/SinglePos2: coverage index >= valueCount",
)));
}
let off = values_off + idx as usize * self.value_format.record_size();
Some(ValueRecord::parse(self.bytes, off, self.value_format).map(|(r, _)| r))
}
}
}
pub fn iter(&self) -> SinglePosIter<'a> {
SinglePosIter {
sub: *self,
cov: self.coverage.iter(),
}
}
}
#[derive(Debug, Clone)]
pub struct SinglePosIter<'a> {
sub: SinglePos<'a>,
cov: crate::tables::gdef::CoverageIter<'a>,
}
impl<'a> Iterator for SinglePosIter<'a> {
type Item = (u16, Result<ValueRecord, Error>);
fn next(&mut self) -> Option<Self::Item> {
let (glyph, _idx) = self.cov.next()?;
let val = self.sub.value(glyph)?;
Some((glyph, val))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PairValue {
pub first: ValueRecord,
pub second: ValueRecord,
}
#[derive(Debug, Clone, Copy)]
pub struct PairPos<'a> {
bytes: &'a [u8],
coverage: Coverage<'a>,
value_format1: ValueFormat,
value_format2: ValueFormat,
inner: PairPosInner<'a>,
}
#[derive(Debug, Clone, Copy)]
enum PairPosInner<'a> {
Format1 { pair_set_count: u16 },
Format2 {
class_def1: ClassDef<'a>,
class_def2: ClassDef<'a>,
class1_count: u16,
class2_count: u16,
matrix_off: usize,
},
}
impl<'a> PairPos<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
let coverage_off = read_u16(bytes, 2)? as usize;
let value_format1 = ValueFormat(read_u16(bytes, 4)?);
let value_format2 = ValueFormat(read_u16(bytes, 6)?);
if !value_format1.is_valid() || !value_format2.is_valid() {
return Err(Error::BadStructure(
"GPOS/PairPos: reserved valueFormat bit set",
));
}
if coverage_off == 0 || coverage_off >= bytes.len() {
return Err(Error::BadStructure(
"GPOS/PairPos: coverageOffset out of range",
));
}
let coverage = Coverage::parse(&bytes[coverage_off..])?;
match format {
1 => {
let pair_set_count = read_u16(bytes, 8)?;
let need = 10usize
.checked_add(
(pair_set_count as usize)
.checked_mul(2)
.ok_or(Error::BadStructure("GPOS/PairPos: pairSetCount overflow"))?,
)
.ok_or(Error::BadStructure("GPOS/PairPos: length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
if pair_set_count as usize != coverage.len() {
return Err(Error::BadStructure(
"GPOS/PairPos1: pairSetCount != coverage length",
));
}
Ok(Self {
bytes,
coverage,
value_format1,
value_format2,
inner: PairPosInner::Format1 { pair_set_count },
})
}
2 => {
let class_def1_off = read_u16(bytes, 8)? as usize;
let class_def2_off = read_u16(bytes, 10)? as usize;
let class1_count = read_u16(bytes, 12)?;
let class2_count = read_u16(bytes, 14)?;
if class_def1_off == 0
|| class_def1_off >= bytes.len()
|| class_def2_off == 0
|| class_def2_off >= bytes.len()
{
return Err(Error::BadStructure(
"GPOS/PairPos2: classDefOffset out of range",
));
}
let class_def1 = ClassDef::parse(&bytes[class_def1_off..])?;
let class_def2 = ClassDef::parse(&bytes[class_def2_off..])?;
let matrix_off = 16usize;
let cell_size = value_format1
.record_size()
.checked_add(value_format2.record_size())
.ok_or(Error::BadStructure("GPOS/PairPos2: cell size overflow"))?;
let cells = (class1_count as usize)
.checked_mul(class2_count as usize)
.ok_or(Error::BadStructure("GPOS/PairPos2: class count overflow"))?;
let matrix_bytes = cells
.checked_mul(cell_size)
.ok_or(Error::BadStructure("GPOS/PairPos2: matrix size overflow"))?;
let need = matrix_off
.checked_add(matrix_bytes)
.ok_or(Error::BadStructure("GPOS/PairPos2: length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
coverage,
value_format1,
value_format2,
inner: PairPosInner::Format2 {
class_def1,
class_def2,
class1_count,
class2_count,
matrix_off,
},
})
}
_ => Err(Error::BadStructure("GPOS/PairPos: unknown format")),
}
}
pub fn format(&self) -> u16 {
match self.inner {
PairPosInner::Format1 { .. } => 1,
PairPosInner::Format2 { .. } => 2,
}
}
pub fn value_format1(&self) -> ValueFormat {
self.value_format1
}
pub fn value_format2(&self) -> ValueFormat {
self.value_format2
}
pub fn coverage(&self) -> Coverage<'a> {
self.coverage
}
fn read_pair(&self, off: usize) -> Result<(PairValue, usize), Error> {
let (first, used1) = ValueRecord::parse(self.bytes, off, self.value_format1)?;
let (second, used2) = ValueRecord::parse(self.bytes, off + used1, self.value_format2)?;
Ok((PairValue { first, second }, used1 + used2))
}
pub fn pair(&self, first_glyph: u16, second_glyph: u16) -> Option<Result<PairValue, Error>> {
let idx = self.coverage.index_of(first_glyph)?;
match self.inner {
PairPosInner::Format1 { pair_set_count } => {
if idx >= pair_set_count {
return Some(Err(Error::BadStructure(
"GPOS/PairPos1: coverage index >= pairSetCount",
)));
}
let off_pos = 10 + idx as usize * 2;
let pair_set_off = match read_u16(self.bytes, off_pos) {
Ok(v) => v as usize,
Err(e) => return Some(Err(e)),
};
if pair_set_off == 0 || pair_set_off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"GPOS/PairPos1: pairSetOffset out of range",
)));
}
self.pair_in_set(pair_set_off, second_glyph)
}
PairPosInner::Format2 {
class_def1,
class_def2,
class1_count,
class2_count,
matrix_off,
} => {
let c1 = class_def1.class_of(first_glyph);
let c2 = class_def2.class_of(second_glyph);
if c1 >= class1_count || c2 >= class2_count {
return None;
}
let cell_size = self.value_format1.record_size() + self.value_format2.record_size();
let cell_index = c1 as usize * class2_count as usize + c2 as usize;
let off = matrix_off + cell_index * cell_size;
Some(self.read_pair(off).map(|(pv, _)| pv))
}
}
}
fn pair_in_set(
&self,
pair_set_off: usize,
second_glyph: u16,
) -> Option<Result<PairValue, Error>> {
let count = match read_u16(self.bytes, pair_set_off) {
Ok(v) => v as usize,
Err(e) => return Some(Err(e)),
};
let rec_size = 2 + self.value_format1.record_size() + self.value_format2.record_size();
let records_off = pair_set_off + 2;
let (mut lo, mut hi) = (0usize, count);
while lo < hi {
let mid = (lo + hi) / 2;
let rec_off = records_off + mid * rec_size;
let sg = match read_u16(self.bytes, rec_off) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
match sg.cmp(&second_glyph) {
core::cmp::Ordering::Equal => {
return Some(self.read_pair(rec_off + 2).map(|(pv, _)| pv));
}
core::cmp::Ordering::Less => lo = mid + 1,
core::cmp::Ordering::Greater => hi = mid,
}
}
None
}
pub fn iter(&self) -> PairPosIter<'a> {
let cov = self.coverage.iter();
PairPosIter {
sub: *self,
cov,
cur_first: None,
set_off: 0,
set_count: 0,
set_idx: 0,
}
}
pub fn class_pair(&self, class1: u16, class2: u16) -> Option<Result<PairValue, Error>> {
match self.inner {
PairPosInner::Format2 {
class1_count,
class2_count,
matrix_off,
..
} => {
if class1 >= class1_count || class2 >= class2_count {
return None;
}
let cell_size = self.value_format1.record_size() + self.value_format2.record_size();
let cell_index = class1 as usize * class2_count as usize + class2 as usize;
let off = matrix_off + cell_index * cell_size;
Some(self.read_pair(off).map(|(pv, _)| pv))
}
PairPosInner::Format1 { .. } => None,
}
}
}
#[derive(Debug, Clone)]
pub struct PairPosIter<'a> {
sub: PairPos<'a>,
cov: crate::tables::gdef::CoverageIter<'a>,
cur_first: Option<u16>,
set_off: usize,
set_count: usize,
set_idx: usize,
}
impl<'a> Iterator for PairPosIter<'a> {
type Item = (u16, u16, Result<PairValue, Error>);
fn next(&mut self) -> Option<Self::Item> {
let pair_set_count = match self.sub.inner {
PairPosInner::Format1 { pair_set_count } => pair_set_count,
PairPosInner::Format2 { .. } => return None,
};
let rec_size =
2 + self.sub.value_format1.record_size() + self.sub.value_format2.record_size();
loop {
if let Some(first) = self.cur_first {
if self.set_idx < self.set_count {
let rec_off = self.set_off + self.set_idx * rec_size;
self.set_idx += 1;
let sg = match read_u16(self.sub.bytes, rec_off) {
Ok(v) => v,
Err(e) => return Some((first, 0, Err(e))),
};
let pv = self.sub.read_pair(rec_off + 2).map(|(pv, _)| pv);
return Some((first, sg, pv));
}
self.cur_first = None;
}
let (glyph, idx) = self.cov.next()?;
if idx >= pair_set_count {
return Some((
glyph,
0,
Err(Error::BadStructure(
"GPOS/PairPos1: coverage index >= pairSetCount",
)),
));
}
let off_pos = 10 + idx as usize * 2;
let pair_set_off = match read_u16(self.sub.bytes, off_pos) {
Ok(v) => v as usize,
Err(e) => return Some((glyph, 0, Err(e))),
};
if pair_set_off == 0 || pair_set_off >= self.sub.bytes.len() {
return Some((
glyph,
0,
Err(Error::BadStructure(
"GPOS/PairPos1: pairSetOffset out of range",
)),
));
}
let count = match read_u16(self.sub.bytes, pair_set_off) {
Ok(v) => v as usize,
Err(e) => return Some((glyph, 0, Err(e))),
};
self.cur_first = Some(glyph);
self.set_off = pair_set_off + 2;
self.set_count = count;
self.set_idx = 0;
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ExtensionPos<'a> {
bytes: &'a [u8],
ext_lookup_type: u16,
ext_offset: u32,
}
impl<'a> ExtensionPos<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
if format != 1 {
return Err(Error::BadStructure(
"GPOS/ExtensionPos: unknown subtable format",
));
}
let ext_lookup_type = read_u16(bytes, 2)?;
if ext_lookup_type == GPOS_LOOKUP_TYPE_EXTENSION {
return Err(Error::BadStructure(
"GPOS/ExtensionPos: extensionLookupType must not be 9",
));
}
if !(GPOS_LOOKUP_TYPE_SINGLE..=GPOS_LOOKUP_TYPE_CHAINED_CONTEXT).contains(&ext_lookup_type)
{
return Err(Error::BadStructure(
"GPOS/ExtensionPos: extensionLookupType out of range",
));
}
let ext_offset = read_u32(bytes, 4)?;
if ext_offset == 0 || (ext_offset as usize) >= bytes.len() {
return Err(Error::BadStructure(
"GPOS/ExtensionPos: extensionOffset out of range",
));
}
Ok(Self {
bytes,
ext_lookup_type,
ext_offset,
})
}
pub fn format(&self) -> u16 {
1
}
pub fn extension_lookup_type(&self) -> u16 {
self.ext_lookup_type
}
pub fn extension_offset(&self) -> u32 {
self.ext_offset
}
pub fn extension_subtable_bytes(&self) -> &'a [u8] {
&self.bytes[self.ext_offset as usize..]
}
pub fn as_single_pos(&self) -> Result<SinglePos<'a>, Error> {
if self.ext_lookup_type != GPOS_LOOKUP_TYPE_SINGLE {
return Err(Error::BadStructure(
"GPOS/ExtensionPos: extensionLookupType is not 1",
));
}
SinglePos::parse(self.extension_subtable_bytes())
}
pub fn as_pair_pos(&self) -> Result<PairPos<'a>, Error> {
if self.ext_lookup_type != GPOS_LOOKUP_TYPE_PAIR {
return Err(Error::BadStructure(
"GPOS/ExtensionPos: extensionLookupType is not 2",
));
}
PairPos::parse(self.extension_subtable_bytes())
}
}
#[derive(Debug, Clone, Copy)]
pub struct GposTable<'a> {
bytes: &'a [u8],
header: LayoutHeader,
}
impl<'a> GposTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let header = LayoutHeader::parse(bytes)?;
let len = bytes.len();
if (header.script_list_off as usize) >= len
|| (header.feature_list_off as usize) >= len
|| (header.lookup_list_off as usize) >= len
{
return Err(Error::BadStructure("GPOS: header offset out of range"));
}
if header.feature_variations_off != 0 && (header.feature_variations_off as usize) >= len {
return Err(Error::BadStructure(
"GPOS: featureVariationsOffset out of range",
));
}
Ok(Self { bytes, header })
}
pub fn version(&self) -> (u16, u16) {
(self.header.major, self.header.minor)
}
pub fn feature_variations_offset(&self) -> u32 {
self.header.feature_variations_off
}
pub fn has_feature_variations(&self) -> bool {
self.header.minor >= 1 && self.header.feature_variations_off != 0
}
pub fn raw(&self) -> &'a [u8] {
self.bytes
}
pub fn script_list(&self) -> Result<ScriptList<'a>, Error> {
ScriptList::parse(&self.bytes[self.header.script_list_off as usize..])
}
pub fn feature_list(&self) -> Result<FeatureList<'a>, Error> {
FeatureList::parse(&self.bytes[self.header.feature_list_off as usize..])
}
pub fn lookup_list(&self) -> Result<LookupList<'a>, Error> {
LookupList::parse(&self.bytes[self.header.lookup_list_off as usize..])
}
pub fn find_script(&self, tag: &[u8; 4]) -> Option<Script<'a>> {
self.script_list().ok()?.find(tag)?.ok()
}
pub fn lookup_count(&self) -> u16 {
self.lookup_list().map(|l| l.count()).unwrap_or(0)
}
pub fn feature_count(&self) -> u16 {
self.feature_list().map(|f| f.count()).unwrap_or(0)
}
pub fn script_count(&self) -> u16 {
self.script_list().map(|s| s.count()).unwrap_or(0)
}
pub fn lookup(&self, i: u16) -> Option<Lookup<'a>> {
self.lookup_list().ok()?.lookup(i)?.ok()
}
pub fn single_pos(&self, lookup_i: u16, sub_i: u16) -> Option<Result<SinglePos<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GPOS_LOOKUP_TYPE_SINGLE {
return Some(Err(Error::BadStructure(
"GPOS/SinglePos: lookup is not type 1",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(SinglePos::parse(bytes))
}
pub fn pair_pos(&self, lookup_i: u16, sub_i: u16) -> Option<Result<PairPos<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GPOS_LOOKUP_TYPE_PAIR {
return Some(Err(Error::BadStructure(
"GPOS/PairPos: lookup is not type 2",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(PairPos::parse(bytes))
}
pub fn extension_pos(
&self,
lookup_i: u16,
sub_i: u16,
) -> Option<Result<ExtensionPos<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GPOS_LOOKUP_TYPE_EXTENSION {
return Some(Err(Error::BadStructure(
"GPOS/ExtensionPos: lookup is not type 9",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(ExtensionPos::parse(bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn be(u: u16) -> [u8; 2] {
u.to_be_bytes()
}
#[test]
fn parses_minimal_v10_table() {
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"kern");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(4));
bytes[48..50].copy_from_slice(&be(2));
bytes[50..52].copy_from_slice(&be(0));
bytes[52..54].copy_from_slice(&be(0));
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.version(), (1, 0));
assert_eq!(g.script_count(), 1);
assert_eq!(g.feature_count(), 1);
assert_eq!(g.lookup_count(), 1);
assert_eq!(g.find_script(b"DFLT").map(|s| s.lang_sys_count()), Some(0));
assert_eq!(g.feature_list().unwrap().tag(0), Some(*b"kern"));
assert_eq!(g.lookup(0).map(|l| l.lookup_type()), Some(2));
}
#[test]
fn rejects_truncated_v11_header() {
let mut bytes = vec![0u8; 12]; bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(1));
assert!(matches!(
GposTable::parse(&bytes),
Err(Error::UnexpectedEof)
));
}
#[test]
fn value_format_field_presence_and_size() {
let vf = ValueFormat(0x0005); assert!(vf.has_x_placement());
assert!(!vf.has_y_placement());
assert!(vf.has_x_advance());
assert!(!vf.has_y_advance());
assert!(vf.is_valid());
assert_eq!(vf.record_size(), 4);
let all = ValueFormat(0x00FF);
assert_eq!(all.record_size(), 16);
assert!(all.is_valid());
let reserved = ValueFormat(0x0100);
assert!(!reserved.is_valid());
assert_eq!(ValueFormat(0).record_size(), 0);
}
#[test]
fn value_record_reads_only_declared_fields_in_order() {
let vf = ValueFormat(0x0049);
assert_eq!(vf.record_size(), 6);
let mut data = Vec::new();
data.extend_from_slice(&(-25i16).to_be_bytes()); data.extend_from_slice(&(40i16).to_be_bytes()); data.extend_from_slice(&(0x1234u16).to_be_bytes()); data.extend_from_slice(&[0xAA, 0xBB]);
let (rec, used) = ValueRecord::parse(&data, 0, vf).unwrap();
assert_eq!(used, 6);
assert_eq!(rec.x_placement, -25);
assert_eq!(rec.y_advance, 40);
assert_eq!(rec.x_advance_device_offset, 0x1234);
assert_eq!(rec.y_placement, 0);
assert_eq!(rec.x_advance, 0);
assert_eq!(rec.y_placement_device_offset, 0);
}
#[test]
fn empty_value_record_consumes_nothing() {
let (rec, used) = ValueRecord::parse(&[], 0, ValueFormat(0)).unwrap();
assert_eq!(used, 0);
assert_eq!(rec, ValueRecord::default());
}
fn singlepos_f1_subtable() -> Vec<u8> {
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&be(1)); b[2..4].copy_from_slice(&be(8)); b[4..6].copy_from_slice(&be(0x0004)); b[6..8].copy_from_slice(&(-50i16).to_be_bytes()); b.extend_from_slice(&be(1)); b.extend_from_slice(&be(3)); b.extend_from_slice(&be(10));
b.extend_from_slice(&be(11));
b.extend_from_slice(&be(20));
b
}
#[test]
fn single_pos_format1_shared_value() {
let b = singlepos_f1_subtable();
let sp = SinglePos::parse(&b).unwrap();
assert_eq!(sp.format(), 1);
assert_eq!(sp.value_format(), ValueFormat(0x0004));
assert_eq!(sp.value_count(), 1);
for g in [10u16, 11, 20] {
let v = sp.value(g).unwrap().unwrap();
assert_eq!(v.x_advance, -50);
assert_eq!(v.x_placement, 0);
}
assert!(sp.value(12).is_none());
let collected: Vec<_> = sp.iter().map(|(g, v)| (g, v.unwrap().x_advance)).collect();
assert_eq!(collected, vec![(10, -50), (11, -50), (20, -50)]);
}
#[test]
fn single_pos_format2_per_glyph_values() {
let mut b = vec![0u8; 16];
b[0..2].copy_from_slice(&be(2));
b[2..4].copy_from_slice(&be(16)); b[4..6].copy_from_slice(&be(0x0005));
b[6..8].copy_from_slice(&be(2)); b[8..10].copy_from_slice(&(3i16).to_be_bytes());
b[10..12].copy_from_slice(&(7i16).to_be_bytes());
b[12..14].copy_from_slice(&(-3i16).to_be_bytes());
b[14..16].copy_from_slice(&(-7i16).to_be_bytes());
b.extend_from_slice(&be(1)); b.extend_from_slice(&be(2)); b.extend_from_slice(&be(5));
b.extend_from_slice(&be(6));
let sp = SinglePos::parse(&b).unwrap();
assert_eq!(sp.format(), 2);
assert_eq!(sp.value_count(), 2);
let v5 = sp.value(5).unwrap().unwrap();
assert_eq!((v5.x_placement, v5.x_advance), (3, 7));
let v6 = sp.value(6).unwrap().unwrap();
assert_eq!((v6.x_placement, v6.x_advance), (-3, -7));
assert!(sp.value(7).is_none());
let pairs: Vec<_> = sp
.iter()
.map(|(g, v)| {
let v = v.unwrap();
(g, v.x_placement, v.x_advance)
})
.collect();
assert_eq!(pairs, vec![(5, 3, 7), (6, -3, -7)]);
}
#[test]
fn single_pos_rejects_reserved_value_format() {
let mut b = singlepos_f1_subtable();
b[4..6].copy_from_slice(&be(0x0100)); assert!(matches!(SinglePos::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn single_pos_rejects_bad_coverage_offset() {
let mut b = singlepos_f1_subtable();
b[2..4].copy_from_slice(&be(0)); assert!(matches!(SinglePos::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn single_pos_format2_truncated_value_array() {
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&be(2));
b[2..4].copy_from_slice(&be(8)); b[4..6].copy_from_slice(&be(0x0005));
b[6..8].copy_from_slice(&be(100)); b.extend_from_slice(&be(1)); b.extend_from_slice(&be(1)); b.extend_from_slice(&be(5));
assert!(matches!(SinglePos::parse(&b), Err(Error::UnexpectedEof)));
}
#[test]
fn single_pos_rejects_unknown_format() {
let mut b = singlepos_f1_subtable();
b[0..2].copy_from_slice(&be(3));
assert!(matches!(SinglePos::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn gpos_table_single_pos_accessor() {
let sub = singlepos_f1_subtable();
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"kern");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1)); bytes[46..48].copy_from_slice(&be(4)); bytes[48..50].copy_from_slice(&be(1)); bytes[50..52].copy_from_slice(&be(0)); bytes[52..54].copy_from_slice(&be(1)); bytes.extend_from_slice(&be(8)); bytes.extend_from_slice(&sub);
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup(0).map(|l| l.lookup_type()), Some(1));
let sp = g.single_pos(0, 0).unwrap().unwrap();
assert_eq!(sp.value(11).unwrap().unwrap().x_advance, -50);
assert!(g.single_pos(5, 0).is_none());
}
fn pairpos_f1_subtable() -> Vec<u8> {
let vf1 = 0x0004u16;
let mut pairset_a = Vec::new();
pairset_a.extend_from_slice(&be(1)); pairset_a.extend_from_slice(&be(40)); pairset_a.extend_from_slice(&(-40i16).to_be_bytes()); let mut pairset_b = Vec::new();
pairset_b.extend_from_slice(&be(2)); pairset_b.extend_from_slice(&be(30));
pairset_b.extend_from_slice(&(-30i16).to_be_bytes());
pairset_b.extend_from_slice(&be(40));
pairset_b.extend_from_slice(&(-50i16).to_be_bytes());
let header_len = 10 + 4; let pairset_a_off = header_len;
let pairset_b_off = pairset_a_off + pairset_a.len();
let coverage_off = pairset_b_off + pairset_b.len();
let mut b = vec![0u8; header_len];
b[0..2].copy_from_slice(&be(1));
b[2..4].copy_from_slice(&be(coverage_off as u16));
b[4..6].copy_from_slice(&be(vf1));
b[6..8].copy_from_slice(&be(0));
b[8..10].copy_from_slice(&be(2)); b[10..12].copy_from_slice(&be(pairset_a_off as u16));
b[12..14].copy_from_slice(&be(pairset_b_off as u16));
b.extend_from_slice(&pairset_a);
b.extend_from_slice(&pairset_b);
b.extend_from_slice(&be(1));
b.extend_from_slice(&be(2));
b.extend_from_slice(&be(8));
b.extend_from_slice(&be(9));
b
}
#[test]
fn pair_pos_format1_lookup() {
let b = pairpos_f1_subtable();
let pp = PairPos::parse(&b).unwrap();
assert_eq!(pp.format(), 1);
assert_eq!(pp.value_format1(), ValueFormat(0x0004));
assert_eq!(pp.value_format2(), ValueFormat(0));
let pv = pp.pair(8, 40).unwrap().unwrap();
assert_eq!(pv.first.x_advance, -40);
assert_eq!(pv.second, ValueRecord::default());
assert_eq!(pp.pair(9, 30).unwrap().unwrap().first.x_advance, -30);
assert_eq!(pp.pair(9, 40).unwrap().unwrap().first.x_advance, -50);
assert!(pp.pair(8, 30).is_none());
assert!(pp.pair(7, 40).is_none());
}
#[test]
fn pair_pos_format1_iter() {
let b = pairpos_f1_subtable();
let pp = PairPos::parse(&b).unwrap();
let triples: Vec<_> = pp
.iter()
.map(|(f, s, v)| (f, s, v.unwrap().first.x_advance))
.collect();
assert_eq!(triples, vec![(8, 40, -40), (9, 30, -30), (9, 40, -50)]);
}
#[test]
fn pair_pos_format1_rejects_count_mismatch() {
let mut b = pairpos_f1_subtable();
b[8..10].copy_from_slice(&be(1)); assert!(matches!(PairPos::parse(&b), Err(Error::BadStructure(_))));
}
fn pairpos_f2_subtable() -> Vec<u8> {
let vf1 = 0x0004u16; let class1_count = 2u16;
let class2_count = 2u16;
let cell = 2usize;
let matrix_len = class1_count as usize * class2_count as usize * cell;
let header_len = 16;
let matrix_off = header_len;
let class_def1_off = matrix_off + matrix_len;
let cd1 = {
let mut v = Vec::new();
v.extend_from_slice(&be(2)); v.extend_from_slice(&be(1)); v.extend_from_slice(&be(8)); v.extend_from_slice(&be(8)); v.extend_from_slice(&be(1)); v
};
let class_def2_off = class_def1_off + cd1.len();
let cd2 = {
let mut v = Vec::new();
v.extend_from_slice(&be(2));
v.extend_from_slice(&be(1));
v.extend_from_slice(&be(40));
v.extend_from_slice(&be(40));
v.extend_from_slice(&be(1));
v
};
let coverage_off = class_def2_off + cd2.len();
let mut b = vec![0u8; header_len];
b[0..2].copy_from_slice(&be(2)); b[2..4].copy_from_slice(&be(coverage_off as u16));
b[4..6].copy_from_slice(&be(vf1));
b[6..8].copy_from_slice(&be(0)); b[8..10].copy_from_slice(&be(class_def1_off as u16));
b[10..12].copy_from_slice(&be(class_def2_off as u16));
b[12..14].copy_from_slice(&be(class1_count));
b[14..16].copy_from_slice(&be(class2_count));
let cells: [i16; 4] = [0, 0, 11, 22];
for c in cells {
b.extend_from_slice(&c.to_be_bytes());
}
b.extend_from_slice(&cd1);
b.extend_from_slice(&cd2);
b.extend_from_slice(&be(1));
b.extend_from_slice(&be(2));
b.extend_from_slice(&be(8));
b.extend_from_slice(&be(9));
b
}
#[test]
fn pair_pos_format2_class_matrix() {
let b = pairpos_f2_subtable();
let pp = PairPos::parse(&b).unwrap();
assert_eq!(pp.format(), 2);
let pv = pp.pair(8, 40).unwrap().unwrap();
assert_eq!(pv.first.x_advance, 22);
assert_eq!(pp.pair(8, 9).unwrap().unwrap().first.x_advance, 11);
assert_eq!(pp.pair(9, 40).unwrap().unwrap().first.x_advance, 0);
assert_eq!(pp.class_pair(1, 1).unwrap().unwrap().first.x_advance, 22);
assert_eq!(pp.class_pair(1, 0).unwrap().unwrap().first.x_advance, 11);
assert!(pp.class_pair(2, 0).is_none());
assert!(pp.pair(7, 40).is_none());
assert_eq!(pp.iter().count(), 0);
let f1_bytes = pairpos_f1_subtable();
let f1 = PairPos::parse(&f1_bytes).unwrap();
assert!(f1.class_pair(0, 0).is_none());
}
#[test]
fn pair_pos_format2_rejects_bad_classdef_offset() {
let mut b = pairpos_f2_subtable();
b[8..10].copy_from_slice(&be(0)); assert!(matches!(PairPos::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn pair_pos_rejects_reserved_value_format() {
let mut b = pairpos_f1_subtable();
b[4..6].copy_from_slice(&be(0x0100)); assert!(matches!(PairPos::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn pair_pos_rejects_unknown_format() {
let mut b = pairpos_f1_subtable();
b[0..2].copy_from_slice(&be(7));
assert!(matches!(PairPos::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn pair_pos_format2_truncated_matrix() {
let mut b = pairpos_f2_subtable();
b[12..14].copy_from_slice(&be(100)); assert!(matches!(PairPos::parse(&b), Err(Error::UnexpectedEof)));
}
#[test]
fn gpos_table_pair_pos_accessor() {
let sub = pairpos_f1_subtable();
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"kern");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1)); bytes[46..48].copy_from_slice(&be(4)); bytes[48..50].copy_from_slice(&be(2)); bytes[50..52].copy_from_slice(&be(0)); bytes[52..54].copy_from_slice(&be(1)); bytes.extend_from_slice(&be(8)); bytes.extend_from_slice(&sub);
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup(0).map(|l| l.lookup_type()), Some(2));
let pp = g.pair_pos(0, 0).unwrap().unwrap();
assert_eq!(pp.pair(9, 40).unwrap().unwrap().first.x_advance, -50);
assert!(g.pair_pos(5, 0).is_none());
}
fn build_extension_pos(ext_type: u16, inner: &[u8]) -> Vec<u8> {
let mut b = Vec::new();
b.extend_from_slice(&be(1)); b.extend_from_slice(&be(ext_type)); b.extend_from_slice(&8u32.to_be_bytes()); b.extend_from_slice(inner); b
}
#[test]
fn extension_pos_round_trip_wrapping_single_pos() {
let inner = singlepos_f1_subtable();
let b = build_extension_pos(GPOS_LOOKUP_TYPE_SINGLE, &inner);
let ext = ExtensionPos::parse(&b).unwrap();
assert_eq!(ext.format(), 1);
assert_eq!(ext.extension_lookup_type(), GPOS_LOOKUP_TYPE_SINGLE);
assert_eq!(ext.extension_offset(), 8);
assert_eq!(ext.extension_subtable_bytes(), &inner[..]);
let sp = ext.as_single_pos().unwrap();
assert_eq!(sp.value(11).unwrap().unwrap().x_advance, -50);
assert!(ext.as_pair_pos().is_err());
}
#[test]
fn extension_pos_round_trip_wrapping_pair_pos() {
let inner = pairpos_f1_subtable();
let b = build_extension_pos(GPOS_LOOKUP_TYPE_PAIR, &inner);
let ext = ExtensionPos::parse(&b).unwrap();
assert_eq!(ext.extension_lookup_type(), GPOS_LOOKUP_TYPE_PAIR);
let pp = ext.as_pair_pos().unwrap();
assert_eq!(pp.pair(9, 40).unwrap().unwrap().first.x_advance, -50);
assert!(ext.as_single_pos().is_err());
}
#[test]
fn extension_pos_raw_bytes_for_untyped_wrapped_type() {
let inner = [0xAAu8, 0xBB, 0xCC, 0xDD];
let b = build_extension_pos(GPOS_LOOKUP_TYPE_CURSIVE, &inner);
let ext = ExtensionPos::parse(&b).unwrap();
assert_eq!(ext.extension_lookup_type(), GPOS_LOOKUP_TYPE_CURSIVE);
assert_eq!(ext.extension_subtable_bytes(), &inner[..]);
assert!(ext.as_single_pos().is_err());
assert!(ext.as_pair_pos().is_err());
}
#[test]
fn extension_pos_rejects_unknown_format() {
let mut b = build_extension_pos(GPOS_LOOKUP_TYPE_SINGLE, &singlepos_f1_subtable());
b[0..2].copy_from_slice(&be(2)); assert!(ExtensionPos::parse(&b).is_err());
}
#[test]
fn extension_pos_rejects_extension_pointing_at_extension() {
let inner = singlepos_f1_subtable();
let b = build_extension_pos(GPOS_LOOKUP_TYPE_EXTENSION, &inner);
assert!(ExtensionPos::parse(&b).is_err());
}
#[test]
fn extension_pos_rejects_out_of_range_type() {
for bad in [0u16, 10, 0xFFFF] {
let b = build_extension_pos(bad, &singlepos_f1_subtable());
assert!(ExtensionPos::parse(&b).is_err(), "type {bad} should reject");
}
}
#[test]
fn extension_pos_rejects_null_and_oob_offset() {
let mut b = build_extension_pos(GPOS_LOOKUP_TYPE_SINGLE, &singlepos_f1_subtable());
b[4..8].copy_from_slice(&0u32.to_be_bytes());
assert!(ExtensionPos::parse(&b).is_err());
let mut b = build_extension_pos(GPOS_LOOKUP_TYPE_SINGLE, &singlepos_f1_subtable());
let len = b.len() as u32;
b[4..8].copy_from_slice(&len.to_be_bytes());
assert!(ExtensionPos::parse(&b).is_err());
}
#[test]
fn extension_pos_rejects_truncated_header() {
let full = build_extension_pos(GPOS_LOOKUP_TYPE_SINGLE, &singlepos_f1_subtable());
for len in 0..8 {
assert!(
ExtensionPos::parse(&full[..len]).is_err(),
"len {len} should reject"
);
}
}
#[test]
fn gpos_table_extension_pos_accessor() {
let sub = build_extension_pos(GPOS_LOOKUP_TYPE_SINGLE, &singlepos_f1_subtable());
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"kern");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1)); bytes[46..48].copy_from_slice(&be(4)); bytes[48..50].copy_from_slice(&be(9)); bytes[50..52].copy_from_slice(&be(0)); bytes[52..54].copy_from_slice(&be(1)); bytes.extend_from_slice(&be(8)); bytes.extend_from_slice(&sub);
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup(0).map(|l| l.lookup_type()), Some(9));
let ext = g.extension_pos(0, 0).unwrap().unwrap();
assert_eq!(ext.extension_lookup_type(), GPOS_LOOKUP_TYPE_SINGLE);
let sp = ext.as_single_pos().unwrap();
assert_eq!(sp.value(11).unwrap().unwrap().x_advance, -50);
assert!(g.extension_pos(5, 0).is_none());
assert!(g.single_pos(0, 0).unwrap().is_err());
assert!(g.pair_pos(0, 0).unwrap().is_err());
}
}