use crate::parser::{read_i16, read_u16, read_u32};
use crate::tables::gdef::Coverage;
use crate::tables::layout::{FeatureList, LayoutHeader, Lookup, LookupList, Script, ScriptList};
use crate::Error;
pub const GSUB_LOOKUP_TYPE_SINGLE: u16 = 1;
pub const GSUB_LOOKUP_TYPE_MULTIPLE: u16 = 2;
pub const GSUB_LOOKUP_TYPE_ALTERNATE: u16 = 3;
pub const GSUB_LOOKUP_TYPE_LIGATURE: u16 = 4;
pub const GSUB_LOOKUP_TYPE_CONTEXT: u16 = 5;
pub const GSUB_LOOKUP_TYPE_CHAINED_CONTEXT: u16 = 6;
pub const GSUB_LOOKUP_TYPE_EXTENSION: u16 = 7;
pub const GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE: u16 = 8;
#[derive(Debug, Clone, Copy)]
pub struct SingleSubst<'a> {
inner: SingleSubstInner<'a>,
}
#[derive(Debug, Clone, Copy)]
enum SingleSubstInner<'a> {
Format1 { coverage: Coverage<'a>, delta: i16 },
Format2 {
coverage: Coverage<'a>,
substitutes: &'a [u8],
},
}
impl<'a> SingleSubst<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
let cov_off = read_u16(bytes, 2)? as usize;
if cov_off == 0 || cov_off >= bytes.len() {
return Err(Error::BadStructure(
"GSUB/SingleSubst: coverageOffset out of range",
));
}
let coverage = Coverage::parse(&bytes[cov_off..])?;
match format {
1 => {
let delta = read_i16(bytes, 4)?;
Ok(Self {
inner: SingleSubstInner::Format1 { coverage, delta },
})
}
2 => {
let glyph_count = read_u16(bytes, 4)? as usize;
if glyph_count != coverage.len() {
return Err(Error::BadStructure(
"GSUB/SingleSubstFormat2: glyphCount != coverage.len()",
));
}
let array_start = 6usize;
let need = array_start
.checked_add(glyph_count.checked_mul(2).ok_or(Error::BadStructure(
"GSUB/SingleSubstFormat2 length overflow",
))?)
.ok_or(Error::BadStructure(
"GSUB/SingleSubstFormat2 length overflow",
))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
inner: SingleSubstInner::Format2 {
coverage,
substitutes: &bytes[array_start..need],
},
})
}
_ => Err(Error::BadStructure(
"GSUB/SingleSubst: unknown subtable format",
)),
}
}
pub fn format(&self) -> u16 {
match self.inner {
SingleSubstInner::Format1 { .. } => 1,
SingleSubstInner::Format2 { .. } => 2,
}
}
pub fn coverage(&self) -> Coverage<'a> {
match self.inner {
SingleSubstInner::Format1 { coverage, .. } => coverage,
SingleSubstInner::Format2 { coverage, .. } => coverage,
}
}
pub fn delta_glyph_id(&self) -> Option<i16> {
match self.inner {
SingleSubstInner::Format1 { delta, .. } => Some(delta),
SingleSubstInner::Format2 { .. } => None,
}
}
pub fn glyph_count(&self) -> Option<u16> {
match self.inner {
SingleSubstInner::Format1 { .. } => None,
SingleSubstInner::Format2 { substitutes, .. } => Some((substitutes.len() / 2) as u16),
}
}
pub fn substitute(&self, input: u16) -> Option<u16> {
match self.inner {
SingleSubstInner::Format1 { coverage, delta } => {
coverage.index_of(input)?;
let sum = (input as i32 + delta as i32).rem_euclid(65536);
Some(sum as u16)
}
SingleSubstInner::Format2 {
coverage,
substitutes,
} => {
let idx = coverage.index_of(input)? as usize;
let off = idx.checked_mul(2).filter(|&o| o + 2 <= substitutes.len())?;
Some(u16::from_be_bytes([substitutes[off], substitutes[off + 1]]))
}
}
}
pub fn iter(&self) -> SingleSubstIter<'a> {
SingleSubstIter {
cov: self.coverage().iter(),
sub: self.inner,
}
}
}
#[derive(Debug, Clone)]
pub struct SingleSubstIter<'a> {
cov: crate::tables::gdef::CoverageIter<'a>,
sub: SingleSubstInner<'a>,
}
impl<'a> Iterator for SingleSubstIter<'a> {
type Item = (u16, u16);
fn next(&mut self) -> Option<Self::Item> {
let (g, idx) = self.cov.next()?;
let out = match self.sub {
SingleSubstInner::Format1 { delta, .. } => {
((g as i32 + delta as i32).rem_euclid(65536)) as u16
}
SingleSubstInner::Format2 { substitutes, .. } => {
let off = (idx as usize).checked_mul(2)?;
if off + 2 > substitutes.len() {
return None;
}
u16::from_be_bytes([substitutes[off], substitutes[off + 1]])
}
};
Some((g, out))
}
}
#[derive(Debug, Clone, Copy)]
pub struct MultipleSubst<'a> {
bytes: &'a [u8],
coverage: Coverage<'a>,
seq_offsets: &'a [u8],
}
impl<'a> MultipleSubst<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
if format != 1 {
return Err(Error::BadStructure(
"GSUB/MultipleSubst: unknown subtable format",
));
}
let cov_off = read_u16(bytes, 2)? as usize;
if cov_off == 0 || cov_off >= bytes.len() {
return Err(Error::BadStructure(
"GSUB/MultipleSubst: coverageOffset out of range",
));
}
let coverage = Coverage::parse(&bytes[cov_off..])?;
let seq_count = read_u16(bytes, 4)? as usize;
if seq_count != coverage.len() {
return Err(Error::BadStructure(
"GSUB/MultipleSubst: sequenceCount != coverage.len()",
));
}
let array_start = 6usize;
let need = array_start
.checked_add(
seq_count
.checked_mul(2)
.ok_or(Error::BadStructure("GSUB/MultipleSubst length overflow"))?,
)
.ok_or(Error::BadStructure("GSUB/MultipleSubst length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
coverage,
seq_offsets: &bytes[array_start..need],
})
}
pub fn format(&self) -> u16 {
1
}
pub fn coverage(&self) -> Coverage<'a> {
self.coverage
}
pub fn sequence_count(&self) -> u16 {
(self.seq_offsets.len() / 2) as u16
}
pub fn sequence(&self, seq_i: u16) -> Option<Result<Sequence<'a>, Error>> {
let off2 = (seq_i as usize).checked_mul(2)?;
if off2 + 2 > self.seq_offsets.len() {
return None;
}
let off = u16::from_be_bytes([self.seq_offsets[off2], self.seq_offsets[off2 + 1]]) as usize;
if off == 0 || off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"GSUB/MultipleSubst: sequenceOffset out of range",
)));
}
Some(Sequence::parse(&self.bytes[off..]))
}
pub fn substitute(&self, input: u16) -> Option<Sequence<'a>> {
let i = self.coverage.index_of(input)?;
self.sequence(i)?.ok()
}
pub fn iter(&self) -> MultipleSubstIter<'a> {
MultipleSubstIter {
cov: self.coverage.iter(),
outer: *self,
}
}
}
#[derive(Debug, Clone)]
pub struct MultipleSubstIter<'a> {
cov: crate::tables::gdef::CoverageIter<'a>,
outer: MultipleSubst<'a>,
}
impl<'a> Iterator for MultipleSubstIter<'a> {
type Item = (u16, Result<Sequence<'a>, Error>);
fn next(&mut self) -> Option<Self::Item> {
let (g, idx) = self.cov.next()?;
let seq = self.outer.sequence(idx)?;
Some((g, seq))
}
}
#[derive(Debug, Clone, Copy)]
pub struct Sequence<'a> {
glyphs: &'a [u8],
}
impl<'a> Sequence<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let count = read_u16(bytes, 0)? as usize;
if count == 0 {
return Err(Error::BadStructure(
"GSUB/Sequence: glyphCount must be >= 1",
));
}
let array_start = 2usize;
let need = array_start
.checked_add(
count
.checked_mul(2)
.ok_or(Error::BadStructure("GSUB/Sequence length overflow"))?,
)
.ok_or(Error::BadStructure("GSUB/Sequence length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
glyphs: &bytes[array_start..need],
})
}
pub fn glyph_count(&self) -> u16 {
(self.glyphs.len() / 2) as u16
}
pub fn glyph(&self, i: u16) -> Option<u16> {
let off = (i as usize).checked_mul(2)?;
if off + 2 > self.glyphs.len() {
return None;
}
Some(u16::from_be_bytes([self.glyphs[off], self.glyphs[off + 1]]))
}
pub fn glyphs(&self) -> SequenceGlyphIter<'a> {
SequenceGlyphIter {
bytes: self.glyphs,
pos: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SequenceGlyphIter<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Iterator for SequenceGlyphIter<'a> {
type Item = u16;
fn next(&mut self) -> Option<Self::Item> {
if self.pos + 2 > self.bytes.len() {
return None;
}
let g = u16::from_be_bytes([self.bytes[self.pos], self.bytes[self.pos + 1]]);
self.pos += 2;
Some(g)
}
}
#[derive(Debug, Clone, Copy)]
pub struct AlternateSubst<'a> {
bytes: &'a [u8],
coverage: Coverage<'a>,
set_offsets: &'a [u8],
}
impl<'a> AlternateSubst<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
if format != 1 {
return Err(Error::BadStructure(
"GSUB/AlternateSubst: unknown subtable format",
));
}
let cov_off = read_u16(bytes, 2)? as usize;
if cov_off == 0 || cov_off >= bytes.len() {
return Err(Error::BadStructure(
"GSUB/AlternateSubst: coverageOffset out of range",
));
}
let coverage = Coverage::parse(&bytes[cov_off..])?;
let set_count = read_u16(bytes, 4)? as usize;
if set_count != coverage.len() {
return Err(Error::BadStructure(
"GSUB/AlternateSubst: alternateSetCount != coverage.len()",
));
}
let array_start = 6usize;
let need = array_start
.checked_add(
set_count
.checked_mul(2)
.ok_or(Error::BadStructure("GSUB/AlternateSubst length overflow"))?,
)
.ok_or(Error::BadStructure("GSUB/AlternateSubst length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
coverage,
set_offsets: &bytes[array_start..need],
})
}
pub fn format(&self) -> u16 {
1
}
pub fn coverage(&self) -> Coverage<'a> {
self.coverage
}
pub fn alternate_set_count(&self) -> u16 {
(self.set_offsets.len() / 2) as u16
}
pub fn alternate_set(&self, set_i: u16) -> Option<Result<AlternateSet<'a>, Error>> {
let off2 = (set_i as usize).checked_mul(2)?;
if off2 + 2 > self.set_offsets.len() {
return None;
}
let off = u16::from_be_bytes([self.set_offsets[off2], self.set_offsets[off2 + 1]]) as usize;
if off == 0 || off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"GSUB/AlternateSubst: alternateSetOffset out of range",
)));
}
Some(AlternateSet::parse(&self.bytes[off..]))
}
pub fn substitute(&self, input: u16) -> Option<AlternateSet<'a>> {
let i = self.coverage.index_of(input)?;
self.alternate_set(i)?.ok()
}
pub fn iter(&self) -> AlternateSubstIter<'a> {
AlternateSubstIter {
cov: self.coverage.iter(),
outer: *self,
}
}
}
#[derive(Debug, Clone)]
pub struct AlternateSubstIter<'a> {
cov: crate::tables::gdef::CoverageIter<'a>,
outer: AlternateSubst<'a>,
}
impl<'a> Iterator for AlternateSubstIter<'a> {
type Item = (u16, Result<AlternateSet<'a>, Error>);
fn next(&mut self) -> Option<Self::Item> {
let (g, idx) = self.cov.next()?;
let set = self.outer.alternate_set(idx)?;
Some((g, set))
}
}
#[derive(Debug, Clone, Copy)]
pub struct AlternateSet<'a> {
glyphs: &'a [u8],
}
impl<'a> AlternateSet<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let count = read_u16(bytes, 0)? as usize;
let array_start = 2usize;
let need = array_start
.checked_add(
count
.checked_mul(2)
.ok_or(Error::BadStructure("GSUB/AlternateSet length overflow"))?,
)
.ok_or(Error::BadStructure("GSUB/AlternateSet length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
glyphs: &bytes[array_start..need],
})
}
pub fn glyph_count(&self) -> u16 {
(self.glyphs.len() / 2) as u16
}
pub fn glyph(&self, i: u16) -> Option<u16> {
let off = (i as usize).checked_mul(2)?;
if off + 2 > self.glyphs.len() {
return None;
}
Some(u16::from_be_bytes([self.glyphs[off], self.glyphs[off + 1]]))
}
pub fn glyphs(&self) -> AlternateGlyphIter<'a> {
AlternateGlyphIter {
bytes: self.glyphs,
pos: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct AlternateGlyphIter<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Iterator for AlternateGlyphIter<'a> {
type Item = u16;
fn next(&mut self) -> Option<Self::Item> {
if self.pos + 2 > self.bytes.len() {
return None;
}
let g = u16::from_be_bytes([self.bytes[self.pos], self.bytes[self.pos + 1]]);
self.pos += 2;
Some(g)
}
}
#[derive(Debug, Clone, Copy)]
pub struct LigatureSubst<'a> {
bytes: &'a [u8],
coverage: Coverage<'a>,
set_offsets: &'a [u8],
}
impl<'a> LigatureSubst<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
if format != 1 {
return Err(Error::BadStructure(
"GSUB/LigatureSubst: unknown subtable format",
));
}
let cov_off = read_u16(bytes, 2)? as usize;
if cov_off == 0 || cov_off >= bytes.len() {
return Err(Error::BadStructure(
"GSUB/LigatureSubst: coverageOffset out of range",
));
}
let coverage = Coverage::parse(&bytes[cov_off..])?;
let set_count = read_u16(bytes, 4)? as usize;
let array_start = 6usize;
let need = array_start
.checked_add(
set_count
.checked_mul(2)
.ok_or(Error::BadStructure("GSUB/LigatureSubst length overflow"))?,
)
.ok_or(Error::BadStructure("GSUB/LigatureSubst length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
coverage,
set_offsets: &bytes[array_start..need],
})
}
pub fn format(&self) -> u16 {
1
}
pub fn coverage(&self) -> Coverage<'a> {
self.coverage
}
pub fn ligature_set_count(&self) -> u16 {
(self.set_offsets.len() / 2) as u16
}
pub fn ligature_set(&self, set_i: u16) -> Option<Result<LigatureSet<'a>, Error>> {
let off2 = (set_i as usize).checked_mul(2)?;
if off2 + 2 > self.set_offsets.len() {
return None;
}
let off = u16::from_be_bytes([self.set_offsets[off2], self.set_offsets[off2 + 1]]) as usize;
if off == 0 || off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"GSUB/LigatureSubst: ligatureSetOffset out of range",
)));
}
Some(LigatureSet::parse(&self.bytes[off..]))
}
pub fn substitute(&self, input: &[u16]) -> Option<(u16, u16)> {
let first = *input.first()?;
let set_i = self.coverage.index_of(first)?;
let set = self.ligature_set(set_i)?.ok()?;
for j in 0..set.ligature_count() {
let lig = set.ligature(j)?.ok()?;
let comp_count = lig.component_count() as usize;
if comp_count == 0 {
continue;
}
if comp_count > input.len() {
continue;
}
let mut ok = true;
for k in 0..(comp_count - 1) {
let want = lig.component_glyph(k as u16)?;
if want != input[k + 1] {
ok = false;
break;
}
}
if ok {
return Some((lig.ligature_glyph(), comp_count as u16));
}
}
None
}
pub fn iter(&self) -> LigatureSubstIter<'a> {
LigatureSubstIter {
cov: self.coverage.iter(),
outer: *self,
}
}
}
#[derive(Debug, Clone)]
pub struct LigatureSubstIter<'a> {
cov: crate::tables::gdef::CoverageIter<'a>,
outer: LigatureSubst<'a>,
}
impl<'a> Iterator for LigatureSubstIter<'a> {
type Item = (u16, Result<LigatureSet<'a>, Error>);
fn next(&mut self) -> Option<Self::Item> {
let (g, idx) = self.cov.next()?;
let set = self.outer.ligature_set(idx)?;
Some((g, set))
}
}
#[derive(Debug, Clone, Copy)]
pub struct LigatureSet<'a> {
bytes: &'a [u8],
lig_offsets: &'a [u8],
}
impl<'a> LigatureSet<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let count = read_u16(bytes, 0)? as usize;
let array_start = 2usize;
let need = array_start
.checked_add(
count
.checked_mul(2)
.ok_or(Error::BadStructure("GSUB/LigatureSet length overflow"))?,
)
.ok_or(Error::BadStructure("GSUB/LigatureSet length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
lig_offsets: &bytes[array_start..need],
})
}
pub fn ligature_count(&self) -> u16 {
(self.lig_offsets.len() / 2) as u16
}
pub fn ligature(&self, i: u16) -> Option<Result<Ligature<'a>, Error>> {
let off2 = (i as usize).checked_mul(2)?;
if off2 + 2 > self.lig_offsets.len() {
return None;
}
let off = u16::from_be_bytes([self.lig_offsets[off2], self.lig_offsets[off2 + 1]]) as usize;
if off == 0 || off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"GSUB/LigatureSet: ligatureOffset out of range",
)));
}
Some(Ligature::parse(&self.bytes[off..]))
}
}
#[derive(Debug, Clone, Copy)]
pub struct Ligature<'a> {
glyph: u16,
component_count: u16,
tail: &'a [u8],
}
impl<'a> Ligature<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let glyph = read_u16(bytes, 0)?;
let component_count = read_u16(bytes, 2)?;
if component_count == 0 {
return Err(Error::BadStructure(
"GSUB/Ligature: componentCount must be >= 1",
));
}
let tail_entries = (component_count - 1) as usize;
let tail_start = 4usize;
let need = tail_start
.checked_add(
tail_entries
.checked_mul(2)
.ok_or(Error::BadStructure("GSUB/Ligature length overflow"))?,
)
.ok_or(Error::BadStructure("GSUB/Ligature length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
glyph,
component_count,
tail: &bytes[tail_start..need],
})
}
pub fn ligature_glyph(&self) -> u16 {
self.glyph
}
pub fn component_count(&self) -> u16 {
self.component_count
}
pub fn component_glyph(&self, i: u16) -> Option<u16> {
let off = (i as usize).checked_mul(2)?;
if off + 2 > self.tail.len() {
return None;
}
Some(u16::from_be_bytes([self.tail[off], self.tail[off + 1]]))
}
pub fn component_glyphs(&self) -> LigatureComponentIter<'a> {
LigatureComponentIter {
bytes: self.tail,
pos: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct LigatureComponentIter<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Iterator for LigatureComponentIter<'a> {
type Item = u16;
fn next(&mut self) -> Option<Self::Item> {
if self.pos + 2 > self.bytes.len() {
return None;
}
let g = u16::from_be_bytes([self.bytes[self.pos], self.bytes[self.pos + 1]]);
self.pos += 2;
Some(g)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ExtensionSubst<'a> {
bytes: &'a [u8],
ext_lookup_type: u16,
ext_offset: u32,
}
impl<'a> ExtensionSubst<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let format = read_u16(bytes, 0)?;
if format != 1 {
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: unknown subtable format",
));
}
let ext_lookup_type = read_u16(bytes, 2)?;
if ext_lookup_type == GSUB_LOOKUP_TYPE_EXTENSION {
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: extensionLookupType must not be 7",
));
}
if !(GSUB_LOOKUP_TYPE_SINGLE..=GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE)
.contains(&ext_lookup_type)
{
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: extensionLookupType out of range",
));
}
let ext_offset = read_u32(bytes, 4)?;
if ext_offset == 0 || (ext_offset as usize) >= bytes.len() {
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: extensionOffset out of range",
));
}
Ok(Self {
bytes,
ext_lookup_type,
ext_offset,
})
}
pub fn format(&self) -> u16 {
1
}
pub fn extension_lookup_type(&self) -> u16 {
self.ext_lookup_type
}
pub fn extension_offset(&self) -> u32 {
self.ext_offset
}
pub fn extension_subtable_bytes(&self) -> &'a [u8] {
&self.bytes[self.ext_offset as usize..]
}
pub fn as_single_subst(&self) -> Result<SingleSubst<'a>, Error> {
if self.ext_lookup_type != GSUB_LOOKUP_TYPE_SINGLE {
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: extensionLookupType is not 1",
));
}
SingleSubst::parse(self.extension_subtable_bytes())
}
pub fn as_multiple_subst(&self) -> Result<MultipleSubst<'a>, Error> {
if self.ext_lookup_type != GSUB_LOOKUP_TYPE_MULTIPLE {
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: extensionLookupType is not 2",
));
}
MultipleSubst::parse(self.extension_subtable_bytes())
}
pub fn as_alternate_subst(&self) -> Result<AlternateSubst<'a>, Error> {
if self.ext_lookup_type != GSUB_LOOKUP_TYPE_ALTERNATE {
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: extensionLookupType is not 3",
));
}
AlternateSubst::parse(self.extension_subtable_bytes())
}
pub fn as_ligature_subst(&self) -> Result<LigatureSubst<'a>, Error> {
if self.ext_lookup_type != GSUB_LOOKUP_TYPE_LIGATURE {
return Err(Error::BadStructure(
"GSUB/ExtensionSubst: extensionLookupType is not 4",
));
}
LigatureSubst::parse(self.extension_subtable_bytes())
}
}
#[derive(Debug, Clone, Copy)]
pub struct GsubTable<'a> {
bytes: &'a [u8],
header: LayoutHeader,
}
impl<'a> GsubTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let header = LayoutHeader::parse(bytes)?;
let len = bytes.len();
if (header.script_list_off as usize) >= len
|| (header.feature_list_off as usize) >= len
|| (header.lookup_list_off as usize) >= len
{
return Err(Error::BadStructure("GSUB: header offset out of range"));
}
if header.feature_variations_off != 0 && (header.feature_variations_off as usize) >= len {
return Err(Error::BadStructure(
"GSUB: featureVariationsOffset out of range",
));
}
Ok(Self { bytes, header })
}
pub fn version(&self) -> (u16, u16) {
(self.header.major, self.header.minor)
}
pub fn feature_variations_offset(&self) -> u32 {
self.header.feature_variations_off
}
pub fn has_feature_variations(&self) -> bool {
self.header.minor >= 1 && self.header.feature_variations_off != 0
}
pub fn raw(&self) -> &'a [u8] {
self.bytes
}
pub fn script_list(&self) -> Result<ScriptList<'a>, Error> {
ScriptList::parse(&self.bytes[self.header.script_list_off as usize..])
}
pub fn feature_list(&self) -> Result<FeatureList<'a>, Error> {
FeatureList::parse(&self.bytes[self.header.feature_list_off as usize..])
}
pub fn lookup_list(&self) -> Result<LookupList<'a>, Error> {
LookupList::parse(&self.bytes[self.header.lookup_list_off as usize..])
}
pub fn find_script(&self, tag: &[u8; 4]) -> Option<Script<'a>> {
self.script_list().ok()?.find(tag)?.ok()
}
pub fn lookup_count(&self) -> u16 {
self.lookup_list().map(|l| l.count()).unwrap_or(0)
}
pub fn feature_count(&self) -> u16 {
self.feature_list().map(|f| f.count()).unwrap_or(0)
}
pub fn script_count(&self) -> u16 {
self.script_list().map(|s| s.count()).unwrap_or(0)
}
pub fn lookup(&self, i: u16) -> Option<Lookup<'a>> {
self.lookup_list().ok()?.lookup(i)?.ok()
}
pub fn single_subst(
&self,
lookup_i: u16,
sub_i: u16,
) -> Option<Result<SingleSubst<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GSUB_LOOKUP_TYPE_SINGLE {
return Some(Err(Error::BadStructure(
"GSUB/SingleSubst: lookup is not type 1",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(SingleSubst::parse(bytes))
}
pub fn multiple_subst(
&self,
lookup_i: u16,
sub_i: u16,
) -> Option<Result<MultipleSubst<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GSUB_LOOKUP_TYPE_MULTIPLE {
return Some(Err(Error::BadStructure(
"GSUB/MultipleSubst: lookup is not type 2",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(MultipleSubst::parse(bytes))
}
pub fn alternate_subst(
&self,
lookup_i: u16,
sub_i: u16,
) -> Option<Result<AlternateSubst<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GSUB_LOOKUP_TYPE_ALTERNATE {
return Some(Err(Error::BadStructure(
"GSUB/AlternateSubst: lookup is not type 3",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(AlternateSubst::parse(bytes))
}
pub fn ligature_subst(
&self,
lookup_i: u16,
sub_i: u16,
) -> Option<Result<LigatureSubst<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GSUB_LOOKUP_TYPE_LIGATURE {
return Some(Err(Error::BadStructure(
"GSUB/LigatureSubst: lookup is not type 4",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(LigatureSubst::parse(bytes))
}
pub fn extension_subst(
&self,
lookup_i: u16,
sub_i: u16,
) -> Option<Result<ExtensionSubst<'a>, Error>> {
let lk = self.lookup(lookup_i)?;
if lk.lookup_type() != GSUB_LOOKUP_TYPE_EXTENSION {
return Some(Err(Error::BadStructure(
"GSUB/ExtensionSubst: lookup is not type 7",
)));
}
let bytes = lk.subtable_bytes(sub_i)?;
Some(ExtensionSubst::parse(bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn be(u: u16) -> [u8; 2] {
u.to_be_bytes()
}
#[test]
fn parses_minimal_v10_table() {
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"liga");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(4));
bytes[48..50].copy_from_slice(&be(4));
bytes[50..52].copy_from_slice(&be(0));
bytes[52..54].copy_from_slice(&be(0));
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.version(), (1, 0));
assert!(!g.has_feature_variations());
assert_eq!(g.feature_variations_offset(), 0);
assert_eq!(g.script_count(), 1);
assert_eq!(g.feature_count(), 1);
assert_eq!(g.lookup_count(), 1);
let scripts = g.script_list().unwrap();
assert_eq!(scripts.count(), 1);
assert_eq!(scripts.tag(0), Some(*b"DFLT"));
let dflt = g.find_script(b"DFLT").expect("DFLT script");
assert!(!dflt.has_default_lang_sys());
assert_eq!(dflt.lang_sys_count(), 0);
let feats = g.feature_list().unwrap();
assert_eq!(feats.tag(0), Some(*b"liga"));
let liga = feats.feature(0).unwrap().unwrap();
assert_eq!(liga.lookup_count(), 1);
assert_eq!(liga.lookup_index(0), Some(0));
let l0 = g.lookup(0).unwrap();
assert_eq!(l0.lookup_type(), 4);
assert!(!l0.flag().ignore_marks());
}
fn build_single_subst_fmt1(delta: i16, glyphs: &[u16]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&be(1)); out.extend_from_slice(&be(6)); out.extend_from_slice(&(delta as u16).to_be_bytes());
out.extend_from_slice(&be(1)); out.extend_from_slice(&be(glyphs.len() as u16));
for &g in glyphs {
out.extend_from_slice(&be(g));
}
out
}
#[test]
fn single_subst_fmt1_round_trip() {
let raw = build_single_subst_fmt1(100, &[20, 21, 22]);
let ss = SingleSubst::parse(&raw).unwrap();
assert_eq!(ss.format(), 1);
assert_eq!(ss.delta_glyph_id(), Some(100));
assert_eq!(ss.glyph_count(), None);
assert_eq!(ss.substitute(20), Some(120));
assert_eq!(ss.substitute(21), Some(121));
assert_eq!(ss.substitute(22), Some(122));
assert_eq!(ss.substitute(23), None);
assert_eq!(ss.substitute(0), None);
let pairs: Vec<_> = ss.iter().collect();
assert_eq!(pairs, vec![(20, 120), (21, 121), (22, 122)]);
}
#[test]
fn single_subst_fmt1_negative_delta_wraps_mod_65536() {
let raw = build_single_subst_fmt1(-10, &[5]);
let ss = SingleSubst::parse(&raw).unwrap();
assert_eq!(ss.substitute(5), Some(65531));
}
#[test]
fn single_subst_fmt1_positive_delta_wraps_mod_65536() {
let raw = build_single_subst_fmt1(10, &[65530]);
let ss = SingleSubst::parse(&raw).unwrap();
assert_eq!(ss.substitute(65530), Some(4));
}
fn build_single_subst_fmt2(inputs: &[u16], outputs: &[u16]) -> Vec<u8> {
assert_eq!(inputs.len(), outputs.len());
let n = inputs.len();
let cov_off = 6 + 2 * n;
let mut out = Vec::new();
out.extend_from_slice(&be(2));
out.extend_from_slice(&be(cov_off as u16));
out.extend_from_slice(&be(n as u16));
for &g in outputs {
out.extend_from_slice(&be(g));
}
out.extend_from_slice(&be(1));
out.extend_from_slice(&be(n as u16));
for &g in inputs {
out.extend_from_slice(&be(g));
}
out
}
#[test]
fn single_subst_fmt2_round_trip() {
let raw = build_single_subst_fmt2(&[10, 30, 50], &[1010, 3030, 5050]);
let ss = SingleSubst::parse(&raw).unwrap();
assert_eq!(ss.format(), 2);
assert_eq!(ss.delta_glyph_id(), None);
assert_eq!(ss.glyph_count(), Some(3));
assert_eq!(ss.substitute(10), Some(1010));
assert_eq!(ss.substitute(30), Some(3030));
assert_eq!(ss.substitute(50), Some(5050));
assert_eq!(ss.substitute(0), None);
assert_eq!(ss.substitute(20), None);
assert_eq!(ss.substitute(60), None);
let pairs: Vec<_> = ss.iter().collect();
assert_eq!(pairs, vec![(10, 1010), (30, 3030), (50, 5050)]);
}
#[test]
fn single_subst_fmt2_rejects_glyph_count_mismatch() {
let mut raw = build_single_subst_fmt2(&[10, 30], &[100, 300]);
raw[4..6].copy_from_slice(&be(7));
assert!(matches!(
SingleSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn single_subst_rejects_unknown_format() {
let mut raw = vec![0u8; 16];
raw[0..2].copy_from_slice(&be(3));
raw[2..4].copy_from_slice(&be(8));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(1));
raw[12..14].copy_from_slice(&be(5));
assert!(matches!(
SingleSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn single_subst_rejects_truncated_array() {
let raw = build_single_subst_fmt2(&[1, 2, 3], &[11, 22, 33]);
let truncated = &raw[..raw.len() - 2 ];
assert!(matches!(
SingleSubst::parse(truncated),
Err(Error::UnexpectedEof) | Err(Error::BadStructure(_))
));
}
#[test]
fn gsub_single_subst_end_to_end() {
let mut bytes = vec![0u8; 62];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(36));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"calt");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[36..38].copy_from_slice(&be(1));
bytes[38..40].copy_from_slice(&be(4));
bytes[40..42].copy_from_slice(&be(1));
bytes[42..44].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(8));
bytes[48..50].copy_from_slice(&be(1)); bytes[50..52].copy_from_slice(&be(6)); bytes[52..54].copy_from_slice(&be(200)); bytes[54..56].copy_from_slice(&be(1)); bytes[56..58].copy_from_slice(&be(2)); bytes[58..60].copy_from_slice(&be(50));
bytes[60..62].copy_from_slice(&be(51));
let g = GsubTable::parse(&bytes).unwrap();
let ss = g.single_subst(0, 0).expect("subtable exists").unwrap();
assert_eq!(ss.format(), 1);
assert_eq!(ss.substitute(50), Some(250));
assert_eq!(ss.substitute(51), Some(251));
assert_eq!(ss.substitute(52), None);
assert!(g.single_subst(0, 1).is_none());
assert!(g.single_subst(99, 0).is_none());
}
#[test]
fn gsub_single_subst_rejects_non_type_1_lookup() {
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"liga");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(4));
bytes[48..50].copy_from_slice(&be(4));
bytes[50..52].copy_from_slice(&be(0));
bytes[52..54].copy_from_slice(&be(0));
let g = GsubTable::parse(&bytes).unwrap();
assert!(matches!(g.single_subst(0, 0), Some(Err(_))));
}
#[test]
fn rejects_unknown_minor_version() {
let mut bytes = vec![0u8; 14];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(2));
assert!(matches!(
GsubTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_offset_past_table() {
let mut bytes = vec![0u8; 10];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(99));
assert!(matches!(
GsubTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
fn build_example_4_subtable() -> Vec<u8> {
let mut out = vec![0u8; 22];
out[0..2].copy_from_slice(&be(1));
out[2..4].copy_from_slice(&be(8));
out[4..6].copy_from_slice(&be(1));
out[6..8].copy_from_slice(&be(14));
out[8..10].copy_from_slice(&be(1));
out[10..12].copy_from_slice(&be(1));
out[12..14].copy_from_slice(&be(0x00F1));
out[14..16].copy_from_slice(&be(3));
out[16..18].copy_from_slice(&be(0x001A));
out[18..20].copy_from_slice(&be(0x001A));
out[20..22].copy_from_slice(&be(0x001D));
out
}
#[test]
fn multiple_subst_example_4_round_trip() {
let raw = build_example_4_subtable();
let ms = MultipleSubst::parse(&raw).unwrap();
assert_eq!(ms.format(), 1);
assert_eq!(ms.sequence_count(), 1);
let seq = ms.sequence(0).unwrap().unwrap();
assert_eq!(seq.glyph_count(), 3);
assert_eq!(seq.glyph(0), Some(0x001A));
assert_eq!(seq.glyph(1), Some(0x001A));
assert_eq!(seq.glyph(2), Some(0x001D));
assert_eq!(seq.glyph(3), None);
let collected: Vec<_> = seq.glyphs().collect();
assert_eq!(collected, vec![0x001A, 0x001A, 0x001D]);
let out_seq = ms.substitute(0x00F1).expect("ffi is covered");
let outputs: Vec<_> = out_seq.glyphs().collect();
assert_eq!(outputs, vec![0x001A, 0x001A, 0x001D]);
assert_eq!(
ms.substitute(0x00F2).map(|s| s.glyph_count()),
None,
"uncovered glyph must not substitute",
);
}
#[test]
fn multiple_subst_iter_walks_coverage_in_order() {
let mut raw = vec![0u8; 30];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(22));
raw[4..6].copy_from_slice(&be(2));
raw[6..8].copy_from_slice(&be(10));
raw[8..10].copy_from_slice(&be(16));
raw[10..12].copy_from_slice(&be(2));
raw[12..14].copy_from_slice(&be(100));
raw[14..16].copy_from_slice(&be(101));
raw[16..18].copy_from_slice(&be(1));
raw[18..20].copy_from_slice(&be(200));
raw[22..24].copy_from_slice(&be(1));
raw[24..26].copy_from_slice(&be(2));
raw[26..28].copy_from_slice(&be(5));
raw[28..30].copy_from_slice(&be(9));
let ms = MultipleSubst::parse(&raw).unwrap();
assert_eq!(ms.sequence_count(), 2);
let pairs: Vec<(u16, Vec<u16>)> = ms
.iter()
.map(|(g, s)| (g, s.unwrap().glyphs().collect()))
.collect();
assert_eq!(pairs, vec![(5, vec![100, 101]), (9, vec![200])],);
}
#[test]
fn multiple_subst_rejects_unknown_format() {
let mut raw = vec![0u8; 14];
raw[0..2].copy_from_slice(&be(2));
raw[2..4].copy_from_slice(&be(8));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(1));
raw[12..14].copy_from_slice(&be(5));
assert!(matches!(
MultipleSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn multiple_subst_rejects_coverage_offset_out_of_range() {
let mut raw = vec![0u8; 10];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(99));
raw[4..6].copy_from_slice(&be(0));
assert!(matches!(
MultipleSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn multiple_subst_rejects_sequence_count_mismatch() {
let mut raw = build_example_4_subtable();
raw[4..6].copy_from_slice(&be(7));
assert!(matches!(
MultipleSubst::parse(&raw),
Err(Error::BadStructure(_) | Error::UnexpectedEof),
));
}
#[test]
fn multiple_subst_rejects_truncated_sequence_offsets_array() {
let mut raw = vec![0u8; 12];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(8));
raw[4..6].copy_from_slice(&be(4));
raw[6..8].copy_from_slice(&be(0));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(4));
assert!(matches!(
MultipleSubst::parse(&raw),
Err(Error::UnexpectedEof) | Err(Error::BadStructure(_))
));
}
#[test]
fn multiple_subst_rejects_zero_glyph_count_sequence() {
let mut raw = vec![0u8; 18];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(12));
raw[4..6].copy_from_slice(&be(1));
raw[6..8].copy_from_slice(&be(10));
raw[10..12].copy_from_slice(&be(0));
raw[12..14].copy_from_slice(&be(1));
raw[14..16].copy_from_slice(&be(1));
raw[16..18].copy_from_slice(&be(7));
let ms = MultipleSubst::parse(&raw).unwrap();
let bad = ms.sequence(0).unwrap();
assert!(matches!(bad, Err(Error::BadStructure(_))));
assert!(ms.substitute(7).is_none());
}
fn build_minimal_multiple_gsub() -> Vec<u8> {
let sub = build_example_4_subtable();
let head_end = 48 + sub.len();
let mut bytes = vec![0u8; head_end];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(36));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"ccmp");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[36..38].copy_from_slice(&be(1));
bytes[38..40].copy_from_slice(&be(4));
bytes[40..42].copy_from_slice(&be(2));
bytes[42..44].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(8));
bytes[48..head_end].copy_from_slice(&sub);
bytes
}
#[test]
fn gsub_multiple_subst_end_to_end() {
let bytes = build_minimal_multiple_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_count(), 1);
let l0 = g.lookup(0).unwrap();
assert_eq!(l0.lookup_type(), GSUB_LOOKUP_TYPE_MULTIPLE);
let ms = g.multiple_subst(0, 0).expect("subtable exists").unwrap();
let seq = ms.substitute(0x00F1).expect("ffi is covered");
let outputs: Vec<_> = seq.glyphs().collect();
assert_eq!(outputs, vec![0x001A, 0x001A, 0x001D]);
assert!(g.multiple_subst(0, 1).is_none());
assert!(g.multiple_subst(99, 0).is_none());
}
#[test]
fn gsub_multiple_subst_rejects_non_type_2_lookup() {
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"liga");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(4));
bytes[48..50].copy_from_slice(&be(4));
bytes[50..52].copy_from_slice(&be(0));
bytes[52..54].copy_from_slice(&be(0));
let g = GsubTable::parse(&bytes).unwrap();
assert!(matches!(g.multiple_subst(0, 0), Some(Err(_))));
}
fn build_example_5_subtable() -> Vec<u8> {
let mut out = vec![0u8; 20];
out[0..2].copy_from_slice(&be(1));
out[2..4].copy_from_slice(&be(8));
out[4..6].copy_from_slice(&be(1));
out[6..8].copy_from_slice(&be(14));
out[8..10].copy_from_slice(&be(1));
out[10..12].copy_from_slice(&be(1));
out[12..14].copy_from_slice(&be(0x003A));
out[14..16].copy_from_slice(&be(2));
out[16..18].copy_from_slice(&be(0x00C9));
out[18..20].copy_from_slice(&be(0x00CA));
out
}
#[test]
fn alternate_subst_example_5_round_trip() {
let raw = build_example_5_subtable();
let alt = AlternateSubst::parse(&raw).unwrap();
assert_eq!(alt.format(), 1);
assert_eq!(alt.alternate_set_count(), 1);
let set = alt.alternate_set(0).unwrap().unwrap();
assert_eq!(set.glyph_count(), 2);
assert_eq!(set.glyph(0), Some(0x00C9));
assert_eq!(set.glyph(1), Some(0x00CA));
assert_eq!(set.glyph(2), None);
let collected: Vec<_> = set.glyphs().collect();
assert_eq!(collected, vec![0x00C9, 0x00CA]);
let out_set = alt.substitute(0x003A).expect("ampersand is covered");
let outputs: Vec<_> = out_set.glyphs().collect();
assert_eq!(outputs, vec![0x00C9, 0x00CA]);
assert_eq!(
alt.substitute(0x003B).map(|s| s.glyph_count()),
None,
"uncovered glyph must not substitute",
);
}
#[test]
fn alternate_subst_iter_walks_coverage_in_order() {
let mut raw = vec![0u8; 30];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(22));
raw[4..6].copy_from_slice(&be(2));
raw[6..8].copy_from_slice(&be(10));
raw[8..10].copy_from_slice(&be(16));
raw[10..12].copy_from_slice(&be(2));
raw[12..14].copy_from_slice(&be(100));
raw[14..16].copy_from_slice(&be(101));
raw[16..18].copy_from_slice(&be(1));
raw[18..20].copy_from_slice(&be(200));
raw[22..24].copy_from_slice(&be(1));
raw[24..26].copy_from_slice(&be(2));
raw[26..28].copy_from_slice(&be(5));
raw[28..30].copy_from_slice(&be(9));
let alt = AlternateSubst::parse(&raw).unwrap();
assert_eq!(alt.alternate_set_count(), 2);
let pairs: Vec<(u16, Vec<u16>)> = alt
.iter()
.map(|(g, s)| (g, s.unwrap().glyphs().collect()))
.collect();
assert_eq!(pairs, vec![(5, vec![100, 101]), (9, vec![200])],);
}
#[test]
fn alternate_subst_rejects_unknown_format() {
let mut raw = vec![0u8; 14];
raw[0..2].copy_from_slice(&be(2));
raw[2..4].copy_from_slice(&be(8));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(1));
raw[12..14].copy_from_slice(&be(5));
assert!(matches!(
AlternateSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn alternate_subst_rejects_coverage_offset_out_of_range() {
let mut raw = vec![0u8; 10];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(99));
raw[4..6].copy_from_slice(&be(0));
assert!(matches!(
AlternateSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn alternate_subst_rejects_set_count_mismatch() {
let mut raw = build_example_5_subtable();
raw[4..6].copy_from_slice(&be(7));
assert!(matches!(
AlternateSubst::parse(&raw),
Err(Error::BadStructure(_) | Error::UnexpectedEof),
));
}
#[test]
fn alternate_subst_rejects_truncated_set_offsets_array() {
let mut raw = vec![0u8; 12];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(8));
raw[4..6].copy_from_slice(&be(4));
raw[6..8].copy_from_slice(&be(0));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(4));
assert!(matches!(
AlternateSubst::parse(&raw),
Err(Error::UnexpectedEof) | Err(Error::BadStructure(_))
));
}
#[test]
fn alternate_subst_accepts_empty_alternate_set() {
let mut raw = vec![0u8; 18];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(12));
raw[4..6].copy_from_slice(&be(1));
raw[6..8].copy_from_slice(&be(10));
raw[10..12].copy_from_slice(&be(0));
raw[12..14].copy_from_slice(&be(1));
raw[14..16].copy_from_slice(&be(1));
raw[16..18].copy_from_slice(&be(7));
let alt = AlternateSubst::parse(&raw).unwrap();
let set = alt.alternate_set(0).unwrap().unwrap();
assert_eq!(set.glyph_count(), 0);
assert_eq!(set.glyph(0), None);
assert_eq!(set.glyphs().count(), 0);
let out = alt.substitute(7).expect("covered");
assert_eq!(out.glyph_count(), 0);
}
fn build_minimal_alternate_gsub() -> Vec<u8> {
let sub = build_example_5_subtable();
let head_end = 48 + sub.len();
let mut bytes = vec![0u8; head_end];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(36));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"aalt");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[36..38].copy_from_slice(&be(1));
bytes[38..40].copy_from_slice(&be(4));
bytes[40..42].copy_from_slice(&be(3));
bytes[42..44].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(8));
bytes[48..head_end].copy_from_slice(&sub);
bytes
}
#[test]
fn gsub_alternate_subst_end_to_end() {
let bytes = build_minimal_alternate_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_count(), 1);
let l0 = g.lookup(0).unwrap();
assert_eq!(l0.lookup_type(), GSUB_LOOKUP_TYPE_ALTERNATE);
let alt = g.alternate_subst(0, 0).expect("subtable exists").unwrap();
let set = alt.substitute(0x003A).expect("ampersand is covered");
let outputs: Vec<_> = set.glyphs().collect();
assert_eq!(outputs, vec![0x00C9, 0x00CA]);
assert!(g.alternate_subst(0, 1).is_none());
assert!(g.alternate_subst(99, 0).is_none());
}
#[test]
fn gsub_alternate_subst_rejects_non_type_3_lookup() {
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"liga");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(4));
bytes[48..50].copy_from_slice(&be(4));
bytes[50..52].copy_from_slice(&be(0));
bytes[52..54].copy_from_slice(&be(0));
let g = GsubTable::parse(&bytes).unwrap();
assert!(matches!(g.alternate_subst(0, 0), Some(Err(_))));
}
fn build_example_6_subtable() -> Vec<u8> {
let mut out = vec![0u8; 52];
out[0..2].copy_from_slice(&be(1)); out[2..4].copy_from_slice(&be(44)); out[4..6].copy_from_slice(&be(2)); out[6..8].copy_from_slice(&be(10)); out[8..10].copy_from_slice(&be(22)); out[10..12].copy_from_slice(&be(1)); out[12..14].copy_from_slice(&be(4)); out[14..16].copy_from_slice(&be(100)); out[16..18].copy_from_slice(&be(3)); out[18..20].copy_from_slice(&be(20)); out[20..22].copy_from_slice(&be(21)); out[22..24].copy_from_slice(&be(2)); out[24..26].copy_from_slice(&be(8)); out[26..28].copy_from_slice(&be(16)); out[28..30].copy_from_slice(&[0, 0]); out[30..32].copy_from_slice(&be(101)); out[32..34].copy_from_slice(&be(3)); out[34..36].copy_from_slice(&be(6)); out[36..38].copy_from_slice(&be(9)); out[38..40].copy_from_slice(&be(102)); out[40..42].copy_from_slice(&be(2)); out[42..44].copy_from_slice(&be(9)); out[44..46].copy_from_slice(&be(1)); out[46..48].copy_from_slice(&be(2)); out[48..50].copy_from_slice(&be(5)); out[50..52].copy_from_slice(&be(6)); out
}
#[test]
fn ligature_subst_example_6_round_trip() {
let raw = build_example_6_subtable();
let ls = LigatureSubst::parse(&raw).unwrap();
assert_eq!(ls.format(), 1);
assert_eq!(ls.ligature_set_count(), 2);
let e_set = ls.ligature_set(0).unwrap().unwrap();
assert_eq!(e_set.ligature_count(), 1);
let etc = e_set.ligature(0).unwrap().unwrap();
assert_eq!(etc.ligature_glyph(), 100);
assert_eq!(etc.component_count(), 3);
let etc_tail: Vec<_> = etc.component_glyphs().collect();
assert_eq!(etc_tail, vec![20, 21]);
let f_set = ls.ligature_set(1).unwrap().unwrap();
assert_eq!(f_set.ligature_count(), 2);
let ffi = f_set.ligature(0).unwrap().unwrap();
assert_eq!(ffi.ligature_glyph(), 101);
assert_eq!(ffi.component_count(), 3);
let ffi_tail: Vec<_> = ffi.component_glyphs().collect();
assert_eq!(ffi_tail, vec![6, 9]);
let fi = f_set.ligature(1).unwrap().unwrap();
assert_eq!(fi.ligature_glyph(), 102);
assert_eq!(fi.component_count(), 2);
let fi_tail: Vec<_> = fi.component_glyphs().collect();
assert_eq!(fi_tail, vec![9]);
}
#[test]
fn ligature_subst_substitute_matches_etc() {
let raw = build_example_6_subtable();
let ls = LigatureSubst::parse(&raw).unwrap();
assert_eq!(ls.substitute(&[5, 20, 21]), Some((100, 3)));
assert_eq!(ls.substitute(&[5, 20, 21, 99]), Some((100, 3)));
}
#[test]
fn ligature_subst_substitute_prefers_ffi_over_fi() {
let raw = build_example_6_subtable();
let ls = LigatureSubst::parse(&raw).unwrap();
assert_eq!(ls.substitute(&[6, 6, 9]), Some((101, 3)));
assert_eq!(ls.substitute(&[6, 9]), Some((102, 2)));
}
#[test]
fn ligature_subst_substitute_returns_none_when_no_match() {
let raw = build_example_6_subtable();
let ls = LigatureSubst::parse(&raw).unwrap();
assert_eq!(ls.substitute(&[]), None);
assert_eq!(ls.substitute(&[7, 9]), None);
assert_eq!(ls.substitute(&[5, 0, 0]), None);
assert_eq!(ls.substitute(&[6, 99]), None);
}
#[test]
fn ligature_subst_iter_walks_coverage_in_order() {
let raw = build_example_6_subtable();
let ls = LigatureSubst::parse(&raw).unwrap();
let glyphs: Vec<_> = ls.iter().map(|(g, _)| g).collect();
assert_eq!(glyphs, vec![5, 6]);
for (_, set_res) in ls.iter() {
let set = set_res.unwrap();
assert!(set.ligature_count() >= 1);
}
}
#[test]
fn ligature_subst_rejects_unknown_format() {
let mut raw = vec![0u8; 16];
raw[0..2].copy_from_slice(&be(2));
raw[2..4].copy_from_slice(&be(8));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(1));
raw[12..14].copy_from_slice(&be(5));
assert!(matches!(
LigatureSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn ligature_subst_rejects_truncated_set_offsets_array() {
let mut raw = vec![0u8; 12];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(8));
raw[4..6].copy_from_slice(&be(4));
raw[6..8].copy_from_slice(&be(0));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(0));
assert!(matches!(
LigatureSubst::parse(&raw),
Err(Error::UnexpectedEof)
));
}
#[test]
fn ligature_subst_rejects_coverage_offset_out_of_range() {
let mut raw = vec![0u8; 10];
raw[0..2].copy_from_slice(&be(1)); raw[2..4].copy_from_slice(&be(99)); raw[4..6].copy_from_slice(&be(0)); assert!(matches!(
LigatureSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn ligature_subst_zero_component_count_rejected() {
let mut raw = vec![0u8; 22];
raw[0..2].copy_from_slice(&be(1));
raw[2..4].copy_from_slice(&be(16));
raw[4..6].copy_from_slice(&be(1));
raw[6..8].copy_from_slice(&be(8));
raw[8..10].copy_from_slice(&be(1));
raw[10..12].copy_from_slice(&be(4));
raw[12..14].copy_from_slice(&be(50));
raw[14..16].copy_from_slice(&be(0)); raw[16..18].copy_from_slice(&be(1));
raw[18..20].copy_from_slice(&be(1));
raw[20..22].copy_from_slice(&be(10));
let ls = LigatureSubst::parse(&raw).unwrap();
let set = ls.ligature_set(0).unwrap().unwrap();
let lig = set.ligature(0).unwrap();
assert!(matches!(lig, Err(Error::BadStructure(_))));
}
fn build_minimal_ligature_gsub() -> Vec<u8> {
let sub = build_example_6_subtable();
let head_end = 48 + sub.len();
let mut bytes = vec![0u8; head_end];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(36));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"liga");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[36..38].copy_from_slice(&be(1));
bytes[38..40].copy_from_slice(&be(4));
bytes[40..42].copy_from_slice(&be(4));
bytes[42..44].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(8));
bytes[48..head_end].copy_from_slice(&sub);
bytes
}
#[test]
fn gsub_ligature_subst_end_to_end() {
let bytes = build_minimal_ligature_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_count(), 1);
let l0 = g.lookup(0).unwrap();
assert_eq!(l0.lookup_type(), GSUB_LOOKUP_TYPE_LIGATURE);
let ls = g.ligature_subst(0, 0).expect("subtable exists").unwrap();
assert_eq!(ls.format(), 1);
assert_eq!(ls.substitute(&[5, 20, 21]), Some((100, 3)));
assert_eq!(ls.substitute(&[6, 6, 9]), Some((101, 3)));
assert_eq!(ls.substitute(&[6, 9]), Some((102, 2)));
assert_eq!(ls.substitute(&[7, 7]), None);
}
#[test]
fn gsub_ligature_subst_rejects_non_type_4_lookup() {
let mut bytes = vec![0u8; 54];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(44));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"liga");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(4));
bytes[48..50].copy_from_slice(&be(1));
bytes[50..52].copy_from_slice(&be(0));
bytes[52..54].copy_from_slice(&be(0));
let g = GsubTable::parse(&bytes).unwrap();
assert!(matches!(g.ligature_subst(0, 0), Some(Err(_))));
}
#[test]
fn gsub_ligature_subst_out_of_range_indices_return_none() {
let bytes = build_minimal_ligature_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert!(g.ligature_subst(0, 1).is_none());
assert!(g.ligature_subst(99, 0).is_none());
}
fn build_extension_subst(ext_type: u16, inner: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&be(1)); out.extend_from_slice(&be(ext_type)); out.extend_from_slice(&8u32.to_be_bytes()); out.extend_from_slice(inner);
out
}
#[test]
fn extension_subst_round_trip_wrapping_single_subst() {
let inner = build_single_subst_fmt1(100, &[20, 21, 22]);
let raw = build_extension_subst(GSUB_LOOKUP_TYPE_SINGLE, &inner);
let ext = ExtensionSubst::parse(&raw).unwrap();
assert_eq!(ext.format(), 1);
assert_eq!(ext.extension_lookup_type(), GSUB_LOOKUP_TYPE_SINGLE);
assert_eq!(ext.extension_offset(), 8);
assert_eq!(ext.extension_subtable_bytes(), &inner[..]);
let ss = ext.as_single_subst().unwrap();
assert_eq!(ss.format(), 1);
assert_eq!(ss.substitute(20), Some(120));
assert_eq!(ss.substitute(23), None);
assert!(matches!(
ext.as_multiple_subst(),
Err(Error::BadStructure(_))
));
assert!(matches!(
ext.as_alternate_subst(),
Err(Error::BadStructure(_))
));
assert!(matches!(
ext.as_ligature_subst(),
Err(Error::BadStructure(_))
));
}
#[test]
fn extension_subst_round_trip_wrapping_ligature_subst() {
let inner = build_example_6_subtable();
let raw = build_extension_subst(GSUB_LOOKUP_TYPE_LIGATURE, &inner);
let ext = ExtensionSubst::parse(&raw).unwrap();
assert_eq!(ext.extension_lookup_type(), GSUB_LOOKUP_TYPE_LIGATURE);
let ls = ext.as_ligature_subst().unwrap();
assert_eq!(ls.substitute(&[5, 20, 21]), Some((100, 3)));
assert!(matches!(ext.as_single_subst(), Err(Error::BadStructure(_))));
}
#[test]
fn extension_subst_undecoded_type_exposes_raw_bytes() {
let inner = [0xAAu8, 0xBB, 0xCC, 0xDD];
let raw = build_extension_subst(GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE, &inner);
let ext = ExtensionSubst::parse(&raw).unwrap();
assert_eq!(
ext.extension_lookup_type(),
GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE
);
assert_eq!(ext.extension_subtable_bytes(), &inner[..]);
assert!(matches!(ext.as_single_subst(), Err(Error::BadStructure(_))));
assert!(matches!(
ext.as_ligature_subst(),
Err(Error::BadStructure(_))
));
}
#[test]
fn extension_subst_rejects_unknown_format() {
let inner = build_single_subst_fmt1(1, &[10]);
let mut raw = build_extension_subst(GSUB_LOOKUP_TYPE_SINGLE, &inner);
raw[0..2].copy_from_slice(&be(2)); assert!(matches!(
ExtensionSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn extension_subst_rejects_nested_extension_type() {
let inner = build_single_subst_fmt1(1, &[10]);
let raw = build_extension_subst(GSUB_LOOKUP_TYPE_EXTENSION, &inner);
assert!(matches!(
ExtensionSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn extension_subst_rejects_out_of_vocabulary_lookup_type() {
let inner = build_single_subst_fmt1(1, &[10]);
for bad in [0u16, 9, 0xFFFF] {
let raw = build_extension_subst(bad, &inner);
assert!(
matches!(ExtensionSubst::parse(&raw), Err(Error::BadStructure(_))),
"extensionLookupType = {bad} must be rejected"
);
}
}
#[test]
fn extension_subst_rejects_offset_out_of_range() {
let inner = build_single_subst_fmt1(1, &[10]);
let mut raw = build_extension_subst(GSUB_LOOKUP_TYPE_SINGLE, &inner);
raw[4..8].copy_from_slice(&0u32.to_be_bytes());
assert!(matches!(
ExtensionSubst::parse(&raw),
Err(Error::BadStructure(_))
));
let mut raw = build_extension_subst(GSUB_LOOKUP_TYPE_SINGLE, &inner);
let len = raw.len() as u32;
raw[4..8].copy_from_slice(&len.to_be_bytes());
assert!(matches!(
ExtensionSubst::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn extension_subst_rejects_truncated_header() {
let inner = build_single_subst_fmt1(1, &[10]);
let raw = build_extension_subst(GSUB_LOOKUP_TYPE_SINGLE, &inner);
assert!(matches!(
ExtensionSubst::parse(&raw[..6]),
Err(Error::UnexpectedEof)
));
assert!(matches!(
ExtensionSubst::parse(&raw[..2]),
Err(Error::UnexpectedEof)
));
}
fn build_minimal_extension_gsub() -> Vec<u8> {
let inner = build_single_subst_fmt1(200, &[50, 51]);
let sub = build_extension_subst(GSUB_LOOKUP_TYPE_SINGLE, &inner);
let head_end = 48 + sub.len();
let mut bytes = vec![0u8; head_end];
bytes[0..2].copy_from_slice(&be(1));
bytes[2..4].copy_from_slice(&be(0));
bytes[4..6].copy_from_slice(&be(10));
bytes[6..8].copy_from_slice(&be(22));
bytes[8..10].copy_from_slice(&be(36));
bytes[10..12].copy_from_slice(&be(1));
bytes[12..16].copy_from_slice(b"DFLT");
bytes[16..18].copy_from_slice(&be(8));
bytes[18..20].copy_from_slice(&be(0));
bytes[20..22].copy_from_slice(&be(0));
bytes[22..24].copy_from_slice(&be(1));
bytes[24..28].copy_from_slice(b"calt");
bytes[28..30].copy_from_slice(&be(8));
bytes[30..32].copy_from_slice(&be(0));
bytes[32..34].copy_from_slice(&be(1));
bytes[34..36].copy_from_slice(&be(0));
bytes[36..38].copy_from_slice(&be(1));
bytes[38..40].copy_from_slice(&be(4));
bytes[40..42].copy_from_slice(&be(7));
bytes[42..44].copy_from_slice(&be(0));
bytes[44..46].copy_from_slice(&be(1));
bytes[46..48].copy_from_slice(&be(8));
bytes[48..head_end].copy_from_slice(&sub);
bytes
}
#[test]
fn gsub_extension_subst_end_to_end() {
let bytes = build_minimal_extension_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert_eq!(g.lookup_count(), 1);
let l0 = g.lookup(0).unwrap();
assert_eq!(l0.lookup_type(), GSUB_LOOKUP_TYPE_EXTENSION);
let ext = g.extension_subst(0, 0).expect("subtable exists").unwrap();
assert_eq!(ext.format(), 1);
assert_eq!(ext.extension_lookup_type(), GSUB_LOOKUP_TYPE_SINGLE);
let ss = ext.as_single_subst().unwrap();
assert_eq!(ss.substitute(50), Some(250));
assert_eq!(ss.substitute(51), Some(251));
assert_eq!(ss.substitute(52), None);
assert!(matches!(g.single_subst(0, 0), Some(Err(_))));
}
#[test]
fn gsub_extension_subst_rejects_non_type_7_lookup() {
let bytes = build_minimal_ligature_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert!(matches!(g.extension_subst(0, 0), Some(Err(_))));
}
#[test]
fn gsub_extension_subst_out_of_range_indices_return_none() {
let bytes = build_minimal_extension_gsub();
let g = GsubTable::parse(&bytes).unwrap();
assert!(g.extension_subst(0, 1).is_none());
assert!(g.extension_subst(99, 0).is_none());
}
}