use crate::parser::{read_i16, read_u16, read_u32};
use crate::Error;
#[allow(dead_code)]
pub const CLASS_BASE: u16 = 1;
#[allow(dead_code)]
pub const CLASS_LIGATURE: u16 = 2;
pub const CLASS_MARK: u16 = 3;
#[allow(dead_code)]
pub const CLASS_COMPONENT: u16 = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaretValue {
DesignUnits(i16),
ContourPoint(u16),
DesignUnitsWithDevice { coordinate: i16, device_offset: u16 },
}
#[derive(Debug, Clone)]
pub struct GdefTable<'a> {
bytes: &'a [u8],
glyph_class_def_off: Option<u32>,
attach_list_off: Option<u32>,
lig_caret_list_off: Option<u32>,
mark_attach_class_def_off: Option<u32>,
mark_glyph_sets_def_off: Option<u32>,
item_var_store_off: Option<u32>,
}
impl<'a> GdefTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 12 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
let minor = read_u16(bytes, 2)?;
if major != 1 {
return Err(Error::BadStructure("GDEF: unsupported version"));
}
let glyph_class_def_off = nz_off16(bytes, 4)?;
let attach_list_off = nz_off16(bytes, 6)?;
let lig_caret_list_off = nz_off16(bytes, 8)?;
let mark_attach_class_def_off = nz_off16(bytes, 10)?;
let mark_glyph_sets_def_off = if minor >= 2 {
if bytes.len() < 14 {
return Err(Error::UnexpectedEof);
}
nz_off16(bytes, 12)?
} else {
None
};
let item_var_store_off = if minor >= 3 {
if bytes.len() < 18 {
return Err(Error::UnexpectedEof);
}
let raw = read_u32(bytes, 14)?;
if raw == 0 {
None
} else {
Some(raw)
}
} else {
None
};
Ok(Self {
bytes,
glyph_class_def_off,
attach_list_off,
lig_caret_list_off,
mark_attach_class_def_off,
mark_glyph_sets_def_off,
item_var_store_off,
})
}
pub fn glyph_class(&self, glyph_id: u16) -> u16 {
let off = match self.glyph_class_def_off {
Some(o) => o as usize,
None => return 0,
};
let sub = match self.bytes.get(off..) {
Some(s) => s,
None => return 0,
};
class_def_lookup(sub, glyph_id).unwrap_or(0)
}
pub fn is_mark(&self, glyph_id: u16) -> bool {
self.glyph_class(glyph_id) == CLASS_MARK
}
pub fn mark_attach_class(&self, glyph_id: u16) -> u16 {
let off = match self.mark_attach_class_def_off {
Some(o) => o as usize,
None => return 0,
};
let sub = match self.bytes.get(off..) {
Some(s) => s,
None => return 0,
};
class_def_lookup(sub, glyph_id).unwrap_or(0)
}
pub fn attach_points(&self, glyph_id: u16) -> Option<Vec<u16>> {
let base = self.attach_list_off? as usize;
let sub = self.bytes.get(base..)?;
if sub.len() < 4 {
return None;
}
let cov_off = read_u16(sub, 0).ok()? as usize;
let count = read_u16(sub, 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
let cov_idx = coverage_lookup(cov, glyph_id)? as usize;
if cov_idx >= count {
return None;
}
let attach_off = read_u16(sub, 4 + cov_idx * 2).ok()? as usize;
let ap = sub.get(attach_off..)?;
if ap.len() < 2 {
return None;
}
let n = read_u16(ap, 0).ok()? as usize;
if ap.len() < 2 + n * 2 {
return None;
}
let mut out = Vec::with_capacity(n);
for i in 0..n {
out.push(read_u16(ap, 2 + i * 2).ok()?);
}
Some(out)
}
pub fn ligature_carets(&self, glyph_id: u16) -> Option<Vec<CaretValue>> {
let base = self.lig_caret_list_off? as usize;
let sub = self.bytes.get(base..)?;
if sub.len() < 4 {
return None;
}
let cov_off = read_u16(sub, 0).ok()? as usize;
let count = read_u16(sub, 2).ok()? as usize;
let cov = sub.get(cov_off..)?;
let cov_idx = coverage_lookup(cov, glyph_id)? as usize;
if cov_idx >= count {
return None;
}
let lg_off = read_u16(sub, 4 + cov_idx * 2).ok()? as usize;
let lg = sub.get(lg_off..)?;
if lg.len() < 2 {
return None;
}
let caret_count = read_u16(lg, 0).ok()? as usize;
if lg.len() < 2 + caret_count * 2 {
return None;
}
let mut out = Vec::with_capacity(caret_count);
for i in 0..caret_count {
let cv_off = read_u16(lg, 2 + i * 2).ok()? as usize;
let cv = lg.get(cv_off..)?;
out.push(parse_caret_value(cv)?);
}
Some(out)
}
pub fn mark_glyph_set_count(&self) -> u16 {
self.mark_glyph_set_sub()
.and_then(|sub| {
if sub.len() < 4 {
return None;
}
read_u16(sub, 2).ok()
})
.unwrap_or(0)
}
pub fn mark_glyph_set_contains(&self, set_index: u16, glyph_id: u16) -> bool {
let sub = match self.mark_glyph_set_sub() {
Some(s) => s,
None => return false,
};
if sub.len() < 4 {
return false;
}
let format = match read_u16(sub, 0) {
Ok(v) => v,
Err(_) => return false,
};
if format != 1 {
return false;
}
let count = match read_u16(sub, 2) {
Ok(v) => v as usize,
Err(_) => return false,
};
if set_index as usize >= count {
return false;
}
let cov_off_pos = 4 + set_index as usize * 4;
if sub.len() < cov_off_pos + 4 {
return false;
}
let cov_off = match read_u32(sub, cov_off_pos) {
Ok(v) => v as usize,
Err(_) => return false,
};
let cov = match sub.get(cov_off..) {
Some(s) => s,
None => return false,
};
coverage_lookup(cov, glyph_id).is_some()
}
fn mark_glyph_set_sub(&self) -> Option<&[u8]> {
let off = self.mark_glyph_sets_def_off? as usize;
self.bytes.get(off..)
}
pub fn item_var_store_bytes(&self) -> Option<&'a [u8]> {
let off = self.item_var_store_off? as usize;
self.bytes.get(off..)
}
}
fn parse_caret_value(bytes: &[u8]) -> Option<CaretValue> {
if bytes.len() < 4 {
return None;
}
let format = read_u16(bytes, 0).ok()?;
match format {
1 => {
let v = read_i16(bytes, 2).ok()?;
Some(CaretValue::DesignUnits(v))
}
2 => {
let p = read_u16(bytes, 2).ok()?;
Some(CaretValue::ContourPoint(p))
}
3 => {
if bytes.len() < 6 {
return None;
}
let v = read_i16(bytes, 2).ok()?;
let dev = read_u16(bytes, 4).ok()?;
Some(CaretValue::DesignUnitsWithDevice {
coordinate: v,
device_offset: dev,
})
}
_ => None,
}
}
fn nz_off16(bytes: &[u8], off: usize) -> Result<Option<u32>, Error> {
let raw = read_u16(bytes, off)? as u32;
Ok(if raw == 0 { None } else { Some(raw) })
}
pub(crate) fn class_def_lookup(bytes: &[u8], glyph_id: u16) -> Option<u16> {
if bytes.len() < 2 {
return None;
}
let format = read_u16(bytes, 0).ok()?;
match format {
1 => {
if bytes.len() < 6 {
return None;
}
let start = read_u16(bytes, 2).ok()?;
let count = read_u16(bytes, 4).ok()?;
if glyph_id < start {
return None;
}
let idx = glyph_id - start;
if idx >= count {
return None;
}
let val = read_u16(bytes, 6 + idx as usize * 2).ok()?;
if val == 0 {
None
} else {
Some(val)
}
}
2 => {
if bytes.len() < 4 {
return None;
}
let n = read_u16(bytes, 2).ok()? as usize;
let header = 4usize;
let mut lo = 0usize;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
let off = header + mid * 6;
let s = read_u16(bytes, off).ok()?;
let e = read_u16(bytes, off + 2).ok()?;
if glyph_id < s {
hi = mid;
} else if glyph_id > e {
lo = mid + 1;
} else {
let v = read_u16(bytes, off + 4).ok()?;
return if v == 0 { None } else { Some(v) };
}
}
None
}
_ => None,
}
}
pub(crate) fn coverage_lookup(bytes: &[u8], glyph_id: u16) -> Option<u16> {
if bytes.len() < 4 {
return None;
}
let format = read_u16(bytes, 0).ok()?;
match format {
1 => {
let count = read_u16(bytes, 2).ok()? as usize;
let mut lo = 0usize;
let mut hi = count;
while lo < hi {
let mid = (lo + hi) / 2;
let g = read_u16(bytes, 4 + mid * 2).ok()?;
if g == glyph_id {
return Some(mid as u16);
}
if g < glyph_id {
lo = mid + 1;
} else {
hi = mid;
}
}
None
}
2 => {
let n = read_u16(bytes, 2).ok()? as usize;
let header = 4usize;
let mut lo = 0usize;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
let off = header + mid * 6;
let s = read_u16(bytes, off).ok()?;
let e = read_u16(bytes, off + 2).ok()?;
let start_idx = read_u16(bytes, off + 4).ok()?;
if glyph_id < s {
hi = mid;
} else if glyph_id > e {
lo = mid + 1;
} else {
return Some(start_idx + (glyph_id - s));
}
}
None
}
_ => None,
}
}
pub(crate) fn popcount_u16(v: u16) -> usize {
v.count_ones() as usize
}
pub(crate) fn lookup_table_slice(
table_bytes: &[u8],
lookup_list_off: u32,
lookup_index: u16,
) -> Option<&[u8]> {
let lookup_list = table_bytes.get(lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let count = read_u16(lookup_list, 0).ok()?;
if lookup_index >= count {
return None;
}
let off = read_u16(lookup_list, 2 + lookup_index as usize * 2).ok()? as usize;
let lookup_off_abs = lookup_list_off as usize + off;
table_bytes.get(lookup_off_abs..)
}
#[allow(dead_code)]
pub(crate) fn offset16(table_bytes: &[u8], abs_off: usize) -> Result<u32, Error> {
Ok(read_u16(table_bytes, abs_off)? as u32)
}
#[allow(dead_code)]
pub(crate) fn offset32(table_bytes: &[u8], abs_off: usize) -> Result<u32, Error> {
read_u32(table_bytes, abs_off)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn class_def_format1_lookup() {
let mut b = vec![0u8; 12];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[2..4].copy_from_slice(&10u16.to_be_bytes());
b[4..6].copy_from_slice(&3u16.to_be_bytes());
b[6..8].copy_from_slice(&1u16.to_be_bytes());
b[8..10].copy_from_slice(&3u16.to_be_bytes());
b[10..12].copy_from_slice(&2u16.to_be_bytes());
assert_eq!(class_def_lookup(&b, 10), Some(1));
assert_eq!(class_def_lookup(&b, 11), Some(3));
assert_eq!(class_def_lookup(&b, 12), Some(2));
assert_eq!(class_def_lookup(&b, 13), None);
assert_eq!(class_def_lookup(&b, 9), None);
}
#[test]
fn coverage_format1() {
let mut b = vec![0u8; 4 + 6];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[2..4].copy_from_slice(&3u16.to_be_bytes());
b[4..6].copy_from_slice(&5u16.to_be_bytes());
b[6..8].copy_from_slice(&10u16.to_be_bytes());
b[8..10].copy_from_slice(&15u16.to_be_bytes());
assert_eq!(coverage_lookup(&b, 5), Some(0));
assert_eq!(coverage_lookup(&b, 10), Some(1));
assert_eq!(coverage_lookup(&b, 15), Some(2));
assert_eq!(coverage_lookup(&b, 11), None);
}
#[test]
fn coverage_format2_indexes_correctly() {
let mut b = vec![0u8; 4 + 12];
b[0..2].copy_from_slice(&2u16.to_be_bytes());
b[2..4].copy_from_slice(&2u16.to_be_bytes());
b[4..6].copy_from_slice(&10u16.to_be_bytes());
b[6..8].copy_from_slice(&12u16.to_be_bytes());
b[8..10].copy_from_slice(&0u16.to_be_bytes());
b[10..12].copy_from_slice(&50u16.to_be_bytes());
b[12..14].copy_from_slice(&51u16.to_be_bytes());
b[14..16].copy_from_slice(&3u16.to_be_bytes());
assert_eq!(coverage_lookup(&b, 10), Some(0));
assert_eq!(coverage_lookup(&b, 12), Some(2));
assert_eq!(coverage_lookup(&b, 50), Some(3));
assert_eq!(coverage_lookup(&b, 51), Some(4));
assert_eq!(coverage_lookup(&b, 13), None);
}
#[test]
fn gdef_class_marks_correctly() {
let class_def_off: u16 = 12;
let mut t = vec![0u8; class_def_off as usize];
t[0..2].copy_from_slice(&1u16.to_be_bytes()); t[2..4].copy_from_slice(&0u16.to_be_bytes()); t[4..6].copy_from_slice(&class_def_off.to_be_bytes()); let mut cd = vec![0u8; 8];
cd[0..2].copy_from_slice(&1u16.to_be_bytes());
cd[2..4].copy_from_slice(&99u16.to_be_bytes());
cd[4..6].copy_from_slice(&1u16.to_be_bytes());
cd[6..8].copy_from_slice(&3u16.to_be_bytes());
t.extend_from_slice(&cd);
let g = GdefTable::parse(&t).unwrap();
assert!(g.is_mark(99));
assert!(!g.is_mark(100));
assert!(g.attach_points(99).is_none());
assert!(g.ligature_carets(99).is_none());
assert_eq!(g.mark_attach_class(99), 0);
assert_eq!(g.mark_glyph_set_count(), 0);
assert!(!g.mark_glyph_set_contains(0, 99));
}
fn build_v10(slot: usize, sub_bytes: &[u8]) -> Vec<u8> {
let mut t = vec![0u8; 12];
t[0..2].copy_from_slice(&1u16.to_be_bytes());
t[2..4].copy_from_slice(&0u16.to_be_bytes());
let sub_off: u16 = 12;
t[slot..slot + 2].copy_from_slice(&sub_off.to_be_bytes());
t.extend_from_slice(sub_bytes);
t
}
#[test]
fn attach_list_returns_point_indices_in_order() {
let mut sub = Vec::new();
let header_len: u16 = 2 + 2 + 2;
let cov_rel: u16 = header_len;
let cov_bytes = {
let mut c = Vec::new();
c.extend_from_slice(&1u16.to_be_bytes());
c.extend_from_slice(&1u16.to_be_bytes());
c.extend_from_slice(&42u16.to_be_bytes());
c
};
let ap_rel: u16 = cov_rel + cov_bytes.len() as u16;
let ap_bytes = {
let mut a = Vec::new();
a.extend_from_slice(&3u16.to_be_bytes());
a.extend_from_slice(&4u16.to_be_bytes());
a.extend_from_slice(&9u16.to_be_bytes());
a.extend_from_slice(&17u16.to_be_bytes());
a
};
sub.extend_from_slice(&cov_rel.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&ap_rel.to_be_bytes()); sub.extend_from_slice(&cov_bytes);
sub.extend_from_slice(&ap_bytes);
let t = build_v10(6, &sub);
let g = GdefTable::parse(&t).unwrap();
assert_eq!(g.attach_points(42), Some(vec![4, 9, 17]));
assert!(g.attach_points(7).is_none());
}
#[test]
fn lig_caret_list_mixes_format1_and_format2() {
let mut sub = Vec::new();
let header_len: u16 = 2 + 2 + 2;
let cov_rel: u16 = header_len;
let cov_bytes = {
let mut c = Vec::new();
c.extend_from_slice(&1u16.to_be_bytes()); c.extend_from_slice(&1u16.to_be_bytes()); c.extend_from_slice(&77u16.to_be_bytes());
c
};
let lg_rel: u16 = cov_rel + cov_bytes.len() as u16;
let lg_header_len: u16 = 2 + 2 * 2;
let cv0_rel: u16 = lg_header_len;
let cv0_bytes = {
let mut c = Vec::new();
c.extend_from_slice(&1u16.to_be_bytes()); c.extend_from_slice(&(-50i16).to_be_bytes()); c
};
let cv1_rel: u16 = cv0_rel + cv0_bytes.len() as u16;
let cv1_bytes = {
let mut c = Vec::new();
c.extend_from_slice(&2u16.to_be_bytes()); c.extend_from_slice(&12u16.to_be_bytes()); c
};
let mut lg_bytes = Vec::new();
lg_bytes.extend_from_slice(&2u16.to_be_bytes()); lg_bytes.extend_from_slice(&cv0_rel.to_be_bytes());
lg_bytes.extend_from_slice(&cv1_rel.to_be_bytes());
lg_bytes.extend_from_slice(&cv0_bytes);
lg_bytes.extend_from_slice(&cv1_bytes);
sub.extend_from_slice(&cov_rel.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes()); sub.extend_from_slice(&lg_rel.to_be_bytes()); sub.extend_from_slice(&cov_bytes);
sub.extend_from_slice(&lg_bytes);
let t = build_v10(8, &sub);
let g = GdefTable::parse(&t).unwrap();
let carets = g.ligature_carets(77).unwrap();
assert_eq!(
carets,
vec![CaretValue::DesignUnits(-50), CaretValue::ContourPoint(12),]
);
assert!(g.ligature_carets(0).is_none());
}
#[test]
fn lig_caret_format3_preserves_device_offset() {
let mut sub = Vec::new();
let header_len: u16 = 2 + 2 + 2;
let cov_rel: u16 = header_len;
let cov_bytes = {
let mut c = Vec::new();
c.extend_from_slice(&1u16.to_be_bytes());
c.extend_from_slice(&1u16.to_be_bytes());
c.extend_from_slice(&5u16.to_be_bytes());
c
};
let lg_rel: u16 = cov_rel + cov_bytes.len() as u16;
let lg_header_len: u16 = 2 + 2;
let cv0_rel: u16 = lg_header_len;
let cv0_bytes = {
let mut c = Vec::new();
c.extend_from_slice(&3u16.to_be_bytes()); c.extend_from_slice(&123i16.to_be_bytes()); c.extend_from_slice(&0xCAFEu16.to_be_bytes()); c
};
let mut lg_bytes = Vec::new();
lg_bytes.extend_from_slice(&1u16.to_be_bytes());
lg_bytes.extend_from_slice(&cv0_rel.to_be_bytes());
lg_bytes.extend_from_slice(&cv0_bytes);
sub.extend_from_slice(&cov_rel.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes());
sub.extend_from_slice(&lg_rel.to_be_bytes());
sub.extend_from_slice(&cov_bytes);
sub.extend_from_slice(&lg_bytes);
let t = build_v10(8, &sub);
let g = GdefTable::parse(&t).unwrap();
let carets = g.ligature_carets(5).unwrap();
assert_eq!(carets.len(), 1);
match carets[0] {
CaretValue::DesignUnitsWithDevice {
coordinate,
device_offset,
} => {
assert_eq!(coordinate, 123);
assert_eq!(device_offset, 0xCAFE);
}
_ => panic!("expected DesignUnitsWithDevice variant"),
}
}
#[test]
fn mark_attach_class_reads_high_byte_class() {
let mut cd = Vec::new();
cd.extend_from_slice(&2u16.to_be_bytes()); cd.extend_from_slice(&1u16.to_be_bytes()); cd.extend_from_slice(&100u16.to_be_bytes());
cd.extend_from_slice(&110u16.to_be_bytes());
cd.extend_from_slice(&7u16.to_be_bytes());
let t = build_v10(10, &cd);
let g = GdefTable::parse(&t).unwrap();
assert_eq!(g.mark_attach_class(99), 0);
assert_eq!(g.mark_attach_class(100), 7);
assert_eq!(g.mark_attach_class(110), 7);
assert_eq!(g.mark_attach_class(111), 0);
}
#[test]
fn mark_glyph_sets_handles_two_sets_with_offset32() {
let mut t = vec![0u8; 14];
t[0..2].copy_from_slice(&1u16.to_be_bytes()); t[2..4].copy_from_slice(&2u16.to_be_bytes()); let mgs_off: u16 = 14;
t[12..14].copy_from_slice(&mgs_off.to_be_bytes());
let mut mgs = Vec::new();
mgs.extend_from_slice(&1u16.to_be_bytes());
mgs.extend_from_slice(&2u16.to_be_bytes());
let header_len: u32 = 4 + 4 * 2;
let cov0_rel: u32 = header_len;
let cov0 = {
let mut c = Vec::new();
c.extend_from_slice(&1u16.to_be_bytes()); c.extend_from_slice(&2u16.to_be_bytes()); c.extend_from_slice(&3u16.to_be_bytes());
c.extend_from_slice(&4u16.to_be_bytes());
c
};
let cov1_rel: u32 = cov0_rel + cov0.len() as u32;
let cov1 = {
let mut c = Vec::new();
c.extend_from_slice(&1u16.to_be_bytes());
c.extend_from_slice(&2u16.to_be_bytes());
c.extend_from_slice(&3u16.to_be_bytes());
c.extend_from_slice(&5u16.to_be_bytes());
c
};
mgs.extend_from_slice(&cov0_rel.to_be_bytes());
mgs.extend_from_slice(&cov1_rel.to_be_bytes());
mgs.extend_from_slice(&cov0);
mgs.extend_from_slice(&cov1);
t.extend_from_slice(&mgs);
let g = GdefTable::parse(&t).unwrap();
assert_eq!(g.mark_glyph_set_count(), 2);
assert!(g.mark_glyph_set_contains(0, 3));
assert!(g.mark_glyph_set_contains(0, 4));
assert!(!g.mark_glyph_set_contains(0, 5));
assert!(g.mark_glyph_set_contains(1, 3));
assert!(g.mark_glyph_set_contains(1, 5));
assert!(!g.mark_glyph_set_contains(1, 4));
assert!(!g.mark_glyph_set_contains(2, 3));
}
#[test]
fn v13_header_with_item_var_store_exposes_raw_slice() {
let mut t = vec![0u8; 18];
t[0..2].copy_from_slice(&1u16.to_be_bytes());
t[2..4].copy_from_slice(&3u16.to_be_bytes()); let ivs_off: u32 = 18;
t[14..18].copy_from_slice(&ivs_off.to_be_bytes());
t.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
let g = GdefTable::parse(&t).unwrap();
assert_eq!(
g.item_var_store_bytes(),
Some(&[0xDE, 0xAD, 0xBE, 0xEF][..])
);
}
#[test]
fn rejects_truncated_v12_header() {
let mut t = vec![0u8; 13];
t[0..2].copy_from_slice(&1u16.to_be_bytes());
t[2..4].copy_from_slice(&2u16.to_be_bytes());
let _ = GdefTable::parse(&t[..12]);
let err = GdefTable::parse(&t).err();
assert!(matches!(err, Some(Error::UnexpectedEof)));
}
#[test]
fn rejects_unknown_major_version() {
let mut t = vec![0u8; 12];
t[0..2].copy_from_slice(&2u16.to_be_bytes());
let err = GdefTable::parse(&t).unwrap_err();
assert!(matches!(err, Error::BadStructure(_)));
}
}