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_PAIR_POS: u16 = 2;
const LOOKUP_MARK_BASE_POS: u16 = 4;
const LOOKUP_MARK_MARK_POS: u16 = 6;
const LOOKUP_EXTENSION_POS: u16 = 9;
const VF_X_PLACEMENT: u16 = 0x0001;
const VF_Y_PLACEMENT: u16 = 0x0002;
const VF_X_ADVANCE: u16 = 0x0004;
#[allow(dead_code)]
const VF_Y_ADVANCE: u16 = 0x0008;
#[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_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 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
}
#[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);
}
}