use crate::parser::read_u16;
use crate::tables::gdef::class_def_lookup;
use crate::Error;
pub const MERGE_LTR: u8 = 0x01;
pub const GROUP_LTR: u8 = 0x02;
pub const SECOND_IS_SUBORDINATE_LTR: u8 = 0x04;
pub const MERGE_RTL: u8 = 0x10;
pub const GROUP_RTL: u8 = 0x20;
pub const SECOND_IS_SUBORDINATE_RTL: u8 = 0x40;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MergeEntry(pub u8);
impl MergeEntry {
pub fn merge_ltr(self) -> bool {
self.0 & MERGE_LTR != 0
}
pub fn group_ltr(self) -> bool {
self.0 & GROUP_LTR != 0
}
pub fn second_is_subordinate_ltr(self) -> bool {
self.0 & SECOND_IS_SUBORDINATE_LTR != 0
}
pub fn merge_rtl(self) -> bool {
self.0 & MERGE_RTL != 0
}
pub fn group_rtl(self) -> bool {
self.0 & GROUP_RTL != 0
}
pub fn second_is_subordinate_rtl(self) -> bool {
self.0 & SECOND_IS_SUBORDINATE_RTL != 0
}
}
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct MergTable {
merge_class_count: usize,
entries: Vec<u8>,
class_defs: Vec<Vec<u8>>,
}
impl MergTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 10 {
return Err(Error::UnexpectedEof);
}
let version = read_u16(bytes, 0)?;
if version != 0 {
return Err(Error::BadStructure("MERG: unsupported version"));
}
let merge_class_count = read_u16(bytes, 2)? as usize;
let merge_data_offset = read_u16(bytes, 4)? as usize;
let class_def_count = read_u16(bytes, 6)? as usize;
let offset_to_class_def_offsets = read_u16(bytes, 8)? as usize;
let entry_count = merge_class_count
.checked_mul(merge_class_count)
.ok_or(Error::BadStructure("MERG: class count overflow"))?;
let entries = if entry_count == 0 {
Vec::new()
} else {
let end = merge_data_offset
.checked_add(entry_count)
.ok_or(Error::BadStructure("MERG: merge-data overflow"))?;
if merge_data_offset == 0 || end > bytes.len() {
return Err(Error::BadStructure("MERG: merge-data out of bounds"));
}
bytes[merge_data_offset..end].to_vec()
};
let mut class_defs = Vec::with_capacity(class_def_count);
if class_def_count != 0 {
let arr_end = offset_to_class_def_offsets
.checked_add(class_def_count * 2)
.ok_or(Error::BadStructure("MERG: class-def offset array overflow"))?;
if offset_to_class_def_offsets == 0 || arr_end > bytes.len() {
return Err(Error::BadStructure("MERG: class-def offsets out of bounds"));
}
for i in 0..class_def_count {
let off = read_u16(bytes, offset_to_class_def_offsets + i * 2)? as usize;
if off == 0 || off > bytes.len() {
return Err(Error::BadStructure("MERG: class-def offset out of bounds"));
}
class_defs.push(bytes[off..].to_vec());
}
}
Ok(Self {
merge_class_count,
entries,
class_defs,
})
}
pub fn merge_class_count(&self) -> usize {
self.merge_class_count
}
pub fn class_def_count(&self) -> usize {
self.class_defs.len()
}
pub fn merge_class(&self, glyph_id: u16) -> u16 {
for cd in &self.class_defs {
if let Some(c) = class_def_lookup(cd, glyph_id) {
return c;
}
}
0
}
pub fn merge_entry(&self, first: u16, second: u16) -> Option<MergeEntry> {
let f = first as usize;
let s = second as usize;
if f >= self.merge_class_count || s >= self.merge_class_count {
return None;
}
self.entries
.get(f * self.merge_class_count + s)
.copied()
.map(MergeEntry)
}
pub fn merge_entry_for_glyphs(&self, first: u16, second: u16) -> Option<MergeEntry> {
self.merge_entry(self.merge_class(first), self.merge_class(second))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_merg(class_count: u16, entries: &[u8], first_glyph: u16, classes: &[u16]) -> Vec<u8> {
let mut out = vec![0u8; 10];
let cd_offsets_off = 10u16;
let cd_off = cd_offsets_off + 2;
let class_def_start = cd_off as usize;
let mut cd = Vec::new();
cd.extend_from_slice(&1u16.to_be_bytes());
cd.extend_from_slice(&first_glyph.to_be_bytes());
cd.extend_from_slice(&(classes.len() as u16).to_be_bytes());
for &c in classes {
cd.extend_from_slice(&c.to_be_bytes());
}
out.extend_from_slice(&cd_off.to_be_bytes()); let cd_at = out.len();
debug_assert_eq!(cd_at, class_def_start);
out.extend_from_slice(&cd);
let merge_data_off = out.len() as u16;
out.extend_from_slice(entries);
out[0..2].copy_from_slice(&0u16.to_be_bytes()); out[2..4].copy_from_slice(&class_count.to_be_bytes());
out[4..6].copy_from_slice(&merge_data_off.to_be_bytes());
out[6..8].copy_from_slice(&1u16.to_be_bytes()); out[8..10].copy_from_slice(&cd_offsets_off.to_be_bytes());
out
}
#[test]
fn decodes_classes_and_entries() {
let entries = [0x00, 0x00, 0x00, MERGE_LTR | SECOND_IS_SUBORDINATE_RTL];
let bytes = build_merg(2, &entries, 10, &[1, 0]);
let m = MergTable::parse(&bytes).unwrap();
assert_eq!(m.merge_class_count(), 2);
assert_eq!(m.class_def_count(), 1);
assert_eq!(m.merge_class(10), 1);
assert_eq!(m.merge_class(11), 0); assert_eq!(m.merge_class(99), 0); let e = m.merge_entry(1, 1).unwrap();
assert!(e.merge_ltr());
assert!(e.second_is_subordinate_rtl());
assert!(!e.merge_rtl());
assert_eq!(m.merge_entry(0, 0).unwrap(), MergeEntry(0));
assert!(m.merge_entry(2, 0).is_none());
}
#[test]
fn merge_entry_for_glyphs_resolves_classes() {
let entries = [0, 0, GROUP_RTL, 0]; let bytes = build_merg(2, &entries, 10, &[1]);
let m = MergTable::parse(&bytes).unwrap();
let e = m.merge_entry_for_glyphs(10, 20).unwrap();
assert!(e.group_rtl());
assert!(!e.merge_ltr());
}
#[test]
fn empty_merge_class_count() {
let mut out = vec![0u8; 10];
out[8..10].copy_from_slice(&0u16.to_be_bytes());
let m = MergTable::parse(&out).unwrap();
assert_eq!(m.merge_class_count(), 0);
assert!(m.merge_entry(0, 0).is_none());
assert_eq!(m.merge_class(5), 0);
}
#[test]
fn rejects_bad_version() {
let mut bytes = build_merg(2, &[0, 0, 0, 0], 10, &[1, 0]);
bytes[0..2].copy_from_slice(&1u16.to_be_bytes());
assert!(matches!(
MergTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_merge_data_out_of_bounds() {
let mut bytes = build_merg(2, &[0, 0, 0, 0], 10, &[1, 0]);
bytes[4..6].copy_from_slice(&9999u16.to_be_bytes());
assert!(matches!(
MergTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_short_header() {
assert!(matches!(
MergTable::parse(&[0u8; 6]),
Err(Error::UnexpectedEof)
));
}
}