use crate::parser::{read_u16, read_u32};
use crate::tables::gdef::{class_def_lookup, coverage_lookup, lookup_table_slice};
use crate::Error;
const LOOKUP_SINGLE_SUBST: u16 = 1;
const LOOKUP_MULTIPLE_SUBST: u16 = 2;
const LOOKUP_ALTERNATE_SUBST: u16 = 3;
const LOOKUP_LIGATURE_SUBST: u16 = 4;
const LOOKUP_CONTEXT_SUBST: u16 = 5;
const LOOKUP_CHAIN_CONTEXT_SUBST: u16 = 6;
const LOOKUP_EXTENSION_SUBST: u16 = 7;
const LOOKUP_REVERSE_CHAIN_CONTEXT_SUBST: u16 = 8;
const MAX_NESTED_LOOKUP_DEPTH: u8 = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GsubFeature {
pub tag: [u8; 4],
pub lookup_indices: Vec<u16>,
}
#[derive(Debug, Clone)]
pub struct GsubTable<'a> {
bytes: &'a [u8],
script_list_off: u32,
feature_list_off: u32,
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 script_list_off = read_u16(bytes, 4)? as u32;
let feature_list_off = read_u16(bytes, 6)? as u32;
let lookup_list_off = read_u16(bytes, 8)? as u32;
for off in [script_list_off, feature_list_off, lookup_list_off] {
if off != 0 && off as usize >= bytes.len() {
return Err(Error::BadOffset);
}
}
Ok(Self {
bytes,
script_list_off,
feature_list_off,
lookup_list_off,
})
}
pub fn lookup_list(&self) -> impl Iterator<Item = (u16, u16, u16)> + '_ {
let lookup_count = if self.lookup_list_off == 0 {
0
} else {
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_SUBST && sub_count > 0 {
if let Some(t) = peek_extension_type(lookup) {
kind = t;
}
}
Some((i, kind, sub_count))
})
}
pub fn features_for_script(
&self,
script_tag: [u8; 4],
lang_tag: Option<[u8; 4]>,
) -> Vec<GsubFeature> {
let mut out = Vec::new();
if self.script_list_off == 0 || self.feature_list_off == 0 {
return out;
}
let script_list = match self.bytes.get(self.script_list_off as usize..) {
Some(s) => s,
None => return out,
};
let feature_list = match self.bytes.get(self.feature_list_off as usize..) {
Some(s) => s,
None => return out,
};
let script_count = match read_u16(script_list, 0) {
Ok(v) => v as usize,
Err(_) => return out,
};
if script_list.len() < 2 + script_count * 6 {
return out;
}
let mut script_off: Option<usize> = None;
for i in 0..script_count {
let r = 2 + i * 6;
let tag = [
script_list[r],
script_list[r + 1],
script_list[r + 2],
script_list[r + 3],
];
if tag == script_tag {
let o = match read_u16(script_list, r + 4) {
Ok(v) => v as usize,
Err(_) => return out,
};
script_off = Some(o);
break;
}
}
let script_off = match script_off {
Some(o) => o,
None => return out,
};
let script = match script_list.get(script_off..) {
Some(s) => s,
None => return out,
};
if script.len() < 4 {
return out;
}
let default_off = match read_u16(script, 0) {
Ok(v) => v as usize,
Err(_) => return out,
};
let lang_count = match read_u16(script, 2) {
Ok(v) => v as usize,
Err(_) => return out,
};
let mut chosen_off: Option<usize> = None;
if let Some(want) = lang_tag {
for i in 0..lang_count {
let r = 4 + i * 6;
if script.len() < r + 6 {
break;
}
let tag = [script[r], script[r + 1], script[r + 2], script[r + 3]];
if tag == want {
chosen_off = match read_u16(script, r + 4) {
Ok(v) => Some(v as usize),
Err(_) => None,
};
break;
}
}
}
let chosen_off = chosen_off.or(if default_off == 0 {
None
} else {
Some(default_off)
});
let chosen_off = match chosen_off {
Some(o) if o != 0 => o,
_ => return out,
};
let langsys = match script.get(chosen_off..) {
Some(s) => s,
None => return out,
};
if langsys.len() < 6 {
return out;
}
let required = match read_u16(langsys, 2) {
Ok(v) => v,
Err(_) => return out,
};
let feat_count = match read_u16(langsys, 4) {
Ok(v) => v as usize,
Err(_) => return out,
};
if langsys.len() < 6 + feat_count * 2 {
return out;
}
let total_features = match read_u16(feature_list, 0) {
Ok(v) => v as usize,
Err(_) => return out,
};
let push_feature = |fi: u16, into: &mut Vec<GsubFeature>| {
if (fi as usize) >= total_features {
return;
}
let r = 2 + fi as usize * 6;
if feature_list.len() < r + 6 {
return;
}
let tag = [
feature_list[r],
feature_list[r + 1],
feature_list[r + 2],
feature_list[r + 3],
];
let foff = match read_u16(feature_list, r + 4) {
Ok(v) => v as usize,
Err(_) => return,
};
let feature = match feature_list.get(foff..) {
Some(s) => s,
None => return,
};
if feature.len() < 4 {
return;
}
let count = match read_u16(feature, 2) {
Ok(v) => v as usize,
Err(_) => return,
};
if feature.len() < 4 + count * 2 {
return;
}
let mut idxs = Vec::with_capacity(count);
for i in 0..count {
if let Ok(v) = read_u16(feature, 4 + i * 2) {
idxs.push(v);
}
}
into.push(GsubFeature {
tag,
lookup_indices: idxs,
});
};
if required != 0xFFFF {
push_feature(required, &mut out);
}
for i in 0..feat_count {
let fi = match read_u16(langsys, 6 + i * 2) {
Ok(v) => v,
Err(_) => continue,
};
push_feature(fi, &mut out);
}
out
}
pub fn apply_lookup_type_1(&self, lookup_index: u16, gid: u16) -> Option<u16> {
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) = if kind == LOOKUP_EXTENSION_SUBST {
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 = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_SINGLE_SUBST {
continue;
}
if let Some(hit) = single_subst_lookup(effective_sub, gid) {
return Some(hit);
}
}
None
}
pub fn apply_lookup_type_4(&self, lookup_index: u16, glyphs: &[u16]) -> Option<(u16, usize)> {
if glyphs.is_empty() || 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) = if kind == LOOKUP_EXTENSION_SUBST {
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 = 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
}
pub fn apply_lookup_type_6(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
) -> Option<Vec<u16>> {
self.apply_chain_context_at(lookup_index, gids, pos, 0)
}
pub fn apply_lookup_type_2(&self, lookup_index: u16, gid: u16) -> Option<Vec<u16>> {
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) = if kind == LOOKUP_EXTENSION_SUBST {
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 = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_MULTIPLE_SUBST {
continue;
}
if let Some(hit) = multiple_subst_lookup(effective_sub, gid) {
return Some(hit);
}
}
None
}
pub fn apply_lookup_type_3(
&self,
lookup_index: u16,
gid: u16,
alternate_index: u16,
) -> Option<u16> {
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) = if kind == LOOKUP_EXTENSION_SUBST {
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 = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_ALTERNATE_SUBST {
continue;
}
if let Some(hit) = alternate_subst_lookup(effective_sub, gid, alternate_index) {
return Some(hit);
}
}
None
}
pub fn apply_lookup_type_5(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
) -> Option<Vec<u16>> {
self.apply_context_at(lookup_index, gids, pos, 0)
}
pub fn apply_lookup_type_8(&self, lookup_index: u16, gids: &[u16], pos: usize) -> Option<u16> {
if self.lookup_list_off == 0 || pos >= gids.len() {
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) = if kind == LOOKUP_EXTENSION_SUBST {
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 = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_REVERSE_CHAIN_CONTEXT_SUBST {
continue;
}
if let Some(hit) = reverse_chain_context_lookup(effective_sub, gids, pos) {
return Some(hit);
}
}
None
}
fn apply_context_at(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
depth: u8,
) -> Option<Vec<u16>> {
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) = if kind == LOOKUP_EXTENSION_SUBST {
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 = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_CONTEXT_SUBST {
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 => context_format1_match(effective_sub, gids, pos),
2 => context_format2_match(effective_sub, gids, pos),
3 => context_format3_match(effective_sub, gids, pos),
_ => None,
};
if let Some(m) = matched {
return self.apply_subst_records(gids, pos, &m, depth);
}
}
None
}
fn apply_chain_context_at(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
depth: u8,
) -> Option<Vec<u16>> {
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) = if kind == LOOKUP_EXTENSION_SUBST {
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 = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != LOOKUP_CHAIN_CONTEXT_SUBST {
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_format1_match(effective_sub, gids, pos),
2 => chain_context_format2_match(effective_sub, gids, pos),
3 => chain_context_format3_match(effective_sub, gids, pos),
_ => None,
};
if let Some(m) = matched {
return self.apply_subst_records(gids, pos, &m, depth);
}
}
None
}
fn apply_subst_records(
&self,
gids: &[u16],
pos: usize,
m: &ChainMatch,
depth: u8,
) -> Option<Vec<u16>> {
let mut out: Vec<u16> = gids.to_vec();
let mut logical_to_phys: Vec<Option<usize>> =
(0..m.input_len).map(|i| Some(pos + i)).collect();
for rec in &m.records {
let seq_idx = rec.sequence_index as usize;
if seq_idx >= logical_to_phys.len() {
continue;
}
let phys = match logical_to_phys[seq_idx] {
Some(p) => p,
None => 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_SUBST {
if let Some(t) = peek_extension_type(lookup) {
nested_kind = t;
}
}
match nested_kind {
LOOKUP_SINGLE_SUBST => {
if let Some(replacement) = self.apply_lookup_type_1(rec.lookup_index, out[phys])
{
out[phys] = replacement;
}
}
LOOKUP_LIGATURE_SUBST => {
let tail = &out[phys..];
if let Some((lig_glyph, consumed)) =
self.apply_lookup_type_4(rec.lookup_index, tail)
{
if consumed >= 1 && phys + consumed <= out.len() {
out.splice(phys..phys + consumed, std::iter::once(lig_glyph));
let removed = consumed - 1;
for slot in logical_to_phys.iter_mut().skip(seq_idx + 1) {
if let Some(p) = *slot {
if p < phys + consumed {
*slot = None;
} else {
*slot = Some(p - removed);
}
}
}
}
}
}
LOOKUP_CHAIN_CONTEXT_SUBST => {
if let Some(rewritten) =
self.apply_chain_context_at(rec.lookup_index, &out, phys, depth + 1)
{
let old_len = out.len();
out = rewritten;
let delta = out.len() as isize - old_len as isize;
if delta != 0 {
for slot in logical_to_phys.iter_mut().skip(seq_idx + 1) {
if let Some(p) = *slot {
let np = p as isize + delta;
if np < phys as isize {
*slot = None;
} else {
*slot = Some(np as usize);
}
}
}
}
}
}
LOOKUP_CONTEXT_SUBST => {
if let Some(rewritten) =
self.apply_context_at(rec.lookup_index, &out, phys, depth + 1)
{
let old_len = out.len();
out = rewritten;
let delta = out.len() as isize - old_len as isize;
if delta != 0 {
for slot in logical_to_phys.iter_mut().skip(seq_idx + 1) {
if let Some(p) = *slot {
let np = p as isize + delta;
if np < phys as isize {
*slot = None;
} else {
*slot = Some(np as usize);
}
}
}
}
}
}
LOOKUP_MULTIPLE_SUBST => {
if let Some(seq) = self.apply_lookup_type_2(rec.lookup_index, out[phys]) {
let inserted = seq.len();
out.splice(phys..phys + 1, seq);
let delta = inserted as isize - 1;
if delta != 0 {
for slot in logical_to_phys.iter_mut().skip(seq_idx + 1) {
if let Some(p) = *slot {
let np = p as isize + delta;
if np < phys as isize {
*slot = None;
} else {
*slot = Some(np as usize);
}
}
}
}
}
}
LOOKUP_ALTERNATE_SUBST => {
if let Some(replacement) =
self.apply_lookup_type_3(rec.lookup_index, out[phys], 0)
{
out[phys] = replacement;
}
}
_ => {
}
}
}
Some(out)
}
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 single_subst_lookup(sub: &[u8], gid: u16) -> Option<u16> {
if sub.len() < 6 {
return None;
}
let format = read_u16(sub, 0).ok()?;
let coverage_off = read_u16(sub, 2).ok()? as usize;
let coverage = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(coverage, gid)?;
match format {
1 => {
let delta = read_u16(sub, 4).ok()? as i16 as i32;
let result = (gid as i32 + delta) & 0xFFFF;
Some(result as u16)
}
2 => {
let count = read_u16(sub, 4).ok()? as usize;
if cov_idx as usize >= count {
return None;
}
let off = 6 + cov_idx as usize * 2;
if sub.len() < off + 2 {
return None;
}
read_u16(sub, off).ok()
}
_ => 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
}
fn multiple_subst_lookup(sub: &[u8], gid: u16) -> Option<Vec<u16>> {
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, gid)? as usize;
let seq_count = read_u16(sub, 4).ok()? as usize;
if cov_idx >= seq_count {
return None;
}
let seq_off = read_u16(sub, 6 + cov_idx * 2).ok()? as usize;
let seq = sub.get(seq_off..)?;
if seq.len() < 2 {
return None;
}
let glyph_count = read_u16(seq, 0).ok()? as usize;
if seq.len() < 2 + glyph_count * 2 {
return None;
}
let mut out = Vec::with_capacity(glyph_count);
for i in 0..glyph_count {
out.push(read_u16(seq, 2 + i * 2).ok()?);
}
Some(out)
}
fn alternate_subst_lookup(sub: &[u8], gid: u16, alternate_index: u16) -> Option<u16> {
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, gid)? as usize;
let alt_count = read_u16(sub, 4).ok()? as usize;
if cov_idx >= alt_count {
return None;
}
let alt_off = read_u16(sub, 6 + cov_idx * 2).ok()? as usize;
let alt_set = sub.get(alt_off..)?;
if alt_set.len() < 2 {
return None;
}
let glyph_count = read_u16(alt_set, 0).ok()? as usize;
let idx = alternate_index as usize;
if idx >= glyph_count {
return None;
}
if alt_set.len() < 2 + (idx + 1) * 2 {
return None;
}
read_u16(alt_set, 2 + idx * 2).ok()
}
fn reverse_chain_context_lookup(sub: &[u8], gids: &[u16], pos: usize) -> Option<u16> {
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, gids[pos])? as usize;
let mut cur = 4usize;
if sub.len() < cur + 2 {
return None;
}
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 la_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
if sub.len() < cur + la_count * 2 {
return None;
}
if pos + 1 + 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 + 1 + i])?;
}
cur += la_count * 2;
if sub.len() < cur + 2 {
return None;
}
let glyph_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
if cov_idx >= glyph_count {
return None;
}
if sub.len() < cur + (cov_idx + 1) * 2 {
return None;
}
read_u16(sub, cur + cov_idx * 2).ok()
}
#[derive(Debug)]
struct ChainMatch {
input_len: usize,
records: Vec<SubstLookupRecord>,
}
#[derive(Debug, Clone, Copy)]
struct SubstLookupRecord {
sequence_index: u16,
lookup_index: u16,
}
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_subst_lookup_records(
bytes: &[u8],
offset: usize,
count: usize,
) -> Option<Vec<SubstLookupRecord>> {
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(SubstLookupRecord {
sequence_index: seq,
lookup_index: lk,
});
}
Some(out)
}
fn chain_context_format1_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
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_format1_rule_match(rule, gids, pos) {
return Some(m);
}
}
None
}
fn chain_context_format1_rule_match(rule: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
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 subst_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_subst_lookup_records(rule, cur, subst_count)?;
Some(ChainMatch {
input_len: in_count,
records,
})
}
fn chain_context_format2_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
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_format2_rule_match(rule, gids, pos, bt_cd, in_cd, la_cd, in_class0)
{
return Some(m);
}
}
None
}
fn chain_context_format2_rule_match(
rule: &[u8],
gids: &[u16],
pos: usize,
bt_cd: Option<&[u8]>,
in_cd: &[u8],
la_cd: Option<&[u8]>,
in_class0: u16,
) -> Option<ChainMatch> {
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 subst_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_subst_lookup_records(rule, cur, subst_count)?;
Some(ChainMatch {
input_len: in_count,
records,
})
}
fn chain_context_format3_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
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 subst_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
let records = read_subst_lookup_records(sub, cur, subst_count)?;
Some(ChainMatch {
input_len: in_count,
records,
})
}
fn context_format1_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
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) = context_format1_rule_match(rule, gids, pos) {
return Some(m);
}
}
None
}
fn context_format1_rule_match(rule: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
let mut cur = 0usize;
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 subst_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_subst_lookup_records(rule, cur, subst_count)?;
Some(ChainMatch {
input_len: in_count,
records,
})
}
fn context_format2_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
if sub.len() < 8 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let cd_off = read_u16(sub, 4).ok()? as usize;
let set_count = read_u16(sub, 6).ok()? as usize;
let coverage = sub.get(coverage_off..)?;
coverage_lookup(coverage, gids[pos])?;
let cd = sub.get(cd_off..)?;
let class0 = class_def_lookup(cd, gids[pos]).unwrap_or(0);
if class0 as usize >= set_count {
return None;
}
let set_off = read_u16(sub, 8 + 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;
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) = context_format2_rule_match(rule, gids, pos, cd) {
return Some(m);
}
}
None
}
fn context_format2_rule_match(
rule: &[u8],
gids: &[u16],
pos: usize,
cd: &[u8],
) -> Option<ChainMatch> {
let mut cur = 0usize;
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(cd, gids[pos + 1 + i]).unwrap_or(0);
if want != got {
return None;
}
}
cur += in_extra * 2;
if rule.len() < cur + 2 {
return None;
}
let subst_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_subst_lookup_records(rule, cur, subst_count)?;
Some(ChainMatch {
input_len: in_count,
records,
})
}
fn context_format3_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainMatch> {
if sub.len() < 6 {
return None;
}
let glyph_count = read_u16(sub, 2).ok()? as usize;
let subst_count = read_u16(sub, 4).ok()? as usize;
if glyph_count == 0 {
return None;
}
let mut cur = 6usize;
if sub.len() < cur + glyph_count * 2 {
return None;
}
if pos + glyph_count > gids.len() {
return None;
}
for i in 0..glyph_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 += glyph_count * 2;
let records = read_subst_lookup_records(sub, cur, subst_count)?;
Some(ChainMatch {
input_len: glyph_count,
records,
})
}
#[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);
}
fn build_feature_tagged_gsub() -> 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(&6u16.to_be_bytes()); sub0.extend_from_slice(&5i16.to_be_bytes()); sub0.extend_from_slice(&cov0);
let mut cov1 = Vec::new();
cov1.extend_from_slice(&1u16.to_be_bytes()); cov1.extend_from_slice(&3u16.to_be_bytes()); cov1.extend_from_slice(&20u16.to_be_bytes());
cov1.extend_from_slice(&21u16.to_be_bytes());
cov1.extend_from_slice(&22u16.to_be_bytes());
let mut sub1 = Vec::new();
sub1.extend_from_slice(&2u16.to_be_bytes()); let cov1_off_in_sub1: u16 = 6 + 6; sub1.extend_from_slice(&cov1_off_in_sub1.to_be_bytes());
sub1.extend_from_slice(&3u16.to_be_bytes()); sub1.extend_from_slice(&200u16.to_be_bytes());
sub1.extend_from_slice(&201u16.to_be_bytes());
sub1.extend_from_slice(&202u16.to_be_bytes());
sub1.extend_from_slice(&cov1);
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
}
let lookup0 = wrap_lookup(LOOKUP_SINGLE_SUBST, &sub0);
let lookup1 = wrap_lookup(LOOKUP_SINGLE_SUBST, &sub1);
let lookup2 = wrap_lookup(LOOKUP_SINGLE_SUBST, &sub0);
let lookup3 = wrap_lookup(LOOKUP_SINGLE_SUBST, &sub0);
let lookup_list_header_len = 2 + 4 * 2; let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&4u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
for lk in [&lookup0, &lookup1, &lookup2, &lookup3] {
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lk.len() as u16;
}
for lk in [&lookup0, &lookup1, &lookup2, &lookup3] {
lookup_list.extend_from_slice(lk);
}
fn build_feature(lookup_idx: u16) -> Vec<u8> {
let mut f = Vec::new();
f.extend_from_slice(&0u16.to_be_bytes()); f.extend_from_slice(&1u16.to_be_bytes()); f.extend_from_slice(&lookup_idx.to_be_bytes());
f
}
let feat_init = build_feature(0);
let feat_medi = build_feature(1);
let feat_fina = build_feature(2);
let feat_isol = build_feature(3);
let tags: [&[u8; 4]; 4] = [b"init", b"medi", b"fina", b"isol"];
let feature_list_header_len = 2 + 4 * 6; let mut feature_list = Vec::new();
feature_list.extend_from_slice(&4u16.to_be_bytes());
let mut running_f = feature_list_header_len as u16;
let payloads = [&feat_init, &feat_medi, &feat_fina, &feat_isol];
for (tag, fbytes) in tags.iter().zip(payloads.iter()) {
feature_list.extend_from_slice(*tag);
feature_list.extend_from_slice(&running_f.to_be_bytes());
running_f += fbytes.len() as u16;
}
for fbytes in payloads {
feature_list.extend_from_slice(fbytes);
}
let mut langsys = Vec::new();
langsys.extend_from_slice(&0u16.to_be_bytes());
langsys.extend_from_slice(&0xFFFFu16.to_be_bytes());
langsys.extend_from_slice(&4u16.to_be_bytes());
langsys.extend_from_slice(&0u16.to_be_bytes());
langsys.extend_from_slice(&1u16.to_be_bytes());
langsys.extend_from_slice(&2u16.to_be_bytes());
langsys.extend_from_slice(&3u16.to_be_bytes());
let mut script = Vec::new();
script.extend_from_slice(&4u16.to_be_bytes()); script.extend_from_slice(&0u16.to_be_bytes()); script.extend_from_slice(&langsys);
let mut script_list = Vec::new();
script_list.extend_from_slice(&1u16.to_be_bytes());
script_list.extend_from_slice(b"arab");
let script_off: u16 = 2 + 6; script_list.extend_from_slice(&script_off.to_be_bytes());
script_list.extend_from_slice(&script);
let header_len = 10u16;
let script_list_off = header_len;
let feature_list_off = script_list_off + script_list.len() as u16;
let lookup_list_off = feature_list_off + feature_list.len() as u16;
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(&script_list_off.to_be_bytes());
gsub.extend_from_slice(&feature_list_off.to_be_bytes());
gsub.extend_from_slice(&lookup_list_off.to_be_bytes());
gsub.extend_from_slice(&script_list);
gsub.extend_from_slice(&feature_list);
gsub.extend_from_slice(&lookup_list);
gsub
}
#[test]
fn gsub_header_parses_version_1_0() {
let bytes = build_feature_tagged_gsub();
let g = GsubTable::parse(&bytes).expect("v1.0 header parses");
assert!(g.script_list_off > 0);
assert!(g.feature_list_off > 0);
assert!(g.lookup_list_off > 0);
}
#[test]
fn script_list_finds_arab_script() {
let bytes = build_feature_tagged_gsub();
let g = GsubTable::parse(&bytes).unwrap();
let feats = g.features_for_script(*b"arab", None);
assert_eq!(feats.len(), 4, "arab script should expose 4 features");
let none = g.features_for_script(*b"latn", None);
assert!(none.is_empty(), "latn isn't in the test ScriptList");
}
#[test]
fn feature_list_returns_init_medi_fina_isol_for_arab() {
let bytes = build_feature_tagged_gsub();
let g = GsubTable::parse(&bytes).unwrap();
let feats = g.features_for_script(*b"arab", None);
let tags: Vec<[u8; 4]> = feats.iter().map(|f| f.tag).collect();
assert_eq!(
tags,
vec![*b"init", *b"medi", *b"fina", *b"isol"],
"expected the four positional-form tags in declaration order"
);
for (i, f) in feats.iter().enumerate() {
assert_eq!(f.lookup_indices, vec![i as u16]);
}
}
#[test]
fn lookup_type_1_format_1_delta_substitution_applies() {
let bytes = build_feature_tagged_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_1(0, 10), Some(15));
}
#[test]
fn lookup_type_1_format_2_array_substitution_applies() {
let bytes = build_feature_tagged_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_1(1, 20), Some(200));
assert_eq!(g.apply_lookup_type_1(1, 21), Some(201));
assert_eq!(g.apply_lookup_type_1(1, 22), Some(202));
}
#[test]
fn gsub_apply_returns_none_when_gid_not_in_coverage() {
let bytes = build_feature_tagged_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_1(0, 99), None);
assert_eq!(g.apply_lookup_type_1(1, 99), None);
assert_eq!(g.apply_lookup_type_1(123, 10), None);
}
#[test]
fn signed_delta_wraps_modulo_65536() {
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(&5u16.to_be_bytes());
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&6u16.to_be_bytes()); sub.extend_from_slice(&(-3i16).to_be_bytes()); sub.extend_from_slice(&cov);
assert_eq!(single_subst_lookup(&sub, 5), Some(2));
let mut cov2 = Vec::new();
cov2.extend_from_slice(&1u16.to_be_bytes());
cov2.extend_from_slice(&1u16.to_be_bytes());
cov2.extend_from_slice(&1u16.to_be_bytes());
let mut sub2 = Vec::new();
sub2.extend_from_slice(&1u16.to_be_bytes());
sub2.extend_from_slice(&6u16.to_be_bytes());
sub2.extend_from_slice(&(-3i16).to_be_bytes());
sub2.extend_from_slice(&cov2);
assert_eq!(single_subst_lookup(&sub2, 1), Some(65534));
}
#[test]
fn apply_lookup_type_4_consumes_correct_count() {
let (bytes, a, b, c, lig) = build_simple_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_4(0, &[a, b, c]), Some((lig, 3)));
assert_eq!(g.apply_lookup_type_4(99, &[a, b, c]), None);
assert_eq!(g.apply_lookup_type_4(0, &[a, b, 555]), None);
assert_eq!(g.apply_lookup_type_4(0, &[]), None);
}
#[test]
fn apply_lookup_type_4_skips_non_ligature_lookups() {
let bytes = build_feature_tagged_gsub();
let g = GsubTable::parse(&bytes).unwrap();
for li in 0..4 {
assert_eq!(g.apply_lookup_type_4(li, &[10, 20, 30]), None);
}
}
fn build_chain_context_format1_gsub() -> Vec<u8> {
let mut cov0 = Vec::new();
cov0.extend_from_slice(&1u16.to_be_bytes()); cov0.extend_from_slice(&2u16.to_be_bytes()); cov0.extend_from_slice(&5u16.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(&6u16.to_be_bytes()); sub0.extend_from_slice(&100i16.to_be_bytes()); sub0.extend_from_slice(&cov0);
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);
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
}
let lookup0 = wrap_lookup(LOOKUP_SINGLE_SUBST, &sub0);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_SUBST, &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 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
}
#[test]
fn gsub_lookup_type_6_format_1_chained_context_simple_sequence() {
let bytes = build_chain_context_format1_gsub();
let g = GsubTable::parse(&bytes).unwrap();
let out = g.apply_lookup_type_6(1, &[1, 10, 99], 1).unwrap();
assert_eq!(out, vec![1, 110, 99]);
}
#[test]
fn chain_context_format1_no_match_when_backtrack_differs() {
let bytes = build_chain_context_format1_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_6(1, &[2, 10, 99], 1), None);
assert_eq!(g.apply_lookup_type_6(1, &[10, 99], 0), None);
assert_eq!(g.apply_lookup_type_6(1, &[1, 10, 50], 1), None);
assert_eq!(g.apply_lookup_type_6(99, &[1, 10, 99], 1), None);
}
fn build_chain_context_format3_gsub() -> Vec<u8> {
let mut cov_lookup0 = Vec::new();
cov_lookup0.extend_from_slice(&1u16.to_be_bytes());
cov_lookup0.extend_from_slice(&2u16.to_be_bytes());
cov_lookup0.extend_from_slice(&5u16.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(&6u16.to_be_bytes());
sub0.extend_from_slice(&100i16.to_be_bytes());
sub0.extend_from_slice(&cov_lookup0);
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);
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
}
let lookup0 = wrap_lookup(LOOKUP_SINGLE_SUBST, &sub0);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_SUBST, &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 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
}
#[test]
fn gsub_lookup_type_6_format_3_coverage_based_chained_context() {
let bytes = build_chain_context_format3_gsub();
let g = GsubTable::parse(&bytes).unwrap();
let out = g.apply_lookup_type_6(1, &[1, 10, 99], 1).unwrap();
assert_eq!(out, vec![1, 110, 99]);
}
#[test]
fn chain_context_format3_no_match_when_window_short() {
let bytes = build_chain_context_format3_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_6(1, &[10, 99], 0), None);
assert_eq!(g.apply_lookup_type_6(1, &[1, 10], 1), None);
assert_eq!(g.apply_lookup_type_6(1, &[1, 10, 12], 1), None);
}
fn build_chain_context_format2_gsub() -> 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(&6u16.to_be_bytes());
sub0.extend_from_slice(&100i16.to_be_bytes());
sub0.extend_from_slice(&cov_lookup0);
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 bt_cd = Vec::new();
bt_cd.extend_from_slice(&1u16.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()); let mut in_cd = Vec::new();
in_cd.extend_from_slice(&1u16.to_be_bytes());
in_cd.extend_from_slice(&10u16.to_be_bytes());
in_cd.extend_from_slice(&1u16.to_be_bytes());
in_cd.extend_from_slice(&1u16.to_be_bytes());
let mut la_cd = Vec::new();
la_cd.extend_from_slice(&1u16.to_be_bytes());
la_cd.extend_from_slice(&99u16.to_be_bytes());
la_cd.extend_from_slice(&1u16.to_be_bytes());
la_cd.extend_from_slice(&3u16.to_be_bytes());
let mut rule = Vec::new();
rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&2u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&3u16.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: u16 = 2 + 2 + 2 + 2 + 2 + 2 + 4;
let cov_off = header_len;
let bt_cd_off = cov_off + cov_in.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 class1_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(&2u16.to_be_bytes()); sub1.extend_from_slice(&0u16.to_be_bytes()); sub1.extend_from_slice(&class1_set_off.to_be_bytes()); sub1.extend_from_slice(&cov_in);
sub1.extend_from_slice(&bt_cd);
sub1.extend_from_slice(&in_cd);
sub1.extend_from_slice(&la_cd);
sub1.extend_from_slice(&rule_set);
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
}
let lookup0 = wrap_lookup(LOOKUP_SINGLE_SUBST, &sub0);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_SUBST, &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 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
}
#[test]
fn chain_context_format2_class_based_substitutes() {
let bytes = build_chain_context_format2_gsub();
let g = GsubTable::parse(&bytes).unwrap();
let out = g.apply_lookup_type_6(1, &[1, 10, 99], 1).unwrap();
assert_eq!(out, vec![1, 110, 99]);
}
#[test]
fn chain_context_format2_no_match_when_class_differs() {
let bytes = build_chain_context_format2_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_6(1, &[2, 10, 99], 1), None);
}
fn wrap_lookup_helper(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 build_singleton_gsub(lookup_type: u16, sub: &[u8]) -> Vec<u8> {
let lookup = wrap_lookup_helper(lookup_type, sub);
let lookup_list_header_len = 2 + 2; let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&(lookup_list_header_len as u16).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
}
fn build_mult_subst_sub() -> Vec<u8> {
let mut seq = Vec::new();
seq.extend_from_slice(&3u16.to_be_bytes());
seq.extend_from_slice(&10u16.to_be_bytes());
seq.extend_from_slice(&11u16.to_be_bytes());
seq.extend_from_slice(&12u16.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(&7u16.to_be_bytes());
let header_len = 8u16;
let cov_off = header_len;
let seq_off = cov_off + cov.len() as u16;
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(&1u16.to_be_bytes()); sub.extend_from_slice(&seq_off.to_be_bytes());
sub.extend_from_slice(&cov);
sub.extend_from_slice(&seq);
sub
}
#[test]
fn lookup_type_2_expands_one_glyph_into_sequence() {
let bytes = build_singleton_gsub(LOOKUP_MULTIPLE_SUBST, &build_mult_subst_sub());
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_2(0, 7), Some(vec![10, 11, 12]));
}
#[test]
fn lookup_type_2_returns_none_off_coverage() {
let bytes = build_singleton_gsub(LOOKUP_MULTIPLE_SUBST, &build_mult_subst_sub());
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_2(0, 99), None);
assert_eq!(g.apply_lookup_type_2(99, 7), None);
let single_bytes = build_singleton_gsub(LOOKUP_SINGLE_SUBST, &build_mult_subst_sub());
let g2 = GsubTable::parse(&single_bytes).unwrap();
assert_eq!(g2.apply_lookup_type_2(0, 7), None);
}
#[test]
fn lookup_type_2_zero_glyph_count_means_deletion() {
let mut seq = Vec::new();
seq.extend_from_slice(&0u16.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(&7u16.to_be_bytes());
let header_len = 8u16;
let cov_off = header_len;
let seq_off = cov_off + cov.len() as u16;
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(&1u16.to_be_bytes());
sub.extend_from_slice(&seq_off.to_be_bytes());
sub.extend_from_slice(&cov);
sub.extend_from_slice(&seq);
let bytes = build_singleton_gsub(LOOKUP_MULTIPLE_SUBST, &sub);
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_2(0, 7), Some(Vec::new()));
}
fn build_alt_subst_sub() -> Vec<u8> {
let mut alt_set = Vec::new();
alt_set.extend_from_slice(&3u16.to_be_bytes()); alt_set.extend_from_slice(&50u16.to_be_bytes());
alt_set.extend_from_slice(&51u16.to_be_bytes());
alt_set.extend_from_slice(&52u16.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(&5u16.to_be_bytes());
let header_len = 8u16;
let cov_off = header_len;
let alt_off = cov_off + cov.len() as u16;
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(&1u16.to_be_bytes()); sub.extend_from_slice(&alt_off.to_be_bytes());
sub.extend_from_slice(&cov);
sub.extend_from_slice(&alt_set);
sub
}
#[test]
fn lookup_type_3_picks_default_alternate_zero() {
let bytes = build_singleton_gsub(LOOKUP_ALTERNATE_SUBST, &build_alt_subst_sub());
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_3(0, 5, 0), Some(50));
}
#[test]
fn lookup_type_3_picks_indexed_alternates() {
let bytes = build_singleton_gsub(LOOKUP_ALTERNATE_SUBST, &build_alt_subst_sub());
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_3(0, 5, 1), Some(51));
assert_eq!(g.apply_lookup_type_3(0, 5, 2), Some(52));
}
#[test]
fn lookup_type_3_out_of_range_alternate_returns_none() {
let bytes = build_singleton_gsub(LOOKUP_ALTERNATE_SUBST, &build_alt_subst_sub());
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_3(0, 5, 3), None);
assert_eq!(g.apply_lookup_type_3(0, 99, 0), None);
let other = build_singleton_gsub(LOOKUP_SINGLE_SUBST, &build_alt_subst_sub());
let g2 = GsubTable::parse(&other).unwrap();
assert_eq!(g2.apply_lookup_type_3(0, 5, 0), None);
}
fn build_singlesubst_for_10_plus_100() -> Vec<u8> {
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 sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&6u16.to_be_bytes()); sub.extend_from_slice(&100i16.to_be_bytes()); sub.extend_from_slice(&cov);
sub
}
fn build_context_format1_sub() -> Vec<u8> {
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(&0u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes());
let rule_set_header = 4u16; let mut rule_set = Vec::new();
rule_set.extend_from_slice(&1u16.to_be_bytes());
rule_set.extend_from_slice(&rule_set_header.to_be_bytes());
rule_set.extend_from_slice(&rule);
let header_len = 8u16;
let cov_off = header_len;
let set_off = cov_off + cov.len() as u16;
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(&1u16.to_be_bytes()); sub.extend_from_slice(&set_off.to_be_bytes());
sub.extend_from_slice(&cov);
sub.extend_from_slice(&rule_set);
sub
}
fn build_context_lookup_gsub(context_format_sub: Vec<u8>) -> Vec<u8> {
let lookup0 = wrap_lookup_helper(LOOKUP_SINGLE_SUBST, &build_singlesubst_for_10_plus_100());
let lookup1 = wrap_lookup_helper(LOOKUP_CONTEXT_SUBST, &context_format_sub);
let lookup_list_header = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header 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 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
}
#[test]
fn lookup_type_5_format_1_simple_glyph_context() {
let bytes = build_context_lookup_gsub(build_context_format1_sub());
let g = GsubTable::parse(&bytes).unwrap();
let out = g.apply_lookup_type_5(1, &[10], 0).unwrap();
assert_eq!(out, vec![110]);
assert_eq!(g.apply_lookup_type_5(1, &[11], 0), None);
assert_eq!(g.apply_lookup_type_5(99, &[10], 0), None);
}
fn build_context_format3_sub() -> Vec<u8> {
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 header_len: u16 = 2 + 2 + 2 + 2 + 4;
let cov_off = header_len;
let mut sub = Vec::new();
sub.extend_from_slice(&3u16.to_be_bytes()); sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&cov_off.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&cov_in);
sub
}
#[test]
fn lookup_type_5_format_3_coverage_based_context() {
let bytes = build_context_lookup_gsub(build_context_format3_sub());
let g = GsubTable::parse(&bytes).unwrap();
let out = g.apply_lookup_type_5(1, &[10], 0).unwrap();
assert_eq!(out, vec![110]);
assert_eq!(g.apply_lookup_type_5(1, &[11], 0), None);
}
fn build_context_format2_sub() -> Vec<u8> {
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 cd = Vec::new();
cd.extend_from_slice(&1u16.to_be_bytes());
cd.extend_from_slice(&10u16.to_be_bytes());
cd.extend_from_slice(&1u16.to_be_bytes());
cd.extend_from_slice(&1u16.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(&0u16.to_be_bytes());
rule.extend_from_slice(&0u16.to_be_bytes());
let rule_set_header = 4u16;
let mut rule_set = Vec::new();
rule_set.extend_from_slice(&1u16.to_be_bytes());
rule_set.extend_from_slice(&rule_set_header.to_be_bytes());
rule_set.extend_from_slice(&rule);
let header_len: u16 = 2 + 2 + 2 + 2 + 4;
let cov_off = header_len;
let cd_off = cov_off + cov_in.len() as u16;
let class1_set_off = cd_off + cd.len() as u16;
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(&cd_off.to_be_bytes());
sub.extend_from_slice(&2u16.to_be_bytes()); sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&class1_set_off.to_be_bytes());
sub.extend_from_slice(&cov_in);
sub.extend_from_slice(&cd);
sub.extend_from_slice(&rule_set);
sub
}
#[test]
fn lookup_type_5_format_2_class_based_context() {
let bytes = build_context_lookup_gsub(build_context_format2_sub());
let g = GsubTable::parse(&bytes).unwrap();
let out = g.apply_lookup_type_5(1, &[10], 0).unwrap();
assert_eq!(out, vec![110]);
assert_eq!(g.apply_lookup_type_5(1, &[11], 0), None);
}
fn build_reverse_chain_sub() -> Vec<u8> {
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_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_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;
let cov_off = header_len;
let bt_off = cov_off + cov_in.len() as u16;
let la_off = bt_off + cov_bt.len() as u16;
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(&1u16.to_be_bytes()); sub.extend_from_slice(&bt_off.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&la_off.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&200u16.to_be_bytes()); sub.extend_from_slice(&cov_in);
sub.extend_from_slice(&cov_bt);
sub.extend_from_slice(&cov_la);
sub
}
#[test]
fn lookup_type_8_reverse_chain_substitutes_under_context() {
let bytes = build_singleton_gsub(
LOOKUP_REVERSE_CHAIN_CONTEXT_SUBST,
&build_reverse_chain_sub(),
);
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(0, &[1, 10, 99], 1), Some(200));
}
#[test]
fn lookup_type_8_no_match_when_backtrack_or_lookahead_misses() {
let bytes = build_singleton_gsub(
LOOKUP_REVERSE_CHAIN_CONTEXT_SUBST,
&build_reverse_chain_sub(),
);
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(0, &[2, 10, 99], 1), None);
assert_eq!(g.apply_lookup_type_8(0, &[1, 10, 50], 1), None);
assert_eq!(g.apply_lookup_type_8(0, &[10, 99], 0), None);
assert_eq!(g.apply_lookup_type_8(0, &[1, 10], 1), None);
assert_eq!(g.apply_lookup_type_8(0, &[1, 11, 99], 1), None);
assert_eq!(g.apply_lookup_type_8(99, &[1, 10, 99], 1), None);
}
#[test]
fn chain_context_can_dispatch_nested_lookup_type_2() {
let mult_sub = {
let mut seq = Vec::new();
seq.extend_from_slice(&2u16.to_be_bytes());
seq.extend_from_slice(&10u16.to_be_bytes());
seq.extend_from_slice(&99u16.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 header_len = 8u16;
let cov_off = header_len;
let seq_off = cov_off + cov.len() as u16;
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(&1u16.to_be_bytes());
sub.extend_from_slice(&seq_off.to_be_bytes());
sub.extend_from_slice(&cov);
sub.extend_from_slice(&seq);
sub
};
let chain_sub = {
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(&0u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&0u16.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 = 4u16;
let mut rule_set = Vec::new();
rule_set.extend_from_slice(&1u16.to_be_bytes());
rule_set.extend_from_slice(&rule_set_header.to_be_bytes());
rule_set.extend_from_slice(&rule);
let header_len = 8u16;
let cov_off = header_len;
let set_off = cov_off + cov.len() as u16;
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(&1u16.to_be_bytes());
sub.extend_from_slice(&set_off.to_be_bytes());
sub.extend_from_slice(&cov);
sub.extend_from_slice(&rule_set);
sub
};
let lookup0 = wrap_lookup_helper(LOOKUP_MULTIPLE_SUBST, &mult_sub);
let lookup1 = wrap_lookup_helper(LOOKUP_CHAIN_CONTEXT_SUBST, &chain_sub);
let lookup_list_header = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header 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 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);
let g = GsubTable::parse(&gsub).unwrap();
let out = g.apply_lookup_type_6(1, &[10], 0).unwrap();
assert_eq!(out, vec![10, 99]);
}
}