use crate::parser::{read_u16, read_u32};
use crate::tables::gdef::{coverage_lookup, lookup_table_slice};
use crate::Error;
const LOOKUP_LIGATURE_SUBST: u16 = 4;
const LOOKUP_EXTENSION_SUBST: u16 = 7;
#[derive(Debug, Clone)]
pub struct GsubTable<'a> {
bytes: &'a [u8],
lookup_list_off: u32,
}
impl<'a> GsubTable<'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("GSUB: 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_ligature(&self, glyphs: &[u16]) -> Option<(u16, usize)> {
if glyphs.is_empty() {
return None;
}
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 = match lookup_table_slice(self.bytes, self.lookup_list_off, i) {
Some(s) => s,
None => continue,
};
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 = 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_SUBST {
if sub.len() < 8 {
continue;
}
let ext_type = read_u16(sub, 2).ok().unwrap_or(0);
let ext_off = read_u32(sub, 4).ok().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_LIGATURE_SUBST {
continue;
}
if let Some(hit) = ligature_subst_lookup(effective_sub, glyphs) {
return Some(hit);
}
}
}
None
}
}
fn ligature_subst_lookup(sub: &[u8], glyphs: &[u16]) -> Option<(u16, usize)> {
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 coverage = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(coverage, glyphs[0])? as usize;
let lig_set_count = read_u16(sub, 4).ok()? as usize;
if cov_idx >= lig_set_count {
return None;
}
let lig_set_off = read_u16(sub, 6 + cov_idx * 2).ok()? as usize;
let lig_set = sub.get(lig_set_off..)?;
if lig_set.len() < 2 {
return None;
}
let lig_count = read_u16(lig_set, 0).ok()? as usize;
for i in 0..lig_count {
let lig_off = read_u16(lig_set, 2 + i * 2).ok()? as usize;
let lig = lig_set.get(lig_off..)?;
if lig.len() < 4 {
continue;
}
let lig_glyph = read_u16(lig, 0).ok()?;
let comp_count = read_u16(lig, 2).ok()? as usize;
if comp_count < 1 {
continue;
}
if comp_count > glyphs.len() {
continue;
}
let remaining = comp_count - 1;
if lig.len() < 4 + remaining * 2 {
continue;
}
let mut ok = true;
for j in 0..remaining {
let want = read_u16(lig, 4 + j * 2).ok()?;
if glyphs[1 + j] != want {
ok = false;
break;
}
}
if ok {
return Some((lig_glyph, comp_count));
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn build_simple_gsub() -> (Vec<u8>, u16, u16, u16, u16) {
let mut lig = Vec::new();
lig.extend_from_slice(&999u16.to_be_bytes());
lig.extend_from_slice(&3u16.to_be_bytes());
lig.extend_from_slice(&200u16.to_be_bytes());
lig.extend_from_slice(&300u16.to_be_bytes());
let mut lig_set = Vec::new();
lig_set.extend_from_slice(&1u16.to_be_bytes());
lig_set.extend_from_slice(&4u16.to_be_bytes()); lig_set.extend_from_slice(&lig);
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(&100u16.to_be_bytes());
let lig_subst_header_len = 8;
let cov_off = lig_subst_header_len;
let lig_set_off = cov_off + cov.len();
let mut lig_subst = Vec::new();
lig_subst.extend_from_slice(&1u16.to_be_bytes());
lig_subst.extend_from_slice(&(cov_off as u16).to_be_bytes());
lig_subst.extend_from_slice(&1u16.to_be_bytes());
lig_subst.extend_from_slice(&(lig_set_off as u16).to_be_bytes());
lig_subst.extend_from_slice(&cov);
lig_subst.extend_from_slice(&lig_set);
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(&lig_subst);
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 gsub = Vec::new();
gsub.extend_from_slice(&1u16.to_be_bytes()); gsub.extend_from_slice(&0u16.to_be_bytes()); gsub.extend_from_slice(&0u16.to_be_bytes()); gsub.extend_from_slice(&0u16.to_be_bytes()); gsub.extend_from_slice(&10u16.to_be_bytes()); gsub.extend_from_slice(&lookup_list);
(gsub, 100, 200, 300, 999)
}
#[test]
fn round_trip_3_glyph_ligature() {
let (bytes, a, b, c, lig) = build_simple_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_ligature(&[a, b, c]), Some((lig, 3)));
assert_eq!(g.lookup_ligature(&[a, b, 999]), None);
assert_eq!(g.lookup_ligature(&[42, b, c]), None);
assert_eq!(g.lookup_ligature(&[a]), None);
}
}