Skip to main content

docling_pdf/render/font/
cmap.rs

1//! CMaps for composite fonts (ISO 32000-1, 9.7.5): the byte-length codespace
2//! ranges that split a string into codes, and the `cidchar` / `cidrange`
3//! entries that map codes to CIDs. Embedded CMap streams are read in full;
4//! of the predefined ones only the `Identity` and Unicode (`Uni*-UCS2`,
5//! `Uni*-UTF16`) families are known — the others (no `cmap-resources` in a
6//! Rust checkout) fall back to two-byte codes.
7
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
11pub struct CMap {
12    /// `(byte length, low, high)` codespace ranges.
13    codespace: Vec<(usize, u32, u32)>,
14    single: HashMap<u32, u32>,
15    /// `(lo, hi, cid_of_lo)`.
16    ranges: Vec<(u32, u32, u32)>,
17    pub identity: bool,
18    /// The codes are Unicode (UCS-2 / UTF-16 BE) — a predefined `Uni*` CMap.
19    pub unicode_codes: bool,
20    pub vertical: bool,
21}
22
23impl CMap {
24    pub fn identity_h() -> CMap {
25        CMap {
26            codespace: vec![(2, 0, 0xFFFF)],
27            single: HashMap::new(),
28            ranges: Vec::new(),
29            identity: true,
30            unicode_codes: false,
31            vertical: false,
32        }
33    }
34
35    /// A predefined CMap by name.
36    pub fn predefined(name: &[u8]) -> CMap {
37        let s = String::from_utf8_lossy(name);
38        let vertical = s.ends_with("-V");
39        let mut c = CMap::identity_h();
40        c.vertical = vertical;
41        if s.starts_with("Identity") {
42            return c;
43        }
44        // Everything else is two-byte with unknown CIDs; the Unicode-keyed
45        // families at least tell what the codes mean.
46        c.identity = false;
47        c.unicode_codes = s.contains("UCS2") || s.contains("UTF16");
48        c
49    }
50
51    /// Parse an embedded CMap stream.
52    pub fn parse(data: &[u8]) -> CMap {
53        let mut c = CMap {
54            codespace: Vec::new(),
55            single: HashMap::new(),
56            ranges: Vec::new(),
57            identity: false,
58            unicode_codes: false,
59            vertical: false,
60        };
61        let toks = tokenize(data);
62        let mut i = 0;
63        while i < toks.len() {
64            match &toks[i] {
65                Tok::Kw(k) if k == "begincodespacerange" => {
66                    i += 1;
67                    while i + 1 < toks.len() {
68                        match (&toks[i], &toks[i + 1]) {
69                            (Tok::Hex(lo), Tok::Hex(hi)) => {
70                                let n = lo.len().clamp(1, 4);
71                                c.codespace.push((n, be(lo), be(hi)));
72                                i += 2;
73                            }
74                            _ => break,
75                        }
76                    }
77                }
78                Tok::Kw(k) if k == "begincidrange" => {
79                    i += 1;
80                    while i + 2 < toks.len() {
81                        match (&toks[i], &toks[i + 1], &toks[i + 2]) {
82                            (Tok::Hex(lo), Tok::Hex(hi), Tok::Num(cid)) => {
83                                c.ranges.push((be(lo), be(hi), *cid as u32));
84                                if c.codespace.is_empty() {
85                                    c.codespace.push((lo.len().clamp(1, 4), 0, u32::MAX));
86                                }
87                                i += 3;
88                            }
89                            _ => break,
90                        }
91                    }
92                }
93                Tok::Kw(k) if k == "begincidchar" => {
94                    i += 1;
95                    while i + 1 < toks.len() {
96                        match (&toks[i], &toks[i + 1]) {
97                            (Tok::Hex(code), Tok::Num(cid)) => {
98                                c.single.insert(be(code), *cid as u32);
99                                if c.codespace.is_empty() {
100                                    c.codespace.push((code.len().clamp(1, 4), 0, u32::MAX));
101                                }
102                                i += 2;
103                            }
104                            _ => break,
105                        }
106                    }
107                }
108                Tok::Kw(k) if k == "usecmap" => {
109                    // `/Identity-H usecmap` and friends: the parent's codes.
110                    if let Some(Tok::Name(n)) = toks.get(i.wrapping_sub(1)) {
111                        if n.starts_with("Identity") {
112                            c.identity = c.single.is_empty() && c.ranges.is_empty();
113                            if c.codespace.is_empty() {
114                                c.codespace.push((2, 0, 0xFFFF));
115                            }
116                        }
117                    }
118                    i += 1;
119                }
120                Tok::Kw(k) if k == "def" => {
121                    if let (Some(Tok::Name(n)), Some(Tok::Num(v))) =
122                        (toks.get(i.wrapping_sub(2)), toks.get(i.wrapping_sub(1)))
123                    {
124                        if n == "WMode" && *v == 1.0 {
125                            c.vertical = true;
126                        }
127                    }
128                    i += 1;
129                }
130                _ => i += 1,
131            }
132        }
133        if c.codespace.is_empty() {
134            c.codespace.push((2, 0, 0xFFFF));
135        }
136        // Shortest byte length first, so a one-byte range wins over a
137        // two-byte one that happens to contain the same leading byte.
138        c.codespace.sort_by_key(|r| r.0);
139        c
140    }
141
142    /// Split `bytes` into `(code, byte length)` pairs.
143    pub fn split(&self, bytes: &[u8]) -> Vec<(u32, usize)> {
144        let mut out = Vec::with_capacity(bytes.len() / 2 + 1);
145        let mut i = 0;
146        while i < bytes.len() {
147            let mut taken = None;
148            for &(n, lo, hi) in &self.codespace {
149                if i + n > bytes.len() {
150                    continue;
151                }
152                let code = bytes[i..i + n]
153                    .iter()
154                    .fold(0u32, |a, &b| (a << 8) | u32::from(b));
155                if code >= lo && code <= hi {
156                    taken = Some((code, n));
157                    break;
158                }
159            }
160            let (code, n) = taken.unwrap_or_else(|| {
161                // Not in any range: the shortest codespace length, per 9.7.6.3.
162                let n = self
163                    .codespace
164                    .first()
165                    .map(|r| r.0)
166                    .unwrap_or(1)
167                    .min(bytes.len() - i);
168                let code = bytes[i..i + n]
169                    .iter()
170                    .fold(0u32, |a, &b| (a << 8) | u32::from(b));
171                (code, n)
172            });
173            out.push((code, n));
174            i += n;
175        }
176        out
177    }
178
179    pub fn cid(&self, code: u32) -> u32 {
180        if self.identity {
181            return code;
182        }
183        if let Some(c) = self.single.get(&code) {
184            return *c;
185        }
186        for &(lo, hi, cid) in &self.ranges {
187            if code >= lo && code <= hi {
188                return cid + (code - lo);
189            }
190        }
191        if self.single.is_empty() && self.ranges.is_empty() {
192            code
193        } else {
194            0
195        }
196    }
197}
198
199fn be(bytes: &[u8]) -> u32 {
200    bytes
201        .iter()
202        .take(4)
203        .fold(0u32, |a, &b| (a << 8) | u32::from(b))
204}
205
206enum Tok {
207    Hex(Vec<u8>),
208    Num(f64),
209    Name(String),
210    Kw(String),
211}
212
213fn tokenize(data: &[u8]) -> Vec<Tok> {
214    let mut out = Vec::new();
215    let mut i = 0;
216    while i < data.len() {
217        let b = data[i];
218        match b {
219            b'%' => {
220                while i < data.len() && data[i] != b'\n' && data[i] != b'\r' {
221                    i += 1;
222                }
223            }
224            b'<' => {
225                if data.get(i + 1) == Some(&b'<') {
226                    i += 2;
227                    continue;
228                }
229                let end = data[i..]
230                    .iter()
231                    .position(|&c| c == b'>')
232                    .map(|p| i + p)
233                    .unwrap_or(data.len());
234                let mut bytes = Vec::new();
235                let mut hi: Option<u8> = None;
236                for &c in &data[i + 1..end] {
237                    let v = match c {
238                        b'0'..=b'9' => c - b'0',
239                        b'a'..=b'f' => c - b'a' + 10,
240                        b'A'..=b'F' => c - b'A' + 10,
241                        _ => continue,
242                    };
243                    match hi.take() {
244                        None => hi = Some(v),
245                        Some(h) => bytes.push((h << 4) | v),
246                    }
247                }
248                if let Some(h) = hi {
249                    bytes.push(h << 4);
250                }
251                out.push(Tok::Hex(bytes));
252                i = end + 1;
253            }
254            b'/' => {
255                let start = i + 1;
256                let mut j = start;
257                while j < data.len()
258                    && !data[j].is_ascii_whitespace()
259                    && !b"/<>[](){}%".contains(&data[j])
260                {
261                    j += 1;
262                }
263                out.push(Tok::Name(
264                    String::from_utf8_lossy(&data[start..j]).into_owned(),
265                ));
266                i = j;
267            }
268            b'[' | b']' | b'{' | b'}' | b'>' | b'(' | b')' => i += 1,
269            c if c.is_ascii_whitespace() => i += 1,
270            _ => {
271                let start = i;
272                while i < data.len()
273                    && !data[i].is_ascii_whitespace()
274                    && !b"/<>[](){}%".contains(&data[i])
275                {
276                    i += 1;
277                }
278                let s = String::from_utf8_lossy(&data[start..i]).into_owned();
279                match s.parse::<f64>() {
280                    Ok(v) => out.push(Tok::Num(v)),
281                    Err(_) => out.push(Tok::Kw(s)),
282                }
283            }
284        }
285    }
286    out
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    #[test]
294    fn embedded_cmap_ranges_and_codespaces() {
295        let src = b"%!PS\n/CIDInit /ProcSet findresource begin 12 dict begin begincmap\n1 begincodespacerange <00> <80> <8140> <9ffc> endcodespacerange\n2 begincidrange <20> <7e> 1 <8140> <817e> 633 endcidrange\n1 begincidchar <80> 97 endcidchar\nendcmap";
296        let c = CMap::parse(src);
297        assert_eq!(
298            c.split(b"\x41\x81\x41\x80"),
299            vec![(0x41, 1), (0x8141, 2), (0x80, 1)]
300        );
301        assert_eq!(c.cid(0x41), 1 + 0x21);
302        assert_eq!(c.cid(0x8141), 634);
303        assert_eq!(c.cid(0x80), 97);
304    }
305
306    #[test]
307    fn identity_is_two_byte() {
308        let c = CMap::predefined(b"Identity-H");
309        assert_eq!(c.split(b"\x00\x41\x12\x34"), vec![(0x41, 2), (0x1234, 2)]);
310        assert_eq!(c.cid(0x1234), 0x1234);
311        assert!(CMap::predefined(b"UniGB-UCS2-H").unicode_codes);
312    }
313}