stet_pdf_reader/content/
cmap.rs1use std::collections::HashMap;
8
9pub struct CMap {
11 pub code_to_cid: HashMap<u32, u32>,
13 pub code_lengths: [u8; 256],
16 pub wmode: u8,
18}
19
20impl CMap {
21 pub fn identity() -> Self {
23 Self {
24 code_to_cid: HashMap::new(),
25 code_lengths: [2; 256],
26 wmode: 0,
27 }
28 }
29
30 pub fn decode(&self, code: u32) -> u32 {
32 self.code_to_cid.get(&code).copied().unwrap_or(code)
34 }
35
36 pub fn code_width(&self, first_byte: u8) -> usize {
38 let w = self.code_lengths[first_byte as usize];
39 if w == 0 { 2 } else { w as usize }
40 }
41
42 pub fn parse(data: &[u8]) -> Self {
44 Self::parse_with_loader(data, None)
45 }
46
47 pub fn parse_with_loader(
49 data: &[u8],
50 loader: Option<&dyn Fn(&[u8]) -> Option<Vec<u8>>>,
51 ) -> Self {
52 let mut code_to_cid = HashMap::new();
53 let mut codespace_ranges: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
54 let mut wmode: u8 = 0;
55
56 let text = String::from_utf8_lossy(data);
57
58 #[allow(clippy::while_let_on_iterator)]
59 let mut lines = text.lines();
60
61 while let Some(line) = lines.next() {
62 let line = line.trim();
63
64 if line.ends_with("usecmap") {
66 let name = line.strip_suffix("usecmap").unwrap_or("").trim();
67 let name = name.strip_prefix('/').unwrap_or(name);
68 if !name.is_empty() {
69 if let Some(load_fn) = loader {
70 if let Some(base_data) = load_fn(name.as_bytes()) {
71 let base = Self::parse_with_loader(&base_data, loader);
72 for (k, v) in base.code_to_cid {
74 code_to_cid.entry(k).or_insert(v);
75 }
76 if codespace_ranges.is_empty() {
77 for fb in 0..256u16 {
79 let w = base.code_lengths[fb as usize];
80 if w > 0 {
81 let low = if w == 1 {
82 vec![fb as u8]
83 } else {
84 vec![fb as u8, 0x00]
85 };
86 let high = if w == 1 {
87 vec![fb as u8]
88 } else {
89 vec![fb as u8, 0xFF]
90 };
91 codespace_ranges.push((low, high));
92 }
93 }
94 }
95 if wmode == 0 {
96 wmode = base.wmode;
97 }
98 }
99 }
100 }
101 }
102
103 if let Some(rest) = line.strip_prefix("/WMode") {
105 let rest = rest.trim();
106 if let Some(rest) = rest.strip_prefix("def").or(Some(rest)) {
107 if let Ok(v) = rest.trim().parse::<u8>() {
108 wmode = v;
109 }
110 }
111 }
112 if line.ends_with("/WMode def") {
114 let parts: Vec<&str> = line.split_whitespace().collect();
115 if let Some(v) = parts.first().and_then(|s| s.parse::<u8>().ok()) {
116 wmode = v;
117 }
118 }
119
120 if line.ends_with("begincodespacerange") {
122 while let Some(range_line) = lines.next() {
123 let range_line = range_line.trim();
124 if range_line == "endcodespacerange" {
125 break;
126 }
127 if let Some((low, high)) = parse_codespace_range(range_line) {
128 codespace_ranges.push((low, high));
129 }
130 }
131 }
132
133 if line.contains("begincidchar") {
138 if let Some(inline) = extract_inline_data(line, "begincidchar", "endcidchar") {
140 if let Some((code, cid)) = parse_cidchar_line(inline) {
141 code_to_cid.insert(code, cid);
142 }
143 } else if line.ends_with("begincidchar") {
144 while let Some(char_line) = lines.next() {
145 let char_line = char_line.trim();
146 if char_line == "endcidchar" {
147 break;
148 }
149 if let Some((code, cid)) = parse_cidchar_line(char_line) {
150 code_to_cid.insert(code, cid);
151 }
152 }
153 }
154 }
155
156 if line.contains("begincidrange") {
158 if let Some(inline) = extract_inline_data(line, "begincidrange", "endcidrange") {
159 if let Some((start, end, cid_start)) = parse_cidrange_line(inline) {
160 for code in start..=end {
161 code_to_cid.insert(code, cid_start + (code - start));
162 }
163 }
164 } else if line.ends_with("begincidrange") {
165 while let Some(range_line) = lines.next() {
166 let range_line = range_line.trim();
167 if range_line == "endcidrange" {
168 break;
169 }
170 if let Some((start, end, cid_start)) = parse_cidrange_line(range_line) {
171 for code in start..=end {
172 code_to_cid.insert(code, cid_start + (code - start));
173 }
174 }
175 }
176 }
177 }
178
179 if line.contains("beginbfchar") {
181 if let Some(inline) = extract_inline_data(line, "beginbfchar", "endbfchar") {
182 if let Some((code, unicode)) = parse_bfchar_line(inline) {
183 code_to_cid.insert(code, unicode);
184 }
185 } else if line.ends_with("beginbfchar") {
186 while let Some(char_line) = lines.next() {
187 let char_line = char_line.trim();
188 if char_line == "endbfchar" {
189 break;
190 }
191 if let Some((code, unicode)) = parse_bfchar_line(char_line) {
192 code_to_cid.insert(code, unicode);
193 }
194 }
195 }
196 }
197
198 if line.contains("beginbfrange") {
199 if let Some(inline) = extract_inline_data(line, "beginbfrange", "endbfrange") {
200 if let Some((start, end, cid_start)) = parse_cidrange_line(inline) {
201 for code in start..=end {
202 code_to_cid.insert(code, cid_start + (code - start));
203 }
204 }
205 } else if line.ends_with("beginbfrange") {
206 while let Some(range_line) = lines.next() {
207 let range_line = range_line.trim();
208 if range_line == "endbfrange" {
209 break;
210 }
211 if let Some((start, end, cid_start)) = parse_cidrange_line(range_line) {
212 for code in start..=end {
213 code_to_cid.insert(code, cid_start + (code - start));
214 }
215 }
216 }
217 }
218 }
219 }
220
221 let mut code_lengths = [0u8; 256];
224 if codespace_ranges.is_empty() {
225 code_lengths = [2; 256];
227 } else {
228 for (low, high) in &codespace_ranges {
229 let width = low.len() as u8;
230 let first_lo = low[0];
231 let first_hi = high[0];
232 for byte in first_lo..=first_hi {
233 let cur = code_lengths[byte as usize];
234 if cur == 0 || width < cur {
236 code_lengths[byte as usize] = width;
237 }
238 }
239 }
240 }
241
242 CMap {
243 code_to_cid,
244 code_lengths,
245 wmode,
246 }
247 }
248}
249
250fn extract_inline_data<'a>(line: &'a str, begin_kw: &str, end_kw: &str) -> Option<&'a str> {
254 let begin_pos = line.find(begin_kw)?;
255 let end_pos = line.find(end_kw)?;
256 if end_pos <= begin_pos {
257 return None;
258 }
259 let data_start = begin_pos + begin_kw.len();
260 if data_start >= end_pos {
261 return None;
262 }
263 let data = line[data_start..end_pos].trim();
264 if data.is_empty() { None } else { Some(data) }
265}
266
267fn parse_codespace_range(line: &str) -> Option<(Vec<u8>, Vec<u8>)> {
270 let tokens = split_cmap_tokens(line);
271 if tokens.len() >= 2 {
272 let low = parse_hex_bytes(&tokens[0])?;
273 let high = parse_hex_bytes(&tokens[1])?;
274 if low.len() == high.len() && !low.is_empty() {
275 Some((low, high))
276 } else {
277 None
278 }
279 } else {
280 None
281 }
282}
283
284fn parse_hex_bytes(s: &str) -> Option<Vec<u8>> {
286 let s = s.trim();
287 if s.starts_with('<') && s.ends_with('>') {
288 let hex = &s[1..s.len() - 1];
289 let mut bytes = Vec::new();
290 let mut i = 0;
291 while i + 1 < hex.len() {
292 bytes.push(u8::from_str_radix(&hex[i..i + 2], 16).ok()?);
293 i += 2;
294 }
295 if i < hex.len() {
297 bytes.push(u8::from_str_radix(&format!("{}0", &hex[i..]), 16).ok()?);
298 }
299 Some(bytes)
300 } else {
301 None
302 }
303}
304
305fn split_cmap_tokens(line: &str) -> Vec<String> {
308 let mut tokens = Vec::new();
309 let mut i = 0;
310 let bytes = line.as_bytes();
311 while i < bytes.len() {
312 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
314 i += 1;
315 }
316 if i >= bytes.len() {
317 break;
318 }
319 if bytes[i] == b'<' {
320 let start = i;
322 while i < bytes.len() && bytes[i] != b'>' {
323 i += 1;
324 }
325 if i < bytes.len() {
326 i += 1; }
328 tokens.push(line[start..i].to_string());
329 } else {
330 let start = i;
332 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b'<' {
333 i += 1;
334 }
335 tokens.push(line[start..i].to_string());
336 }
337 }
338 tokens
339}
340
341fn parse_hex(s: &str) -> Option<u32> {
343 let s = s.trim();
344 if s.starts_with('<') && s.ends_with('>') {
345 u32::from_str_radix(&s[1..s.len() - 1], 16).ok()
346 } else {
347 None
348 }
349}
350
351fn parse_cidchar_line(line: &str) -> Option<(u32, u32)> {
353 let tokens = split_cmap_tokens(line);
354 if tokens.len() >= 2 {
355 let code = parse_hex(&tokens[0])?;
356 let cid = tokens[1].parse::<u32>().ok()?;
357 Some((code, cid))
358 } else {
359 None
360 }
361}
362
363fn parse_cidrange_line(line: &str) -> Option<(u32, u32, u32)> {
365 let tokens = split_cmap_tokens(line);
366 if tokens.len() >= 3 {
367 let start = parse_hex(&tokens[0])?;
368 let end = parse_hex(&tokens[1])?;
369 let cid_start = if tokens[2].starts_with('<') {
370 parse_hex(&tokens[2])?
371 } else {
372 tokens[2].parse::<u32>().ok()?
373 };
374 Some((start, end, cid_start))
375 } else {
376 None
377 }
378}
379
380fn parse_bfchar_line(line: &str) -> Option<(u32, u32)> {
382 let tokens = split_cmap_tokens(line);
383 if tokens.len() >= 2 {
384 let code = parse_hex(&tokens[0])?;
385 let unicode = parse_hex(&tokens[1])?;
386 Some((code, unicode))
387 } else {
388 None
389 }
390}