use crate::parser::{read_i16, read_u16, read_u32};
use crate::tables::device::{read_device_offset, resolve_device_delta};
use crate::tables::gdef::{
class_def_lookup, coverage_lookup, lookup_table_slice, popcount_u16, GdefTable,
};
use crate::tables::mvar::ItemVariationStore;
use crate::Error;
const LOOKUP_SINGLE_POS: u16 = 1;
const LOOKUP_PAIR_POS: u16 = 2;
const LOOKUP_CURSIVE_POS: u16 = 3;
const LOOKUP_MARK_BASE_POS: u16 = 4;
const LOOKUP_MARK_LIGATURE_POS: u16 = 5;
const LOOKUP_MARK_MARK_POS: u16 = 6;
const LOOKUP_CONTEXT_POS: u16 = 7;
const LOOKUP_CHAIN_CONTEXT_POS: u16 = 8;
const LOOKUP_EXTENSION_POS: u16 = 9;
const MAX_NESTED_LOOKUP_DEPTH: u8 = 8;
const VF_X_PLACEMENT: u16 = 0x0001;
const VF_Y_PLACEMENT: u16 = 0x0002;
const VF_X_ADVANCE: u16 = 0x0004;
const VF_Y_ADVANCE: u16 = 0x0008;
const VF_X_PLA_DEVICE: u16 = 0x0010;
const VF_Y_PLA_DEVICE: u16 = 0x0020;
const VF_X_ADV_DEVICE: u16 = 0x0040;
const VF_Y_ADV_DEVICE: u16 = 0x0080;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PosValue {
pub x_placement: i16,
pub y_placement: i16,
pub x_advance: i16,
pub y_advance: i16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PosRecord {
pub glyph_index: usize,
pub value: PosValue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CursiveAttachment {
pub entry: Option<(i16, i16)>,
pub exit: Option<(i16, i16)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GposFeature {
pub tag: [u8; 4],
pub lookup_indices: Vec<u16>,
}
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct GposTable<'a> {
bytes: &'a [u8],
script_list_off: u32,
feature_list_off: u32,
lookup_list_off: u32,
feature_variations_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 minor = read_u16(bytes, 2)?;
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;
let feature_variations_off = if minor >= 1 && bytes.len() >= 14 {
read_u32(bytes, 10)?
} else {
0
};
for off in [
script_list_off,
feature_list_off,
lookup_list_off,
feature_variations_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,
feature_variations_off,
})
}
pub fn features_for_script(
&self,
script_tag: [u8; 4],
lang_tag: Option<[u8; 4]>,
) -> Vec<GposFeature> {
self.features_for_script_inner(script_tag, lang_tag, None)
}
pub fn features_for_script_at_coords(
&self,
script_tag: [u8; 4],
lang_tag: Option<[u8; 4]>,
normalised_coords: &[f32],
) -> Vec<GposFeature> {
let fv = match super::feature_variations::FeatureVariations::parse(
self.bytes,
self.feature_variations_off,
) {
Ok(Some(fv)) => fv,
_ => return self.features_for_script_inner(script_tag, lang_tag, None),
};
let subst = fv.active_substitution(normalised_coords);
self.features_for_script_inner(script_tag, lang_tag, subst.as_ref())
}
pub fn has_feature_variations(&self) -> bool {
self.feature_variations_off != 0
}
fn features_for_script_inner(
&self,
script_tag: [u8; 4],
lang_tag: Option<[u8; 4]>,
subst: Option<&super::feature_variations::FeatureTableSubstitution<'a>>,
) -> Vec<GposFeature> {
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<GposFeature>| {
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],
];
if let Some(s) = subst {
if let Some(idxs) = s.lookup_indices_for_feature(fi) {
into.push(GposFeature {
tag,
lookup_indices: idxs,
});
return;
}
}
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(GposFeature {
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 lookup_list(&self) -> impl Iterator<Item = (u16, u16, u16)> + '_ {
let lookup_count = self
.bytes
.get(self.lookup_list_off as usize..)
.and_then(|s| read_u16(s, 0).ok())
.unwrap_or(0);
(0..lookup_count).filter_map(move |i| {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
return None;
}
let mut kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()?;
if kind == LOOKUP_EXTENSION_POS && sub_count > 0 {
if let Some(t) = peek_extension_type(lookup) {
kind = t;
}
}
Some((i, kind, sub_count))
})
}
pub fn lookup_flags(&self, lookup_index: u16) -> u16 {
lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)
.filter(|l| l.len() >= 6)
.and_then(|l| read_u16(l, 2).ok())
.unwrap_or(0)
}
pub fn mark_filtering_set(&self, lookup_index: u16) -> Option<u16> {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
let flags = read_u16(lookup, 2).ok()?;
if flags & 0x0010 == 0 {
return None;
}
let sub_count = read_u16(lookup, 4).ok()? as usize;
read_u16(lookup, 6 + sub_count * 2).ok()
}
pub fn apply_lookup_type_1(&self, lookup_index: u16, gid: u16) -> Option<PosValue> {
if self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_SINGLE_POS {
continue;
}
if let Some(v) = single_pos_lookup(effective_sub, gid) {
return Some(v);
}
}
None
}
pub fn apply_lookup_type_1_var(
&self,
lookup_index: u16,
gid: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> Option<PosValue> {
if self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_SINGLE_POS {
continue;
}
if let Some(v) = single_pos_lookup_var(effective_sub, gid, ivs, normalised_coords) {
return Some(v);
}
}
None
}
pub fn apply_lookup_type_3(&self, lookup_index: u16, gid: u16) -> Option<CursiveAttachment> {
if self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_CURSIVE_POS {
continue;
}
if let Some(v) = cursive_pos_lookup(effective_sub, gid, AnchorCtx::STATIC) {
return Some(v);
}
}
None
}
pub fn lookup_cursive_attachment(&self, gid: u16) -> Option<CursiveAttachment> {
self.lookup_cursive_attachment_ctx(gid, AnchorCtx::STATIC)
}
pub fn lookup_cursive_attachment_var(
&self,
gid: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> Option<CursiveAttachment> {
self.lookup_cursive_attachment_ctx(
gid,
AnchorCtx {
ivs,
coords: normalised_coords,
},
)
}
fn lookup_cursive_attachment_ctx(
&self,
gid: u16,
ctx: AnchorCtx<'_>,
) -> Option<CursiveAttachment> {
let lookup_list = self.bytes.get(self.lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let lookup_count = read_u16(lookup_list, 0).ok()?;
for i in 0..lookup_count {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
continue;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_CURSIVE_POS {
continue;
}
if let Some(v) = cursive_pos_lookup(effective_sub, gid, ctx) {
return Some(v);
}
}
}
None
}
pub fn apply_lookup_type_5(
&self,
lookup_index: u16,
ligature: u16,
ligature_component: u16,
mark: u16,
) -> Option<(i16, i16)> {
if self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_LIGATURE_POS {
continue;
}
if let Some(v) = mark_ligature_pos_lookup(
effective_sub,
ligature,
ligature_component,
mark,
AnchorCtx::STATIC,
) {
return Some(v);
}
}
None
}
pub fn lookup_mark_to_ligature(
&self,
ligature: u16,
ligature_component: u16,
mark: u16,
) -> Option<(i16, i16)> {
self.lookup_mark_to_ligature_ctx(ligature, ligature_component, mark, AnchorCtx::STATIC)
}
pub fn lookup_mark_to_ligature_var(
&self,
ligature: u16,
ligature_component: u16,
mark: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> Option<(i16, i16)> {
self.lookup_mark_to_ligature_ctx(
ligature,
ligature_component,
mark,
AnchorCtx {
ivs,
coords: normalised_coords,
},
)
}
fn lookup_mark_to_ligature_ctx(
&self,
ligature: u16,
ligature_component: u16,
mark: u16,
ctx: AnchorCtx<'_>,
) -> Option<(i16, i16)> {
let lookup_list = self.bytes.get(self.lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let lookup_count = read_u16(lookup_list, 0).ok()?;
for i in 0..lookup_count {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, i)?;
if lookup.len() < 6 {
continue;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_LIGATURE_POS {
continue;
}
if let Some(v) =
mark_ligature_pos_lookup(effective_sub, ligature, ligature_component, mark, ctx)
{
return Some(v);
}
}
}
None
}
pub fn apply_lookup_type_8(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
) -> Option<Vec<PosRecord>> {
self.apply_chain_context_at(lookup_index, gids, pos, 0)
}
pub fn apply_lookup_type_7(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
) -> Option<Vec<PosRecord>> {
self.apply_context_at(lookup_index, gids, pos, 0)
}
pub fn lookup_mark_to_base(&self, base: u16, mark: u16) -> Option<(i16, i16)> {
self.lookup_mark_to_base_ctx(base, mark, AnchorCtx::STATIC)
}
pub fn lookup_mark_to_base_var(
&self,
base: u16,
mark: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> Option<(i16, i16)> {
self.lookup_mark_to_base_ctx(
base,
mark,
AnchorCtx {
ivs,
coords: normalised_coords,
},
)
}
fn lookup_mark_to_base_ctx(
&self,
base: u16,
mark: u16,
ctx: AnchorCtx<'_>,
) -> 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, ctx) {
return Some(v);
}
}
}
None
}
pub fn lookup_mark_to_mark(&self, mark1: u16, mark2: u16) -> Option<(i16, i16)> {
self.lookup_mark_to_mark_ctx(mark1, mark2, AnchorCtx::STATIC)
}
pub fn lookup_mark_to_mark_var(
&self,
mark1: u16,
mark2: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> Option<(i16, i16)> {
self.lookup_mark_to_mark_ctx(
mark1,
mark2,
AnchorCtx {
ivs,
coords: normalised_coords,
},
)
}
fn lookup_mark_to_mark_ctx(
&self,
mark1: u16,
mark2: u16,
ctx: AnchorCtx<'_>,
) -> 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, ctx) {
return Some(v);
}
}
}
None
}
pub fn lookup_kerning(&self, left: u16, right: u16, gdef: Option<&GdefTable<'_>>) -> i16 {
self.lookup_kerning_ctx(left, right, gdef, AnchorCtx::STATIC)
}
pub fn lookup_kerning_var(
&self,
left: u16,
right: u16,
gdef: Option<&GdefTable<'_>>,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> i16 {
self.lookup_kerning_ctx(
left,
right,
gdef,
AnchorCtx {
ivs,
coords: normalised_coords,
},
)
}
fn lookup_kerning_ctx(
&self,
left: u16,
right: u16,
gdef: Option<&GdefTable<'_>>,
ctx: AnchorCtx<'_>,
) -> 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_ctx(effective_sub, left, right, ctx) {
return v;
}
}
}
0
}
fn walk_lookup_subtables<T>(
&self,
lookup_index: u16,
want_kind: u16,
mut f: impl FnMut(&[u8]) -> Option<T>,
) -> Option<T> {
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_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 = match sub.get(ext_off..) {
Some(s) => s,
None => continue,
};
(ext_type, ext)
} else {
(kind, sub)
};
if effective_kind != want_kind {
continue;
}
if let Some(v) = f(effective_sub) {
return Some(v);
}
}
None
}
pub fn lookup_kerning_at(
&self,
lookup_index: u16,
left: u16,
right: u16,
gdef: Option<&GdefTable<'_>>,
) -> i16 {
let lookup = match lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index) {
Some(l) if l.len() >= 6 => l,
_ => return 0,
};
let flag = read_u16(lookup, 2).unwrap_or(0);
if (flag & 0x0008) != 0 {
if let Some(g) = gdef {
if g.is_mark(left) || g.is_mark(right) {
return 0;
}
}
}
self.walk_lookup_subtables(lookup_index, LOOKUP_PAIR_POS, |sub| {
pair_pos_lookup(sub, left, right)
})
.unwrap_or(0)
}
pub fn apply_mark_to_base_at(
&self,
lookup_index: u16,
base: u16,
mark: u16,
) -> Option<(i16, i16)> {
self.walk_lookup_subtables(lookup_index, LOOKUP_MARK_BASE_POS, |sub| {
mark_base_pos_lookup(sub, base, mark, AnchorCtx::STATIC)
})
}
pub fn apply_mark_to_mark_at(
&self,
lookup_index: u16,
mark1: u16,
mark2: u16,
) -> Option<(i16, i16)> {
self.walk_lookup_subtables(lookup_index, LOOKUP_MARK_MARK_POS, |sub| {
mark_mark_pos_lookup(sub, mark1, mark2, AnchorCtx::STATIC)
})
}
}
fn pair_pos_lookup(sub: &[u8], left: u16, right: u16) -> Option<i16> {
pair_pos_lookup_ctx(sub, left, right, AnchorCtx::STATIC)
}
fn pair_pos_lookup_ctx(sub: &[u8], left: u16, right: u16, ctx: AnchorCtx<'_>) -> 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, ctx),
2 => pair_pos_format2(sub, left, right, value_format1, v1_size, v2_size, ctx),
_ => None,
}
}
#[allow(clippy::too_many_arguments)]
fn pair_pos_format1(
sub: &[u8],
cov_idx: u16,
right: u16,
value_format1: u16,
v1_size: usize,
v2_size: usize,
ctx: AnchorCtx<'_>,
) -> 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_var(
pair_set,
off + 2,
value_format1,
pair_set,
ctx,
));
}
if sg < right {
lo = mid + 1;
} else {
hi = mid;
}
}
None
}
#[allow(clippy::too_many_arguments)]
fn pair_pos_format2(
sub: &[u8],
left: u16,
right: u16,
value_format1: u16,
v1_size: usize,
v2_size: usize,
ctx: AnchorCtx<'_>,
) -> 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_var(sub, off, value_format1, sub, ctx))
}
fn mark_base_pos_lookup(
sub: &[u8],
base: u16,
mark: u16,
ctx: AnchorCtx<'_>,
) -> 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_with(mark_anchor, ctx)?;
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_with(base_anchor, ctx)?;
Some((bx.wrapping_sub(mx), by.wrapping_sub(my)))
}
fn mark_mark_pos_lookup(
sub: &[u8],
mark1: u16,
mark2: u16,
ctx: AnchorCtx<'_>,
) -> 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_with(new_mark_anchor, ctx)?;
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_with(prev_anchor, ctx)?;
Some((bx.wrapping_sub(mx), by.wrapping_sub(my)))
}
fn cursive_pos_lookup(sub: &[u8], gid: u16, ctx: AnchorCtx<'_>) -> Option<CursiveAttachment> {
if sub.len() < 6 {
return None;
}
let format = read_u16(sub, 0).ok()?;
if format != 1 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let entry_exit_count = read_u16(sub, 4).ok()? as usize;
let cov = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(cov, gid)? as usize;
if cov_idx >= entry_exit_count {
return None;
}
let rec_off = 6 + cov_idx * 4;
if sub.len() < rec_off + 4 {
return None;
}
let entry_off = read_u16(sub, rec_off).ok()? as usize;
let exit_off = read_u16(sub, rec_off + 2).ok()? as usize;
let entry = if entry_off == 0 {
None
} else {
sub.get(entry_off..).and_then(|a| parse_anchor_with(a, ctx))
};
let exit = if exit_off == 0 {
None
} else {
sub.get(exit_off..).and_then(|a| parse_anchor_with(a, ctx))
};
Some(CursiveAttachment { entry, exit })
}
fn mark_ligature_pos_lookup(
sub: &[u8],
ligature: u16,
ligature_component: u16,
mark: u16,
ctx: AnchorCtx<'_>,
) -> Option<(i16, i16)> {
if sub.len() < 12 {
return None;
}
let format = read_u16(sub, 0).ok()?;
if format != 1 {
return None;
}
let mark_cov_off = read_u16(sub, 2).ok()? as usize;
let lig_cov_off = read_u16(sub, 4).ok()? as usize;
let mark_class_count = read_u16(sub, 6).ok()? as usize;
let mark_array_off = read_u16(sub, 8).ok()? as usize;
let lig_array_off = read_u16(sub, 10).ok()? as usize;
let mark_cov = sub.get(mark_cov_off..)?;
let lig_cov = sub.get(lig_cov_off..)?;
let mark_idx = coverage_lookup(mark_cov, mark)? as usize;
let lig_idx = coverage_lookup(lig_cov, ligature)? as usize;
let mark_array = sub.get(mark_array_off..)?;
if mark_array.len() < 2 {
return None;
}
let mark_count = read_u16(mark_array, 0).ok()? as usize;
if mark_idx >= mark_count {
return None;
}
let mr_off = 2 + mark_idx * 4;
let mark_class = read_u16(mark_array, mr_off).ok()? as usize;
let mark_anchor_off_local = read_u16(mark_array, mr_off + 2).ok()? as usize;
if mark_class >= mark_class_count {
return None;
}
let mark_anchor = mark_array.get(mark_anchor_off_local..)?;
let (mx, my) = parse_anchor_with(mark_anchor, ctx)?;
let lig_array = sub.get(lig_array_off..)?;
if lig_array.len() < 2 {
return None;
}
let lig_count = read_u16(lig_array, 0).ok()? as usize;
if lig_idx >= lig_count {
return None;
}
let lig_attach_off = read_u16(lig_array, 2 + lig_idx * 2).ok()? as usize;
let lig_attach = lig_array.get(lig_attach_off..)?;
if lig_attach.len() < 2 {
return None;
}
let component_count = read_u16(lig_attach, 0).ok()? as usize;
if (ligature_component as usize) >= component_count {
return None;
}
let comp_record_size = mark_class_count * 2;
let comp_off = 2 + ligature_component as usize * comp_record_size;
let lig_anchor_off_local = read_u16(lig_attach, comp_off + mark_class * 2).ok()? as usize;
if lig_anchor_off_local == 0 {
return None;
}
let lig_anchor = lig_attach.get(lig_anchor_off_local..)?;
let (lx, ly) = parse_anchor_with(lig_anchor, ctx)?;
Some((lx.wrapping_sub(mx), ly.wrapping_sub(my)))
}
#[derive(Clone, Copy)]
struct AnchorCtx<'a> {
ivs: Option<&'a ItemVariationStore>,
coords: &'a [f32],
}
impl AnchorCtx<'_> {
const STATIC: AnchorCtx<'static> = AnchorCtx {
ivs: None,
coords: &[],
};
}
fn parse_anchor_with(bytes: &[u8], ctx: AnchorCtx<'_>) -> Option<(i16, i16)> {
if bytes.len() < 6 {
return None;
}
let format = read_u16(bytes, 0).ok()?;
let mut x = read_i16(bytes, 2).ok()?;
let mut y = read_i16(bytes, 4).ok()?;
match format {
1 | 2 => Some((x, y)),
3 => {
if bytes.len() >= 10 {
let x_dev = read_device_offset(bytes, 6);
let y_dev = read_device_offset(bytes, 8);
x = saturating_add_delta(x, bytes, x_dev, ctx.ivs, ctx.coords);
y = saturating_add_delta(y, bytes, y_dev, ctx.ivs, ctx.coords);
}
Some((x, y))
}
_ => None,
}
}
fn extract_x_advance_var(
bytes: &[u8],
off: usize,
value_format: u16,
device_base: &[u8],
ctx: AnchorCtx<'_>,
) -> i16 {
let mut p = off;
if value_format & VF_X_PLACEMENT != 0 {
p += 2;
}
if value_format & VF_Y_PLACEMENT != 0 {
p += 2;
}
let mut x_adv = 0i16;
let mut x_adv_present = false;
if value_format & VF_X_ADVANCE != 0 {
x_adv = read_i16(bytes, p).unwrap_or(0);
x_adv_present = true;
p += 2;
}
if value_format & VF_Y_ADVANCE != 0 {
p += 2;
}
if value_format & VF_X_PLA_DEVICE != 0 {
p += 2;
}
if value_format & VF_Y_PLA_DEVICE != 0 {
p += 2;
}
if value_format & VF_X_ADV_DEVICE != 0 {
let dev = read_device_offset(bytes, p);
if x_adv_present {
x_adv = saturating_add_delta(x_adv, device_base, dev, ctx.ivs, ctx.coords);
}
}
x_adv
}
fn value_record_size(value_format: u16) -> usize {
popcount_u16(value_format) * 2
}
fn parse_value_record(bytes: &[u8], off: usize, value_format: u16) -> PosValue {
let mut v = PosValue::default();
let mut p = off;
if value_format & VF_X_PLACEMENT != 0 {
v.x_placement = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_Y_PLACEMENT != 0 {
v.y_placement = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_X_ADVANCE != 0 {
v.x_advance = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_Y_ADVANCE != 0 {
v.y_advance = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_X_PLA_DEVICE != 0 {
p += 2;
}
if value_format & VF_Y_PLA_DEVICE != 0 {
p += 2;
}
if value_format & VF_X_ADV_DEVICE != 0 {
p += 2;
}
if value_format & VF_Y_ADV_DEVICE != 0 {
p += 2;
}
let _ = p;
v
}
fn parse_value_record_var(
bytes: &[u8],
off: usize,
value_format: u16,
subtable_base: &[u8],
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> PosValue {
let mut v = PosValue::default();
let mut p = off;
if value_format & VF_X_PLACEMENT != 0 {
v.x_placement = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_Y_PLACEMENT != 0 {
v.y_placement = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_X_ADVANCE != 0 {
v.x_advance = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_Y_ADVANCE != 0 {
v.y_advance = read_i16(bytes, p).unwrap_or(0);
p += 2;
}
if value_format & VF_X_PLA_DEVICE != 0 {
let d = read_device_offset(bytes, p);
v.x_placement =
saturating_add_delta(v.x_placement, subtable_base, d, ivs, normalised_coords);
p += 2;
}
if value_format & VF_Y_PLA_DEVICE != 0 {
let d = read_device_offset(bytes, p);
v.y_placement =
saturating_add_delta(v.y_placement, subtable_base, d, ivs, normalised_coords);
p += 2;
}
if value_format & VF_X_ADV_DEVICE != 0 {
let d = read_device_offset(bytes, p);
v.x_advance = saturating_add_delta(v.x_advance, subtable_base, d, ivs, normalised_coords);
p += 2;
}
if value_format & VF_Y_ADV_DEVICE != 0 {
let d = read_device_offset(bytes, p);
v.y_advance = saturating_add_delta(v.y_advance, subtable_base, d, ivs, normalised_coords);
p += 2;
}
let _ = p;
v
}
fn saturating_add_delta(
base: i16,
subtable_base: &[u8],
offset: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> i16 {
let delta = resolve_device_delta(subtable_base, offset, ivs, normalised_coords);
let rounded = delta.round() as i32;
(base as i32 + rounded).clamp(i16::MIN as i32, i16::MAX as i32) as i16
}
fn single_pos_lookup(sub: &[u8], gid: u16) -> Option<PosValue> {
if sub.len() < 6 {
return None;
}
let format = read_u16(sub, 0).ok()?;
let coverage_off = read_u16(sub, 2).ok()? as usize;
let value_format = read_u16(sub, 4).ok()?;
let cov = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(cov, gid)? as usize;
let vr_size = value_record_size(value_format);
match format {
1 => {
Some(parse_value_record(sub, 6, value_format))
}
2 => {
let value_count = read_u16(sub, 6).ok()? as usize;
if cov_idx >= value_count {
return None;
}
let vr_off = 8 + cov_idx * vr_size;
if sub.len() < vr_off + vr_size {
return None;
}
Some(parse_value_record(sub, vr_off, value_format))
}
_ => None,
}
}
fn single_pos_lookup_var(
sub: &[u8],
gid: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> Option<PosValue> {
if sub.len() < 6 {
return None;
}
let format = read_u16(sub, 0).ok()?;
let coverage_off = read_u16(sub, 2).ok()? as usize;
let value_format = read_u16(sub, 4).ok()?;
let cov = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(cov, gid)? as usize;
let vr_size = value_record_size(value_format);
match format {
1 => Some(parse_value_record_var(
sub,
6,
value_format,
sub,
ivs,
normalised_coords,
)),
2 => {
let value_count = read_u16(sub, 6).ok()? as usize;
if cov_idx >= value_count {
return None;
}
let vr_off = 8 + cov_idx * vr_size;
if sub.len() < vr_off + vr_size {
return None;
}
Some(parse_value_record_var(
sub,
vr_off,
value_format,
sub,
ivs,
normalised_coords,
))
}
_ => None,
}
}
fn unwrap_extension(kind: u16, sub: &[u8]) -> Option<(u16, &[u8])> {
if kind == LOOKUP_EXTENSION_POS {
if sub.len() < 8 {
return None;
}
let ext_type = read_u16(sub, 2).ok()?;
let ext_off = read_u32(sub, 4).ok()? as usize;
let ext = sub.get(ext_off..)?;
Some((ext_type, ext))
} else {
Some((kind, sub))
}
}
fn peek_extension_type(lookup: &[u8]) -> Option<u16> {
if lookup.len() < 8 {
return None;
}
let sub_count = read_u16(lookup, 4).ok()? as usize;
if sub_count == 0 {
return None;
}
let sub_off = read_u16(lookup, 6).ok()? as usize;
let sub = lookup.get(sub_off..)?;
if sub.len() < 8 {
return None;
}
read_u16(sub, 2).ok()
}
fn read_pos_lookup_records(
bytes: &[u8],
offset: usize,
count: usize,
) -> Option<Vec<PosLookupRecord>> {
if bytes.len() < offset + count * 4 {
return None;
}
let mut out = Vec::with_capacity(count);
for i in 0..count {
let off = offset + i * 4;
let seq = read_u16(bytes, off).ok()?;
let lk = read_u16(bytes, off + 2).ok()?;
out.push(PosLookupRecord {
sequence_index: seq,
lookup_index: lk,
});
}
Some(out)
}
#[derive(Debug, Clone, Copy)]
struct PosLookupRecord {
sequence_index: u16,
lookup_index: u16,
}
#[derive(Debug)]
struct ChainPosMatch {
input_len: usize,
records: Vec<PosLookupRecord>,
}
impl<'a> GposTable<'a> {
fn apply_context_at(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
depth: u8,
) -> Option<Vec<PosRecord>> {
if depth >= MAX_NESTED_LOOKUP_DEPTH {
return None;
}
if pos >= gids.len() || self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = match unwrap_extension(kind, sub) {
Some(p) => p,
None => continue,
};
if effective_kind != LOOKUP_CONTEXT_POS {
continue;
}
if effective_sub.len() < 2 {
continue;
}
let format = match read_u16(effective_sub, 0) {
Ok(v) => v,
Err(_) => continue,
};
let matched = match format {
1 => context_pos_format1_match(effective_sub, gids, pos),
2 => context_pos_format2_match(effective_sub, gids, pos),
3 => context_pos_format3_match(effective_sub, gids, pos),
_ => None,
};
if let Some(m) = matched {
return Some(self.apply_pos_records(gids, pos, &m, depth));
}
}
None
}
fn apply_chain_context_at(
&self,
lookup_index: u16,
gids: &[u16],
pos: usize,
depth: u8,
) -> Option<Vec<PosRecord>> {
if depth >= MAX_NESTED_LOOKUP_DEPTH {
return None;
}
if pos >= gids.len() || self.lookup_list_off == 0 {
return None;
}
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = match unwrap_extension(kind, sub) {
Some(p) => p,
None => continue,
};
if effective_kind != LOOKUP_CHAIN_CONTEXT_POS {
continue;
}
if effective_sub.len() < 2 {
continue;
}
let format = match read_u16(effective_sub, 0) {
Ok(v) => v,
Err(_) => continue,
};
let matched = match format {
1 => chain_context_pos_format1_match(effective_sub, gids, pos),
2 => chain_context_pos_format2_match(effective_sub, gids, pos),
3 => chain_context_pos_format3_match(effective_sub, gids, pos),
_ => None,
};
if let Some(m) = matched {
return Some(self.apply_pos_records(gids, pos, &m, depth));
}
}
None
}
fn apply_pos_records(
&self,
gids: &[u16],
pos: usize,
m: &ChainPosMatch,
depth: u8,
) -> Vec<PosRecord> {
let mut out: Vec<PosRecord> = Vec::new();
for rec in &m.records {
let seq_idx = rec.sequence_index as usize;
if seq_idx >= m.input_len {
continue;
}
let abs_idx = pos + seq_idx;
if abs_idx >= gids.len() {
continue;
}
let lookup =
match lookup_table_slice(self.bytes, self.lookup_list_off, rec.lookup_index) {
Some(s) => s,
None => continue,
};
if lookup.len() < 2 {
continue;
}
let mut nested_kind = match read_u16(lookup, 0) {
Ok(v) => v,
Err(_) => continue,
};
if nested_kind == LOOKUP_EXTENSION_POS {
if let Some(t) = peek_extension_type(lookup) {
nested_kind = t;
}
}
match nested_kind {
LOOKUP_SINGLE_POS => {
if let Some(v) = self.apply_lookup_type_1(rec.lookup_index, gids[abs_idx]) {
out.push(PosRecord {
glyph_index: abs_idx,
value: v,
});
}
}
LOOKUP_PAIR_POS if abs_idx + 1 < gids.len() => {
let dx = self.lookup_kerning(gids[abs_idx], gids[abs_idx + 1], None);
if dx != 0 {
out.push(PosRecord {
glyph_index: abs_idx,
value: PosValue {
x_advance: dx,
..PosValue::default()
},
});
}
}
LOOKUP_CURSIVE_POS if abs_idx > 0 => {
let prev = self.apply_lookup_type_3(rec.lookup_index, gids[abs_idx - 1]);
let curr = self.apply_lookup_type_3(rec.lookup_index, gids[abs_idx]);
if let (Some(p), Some(c)) = (prev, curr) {
if let (Some((px, py)), Some((cx, cy))) = (p.exit, c.entry) {
out.push(PosRecord {
glyph_index: abs_idx,
value: PosValue {
x_placement: px.wrapping_sub(cx),
y_placement: py.wrapping_sub(cy),
..PosValue::default()
},
});
}
}
}
LOOKUP_MARK_BASE_POS if abs_idx + 1 < gids.len() => {
if let Some((dx, dy)) = self.lookup_mark_to_base_via_lookup(
rec.lookup_index,
gids[abs_idx],
gids[abs_idx + 1],
) {
out.push(PosRecord {
glyph_index: abs_idx + 1,
value: PosValue {
x_placement: dx,
y_placement: dy,
..PosValue::default()
},
});
}
}
LOOKUP_MARK_MARK_POS if abs_idx + 1 < gids.len() => {
if let Some((dx, dy)) = self.lookup_mark_to_mark_via_lookup(
rec.lookup_index,
gids[abs_idx],
gids[abs_idx + 1],
) {
out.push(PosRecord {
glyph_index: abs_idx + 1,
value: PosValue {
x_placement: dx,
y_placement: dy,
..PosValue::default()
},
});
}
}
LOOKUP_CONTEXT_POS => {
if let Some(mut nested) =
self.apply_context_at(rec.lookup_index, gids, abs_idx, depth + 1)
{
out.append(&mut nested);
}
}
LOOKUP_CHAIN_CONTEXT_POS => {
if let Some(mut nested) =
self.apply_chain_context_at(rec.lookup_index, gids, abs_idx, depth + 1)
{
out.append(&mut nested);
}
}
_ => {
}
}
}
out
}
fn lookup_mark_to_base_via_lookup(
&self,
lookup_index: u16,
base: u16,
mark: u16,
) -> Option<(i16, i16)> {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_BASE_POS {
continue;
}
if let Some(v) = mark_base_pos_lookup(effective_sub, base, mark, AnchorCtx::STATIC) {
return Some(v);
}
}
None
}
fn lookup_mark_to_mark_via_lookup(
&self,
lookup_index: u16,
mark1: u16,
mark2: u16,
) -> Option<(i16, i16)> {
let lookup = lookup_table_slice(self.bytes, self.lookup_list_off, lookup_index)?;
if lookup.len() < 6 {
return None;
}
let kind = read_u16(lookup, 0).ok()?;
let sub_count = read_u16(lookup, 4).ok()? as usize;
for s in 0..sub_count {
let sub_off = read_u16(lookup, 6 + s * 2).ok()? as usize;
let sub = lookup.get(sub_off..)?;
let (effective_kind, effective_sub) = unwrap_extension(kind, sub)?;
if effective_kind != LOOKUP_MARK_MARK_POS {
continue;
}
if let Some(v) = mark_mark_pos_lookup(effective_sub, mark1, mark2, AnchorCtx::STATIC) {
return Some(v);
}
}
None
}
}
fn context_pos_format1_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 6 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let set_count = read_u16(sub, 4).ok()? as usize;
let coverage = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(coverage, gids[pos])? as usize;
if cov_idx >= set_count {
return None;
}
let set_off = read_u16(sub, 6 + cov_idx * 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_pos_format1_rule_match(rule, gids, pos) {
return Some(m);
}
}
None
}
fn context_pos_format1_rule_match(rule: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if rule.len() < 4 {
return None;
}
let glyph_count = read_u16(rule, 0).ok()? as usize;
if glyph_count == 0 {
return None;
}
let seq_lookup_count = read_u16(rule, 2).ok()? as usize;
let in_extra = glyph_count - 1;
let mut cur = 4usize;
if rule.len() < cur + in_extra * 2 {
return None;
}
if pos + glyph_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;
let records = read_pos_lookup_records(rule, cur, seq_lookup_count)?;
Some(ChainPosMatch {
input_len: glyph_count,
records,
})
}
fn context_pos_format2_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 8 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let class_def_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 class_def = sub.get(class_def_off..)?;
let in_class0 = class_def_lookup(class_def, gids[pos]).unwrap_or(0);
if in_class0 as usize >= set_count {
return None;
}
let set_off = read_u16(sub, 8 + 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;
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_pos_format2_rule_match(rule, gids, pos, class_def) {
return Some(m);
}
}
None
}
fn context_pos_format2_rule_match(
rule: &[u8],
gids: &[u16],
pos: usize,
class_def: &[u8],
) -> Option<ChainPosMatch> {
if rule.len() < 4 {
return None;
}
let glyph_count = read_u16(rule, 0).ok()? as usize;
if glyph_count == 0 {
return None;
}
let seq_lookup_count = read_u16(rule, 2).ok()? as usize;
let in_extra = glyph_count - 1;
let mut cur = 4usize;
if rule.len() < cur + in_extra * 2 {
return None;
}
if pos + glyph_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(class_def, gids[pos + 1 + i]).unwrap_or(0);
if want != got {
return None;
}
}
cur += in_extra * 2;
let records = read_pos_lookup_records(rule, cur, seq_lookup_count)?;
Some(ChainPosMatch {
input_len: glyph_count,
records,
})
}
fn context_pos_format3_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 6 {
return None;
}
let glyph_count = read_u16(sub, 2).ok()? as usize;
if glyph_count == 0 {
return None;
}
let seq_lookup_count = read_u16(sub, 4).ok()? as usize;
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_pos_lookup_records(sub, cur, seq_lookup_count)?;
Some(ChainPosMatch {
input_len: glyph_count,
records,
})
}
fn chain_context_pos_format1_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 6 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let coverage = sub.get(coverage_off..)?;
let cov_idx = coverage_lookup(coverage, gids[pos])? as usize;
let set_count = read_u16(sub, 4).ok()? as usize;
if cov_idx >= set_count {
return None;
}
let set_off = read_u16(sub, 6 + cov_idx * 2).ok()? as usize;
let set = sub.get(set_off..)?;
if set.len() < 2 {
return None;
}
let rule_count = read_u16(set, 0).ok()? as usize;
for r in 0..rule_count {
let rule_off = read_u16(set, 2 + r * 2).ok()? as usize;
let rule = match set.get(rule_off..) {
Some(b) => b,
None => continue,
};
if let Some(m) = chain_context_pos_format1_rule_match(rule, gids, pos) {
return Some(m);
}
}
None
}
fn chain_context_pos_format1_rule_match(
rule: &[u8],
gids: &[u16],
pos: usize,
) -> Option<ChainPosMatch> {
let mut cur = 0usize;
if rule.len() < cur + 2 {
return None;
}
let bt_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + bt_count * 2 {
return None;
}
if pos < bt_count {
return None;
}
for i in 0..bt_count {
let want = read_u16(rule, cur + i * 2).ok()?;
if gids[pos - 1 - i] != want {
return None;
}
}
cur += bt_count * 2;
if rule.len() < cur + 2 {
return None;
}
let in_count = read_u16(rule, cur).ok()? as usize;
if in_count == 0 {
return None;
}
cur += 2;
let in_extra = in_count - 1;
if rule.len() < cur + in_extra * 2 {
return None;
}
if pos + in_count > gids.len() {
return None;
}
for i in 0..in_extra {
let want = read_u16(rule, cur + i * 2).ok()?;
if gids[pos + 1 + i] != want {
return None;
}
}
cur += in_extra * 2;
if rule.len() < cur + 2 {
return None;
}
let la_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + la_count * 2 {
return None;
}
if pos + in_count + la_count > gids.len() {
return None;
}
for i in 0..la_count {
let want = read_u16(rule, cur + i * 2).ok()?;
if gids[pos + in_count + i] != want {
return None;
}
}
cur += la_count * 2;
if rule.len() < cur + 2 {
return None;
}
let pos_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_pos_lookup_records(rule, cur, pos_count)?;
Some(ChainPosMatch {
input_len: in_count,
records,
})
}
fn chain_context_pos_format2_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 12 {
return None;
}
let coverage_off = read_u16(sub, 2).ok()? as usize;
let bt_cd_off = read_u16(sub, 4).ok()? as usize;
let in_cd_off = read_u16(sub, 6).ok()? as usize;
let la_cd_off = read_u16(sub, 8).ok()? as usize;
let set_count = read_u16(sub, 10).ok()? as usize;
let coverage = sub.get(coverage_off..)?;
coverage_lookup(coverage, gids[pos])?;
let in_cd = sub.get(in_cd_off..)?;
let in_class0 = class_def_lookup(in_cd, gids[pos]).unwrap_or(0);
if in_class0 as usize >= set_count {
return None;
}
let set_off = read_u16(sub, 12 + in_class0 as usize * 2).ok()? as usize;
if set_off == 0 {
return None;
}
let set = sub.get(set_off..)?;
if set.len() < 2 {
return None;
}
let rule_count = read_u16(set, 0).ok()? as usize;
let bt_cd = sub.get(bt_cd_off..);
let la_cd = sub.get(la_cd_off..);
for r in 0..rule_count {
let rule_off = read_u16(set, 2 + r * 2).ok()? as usize;
let rule = match set.get(rule_off..) {
Some(b) => b,
None => continue,
};
if let Some(m) =
chain_context_pos_format2_rule_match(rule, gids, pos, bt_cd, in_cd, la_cd, in_class0)
{
return Some(m);
}
}
None
}
fn chain_context_pos_format2_rule_match(
rule: &[u8],
gids: &[u16],
pos: usize,
bt_cd: Option<&[u8]>,
in_cd: &[u8],
la_cd: Option<&[u8]>,
in_class0: u16,
) -> Option<ChainPosMatch> {
let mut cur = 0usize;
if rule.len() < cur + 2 {
return None;
}
let bt_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + bt_count * 2 {
return None;
}
if pos < bt_count {
return None;
}
if bt_count > 0 {
let bt_cd = bt_cd?;
for i in 0..bt_count {
let want = read_u16(rule, cur + i * 2).ok()?;
let got = class_def_lookup(bt_cd, gids[pos - 1 - i]).unwrap_or(0);
if want != got {
return None;
}
}
}
cur += bt_count * 2;
if rule.len() < cur + 2 {
return None;
}
let in_count = read_u16(rule, cur).ok()? as usize;
if in_count == 0 {
return None;
}
cur += 2;
let in_extra = in_count - 1;
if rule.len() < cur + in_extra * 2 {
return None;
}
if pos + in_count > gids.len() {
return None;
}
for i in 0..in_extra {
let want = read_u16(rule, cur + i * 2).ok()?;
let got = class_def_lookup(in_cd, gids[pos + 1 + i]).unwrap_or(0);
if want != got {
return None;
}
}
cur += in_extra * 2;
let _ = in_class0;
if rule.len() < cur + 2 {
return None;
}
let la_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
if rule.len() < cur + la_count * 2 {
return None;
}
if pos + in_count + la_count > gids.len() {
return None;
}
if la_count > 0 {
let la_cd = la_cd?;
for i in 0..la_count {
let want = read_u16(rule, cur + i * 2).ok()?;
let got = class_def_lookup(la_cd, gids[pos + in_count + i]).unwrap_or(0);
if want != got {
return None;
}
}
}
cur += la_count * 2;
if rule.len() < cur + 2 {
return None;
}
let pos_count = read_u16(rule, cur).ok()? as usize;
cur += 2;
let records = read_pos_lookup_records(rule, cur, pos_count)?;
Some(ChainPosMatch {
input_len: in_count,
records,
})
}
fn chain_context_pos_format3_match(sub: &[u8], gids: &[u16], pos: usize) -> Option<ChainPosMatch> {
if sub.len() < 4 {
return None;
}
let mut cur = 2usize;
let bt_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
if sub.len() < cur + bt_count * 2 {
return None;
}
if pos < bt_count {
return None;
}
for i in 0..bt_count {
let cov_off = read_u16(sub, cur + i * 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
coverage_lookup(cov, gids[pos - 1 - i])?;
}
cur += bt_count * 2;
if sub.len() < cur + 2 {
return None;
}
let in_count = read_u16(sub, cur).ok()? as usize;
if in_count == 0 {
return None;
}
cur += 2;
if sub.len() < cur + in_count * 2 {
return None;
}
if pos + in_count > gids.len() {
return None;
}
for i in 0..in_count {
let cov_off = read_u16(sub, cur + i * 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
coverage_lookup(cov, gids[pos + i])?;
}
cur += in_count * 2;
if sub.len() < cur + 2 {
return None;
}
let la_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
if sub.len() < cur + la_count * 2 {
return None;
}
if pos + in_count + la_count > gids.len() {
return None;
}
for i in 0..la_count {
let cov_off = read_u16(sub, cur + i * 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
coverage_lookup(cov, gids[pos + in_count + i])?;
}
cur += la_count * 2;
if sub.len() < cur + 2 {
return None;
}
let pos_count = read_u16(sub, cur).ok()? as usize;
cur += 2;
let records = read_pos_lookup_records(sub, cur, pos_count)?;
Some(ChainPosMatch {
input_len: in_count,
records,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn build_simple_pp1() -> Vec<u8> {
let mut pvr = Vec::new();
pvr.extend_from_slice(&60u16.to_be_bytes());
pvr.extend_from_slice(&(-100i16).to_be_bytes());
let mut pair_set = Vec::new();
pair_set.extend_from_slice(&1u16.to_be_bytes());
pair_set.extend_from_slice(&pvr);
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&50u16.to_be_bytes());
let header = 10;
let pair_set_offsets_size = 2;
let cov_off = header + pair_set_offsets_size;
let pair_set_off = cov_off + cov.len();
let mut pp1 = Vec::new();
pp1.extend_from_slice(&1u16.to_be_bytes()); pp1.extend_from_slice(&(cov_off as u16).to_be_bytes());
pp1.extend_from_slice(&VF_X_ADVANCE.to_be_bytes()); pp1.extend_from_slice(&0u16.to_be_bytes()); pp1.extend_from_slice(&1u16.to_be_bytes()); pp1.extend_from_slice(&(pair_set_off as u16).to_be_bytes());
pp1.extend_from_slice(&cov);
pp1.extend_from_slice(&pair_set);
let mut lookup = Vec::new();
lookup.extend_from_slice(&2u16.to_be_bytes());
lookup.extend_from_slice(&0u16.to_be_bytes());
lookup.extend_from_slice(&1u16.to_be_bytes());
lookup.extend_from_slice(&8u16.to_be_bytes());
lookup.extend_from_slice(&pp1);
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&4u16.to_be_bytes());
lookup_list.extend_from_slice(&lookup);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
fn build_pp1_var_kern(base: i16) -> Vec<u8> {
let vf1 = VF_X_ADVANCE | VF_X_ADV_DEVICE;
let mut pair_set = Vec::new();
pair_set.extend_from_slice(&1u16.to_be_bytes()); pair_set.extend_from_slice(&60u16.to_be_bytes()); pair_set.extend_from_slice(&base.to_be_bytes()); pair_set.extend_from_slice(&8u16.to_be_bytes()); pair_set.extend_from_slice(&0u16.to_be_bytes()); pair_set.extend_from_slice(&0u16.to_be_bytes()); pair_set.extend_from_slice(&0x8000u16.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(&50u16.to_be_bytes());
let header = 10;
let cov_off = header + 2;
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(&vf1.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 lookup = wrap_lookup(LOOKUP_PAIR_POS, &pp1);
wrap_gpos_single(&lookup)
}
#[test]
fn pair_pos_var_kerning_tracks_instance() {
let bytes = build_pp1_var_kern(-100);
let g = GposTable::parse(&bytes).unwrap();
let ivs_bytes = build_single_region_ivs(-40);
let ivs = ItemVariationStore::parse(&ivs_bytes).unwrap();
assert_eq!(g.lookup_kerning(50, 60, None), -100);
assert_eq!(g.lookup_kerning_var(50, 60, None, Some(&ivs), &[0.0]), -100);
assert_eq!(g.lookup_kerning_var(50, 60, None, Some(&ivs), &[1.0]), -140);
assert_eq!(g.lookup_kerning_var(50, 60, None, Some(&ivs), &[0.5]), -120);
assert_eq!(g.lookup_kerning_var(50, 60, None, None, &[1.0]), -100);
assert_eq!(g.lookup_kerning_var(50, 61, None, Some(&ivs), &[1.0]), 0);
}
#[test]
fn pair_pos_var_matches_static_without_device() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(
g.lookup_kerning(50, 60, None),
g.lookup_kerning_var(50, 60, None, None, &[0.3])
);
}
#[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_mark_base_var_y() -> Vec<u8> {
let mut base_anchor = Vec::new();
base_anchor.extend_from_slice(&3u16.to_be_bytes()); base_anchor.extend_from_slice(&100i16.to_be_bytes()); base_anchor.extend_from_slice(&800i16.to_be_bytes()); base_anchor.extend_from_slice(&0u16.to_be_bytes()); base_anchor.extend_from_slice(&10u16.to_be_bytes()); base_anchor.extend_from_slice(&0u16.to_be_bytes()); base_anchor.extend_from_slice(&0u16.to_be_bytes()); base_anchor.extend_from_slice(&0x8000u16.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 lookup = wrap_lookup(LOOKUP_MARK_BASE_POS, &mbp);
wrap_gpos_single(&lookup)
}
#[test]
fn mark_to_base_var_shifts_anchor_with_instance() {
let bytes = build_mark_base_var_y();
let g = GposTable::parse(&bytes).unwrap();
let ivs_bytes = build_single_region_ivs(120);
let ivs = ItemVariationStore::parse(&ivs_bytes).unwrap();
assert_eq!(g.lookup_mark_to_base(10, 200), Some((50, 800)));
assert_eq!(
g.lookup_mark_to_base_var(10, 200, Some(&ivs), &[0.0]),
Some((50, 800))
);
assert_eq!(
g.lookup_mark_to_base_var(10, 200, Some(&ivs), &[1.0]),
Some((50, 920))
);
assert_eq!(
g.lookup_mark_to_base_var(10, 200, Some(&ivs), &[0.5]),
Some((50, 860))
);
assert_eq!(
g.lookup_mark_to_base_var(10, 200, None, &[1.0]),
Some((50, 800))
);
}
#[test]
fn mark_to_base_var_matches_static_for_format1_anchors() {
let bytes = build_simple_mark_base();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(
g.lookup_mark_to_base(10, 200),
g.lookup_mark_to_base_var(10, 200, None, &[0.7])
);
}
fn build_simple_mark_mark() -> Vec<u8> {
let mut prev_anchor = Vec::new();
prev_anchor.extend_from_slice(&1u16.to_be_bytes());
prev_anchor.extend_from_slice(&60i16.to_be_bytes());
prev_anchor.extend_from_slice(&1200i16.to_be_bytes());
let mut new_anchor = Vec::new();
new_anchor.extend_from_slice(&1u16.to_be_bytes());
new_anchor.extend_from_slice(&30i16.to_be_bytes());
new_anchor.extend_from_slice(&0i16.to_be_bytes());
let mut new_mark_array = Vec::new();
new_mark_array.extend_from_slice(&1u16.to_be_bytes());
new_mark_array.extend_from_slice(&0u16.to_be_bytes()); new_mark_array.extend_from_slice(&6u16.to_be_bytes()); new_mark_array.extend_from_slice(&new_anchor);
let mut prev_array = Vec::new();
prev_array.extend_from_slice(&1u16.to_be_bytes());
prev_array.extend_from_slice(&4u16.to_be_bytes()); prev_array.extend_from_slice(&prev_anchor);
let mut new_cov = Vec::new();
new_cov.extend_from_slice(&1u16.to_be_bytes());
new_cov.extend_from_slice(&1u16.to_be_bytes());
new_cov.extend_from_slice(&40u16.to_be_bytes());
let mut prev_cov = Vec::new();
prev_cov.extend_from_slice(&1u16.to_be_bytes());
prev_cov.extend_from_slice(&1u16.to_be_bytes());
prev_cov.extend_from_slice(&30u16.to_be_bytes());
let header = 12usize;
let new_cov_off = header;
let prev_cov_off = new_cov_off + new_cov.len();
let new_mark_array_off = prev_cov_off + prev_cov.len();
let prev_array_off = new_mark_array_off + new_mark_array.len();
let mut mmp = Vec::new();
mmp.extend_from_slice(&1u16.to_be_bytes()); mmp.extend_from_slice(&(new_cov_off as u16).to_be_bytes()); mmp.extend_from_slice(&(prev_cov_off as u16).to_be_bytes()); mmp.extend_from_slice(&1u16.to_be_bytes()); mmp.extend_from_slice(&(new_mark_array_off as u16).to_be_bytes());
mmp.extend_from_slice(&(prev_array_off as u16).to_be_bytes());
mmp.extend_from_slice(&new_cov);
mmp.extend_from_slice(&prev_cov);
mmp.extend_from_slice(&new_mark_array);
mmp.extend_from_slice(&prev_array);
let mut lookup = Vec::new();
lookup.extend_from_slice(&6u16.to_be_bytes());
lookup.extend_from_slice(&0u16.to_be_bytes());
lookup.extend_from_slice(&1u16.to_be_bytes());
lookup.extend_from_slice(&8u16.to_be_bytes());
lookup.extend_from_slice(&mmp);
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&4u16.to_be_bytes());
lookup_list.extend_from_slice(&lookup);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn mark_to_mark_round_trip() {
let bytes = build_simple_mark_mark();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_mark(30, 40), Some((30, 1200)));
assert_eq!(g.lookup_mark_to_mark(31, 40), None);
assert_eq!(g.lookup_mark_to_mark(30, 41), None);
}
#[test]
fn mark_to_mark_missing_table_returns_none() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_mark(50, 60), None);
let bytes = build_simple_mark_base();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_mark(10, 200), None);
}
fn wrap_lookup(lookup_type: u16, sub: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&lookup_type.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes());
out.extend_from_slice(&8u16.to_be_bytes());
out.extend_from_slice(sub);
out
}
fn wrap_gpos_single(lookup: &[u8]) -> Vec<u8> {
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&1u16.to_be_bytes());
lookup_list.extend_from_slice(&4u16.to_be_bytes());
lookup_list.extend_from_slice(lookup);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
fn build_single_pos_format1() -> Vec<u8> {
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&2u16.to_be_bytes());
cov.extend_from_slice(&7u16.to_be_bytes());
cov.extend_from_slice(&9u16.to_be_bytes());
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&8u16.to_be_bytes()); sub.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub.extend_from_slice(&(-150i16).to_be_bytes()); sub.extend_from_slice(&cov);
let lookup = wrap_lookup(LOOKUP_SINGLE_POS, &sub);
wrap_gpos_single(&lookup)
}
fn build_single_pos_format2() -> Vec<u8> {
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&2u16.to_be_bytes());
cov.extend_from_slice(&7u16.to_be_bytes());
cov.extend_from_slice(&9u16.to_be_bytes());
let vf = VF_X_PLACEMENT | VF_Y_PLACEMENT | VF_X_ADVANCE;
let cov_off = 20u16;
let mut sub = Vec::new();
sub.extend_from_slice(&2u16.to_be_bytes()); sub.extend_from_slice(&cov_off.to_be_bytes());
sub.extend_from_slice(&vf.to_be_bytes());
sub.extend_from_slice(&2u16.to_be_bytes()); sub.extend_from_slice(&10i16.to_be_bytes());
sub.extend_from_slice(&20i16.to_be_bytes());
sub.extend_from_slice(&30i16.to_be_bytes());
sub.extend_from_slice(&(-5i16).to_be_bytes());
sub.extend_from_slice(&(-15i16).to_be_bytes());
sub.extend_from_slice(&40i16.to_be_bytes());
sub.extend_from_slice(&cov);
let lookup = wrap_lookup(LOOKUP_SINGLE_POS, &sub);
wrap_gpos_single(&lookup)
}
#[test]
fn single_pos_format1_returns_shared_value_for_every_covered_glyph() {
let bytes = build_single_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let v7 = g.apply_lookup_type_1(0, 7).unwrap();
let v9 = g.apply_lookup_type_1(0, 9).unwrap();
assert_eq!(v7, v9);
assert_eq!(v7.x_advance, -150);
assert_eq!(g.apply_lookup_type_1(0, 8), None);
}
#[test]
fn single_pos_format2_returns_per_glyph_value() {
let bytes = build_single_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
let v7 = g.apply_lookup_type_1(0, 7).unwrap();
assert_eq!(v7.x_placement, 10);
assert_eq!(v7.y_placement, 20);
assert_eq!(v7.x_advance, 30);
let v9 = g.apply_lookup_type_1(0, 9).unwrap();
assert_eq!(v9.x_placement, -5);
assert_eq!(v9.y_placement, -15);
assert_eq!(v9.x_advance, 40);
assert_eq!(g.apply_lookup_type_1(0, 8), None);
}
#[test]
fn single_pos_returns_none_when_lookup_index_out_of_range() {
let bytes = build_single_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_1(99, 7), None);
}
fn build_single_region_ivs(delta: i16) -> Vec<u8> {
let mut b = vec![0u8; 32];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[2..6].copy_from_slice(&12u32.to_be_bytes());
b[6..8].copy_from_slice(&1u16.to_be_bytes());
b[8..12].copy_from_slice(&22u32.to_be_bytes());
b[12..14].copy_from_slice(&1u16.to_be_bytes());
b[14..16].copy_from_slice(&1u16.to_be_bytes());
b[16..18].copy_from_slice(&0i16.to_be_bytes());
b[18..20].copy_from_slice(&16384i16.to_be_bytes());
b[20..22].copy_from_slice(&16384i16.to_be_bytes());
b[22..24].copy_from_slice(&1u16.to_be_bytes());
b[24..26].copy_from_slice(&1u16.to_be_bytes());
b[26..28].copy_from_slice(&1u16.to_be_bytes());
b[28..30].copy_from_slice(&0u16.to_be_bytes());
b[30..32].copy_from_slice(&delta.to_be_bytes());
b
}
fn build_single_pos_format1_with_var_xadv(base: i16) -> Vec<u8> {
let vf = VF_X_ADVANCE | VF_X_ADV_DEVICE;
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&16u16.to_be_bytes()); sub.extend_from_slice(&vf.to_be_bytes());
sub.extend_from_slice(&base.to_be_bytes()); sub.extend_from_slice(&10u16.to_be_bytes()); sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&0x8000u16.to_be_bytes()); sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&7u16.to_be_bytes()); let lookup = wrap_lookup(LOOKUP_SINGLE_POS, &sub);
wrap_gpos_single(&lookup)
}
#[test]
fn single_pos_var_applies_variation_index_delta() {
let bytes = build_single_pos_format1_with_var_xadv(100);
let g = GposTable::parse(&bytes).unwrap();
let ivs_bytes = build_single_region_ivs(-30);
let ivs = ItemVariationStore::parse(&ivs_bytes).unwrap();
let v_default = g.apply_lookup_type_1_var(0, 7, Some(&ivs), &[0.0]).unwrap();
assert_eq!(v_default.x_advance, 100);
let v_max = g.apply_lookup_type_1_var(0, 7, Some(&ivs), &[1.0]).unwrap();
assert_eq!(v_max.x_advance, 70);
let v_half = g.apply_lookup_type_1_var(0, 7, Some(&ivs), &[0.5]).unwrap();
assert_eq!(v_half.x_advance, 85);
let v_no_ivs = g.apply_lookup_type_1_var(0, 7, None, &[1.0]).unwrap();
assert_eq!(v_no_ivs.x_advance, 100);
let v_static = g.apply_lookup_type_1(0, 7).unwrap();
assert_eq!(v_static.x_advance, 100);
}
#[test]
fn single_pos_var_matches_plain_when_no_device_offsets() {
let bytes = build_single_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let v_plain = g.apply_lookup_type_1(0, 7).unwrap();
let v_var = g.apply_lookup_type_1_var(0, 7, None, &[0.5]).unwrap();
assert_eq!(v_plain, v_var);
assert_eq!(v_var.x_advance, -150);
}
#[test]
fn single_pos_returns_none_when_lookup_is_not_type_1() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_1(0, 50), None);
}
fn build_chain_context_pos_format1() -> Vec<u8> {
let mut cov0 = Vec::new();
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&10u16.to_be_bytes());
let mut sub0 = Vec::new();
sub0.extend_from_slice(&1u16.to_be_bytes()); sub0.extend_from_slice(&8u16.to_be_bytes()); sub0.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub0.extend_from_slice(&50i16.to_be_bytes());
sub0.extend_from_slice(&cov0);
let lookup0 = wrap_lookup(LOOKUP_SINGLE_POS, &sub0);
let mut cov_in = Vec::new();
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&10u16.to_be_bytes());
let mut rule = Vec::new();
rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&99u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes()); let rule_set_header_len = 4u16;
let mut rule_set = Vec::new();
rule_set.extend_from_slice(&1u16.to_be_bytes());
rule_set.extend_from_slice(&rule_set_header_len.to_be_bytes());
rule_set.extend_from_slice(&rule);
let header_len = 8u16;
let cov_off = header_len;
let set_off = cov_off + cov_in.len() as u16;
let mut sub1 = Vec::new();
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&cov_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&set_off.to_be_bytes());
sub1.extend_from_slice(&cov_in);
sub1.extend_from_slice(&rule_set);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_POS, &sub1);
let lookup_list_header_len = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lookup0.len() as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
lookup_list.extend_from_slice(&lookup0);
lookup_list.extend_from_slice(&lookup1);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn chain_context_pos_format1_dispatches_nested_single_pos() {
let bytes = build_chain_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_8(1, &[1, 10, 99], 1).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn chain_context_pos_format1_no_match_when_backtrack_or_lookahead_misses() {
let bytes = build_chain_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(1, &[2, 10, 99], 1), None);
assert_eq!(g.apply_lookup_type_8(1, &[10, 99], 0), None);
assert_eq!(g.apply_lookup_type_8(1, &[1, 10, 50], 1), None);
assert_eq!(g.apply_lookup_type_8(99, &[1, 10, 99], 1), None);
}
fn build_chain_context_pos_format3() -> Vec<u8> {
let mut cov_lookup0 = Vec::new();
cov_lookup0.extend_from_slice(&1u16.to_be_bytes());
cov_lookup0.extend_from_slice(&1u16.to_be_bytes());
cov_lookup0.extend_from_slice(&10u16.to_be_bytes());
let mut sub0 = Vec::new();
sub0.extend_from_slice(&1u16.to_be_bytes());
sub0.extend_from_slice(&8u16.to_be_bytes());
sub0.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub0.extend_from_slice(&50i16.to_be_bytes());
sub0.extend_from_slice(&cov_lookup0);
let lookup0 = wrap_lookup(LOOKUP_SINGLE_POS, &sub0);
let mut cov_bt = Vec::new();
cov_bt.extend_from_slice(&1u16.to_be_bytes());
cov_bt.extend_from_slice(&1u16.to_be_bytes());
cov_bt.extend_from_slice(&1u16.to_be_bytes());
let mut cov_in = Vec::new();
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&1u16.to_be_bytes());
cov_in.extend_from_slice(&10u16.to_be_bytes());
let mut cov_la = Vec::new();
cov_la.extend_from_slice(&1u16.to_be_bytes());
cov_la.extend_from_slice(&1u16.to_be_bytes());
cov_la.extend_from_slice(&99u16.to_be_bytes());
let header_len: u16 = 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4;
let bt_off = header_len;
let in_off = bt_off + cov_bt.len() as u16;
let la_off = in_off + cov_in.len() as u16;
let mut sub1 = Vec::new();
sub1.extend_from_slice(&3u16.to_be_bytes()); sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&bt_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&in_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&la_off.to_be_bytes());
sub1.extend_from_slice(&1u16.to_be_bytes()); sub1.extend_from_slice(&0u16.to_be_bytes()); sub1.extend_from_slice(&0u16.to_be_bytes()); sub1.extend_from_slice(&cov_bt);
sub1.extend_from_slice(&cov_in);
sub1.extend_from_slice(&cov_la);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_POS, &sub1);
let lookup_list_header_len = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lookup0.len() as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
lookup_list.extend_from_slice(&lookup0);
lookup_list.extend_from_slice(&lookup1);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn chain_context_pos_format3_coverage_based_dispatch() {
let bytes = build_chain_context_pos_format3();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_8(1, &[1, 10, 99], 1).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn chain_context_pos_format3_no_match_when_window_short_or_uncovered() {
let bytes = build_chain_context_pos_format3();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(1, &[10, 99], 0), None);
assert_eq!(g.apply_lookup_type_8(1, &[1, 10], 1), None);
assert_eq!(g.apply_lookup_type_8(1, &[1, 10, 12], 1), None);
}
fn build_chain_context_pos_format2() -> Vec<u8> {
let mut in_cd = Vec::new();
in_cd.extend_from_slice(&2u16.to_be_bytes());
in_cd.extend_from_slice(&1u16.to_be_bytes()); in_cd.extend_from_slice(&10u16.to_be_bytes()); in_cd.extend_from_slice(&11u16.to_be_bytes()); in_cd.extend_from_slice(&1u16.to_be_bytes());
let mut bt_cd = Vec::new();
bt_cd.extend_from_slice(&2u16.to_be_bytes());
bt_cd.extend_from_slice(&1u16.to_be_bytes());
bt_cd.extend_from_slice(&1u16.to_be_bytes());
bt_cd.extend_from_slice(&2u16.to_be_bytes());
bt_cd.extend_from_slice(&1u16.to_be_bytes());
let mut la_cd = Vec::new();
la_cd.extend_from_slice(&2u16.to_be_bytes());
la_cd.extend_from_slice(&1u16.to_be_bytes());
la_cd.extend_from_slice(&99u16.to_be_bytes());
la_cd.extend_from_slice(&99u16.to_be_bytes());
la_cd.extend_from_slice(&1u16.to_be_bytes());
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&10u16.to_be_bytes());
let mut rule = Vec::new();
rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes());
let mut set = Vec::new();
set.extend_from_slice(&1u16.to_be_bytes()); set.extend_from_slice(&4u16.to_be_bytes()); set.extend_from_slice(&rule);
let set_count = 2u16;
let header_len = 12 + (set_count as usize) * 2;
let cov_off = header_len as u16;
let bt_cd_off = cov_off + cov.len() as u16;
let in_cd_off = bt_cd_off + bt_cd.len() as u16;
let la_cd_off = in_cd_off + in_cd.len() as u16;
let set_off = la_cd_off + la_cd.len() as u16;
let mut sub1 = Vec::new();
sub1.extend_from_slice(&2u16.to_be_bytes()); sub1.extend_from_slice(&cov_off.to_be_bytes());
sub1.extend_from_slice(&bt_cd_off.to_be_bytes());
sub1.extend_from_slice(&in_cd_off.to_be_bytes());
sub1.extend_from_slice(&la_cd_off.to_be_bytes());
sub1.extend_from_slice(&set_count.to_be_bytes());
sub1.extend_from_slice(&0u16.to_be_bytes());
sub1.extend_from_slice(&set_off.to_be_bytes());
sub1.extend_from_slice(&cov);
sub1.extend_from_slice(&bt_cd);
sub1.extend_from_slice(&in_cd);
sub1.extend_from_slice(&la_cd);
sub1.extend_from_slice(&set);
let mut cov0 = Vec::new();
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&1u16.to_be_bytes());
cov0.extend_from_slice(&10u16.to_be_bytes());
let mut sub0 = Vec::new();
sub0.extend_from_slice(&1u16.to_be_bytes());
sub0.extend_from_slice(&8u16.to_be_bytes());
sub0.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub0.extend_from_slice(&50i16.to_be_bytes());
sub0.extend_from_slice(&cov0);
let lookup0 = wrap_lookup(LOOKUP_SINGLE_POS, &sub0);
let lookup1 = wrap_lookup(LOOKUP_CHAIN_CONTEXT_POS, &sub1);
let lookup_list_header_len = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lookup0.len() as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
lookup_list.extend_from_slice(&lookup0);
lookup_list.extend_from_slice(&lookup1);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&0u16.to_be_bytes());
gpos.extend_from_slice(&10u16.to_be_bytes());
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn chain_context_pos_format2_class_based_dispatch() {
let bytes = build_chain_context_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_8(1, &[1, 10, 99], 1).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn chain_context_pos_format2_no_match_when_class_differs() {
let bytes = build_chain_context_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_8(1, &[5, 10, 99], 1), None);
}
#[test]
fn lookup_list_reports_index_type_and_subtable_count() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, 2u16, 1u16)]);
let bytes = build_simple_mark_base();
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, 4u16, 1u16)]);
let bytes = build_chain_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, 1u16, 1u16), (1u16, 8u16, 1u16)]);
}
#[test]
fn value_record_size_packs_low_byte_only() {
assert_eq!(value_record_size(VF_X_PLACEMENT | VF_Y_PLACEMENT), 4);
let all = VF_X_PLACEMENT
| VF_Y_PLACEMENT
| VF_X_ADVANCE
| VF_Y_ADVANCE
| VF_X_PLA_DEVICE
| VF_Y_PLA_DEVICE
| VF_X_ADV_DEVICE
| VF_Y_ADV_DEVICE;
assert_eq!(value_record_size(all), 16);
assert_eq!(value_record_size(0), 0);
}
fn build_cursive_pos_format1() -> Vec<u8> {
fn anchor(x: i16, y: i16) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&1u16.to_be_bytes());
v.extend_from_slice(&x.to_be_bytes());
v.extend_from_slice(&y.to_be_bytes());
v
}
let a5_entry = anchor(50, 100);
let a6_entry = anchor(60, 100);
let a6_exit = anchor(120, 200);
let a7_exit = anchor(140, 200);
let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes()); cov.extend_from_slice(&3u16.to_be_bytes()); cov.extend_from_slice(&5u16.to_be_bytes());
cov.extend_from_slice(&6u16.to_be_bytes());
cov.extend_from_slice(&7u16.to_be_bytes());
let header_len = 6 + 3 * 4;
let cov_off = (header_len + 4 * 6) as u16; let a5_entry_off = header_len as u16;
let a6_entry_off = a5_entry_off + 6;
let a6_exit_off = a6_entry_off + 6;
let a7_exit_off = a6_exit_off + 6;
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&cov_off.to_be_bytes());
sub.extend_from_slice(&3u16.to_be_bytes()); sub.extend_from_slice(&a5_entry_off.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&a6_entry_off.to_be_bytes());
sub.extend_from_slice(&a6_exit_off.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&a7_exit_off.to_be_bytes());
sub.extend_from_slice(&a5_entry);
sub.extend_from_slice(&a6_entry);
sub.extend_from_slice(&a6_exit);
sub.extend_from_slice(&a7_exit);
sub.extend_from_slice(&cov);
let lookup = wrap_lookup(LOOKUP_CURSIVE_POS, &sub);
wrap_gpos_single(&lookup)
}
#[test]
fn cursive_pos_format1_returns_entry_and_exit_anchors() {
let bytes = build_cursive_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let r5 = g.apply_lookup_type_3(0, 5).unwrap();
assert_eq!(r5.entry, Some((50, 100)));
assert_eq!(r5.exit, None);
let r6 = g.apply_lookup_type_3(0, 6).unwrap();
assert_eq!(r6.entry, Some((60, 100)));
assert_eq!(r6.exit, Some((120, 200)));
let r7 = g.apply_lookup_type_3(0, 7).unwrap();
assert_eq!(r7.entry, None);
assert_eq!(r7.exit, Some((140, 200)));
}
#[test]
fn cursive_pos_returns_none_off_coverage() {
let bytes = build_cursive_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_3(0, 4), None);
assert_eq!(g.apply_lookup_type_3(0, 8), None);
}
#[test]
fn cursive_pos_returns_none_when_lookup_is_not_type_3() {
let bytes = build_simple_pp1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_3(0, 5), None);
}
#[test]
fn lookup_cursive_attachment_walks_lookup_list() {
let bytes = build_cursive_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let r6 = g.lookup_cursive_attachment(6).unwrap();
assert_eq!(r6.entry, Some((60, 100)));
assert_eq!(r6.exit, Some((120, 200)));
assert_eq!(g.lookup_cursive_attachment(99), None);
}
fn build_mark_ligature_pos_format1() -> Vec<u8> {
fn anchor(x: i16, y: i16) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&1u16.to_be_bytes());
v.extend_from_slice(&x.to_be_bytes());
v.extend_from_slice(&y.to_be_bytes());
v
}
let mark_anchor = anchor(10, 0);
let mut mark_array = Vec::new();
mark_array.extend_from_slice(&1u16.to_be_bytes()); mark_array.extend_from_slice(&0u16.to_be_bytes()); mark_array.extend_from_slice(&6u16.to_be_bytes()); mark_array.extend_from_slice(&mark_anchor);
let comp0_anchor = anchor(300, 800);
let comp1_anchor = anchor(500, 850);
let mut lig_attach = Vec::new();
lig_attach.extend_from_slice(&2u16.to_be_bytes()); lig_attach.extend_from_slice(&6u16.to_be_bytes()); lig_attach.extend_from_slice(&12u16.to_be_bytes()); lig_attach.extend_from_slice(&comp0_anchor);
lig_attach.extend_from_slice(&comp1_anchor);
let mut lig_array = Vec::new();
lig_array.extend_from_slice(&1u16.to_be_bytes()); lig_array.extend_from_slice(&4u16.to_be_bytes()); lig_array.extend_from_slice(&lig_attach);
let mut mark_cov = Vec::new();
mark_cov.extend_from_slice(&1u16.to_be_bytes());
mark_cov.extend_from_slice(&1u16.to_be_bytes());
mark_cov.extend_from_slice(&200u16.to_be_bytes());
let mut lig_cov = Vec::new();
lig_cov.extend_from_slice(&1u16.to_be_bytes());
lig_cov.extend_from_slice(&1u16.to_be_bytes());
lig_cov.extend_from_slice(&100u16.to_be_bytes());
let header = 12usize;
let mark_cov_off = header;
let lig_cov_off = mark_cov_off + mark_cov.len();
let mark_array_off = lig_cov_off + lig_cov.len();
let lig_array_off = mark_array_off + mark_array.len();
let mut mlp = Vec::new();
mlp.extend_from_slice(&1u16.to_be_bytes()); mlp.extend_from_slice(&(mark_cov_off as u16).to_be_bytes());
mlp.extend_from_slice(&(lig_cov_off as u16).to_be_bytes());
mlp.extend_from_slice(&1u16.to_be_bytes()); mlp.extend_from_slice(&(mark_array_off as u16).to_be_bytes());
mlp.extend_from_slice(&(lig_array_off as u16).to_be_bytes());
mlp.extend_from_slice(&mark_cov);
mlp.extend_from_slice(&lig_cov);
mlp.extend_from_slice(&mark_array);
mlp.extend_from_slice(&lig_array);
let lookup = wrap_lookup(LOOKUP_MARK_LIGATURE_POS, &mlp);
wrap_gpos_single(&lookup)
}
#[test]
fn mark_to_ligature_attaches_to_each_component() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_5(0, 100, 0, 200), Some((290, 800)));
assert_eq!(g.apply_lookup_type_5(0, 100, 1, 200), Some((490, 850)));
}
#[test]
fn mark_to_ligature_returns_none_for_out_of_range_component() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_5(0, 100, 2, 200), None);
}
#[test]
fn mark_to_ligature_returns_none_for_uncovered_glyphs() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_5(0, 101, 0, 200), None);
assert_eq!(g.apply_lookup_type_5(0, 100, 0, 201), None);
}
#[test]
fn lookup_mark_to_ligature_walks_lookup_list() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_mark_to_ligature(100, 1, 200), Some((490, 850)));
assert_eq!(g.lookup_mark_to_ligature(99, 0, 200), None);
}
#[test]
fn lookup_mark_to_ligature_var_matches_static_for_format1_anchors() {
let bytes = build_mark_ligature_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(
g.lookup_mark_to_ligature(100, 1, 200),
g.lookup_mark_to_ligature_var(100, 1, 200, None, &[0.9])
);
}
fn build_extension_wrapped_lookup(inner_type: u16, inner_sub: &[u8]) -> Vec<u8> {
let mut ext_sub = Vec::new();
ext_sub.extend_from_slice(&1u16.to_be_bytes()); ext_sub.extend_from_slice(&inner_type.to_be_bytes()); ext_sub.extend_from_slice(&8u32.to_be_bytes()); ext_sub.extend_from_slice(inner_sub);
let lookup = wrap_lookup(LOOKUP_EXTENSION_POS, &ext_sub);
wrap_gpos_single(&lookup)
}
#[test]
fn extension_wrapper_unwraps_for_cursive_pos_lookup() {
let mut cursive_sub = Vec::new();
cursive_sub.extend_from_slice(&1u16.to_be_bytes()); cursive_sub.extend_from_slice(&22u16.to_be_bytes()); cursive_sub.extend_from_slice(&1u16.to_be_bytes()); cursive_sub.extend_from_slice(&10u16.to_be_bytes()); cursive_sub.extend_from_slice(&16u16.to_be_bytes()); cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&70i16.to_be_bytes());
cursive_sub.extend_from_slice(&110i16.to_be_bytes());
cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&130i16.to_be_bytes());
cursive_sub.extend_from_slice(&210i16.to_be_bytes());
cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&1u16.to_be_bytes());
cursive_sub.extend_from_slice(&6u16.to_be_bytes());
let bytes = build_extension_wrapped_lookup(LOOKUP_CURSIVE_POS, &cursive_sub);
let g = GposTable::parse(&bytes).unwrap();
let v: Vec<_> = g.lookup_list().collect();
assert_eq!(v, vec![(0u16, LOOKUP_CURSIVE_POS, 1u16)]);
let r = g.apply_lookup_type_3(0, 6).unwrap();
assert_eq!(r.entry, Some((70, 110)));
assert_eq!(r.exit, Some((130, 210)));
}
fn single_pos_sub(gid: u16, adv: i16) -> 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(&gid.to_be_bytes());
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&8u16.to_be_bytes()); sub.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub.extend_from_slice(&adv.to_be_bytes());
sub.extend_from_slice(&cov);
sub
}
fn assemble_context_gpos(single_sub: &[u8], ctx_sub: &[u8]) -> Vec<u8> {
let lookup0 = wrap_lookup(LOOKUP_SINGLE_POS, single_sub);
let lookup1 = wrap_lookup(LOOKUP_CONTEXT_POS, ctx_sub);
let lookup_list_header_len = 2 + 2 * 2;
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&2u16.to_be_bytes());
let mut running = lookup_list_header_len as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
running += lookup0.len() as u16;
lookup_list.extend_from_slice(&running.to_be_bytes());
lookup_list.extend_from_slice(&lookup0);
lookup_list.extend_from_slice(&lookup1);
let mut gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes()); gpos.extend_from_slice(&0u16.to_be_bytes()); gpos.extend_from_slice(&0u16.to_be_bytes()); gpos.extend_from_slice(&0u16.to_be_bytes()); gpos.extend_from_slice(&10u16.to_be_bytes()); gpos.extend_from_slice(&lookup_list);
gpos
}
fn build_context_pos_format1() -> Vec<u8> {
let single_sub = single_pos_sub(20, 50);
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(&2u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&20u16.to_be_bytes()); rule.extend_from_slice(&1u16.to_be_bytes()); rule.extend_from_slice(&0u16.to_be_bytes()); let mut set = Vec::new();
set.extend_from_slice(&1u16.to_be_bytes());
set.extend_from_slice(&4u16.to_be_bytes());
set.extend_from_slice(&rule);
let 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(&set);
assemble_context_gpos(&single_sub, &sub)
}
#[test]
fn context_pos_format1_dispatches_nested_single_pos() {
let bytes = build_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_7(1, &[10, 20], 0).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn context_pos_format1_no_match() {
let bytes = build_context_pos_format1();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_7(1, &[10, 21], 0), None);
assert_eq!(g.apply_lookup_type_7(1, &[11, 20], 0), None);
assert_eq!(g.apply_lookup_type_7(1, &[10], 0), None);
assert_eq!(g.apply_lookup_type_7(99, &[10, 20], 0), None);
assert_eq!(g.apply_lookup_type_7(0, &[10, 20], 0), None);
}
fn build_context_pos_format2() -> Vec<u8> {
let single_sub = single_pos_sub(20, 50);
let mut cd = Vec::new();
cd.extend_from_slice(&2u16.to_be_bytes()); cd.extend_from_slice(&2u16.to_be_bytes()); cd.extend_from_slice(&10u16.to_be_bytes()); cd.extend_from_slice(&10u16.to_be_bytes()); cd.extend_from_slice(&1u16.to_be_bytes()); cd.extend_from_slice(&20u16.to_be_bytes());
cd.extend_from_slice(&20u16.to_be_bytes());
cd.extend_from_slice(&2u16.to_be_bytes()); let mut cov = Vec::new();
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&1u16.to_be_bytes());
cov.extend_from_slice(&10u16.to_be_bytes());
let mut rule = Vec::new();
rule.extend_from_slice(&2u16.to_be_bytes());
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(&0u16.to_be_bytes()); let mut set = Vec::new();
set.extend_from_slice(&1u16.to_be_bytes());
set.extend_from_slice(&4u16.to_be_bytes());
set.extend_from_slice(&rule);
let set_count = 2u16;
let header_len = 8 + (set_count as usize) * 2;
let cov_off = header_len as u16;
let cd_off = cov_off + cov.len() as u16;
let 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(&set_count.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&set_off.to_be_bytes()); sub.extend_from_slice(&cov);
sub.extend_from_slice(&cd);
sub.extend_from_slice(&set);
assemble_context_gpos(&single_sub, &sub)
}
#[test]
fn context_pos_format2_class_based_dispatch() {
let bytes = build_context_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_7(1, &[10, 20], 0).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn context_pos_format2_no_match_when_class_differs() {
let bytes = build_context_pos_format2();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_7(1, &[10, 30], 0), None);
}
fn build_context_pos_format3() -> Vec<u8> {
let single_sub = single_pos_sub(20, 50);
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 cov1 = Vec::new();
cov1.extend_from_slice(&1u16.to_be_bytes());
cov1.extend_from_slice(&1u16.to_be_bytes());
cov1.extend_from_slice(&20u16.to_be_bytes());
let glyph_count = 2u16;
let header_len = 6 + (glyph_count as usize) * 2 + 4;
let cov0_off = header_len as u16;
let cov1_off = cov0_off + cov0.len() as u16;
let mut sub = Vec::new();
sub.extend_from_slice(&3u16.to_be_bytes()); sub.extend_from_slice(&glyph_count.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&cov0_off.to_be_bytes());
sub.extend_from_slice(&cov1_off.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&cov0);
sub.extend_from_slice(&cov1);
assemble_context_gpos(&single_sub, &sub)
}
#[test]
fn context_pos_format3_coverage_based_dispatch() {
let bytes = build_context_pos_format3();
let g = GposTable::parse(&bytes).unwrap();
let recs = g.apply_lookup_type_7(1, &[10, 20], 0).unwrap();
assert_eq!(recs.len(), 1);
assert_eq!(recs[0].glyph_index, 1);
assert_eq!(recs[0].value.x_advance, 50);
}
#[test]
fn context_pos_format3_no_match_when_window_short_or_uncovered() {
let bytes = build_context_pos_format3();
let g = GposTable::parse(&bytes).unwrap();
assert_eq!(g.apply_lookup_type_7(1, &[10, 21], 0), None);
assert_eq!(g.apply_lookup_type_7(1, &[10], 0), None);
}
fn build_feature_tagged_gpos(version_1_1: bool) -> Vec<u8> {
fn single_pos_lookup(gid: u16, xadv: i16) -> 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(&gid.to_be_bytes());
let mut sub = Vec::new();
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&8u16.to_be_bytes()); sub.extend_from_slice(&VF_X_ADVANCE.to_be_bytes());
sub.extend_from_slice(&xadv.to_be_bytes());
sub.extend_from_slice(&cov);
wrap_lookup(LOOKUP_SINGLE_POS, &sub)
}
let l0 = single_pos_lookup(10, -50);
let l1 = single_pos_lookup(20, -30);
let l2 = single_pos_lookup(30, 40);
let mut lookup_list = Vec::new();
lookup_list.extend_from_slice(&3u16.to_be_bytes());
let off0: u16 = 2 + 3 * 2;
let off1: u16 = off0 + l0.len() as u16;
let off2: u16 = off1 + l1.len() as u16;
lookup_list.extend_from_slice(&off0.to_be_bytes());
lookup_list.extend_from_slice(&off1.to_be_bytes());
lookup_list.extend_from_slice(&off2.to_be_bytes());
lookup_list.extend_from_slice(&l0);
lookup_list.extend_from_slice(&l1);
lookup_list.extend_from_slice(&l2);
fn feature(lookup: 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.to_be_bytes());
f
}
let f0 = feature(0);
let f1 = feature(1);
let f2 = feature(2);
let mut feature_list = Vec::new();
feature_list.extend_from_slice(&3u16.to_be_bytes());
let records_len: u16 = 2 + 3 * 6;
let fo0 = records_len;
let fo1 = fo0 + f0.len() as u16;
let fo2 = fo1 + f1.len() as u16;
feature_list.extend_from_slice(b"kern");
feature_list.extend_from_slice(&fo0.to_be_bytes());
feature_list.extend_from_slice(b"mark");
feature_list.extend_from_slice(&fo1.to_be_bytes());
feature_list.extend_from_slice(b"cpsp");
feature_list.extend_from_slice(&fo2.to_be_bytes());
feature_list.extend_from_slice(&f0);
feature_list.extend_from_slice(&f1);
feature_list.extend_from_slice(&f2);
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(&3u16.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());
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"latn");
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: u16 = if version_1_1 { 14 } else { 10 };
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 gpos = Vec::new();
gpos.extend_from_slice(&1u16.to_be_bytes()); gpos.extend_from_slice(&(version_1_1 as u16).to_be_bytes()); gpos.extend_from_slice(&script_list_off.to_be_bytes());
gpos.extend_from_slice(&feature_list_off.to_be_bytes());
gpos.extend_from_slice(&lookup_list_off.to_be_bytes());
if version_1_1 {
gpos.extend_from_slice(&0u32.to_be_bytes()); }
gpos.extend_from_slice(&script_list);
gpos.extend_from_slice(&feature_list);
gpos.extend_from_slice(&lookup_list);
gpos
}
#[test]
fn gpos_features_for_script_resolves_tags_and_lookup_indices() {
let bytes = build_feature_tagged_gpos(false);
let g = GposTable::parse(&bytes).unwrap();
let feats = g.features_for_script(*b"latn", None);
let tags: Vec<[u8; 4]> = feats.iter().map(|f| f.tag).collect();
assert_eq!(
tags,
vec![*b"kern", *b"mark", *b"cpsp"],
"latn features in declaration order"
);
assert_eq!(feats[0].lookup_indices, vec![0]);
assert_eq!(feats[1].lookup_indices, vec![1]);
assert_eq!(feats[2].lookup_indices, vec![2]);
let adj = g
.apply_lookup_type_1(feats[0].lookup_indices[0], 10)
.unwrap();
assert_eq!(adj.x_advance, -50);
}
#[test]
fn gpos_features_for_script_unknown_script_is_empty() {
let bytes = build_feature_tagged_gpos(false);
let g = GposTable::parse(&bytes).unwrap();
assert!(g.features_for_script(*b"arab", None).is_empty());
}
#[test]
fn gpos_v1_1_header_parses_and_has_no_feature_variations_when_null() {
let bytes = build_feature_tagged_gpos(true);
let g = GposTable::parse(&bytes).unwrap();
assert!(!g.has_feature_variations());
let feats = g.features_for_script(*b"latn", None);
assert_eq!(feats.len(), 3);
let at = g.features_for_script_at_coords(*b"latn", None, &[0.5]);
assert_eq!(at, feats);
}
#[test]
fn gpos_v1_0_header_has_no_feature_variations() {
let bytes = build_feature_tagged_gpos(false);
let g = GposTable::parse(&bytes).unwrap();
assert!(!g.has_feature_variations());
}
}