use std::collections::HashMap;
pub struct CMap {
pub code_to_cid: HashMap<u32, u32>,
pub code_lengths: [u8; 256],
pub wmode: u8,
}
impl CMap {
pub fn identity() -> Self {
Self {
code_to_cid: HashMap::new(),
code_lengths: [2; 256],
wmode: 0,
}
}
pub fn decode(&self, code: u32) -> u32 {
self.code_to_cid.get(&code).copied().unwrap_or(code)
}
pub fn code_width(&self, first_byte: u8) -> usize {
let w = self.code_lengths[first_byte as usize];
if w == 0 { 2 } else { w as usize }
}
pub fn parse(data: &[u8]) -> Self {
Self::parse_with_loader(data, None)
}
pub fn parse_with_loader(
data: &[u8],
loader: Option<&dyn Fn(&[u8]) -> Option<Vec<u8>>>,
) -> Self {
let mut code_to_cid = HashMap::new();
let mut codespace_ranges: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let mut wmode: u8 = 0;
let text = String::from_utf8_lossy(data);
#[allow(clippy::while_let_on_iterator)]
let mut lines = text.lines();
while let Some(line) = lines.next() {
let line = line.trim();
if line.ends_with("usecmap") {
let name = line.strip_suffix("usecmap").unwrap_or("").trim();
let name = name.strip_prefix('/').unwrap_or(name);
if !name.is_empty() {
if let Some(load_fn) = loader {
if let Some(base_data) = load_fn(name.as_bytes()) {
let base = Self::parse_with_loader(&base_data, loader);
for (k, v) in base.code_to_cid {
code_to_cid.entry(k).or_insert(v);
}
if codespace_ranges.is_empty() {
for fb in 0..256u16 {
let w = base.code_lengths[fb as usize];
if w > 0 {
let low = if w == 1 {
vec![fb as u8]
} else {
vec![fb as u8, 0x00]
};
let high = if w == 1 {
vec![fb as u8]
} else {
vec![fb as u8, 0xFF]
};
codespace_ranges.push((low, high));
}
}
}
if wmode == 0 {
wmode = base.wmode;
}
}
}
}
}
if let Some(rest) = line.strip_prefix("/WMode") {
let rest = rest.trim();
if let Some(rest) = rest.strip_prefix("def").or(Some(rest)) {
if let Ok(v) = rest.trim().parse::<u8>() {
wmode = v;
}
}
}
if line.ends_with("/WMode def") {
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(v) = parts.first().and_then(|s| s.parse::<u8>().ok()) {
wmode = v;
}
}
if line.ends_with("begincodespacerange") {
while let Some(range_line) = lines.next() {
let range_line = range_line.trim();
if range_line == "endcodespacerange" {
break;
}
if let Some((low, high)) = parse_codespace_range(range_line) {
codespace_ranges.push((low, high));
}
}
}
if line.contains("begincidchar") {
if let Some(inline) = extract_inline_data(line, "begincidchar", "endcidchar") {
if let Some((code, cid)) = parse_cidchar_line(inline) {
code_to_cid.insert(code, cid);
}
} else if line.ends_with("begincidchar") {
while let Some(char_line) = lines.next() {
let char_line = char_line.trim();
if char_line == "endcidchar" {
break;
}
if let Some((code, cid)) = parse_cidchar_line(char_line) {
code_to_cid.insert(code, cid);
}
}
}
}
if line.contains("begincidrange") {
if let Some(inline) = extract_inline_data(line, "begincidrange", "endcidrange") {
if let Some((start, end, cid_start)) = parse_cidrange_line(inline) {
for code in start..=end {
code_to_cid.insert(code, cid_start + (code - start));
}
}
} else if line.ends_with("begincidrange") {
while let Some(range_line) = lines.next() {
let range_line = range_line.trim();
if range_line == "endcidrange" {
break;
}
if let Some((start, end, cid_start)) = parse_cidrange_line(range_line) {
for code in start..=end {
code_to_cid.insert(code, cid_start + (code - start));
}
}
}
}
}
if line.contains("beginbfchar") {
if let Some(inline) = extract_inline_data(line, "beginbfchar", "endbfchar") {
if let Some((code, unicode)) = parse_bfchar_line(inline) {
code_to_cid.insert(code, unicode);
}
} else if line.ends_with("beginbfchar") {
while let Some(char_line) = lines.next() {
let char_line = char_line.trim();
if char_line == "endbfchar" {
break;
}
if let Some((code, unicode)) = parse_bfchar_line(char_line) {
code_to_cid.insert(code, unicode);
}
}
}
}
if line.contains("beginbfrange") {
if let Some(inline) = extract_inline_data(line, "beginbfrange", "endbfrange") {
if let Some((start, end, cid_start)) = parse_cidrange_line(inline) {
for code in start..=end {
code_to_cid.insert(code, cid_start + (code - start));
}
}
} else if line.ends_with("beginbfrange") {
while let Some(range_line) = lines.next() {
let range_line = range_line.trim();
if range_line == "endbfrange" {
break;
}
if let Some((start, end, cid_start)) = parse_cidrange_line(range_line) {
for code in start..=end {
code_to_cid.insert(code, cid_start + (code - start));
}
}
}
}
}
}
let mut code_lengths = [0u8; 256];
if codespace_ranges.is_empty() {
code_lengths = [2; 256];
} else {
for (low, high) in &codespace_ranges {
let width = low.len() as u8;
let first_lo = low[0];
let first_hi = high[0];
for byte in first_lo..=first_hi {
let cur = code_lengths[byte as usize];
if cur == 0 || width < cur {
code_lengths[byte as usize] = width;
}
}
}
}
CMap {
code_to_cid,
code_lengths,
wmode,
}
}
}
fn extract_inline_data<'a>(line: &'a str, begin_kw: &str, end_kw: &str) -> Option<&'a str> {
let begin_pos = line.find(begin_kw)?;
let end_pos = line.find(end_kw)?;
if end_pos <= begin_pos {
return None;
}
let data_start = begin_pos + begin_kw.len();
if data_start >= end_pos {
return None;
}
let data = line[data_start..end_pos].trim();
if data.is_empty() { None } else { Some(data) }
}
fn parse_codespace_range(line: &str) -> Option<(Vec<u8>, Vec<u8>)> {
let tokens = split_cmap_tokens(line);
if tokens.len() >= 2 {
let low = parse_hex_bytes(&tokens[0])?;
let high = parse_hex_bytes(&tokens[1])?;
if low.len() == high.len() && !low.is_empty() {
Some((low, high))
} else {
None
}
} else {
None
}
}
fn parse_hex_bytes(s: &str) -> Option<Vec<u8>> {
let s = s.trim();
if s.starts_with('<') && s.ends_with('>') {
let hex = &s[1..s.len() - 1];
let mut bytes = Vec::new();
let mut i = 0;
while i + 1 < hex.len() {
bytes.push(u8::from_str_radix(&hex[i..i + 2], 16).ok()?);
i += 2;
}
if i < hex.len() {
bytes.push(u8::from_str_radix(&format!("{}0", &hex[i..]), 16).ok()?);
}
Some(bytes)
} else {
None
}
}
fn split_cmap_tokens(line: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut i = 0;
let bytes = line.as_bytes();
while i < bytes.len() {
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
if i >= bytes.len() {
break;
}
if bytes[i] == b'<' {
let start = i;
while i < bytes.len() && bytes[i] != b'>' {
i += 1;
}
if i < bytes.len() {
i += 1; }
tokens.push(line[start..i].to_string());
} else {
let start = i;
while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b'<' {
i += 1;
}
tokens.push(line[start..i].to_string());
}
}
tokens
}
fn parse_hex(s: &str) -> Option<u32> {
let s = s.trim();
if s.starts_with('<') && s.ends_with('>') {
u32::from_str_radix(&s[1..s.len() - 1], 16).ok()
} else {
None
}
}
fn parse_cidchar_line(line: &str) -> Option<(u32, u32)> {
let tokens = split_cmap_tokens(line);
if tokens.len() >= 2 {
let code = parse_hex(&tokens[0])?;
let cid = tokens[1].parse::<u32>().ok()?;
Some((code, cid))
} else {
None
}
}
fn parse_cidrange_line(line: &str) -> Option<(u32, u32, u32)> {
let tokens = split_cmap_tokens(line);
if tokens.len() >= 3 {
let start = parse_hex(&tokens[0])?;
let end = parse_hex(&tokens[1])?;
let cid_start = if tokens[2].starts_with('<') {
parse_hex(&tokens[2])?
} else {
tokens[2].parse::<u32>().ok()?
};
Some((start, end, cid_start))
} else {
None
}
}
fn parse_bfchar_line(line: &str) -> Option<(u32, u32)> {
let tokens = split_cmap_tokens(line);
if tokens.len() >= 2 {
let code = parse_hex(&tokens[0])?;
let unicode = parse_hex(&tokens[1])?;
Some((code, unicode))
} else {
None
}
}