use crate::parser::{read_u16, read_u24, read_u32};
use crate::Error;
#[derive(Debug, Clone)]
pub struct CmapTable<'a> {
subtable: Subtable<'a>,
variation: Option<&'a [u8]>,
}
#[derive(Debug, Clone)]
enum Subtable<'a> {
Format0(&'a [u8]),
Format2(&'a [u8]),
Format4(&'a [u8]),
Format6(&'a [u8]),
Format8(&'a [u8]),
Format10(&'a [u8]),
Format12(&'a [u8]),
Format13(&'a [u8]),
}
impl<'a> CmapTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 4 {
return Err(Error::UnexpectedEof);
}
let _version = read_u16(bytes, 0)?;
let num_tables = read_u16(bytes, 2)?;
let header_end = 4 + (num_tables as usize) * 8;
if bytes.len() < header_end {
return Err(Error::UnexpectedEof);
}
let mut best: Option<Subtable<'_>> = None;
let mut best_rank = i32::MIN;
let mut variation: Option<&'a [u8]> = None;
for i in 0..num_tables as usize {
let off = 4 + i * 8;
let platform_id = read_u16(bytes, off)?;
let encoding_id = read_u16(bytes, off + 2)?;
let sub_off = read_u32(bytes, off + 4)? as usize;
if sub_off + 2 > bytes.len() {
return Err(Error::BadOffset);
}
let format = read_u16(bytes, sub_off)?;
if format == 14 {
if sub_off + 6 > bytes.len() {
return Err(Error::BadOffset);
}
let length = read_u32(bytes, sub_off + 2)? as usize;
let sub = bytes
.get(sub_off..sub_off + length)
.ok_or(Error::BadOffset)?;
if variation.is_none() {
variation = Some(sub);
}
continue;
}
if !is_supported_format(format) {
continue;
}
let length = subtable_length(bytes, sub_off, format)?;
let sub = bytes
.get(sub_off..sub_off + length)
.ok_or(Error::BadOffset)?;
let candidate = match format {
0 => Subtable::Format0(sub),
2 => Subtable::Format2(sub),
4 => Subtable::Format4(sub),
6 => Subtable::Format6(sub),
8 => Subtable::Format8(sub),
10 => Subtable::Format10(sub),
12 => Subtable::Format12(sub),
13 => Subtable::Format13(sub),
_ => unreachable!("filtered by is_supported_format above"),
};
let rank = subtable_rank(format, platform_id, encoding_id);
if rank > best_rank {
best_rank = rank;
best = Some(candidate);
}
}
Ok(Self {
subtable: best.ok_or(Error::UnsupportedCmapFormat(0xFFFF))?,
variation,
})
}
pub fn lookup(&self, codepoint: u32) -> Option<u16> {
match &self.subtable {
Subtable::Format0(b) => lookup_format0(b, codepoint),
Subtable::Format2(b) => lookup_format2(b, codepoint),
Subtable::Format4(b) => lookup_format4(b, codepoint),
Subtable::Format6(b) => lookup_format6(b, codepoint),
Subtable::Format8(b) => lookup_format8(b, codepoint),
Subtable::Format10(b) => lookup_format10(b, codepoint),
Subtable::Format12(b) => lookup_format12(b, codepoint),
Subtable::Format13(b) => lookup_format13(b, codepoint),
}
}
pub fn lookup_variation(&self, codepoint: u32, variation_selector: u32) -> Option<u16> {
let bytes = self.variation?;
let num_records = read_u32(bytes, 6).ok()? as usize;
let records_off = 10usize;
let rec_size = 11;
if records_off + num_records * rec_size > bytes.len() {
return None;
}
let mut lo = 0usize;
let mut hi = num_records;
let rec_off = loop {
if lo >= hi {
return None;
}
let mid = (lo + hi) / 2;
let off = records_off + mid * rec_size;
let vs = read_u24(bytes, off).ok()?;
match vs.cmp(&variation_selector) {
core::cmp::Ordering::Less => lo = mid + 1,
core::cmp::Ordering::Greater => hi = mid,
core::cmp::Ordering::Equal => break off,
}
};
let default_off = read_u32(bytes, rec_off + 3).ok()? as usize;
let non_default_off = read_u32(bytes, rec_off + 7).ok()? as usize;
if non_default_off != 0 {
if let Some(g) = lookup_non_default_uvs(bytes, non_default_off, codepoint) {
return Some(g);
}
}
if default_off != 0 && range_contains(bytes, default_off, codepoint) {
return self.lookup(codepoint);
}
None
}
}
fn lookup_non_default_uvs(bytes: &[u8], table_off: usize, codepoint: u32) -> Option<u16> {
if table_off + 4 > bytes.len() {
return None;
}
let n = read_u32(bytes, table_off).ok()? as usize;
let entries_off = table_off + 4;
let entry_size = 5;
if entries_off + n * entry_size > bytes.len() {
return None;
}
let mut lo = 0usize;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
let off = entries_off + mid * entry_size;
let cp = read_u24(bytes, off).ok()?;
match cp.cmp(&codepoint) {
core::cmp::Ordering::Less => lo = mid + 1,
core::cmp::Ordering::Greater => hi = mid,
core::cmp::Ordering::Equal => return read_u16(bytes, off + 3).ok(),
}
}
None
}
fn range_contains(bytes: &[u8], table_off: usize, codepoint: u32) -> bool {
if table_off + 4 > bytes.len() {
return false;
}
let Ok(n_u32) = read_u32(bytes, table_off) else {
return false;
};
let n = n_u32 as usize;
let entries_off = table_off + 4;
let entry_size = 4;
if entries_off + n * entry_size > bytes.len() {
return false;
}
let mut lo = 0usize;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
let off = entries_off + mid * entry_size;
let Ok(start) = read_u24(bytes, off) else {
return false;
};
if start <= codepoint {
lo = mid + 1;
} else {
hi = mid;
}
}
if lo == 0 {
return false;
}
let cand = lo - 1;
let off = entries_off + cand * entry_size;
let Ok(start) = read_u24(bytes, off) else {
return false;
};
let Ok(extra) = bytes
.get(off + 3)
.copied()
.ok_or(crate::Error::UnexpectedEof)
else {
return false;
};
let end = start + extra as u32;
codepoint >= start && codepoint <= end
}
fn is_supported_format(format: u16) -> bool {
matches!(format, 0 | 2 | 4 | 6 | 8 | 10 | 12 | 13)
}
fn subtable_length(bytes: &[u8], off: usize, format: u16) -> Result<usize, Error> {
Ok(match format {
0 | 2 | 4 | 6 => read_u16(bytes, off + 2)? as usize,
8 | 10 | 12 | 13 => read_u32(bytes, off + 4)? as usize,
_ => return Err(Error::UnsupportedCmapFormat(format)),
})
}
fn subtable_rank(format: u16, platform: u16, encoding: u16) -> i32 {
let format_score = match format {
12 => 400,
8 => 350,
4 => 300,
10 => 250,
6 => 200,
0 => 100,
2 => 60,
13 => 50,
_ => 0,
};
let platform_score = match (platform, encoding) {
(0, _) => 30,
(3, 10) => 25, (3, 1) => 20, _ => 5,
};
format_score + platform_score
}
fn lookup_format0(bytes: &[u8], codepoint: u32) -> Option<u16> {
if codepoint > 0xFF {
return None;
}
let glyph_array_off = 6;
if bytes.len() < glyph_array_off + 256 {
return None;
}
let g = bytes[glyph_array_off + codepoint as usize];
if g == 0 {
None
} else {
Some(g as u16)
}
}
fn lookup_format2(bytes: &[u8], codepoint: u32) -> Option<u16> {
if codepoint > 0xFFFF {
return None;
}
let high = ((codepoint >> 8) & 0xFF) as u8;
let low = (codepoint & 0xFF) as u8;
let sub_header_keys_off = 6usize;
let sub_headers_off = sub_header_keys_off + 512;
if bytes.len() < sub_headers_off {
return None;
}
let key = read_u16(bytes, sub_header_keys_off + (high as usize) * 2).ok()?;
if key % 8 != 0 {
return None;
}
let sub_header_offset = sub_headers_off + key as usize;
if sub_header_offset + 8 > bytes.len() {
return None;
}
let first_code = read_u16(bytes, sub_header_offset).ok()?;
let entry_count = read_u16(bytes, sub_header_offset + 2).ok()?;
let id_delta = read_u16(bytes, sub_header_offset + 4).ok()? as i16 as i32;
let id_range_offset = read_u16(bytes, sub_header_offset + 6).ok()? as usize;
if key == 0 && high != 0 {
return None;
}
if entry_count == 0 {
return None;
}
let low_u16 = low as u16;
if low_u16 < first_code {
return None;
}
let idx = (low_u16 - first_code) as usize;
if idx >= entry_count as usize {
return None;
}
let id_range_field_addr = sub_header_offset + 6;
let target = id_range_field_addr
.checked_add(id_range_offset)?
.checked_add(2 * idx)?;
let raw = read_u16(bytes, target).ok()?;
if raw == 0 {
return None;
}
let g = (raw as i32 + id_delta) & 0xFFFF;
Some(g as u16)
}
fn lookup_format4(bytes: &[u8], codepoint: u32) -> Option<u16> {
if codepoint > 0xFFFF {
return None;
}
let cp = codepoint as u16;
let seg_count_x2 = read_u16(bytes, 6).ok()? as usize;
let seg_count = seg_count_x2 / 2;
if seg_count == 0 {
return None;
}
let end_code_off = 14usize;
let reserved_pad = end_code_off + seg_count_x2; let start_code_off = reserved_pad + 2;
let id_delta_off = start_code_off + seg_count_x2;
let id_range_offset_off = id_delta_off + seg_count_x2;
let glyph_id_array_off = id_range_offset_off + seg_count_x2;
if bytes.len() < glyph_id_array_off {
return None;
}
let mut lo = 0usize;
let mut hi = seg_count;
while lo < hi {
let mid = lo + (hi - lo) / 2;
let end = read_u16(bytes, end_code_off + mid * 2).ok()?;
if end < cp {
lo = mid + 1;
} else {
hi = mid;
}
}
if lo >= seg_count {
return None;
}
let seg = lo;
let start = read_u16(bytes, start_code_off + seg * 2).ok()?;
if start > cp {
return None;
}
let id_delta = read_u16(bytes, id_delta_off + seg * 2).ok()? as i32 as i16;
let id_range_offset = read_u16(bytes, id_range_offset_off + seg * 2).ok()?;
if id_range_offset == 0 {
let g = (cp as i32 + id_delta as i32) & 0xFFFF;
if g == 0 {
return None;
}
return Some(g as u16);
}
let target = (id_range_offset_off + seg * 2)
.checked_add(id_range_offset as usize)?
.checked_add(2 * (cp as usize - start as usize))?;
let raw = read_u16(bytes, target).ok()?;
if raw == 0 {
return None;
}
let g = (raw as i32 + id_delta as i32) & 0xFFFF;
Some(g as u16)
}
fn lookup_format6(bytes: &[u8], codepoint: u32) -> Option<u16> {
if codepoint > 0xFFFF {
return None;
}
let cp = codepoint as u16;
let first_code = read_u16(bytes, 6).ok()?;
let entry_count = read_u16(bytes, 8).ok()?;
if cp < first_code {
return None;
}
let idx = cp - first_code;
if idx >= entry_count {
return None;
}
let g = read_u16(bytes, 10 + idx as usize * 2).ok()?;
if g == 0 {
None
} else {
Some(g)
}
}
fn lookup_format8(bytes: &[u8], codepoint: u32) -> Option<u16> {
const IS32_OFF: usize = 12;
const NUM_GROUPS_OFF: usize = 8204;
const GROUPS_OFF: usize = 8208;
let is32 = |word: u32| -> Option<bool> {
let byte = bytes.get(IS32_OFF + (word as usize) / 8)?;
Some(byte & (1 << (7 - word % 8)) != 0)
};
if codepoint <= 0xFFFF {
if is32(codepoint)? {
return None;
}
} else if !is32(codepoint >> 16)? {
return None;
}
let num_groups = read_u32(bytes, NUM_GROUPS_OFF).ok()? as usize;
if GROUPS_OFF + num_groups * 12 > bytes.len() {
return None;
}
let mut lo = 0usize;
let mut hi = num_groups;
while lo < hi {
let mid = (lo + hi) / 2;
let off = GROUPS_OFF + mid * 12;
let start = read_u32(bytes, off).ok()?;
let end = read_u32(bytes, off + 4).ok()?;
if codepoint < start {
hi = mid;
} else if codepoint > end {
lo = mid + 1;
} else {
let start_glyph = read_u32(bytes, off + 8).ok()?;
let g = start_glyph.checked_add(codepoint - start)?;
if g > u16::MAX as u32 {
return None;
}
return Some(g as u16);
}
}
None
}
fn lookup_format10(bytes: &[u8], codepoint: u32) -> Option<u16> {
let start = read_u32(bytes, 12).ok()?;
let num_chars = read_u32(bytes, 16).ok()?;
if codepoint < start {
return None;
}
let idx = codepoint - start;
if idx >= num_chars {
return None;
}
let g = read_u16(bytes, 20 + idx as usize * 2).ok()?;
if g == 0 {
None
} else {
Some(g)
}
}
fn lookup_format12(bytes: &[u8], codepoint: u32) -> Option<u16> {
let num_groups = read_u32(bytes, 12).ok()? as usize;
if 16 + num_groups * 12 > bytes.len() {
return None;
}
let mut lo = 0usize;
let mut hi = num_groups;
while lo < hi {
let mid = (lo + hi) / 2;
let off = 16 + mid * 12;
let start = read_u32(bytes, off).ok()?;
let end = read_u32(bytes, off + 4).ok()?;
if codepoint < start {
hi = mid;
} else if codepoint > end {
lo = mid + 1;
} else {
let start_glyph = read_u32(bytes, off + 8).ok()?;
let g = start_glyph.checked_add(codepoint - start)?;
if g > u16::MAX as u32 {
return None;
}
return Some(g as u16);
}
}
None
}
fn lookup_format13(bytes: &[u8], codepoint: u32) -> Option<u16> {
let num_groups = read_u32(bytes, 12).ok()? as usize;
if 16 + num_groups * 12 > bytes.len() {
return None;
}
let mut lo = 0usize;
let mut hi = num_groups;
while lo < hi {
let mid = (lo + hi) / 2;
let off = 16 + mid * 12;
let start = read_u32(bytes, off).ok()?;
let end = read_u32(bytes, off + 4).ok()?;
if codepoint < start {
hi = mid;
} else if codepoint > end {
lo = mid + 1;
} else {
let glyph = read_u32(bytes, off + 8).ok()?;
if glyph == 0 || glyph > u16::MAX as u32 {
return None;
}
return Some(glyph as u16);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn build_cmap_with_subtable(format: u16, sub: &[u8]) -> Vec<u8> {
let mut out = vec![0u8; 4 + 8];
out[0..2].copy_from_slice(&0u16.to_be_bytes()); out[2..4].copy_from_slice(&1u16.to_be_bytes()); let (platform, enc): (u16, u16) = match format {
13 => (0, 6),
12 => (3, 10),
8 | 10 => (0, 4),
2 => (1, 1),
_ => (3, 1),
};
out[4..6].copy_from_slice(&platform.to_be_bytes());
out[6..8].copy_from_slice(&enc.to_be_bytes());
out[8..12].copy_from_slice(&12u32.to_be_bytes()); out.extend_from_slice(sub);
let _ = format;
out
}
#[test]
fn format0_round_trip() {
let mut sub = vec![0u8; 6 + 256];
sub[0..2].copy_from_slice(&0u16.to_be_bytes()); sub[2..4].copy_from_slice(&((6 + 256) as u16).to_be_bytes()); sub[6 + 65] = 7;
let cmap_bytes = build_cmap_with_subtable(0, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(65), Some(7));
assert_eq!(cmap.lookup(64), None);
assert_eq!(cmap.lookup(0x10000), None);
}
#[test]
fn format6_round_trip() {
let mut sub = vec![0u8; 10 + 4];
sub[0..2].copy_from_slice(&6u16.to_be_bytes());
sub[2..4].copy_from_slice(&((10 + 4) as u16).to_be_bytes());
sub[6..8].copy_from_slice(&100u16.to_be_bytes()); sub[8..10].copy_from_slice(&2u16.to_be_bytes()); sub[10..12].copy_from_slice(&77u16.to_be_bytes()); sub[12..14].copy_from_slice(&0u16.to_be_bytes()); let cmap_bytes = build_cmap_with_subtable(6, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(100), Some(77));
assert_eq!(cmap.lookup(101), None);
assert_eq!(cmap.lookup(99), None);
}
#[test]
fn format12_round_trip() {
let mut sub = vec![0u8; 16 + 24];
sub[0..2].copy_from_slice(&12u16.to_be_bytes());
sub[4..8].copy_from_slice(&((16 + 24) as u32).to_be_bytes());
sub[12..16].copy_from_slice(&2u32.to_be_bytes()); sub[16..20].copy_from_slice(&0x4E00u32.to_be_bytes());
sub[20..24].copy_from_slice(&0x4E02u32.to_be_bytes());
sub[24..28].copy_from_slice(&1000u32.to_be_bytes());
sub[28..32].copy_from_slice(&0x1F600u32.to_be_bytes());
sub[32..36].copy_from_slice(&0x1F600u32.to_be_bytes());
sub[36..40].copy_from_slice(&5000u32.to_be_bytes());
let cmap_bytes = build_cmap_with_subtable(12, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x4E00), Some(1000));
assert_eq!(cmap.lookup(0x4E01), Some(1001));
assert_eq!(cmap.lookup(0x4E02), Some(1002));
assert_eq!(cmap.lookup(0x4E03), None);
assert_eq!(cmap.lookup(0x1F600), Some(5000));
}
#[test]
fn format14_subtable_is_skipped_not_rejected() {
let mut sub12 = vec![0u8; 16 + 12];
sub12[0..2].copy_from_slice(&12u16.to_be_bytes()); sub12[4..8].copy_from_slice(&((16 + 12) as u32).to_be_bytes()); sub12[12..16].copy_from_slice(&1u32.to_be_bytes()); sub12[16..20].copy_from_slice(&0x1F600u32.to_be_bytes()); sub12[20..24].copy_from_slice(&0x1F600u32.to_be_bytes()); sub12[24..28].copy_from_slice(&5u32.to_be_bytes());
let mut sub14 = vec![0u8; 10];
sub14[0..2].copy_from_slice(&14u16.to_be_bytes()); sub14[2..6].copy_from_slice(&10u32.to_be_bytes()); sub14[6..10].copy_from_slice(&0u32.to_be_bytes());
let header_len = 4 + 2 * 8;
let sub12_off = header_len;
let sub14_off = sub12_off + sub12.len();
let mut out = vec![0u8; header_len];
out[0..2].copy_from_slice(&0u16.to_be_bytes()); out[2..4].copy_from_slice(&2u16.to_be_bytes()); out[4..6].copy_from_slice(&3u16.to_be_bytes());
out[6..8].copy_from_slice(&10u16.to_be_bytes());
out[8..12].copy_from_slice(&(sub12_off as u32).to_be_bytes());
out[12..14].copy_from_slice(&0u16.to_be_bytes());
out[14..16].copy_from_slice(&5u16.to_be_bytes());
out[16..20].copy_from_slice(&(sub14_off as u32).to_be_bytes());
out.extend_from_slice(&sub12);
out.extend_from_slice(&sub14);
let cmap = CmapTable::parse(&out).expect("format-14 sibling must not fail parse");
assert_eq!(cmap.lookup(0x1F600), Some(5));
assert_eq!(cmap.lookup(0x1F601), None);
}
fn build_cmap_with_format12_and_format14() -> Vec<u8> {
let num_groups: u32 = 2;
let sub12_len: usize = 16 + num_groups as usize * 12;
let mut sub12 = vec![0u8; sub12_len];
sub12[0..2].copy_from_slice(&12u16.to_be_bytes());
sub12[4..8].copy_from_slice(&(sub12_len as u32).to_be_bytes());
sub12[12..16].copy_from_slice(&num_groups.to_be_bytes());
sub12[16..20].copy_from_slice(&0x2728u32.to_be_bytes());
sub12[20..24].copy_from_slice(&0x2728u32.to_be_bytes());
sub12[24..28].copy_from_slice(&7u32.to_be_bytes());
sub12[28..32].copy_from_slice(&0x1F600u32.to_be_bytes());
sub12[32..36].copy_from_slice(&0x1F600u32.to_be_bytes());
sub12[36..40].copy_from_slice(&5u32.to_be_bytes());
let header_len = 10usize; let record_len = 11usize;
let default_table_len = 4 + 4; let non_default_table_len = 4 + 5; let sub14_len = header_len + record_len + default_table_len + non_default_table_len;
let mut sub14 = vec![0u8; sub14_len];
sub14[0..2].copy_from_slice(&14u16.to_be_bytes());
sub14[2..6].copy_from_slice(&(sub14_len as u32).to_be_bytes());
sub14[6..10].copy_from_slice(&1u32.to_be_bytes());
let default_off = (header_len + record_len) as u32; let non_default_off = default_off + default_table_len as u32;
let vs_bytes = 0xFE0Fu32.to_be_bytes();
sub14[10..13].copy_from_slice(&vs_bytes[1..4]);
sub14[13..17].copy_from_slice(&default_off.to_be_bytes());
sub14[17..21].copy_from_slice(&non_default_off.to_be_bytes());
let off = default_off as usize;
sub14[off..off + 4].copy_from_slice(&1u32.to_be_bytes());
let r = off + 4;
let start_bytes = 0x1F600u32.to_be_bytes();
sub14[r..r + 3].copy_from_slice(&start_bytes[1..4]);
sub14[r + 3] = 0;
let off = non_default_off as usize;
sub14[off..off + 4].copy_from_slice(&1u32.to_be_bytes());
let m = off + 4;
let cp_bytes = 0x2728u32.to_be_bytes();
sub14[m..m + 3].copy_from_slice(&cp_bytes[1..4]);
sub14[m + 3..m + 5].copy_from_slice(&9999u16.to_be_bytes());
let header_len = 4 + 2 * 8;
let sub12_off = header_len;
let sub14_off = sub12_off + sub12.len();
let mut out = vec![0u8; header_len];
out[0..2].copy_from_slice(&0u16.to_be_bytes());
out[2..4].copy_from_slice(&2u16.to_be_bytes());
out[4..6].copy_from_slice(&3u16.to_be_bytes());
out[6..8].copy_from_slice(&10u16.to_be_bytes());
out[8..12].copy_from_slice(&(sub12_off as u32).to_be_bytes());
out[12..14].copy_from_slice(&0u16.to_be_bytes());
out[14..16].copy_from_slice(&5u16.to_be_bytes());
out[16..20].copy_from_slice(&(sub14_off as u32).to_be_bytes());
out.extend_from_slice(&sub12);
out.extend_from_slice(&sub14);
out
}
#[test]
fn variation_lookup_default_returns_base_glyph() {
let cmap_bytes = build_cmap_with_format12_and_format14();
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x1F600), Some(5));
assert_eq!(cmap.lookup(0x2728), Some(7));
assert_eq!(cmap.lookup_variation(0x1F600, 0xFE0F), Some(5));
}
#[test]
fn variation_lookup_non_default_overrides_base() {
let cmap_bytes = build_cmap_with_format12_and_format14();
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup_variation(0x2728, 0xFE0F), Some(9999));
}
#[test]
fn variation_lookup_misses_return_none() {
let cmap_bytes = build_cmap_with_format12_and_format14();
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup_variation(0x1F600, 0xFE0E), None);
assert_eq!(cmap.lookup_variation(0x1F601, 0xFE0F), None);
}
#[test]
fn variation_lookup_returns_none_when_no_format14() {
let mut sub = vec![0u8; 16 + 12];
sub[0..2].copy_from_slice(&12u16.to_be_bytes());
sub[4..8].copy_from_slice(&((16 + 12) as u32).to_be_bytes());
sub[12..16].copy_from_slice(&1u32.to_be_bytes());
sub[16..20].copy_from_slice(&0x1F600u32.to_be_bytes());
sub[20..24].copy_from_slice(&0x1F600u32.to_be_bytes());
sub[24..28].copy_from_slice(&5u32.to_be_bytes());
let cmap_bytes = build_cmap_with_subtable(12, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x1F600), Some(5));
assert_eq!(cmap.lookup_variation(0x1F600, 0xFE0F), None);
}
#[test]
fn cmap_with_only_format14_fails_cleanly() {
let mut sub14 = vec![0u8; 10];
sub14[0..2].copy_from_slice(&14u16.to_be_bytes());
sub14[2..6].copy_from_slice(&10u32.to_be_bytes());
sub14[6..10].copy_from_slice(&0u32.to_be_bytes());
let header_len = 4 + 8;
let mut out = vec![0u8; header_len];
out[0..2].copy_from_slice(&0u16.to_be_bytes());
out[2..4].copy_from_slice(&1u16.to_be_bytes());
out[4..6].copy_from_slice(&0u16.to_be_bytes());
out[6..8].copy_from_slice(&5u16.to_be_bytes());
out[8..12].copy_from_slice(&(header_len as u32).to_be_bytes());
out.extend_from_slice(&sub14);
match CmapTable::parse(&out) {
Err(Error::UnsupportedCmapFormat(0xFFFF)) => {}
other => panic!("expected UnsupportedCmapFormat(0xFFFF), got {other:?}"),
}
}
#[test]
fn format4_round_trip() {
let seg_count: u16 = 2;
let seg_count_x2: u16 = seg_count * 2;
let header = 14;
let arrays_len = seg_count_x2 as usize * 4 + 2 ;
let length = header + arrays_len;
let mut sub = vec![0u8; length];
sub[0..2].copy_from_slice(&4u16.to_be_bytes()); sub[2..4].copy_from_slice(&(length as u16).to_be_bytes());
sub[6..8].copy_from_slice(&seg_count_x2.to_be_bytes());
sub[14..16].copy_from_slice(&67u16.to_be_bytes());
sub[16..18].copy_from_slice(&0xFFFFu16.to_be_bytes());
sub[18..20].copy_from_slice(&0u16.to_be_bytes());
sub[20..22].copy_from_slice(&65u16.to_be_bytes());
sub[22..24].copy_from_slice(&0xFFFFu16.to_be_bytes());
sub[24..26].copy_from_slice(&35u16.to_be_bytes());
sub[26..28].copy_from_slice(&1u16.to_be_bytes());
let cmap_bytes = build_cmap_with_subtable(4, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup('A' as u32), Some(100));
assert_eq!(cmap.lookup('B' as u32), Some(101));
assert_eq!(cmap.lookup('C' as u32), Some(102));
assert_eq!(cmap.lookup('D' as u32), None);
}
fn build_format4_direct(segs: &[(u16, u16, u16)]) -> Vec<u8> {
let mut all: Vec<(u16, u16, u16)> = segs.to_vec();
all.push((0xFFFF, 0xFFFF, 1));
let seg_count = all.len() as u16;
let seg_count_x2 = seg_count * 2;
let header = 14;
let arrays_len = seg_count_x2 as usize * 4 + 2 ;
let length = header + arrays_len;
let mut sub = vec![0u8; length];
sub[0..2].copy_from_slice(&4u16.to_be_bytes());
sub[2..4].copy_from_slice(&(length as u16).to_be_bytes());
sub[6..8].copy_from_slice(&seg_count_x2.to_be_bytes());
for (i, (_, end, _)) in all.iter().enumerate() {
let off = 14 + i * 2;
sub[off..off + 2].copy_from_slice(&end.to_be_bytes());
}
let start_off = 14 + seg_count_x2 as usize + 2;
let delta_off = start_off + seg_count_x2 as usize;
for (i, (start, _, delta)) in all.iter().enumerate() {
sub[start_off + i * 2..start_off + i * 2 + 2].copy_from_slice(&start.to_be_bytes());
sub[delta_off + i * 2..delta_off + i * 2 + 2].copy_from_slice(&delta.to_be_bytes());
}
sub
}
#[test]
fn format4_binary_search_resolves_many_segments() {
let mut segs: Vec<(u16, u16, u16)> = Vec::with_capacity(200);
for i in 0..200u16 {
let cp = 0x0100 + i * 2;
let want_glyph = i + 1;
let delta = want_glyph.wrapping_sub(cp);
segs.push((cp, cp, delta));
}
let sub = build_format4_direct(&segs);
let cmap_bytes = build_cmap_with_subtable(4, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
for i in 0..200u16 {
let cp = 0x0100 + i * 2;
assert_eq!(
cmap.lookup(cp as u32),
Some(i + 1),
"codepoint {cp:#06x} expected glyph {}",
i + 1
);
if i + 1 < 200 {
assert_eq!(
cmap.lookup((cp + 1) as u32),
None,
"codepoint {:#06x} unexpectedly mapped",
cp + 1
);
}
}
assert_eq!(cmap.lookup(0x00FF), None);
}
#[test]
fn format4_indirect_mapping_resolves_through_glyph_id_array() {
let seg_count: u16 = 2;
let seg_count_x2: u16 = seg_count * 2;
let header = 14;
let glyph_id_array_bytes: usize = 4 * 2;
let arrays_len = seg_count_x2 as usize * 4 + 2 + glyph_id_array_bytes;
let length = header + arrays_len;
let mut sub = vec![0u8; length];
sub[0..2].copy_from_slice(&4u16.to_be_bytes());
sub[2..4].copy_from_slice(&(length as u16).to_be_bytes());
sub[6..8].copy_from_slice(&seg_count_x2.to_be_bytes());
sub[14..16].copy_from_slice(&68u16.to_be_bytes());
sub[16..18].copy_from_slice(&0xFFFFu16.to_be_bytes());
sub[20..22].copy_from_slice(&65u16.to_be_bytes());
sub[22..24].copy_from_slice(&0xFFFFu16.to_be_bytes());
sub[24..26].copy_from_slice(&10u16.to_be_bytes());
sub[26..28].copy_from_slice(&1u16.to_be_bytes());
let id_range_offset_off = 28usize;
sub[id_range_offset_off..id_range_offset_off + 2].copy_from_slice(&4u16.to_be_bytes());
let glyph_id_array_off = id_range_offset_off + seg_count_x2 as usize; for (i, &raw) in [100u16, 200, 300, 400].iter().enumerate() {
let off = glyph_id_array_off + i * 2;
sub[off..off + 2].copy_from_slice(&raw.to_be_bytes());
}
let cmap_bytes = build_cmap_with_subtable(4, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup('A' as u32), Some(110));
assert_eq!(cmap.lookup('B' as u32), Some(210));
assert_eq!(cmap.lookup('C' as u32), Some(310));
assert_eq!(cmap.lookup('D' as u32), Some(410));
assert_eq!(cmap.lookup('E' as u32), None);
}
#[test]
fn format4_truncated_arrays_does_not_panic() {
let segs = vec![(65u16, 67u16, 35u16)];
let sub = build_format4_direct(&segs);
let mut cmap_bytes = build_cmap_with_subtable(4, &sub);
let cut = cmap_bytes.len() - 4;
cmap_bytes.truncate(cut);
let new_len = (sub.len() - 4) as u16;
cmap_bytes[14..16].copy_from_slice(&new_len.to_be_bytes());
let cmap = CmapTable::parse(&cmap_bytes).expect("parse must tolerate length=trimmed");
let _ = cmap.lookup('A' as u32);
let _ = cmap.lookup('Z' as u32);
}
fn build_format13(groups: &[(u32, u32, u32)]) -> Vec<u8> {
let num_groups = groups.len() as u32;
let sub_len = 16 + num_groups as usize * 12;
let mut sub = vec![0u8; sub_len];
sub[0..2].copy_from_slice(&13u16.to_be_bytes()); sub[4..8].copy_from_slice(&(sub_len as u32).to_be_bytes()); sub[12..16].copy_from_slice(&num_groups.to_be_bytes()); for (i, &(start, end, glyph)) in groups.iter().enumerate() {
let off = 16 + i * 12;
sub[off..off + 4].copy_from_slice(&start.to_be_bytes());
sub[off + 4..off + 8].copy_from_slice(&end.to_be_bytes());
sub[off + 8..off + 12].copy_from_slice(&glyph.to_be_bytes());
}
sub
}
#[test]
fn format13_single_range_maps_all_to_one_glyph() {
let sub = build_format13(&[(0x0000, 0xFFFF, 1)]);
let cmap_bytes = build_cmap_with_subtable(13, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x0041), Some(1)); assert_eq!(cmap.lookup(0x4E00), Some(1)); assert_eq!(cmap.lookup(0xFFFF), Some(1));
assert_eq!(cmap.lookup(0x10000), None);
}
#[test]
fn format13_multi_range_each_collapses_to_its_glyph() {
let sub = build_format13(&[
(0x3040, 0x309F, 2),
(0x4E00, 0x9FFF, 3),
(0x1F600, 0x1F64F, 4),
]);
let cmap_bytes = build_cmap_with_subtable(13, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x3040), Some(2));
assert_eq!(cmap.lookup(0x3060), Some(2));
assert_eq!(cmap.lookup(0x309F), Some(2));
assert_eq!(cmap.lookup(0x4E00), Some(3));
assert_eq!(cmap.lookup(0x5000), Some(3));
assert_eq!(cmap.lookup(0x9FFF), Some(3));
assert_eq!(cmap.lookup(0x1F600), Some(4));
assert_eq!(cmap.lookup(0x1F62D), Some(4));
assert_eq!(cmap.lookup(0x1F64F), Some(4));
assert_eq!(cmap.lookup(0x303F), None);
assert_eq!(cmap.lookup(0x30A0), None);
assert_eq!(cmap.lookup(0xA000), None);
assert_eq!(cmap.lookup(0x1F5FF), None);
}
#[test]
fn format13_does_not_add_running_offset() {
let sub = build_format13(&[(0x0061, 0x0063, 7)]);
let cmap_bytes = build_cmap_with_subtable(13, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x0061), Some(7));
assert_eq!(cmap.lookup(0x0062), Some(7));
assert_eq!(cmap.lookup(0x0063), Some(7));
assert_eq!(cmap.lookup(0x0064), None);
}
#[test]
fn format13_glyph_zero_returns_none() {
let sub = build_format13(&[(0x0030, 0x0039, 0)]);
let cmap_bytes = build_cmap_with_subtable(13, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
for cp in 0x0030..=0x0039 {
assert_eq!(cmap.lookup(cp), None, "cp {cp:#04x} should miss");
}
}
#[test]
fn format13_binary_search_resolves_many_ranges() {
let mut groups: Vec<(u32, u32, u32)> = Vec::with_capacity(200);
for i in 0..200u32 {
let cp = 0x10000 + i * 2; groups.push((cp, cp, 5));
}
let sub = build_format13(&groups);
let cmap_bytes = build_cmap_with_subtable(13, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
for i in 0..200u32 {
let cp = 0x10000 + i * 2;
assert_eq!(cmap.lookup(cp), Some(5), "cp {cp:#06x}");
if i + 1 < 200 {
assert_eq!(cmap.lookup(cp + 1), None, "gap after {cp:#06x}");
}
}
assert_eq!(cmap.lookup(0x0FFFF), None);
}
#[test]
fn format13_does_not_displace_format12() {
let sub12_len = 16 + 12;
let mut sub12 = vec![0u8; sub12_len];
sub12[0..2].copy_from_slice(&12u16.to_be_bytes());
sub12[4..8].copy_from_slice(&(sub12_len as u32).to_be_bytes());
sub12[12..16].copy_from_slice(&1u32.to_be_bytes());
sub12[16..20].copy_from_slice(&0x0041u32.to_be_bytes());
sub12[20..24].copy_from_slice(&0x0041u32.to_be_bytes());
sub12[24..28].copy_from_slice(&100u32.to_be_bytes());
let sub13 = build_format13(&[(0x0041, 0x0041, 1)]);
let header_len = 4 + 2 * 8;
let sub12_off = header_len;
let sub13_off = sub12_off + sub12.len();
let mut out = vec![0u8; header_len];
out[0..2].copy_from_slice(&0u16.to_be_bytes());
out[2..4].copy_from_slice(&2u16.to_be_bytes());
out[4..6].copy_from_slice(&3u16.to_be_bytes());
out[6..8].copy_from_slice(&10u16.to_be_bytes());
out[8..12].copy_from_slice(&(sub12_off as u32).to_be_bytes());
out[12..14].copy_from_slice(&0u16.to_be_bytes());
out[14..16].copy_from_slice(&6u16.to_be_bytes());
out[16..20].copy_from_slice(&(sub13_off as u32).to_be_bytes());
out.extend_from_slice(&sub12);
out.extend_from_slice(&sub13);
let cmap = CmapTable::parse(&out).unwrap();
assert_eq!(cmap.lookup(0x0041), Some(100));
}
#[test]
fn format13_only_is_pickable_as_last_resort() {
let sub = build_format13(&[(0x0000, 0x10FFFF, 1)]);
let cmap_bytes = build_cmap_with_subtable(13, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x0041), Some(1));
assert_eq!(cmap.lookup(0x1F600), Some(1));
assert_eq!(cmap.lookup(0x10FFFF), Some(1));
}
fn build_format8(is32_words: &[u32], groups: &[(u32, u32, u32)]) -> Vec<u8> {
let num_groups = groups.len() as u32;
let sub_len = 8208 + groups.len() * 12;
let mut sub = vec![0u8; sub_len];
sub[0..2].copy_from_slice(&8u16.to_be_bytes()); sub[4..8].copy_from_slice(&(sub_len as u32).to_be_bytes()); for &w in is32_words {
sub[12 + (w as usize) / 8] |= 1 << (7 - w % 8);
}
sub[8204..8208].copy_from_slice(&num_groups.to_be_bytes());
for (i, &(start, end, glyph)) in groups.iter().enumerate() {
let off = 8208 + i * 12;
sub[off..off + 4].copy_from_slice(&start.to_be_bytes());
sub[off + 4..off + 8].copy_from_slice(&end.to_be_bytes());
sub[off + 8..off + 12].copy_from_slice(&glyph.to_be_bytes());
}
sub
}
#[test]
fn format8_round_trip() {
let sub = build_format8(
&[0x0001],
&[(0x4E00, 0x4E02, 1000), (0x10400, 0x10401, 2000)],
);
let cmap_bytes = build_cmap_with_subtable(8, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x4E00), Some(1000));
assert_eq!(cmap.lookup(0x4E01), Some(1001));
assert_eq!(cmap.lookup(0x4E02), Some(1002));
assert_eq!(cmap.lookup(0x4E03), None);
assert_eq!(cmap.lookup(0x4DFF), None);
assert_eq!(cmap.lookup(0x10400), Some(2000));
assert_eq!(cmap.lookup(0x10401), Some(2001));
assert_eq!(cmap.lookup(0x10402), None);
}
#[test]
fn format8_is32_array_gates_lookups() {
let sub = build_format8(&[0x0001], &[(0x0001, 0x0001, 77), (0x20000, 0x20001, 88)]);
let cmap_bytes = build_cmap_with_subtable(8, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x0001), None);
assert_eq!(cmap.lookup(0x20000), None);
assert_eq!(cmap.lookup(0x1FFFF), None);
}
#[test]
fn format8_outranks_format4() {
let sub4 = build_format4_direct(&[(65, 65, 1u16.wrapping_sub(65))]);
let sub8 = build_format8(&[0x0001], &[(0x41, 0x41, 100), (0x10400, 0x10400, 2000)]);
let header_len = 4 + 2 * 8;
let sub4_off = header_len;
let sub8_off = sub4_off + sub4.len();
let mut out = vec![0u8; header_len];
out[0..2].copy_from_slice(&0u16.to_be_bytes());
out[2..4].copy_from_slice(&2u16.to_be_bytes());
out[4..6].copy_from_slice(&3u16.to_be_bytes());
out[6..8].copy_from_slice(&1u16.to_be_bytes());
out[8..12].copy_from_slice(&(sub4_off as u32).to_be_bytes());
out[12..14].copy_from_slice(&0u16.to_be_bytes());
out[14..16].copy_from_slice(&4u16.to_be_bytes());
out[16..20].copy_from_slice(&(sub8_off as u32).to_be_bytes());
out.extend_from_slice(&sub4);
out.extend_from_slice(&sub8);
let cmap = CmapTable::parse(&out).unwrap();
assert_eq!(cmap.lookup(0x41), Some(100));
assert_eq!(cmap.lookup(0x10400), Some(2000));
}
fn build_format10(start: u32, glyphs: &[u16]) -> Vec<u8> {
let sub_len = 20 + glyphs.len() * 2;
let mut sub = vec![0u8; sub_len];
sub[0..2].copy_from_slice(&10u16.to_be_bytes()); sub[4..8].copy_from_slice(&(sub_len as u32).to_be_bytes()); sub[12..16].copy_from_slice(&start.to_be_bytes());
sub[16..20].copy_from_slice(&(glyphs.len() as u32).to_be_bytes());
for (i, &g) in glyphs.iter().enumerate() {
let off = 20 + i * 2;
sub[off..off + 2].copy_from_slice(&g.to_be_bytes());
}
sub
}
#[test]
fn format10_round_trip() {
let sub = build_format10(0x10300, &[50, 0, 52]);
let cmap_bytes = build_cmap_with_subtable(10, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x10300), Some(50));
assert_eq!(cmap.lookup(0x10301), None);
assert_eq!(cmap.lookup(0x10302), Some(52));
assert_eq!(cmap.lookup(0x102FF), None);
assert_eq!(cmap.lookup(0x10303), None);
assert_eq!(cmap.lookup(0x0041), None);
}
#[test]
fn format10_only_is_pickable() {
let glyphs: Vec<u16> = (1u16..=8).collect();
let sub = build_format10(0x1D400, &glyphs);
let cmap_bytes = build_cmap_with_subtable(10, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
for (i, g) in glyphs.iter().enumerate() {
assert_eq!(cmap.lookup(0x1D400 + i as u32), Some(*g));
}
}
fn build_format2(
sub_header_keys: &[u16; 256],
sub_headers: &[(u16, u16, i16, u16)],
glyph_id_array: &[u16],
) -> Vec<u8> {
let header_len = 6;
let keys_len = 512;
let sub_headers_len = sub_headers.len() * 8;
let glyph_array_len = glyph_id_array.len() * 2;
let total = header_len + keys_len + sub_headers_len + glyph_array_len;
let mut sub = vec![0u8; total];
sub[0..2].copy_from_slice(&2u16.to_be_bytes()); sub[2..4].copy_from_slice(&(total as u16).to_be_bytes()); for (i, k) in sub_header_keys.iter().enumerate() {
let off = 6 + i * 2;
sub[off..off + 2].copy_from_slice(&k.to_be_bytes());
}
let sub_headers_off = header_len + keys_len;
for (i, &(first_code, entry_count, id_delta, id_range_offset)) in
sub_headers.iter().enumerate()
{
let off = sub_headers_off + i * 8;
sub[off..off + 2].copy_from_slice(&first_code.to_be_bytes());
sub[off + 2..off + 4].copy_from_slice(&entry_count.to_be_bytes());
sub[off + 4..off + 6].copy_from_slice(&(id_delta as u16).to_be_bytes());
sub[off + 6..off + 8].copy_from_slice(&id_range_offset.to_be_bytes());
}
let glyph_array_off = sub_headers_off + sub_headers_len;
for (i, &g) in glyph_id_array.iter().enumerate() {
let off = glyph_array_off + i * 2;
sub[off..off + 2].copy_from_slice(&g.to_be_bytes());
}
sub
}
#[test]
fn format2_subheader_zero_maps_single_byte() {
let keys = [0u16; 256];
let sub_headers = [(0x30u16, 10u16, 0i16, 2u16)];
let glyph_ids: Vec<u16> = (100..110).collect();
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
for (i, cp) in (0x30u32..0x3A).enumerate() {
assert_eq!(cmap.lookup(cp), Some(100 + i as u16), "cp {cp:#x}");
}
assert_eq!(cmap.lookup(0x2F), None);
assert_eq!(cmap.lookup(0x3A), None);
}
#[test]
fn format2_id_delta_offsets_nonzero_entries() {
let keys = [0u16; 256];
let sub_headers = [(0x30u16, 3u16, 1000i16, 2u16)];
let glyph_ids = [200u16, 0u16, 300u16];
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x30), Some(1200));
assert_eq!(cmap.lookup(0x31), None);
assert_eq!(cmap.lookup(0x32), Some(1300));
}
#[test]
fn format2_id_delta_wraps_modulo_65536() {
let keys = [0u16; 256];
let sub_headers = [(0x30u16, 1u16, -6i16, 2u16)];
let glyph_ids = [5u16];
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x30), Some(0xFFFF));
}
#[test]
fn format2_lead_byte_routes_to_subheader() {
let mut keys = [0u16; 256];
keys[0x81] = 8;
let sub_headers = [
(0x20u16, 1u16, 0i16, 10u16), (0x40u16, 3u16, 0i16, 4u16), ];
let glyph_ids = [7u16, 1001u16, 1002u16, 1003u16];
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x20), Some(7));
assert_eq!(cmap.lookup(0x8140), Some(1001));
assert_eq!(cmap.lookup(0x8141), Some(1002));
assert_eq!(cmap.lookup(0x8142), Some(1003));
assert_eq!(cmap.lookup(0x8143), None);
assert_eq!(cmap.lookup(0x813F), None);
assert_eq!(cmap.lookup(0x8240), None);
}
#[test]
fn format2_id_delta_lets_subheaders_share_sub_array() {
let mut keys = [0u16; 256];
keys[0x81] = 8; keys[0x82] = 16;
let sub_headers = [
(0x00u16, 0u16, 0i16, 0u16),
(0x40u16, 3u16, 100i16, 10u16),
(0x40u16, 3u16, 200i16, 2u16),
];
let glyph_ids = [1u16, 2u16, 3u16];
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x8140), Some(101));
assert_eq!(cmap.lookup(0x8141), Some(102));
assert_eq!(cmap.lookup(0x8142), Some(103));
assert_eq!(cmap.lookup(0x8240), Some(201));
assert_eq!(cmap.lookup(0x8241), Some(202));
assert_eq!(cmap.lookup(0x8242), Some(203));
}
#[test]
fn format2_zero_glyph_array_entry_is_missing_glyph() {
let mut keys = [0u16; 256];
keys[0x81] = 8;
let sub_headers = [
(0x00u16, 0u16, 0i16, 0u16),
(0x40u16, 2u16, 500i16, 2u16),
];
let glyph_ids = [0u16, 42u16];
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x8140), None);
assert_eq!(cmap.lookup(0x8141), Some(542));
}
#[test]
fn format2_high_byte_through_subheader_zero_is_rejected() {
let keys = [0u16; 256]; let sub_headers = [(0x20u16, 0xE0u16, 0i16, 2u16)];
let glyph_ids: Vec<u16> = (1u16..(1 + 0xE0)).collect();
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
assert_eq!(cmap.lookup(0x41), Some(1 + (0x41 - 0x20) as u16));
assert_eq!(cmap.lookup(0x4141), None);
}
#[test]
fn format2_does_not_displace_format12() {
let sub12_len = 16 + 12;
let mut sub12 = vec![0u8; sub12_len];
sub12[0..2].copy_from_slice(&12u16.to_be_bytes());
sub12[4..8].copy_from_slice(&(sub12_len as u32).to_be_bytes());
sub12[12..16].copy_from_slice(&1u32.to_be_bytes());
sub12[16..20].copy_from_slice(&0x0041u32.to_be_bytes());
sub12[20..24].copy_from_slice(&0x0041u32.to_be_bytes());
sub12[24..28].copy_from_slice(&7u32.to_be_bytes());
let keys = [0u16; 256];
let sub_headers = [(0x41u16, 1u16, 0i16, 2u16)];
let glyph_ids = [99u16];
let sub2 = build_format2(&keys, &sub_headers, &glyph_ids);
let header_len = 4 + 2 * 8;
let sub12_off = header_len;
let sub2_off = sub12_off + sub12.len();
let mut out = vec![0u8; header_len];
out[0..2].copy_from_slice(&0u16.to_be_bytes());
out[2..4].copy_from_slice(&2u16.to_be_bytes());
out[4..6].copy_from_slice(&3u16.to_be_bytes());
out[6..8].copy_from_slice(&10u16.to_be_bytes());
out[8..12].copy_from_slice(&(sub12_off as u32).to_be_bytes());
out[12..14].copy_from_slice(&1u16.to_be_bytes());
out[14..16].copy_from_slice(&1u16.to_be_bytes());
out[16..20].copy_from_slice(&(sub2_off as u32).to_be_bytes());
out.extend_from_slice(&sub12);
out.extend_from_slice(&sub2);
let cmap = CmapTable::parse(&out).unwrap();
assert_eq!(cmap.lookup(0x0041), Some(7));
}
#[test]
fn format2_only_is_pickable() {
let keys = [0u16; 256];
let sub_headers = [(0x30u16, 10u16, 0i16, 2u16)];
let glyph_ids: Vec<u16> = (1u16..11).collect();
let sub = build_format2(&keys, &sub_headers, &glyph_ids);
let cmap_bytes = build_cmap_with_subtable(2, &sub);
let cmap = CmapTable::parse(&cmap_bytes).unwrap();
for (i, cp) in (0x30u32..0x3A).enumerate() {
assert_eq!(cmap.lookup(cp), Some(1 + i as u16));
}
}
}