use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use super::bcmap::decode_bcmap;
use super::data::bcmaps::bcmap_bytes;
use super::lexer::{Cmd, Lexer, Op, Token};
use crate::error::{Error, Result};
const MAX_MAP_RANGE: u32 = 0x00FF_FFFF;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum MapValue {
Cid(u32),
Bytes(Vec<u8>),
}
#[derive(Debug, Clone)]
pub(crate) struct CMap {
codespace_ranges: [Vec<(u32, u32)>; 4],
map: HashMap<u32, MapValue>,
vertical: bool,
name: String,
identity: bool,
use_cmap: Option<String>,
max_entries: usize,
truncated: bool,
}
impl CMap {
pub fn new() -> Self {
Self::with_limit(usize::MAX)
}
pub fn with_limit(max_entries: usize) -> Self {
Self {
codespace_ranges: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
map: HashMap::new(),
vertical: false,
name: String::new(),
identity: false,
use_cmap: None,
max_entries,
truncated: false,
}
}
pub fn identity_h() -> Self {
let mut c = Self::new();
c.vertical = false;
c.name = "Identity-H".into();
c.identity = true;
c.add_codespace_range(2, 0, 0xffff);
c
}
pub fn identity_v() -> Self {
let mut c = Self::new();
c.vertical = true;
c.name = "Identity-V".into();
c.identity = true;
c.add_codespace_range(2, 0, 0xffff);
c
}
pub fn from_identity_name(name: &str) -> Option<Self> {
match name {
"Identity-H" => Some(Self::identity_h()),
"Identity-V" => Some(Self::identity_v()),
_ => None,
}
}
pub fn name(&self) -> &str {
&self.name
}
#[allow(dead_code)]
pub fn vertical(&self) -> bool {
self.vertical
}
pub fn set_vertical(&mut self, v: bool) {
self.vertical = v;
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = name.into();
}
pub fn is_identity(&self) -> bool {
self.identity
}
pub fn truncated(&self) -> bool {
self.truncated
}
#[allow(dead_code)]
pub fn use_cmap(&self) -> Option<&str> {
self.use_cmap.as_deref()
}
pub fn has_codespace(&self) -> bool {
self.codespace_ranges.iter().any(|r| !r.is_empty())
}
#[allow(dead_code)]
pub fn codespace_ranges_for(&self, n: usize) -> &[(u32, u32)] {
if (1..=4).contains(&n) {
&self.codespace_ranges[n - 1]
} else {
&[]
}
}
pub fn contains(&self, code: u32) -> bool {
if self.identity {
return code <= 0xffff;
}
self.map.contains_key(&code)
}
pub fn add_codespace_range(&mut self, n: usize, low: u32, high: u32) {
if (1..=4).contains(&n) {
self.codespace_ranges[n - 1].push((low, high));
}
}
pub fn extend_from_parent(&mut self, parent: &CMap) {
if !self.has_codespace() {
for i in 0..4 {
self.codespace_ranges[i] = parent.codespace_ranges[i].clone();
}
}
parent.for_each(|key, value| {
if !self.contains(key) {
self.map_one(key, value.clone());
}
});
if parent.truncated {
self.truncated = true;
}
}
pub fn load_predefined(name: &str) -> Result<Self> {
let arc = Self::load_predefined_shared(name, None)?;
Ok(Arc::try_unwrap(arc).unwrap_or_else(|a| (*a).clone()))
}
pub fn load_predefined_shared(
name: &str,
cache: Option<&Mutex<HashMap<String, Arc<CMap>>>>,
) -> Result<Arc<CMap>> {
if let Some(cache) = cache {
if let Ok(guard) = cache.lock() {
if let Some(c) = guard.get(name) {
return Ok(Arc::clone(c));
}
}
}
let built = Self::build_predefined(name, cache)?;
let arc = Arc::new(built);
if let Some(cache) = cache {
if let Ok(mut guard) = cache.lock() {
if let Some(c) = guard.get(name) {
return Ok(Arc::clone(c));
}
guard.insert(name.to_string(), Arc::clone(&arc));
}
}
Ok(arc)
}
fn build_predefined(
name: &str,
cache: Option<&Mutex<HashMap<String, Arc<CMap>>>>,
) -> Result<Self> {
if let Some(c) = Self::from_identity_name(name) {
return Ok(c);
}
let Some(bytes) = bcmap_bytes(name) else {
return Err(Error::Reader(format!("Unknown CMap name: {name}")));
};
let mut cmap = Self::new();
cmap.set_name(name);
let use_parent = decode_bcmap(bytes, &mut cmap).map_err(|e| {
Error::Reader(format!("failed to decode bcmap {name}: {e}"))
})?;
if let Some(parent_name) = use_parent {
cmap.use_cmap = Some(parent_name.clone());
let parent = Self::load_predefined_shared(&parent_name, cache)?;
cmap.extend_from_parent(&parent);
}
Ok(cmap)
}
pub fn map_one(&mut self, src: u32, dst: MapValue) {
if !self.reserve_entries(1) {
return;
}
self.map.insert(src, dst);
}
pub fn map_cid_range(&mut self, low: u32, high: u32, mut dst_low: u32) {
if high < low || high - low > MAX_MAP_RANGE {
return;
}
let count = u64::from(high - low) + 1;
if !self.reserve_entries(count) {
return;
}
for code in low..=high {
self.map.insert(code, MapValue::Cid(dst_low));
dst_low = dst_low.saturating_add(1);
}
}
pub fn map_bf_range(&mut self, low: u32, high: u32, mut dst: Vec<u8>) {
if high < low || high - low > MAX_MAP_RANGE || dst.is_empty() {
return;
}
let count = u64::from(high - low) + 1;
if !self.reserve_entries(count) {
return;
}
for code in low..=high {
self.map.insert(code, MapValue::Bytes(dst.clone()));
increment_last_byte(&mut dst);
}
}
pub fn map_bf_range_to_array(&mut self, low: u32, high: u32, array: &[Vec<u8>]) {
if high < low || high - low > MAX_MAP_RANGE {
return;
}
let count_range = u64::from(high - low) + 1;
let effective = count_range.min(array.len() as u64);
if !self.reserve_entries(effective) {
return;
}
for (code, dst) in (low..=high).zip(array) {
self.map.insert(code, MapValue::Bytes(dst.clone()));
}
}
fn reserve_entries(&mut self, count: u64) -> bool {
let remaining = (self.max_entries as u64).saturating_sub(self.map.len() as u64);
if count > remaining {
self.truncated = true;
return false;
}
true
}
pub fn lookup(&self, code: u32) -> Option<Cow<'_, MapValue>> {
if self.identity {
if code <= 0xffff {
return Some(Cow::Owned(MapValue::Cid(code)));
}
return None;
}
self.map.get(&code).map(Cow::Borrowed)
}
pub fn for_each<F: FnMut(u32, &MapValue)>(&self, mut f: F) {
if self.identity {
for i in 0u32..=0xffff {
f(i, &MapValue::Cid(i));
}
return;
}
for (&k, v) in &self.map {
f(k, v);
}
}
pub fn read_char_code(&self, bytes: &[u8], offset: usize) -> Option<(u32, usize)> {
let mut c: u32 = 0;
for (n, &b) in bytes.get(offset..)?.iter().take(4).enumerate() {
c = (c << 8) | u32::from(b);
for &(low, high) in &self.codespace_ranges[n] {
if low <= c && c <= high {
return Some((c, n + 1));
}
}
}
None
}
pub fn parse_embedded(data: &[u8], max_entries: usize) -> Self {
let mut cmap = Self::with_limit(max_entries);
let mut lexer = Lexer::new(data);
let mut previous: Option<Token> = None;
loop {
let tok = lexer.next_token();
match &tok {
Token::Eof => break,
Token::Name(n) => {
if n == "WMode" {
if let Token::Int(v) = lexer.next_token() {
cmap.vertical = v != 0;
}
} else if n == "CMapName" {
if let Token::Name(nm) = lexer.next_token() {
cmap.name = nm.into_owned();
}
}
previous = Some(tok);
}
Token::Cmd(cmd) => match cmd {
Cmd::Op(Op::EndCmap) => break,
Cmd::Op(Op::UseCmap) => {
if let Some(Token::Name(nm)) = &previous {
cmap.use_cmap = Some(nm.to_string());
}
previous = None;
}
Cmd::Op(Op::BeginCodespaceRange) => {
parse_codespace_range(&mut cmap, &mut lexer);
previous = None;
}
Cmd::Op(Op::BeginBfChar) => {
parse_bf_char(&mut cmap, &mut lexer);
previous = None;
}
Cmd::Op(Op::BeginCidChar) => {
parse_cid_char(&mut cmap, &mut lexer);
previous = None;
}
Cmd::Op(Op::BeginBfRange) => {
parse_bf_range(&mut cmap, &mut lexer);
previous = None;
}
Cmd::Op(Op::BeginCidRange) => {
parse_cid_range(&mut cmap, &mut lexer);
previous = None;
}
_ => {
previous = Some(tok);
}
},
_ => {
previous = Some(tok);
}
}
}
if let Some(parent_name) = cmap.use_cmap.clone() {
if let Ok(parent) = Self::load_predefined(&parent_name) {
cmap.extend_from_parent(&parent);
}
}
if (cmap.name == "Identity-H" || cmap.name == "Identity-V") && cmap.map.is_empty() {
let vertical = cmap.name == "Identity-V";
return if vertical {
Self::identity_v()
} else {
Self::identity_h()
};
}
cmap
}
}
impl Default for CMap {
fn default() -> Self {
Self::new()
}
}
fn increment_last_byte(dst: &mut Vec<u8>) {
if dst.is_empty() {
return;
}
let last = dst.len() - 1;
if dst[last] < 0xff {
dst[last] += 1;
return;
}
if last == 0 {
dst[0] = 0;
return;
}
let prev = last - 1;
dst[prev] = dst[prev].wrapping_add(1);
dst[last] = 0;
}
pub(crate) fn bytes_to_int(bytes: &[u8]) -> u32 {
let mut a: u32 = 0;
for &b in bytes {
a = (a << 8) | u32::from(b);
}
a
}
fn expect_str(tok: Token) -> Option<Vec<u8>> {
match tok {
Token::Str(s) => Some(s),
_ => None,
}
}
fn expect_int(tok: Token) -> Option<i64> {
match tok {
Token::Int(n) => Some(n),
Token::Real(n) if n.fract() == 0.0 && n.is_finite() => Some(n as i64),
_ => None,
}
}
fn parse_codespace_range(cmap: &mut CMap, lexer: &mut Lexer<'_>) {
loop {
let tok = lexer.next_token();
match &tok {
Token::Eof => break,
Token::Cmd(Cmd::Op(Op::EndCodespaceRange)) => return,
Token::Str(low_bytes) => {
let n = low_bytes.len();
let low = bytes_to_int(low_bytes);
let high_tok = lexer.next_token();
let Some(high_bytes) = expect_str(high_tok) else {
return;
};
let high = bytes_to_int(&high_bytes);
let n = if n == 0 { high_bytes.len() } else { n };
cmap.add_codespace_range(n.max(1).min(4), low, high);
}
_ => return,
}
}
}
fn parse_bf_char(cmap: &mut CMap, lexer: &mut Lexer<'_>) {
loop {
let tok = lexer.next_token();
match &tok {
Token::Eof => break,
Token::Cmd(Cmd::Op(Op::EndBfChar)) => return,
Token::Str(src_bytes) => {
let src = bytes_to_int(src_bytes);
let dst_tok = lexer.next_token();
let Some(dst) = expect_str(dst_tok) else {
continue;
};
cmap.map_one(src, MapValue::Bytes(dst));
}
_ => continue,
}
}
}
fn parse_cid_char(cmap: &mut CMap, lexer: &mut Lexer<'_>) {
loop {
let tok = lexer.next_token();
match &tok {
Token::Eof => break,
Token::Cmd(Cmd::Op(Op::EndCidChar)) => return,
Token::Str(src_bytes) => {
let src = bytes_to_int(src_bytes);
let dst_tok = lexer.next_token();
let Some(dst) = expect_int(dst_tok) else {
continue;
};
if dst >= 0 {
cmap.map_one(src, MapValue::Cid(dst as u32));
}
}
_ => continue,
}
}
}
fn parse_cid_range(cmap: &mut CMap, lexer: &mut Lexer<'_>) {
loop {
let tok = lexer.next_token();
match &tok {
Token::Eof => break,
Token::Cmd(Cmd::Op(Op::EndCidRange)) => return,
Token::Str(low_bytes) => {
let low = bytes_to_int(low_bytes);
let high_tok = lexer.next_token();
let Some(high_bytes) = expect_str(high_tok) else {
continue;
};
let high = bytes_to_int(&high_bytes);
let dst_tok = lexer.next_token();
let Some(dst) = expect_int(dst_tok) else {
continue;
};
if dst >= 0 {
cmap.map_cid_range(low, high, dst as u32);
}
}
_ => continue,
}
}
}
fn parse_bf_range(cmap: &mut CMap, lexer: &mut Lexer<'_>) {
loop {
let tok = lexer.next_token();
match &tok {
Token::Eof => break,
Token::Cmd(Cmd::Op(Op::EndBfRange)) => return,
Token::Str(low_bytes) => {
let low = bytes_to_int(low_bytes);
let high_tok = lexer.next_token();
let Some(high_bytes) = expect_str(high_tok) else {
return;
};
let high = bytes_to_int(&high_bytes);
let dst_tok = lexer.next_token();
match dst_tok {
Token::Str(dst) => {
cmap.map_bf_range(low, high, dst);
}
Token::Int(n) if (0..=0xffff).contains(&n) => {
let dst = vec![((n as u16) >> 8) as u8, (n as u16) as u8];
cmap.map_bf_range(low, high, dst);
}
Token::ArrayStart => {
let mut array = Vec::new();
loop {
let t = lexer.next_token();
match t {
Token::ArrayEnd | Token::Eof => break,
Token::Str(s) => array.push(s),
Token::Int(n) if (0..=0xffff).contains(&n) => {
array.push(vec![((n as u16) >> 8) as u8, (n as u16) as u8]);
}
_ => {}
}
}
cmap.map_bf_range_to_array(low, high, &array);
}
_ => return,
}
}
_ => return,
}
}
}
pub(crate) fn utf16be_bytes_to_string(token: &[u8]) -> String {
let (first, rest) = if token.len() % 2 != 0 {
(Some(u16::from(token[0])), &token[1..])
} else {
(None, token)
};
let mut units = first
.into_iter()
.chain(rest.chunks_exact(2).map(|p| u16::from_be_bytes([p[0], p[1]])));
let mut out = String::new();
while let Some(w1) = units.next() {
if (w1 & 0xf800) != 0xd800 {
if let Some(ch) = char::from_u32(u32::from(w1)) {
out.push(ch);
}
continue;
}
let Some(w2) = units.next() else { break };
let cp = (u32::from(w1 & 0x3ff) << 10) + u32::from(w2 & 0x3ff) + 0x1_0000;
if let Some(ch) = char::from_u32(cp) {
out.push(ch);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_h_two_byte_read() {
let cmap = CMap::identity_h();
let (code, len) = cmap.read_char_code(&[0x00, 0x41, 0x00, 0x42], 0).expect("match");
assert_eq!(code, 0x0041);
assert_eq!(len, 2);
let (code2, len2) = cmap.read_char_code(&[0x00, 0x41, 0x00, 0x42], 2).expect("match");
assert_eq!(code2, 0x0042);
assert_eq!(len2, 2);
assert_eq!(cmap.lookup(0x0041).as_deref(), Some(&MapValue::Cid(0x0041)));
}
#[test]
fn read_char_code_no_match_consumes_one() {
let mut cmap = CMap::new();
cmap.add_codespace_range(1, 0x20, 0x7e);
assert_eq!(cmap.read_char_code(&[0x01, 0x41], 0), None);
let (code2, len2) = cmap.read_char_code(b"A", 0).expect("match");
assert_eq!(code2, b'A' as u32);
assert_eq!(len2, 1);
}
#[test]
fn embedded_cidrange_decode() {
let data = br#"
/CIDInit /ProcSet findresource begin
12 dict begin
begincmap
/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> def
/CMapName /Test-H def
/CMapType 1 def
1 begincodespacerange
<00> <FF>
endcodespacerange
1 begincidrange
<41> <43> 100
endcidrange
endcmap
"#;
let cmap = CMap::parse_embedded(data, crate::extract::DEFAULT_MAX_CMAP_ENTRIES);
assert_eq!(cmap.lookup(0x41).as_deref(), Some(&MapValue::Cid(100)));
assert_eq!(cmap.lookup(0x42).as_deref(), Some(&MapValue::Cid(101)));
assert_eq!(cmap.lookup(0x43).as_deref(), Some(&MapValue::Cid(102)));
let (code, len) = cmap.read_char_code(b"B", 0).expect("match");
assert_eq!(code, b'B' as u32);
assert_eq!(len, 1);
}
fn bytes_str(cmap: &CMap, code: u32) -> String {
match cmap.lookup(code).as_deref() {
Some(MapValue::Bytes(b)) => utf16be_bytes_to_string(b),
_ => panic!("expected bytes"),
}
}
#[test]
fn tounicode_bfchar_bfrange_array_surrogate_odd() {
let data = br#"
begincmap
1 begincodespacerange
<00> <FF>
endcodespacerange
1 beginbfchar
<01> <0041>
endbfchar
1 beginbfrange
<10> <11> <0042>
endbfrange
1 beginbfrange
<20> <21> [<0044> <0045>]
endbfrange
1 beginbfchar
<30> <D83DDE00>
endbfchar
1 beginbfchar
<40> <41>
endbfchar
endcmap
"#;
let cmap = CMap::parse_embedded(data, crate::extract::DEFAULT_MAX_CMAP_ENTRIES);
assert_eq!(bytes_str(&cmap, 0x01), "A");
assert_eq!(bytes_str(&cmap, 0x10), "B");
assert_eq!(bytes_str(&cmap, 0x11), "C");
assert_eq!(bytes_str(&cmap, 0x20), "D");
assert_eq!(bytes_str(&cmap, 0x21), "E");
assert_eq!(bytes_str(&cmap, 0x30), "\u{1F600}");
assert_eq!(bytes_str(&cmap, 0x40), "A");
}
#[cfg(feature = "cmap-japan1")]
#[test]
fn load_90ms_rksj_h_codespace_and_read() {
let cmap = CMap::load_predefined("90ms-RKSJ-H").expect("load 90ms-RKSJ-H");
assert!(!cmap.codespace_ranges_for(1).is_empty());
assert!(!cmap.codespace_ranges_for(2).is_empty());
let (code, len) = cmap.read_char_code(&[0x41], 0).expect("match");
assert_eq!(code, 0x41);
assert_eq!(len, 1);
let (code2, len2) = cmap.read_char_code(&[0x81, 0x40], 0).expect("match");
assert_eq!(code2, 0x8140);
assert_eq!(len2, 2);
}
#[cfg(feature = "cmap-japan1")]
#[test]
fn japan1_cid_to_ucs2_fullwidth_space() {
let rksj = CMap::load_predefined("90ms-RKSJ-H").expect("load RKSJ");
let ucs2 = CMap::load_predefined("Adobe-Japan1-UCS2").expect("load UCS2");
let cid = match rksj.lookup(0x8140).as_deref() {
Some(MapValue::Cid(c)) => *c,
other => panic!("expected Cid, got {other:?}"),
};
match ucs2.lookup(cid).as_deref() {
Some(MapValue::Bytes(b)) => {
let s = utf16be_bytes_to_string(b);
assert_eq!(s, "\u{3000}");
}
other => panic!("expected Bytes, got {other:?}"),
}
}
#[cfg(feature = "cmap-japan1")]
#[test]
fn load_90ms_rksj_v_usecmap_and_vertical() {
let cmap = CMap::load_predefined("90ms-RKSJ-V").expect("load V");
assert!(cmap.vertical());
assert_eq!(cmap.use_cmap(), Some("90ms-RKSJ-H"));
assert!(cmap.has_codespace());
assert!(cmap.lookup(0x8140).is_some());
}
#[cfg(feature = "cmap-japan1")]
#[test]
fn embedded_usecmap_inherits_parent() {
let data = br#"
begincmap
/90ms-RKSJ-H usecmap
1 begincidchar
<8140> 42
endcidchar
endcmap
"#;
let cmap = CMap::parse_embedded(data, crate::extract::DEFAULT_MAX_CMAP_ENTRIES);
assert_eq!(cmap.use_cmap(), Some("90ms-RKSJ-H"));
assert!(cmap.has_codespace());
assert!(!cmap.codespace_ranges_for(1).is_empty());
assert!(!cmap.codespace_ranges_for(2).is_empty());
assert_eq!(cmap.lookup(0x8140).as_deref(), Some(&MapValue::Cid(42)));
let parent = CMap::load_predefined("90ms-RKSJ-H").expect("parent");
let parent_cid = match parent.lookup(0x8140).as_deref() {
Some(MapValue::Cid(c)) => *c,
other => panic!("expected Cid, got {other:?}"),
};
assert_ne!(parent_cid, 42, "parent must differ so override is observable");
assert_eq!(cmap.lookup(0x41).as_deref(), parent.lookup(0x41).as_deref());
assert!(cmap.lookup(0x41).is_some());
let (code, len) = cmap.read_char_code(&[0x81, 0x40], 0).expect("match");
assert_eq!(code, 0x8140);
assert_eq!(len, 2);
}
#[test]
fn unknown_cmap_name_errors() {
let err = CMap::load_predefined("Not-A-Real-CMap").unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("Unknown CMap name"),
"unexpected error: {msg}"
);
}
#[test]
fn identity_via_load_predefined() {
let h = CMap::load_predefined("Identity-H").unwrap();
assert!(h.is_identity());
assert!(!h.vertical());
let v = CMap::load_predefined("Identity-V").unwrap();
assert!(v.is_identity());
assert!(v.vertical());
}
#[test]
fn feature_gates_bcmap_bytes() {
use super::super::data::bcmaps::{bcmap_bytes, bundled_features};
let f = bundled_features();
#[cfg(feature = "cmap-gb1")]
{
assert!(f.gb1);
assert!(bcmap_bytes("GB-EUC-H").is_some());
}
#[cfg(feature = "cmap-cns1")]
{
assert!(f.cns1);
assert!(bcmap_bytes("B5-H").is_some());
}
#[cfg(feature = "cmap-japan1")]
{
assert!(f.japan1);
assert!(bcmap_bytes("90ms-RKSJ-H").is_some());
}
#[cfg(feature = "cmap-korea1")]
{
assert!(f.korea1);
assert!(bcmap_bytes("KSC-EUC-H").is_some());
}
let _ = f;
assert!(bcmap_bytes("No-Such-CMap").is_none());
}
#[test]
fn max_entries_cuts_off_giant_bfrange() {
let data = br#"
begincmap
1 begincodespacerange
<000000> <FFFFFF>
endcodespacerange
1 beginbfrange
<000000> <FFFFFF> <0000>
endbfrange
endcmap
"#;
let cmap = CMap::parse_embedded(data, 1_000);
assert!(cmap.truncated());
assert!(cmap.map.len() <= 1_000);
assert_eq!(cmap.map.len(), 0);
}
#[test]
fn max_entries_cuts_off_accumulating_ranges() {
let mut s = String::from("begincmap\n1 begincodespacerange\n<000000> <FFFFFF>\nendcodespacerange\n");
for i in 0..10 {
s.push_str(&format!(
"1 beginbfrange\n<{:06X}> <{:06X}> <0041>\nendbfrange\n",
i * 0x100,
i * 0x100 + 99
));
}
s.push_str("endcmap\n");
let cmap = CMap::parse_embedded(s.as_bytes(), 300);
assert!(cmap.truncated());
assert_eq!(cmap.map.len(), 300);
}
#[test]
fn max_entries_permits_within_budget() {
let data = br#"
begincmap
1 begincodespacerange
<00> <FF>
endcodespacerange
1 beginbfrange
<00> <7F> <0041>
endbfrange
endcmap
"#;
let cmap = CMap::parse_embedded(data, 1_000);
assert!(!cmap.truncated());
assert_eq!(cmap.map.len(), 128);
}
#[test]
fn max_entries_cuts_off_bfchar_batch() {
let mut s = String::from("begincmap\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n");
s.push_str("500 beginbfchar\n");
for i in 0..500u32 {
s.push_str(&format!("<{:04X}> <0041>\n", i));
}
s.push_str("endbfchar\nendcmap\n");
let cmap = CMap::parse_embedded(s.as_bytes(), 100);
assert!(cmap.truncated());
assert_eq!(cmap.map.len(), 100);
}
#[test]
fn max_entries_range_over_max_map_range_still_dropped() {
let data = br#"
begincmap
1 begincodespacerange
<00000000> <FFFFFFFF>
endcodespacerange
1 beginbfrange
<00000000> <02000000> <0041>
endbfrange
endcmap
"#;
let cmap = CMap::parse_embedded(data, 100_000_000);
assert!(!cmap.truncated());
assert_eq!(cmap.map.len(), 0);
}
#[test]
fn extend_from_parent_propagates_truncated() {
let data = br#"
begincmap
1 begincodespacerange
<00> <FF>
endcodespacerange
1 beginbfrange
<00> <7F> <0041>
endbfrange
endcmap
"#;
let parent = CMap::parse_embedded(data, 10);
assert!(parent.truncated());
let mut child = CMap::with_limit(usize::MAX);
child.extend_from_parent(&parent);
assert!(child.truncated());
}
}