use crate::parser::{read_i16, read_u16, read_u32};
use crate::tables::gdef::{
class_def_lookup, coverage_lookup, lookup_table_slice, popcount_u16, GdefTable,
};
use crate::Error;
const LOOKUP_SINGLE_POS: u16 = 1;
const LOOKUP_PAIR_POS: u16 = 2;
const LOOKUP_CURSIVE_POS: u16 = 3;
const LOOKUP_MARK_BASE_POS: u16 = 4;
const LOOKUP_MARK_LIGATURE_POS: u16 = 5;
const LOOKUP_MARK_MARK_POS: u16 = 6;
const LOOKUP_CHAIN_CONTEXT_POS: u16 = 8;
const LOOKUP_EXTENSION_POS: u16 = 9;
const MAX_NESTED_LOOKUP_DEPTH: u8 = 8;
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_PLA_DEVICE: u16 = 0x0010;
const VF_Y_PLA_DEVICE: u16 = 0x0020;
const VF_X_ADV_DEVICE: u16 = 0x0040;
const VF_Y_ADV_DEVICE: u16 = 0x0080;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PosValue {
pub x_placement: i16,
pub y_placement: i16,
pub x_advance: i16,
pub y_advance: i16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PosRecord {
pub glyph_index: usize,
pub value: PosValue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CursiveAttachment {
pub entry: Option<(i16, i16)>,
pub exit: Option<(i16, i16)>,
}
#[derive(Debug, Clone)]
pub struct GposTable<'a> {
bytes: &'a [u8],
lookup_list_off: u32,
}
impl<'a> GposTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 10 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
if major != 1 {
return Err(Error::BadStructure("GPOS: unsupported major version"));
}
let lookup_list_off = read_u16(bytes, 8)? as u32;
if lookup_list_off as usize >= bytes.len() {
return Err(Error::BadOffset);
}
Ok(Self {
bytes,
lookup_list_off,
})
}
pub fn lookup_list(&self) -> impl Iterator<Item = (u16, u16, u16)> + '_ {
let lookup_count = self
.bytes
.get(self.lookup_list_off as usize..)
.and_then(|s| read_u16(s, 0).ok())
.unwrap_or(0);
(0..lookup_count).filter_map(move |i| {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
return None;
}
let mut kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()?;
if kind == LOOKUP_EXTENSION_POS && sub_count > 0 {
if let Some(t) = peek_extension_type(lookup) {
kind = t;
}
}
Some((i, kind, sub_count))
})
}
pub fn apply_lookup_type_1(&self, lookup_index: u16, gid: u16) -> Option<PosValue> {
if self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_SINGLE_POS {
continue;
}
if let Some(v) = single_pos_lookup(effective_sub, gid) {
return Some(v);
}
}
None
}
pub fn apply_lookup_type_3(&self, lookup_index: u16, gid: u16) -> Option<CursiveAttachment> {
if self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_CURSIVE_POS {
continue;
}
if let Some(v) = cursive_pos_lookup(effective_sub, gid) {
return Some(v);
}
}
None
}
pub fn lookup_cursive_attachment(&self, gid: u16) -> Option<CursiveAttachment> {
let lookup_list = self.bytes.get(self.lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let lookup_count = read_u16(lookup_list, 0).ok()?;
for i in 0..lookup_count {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
continue;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_CURSIVE_POS {
continue;
}
if let Some(v) = cursive_pos_lookup(effective_sub, gid) {
return Some(v);
}
}
}
None
}
pub fn apply_lookup_type_5(
&self,
lookup_index: u16,
ligature: u16,
ligature_component: u16,
mark: u16,
) -> Option<(i16, i16)> {
if self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_LIGATURE_POS {
continue;
}
if let Some(v) =
mark_ligature_pos_lookup(effective_sub, ligature, ligature_component, mark)
{
return Some(v);
}
}
None
}
pub fn lookup_mark_to_ligature(
&self,
ligature: u16,
ligature_component: u16,
mark: u16,
) -> Option<(i16, i16)> {
let lookup_list = self.bytes.get(self.lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let lookup_count = read_u16(lookup_list, 0).ok()?;
for i in 0..lookup_count {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
continue;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_LIGATURE_POS {
continue;
}
if let Some(v) =
mark_ligature_pos_lookup(effective_sub, ligature, ligature_component, mark)
{
return Some(v);
}
}
}
None
}
pub fn apply_lookup_type_8(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
) -> Option<Vec<PosRecord>> {
self.apply_chain_context_at(lookup_index, gids, pos, 0)
}
pub fn lookup_mark_to_base(&self, base: u16, mark: u16) -> Option<(i16, i16)> {
let lookup_list = self.bytes.get(self.lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let lookup_count = read_u16(lookup_list, 0).ok()?;
for i in 0..lookup_count {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
continue;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = if kind == LOOKUP_EXTENSION_POS {
if sub.len() < 8 {
continue;
}
let ext_type = read_u16(sub, 2).ok()?;
let ext_off = read_u32(sub, 4).ok()? as usize;
let ext = sub.get(ext_off..)?;
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_MARK_BASE_POS {
continue;
}
if let Some(v) = mark_base_pos_lookup(effective_sub, base, mark) {
return Some(v);
}
}
}
None
}
pub fn lookup_mark_to_mark(&self, mark1: u16, mark2: u16) -> Option<(i16, i16)> {
let lookup_list = self.bytes.get(self.lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let lookup_count = read_u16(lookup_list, 0).ok()?;
for i in 0..lookup_count {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
continue;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = if kind == LOOKUP_EXTENSION_POS {
if sub.len() < 8 {
continue;
}
let ext_type = read_u16(sub, 2).ok()?;
let ext_off = read_u32(sub, 4).ok()? as usize;
let ext = sub.get(ext_off..)?;
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_MARK_MARK_POS {
continue;
}
if let Some(v) = mark_mark_pos_lookup(effective_sub, mark1, mark2) {
return Some(v);
}
}
}
None
}
pub fn lookup_kerning(&self, left: u16, right: u16, gdef: Option<&GdefTable<'_>>) -> i16 {
let lookup_list = match self.bytes.get(self.lookup_list_off as usize..) {
Some(s) => s,
None => return 0,
};
if lookup_list.len() < 2 {
return 0;
}
let lookup_count = match read_u16(lookup_list, 0) {
Ok(c) => c,
Err(_) => return 0,
};
for i in 0..lookup_count {
let lookup = match lookup_table_slice(self.bytes, self.lookup_list_off, i) {
Some(s) => s,
None => continue,
};
if lookup.len() < 6 {
continue;
}
let kind = match read_u16(lookup, 0) {
Ok(k) => k,
Err(_) => continue,
};
let flag = read_u16(lookup, 2).unwrap_or(0);
let ignore_marks = (flag & 0x0008) != 0;
if ignore_marks {
if let Some(g) = gdef {
if g.is_mark(left) || g.is_mark(right) {
continue;
}
}
}
let sub_count = read_u16(lookup, 4).unwrap_or(0) as usize;
for s in 0..sub_count {
let sub_off = match read_u16(lookup, 6 + s * 2) {
Ok(o) => o as usize,
Err(_) => continue,
};
let sub = match lookup.get(sub_off..) {
Some(b) => b,
None => continue,
};
let (effective_kind, effective_sub) = if kind == LOOKUP_EXTENSION_POS {
if sub.len() < 8 {
continue;
}
let ext_type = read_u16(sub, 2).unwrap_or(0);
let ext_off = read_u32(sub, 4).unwrap_or(0) as usize;
let ext = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_PAIR_POS {
continue;
}
if let Some(v) = pair_pos_lookup(effective_sub, left, right) {
return v;
}
}
}
0
}
}
fn pair_pos_lookup(sub: &[u8], left: u16, right: u16) -> Option<i16> {
if sub.len() < 8 {
return None;
}
let format = read_u16(sub, 0).ok()?;
let coverage_off = read_u16(sub, 2).ok()? as usize;
let value_format1 = read_u16(sub, 4).ok()?;
let value_format2 = read_u16(sub, 6).ok()?;
let cov = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(cov, left)?;
let v1_size = popcount_u16(value_format1) * 2;
let v2_size = popcount_u16(value_format2) * 2;
match format {
1 => pair_pos_format1(sub, cov_idx, right, value_format1, v1_size, v2_size),
2 => pair_pos_format2(sub, left, right, value_format1, v1_size, v2_size),
_ => None,
}
}
fn pair_pos_format1(
sub: &[u8],
cov_idx: u16,
right: u16,
value_format1: u16,
v1_size: usize,
v2_size: usize,
) -> Option<i16> {
let pair_set_count = read_u16(sub, 8).ok()?;
if cov_idx >= pair_set_count {
return None;
}
let pair_set_off = read_u16(sub, 10 + cov_idx as usize * 2).ok()? as usize;
let pair_set = sub.get(pair_set_off..)?;
if pair_set.len() < 2 {
return None;
}
let pair_value_count = read_u16(pair_set, 0).ok()? as usize;
let record_size = 2 + v1_size + v2_size;
let mut lo = 0usize;
let mut hi = pair_value_count;
while lo < hi {
let mid = (lo + hi) / 2;
let off = 2 + mid * record_size;
let sg = read_u16(pair_set, off).ok()?;
if sg == right {
return Some(extract_x_advance(pair_set, off + 2, value_format1));
}
if sg < right {
lo = mid + 1;
} else {
hi = mid;
}
}
None
}
fn pair_pos_format2(
sub: &[u8],
left: u16,
right: u16,
value_format1: u16,
v1_size: usize,
v2_size: usize,
) -> Option<i16> {
let class_def1_off = read_u16(sub, 8).ok()? as usize;
let class_def2_off = read_u16(sub, 10).ok()? as usize;
let _class1_count = read_u16(sub, 12).ok()?;
let class2_count = read_u16(sub, 14).ok()? as usize;
let cd1 = sub.get(class_def1_off..)?;
let cd2 = sub.get(class_def2_off..)?;
let class1 = class_def_lookup(cd1, left).unwrap_or(0);
let class2 = class_def_lookup(cd2, right).unwrap_or(0);
let class2_record_size = v1_size + v2_size;
let class1_record_size = class2_count * class2_record_size;
let class1_records_start = 16usize;
let off = class1_records_start
+ class1 as usize * class1_record_size
+ class2 as usize * class2_record_size;
if v1_size == 0 {
return None;
}
Some(extract_x_advance(sub, off, value_format1))
}
fn mark_base_pos_lookup(sub: &[u8], base: u16, mark: u16) -> Option<(i16, i16)> {
if sub.len() < 12 {
return None;
}
let format = read_u16(sub, 0).ok()?;
if format != 1 {
return None;
}
let mark_cov_off = read_u16(sub, 2).ok()? as usize;
let base_cov_off = read_u16(sub, 4).ok()? as usize;
let mark_class_count = read_u16(sub, 6).ok()? as usize;
let mark_array_off = read_u16(sub, 8).ok()? as usize;
let base_array_off = read_u16(sub, 10).ok()? as usize;
let mark_cov = sub.get(mark_cov_off..)?;
let base_cov = sub.get(base_cov_off..)?;
let mark_idx = coverage_lookup(mark_cov, mark)? as usize;
let base_idx = coverage_lookup(base_cov, base)? as usize;
let mark_array = sub.get(mark_array_off..)?;
if mark_array.len() < 2 {
return None;
}
let mark_count = read_u16(mark_array, 0).ok()? as usize;
if mark_idx >= mark_count {
return None;
}
let mr_off = 2 + mark_idx * 4;
let mark_class = read_u16(mark_array, mr_off).ok()? as usize;
let mark_anchor_off_local = read_u16(mark_array, mr_off + 2).ok()? as usize;
if mark_class >= mark_class_count {
return None;
}
let mark_anchor = mark_array.get(mark_anchor_off_local..)?;
let (mx, my) = parse_anchor(mark_anchor)?;
let base_array = sub.get(base_array_off..)?;
if base_array.len() < 2 {
return None;
}
let base_count = read_u16(base_array, 0).ok()? as usize;
if base_idx >= base_count {
return None;
}
let br_off = 2 + base_idx * mark_class_count * 2;
let base_anchor_off_local = read_u16(base_array, br_off + mark_class * 2).ok()? as usize;
if base_anchor_off_local == 0 {
return None;
}
let base_anchor = base_array.get(base_anchor_off_local..)?;
let (bx, by) = parse_anchor(base_anchor)?;
Some((bx.wrapping_sub(mx), by.wrapping_sub(my)))
}
fn mark_mark_pos_lookup(sub: &[u8], mark1: u16, mark2: u16) -> Option<(i16, i16)> {
if sub.len() < 12 {
return None;
}
let format = read_u16(sub, 0).ok()?;
if format != 1 {
return None;
}
let mark1_cov_off = read_u16(sub, 2).ok()? as usize;
let mark2_cov_off = read_u16(sub, 4).ok()? as usize;
let mark_class_count = read_u16(sub, 6).ok()? as usize;
let mark1_array_off = read_u16(sub, 8).ok()? as usize;
let mark2_array_off = read_u16(sub, 10).ok()? as usize;
let mark2_cov = sub.get(mark1_cov_off..)?; let mark1_cov = sub.get(mark2_cov_off..)?; let mark2_idx = coverage_lookup(mark2_cov, mark2)? as usize;
let mark1_idx = coverage_lookup(mark1_cov, mark1)? as usize;
let new_mark_array = sub.get(mark1_array_off..)?;
if new_mark_array.len() < 2 {
return None;
}
let new_mark_count = read_u16(new_mark_array, 0).ok()? as usize;
if mark2_idx >= new_mark_count {
return None;
}
let nr_off = 2 + mark2_idx * 4;
let new_mark_class = read_u16(new_mark_array, nr_off).ok()? as usize;
let new_anchor_off_local = read_u16(new_mark_array, nr_off + 2).ok()? as usize;
if new_mark_class >= mark_class_count {
return None;
}
let new_mark_anchor = new_mark_array.get(new_anchor_off_local..)?;
let (mx, my) = parse_anchor(new_mark_anchor)?;
let prev_array = sub.get(mark2_array_off..)?;
if prev_array.len() < 2 {
return None;
}
let prev_count = read_u16(prev_array, 0).ok()? as usize;
if mark1_idx >= prev_count {
return None;
}
let pr_off = 2 + mark1_idx * mark_class_count * 2;
let prev_anchor_off_local = read_u16(prev_array, pr_off + new_mark_class * 2).ok()? as usize;
if prev_anchor_off_local == 0 {
return None;
}
let prev_anchor = prev_array.get(prev_anchor_off_local..)?;
let (bx, by) = parse_anchor(prev_anchor)?;
Some((bx.wrapping_sub(mx), by.wrapping_sub(my)))
}
fn cursive_pos_lookup(sub: &[u8], gid: u16) -> Option<CursiveAttachment> {
if sub.len() < 6 {
return None;
}
let format = read_u16(sub, 0).ok()?;
if format != 1 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let entry_exit_count = read_u16(sub, 4).ok()? as usize;
let cov = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(cov, gid)? as usize;
if cov_idx >= entry_exit_count {
return None;
}
let rec_off = 6 + cov_idx * 4;
if sub.len() < rec_off + 4 {
return None;
}
let entry_off = read_u16(sub, rec_off).ok()? as usize;
let exit_off = read_u16(sub, rec_off + 2).ok()? as usize;
let entry = if entry_off == 0 {
None
} else {
sub.get(entry_off..).and_then(parse_anchor)
};
let exit = if exit_off == 0 {
None
} else {
sub.get(exit_off..).and_then(parse_anchor)
};
Some(CursiveAttachment { entry, exit })
}
fn mark_ligature_pos_lookup(
sub: &[u8],
ligature: u16,
ligature_component: u16,
mark: u16,
) -> Option<(i16, i16)> {
if sub.len() < 12 {
return None;
}
let format = read_u16(sub, 0).ok()?;
if format != 1 {
return None;
}
let mark_cov_off = read_u16(sub, 2).ok()? as usize;
let lig_cov_off = read_u16(sub, 4).ok()? as usize;
let mark_class_count = read_u16(sub, 6).ok()? as usize;
let mark_array_off = read_u16(sub, 8).ok()? as usize;
let lig_array_off = read_u16(sub, 10).ok()? as usize;
let mark_cov = sub.get(mark_cov_off..)?;
let lig_cov = sub.get(lig_cov_off..)?;
let mark_idx = coverage_lookup(mark_cov, mark)? as usize;
let lig_idx = coverage_lookup(lig_cov, ligature)? as usize;
let mark_array = sub.get(mark_array_off..)?;
if mark_array.len() < 2 {
return None;
}
let mark_count = read_u16(mark_array, 0).ok()? as usize;
if mark_idx >= mark_count {
return None;
}
let mr_off = 2 + mark_idx * 4;
let mark_class = read_u16(mark_array, mr_off).ok()? as usize;
let mark_anchor_off_local = read_u16(mark_array, mr_off + 2).ok()? as usize;
if mark_class >= mark_class_count {
return None;
}
let mark_anchor = mark_array.get(mark_anchor_off_local..)?;
let (mx, my) = parse_anchor(mark_anchor)?;
let lig_array = sub.get(lig_array_off..)?;
if lig_array.len() < 2 {
return None;
}
let lig_count = read_u16(lig_array, 0).ok()? as usize;
if lig_idx >= lig_count {
return None;
}
let lig_attach_off = read_u16(lig_array, 2 + lig_idx * 2).ok()? as usize;
let lig_attach = lig_array.get(lig_attach_off..)?;
if lig_attach.len() < 2 {
return None;
}
let component_count = read_u16(lig_attach, 0).ok()? as usize;
if (ligature_component as usize) >= component_count {
return None;
}
let comp_record_size = mark_class_count * 2;
let comp_off = 2 + ligature_component as usize * comp_record_size;
let lig_anchor_off_local = read_u16(lig_attach, comp_off + mark_class * 2).ok()? as usize;
if lig_anchor_off_local == 0 {
return None;
}
let lig_anchor = lig_attach.get(lig_anchor_off_local..)?;
let (lx, ly) = parse_anchor(lig_anchor)?;
Some((lx.wrapping_sub(mx), ly.wrapping_sub(my)))
}
fn parse_anchor(bytes: &[u8]) -> Option<(i16, i16)> {
if bytes.len() < 6 {
return None;
}
let format = read_u16(bytes, 0).ok()?;
let x = read_i16(bytes, 2).ok()?;
let y = read_i16(bytes, 4).ok()?;
match format {
1..=3 => Some((x, y)),
_ => None,
}
}
fn extract_x_advance(bytes: &[u8], off: usize, value_format: u16) -> i16 {
let mut p = off;
if value_format & VF_X_PLACEMENT != 0 {
p += 2;
}
if value_format & VF_Y_PLACEMENT != 0 {
p += 2;
}
if value_format & VF_X_ADVANCE != 0 {
return read_i16(bytes, p).unwrap_or(0);
}
0
}
fn value_record_size(value_format: u16) -> usize {
popcount_u16(value_format) * 2
}
fn parse_value_record(bytes: &[u8], off: usize, value_format: u16) -> PosValue {
let mut v = PosValue::default();
let mut p = off;
if value_format & VF_X_PLACEMENT != 0 {
v.x_placement = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_Y_PLACEMENT != 0 {
v.y_placement = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_X_ADVANCE != 0 {
v.x_advance = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_Y_ADVANCE != 0 {
v.y_advance = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_X_PLA_DEVICE != 0 {
p += 2;
}
if value_format & VF_Y_PLA_DEVICE != 0 {
p += 2;
}
if value_format & VF_X_ADV_DEVICE != 0 {
p += 2;
}
if value_format & VF_Y_ADV_DEVICE != 0 {
p += 2;
}
let _ = p;
v
}
fn single_pos_lookup(sub: &[u8], gid: u16) -> Option<PosValue> {
if sub.len() < 6 {
return None;
}
let format = read_u16(sub, 0).ok()?;
let coverage_off = read_u16(sub, 2).ok()? as usize;
let value_format = read_u16(sub, 4).ok()?;
let cov = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(cov, gid)? as usize;
let vr_size = value_record_size(value_format);
match format {
1 => {
Some(parse_value_record(sub, 6, value_format))
}
2 => {
let value_count = read_u16(sub, 6).ok()? as usize;
if cov_idx >= value_count {
return None;
}
let vr_off = 8 + cov_idx * vr_size;
if sub.len() < vr_off + vr_size {
return None;
}
Some(parse_value_record(sub, vr_off, value_format))
}
_ => None,
}
}
fn unwrap_extension(kind: u16, sub: &[u8]) -> Option<(u16, &[u8])> {
if kind == LOOKUP_EXTENSION_POS {
if sub.len() < 8 {
return None;
}
let ext_type = read_u16(sub, 2).ok()?;
let ext_off = read_u32(sub, 4).ok()? as usize;
let ext = sub.get(ext_off..)?;
Some((ext_type, ext))
} else {
Some((kind, sub))
}
}
fn peek_extension_type(lookup: &[u8]) -> Option<u16> {
if lookup.len() < 8 {
return None;
}
let sub_count = read_u16(lookup, 4).ok()? as usize;
if sub_count == 0 {
return None;
}
let sub_off = read_u16(lookup, 6).ok()? as usize;
let sub = lookup.get(sub_off..)?;
if sub.len() < 8 {
return None;
}
read_u16(sub, 2).ok()
}
fn read_pos_lookup_records(
bytes: &[u8],
offset: usize,
count: usize,
) -> Option<Vec<PosLookupRecord>> {
if bytes.len() < offset + count * 4 {
return None;
}
let mut out = Vec::with_capacity(count);
for i in 0..count {
let off = offset + i * 4;
let seq = read_u16(bytes, off).ok()?;
let lk = read_u16(bytes, off + 2).ok()?;
out.push(PosLookupRecord {
sequence_index: seq,
lookup_index: lk,
});
}
Some(out)
}
#[derive(Debug, Clone, Copy)]
struct PosLookupRecord {
sequence_index: u16,
lookup_index: u16,
}
#[derive(Debug)]
struct ChainPosMatch {
input_len: usize,
records: Vec<PosLookupRecord>,
}
impl<'a> GposTable<'a> {
fn apply_chain_context_at(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
depth: u8,
) -> Option<Vec<PosRecord>> {
if depth >= MAX_NESTED_LOOKUP_DEPTH {
return None;
}
if pos >= gids.len() || self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = match unwrap_extension(kind, sub) {
Some(p) => p,
None => continue,
};
if effective_kind != LOOKUP_CHAIN_CONTEXT_POS {
continue;
}
if effective_sub.len() < 2 {
continue;
}
let format = match read_u16(effective_sub, 0) {
Ok(v) => v,
Err(_) => continue,
};
let matched = match format {
1 => chain_context_pos_format1_match(effective_sub, gids, pos),
2 => chain_context_pos_format2_match(effective_sub, gids, pos),
3 => chain_context_pos_format3_match(effective_sub, gids, pos),
_ => None,
};
if let Some(m) = matched {
return Some(self.apply_pos_records(gids, pos, &m, depth));
}
}
None
}
fn apply_pos_records(
&self,
gids: &[u16],
pos: usize,
m: &ChainPosMatch,
depth: u8,
) -> Vec<PosRecord> {
let mut out: Vec<PosRecord> = Vec::new();
for rec in &m.records {
let seq_idx = rec.sequence_index as usize;
if seq_idx >= m.input_len {
continue;
}
let abs_idx = pos + seq_idx;
if abs_idx >= gids.len() {
continue;
}
let lookup =
match lookup_table_slice(self.bytes, self.lookup_list_off, rec.lookup_index) {
Some(s) => s,
None => continue,
};
if lookup.len() < 2 {
continue;
}
let mut nested_kind = match read_u16(lookup, 0) {
Ok(v) => v,
Err(_) => continue,
};
if nested_kind == LOOKUP_EXTENSION_POS {
if let Some(t) = peek_extension_type(lookup) {
nested_kind = t;
}
}
match nested_kind {
LOOKUP_SINGLE_POS => {
if let Some(v) = self.apply_lookup_type_1(rec.lookup_index, gids[abs_idx]) {
out.push(PosRecord {
glyph_index: abs_idx,
value: v,
});
}
}
LOOKUP_PAIR_POS if abs_idx + 1 < gids.len() => {
let dx = self.lookup_kerning(gids[abs_idx], gids[abs_idx + 1], None);
if dx != 0 {
out.push(PosRecord {
glyph_index: abs_idx,
value: PosValue {
x_advance: dx,
..PosValue::default()
},
});
}
}
LOOKUP_CURSIVE_POS if abs_idx > 0 => {
let prev = self.apply_lookup_type_3(rec.lookup_index, gids[abs_idx - 1]);
let curr = self.apply_lookup_type_3(rec.lookup_index, gids[abs_idx]);
if let (Some(p), Some(c)) = (prev, curr) {
if let (Some((px, py)), Some((cx, cy))) = (p.exit, c.entry) {
out.push(PosRecord {
glyph_index: abs_idx,
value: PosValue {
x_placement: px.wrapping_sub(cx),
y_placement: py.wrapping_sub(cy),
..PosValue::default()
},
});
}
}
}
LOOKUP_MARK_BASE_POS if abs_idx + 1 < gids.len() => {
if let Some((dx, dy)) = self.lookup_mark_to_base_via_lookup(
rec.lookup_index,
gids[abs_idx],
gids[abs_idx + 1],
) {
out.push(PosRecord {
glyph_index: abs_idx + 1,
value: PosValue {
x_placement: dx,
y_placement: dy,
..PosValue::default()
},
});
}
}
LOOKUP_MARK_MARK_POS if abs_idx + 1 < gids.len() => {
if let Some((dx, dy)) = self.lookup_mark_to_mark_via_lookup(
rec.lookup_index,
gids[abs_idx],
gids[abs_idx + 1],
) {
out.push(PosRecord {
glyph_index: abs_idx + 1,
value: PosValue {
x_placement: dx,
y_placement: dy,
..PosValue::default()
},
});
}
}
LOOKUP_CHAIN_CONTEXT_POS => {
if let Some(mut nested) =
self.apply_chain_context_at(rec.lookup_index, gids, abs_idx, depth + 1)
{
out.append(&mut nested);
}
}
_ => {
}
}
}
out
}
fn lookup_mark_to_base_via_lookup(
&self,
lookup_index: u16,
base: u16,
mark: u16,
) -> Option<(i16, i16)> {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_BASE_POS {
continue;
}
if let Some(v) = mark_base_pos_lookup(effective_sub, base, mark) {
return Some(v);
}
}
None
}
fn lookup_mark_to_mark_via_lookup(
&self,
lookup_index: u16,
mark1: u16,
mark2: u16,
) -> Option<(i16, i16)> {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_MARK_POS {
continue;
}
if let Some(v) = mark_mark_pos_lookup(effective_sub, mark1, mark2) {
return Some(v);
}
}
None
}
}
fn chain_context_pos_format1_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 6 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let coverage = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(coverage, gids[pos])? as usize;
let set_count = read_u16(sub, 4).ok()? as usize;
if cov_idx >= set_count {
return None;
}
let set_off = read_u16(sub, 6 + cov_idx * 2).ok()? as usize;
let set = sub.get(set_off..)?;
if set.len() < 2 {
return None;
}
let rule_count = read_u16(set, 0).ok()? as usize;
for r in 0..rule_count {
let rule_off = read_u16(set, 2 + r * 2).ok()? as usize;
let rule = match set.get(rule_off..) {
Some(b) => b,
None => continue,
};
if let Some(m) = chain_context_pos_format1_rule_match(rule, gids, pos) {
return Some(m);
}
}
None
}
fn chain_context_pos_format1_rule_match(
rule: &[u8],
gids: &[u16],
pos: usize,
) -> Option<ChainPosMatch> {
let mut cur = 0usize;
if rule.len() < cur + 2 {
return None;
}
let bt_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + bt_count * 2 {
return None;
}
if pos < bt_count {
return None;
}
for i in 0..bt_count {
let want = read_u16(rule, cur + i * 2).ok()?;
if gids[pos - 1 - i] != want {
return None;
}
}
cur += bt_count * 2;
if rule.len() < cur + 2 {
return None;
}
let in_count = read_u16(rule, cur).ok()? as usize;
if in_count == 0 {
return None;
}
cur += 2;
let in_extra = in_count - 1;
if rule.len() < cur + in_extra * 2 {
return None;
}
if pos + in_count > gids.len() {
return None;
}
for i in 0..in_extra {
let want = read_u16(rule, cur + i * 2).ok()?;
if gids[pos + 1 + i] != want {
return None;
}
}
cur += in_extra * 2;
if rule.len() < cur + 2 {
return None;
}
let la_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + la_count * 2 {
return None;
}
if pos + in_count + la_count > gids.len() {
return None;
}
for i in 0..la_count {
let want = read_u16(rule, cur + i * 2).ok()?;
if gids[pos + in_count + i] != want {
return None;
}
}
cur += la_count * 2;
if rule.len() < cur + 2 {
return None;
}
let pos_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_pos_lookup_records(rule, cur, pos_count)?;
Some(ChainPosMatch {
input_len: in_count,
records,
})
}
fn chain_context_pos_format2_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 12 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let bt_cd_off = read_u16(sub, 4).ok()? as usize;
let in_cd_off = read_u16(sub, 6).ok()? as usize;
let la_cd_off = read_u16(sub, 8).ok()? as usize;
let set_count = read_u16(sub, 10).ok()? as usize;
let coverage = sub.get(coverage_off..)?;
coverage_lookup(coverage, gids[pos])?;
let in_cd = sub.get(in_cd_off..)?;
let in_class0 = class_def_lookup(in_cd, gids[pos]).unwrap_or(0);
if in_class0 as usize >= set_count {
return None;
}
let set_off = read_u16(sub, 12 + in_class0 as usize * 2).ok()? as usize;
if set_off == 0 {
return None;
}
let set = sub.get(set_off..)?;
if set.len() < 2 {
return None;
}
let rule_count = read_u16(set, 0).ok()? as usize;
let bt_cd = sub.get(bt_cd_off..);
let la_cd = sub.get(la_cd_off..);
for r in 0..rule_count {
let rule_off = read_u16(set, 2 + r * 2).ok()? as usize;
let rule = match set.get(rule_off..) {
Some(b) => b,
None => continue,
};
if let Some(m) =
chain_context_pos_format2_rule_match(rule, gids, pos, bt_cd, in_cd, la_cd, in_class0)
{
return Some(m);
}
}
None
}
fn chain_context_pos_format2_rule_match(
rule: &[u8],
gids: &[u16],
pos: usize,
bt_cd: Option<&[u8]>,
in_cd: &[u8],
la_cd: Option<&[u8]>,
in_class0: u16,
) -> Option<ChainPosMatch> {
let mut cur = 0usize;
if rule.len() < cur + 2 {
return None;
}
let bt_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + bt_count * 2 {
return None;
}
if pos < bt_count {
return None;
}
if bt_count > 0 {
let bt_cd = bt_cd?;
for i in 0..bt_count {
let want = read_u16(rule, cur + i * 2).ok()?;
let got = class_def_lookup(bt_cd, gids[pos - 1 - i]).unwrap_or(0);
if want != got {
return None;
}
}
}
cur += bt_count * 2;
if rule.len() < cur + 2 {
return None;
}
let in_count = read_u16(rule, cur).ok()? as usize;
if in_count == 0 {
return None;
}
cur += 2;
let in_extra = in_count - 1;
if rule.len() < cur + in_extra * 2 {
return None;
}
if pos + in_count > gids.len() {
return None;
}
for i in 0..in_extra {
let want = read_u16(rule, cur + i * 2).ok()?;
let got = class_def_lookup(in_cd, gids[pos + 1 + i]).unwrap_or(0);
if want != got {
return None;
}
}
cur += in_extra * 2;
let _ = in_class0;
if rule.len() < cur + 2 {
return None;
}
let la_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + la_count * 2 {
return None;
}
if pos + in_count + la_count > gids.len() {
return None;
}
if la_count > 0 {
let la_cd = la_cd?;
for i in 0..la_count {
let want = read_u16(rule, cur + i * 2).ok()?;
let got = class_def_lookup(la_cd, gids[pos + in_count + i]).unwrap_or(0);
if want != got {
return None;
}
}
}
cur += la_count * 2;
if rule.len() < cur + 2 {
return None;
}
let pos_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_pos_lookup_records(rule, cur, pos_count)?;
Some(ChainPosMatch {
input_len: in_count,
records,
})
}
fn chain_context_pos_format3_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 4 {
return None;
}
let mut cur = 2usize;
let bt_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
if sub.len() < cur + bt_count * 2 {
return None;
}
if pos < bt_count {
return None;
}
for i in 0..bt_count {
let cov_off = read_u16(sub, cur + i * 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
coverage_lookup(cov, gids[pos - 1 - i])?;
}
cur += bt_count * 2;
if sub.len() < cur + 2 {
return None;
}
let in_count = read_u16(sub, cur).ok()? as usize;
if in_count == 0 {
return None;
}
cur += 2;
if sub.len() < cur + in_count * 2 {
return None;
}
if pos + in_count > gids.len() {
return None;
}
for i in 0..in_count {
let cov_off = read_u16(sub, cur + i * 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
coverage_lookup(cov, gids[pos + i])?;
}
cur += in_count * 2;
if sub.len() < cur + 2 {
return None;
}
let la_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
if sub.len() < cur + la_count * 2 {
return None;
}
if pos + in_count + la_count > gids.len() {
return None;
}
for i in 0..la_count {
let cov_off = read_u16(sub, cur + i * 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
coverage_lookup(cov, gids[pos + in_count + i])?;
}
cur += la_count * 2;
if sub.len() < cur + 2 {
return None;
}
let pos_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
let records = read_pos_lookup_records(sub, cur, pos_count)?;
Some(ChainPosMatch {
input_len: in_count,
records,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn build_simple_pp1() -> Vec<u8> {
let mut pvr = Vec::new();
pvr.extend_from_slice(&60u16.to_be_bytes());
pvr.extend_from_slice(&(-100i16).to_be_bytes());
let mut pair_set = Vec::new();
pair_set.extend_from_slice(&1u16.to_be_bytes());
pair_set.extend_from_slice(&pvr);
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&50u16.to_be_bytes());
let header = 10;
let pair_set_offsets_size = 2;
let cov_off = header + pair_set_offsets_size;
let pair_set_off = cov_off + cov.len();
let mut pp1 = Vec::new();
pp1.extend_from_slice(&1u16.to_be_bytes()); pp1.extend_from_slice(&(cov_off as u16).to_be_bytes());
pp1.extend_from_slice(&VF_X_ADVANCE.to_be_bytes()); pp1.extend_from_slice(&0u16.to_be_bytes()); pp1.extend_from_slice(&1u16.to_be_bytes()); pp1.extend_from_slice(&(pair_set_off as u16).to_be_bytes());
pp1.extend_from_slice(&cov);
pp1.extend_from_slice(&pair_set);
let mut lookup = Vec::new();
lookup.extend_from_slice(&2u16.to_be_bytes());
lookup.extend_from_slice(&0u16.to_be_bytes());
lookup.extend_from_slice(&1u16.to_be_bytes());
lookup.extend_from_slice(&8u16.to_be_bytes());
lookup.extend_from_slice(&pp1);
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&4u16.to_be_bytes());
lookup_list.extend_from_slice(&lookup);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn pair_pos_format1_round_trip() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_kerning(50, 60, None), -100);
assert_eq!(g.lookup_kerning(50, 61, None), 0);
assert_eq!(g.lookup_kerning(99, 60, None), 0);
}
fn build_simple_mark_base() -> Vec<u8> {
let mut base_anchor = Vec::new();
base_anchor.extend_from_slice(&1u16.to_be_bytes());
base_anchor.extend_from_slice(&100i16.to_be_bytes());
base_anchor.extend_from_slice(&800i16.to_be_bytes());
let mut mark_anchor = Vec::new();
mark_anchor.extend_from_slice(&1u16.to_be_bytes());
mark_anchor.extend_from_slice(&50i16.to_be_bytes());
mark_anchor.extend_from_slice(&0i16.to_be_bytes());
let mut mark_array = Vec::new();
mark_array.extend_from_slice(&1u16.to_be_bytes());
mark_array.extend_from_slice(&0u16.to_be_bytes()); mark_array.extend_from_slice(&6u16.to_be_bytes()); mark_array.extend_from_slice(&mark_anchor);
let mut base_array = Vec::new();
base_array.extend_from_slice(&1u16.to_be_bytes());
base_array.extend_from_slice(&4u16.to_be_bytes());
base_array.extend_from_slice(&base_anchor);
let mut mark_cov = Vec::new();
mark_cov.extend_from_slice(&1u16.to_be_bytes());
mark_cov.extend_from_slice(&1u16.to_be_bytes());
mark_cov.extend_from_slice(&200u16.to_be_bytes());
let mut base_cov = Vec::new();
base_cov.extend_from_slice(&1u16.to_be_bytes());
base_cov.extend_from_slice(&1u16.to_be_bytes());
base_cov.extend_from_slice(&10u16.to_be_bytes());
let header = 12usize;
let mark_cov_off = header;
let base_cov_off = mark_cov_off + mark_cov.len();
let mark_array_off = base_cov_off + base_cov.len();
let base_array_off = mark_array_off + mark_array.len();
let mut mbp = Vec::new();
mbp.extend_from_slice(&1u16.to_be_bytes()); mbp.extend_from_slice(&(mark_cov_off as u16).to_be_bytes());
mbp.extend_from_slice(&(base_cov_off as u16).to_be_bytes());
mbp.extend_from_slice(&1u16.to_be_bytes()); mbp.extend_from_slice(&(mark_array_off as u16).to_be_bytes());
mbp.extend_from_slice(&(base_array_off as u16).to_be_bytes());
mbp.extend_from_slice(&mark_cov);
mbp.extend_from_slice(&base_cov);
mbp.extend_from_slice(&mark_array);
mbp.extend_from_slice(&base_array);
let mut lookup = Vec::new();
lookup.extend_from_slice(&4u16.to_be_bytes());
lookup.extend_from_slice(&0u16.to_be_bytes());
lookup.extend_from_slice(&1u16.to_be_bytes());
lookup.extend_from_slice(&8u16.to_be_bytes());
lookup.extend_from_slice(&mbp);
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&4u16.to_be_bytes());
lookup_list.extend_from_slice(&lookup);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes()); gpos.extend_from_slice(&0u16.to_be_bytes()); gpos.extend_from_slice(&0u16.to_be_bytes()); gpos.extend_from_slice(&0u16.to_be_bytes()); gpos.extend_from_slice(&10u16.to_be_bytes()); gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn mark_to_base_round_trip() {
let bytes = build_simple_mark_base();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_base(10, 200), Some((50, 800)));
assert_eq!(g.lookup_mark_to_base(11, 200), None);
assert_eq!(g.lookup_mark_to_base(10, 201), None);
}
#[test]
fn mark_to_base_missing_table_returns_none() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_base(50, 60), None);
}
fn build_simple_mark_mark() -> Vec<u8> {
let mut prev_anchor = Vec::new();
prev_anchor.extend_from_slice(&1u16.to_be_bytes());
prev_anchor.extend_from_slice(&60i16.to_be_bytes());
prev_anchor.extend_from_slice(&1200i16.to_be_bytes());
let mut new_anchor = Vec::new();
new_anchor.extend_from_slice(&1u16.to_be_bytes());
new_anchor.extend_from_slice(&30i16.to_be_bytes());
new_anchor.extend_from_slice(&0i16.to_be_bytes());
let mut new_mark_array = Vec::new();
new_mark_array.extend_from_slice(&1u16.to_be_bytes());
new_mark_array.extend_from_slice(&0u16.to_be_bytes()); new_mark_array.extend_from_slice(&6u16.to_be_bytes()); new_mark_array.extend_from_slice(&new_anchor);
let mut prev_array = Vec::new();
prev_array.extend_from_slice(&1u16.to_be_bytes());
prev_array.extend_from_slice(&4u16.to_be_bytes()); prev_array.extend_from_slice(&prev_anchor);
let mut new_cov = Vec::new();
new_cov.extend_from_slice(&1u16.to_be_bytes());
new_cov.extend_from_slice(&1u16.to_be_bytes());
new_cov.extend_from_slice(&40u16.to_be_bytes());
let mut prev_cov = Vec::new();
prev_cov.extend_from_slice(&1u16.to_be_bytes());
prev_cov.extend_from_slice(&1u16.to_be_bytes());
prev_cov.extend_from_slice(&30u16.to_be_bytes());
let header = 12usize;
let new_cov_off = header;
let prev_cov_off = new_cov_off + new_cov.len();
let new_mark_array_off = prev_cov_off + prev_cov.len();
let prev_array_off = new_mark_array_off + new_mark_array.len();
let mut mmp = Vec::new();
mmp.extend_from_slice(&1u16.to_be_bytes()); mmp.extend_from_slice(&(new_cov_off as u16).to_be_bytes()); mmp.extend_from_slice(&(prev_cov_off as u16).to_be_bytes()); mmp.extend_from_slice(&1u16.to_be_bytes()); mmp.extend_from_slice(&(new_mark_array_off as u16).to_be_bytes());
mmp.extend_from_slice(&(prev_array_off as u16).to_be_bytes());
mmp.extend_from_slice(&new_cov);
mmp.extend_from_slice(&prev_cov);
mmp.extend_from_slice(&new_mark_array);
mmp.extend_from_slice(&prev_array);
let mut lookup = Vec::new();
lookup.extend_from_slice(&6u16.to_be_bytes());
lookup.extend_from_slice(&0u16.to_be_bytes());
lookup.extend_from_slice(&1u16.to_be_bytes());
lookup.extend_from_slice(&8u16.to_be_bytes());
lookup.extend_from_slice(&mmp);
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&4u16.to_be_bytes());
lookup_list.extend_from_slice(&lookup);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn mark_to_mark_round_trip() {
let bytes = build_simple_mark_mark();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_mark(30, 40), Some((30, 1200)));
assert_eq!(g.lookup_mark_to_mark(31, 40), None);
assert_eq!(g.lookup_mark_to_mark(30, 41), None);
}
#[test]
fn mark_to_mark_missing_table_returns_none() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_mark(50, 60), None);
let bytes = build_simple_mark_base();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_mark(10, 200), None);
}
fn wrap_lookup(lookup_type: u16, sub: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&lookup_type.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes());
out.extend_from_slice(&8u16.to_be_bytes());
out.extend_from_slice(sub);
out
}
fn wrap_gpos_single(lookup: &[u8]) -> Vec<u8> {
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&4u16.to_be_bytes());
lookup_list.extend_from_slice(lookup);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
fn build_single_pos_format1() -> Vec<u8> {
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&2u16.to_be_bytes());
cov.extend_from_slice(&7u16.to_be_bytes());
cov.extend_from_slice(&9u16.to_be_bytes());
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&8u16.to_be_bytes()); sub.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub.extend_from_slice(&(-150i16).to_be_bytes()); sub.extend_from_slice(&cov);
let lookup = wrap_lookup(LOOKUP_SINGLE_POS, &sub);
wrap_gpos_single(&lookup)
}
fn build_single_pos_format2() -> Vec<u8> {
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&2u16.to_be_bytes());
cov.extend_from_slice(&7u16.to_be_bytes());
cov.extend_from_slice(&9u16.to_be_bytes());
let vf = VF_X_PLACEMENT | VF_Y_PLACEMENT | VF_X_ADVANCE;
let cov_off = 20u16;
let mut sub = Vec::new();
sub.extend_from_slice(&2u16.to_be_bytes()); sub.extend_from_slice(&cov_off.to_be_bytes());
sub.extend_from_slice(&vf.to_be_bytes());
sub.extend_from_slice(&2u16.to_be_bytes()); sub.extend_from_slice(&10i16.to_be_bytes());
sub.extend_from_slice(&20i16.to_be_bytes());
sub.extend_from_slice(&30i16.to_be_bytes());
sub.extend_from_slice(&(-5i16).to_be_bytes());
sub.extend_from_slice(&(-15i16).to_be_bytes());
sub.extend_from_slice(&40i16.to_be_bytes());
sub.extend_from_slice(&cov);
let lookup = wrap_lookup(LOOKUP_SINGLE_POS, &sub);
wrap_gpos_single(&lookup)
}
#[test]
fn single_pos_format1_returns_shared_value_for_every_covered_glyph() {
let bytes = build_single_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let v7 = g.apply_lookup_type_1(0, 7).unwrap();
let v9 = g.apply_lookup_type_1(0, 9).unwrap();
assert_eq!(v7, v9);
assert_eq!(v7.x_advance, -150);
assert_eq!(g.apply_lookup_type_1(0, 8), None);
}
#[test]
fn single_pos_format2_returns_per_glyph_value() {
let bytes = build_single_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
let v7 = g.apply_lookup_type_1(0, 7).unwrap();
assert_eq!(v7.x_placement, 10);
assert_eq!(v7.y_placement, 20);
assert_eq!(v7.x_advance, 30);
let v9 = g.apply_lookup_type_1(0, 9).unwrap();
assert_eq!(v9.x_placement, -5);
assert_eq!(v9.y_placement, -15);
assert_eq!(v9.x_advance, 40);
assert_eq!(g.apply_lookup_type_1(0, 8), None);
}
#[test]
fn single_pos_returns_none_when_lookup_index_out_of_range() {
let bytes = build_single_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_1(99, 7), None);
}
#[test]
fn single_pos_returns_none_when_lookup_is_not_type_1() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_1(0, 50), None);
}
fn build_chain_context_pos_format1() -> Vec<u8> {
let mut cov0 = Vec::new();
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&10u16.to_be_bytes());
let mut sub0 = Vec::new();
sub0.extend_from_slice(&1u16.to_be_bytes()); sub0.extend_from_slice(&8u16.to_be_bytes()); sub0.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub0.extend_from_slice(&50i16.to_be_bytes());
sub0.extend_from_slice(&cov0);
let lookup0 = wrap_lookup(LOOKUP_SINGLE_POS, &sub0);
let mut cov_in = Vec::new();
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&10u16.to_be_bytes());
let mut rule = Vec::new();
rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&99u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes()); let rule_set_header_len = 4u16;
let mut rule_set = Vec::new();
rule_set.extend_from_slice(&1u16.to_be_bytes());
rule_set.extend_from_slice(&rule_set_header_len.to_be_bytes());
rule_set.extend_from_slice(&rule);
let header_len = 8u16;
let cov_off = header_len;
let set_off = cov_off + cov_in.len() as u16;
let mut sub1 = Vec::new();
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&cov_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&set_off.to_be_bytes());
sub1.extend_from_slice(&cov_in);
sub1.extend_from_slice(&rule_set);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_POS, &sub1);
let lookup_list_header_len = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lookup0.len() as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
lookup_list.extend_from_slice(&lookup0);
lookup_list.extend_from_slice(&lookup1);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn chain_context_pos_format1_dispatches_nested_single_pos() {
let bytes = build_chain_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_8(1, &[1, 10, 99], 1).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn chain_context_pos_format1_no_match_when_backtrack_or_lookahead_misses() {
let bytes = build_chain_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(1, &[2, 10, 99], 1), None);
assert_eq!(g.apply_lookup_type_8(1, &[10, 99], 0), None);
assert_eq!(g.apply_lookup_type_8(1, &[1, 10, 50], 1), None);
assert_eq!(g.apply_lookup_type_8(99, &[1, 10, 99], 1), None);
}
fn build_chain_context_pos_format3() -> Vec<u8> {
let mut cov_lookup0 = Vec::new();
cov_lookup0.extend_from_slice(&1u16.to_be_bytes());
cov_lookup0.extend_from_slice(&1u16.to_be_bytes());
cov_lookup0.extend_from_slice(&10u16.to_be_bytes());
let mut sub0 = Vec::new();
sub0.extend_from_slice(&1u16.to_be_bytes());
sub0.extend_from_slice(&8u16.to_be_bytes());
sub0.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub0.extend_from_slice(&50i16.to_be_bytes());
sub0.extend_from_slice(&cov_lookup0);
let lookup0 = wrap_lookup(LOOKUP_SINGLE_POS, &sub0);
let mut cov_bt = Vec::new();
cov_bt.extend_from_slice(&1u16.to_be_bytes());
cov_bt.extend_from_slice(&1u16.to_be_bytes());
cov_bt.extend_from_slice(&1u16.to_be_bytes());
let mut cov_in = Vec::new();
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&10u16.to_be_bytes());
let mut cov_la = Vec::new();
cov_la.extend_from_slice(&1u16.to_be_bytes());
cov_la.extend_from_slice(&1u16.to_be_bytes());
cov_la.extend_from_slice(&99u16.to_be_bytes());
let header_len: u16 = 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4;
let bt_off = header_len;
let in_off = bt_off + cov_bt.len() as u16;
let la_off = in_off + cov_in.len() as u16;
let mut sub1 = Vec::new();
sub1.extend_from_slice(&3u16.to_be_bytes()); sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&bt_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&in_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&la_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&0u16.to_be_bytes()); sub1.extend_from_slice(&0u16.to_be_bytes()); sub1.extend_from_slice(&cov_bt);
sub1.extend_from_slice(&cov_in);
sub1.extend_from_slice(&cov_la);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_POS, &sub1);
let lookup_list_header_len = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lookup0.len() as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
lookup_list.extend_from_slice(&lookup0);
lookup_list.extend_from_slice(&lookup1);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn chain_context_pos_format3_coverage_based_dispatch() {
let bytes = build_chain_context_pos_format3();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_8(1, &[1, 10, 99], 1).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn chain_context_pos_format3_no_match_when_window_short_or_uncovered() {
let bytes = build_chain_context_pos_format3();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(1, &[10, 99], 0), None);
assert_eq!(g.apply_lookup_type_8(1, &[1, 10], 1), None);
assert_eq!(g.apply_lookup_type_8(1, &[1, 10, 12], 1), None);
}
fn build_chain_context_pos_format2() -> Vec<u8> {
let mut in_cd = Vec::new();
in_cd.extend_from_slice(&2u16.to_be_bytes());
in_cd.extend_from_slice(&1u16.to_be_bytes()); in_cd.extend_from_slice(&10u16.to_be_bytes()); in_cd.extend_from_slice(&11u16.to_be_bytes()); in_cd.extend_from_slice(&1u16.to_be_bytes());
let mut bt_cd = Vec::new();
bt_cd.extend_from_slice(&2u16.to_be_bytes());
bt_cd.extend_from_slice(&1u16.to_be_bytes());
bt_cd.extend_from_slice(&1u16.to_be_bytes());
bt_cd.extend_from_slice(&2u16.to_be_bytes());
bt_cd.extend_from_slice(&1u16.to_be_bytes());
let mut la_cd = Vec::new();
la_cd.extend_from_slice(&2u16.to_be_bytes());
la_cd.extend_from_slice(&1u16.to_be_bytes());
la_cd.extend_from_slice(&99u16.to_be_bytes());
la_cd.extend_from_slice(&99u16.to_be_bytes());
la_cd.extend_from_slice(&1u16.to_be_bytes());
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&10u16.to_be_bytes());
let mut rule = Vec::new();
rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes());
let mut set = Vec::new();
set.extend_from_slice(&1u16.to_be_bytes()); set.extend_from_slice(&4u16.to_be_bytes()); set.extend_from_slice(&rule);
let set_count = 2u16;
let header_len = 12 + (set_count as usize) * 2;
let cov_off = header_len as u16;
let bt_cd_off = cov_off + cov.len() as u16;
let in_cd_off = bt_cd_off + bt_cd.len() as u16;
let la_cd_off = in_cd_off + in_cd.len() as u16;
let set_off = la_cd_off + la_cd.len() as u16;
let mut sub1 = Vec::new();
sub1.extend_from_slice(&2u16.to_be_bytes()); sub1.extend_from_slice(&cov_off.to_be_bytes());
sub1.extend_from_slice(&bt_cd_off.to_be_bytes());
sub1.extend_from_slice(&in_cd_off.to_be_bytes());
sub1.extend_from_slice(&la_cd_off.to_be_bytes());
sub1.extend_from_slice(&set_count.to_be_bytes());
sub1.extend_from_slice(&0u16.to_be_bytes());
sub1.extend_from_slice(&set_off.to_be_bytes());
sub1.extend_from_slice(&cov);
sub1.extend_from_slice(&bt_cd);
sub1.extend_from_slice(&in_cd);
sub1.extend_from_slice(&la_cd);
sub1.extend_from_slice(&set);
let mut cov0 = Vec::new();
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&10u16.to_be_bytes());
let mut sub0 = Vec::new();
sub0.extend_from_slice(&1u16.to_be_bytes());
sub0.extend_from_slice(&8u16.to_be_bytes());
sub0.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub0.extend_from_slice(&50i16.to_be_bytes());
sub0.extend_from_slice(&cov0);
let lookup0 = wrap_lookup(LOOKUP_SINGLE_POS, &sub0);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_POS, &sub1);
let lookup_list_header_len = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lookup0.len() as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
lookup_list.extend_from_slice(&lookup0);
lookup_list.extend_from_slice(&lookup1);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn chain_context_pos_format2_class_based_dispatch() {
let bytes = build_chain_context_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_8(1, &[1, 10, 99], 1).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn chain_context_pos_format2_no_match_when_class_differs() {
let bytes = build_chain_context_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(1, &[5, 10, 99], 1), None);
}
#[test]
fn lookup_list_reports_index_type_and_subtable_count() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, 2u16, 1u16)]);
let bytes = build_simple_mark_base();
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, 4u16, 1u16)]);
let bytes = build_chain_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, 1u16, 1u16), (1u16, 8u16, 1u16)]);
}
#[test]
fn value_record_size_packs_low_byte_only() {
assert_eq!(value_record_size(VF_X_PLACEMENT | VF_Y_PLACEMENT), 4);
let all = VF_X_PLACEMENT
| VF_Y_PLACEMENT
| VF_X_ADVANCE
| VF_Y_ADVANCE
| VF_X_PLA_DEVICE
| VF_Y_PLA_DEVICE
| VF_X_ADV_DEVICE
| VF_Y_ADV_DEVICE;
assert_eq!(value_record_size(all), 16);
assert_eq!(value_record_size(0), 0);
}
fn build_cursive_pos_format1() -> Vec<u8> {
fn anchor(x: i16, y: i16) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&1u16.to_be_bytes());
v.extend_from_slice(&x.to_be_bytes());
v.extend_from_slice(&y.to_be_bytes());
v
}
let a5_entry = anchor(50, 100);
let a6_entry = anchor(60, 100);
let a6_exit = anchor(120, 200);
let a7_exit = anchor(140, 200);
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes()); cov.extend_from_slice(&3u16.to_be_bytes()); cov.extend_from_slice(&5u16.to_be_bytes());
cov.extend_from_slice(&6u16.to_be_bytes());
cov.extend_from_slice(&7u16.to_be_bytes());
let header_len = 6 + 3 * 4;
let cov_off = (header_len + 4 * 6) as u16; let a5_entry_off = header_len as u16;
let a6_entry_off = a5_entry_off + 6;
let a6_exit_off = a6_entry_off + 6;
let a7_exit_off = a6_exit_off + 6;
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&cov_off.to_be_bytes());
sub.extend_from_slice(&3u16.to_be_bytes()); sub.extend_from_slice(&a5_entry_off.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&a6_entry_off.to_be_bytes());
sub.extend_from_slice(&a6_exit_off.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&a7_exit_off.to_be_bytes());
sub.extend_from_slice(&a5_entry);
sub.extend_from_slice(&a6_entry);
sub.extend_from_slice(&a6_exit);
sub.extend_from_slice(&a7_exit);
sub.extend_from_slice(&cov);
let lookup = wrap_lookup(LOOKUP_CURSIVE_POS, &sub);
wrap_gpos_single(&lookup)
}
#[test]
fn cursive_pos_format1_returns_entry_and_exit_anchors() {
let bytes = build_cursive_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let r5 = g.apply_lookup_type_3(0, 5).unwrap();
assert_eq!(r5.entry, Some((50, 100)));
assert_eq!(r5.exit, None);
let r6 = g.apply_lookup_type_3(0, 6).unwrap();
assert_eq!(r6.entry, Some((60, 100)));
assert_eq!(r6.exit, Some((120, 200)));
let r7 = g.apply_lookup_type_3(0, 7).unwrap();
assert_eq!(r7.entry, None);
assert_eq!(r7.exit, Some((140, 200)));
}
#[test]
fn cursive_pos_returns_none_off_coverage() {
let bytes = build_cursive_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_3(0, 4), None);
assert_eq!(g.apply_lookup_type_3(0, 8), None);
}
#[test]
fn cursive_pos_returns_none_when_lookup_is_not_type_3() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_3(0, 5), None);
}
#[test]
fn lookup_cursive_attachment_walks_lookup_list() {
let bytes = build_cursive_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let r6 = g.lookup_cursive_attachment(6).unwrap();
assert_eq!(r6.entry, Some((60, 100)));
assert_eq!(r6.exit, Some((120, 200)));
assert_eq!(g.lookup_cursive_attachment(99), None);
}
fn build_mark_ligature_pos_format1() -> Vec<u8> {
fn anchor(x: i16, y: i16) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&1u16.to_be_bytes());
v.extend_from_slice(&x.to_be_bytes());
v.extend_from_slice(&y.to_be_bytes());
v
}
let mark_anchor = anchor(10, 0);
let mut mark_array = Vec::new();
mark_array.extend_from_slice(&1u16.to_be_bytes()); mark_array.extend_from_slice(&0u16.to_be_bytes()); mark_array.extend_from_slice(&6u16.to_be_bytes()); mark_array.extend_from_slice(&mark_anchor);
let comp0_anchor = anchor(300, 800);
let comp1_anchor = anchor(500, 850);
let mut lig_attach = Vec::new();
lig_attach.extend_from_slice(&2u16.to_be_bytes()); lig_attach.extend_from_slice(&6u16.to_be_bytes()); lig_attach.extend_from_slice(&12u16.to_be_bytes()); lig_attach.extend_from_slice(&comp0_anchor);
lig_attach.extend_from_slice(&comp1_anchor);
let mut lig_array = Vec::new();
lig_array.extend_from_slice(&1u16.to_be_bytes()); lig_array.extend_from_slice(&4u16.to_be_bytes()); lig_array.extend_from_slice(&lig_attach);
let mut mark_cov = Vec::new();
mark_cov.extend_from_slice(&1u16.to_be_bytes());
mark_cov.extend_from_slice(&1u16.to_be_bytes());
mark_cov.extend_from_slice(&200u16.to_be_bytes());
let mut lig_cov = Vec::new();
lig_cov.extend_from_slice(&1u16.to_be_bytes());
lig_cov.extend_from_slice(&1u16.to_be_bytes());
lig_cov.extend_from_slice(&100u16.to_be_bytes());
let header = 12usize;
let mark_cov_off = header;
let lig_cov_off = mark_cov_off + mark_cov.len();
let mark_array_off = lig_cov_off + lig_cov.len();
let lig_array_off = mark_array_off + mark_array.len();
let mut mlp = Vec::new();
mlp.extend_from_slice(&1u16.to_be_bytes()); mlp.extend_from_slice(&(mark_cov_off as u16).to_be_bytes());
mlp.extend_from_slice(&(lig_cov_off as u16).to_be_bytes());
mlp.extend_from_slice(&1u16.to_be_bytes()); mlp.extend_from_slice(&(mark_array_off as u16).to_be_bytes());
mlp.extend_from_slice(&(lig_array_off as u16).to_be_bytes());
mlp.extend_from_slice(&mark_cov);
mlp.extend_from_slice(&lig_cov);
mlp.extend_from_slice(&mark_array);
mlp.extend_from_slice(&lig_array);
let lookup = wrap_lookup(LOOKUP_MARK_LIGATURE_POS, &mlp);
wrap_gpos_single(&lookup)
}
#[test]
fn mark_to_ligature_attaches_to_each_component() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_5(0, 100, 0, 200), Some((290, 800)));
assert_eq!(g.apply_lookup_type_5(0, 100, 1, 200), Some((490, 850)));
}
#[test]
fn mark_to_ligature_returns_none_for_out_of_range_component() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_5(0, 100, 2, 200), None);
}
#[test]
fn mark_to_ligature_returns_none_for_uncovered_glyphs() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_5(0, 101, 0, 200), None);
assert_eq!(g.apply_lookup_type_5(0, 100, 0, 201), None);
}
#[test]
fn lookup_mark_to_ligature_walks_lookup_list() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_ligature(100, 1, 200), Some((490, 850)));
assert_eq!(g.lookup_mark_to_ligature(99, 0, 200), None);
}
fn build_extension_wrapped_lookup(inner_type: u16, inner_sub: &[u8]) -> Vec<u8> {
let mut ext_sub = Vec::new();
ext_sub.extend_from_slice(&1u16.to_be_bytes()); ext_sub.extend_from_slice(&inner_type.to_be_bytes()); ext_sub.extend_from_slice(&8u32.to_be_bytes()); ext_sub.extend_from_slice(inner_sub);
let lookup = wrap_lookup(LOOKUP_EXTENSION_POS, &ext_sub);
wrap_gpos_single(&lookup)
}
#[test]
fn extension_wrapper_unwraps_for_cursive_pos_lookup() {
let mut cursive_sub = Vec::new();
cursive_sub.extend_from_slice(&1u16.to_be_bytes()); cursive_sub.extend_from_slice(&22u16.to_be_bytes()); cursive_sub.extend_from_slice(&1u16.to_be_bytes()); cursive_sub.extend_from_slice(&10u16.to_be_bytes()); cursive_sub.extend_from_slice(&16u16.to_be_bytes()); cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&70i16.to_be_bytes());
cursive_sub.extend_from_slice(&110i16.to_be_bytes());
cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&130i16.to_be_bytes());
cursive_sub.extend_from_slice(&210i16.to_be_bytes());
cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&6u16.to_be_bytes());
let bytes = build_extension_wrapped_lookup(LOOKUP_CURSIVE_POS, &cursive_sub);
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, LOOKUP_CURSIVE_POS, 1u16)]);
let r = g.apply_lookup_type_3(0, 6).unwrap();
assert_eq!(r.entry, Some((70, 110)));
assert_eq!(r.exit, Some((130, 210)));
}
}