use crate::parser::{read_u16, read_u8};
use crate::Error;
pub(crate) const EXPERT_SIDS: [u16; 165] = [
1, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 13, 14, 15, 99,
239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 27, 28, 249, 250, 251, 252, 253, 254, 255,
256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 109, 110, 267, 268, 269, 270, 271, 272,
273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291,
292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310,
311, 312, 313, 314, 315, 316, 317, 318, 158, 155, 163, 319, 320, 321, 322, 323, 324, 325, 326,
150, 164, 169, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362,
363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378,
];
pub(crate) const EXPERT_SUBSET_SIDS: [u16; 86] = [
1, 231, 232, 235, 236, 237, 238, 13, 14, 15, 99, 239, 240, 241, 242, 243, 244, 245, 246, 247,
248, 27, 28, 249, 250, 251, 253, 254, 255, 256, 257, 258, 259,
260, 261, 262, 263, 264, 265, 266, 109, 110, 267, 268, 269, 270, 272, 300, 301, 302, 305, 314,
315, 158, 155, 163, 320, 321, 322, 323, 324, 325, 326, 150, 164, 169, 327, 328, 329, 330, 331,
332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346,
];
#[derive(Debug, Clone)]
pub(crate) enum Charset<'a> {
IsoAdobe,
Expert,
ExpertSubset,
Format0 { bytes: &'a [u8], num_glyphs: u32 },
Format1 { bytes: &'a [u8], num_glyphs: u32 },
Format2 { bytes: &'a [u8], num_glyphs: u32 },
}
impl<'a> Charset<'a> {
pub(crate) fn parse(bytes: &'a [u8], top_off: i32, num_glyphs: u32) -> Result<Self, Error> {
match top_off {
0 => Ok(Self::IsoAdobe),
1 => Ok(Self::Expert),
2 => Ok(Self::ExpertSubset),
n if n < 0 => Err(Error::Cff("negative charset offset")),
n => {
let off = n as usize;
if off >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let format = read_u8(bytes, off)?;
let payload = &bytes[off + 1..];
match format {
0 => Ok(Self::Format0 {
bytes: payload,
num_glyphs,
}),
1 => Ok(Self::Format1 {
bytes: payload,
num_glyphs,
}),
2 => Ok(Self::Format2 {
bytes: payload,
num_glyphs,
}),
_ => Err(Error::Cff("unknown charset format")),
}
}
}
}
pub(crate) fn gid_of_sid(&self, sid: u16) -> Option<u16> {
if sid == 0 {
return Some(0); }
match self {
Self::IsoAdobe => {
if (sid as usize) < 229 {
Some(sid)
} else {
None
}
}
Self::Expert => predefined_gid_of_sid(&EXPERT_SIDS, sid),
Self::ExpertSubset => predefined_gid_of_sid(&EXPERT_SUBSET_SIDS, sid),
Self::Format0 { bytes, num_glyphs } => {
let n = (*num_glyphs).saturating_sub(1) as usize;
for i in 0..n {
let off = i * 2;
if let Ok(entry) = read_u16(bytes, off) {
if entry == sid {
return Some((i + 1) as u16);
}
} else {
return None;
}
}
None
}
Self::Format1 { bytes, num_glyphs } => walk_runs_for_sid(bytes, *num_glyphs, sid, 1),
Self::Format2 { bytes, num_glyphs } => walk_runs_for_sid(bytes, *num_glyphs, sid, 2),
}
}
pub(crate) fn sid_of(&self, gid: u16) -> Option<u16> {
if gid == 0 {
return Some(0); }
match self {
Self::IsoAdobe => {
if (gid as usize) < 229 {
Some(gid)
} else {
None
}
}
Self::Expert => predefined_sid_of(&EXPERT_SIDS, gid),
Self::ExpertSubset => predefined_sid_of(&EXPERT_SUBSET_SIDS, gid),
Self::Format0 { bytes, num_glyphs } => {
if (gid as u32) >= *num_glyphs {
return None;
}
let off = (gid as usize - 1) * 2;
read_u16(bytes, off).ok()
}
Self::Format1 { bytes, num_glyphs } => walk_runs(bytes, *num_glyphs, gid, 1),
Self::Format2 { bytes, num_glyphs } => walk_runs(bytes, *num_glyphs, gid, 2),
}
}
}
fn predefined_sid_of(table: &[u16], gid: u16) -> Option<u16> {
if gid == 0 {
return Some(0); }
table.get(gid as usize - 1).copied()
}
fn predefined_gid_of_sid(table: &[u16], target_sid: u16) -> Option<u16> {
if target_sid == 0 {
return Some(0); }
table
.iter()
.position(|&s| s == target_sid)
.map(|i| (i + 1) as u16)
}
fn walk_runs_for_sid(
bytes: &[u8],
num_glyphs: u32,
target_sid: u16,
n_left_size: usize,
) -> Option<u16> {
let mut gid: u32 = 1;
let mut off: usize = 0;
while gid < num_glyphs {
let first = read_u16(bytes, off).ok()?;
off += 2;
let n_left: u32 = match n_left_size {
1 => read_u8(bytes, off).ok()? as u32,
2 => read_u16(bytes, off).ok()? as u32,
_ => unreachable!(),
};
off += n_left_size;
let run_last_sid = (first as u32) + n_left;
if (target_sid as u32) >= (first as u32) && (target_sid as u32) <= run_last_sid {
let in_run = (target_sid as u32) - (first as u32);
let resolved = gid + in_run;
if resolved > u16::MAX as u32 {
return None;
}
return Some(resolved as u16);
}
gid = gid + n_left + 1;
}
None
}
fn walk_runs(bytes: &[u8], num_glyphs: u32, target_gid: u16, n_left_size: usize) -> Option<u16> {
let mut gid: u32 = 1;
let mut off: usize = 0;
while gid < num_glyphs {
let first = read_u16(bytes, off).ok()?;
off += 2;
let n_left: u32 = match n_left_size {
1 => read_u8(bytes, off).ok()? as u32,
2 => read_u16(bytes, off).ok()? as u32,
_ => unreachable!(),
};
off += n_left_size;
let run_end = gid + n_left;
if (target_gid as u32) >= gid && (target_gid as u32) <= run_end {
let in_run = (target_gid as u32) - gid;
let sid = first as u32 + in_run;
if sid > u16::MAX as u32 {
return None;
}
return Some(sid as u16);
}
gid = run_end + 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iso_adobe_identity() {
let cs = Charset::IsoAdobe;
assert_eq!(cs.sid_of(0), Some(0));
assert_eq!(cs.sid_of(1), Some(1));
assert_eq!(cs.sid_of(228), Some(228));
assert_eq!(cs.sid_of(229), None);
}
#[test]
fn format0_walk() {
let payload = vec![0x00, 100, 0x00, 200, 0x01, 0x2C];
let cs = Charset::Format0 {
bytes: &payload,
num_glyphs: 4,
};
assert_eq!(cs.sid_of(1), Some(100));
assert_eq!(cs.sid_of(2), Some(200));
assert_eq!(cs.sid_of(3), Some(300));
assert_eq!(cs.sid_of(4), None);
}
#[test]
fn format1_walk() {
let payload = vec![0x00, 50, 0x02, 0x00, 70, 0x01];
let cs = Charset::Format1 {
bytes: &payload,
num_glyphs: 6,
};
assert_eq!(cs.sid_of(1), Some(50));
assert_eq!(cs.sid_of(2), Some(51));
assert_eq!(cs.sid_of(3), Some(52));
assert_eq!(cs.sid_of(4), Some(70));
assert_eq!(cs.sid_of(5), Some(71));
}
#[test]
fn gid_of_sid_iso_adobe() {
let cs = Charset::IsoAdobe;
assert_eq!(cs.gid_of_sid(0), Some(0));
assert_eq!(cs.gid_of_sid(1), Some(1));
assert_eq!(cs.gid_of_sid(228), Some(228));
assert_eq!(cs.gid_of_sid(229), None);
}
#[test]
fn gid_of_sid_format0() {
let payload = vec![0x00, 100, 0x00, 200, 0x01, 0x2C]; let cs = Charset::Format0 {
bytes: &payload,
num_glyphs: 4,
};
assert_eq!(cs.gid_of_sid(100), Some(1));
assert_eq!(cs.gid_of_sid(200), Some(2));
assert_eq!(cs.gid_of_sid(300), Some(3));
assert_eq!(cs.gid_of_sid(999), None);
assert_eq!(cs.gid_of_sid(0), Some(0));
}
#[test]
fn gid_of_sid_format1_within_run() {
let payload = vec![0x00, 50, 0x02, 0x00, 70, 0x01];
let cs = Charset::Format1 {
bytes: &payload,
num_glyphs: 6,
};
assert_eq!(cs.gid_of_sid(50), Some(1));
assert_eq!(cs.gid_of_sid(51), Some(2));
assert_eq!(cs.gid_of_sid(52), Some(3));
assert_eq!(cs.gid_of_sid(70), Some(4));
assert_eq!(cs.gid_of_sid(71), Some(5));
assert_eq!(cs.gid_of_sid(60), None);
}
#[test]
fn expert_table_lengths() {
assert_eq!(EXPERT_SIDS.len(), 165);
assert_eq!(EXPERT_SUBSET_SIDS.len(), 86);
}
#[test]
fn expert_landmark_sids() {
let cs = Charset::Expert;
assert_eq!(cs.sid_of(0), Some(0));
assert_eq!(cs.sid_of(1), Some(1));
assert_eq!(cs.sid_of(2), Some(229));
assert_eq!(cs.sid_of(12), Some(13));
assert_eq!(cs.sid_of(15), Some(99));
assert_eq!(cs.sid_of(16), Some(239));
assert_eq!(cs.sid_of(165), Some(378));
assert_eq!(cs.sid_of(166), None);
}
#[test]
fn expert_subset_landmark_sids() {
let cs = Charset::ExpertSubset;
assert_eq!(cs.sid_of(0), Some(0));
assert_eq!(cs.sid_of(1), Some(1)); assert_eq!(cs.sid_of(2), Some(231)); assert_eq!(cs.sid_of(11), Some(99)); assert_eq!(cs.sid_of(12), Some(239)); assert_eq!(cs.sid_of(86), Some(346)); assert_eq!(cs.sid_of(87), None);
}
#[test]
fn expert_round_trip_sid_gid() {
let cs = Charset::Expert;
for gid in 0u16..=165 {
let sid = cs.sid_of(gid).expect("every Expert gid has a SID");
assert_eq!(
cs.gid_of_sid(sid),
Some(gid),
"Expert gid {gid} (sid {sid}) failed to round-trip"
);
}
}
#[test]
fn expert_subset_round_trip_sid_gid() {
let cs = Charset::ExpertSubset;
for gid in 0u16..=86 {
let sid = cs.sid_of(gid).expect("every ExpertSubset gid has a SID");
assert_eq!(
cs.gid_of_sid(sid),
Some(gid),
"ExpertSubset gid {gid} (sid {sid}) failed to round-trip"
);
}
}
#[test]
fn expert_sids_resolve_to_standard_strings() {
use crate::cff::strings::STANDARD_STRINGS;
for &sid in EXPERT_SIDS.iter().chain(EXPERT_SUBSET_SIDS.iter()) {
assert!(
(sid as usize) < STANDARD_STRINGS.len(),
"predefined-charset SID {sid} exceeds the standard-strings table"
);
}
assert_eq!(STANDARD_STRINGS[229], "exclamsmall");
assert_eq!(STANDARD_STRINGS[378], "Ydieresissmall");
assert_eq!(STANDARD_STRINGS[346], "commainferior");
}
#[test]
fn parse_dispatches_predefined_expert() {
let cs = Charset::parse(&[], 1, 0).expect("expert parse");
assert!(matches!(cs, Charset::Expert));
let cs = Charset::parse(&[], 2, 0).expect("expert-subset parse");
assert!(matches!(cs, Charset::ExpertSubset));
}
#[test]
fn parse_via_offset_dispatch_format0() {
let mut table = vec![0u8, 0, 0, 0]; table.push(0); table.extend_from_slice(&[0, 100, 0, 200]); let cs = Charset::parse(&table, 4, 3).expect("parse");
assert!(matches!(cs, Charset::Format0 { .. }));
assert_eq!(cs.sid_of(1), Some(100));
assert_eq!(cs.sid_of(2), Some(200));
}
}