//! OpenType advanced typography tables. use super::{raw_tag, Bytes, RawTag}; pub const GDEF: RawTag = raw_tag(b"GDEF"); pub const GSUB: RawTag = raw_tag(b"GSUB"); pub const GPOS: RawTag = raw_tag(b"GPOS"); pub const DFLT: RawTag = raw_tag(b"DFLT"); /// Glyph definition table. #[derive(Copy, Clone)] pub struct Gdef<'a> { data: Bytes<'a>, classes: u16, mark_classes: u16, mark_sets: u16, var_store: u32, } impl<'a> Gdef<'a> { pub fn new(data: &'a [u8]) -> Option { let b = Bytes::new(data); let major = b.read::(0)?; let minor = b.read::(2)?; let classes = b.read::(4)?; let mark_classes = b.read::(10)?; let mark_sets = if major > 1 || minor >= 2 { b.read_or_default::(12) } else { 0 }; let var_store = if major > 1 || minor >= 3 { b.read_or_default::(14) } else { 0 }; Some(Self { data: b, classes, mark_classes, mark_sets, var_store, }) } pub fn from_offset(data: &'a [u8], offset: u32) -> Option { if offset == 0 { return None; } Self::new(data.get(offset as usize..)?) } pub fn empty() -> Self { Self { data: Bytes::new(&[]), classes: 0, mark_classes: 0, mark_sets: 0, var_store: 0, } } pub fn ok(&self) -> bool { self.data.len() != 0 } /// Returns true if glyph classes are available. pub fn has_classes(&self) -> bool { self.classes != 0 } /// Returns the class for the specified glyph id. pub fn class(&self, glyph_id: u16) -> u16 { classdef(&self.data, self.classes as u32, glyph_id) } /// Returns true if mark glyph classes are available. pub fn has_mark_classes(&self) -> bool { self.mark_classes != 0 } /// Returns the mark class for the specified glyph id. pub fn mark_class(&self, glyph_id: u16) -> u16 { classdef(&self.data, self.mark_classes as u32, glyph_id) } pub fn mark_set_coverage(&self, set_offset: u32, glyph_id: u16) -> Option { if set_offset == 0 { return None; } // Coverage is validated by mark_set_offset() below. unsafe { fast_coverage(&self.data, set_offset, glyph_id) } } pub fn mark_set_offset(&self, set_index: u16) -> Option { if self.mark_sets == 0 { return None; } let set = set_index as usize; let b = &self.data; let sets_base = self.mark_sets as usize; let len = b.read::(sets_base + 2)? as usize; if set >= len { return None; } let offset = b.read::(sets_base + 4 + set * 4)?; let set_offset = sets_base as u32 + offset; (offset != 0 && validate_coverage(b, set_offset)).then_some(set_offset) } pub fn has_var_store(&self) -> bool { self.var_store != 0 } pub fn delta(&self, outer: u16, inner: u16, coords: &[i16]) -> f32 { if self.var_store != 0 { super::var::item_delta(self.data.data(), self.var_store, outer, inner, coords) .map(|d| d.to_f32()) .unwrap_or(0.) } else { 0. } } } /// Feature lookup kind. #[derive(Copy, Clone, PartialEq, Debug)] #[repr(u8)] pub enum LookupKind { SingleSub, MultiSub, AltSub, LigSub, SingleAdj, PairAdj, Cursive, MarkToBase, MarkToLig, MarkToMark, Context, ChainContext, RevChainContext, } /// Data associated with a feature lookup. #[derive(Copy, Clone)] pub struct LookupData { pub index: u16, pub stage: u8, pub kind: LookupKind, pub feature: u16, pub mask: u8, pub ignored: u8, pub is_ext: bool, pub offset: u32, pub coverage: u32, pub count: u16, pub subtables: (u16, u16), pub mark_set: u32, pub mark_check: u8, pub mark_class: u8, } impl LookupData { pub fn subtable_data(&self, b: &Bytes, index: u16) -> Option { let base = self.offset as usize; let subtable_base = base + 6; let mut offset = base + b.read::(subtable_base + index as usize * 2)? as usize; if self.is_ext { offset = offset + b.read::(offset + 4)? as usize; } let fmt = b.read::(offset)?; subtable_data(b, offset as u32, self.kind, fmt) } } /// Lookup subtable kind, flattened to include the associated format. #[derive(Copy, Clone, PartialEq, Debug)] #[repr(u8)] pub enum SubtableKind { SingleSub1, SingleSub2, MultiSub1, AltSub1, LigSub1, SingleAdj1, SingleAdj2, PairAdj1, PairAdj2, Cursive1, MarkToBase1, MarkToLig1, MarkToMark1, Context1, Context2, Context3, ChainContext1, ChainContext2, ChainContext3, RevChainContext1, } /// Data associated with a lookup subtable. #[derive(Copy, Clone)] pub struct SubtableData { pub offset: u32, pub kind: SubtableKind, pub coverage: u16, } impl SubtableData { pub fn coverage(&self, b: &Bytes, glyph_id: u16) -> Option { unsafe { fast_coverage(b, self.offset + self.coverage as u32, glyph_id) } } } /// Feature substitutions for variable fonts. #[derive(Copy, Clone)] pub struct FeatureSubsts(u32); impl FeatureSubsts { pub fn new(b: &Bytes, offset: u32, coords: &[i16]) -> Option { if offset == 0 || coords.is_empty() { return None; } let base = offset as usize; let count = b.read::(base + 4)? as usize; for i in 0..count { let rec = base + 8 + i * 8; let condset_table = base + b.read::(rec)? as usize; let condset_count = b.read::(condset_table)? as usize; let mut matched = 0; for j in 0..condset_count { let cond_table = condset_table + b.read::(condset_table + 2 + j * 4)? as usize; let format = b.read::(cond_table)?; if format != 1 { break; } let axis = b.read::(cond_table + 2)? as usize; if axis >= coords.len() { break; } let coord = coords[axis]; let min = b.read::(cond_table + 4)?; if coord < min { break; } let max = b.read::(cond_table + 6)?; if coord > max { break; } matched += 1; } if matched == condset_count { return Some(Self(offset + b.read::(rec + 4)?)); } } None } pub fn apply(self, b: &Bytes, index: u16) -> Option { let mut base = self.0 as usize; let count = b.read::(base + 4)? as usize; base += 6; let mut l = 0; let mut h = count; while l < h { use core::cmp::Ordering::*; let i = (l + h) / 2; let rec = base + i * 6; let idx = b.read::(rec)?; match index.cmp(&idx) { Less => h = i, Greater => l = i + 1, Equal => return Some((self.0 + b.read::(rec + 2)?) as usize), } } None } } pub fn script_count(b: &Bytes, gsubgpos_offset: u32) -> u16 { if gsubgpos_offset == 0 { return 0; } let base = gsubgpos_offset as usize; let offset = b.read_or_default::(base + 4) as usize; if offset == 0 { return 0; } b.read_or_default::(base + offset) } pub fn script_at(b: &Bytes, gsubgpos_offset: u32, index: u16) -> Option<(RawTag, u32)> { if gsubgpos_offset == 0 { return None; } let base = gsubgpos_offset as usize; let sbase = base + b.read::(base + 4)? as usize; let rec = sbase + 2 + index as usize * 6; let tag = b.read::(rec)?; let offset = sbase as u32 + b.read::(rec + 4)? as u32; Some((tag, offset)) } pub fn script_by_tag(b: &Bytes, gsubgpos_offset: u32, script: RawTag) -> Option { if gsubgpos_offset == 0 { return None; } let base = gsubgpos_offset as usize; let sbase = base + b.read::(base + 4)? as usize; let mut l = 0; let mut h = b.read::(sbase)? as usize; while l < h { use core::cmp::Ordering::*; let i = l + (h - l) / 2; let rec = sbase + 2 + i * 6; let t = b.read::(rec)?; match script.cmp(&t) { Less => h = i, Greater => l = i + 1, Equal => return Some(sbase as u32 + b.read::(rec + 4)? as u32), } } None } pub fn script_language_count(b: &Bytes, script_offset: u32) -> u16 { if script_offset == 0 { return 0; } b.read::(script_offset as usize + 2) .map(|n| n + 1) .unwrap_or(0) } pub fn script_default_language(b: &Bytes, script_offset: u32) -> Option { if script_offset == 0 { return None; } let offset = b.read::(script_offset as usize)? as u32; if offset == 0 { None } else { Some(script_offset + offset) } } pub fn script_language_at(b: &Bytes, script_offset: u32, index: u16) -> Option<(RawTag, u32)> { if script_offset == 0 { return None; } let index = if index == 0 { return Some((DFLT, script_default_language(b, script_offset)?)); } else { index - 1 }; let rec = script_offset as usize + 4 + index as usize * 6; let tag = b.read::(rec)?; let offset = b.read::(rec + 4)? as u32; if offset == 0 { return None; } Some((tag, script_offset + offset)) } pub fn script_language_by_tag( b: &Bytes, script_offset: u32, language: Option, ) -> Option<(u32, bool)> { if script_offset == 0 { return None; } let base = script_offset as usize; if let Some(lang) = language { let mut l = 0; let mut h = b.read::(base + 2)? as usize; while l < h { use core::cmp::Ordering::*; let i = (l + h) / 2; let rec = base + 4 + i * 6; let t = b.read::(rec)?; match lang.cmp(&t) { Less => h = i, Greater => l = i + 1, Equal => { let lang_offset = b.read::(rec + 4)? as usize; if lang_offset == 0 { return None; } return Some((script_offset + lang_offset as u32, false)); } } } } let default = b.read::(base)? as usize; if default == 0 { return None; } Some(((base + default) as u32, true)) } pub fn language_or_default_by_tags( b: &Bytes, gsubgpos_offset: u32, script: RawTag, lang: Option, ) -> Option<(u32, [RawTag; 2])> { if let Some(script_offset) = script_by_tag(b, gsubgpos_offset, script) { let (lang_offset, is_default) = script_language_by_tag(b, script_offset, lang)?; Some(( lang_offset, [ script, if is_default { DFLT } else { lang.unwrap_or(DFLT) }, ], )) } else { let (lang_offset, is_default) = language_by_tags(b, gsubgpos_offset, DFLT, lang)?; Some(( lang_offset, [ DFLT, if is_default { DFLT } else { lang.unwrap_or(DFLT) }, ], )) } } pub fn language_by_tags( b: &Bytes, gsubgpos_offset: u32, script: RawTag, language: Option, ) -> Option<(u32, bool)> { script_language_by_tag(b, script_by_tag(b, gsubgpos_offset, script)?, language) } pub fn language_feature_count(b: &Bytes, language_offset: u32) -> u16 { if language_offset == 0 { return 0; } b.read_or_default(language_offset as usize + 4) } pub fn language_feature_at(b: &Bytes, language_offset: u32, index: u16) -> Option { b.read(language_offset as usize + 6 + index as usize * 2) } pub fn language_features<'a>( b: Bytes<'a>, gsubgpos_offset: u32, language_offset: u32, ) -> impl Iterator + 'a + Clone { let mut count = language_feature_count(&b, language_offset); if gsubgpos_offset == 0 { count = 0; } let base = gsubgpos_offset as usize; let fbase = b.read_or_default::(base + 6) as usize; if fbase == 0 { count = 0; } let fbase = base + fbase; (0..count).filter_map(move |i| { let index = language_feature_at(&b, language_offset, i)?; let rec = fbase + 2 + index as usize * 6; let tag = b.read::(rec)?; let offset = b.read::(rec + 4)?; if offset == 0 { return None; } Some((tag, fbase as u32 + offset as u32)) }) } pub fn feature_count(b: &Bytes, gsubgpos_offset: u32) -> u16 { if gsubgpos_offset == 0 { return 0; } let base = gsubgpos_offset as usize; let fbase = b.read_or_default::(base + 6) as usize; if fbase == 0 { return 0; } b.read_or_default::(base + fbase) } pub fn feature_at(b: &Bytes, gsubgpos_offset: u32, index: u16) -> Option<(RawTag, u32)> { if gsubgpos_offset == 0 { return None; } let base = gsubgpos_offset as usize; let fbase = b.read::(base + 6)? as usize; if fbase == 0 { return None; } let fbase = base + fbase; let rec = fbase + 2 + index as usize * 6; let tag = b.read::(rec)?; let offset = b.read::(rec + 4)?; if offset == 0 { return None; } Some((tag, fbase as u32 + offset as u32)) } pub fn feature_var_offset(b: &Bytes, gsubgpos_offset: u32) -> u32 { if gsubgpos_offset == 0 { return 0; } let base = gsubgpos_offset as usize; let major = b.read_or_default::(base); if major > 1 || (major == 1 && b.read_or_default::(base + 2) >= 1) { let offset = b.read_or_default::(base + 10); if offset != 0 { gsubgpos_offset + offset } else { 0 } } else { 0 } } pub fn lookup_data( b: &Bytes, stage: u8, list_base: u32, index: u16, mask: u8, gdef: Option<&Gdef>, ) -> Option { if list_base == 0 { return None; } let base = list_base as usize; let rec = base + 2 + index as usize * 2; let offset = b.read::(rec)?; let base = base + offset as usize; let mut kind = b.read::(base)? as u8; let flag = b.read::(base + 2)?; let f = flag as u8; let count = b.read::(base + 4)?; let mark_class = (flag >> 8) as u8; let ignore_marks = f & (1 << 3) != 0; let mut mark_check = 0; let mut mark_set = 0; if !ignore_marks { if let Some(gdef) = gdef { mark_check = (mark_class != 0 && gdef.has_mark_classes()) as u8; mark_set = if gdef.ok() && flag & 0x10 != 0 { let idx = b.read::(base + 6 + count as usize * 2)?; mark_check = 1; gdef.mark_set_offset(idx).unwrap_or(0) } else { 0 }; } } let is_sub = stage == 0; let subtables = base + 6; let is_ext = (is_sub && kind == 7) || (!is_sub && kind == 9); if is_ext && count > 0 { let s = base + b.read::(subtables)? as usize; kind = b.read::(s + 2)? as u8; } use LookupKind::*; let kind = if stage == 0 { match kind { 1 => SingleSub, 2 => MultiSub, 3 => AltSub, 4 => LigSub, 5 => Context, 6 => ChainContext, 8 => RevChainContext, _ => return None, } } else { match kind { 1 => SingleAdj, 2 => PairAdj, 3 => Cursive, 4 => MarkToBase, 5 => MarkToLig, 6 => MarkToMark, 7 => Context, 8 => ChainContext, _ => return None, } }; let ignored = (f & 0b1110) | 1 << 5; Some(LookupData { index, stage, kind, feature: 0, mask, ignored, is_ext, offset: base as u32, count, coverage: !0, subtables: (0, 0), mark_class, mark_set, mark_check, }) } pub fn subtable_data(b: &Bytes, offset: u32, kind: LookupKind, fmt: u16) -> Option { let base = offset as usize; fn cov(b: &Bytes, base: usize, offset: usize) -> Option { let c = b.read::(base + offset)?; validate_coverage(b, base as u32 + c as u32).then_some(c) } use LookupKind::*; match kind { SingleSub => { let kind = match fmt { 1 => SubtableKind::SingleSub1, 2 => SubtableKind::SingleSub2, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } MultiSub => { let kind = match fmt { 1 => SubtableKind::MultiSub1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } AltSub => { let kind = match fmt { 1 => SubtableKind::AltSub1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } LigSub => { let kind = match fmt { 1 => SubtableKind::LigSub1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } SingleAdj => { let kind = match fmt { 1 => SubtableKind::SingleAdj1, 2 => SubtableKind::SingleAdj2, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } PairAdj => { let kind = match fmt { 1 => SubtableKind::PairAdj1, 2 => SubtableKind::PairAdj2, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } Cursive => { let kind = match fmt { 1 => SubtableKind::Cursive1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } MarkToBase => { let kind = match fmt { 1 => SubtableKind::MarkToBase1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } MarkToLig => { let kind = match fmt { 1 => SubtableKind::MarkToLig1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } MarkToMark => { let kind = match fmt { 1 => SubtableKind::MarkToMark1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } Context => match fmt { 1 | 2 => { let kind = if fmt == 1 { SubtableKind::Context1 } else { SubtableKind::Context2 }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } 3 => { let coverage = cov(b, base, 6)?; Some(SubtableData { kind: SubtableKind::Context3, offset, coverage, }) } _ => None, }, ChainContext => match fmt { 1 | 2 => { let kind = if fmt == 1 { SubtableKind::ChainContext1 } else { SubtableKind::ChainContext2 }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } 3 => { let backtrack_len = b.read::(base + 2)? as usize * 2; let input_len = b.read::(base + backtrack_len + 4)?; if input_len == 0 { return None; } let coverage = cov(b, base, backtrack_len + 6)?; Some(SubtableData { kind: SubtableKind::ChainContext3, offset, coverage, }) } _ => None, }, RevChainContext => { let kind = match fmt { 1 => SubtableKind::RevChainContext1, _ => return None, }; let coverage = cov(b, base, 2)?; Some(SubtableData { offset, kind, coverage, }) } } } fn validate_coverage(b: &Bytes, coverage_offset: u32) -> bool { if coverage_offset == 0 { return false; } let base = coverage_offset as usize; let arr = base + 4; match (b.read::(base), b.read::(base + 2)) { // Empty subtable coverage is useless, so mark empty coverage subtables as invalid. (Some(_), Some(0)) => false, (Some(1), Some(len)) => b.check_range(arr, len as usize * 2), (Some(2), Some(len)) => b.check_range(arr, len as usize * 6), _ => false, } } pub unsafe fn fast_coverage(b: &Bytes, coverage_offset: u32, glyph_id: u16) -> Option { let base = coverage_offset as usize; let fmt = b.read_unchecked::(base); let len = b.read_unchecked::(base + 2) as usize; let arr = base + 4; if fmt == 1 { let mut l = 0; let mut h = len; while l < h { use core::cmp::Ordering::*; let i = (l + h) / 2; let g = b.read_unchecked::(arr + i * 2); match glyph_id.cmp(&g) { Less => h = i, Greater => l = i + 1, Equal => return Some(i as u16), } } } else if fmt == 2 { let mut l = 0; let mut h = len; while l < h { let i = (l + h) / 2; let rec = arr + i * 6; let start = b.read_unchecked::(rec); if glyph_id < start { h = i; } else if glyph_id > b.read_unchecked::(rec + 2) { l = i + 1; } else { let base = b.read_unchecked::(rec + 4); return Some(base + glyph_id - start); } } } None } pub fn coverage(b: &Bytes, coverage_offset: u32, glyph_id: u16) -> Option { if coverage_offset == 0 { return None; } let base = coverage_offset as usize; let fmt = b.read::(base)?; let len = b.read::(base + 2)? as usize; let arr = base + 4; if fmt == 1 { if !b.check_range(arr, len * 2) { return None; } let mut l = 0; let mut h = len; while l < h { use core::cmp::Ordering::*; let i = (l + h) / 2; let g = unsafe { b.read_unchecked::(arr + i * 2) }; match glyph_id.cmp(&g) { Less => h = i, Greater => l = i + 1, Equal => return Some(i as u16), } } } else if fmt == 2 { if !b.check_range(arr, len * 6) { return None; } let mut l = 0; let mut h = len; while l < h { let i = (l + h) / 2; let rec = arr + i * 6; let start = unsafe { b.read_unchecked::(rec) }; if glyph_id < start { h = i; } else if glyph_id > unsafe { b.read_unchecked::(rec + 2) } { l = i + 1; } else { let base = unsafe { b.read_unchecked::(rec + 4) }; return Some(base + (glyph_id - start)); } } } None } pub fn classdef(b: &Bytes, classdef_offset: u32, glyph_id: u16) -> u16 { if classdef_offset == 0 { return 0; } let base = classdef_offset as usize; let fmt = b.read_or_default::(base); if fmt == 1 { let start = b.read_or_default::(base + 2); let len = b.read_or_default::(base + 4); let end = start + len - 1; let arr = base + 6; if glyph_id >= start && glyph_id <= end { return b.read_or_default::(arr + (glyph_id - start) as usize * 2); } return 0; } else if fmt == 2 { let len = b.read_or_default::(base + 2) as usize; let arr = base + 4; if !b.check_range(arr, len * 6) { return 0; } let mut l = 0; let mut h = len; while l < h { let i = (l + h) / 2; let rec = arr + i * 6; let start = unsafe { b.read_unchecked::(rec) }; if glyph_id < start { h = i; } else if glyph_id > unsafe { b.read_unchecked::(rec + 2) } { l = i + 1; } else { return unsafe { b.read_unchecked::(rec + 4) }; } } } 0 }