use crate::parser::{read_u16, read_u32};
use crate::Error;
#[inline]
fn read_tag(bytes: &[u8], off: usize) -> Result<[u8; 4], Error> {
if bytes.len() < off + 4 {
return Err(Error::UnexpectedEof);
}
Ok([bytes[off], bytes[off + 1], bytes[off + 2], bytes[off + 3]])
}
#[derive(Debug, Clone, Copy)]
pub struct ScriptList<'a> {
bytes: &'a [u8],
count: u16,
}
impl<'a> ScriptList<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let count = read_u16(bytes, 0)?;
let need = 2usize
.checked_add(
(count as usize)
.checked_mul(6)
.ok_or(Error::BadStructure("ScriptList scriptCount overflow"))?,
)
.ok_or(Error::BadStructure("ScriptList length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self { bytes, count })
}
pub fn count(&self) -> u16 {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn tag(&self, i: u16) -> Option<[u8; 4]> {
if i >= self.count {
return None;
}
let off = 2 + (i as usize) * 6;
read_tag(self.bytes, off).ok()
}
pub fn script(&self, i: u16) -> Option<Result<Script<'a>, Error>> {
if i >= self.count {
return None;
}
let off = 2 + (i as usize) * 6;
let script_off = read_u16(self.bytes, off + 4).ok()? as usize;
Some(self.script_at(script_off))
}
fn script_at(&self, off: usize) -> Result<Script<'a>, Error> {
if off == 0 || off >= self.bytes.len() {
return Err(Error::BadStructure("ScriptList: scriptOffset out of range"));
}
Script::parse(&self.bytes[off..])
}
pub fn find(&self, tag: &[u8; 4]) -> Option<Result<Script<'a>, Error>> {
let mut lo = 0i32;
let mut hi = self.count as i32 - 1;
while lo <= hi {
let mid = (lo + hi) / 2;
let mid_tag = self.tag(mid as u16)?;
match mid_tag.cmp(tag) {
std::cmp::Ordering::Equal => {
return self.script(mid as u16);
}
std::cmp::Ordering::Less => lo = mid + 1,
std::cmp::Ordering::Greater => hi = mid - 1,
}
}
None
}
pub fn iter(&self) -> ScriptListIter<'a> {
ScriptListIter {
list: *self,
next: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct ScriptListIter<'a> {
list: ScriptList<'a>,
next: u16,
}
impl<'a> Iterator for ScriptListIter<'a> {
type Item = ([u8; 4], Result<Script<'a>, Error>);
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.list.count {
return None;
}
let i = self.next;
self.next += 1;
let tag = self.list.tag(i)?;
let script = self.list.script(i)?;
Some((tag, script))
}
}
#[derive(Debug, Clone, Copy)]
pub struct Script<'a> {
bytes: &'a [u8],
default_off: u16,
count: u16,
}
impl<'a> Script<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let default_off = read_u16(bytes, 0)?;
let count = read_u16(bytes, 2)?;
let need = 4usize
.checked_add(
(count as usize)
.checked_mul(6)
.ok_or(Error::BadStructure("Script langSysCount overflow"))?,
)
.ok_or(Error::BadStructure("Script length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
default_off,
count,
})
}
pub fn has_default_lang_sys(&self) -> bool {
self.default_off != 0
}
pub fn lang_sys_count(&self) -> u16 {
self.count
}
pub fn default_lang_sys(&self) -> Option<Result<LangSys<'a>, Error>> {
if self.default_off == 0 {
return None;
}
let off = self.default_off as usize;
if off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"Script: defaultLangSysOffset out of range",
)));
}
Some(LangSys::parse(&self.bytes[off..]))
}
pub fn lang_sys_tag(&self, i: u16) -> Option<[u8; 4]> {
if i >= self.count {
return None;
}
let off = 4 + (i as usize) * 6;
read_tag(self.bytes, off).ok()
}
pub fn lang_sys(&self, i: u16) -> Option<Result<LangSys<'a>, Error>> {
if i >= self.count {
return None;
}
let off = 4 + (i as usize) * 6;
let lang_off = read_u16(self.bytes, off + 4).ok()? as usize;
if lang_off == 0 || lang_off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"Script: langSysOffset out of range",
)));
}
Some(LangSys::parse(&self.bytes[lang_off..]))
}
pub fn find_lang_sys(&self, tag: &[u8; 4]) -> Option<Result<LangSys<'a>, Error>> {
let mut lo = 0i32;
let mut hi = self.count as i32 - 1;
while lo <= hi {
let mid = (lo + hi) / 2;
let mid_tag = self.lang_sys_tag(mid as u16)?;
match mid_tag.cmp(tag) {
std::cmp::Ordering::Equal => return self.lang_sys(mid as u16),
std::cmp::Ordering::Less => lo = mid + 1,
std::cmp::Ordering::Greater => hi = mid - 1,
}
}
None
}
}
pub const NO_REQUIRED_FEATURE: u16 = 0xFFFF;
#[derive(Debug, Clone, Copy)]
pub struct LangSys<'a> {
bytes: &'a [u8],
required: u16,
count: u16,
}
impl<'a> LangSys<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let _ = read_u16(bytes, 0)?;
let required = read_u16(bytes, 2)?;
let count = read_u16(bytes, 4)?;
let need = 6usize
.checked_add(
(count as usize)
.checked_mul(2)
.ok_or(Error::BadStructure("LangSys featureIndexCount overflow"))?,
)
.ok_or(Error::BadStructure("LangSys length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
required,
count,
})
}
pub fn required_feature_index(&self) -> Option<u16> {
if self.required == NO_REQUIRED_FEATURE {
None
} else {
Some(self.required)
}
}
pub fn feature_count(&self) -> u16 {
self.count
}
pub fn feature_index(&self, i: u16) -> Option<u16> {
if i >= self.count {
return None;
}
let off = 6 + (i as usize) * 2;
read_u16(self.bytes, off).ok()
}
pub fn feature_indices(&self) -> impl Iterator<Item = u16> + '_ {
(0..self.count).filter_map(move |i| self.feature_index(i))
}
}
#[derive(Debug, Clone, Copy)]
pub struct FeatureList<'a> {
bytes: &'a [u8],
count: u16,
}
impl<'a> FeatureList<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let count = read_u16(bytes, 0)?;
let need = 2usize
.checked_add(
(count as usize)
.checked_mul(6)
.ok_or(Error::BadStructure("FeatureList featureCount overflow"))?,
)
.ok_or(Error::BadStructure("FeatureList length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self { bytes, count })
}
pub fn count(&self) -> u16 {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn tag(&self, i: u16) -> Option<[u8; 4]> {
if i >= self.count {
return None;
}
let off = 2 + (i as usize) * 6;
read_tag(self.bytes, off).ok()
}
pub fn feature(&self, i: u16) -> Option<Result<Feature<'a>, Error>> {
if i >= self.count {
return None;
}
let off = 2 + (i as usize) * 6;
let feat_off = read_u16(self.bytes, off + 4).ok()? as usize;
if feat_off == 0 || feat_off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"FeatureList: featureOffset out of range",
)));
}
Some(Feature::parse(&self.bytes[feat_off..]))
}
pub fn iter(&self) -> FeatureListIter<'a> {
FeatureListIter {
list: *self,
next: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct FeatureListIter<'a> {
list: FeatureList<'a>,
next: u16,
}
impl<'a> Iterator for FeatureListIter<'a> {
type Item = ([u8; 4], Result<Feature<'a>, Error>);
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.list.count {
return None;
}
let i = self.next;
self.next += 1;
let tag = self.list.tag(i)?;
let feat = self.list.feature(i)?;
Some((tag, feat))
}
}
#[derive(Debug, Clone, Copy)]
pub struct Feature<'a> {
bytes: &'a [u8],
params_off: u16,
count: u16,
}
impl<'a> Feature<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let params_off = read_u16(bytes, 0)?;
let count = read_u16(bytes, 2)?;
let need = 4usize
.checked_add(
(count as usize)
.checked_mul(2)
.ok_or(Error::BadStructure("Feature lookupIndexCount overflow"))?,
)
.ok_or(Error::BadStructure("Feature length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
params_off,
count,
})
}
pub fn feature_params_offset(&self) -> u16 {
self.params_off
}
pub fn lookup_count(&self) -> u16 {
self.count
}
pub fn lookup_index(&self, i: u16) -> Option<u16> {
if i >= self.count {
return None;
}
let off = 4 + (i as usize) * 2;
read_u16(self.bytes, off).ok()
}
pub fn lookup_indices(&self) -> impl Iterator<Item = u16> + '_ {
(0..self.count).filter_map(move |i| self.lookup_index(i))
}
}
#[derive(Debug, Clone, Copy)]
pub struct LookupList<'a> {
bytes: &'a [u8],
count: u16,
}
impl<'a> LookupList<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let count = read_u16(bytes, 0)?;
let need = 2usize
.checked_add(
(count as usize)
.checked_mul(2)
.ok_or(Error::BadStructure("LookupList lookupCount overflow"))?,
)
.ok_or(Error::BadStructure("LookupList length overflow"))?;
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
Ok(Self { bytes, count })
}
pub fn count(&self) -> u16 {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn lookup(&self, i: u16) -> Option<Result<Lookup<'a>, Error>> {
if i >= self.count {
return None;
}
let off = 2 + (i as usize) * 2;
let lkup_off = read_u16(self.bytes, off).ok()? as usize;
if lkup_off == 0 || lkup_off >= self.bytes.len() {
return Some(Err(Error::BadStructure(
"LookupList: lookupOffset out of range",
)));
}
Some(Lookup::parse(&self.bytes[lkup_off..]))
}
pub fn iter(&self) -> LookupListIter<'a> {
LookupListIter {
list: *self,
next: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct LookupListIter<'a> {
list: LookupList<'a>,
next: u16,
}
impl<'a> Iterator for LookupListIter<'a> {
type Item = Result<Lookup<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.list.count {
return None;
}
let i = self.next;
self.next += 1;
self.list.lookup(i)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LookupFlag(pub u16);
impl LookupFlag {
pub const RIGHT_TO_LEFT: u16 = 0x0001;
pub const IGNORE_BASE_GLYPHS: u16 = 0x0002;
pub const IGNORE_LIGATURES: u16 = 0x0004;
pub const IGNORE_MARKS: u16 = 0x0008;
pub const USE_MARK_FILTERING_SET: u16 = 0x0010;
pub const MARK_ATTACHMENT_CLASS_MASK: u16 = 0xFF00;
pub fn bits(self) -> u16 {
self.0
}
pub fn right_to_left(self) -> bool {
self.0 & Self::RIGHT_TO_LEFT != 0
}
pub fn ignore_base_glyphs(self) -> bool {
self.0 & Self::IGNORE_BASE_GLYPHS != 0
}
pub fn ignore_ligatures(self) -> bool {
self.0 & Self::IGNORE_LIGATURES != 0
}
pub fn ignore_marks(self) -> bool {
self.0 & Self::IGNORE_MARKS != 0
}
pub fn use_mark_filtering_set(self) -> bool {
self.0 & Self::USE_MARK_FILTERING_SET != 0
}
pub fn mark_attachment_type(self) -> u8 {
((self.0 & Self::MARK_ATTACHMENT_CLASS_MASK) >> 8) as u8
}
}
#[derive(Debug, Clone, Copy)]
pub struct Lookup<'a> {
bytes: &'a [u8],
lookup_type: u16,
flag: LookupFlag,
sub_count: u16,
mark_filtering_set: u16,
}
impl<'a> Lookup<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let lookup_type = read_u16(bytes, 0)?;
let flag_bits = read_u16(bytes, 2)?;
let flag = LookupFlag(flag_bits);
let sub_count = read_u16(bytes, 4)?;
let sub_array = 6usize
.checked_add(
(sub_count as usize)
.checked_mul(2)
.ok_or(Error::BadStructure("Lookup subTableCount overflow"))?,
)
.ok_or(Error::BadStructure("Lookup length overflow"))?;
let need = if flag.use_mark_filtering_set() {
sub_array
.checked_add(2)
.ok_or(Error::BadStructure("Lookup mark-filtering overflow"))?
} else {
sub_array
};
if bytes.len() < need {
return Err(Error::UnexpectedEof);
}
let mark_filtering_set = if flag.use_mark_filtering_set() {
read_u16(bytes, sub_array)?
} else {
0
};
Ok(Self {
bytes,
lookup_type,
flag,
sub_count,
mark_filtering_set,
})
}
pub fn lookup_type(&self) -> u16 {
self.lookup_type
}
pub fn flag(&self) -> LookupFlag {
self.flag
}
pub fn subtable_count(&self) -> u16 {
self.sub_count
}
pub fn subtable_bytes(&self, i: u16) -> Option<&'a [u8]> {
if i >= self.sub_count {
return None;
}
let off_off = 6 + (i as usize) * 2;
let sub_off = read_u16(self.bytes, off_off).ok()? as usize;
if sub_off == 0 || sub_off >= self.bytes.len() {
return None;
}
Some(&self.bytes[sub_off..])
}
pub fn mark_filtering_set(&self) -> Option<u16> {
if self.flag.use_mark_filtering_set() {
Some(self.mark_filtering_set)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct LayoutHeader {
pub(crate) major: u16,
pub(crate) minor: u16,
pub(crate) script_list_off: u16,
pub(crate) feature_list_off: u16,
pub(crate) lookup_list_off: u16,
pub(crate) feature_variations_off: u32,
}
impl LayoutHeader {
pub(crate) fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 10 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
let minor = read_u16(bytes, 2)?;
if major != 1 || minor > 1 {
return Err(Error::BadStructure(
"GSUB/GPOS: only version 1.0 / 1.1 are defined",
));
}
let script_list_off = read_u16(bytes, 4)?;
let feature_list_off = read_u16(bytes, 6)?;
let lookup_list_off = read_u16(bytes, 8)?;
let feature_variations_off = if minor == 1 {
if bytes.len() < 14 {
return Err(Error::UnexpectedEof);
}
read_u32(bytes, 10)?
} else {
0
};
Ok(Self {
major,
minor,
script_list_off,
feature_list_off,
lookup_list_off,
feature_variations_off,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn be(u: u16) -> [u8; 2] {
u.to_be_bytes()
}
fn be32(u: u32) -> [u8; 4] {
u.to_be_bytes()
}
#[test]
fn header_v10_round_trip() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&be(1));
bytes.extend_from_slice(&be(0));
bytes.extend_from_slice(&be(10));
bytes.extend_from_slice(&be(20));
bytes.extend_from_slice(&be(30));
let h = LayoutHeader::parse(&bytes).unwrap();
assert_eq!(h.major, 1);
assert_eq!(h.minor, 0);
assert_eq!(h.script_list_off, 10);
assert_eq!(h.feature_list_off, 20);
assert_eq!(h.lookup_list_off, 30);
assert_eq!(h.feature_variations_off, 0);
}
#[test]
fn header_v11_round_trip() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&be(1));
bytes.extend_from_slice(&be(1));
bytes.extend_from_slice(&be(14));
bytes.extend_from_slice(&be(24));
bytes.extend_from_slice(&be(34));
bytes.extend_from_slice(&be32(99_999));
let h = LayoutHeader::parse(&bytes).unwrap();
assert_eq!(h.minor, 1);
assert_eq!(h.feature_variations_off, 99_999);
}
#[test]
fn header_rejects_unknown_versions() {
let bytes_v2 = {
let mut b = vec![0u8; 14];
b[0..2].copy_from_slice(&be(2));
b
};
assert!(matches!(
LayoutHeader::parse(&bytes_v2),
Err(Error::BadStructure(_))
));
let bytes_v12 = {
let mut b = vec![0u8; 14];
b[0..2].copy_from_slice(&be(1));
b[2..4].copy_from_slice(&be(2));
b
};
assert!(matches!(
LayoutHeader::parse(&bytes_v12),
Err(Error::BadStructure(_))
));
}
#[test]
fn synthetic_round_trip() {
let mut lang_sys = Vec::new();
lang_sys.extend_from_slice(&be(0));
lang_sys.extend_from_slice(&be(NO_REQUIRED_FEATURE));
lang_sys.extend_from_slice(&be(2));
lang_sys.extend_from_slice(&be(0));
lang_sys.extend_from_slice(&be(1));
let ls = LangSys::parse(&lang_sys).unwrap();
assert!(ls.required_feature_index().is_none());
assert_eq!(ls.feature_count(), 2);
assert_eq!(ls.feature_index(0), Some(0));
assert_eq!(ls.feature_index(1), Some(1));
assert_eq!(ls.feature_index(2), None);
let v: Vec<_> = ls.feature_indices().collect();
assert_eq!(v, vec![0, 1]);
let mut feature = Vec::new();
feature.extend_from_slice(&be(0));
feature.extend_from_slice(&be(1));
feature.extend_from_slice(&be(0));
let f = Feature::parse(&feature).unwrap();
assert_eq!(f.feature_params_offset(), 0);
assert_eq!(f.lookup_count(), 1);
assert_eq!(f.lookup_index(0), Some(0));
let mut lookup = Vec::new();
lookup.extend_from_slice(&be(1));
lookup.extend_from_slice(&be(0));
lookup.extend_from_slice(&be(0));
let l = Lookup::parse(&lookup).unwrap();
assert_eq!(l.lookup_type(), 1);
assert_eq!(l.subtable_count(), 0);
assert!(l.mark_filtering_set().is_none());
assert!(!l.flag().right_to_left());
assert!(!l.flag().ignore_marks());
}
#[test]
fn lookup_with_mark_filtering_set() {
let mut lookup = Vec::new();
lookup.extend_from_slice(&be(4));
lookup.extend_from_slice(&be(LookupFlag::USE_MARK_FILTERING_SET));
lookup.extend_from_slice(&be(0));
lookup.extend_from_slice(&be(7));
let l = Lookup::parse(&lookup).unwrap();
assert!(l.flag().use_mark_filtering_set());
assert_eq!(l.mark_filtering_set(), Some(7));
}
#[test]
fn lookup_flag_helpers() {
let f = LookupFlag(0x0A0E);
assert!(!f.right_to_left());
assert!(f.ignore_base_glyphs());
assert!(f.ignore_ligatures());
assert!(f.ignore_marks());
assert!(!f.use_mark_filtering_set());
assert_eq!(f.mark_attachment_type(), 0x0A);
}
#[test]
fn truncation_surfaces_unexpected_eof() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&be(3));
bytes.extend_from_slice(b"DFLT");
bytes.extend_from_slice(&be(0));
bytes.extend_from_slice(b"latn");
bytes.extend_from_slice(&be(0));
assert!(matches!(
ScriptList::parse(&bytes),
Err(Error::UnexpectedEof)
));
let mut lookup = Vec::new();
lookup.extend_from_slice(&be(4));
lookup.extend_from_slice(&be(LookupFlag::USE_MARK_FILTERING_SET));
lookup.extend_from_slice(&be(0));
assert!(matches!(Lookup::parse(&lookup), Err(Error::UnexpectedEof)));
}
}