Skip to main content

pdf_font/cmap/
mod.rs

1/*!
2A parser for cmap files, as they are found in PDFs.
3
4This crate provides a parser for cmap files and allows you to
5- Map character codes from text-showing operators to CID identifiers.
6- Map CIDs to Unicode characters or strings.
7
8## Safety
9This crate forbids unsafe code via a crate-level attribute.
10*/
11
12#[cfg(feature = "embed-cmaps")]
13mod bcmap;
14mod parse;
15
16#[cfg(feature = "embed-cmaps")]
17pub use bcmap::load_embedded;
18
19/// Look up an embedded binary cmap by name.
20///
21/// Returns `None` when the `embed-cmaps` feature is not enabled.
22#[cfg(not(feature = "embed-cmaps"))]
23pub fn load_embedded(_name: CMapName<'_>) -> Option<&'static [u8]> {
24    None
25}
26
27use alloc::boxed::Box;
28use alloc::string::String;
29use alloc::vec::Vec;
30
31/// A CID (Character Identifier).
32pub type Cid = u32;
33
34/// The name of the cmap.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum CMapName<'a> {
37    // ── Adobe-Japan1 (Japanese) ── Shift-JIS (RKSJ) encodings ──
38    /// `83pv-RKSJ-H` — Adobe-Japan1, Mac Shift-JIS (`KanjiTalk` 6), horizontal.
39    N83pvRksjH,
40    /// `90ms-RKSJ-H` — Adobe-Japan1, Windows Shift-JIS (code page 932), horizontal.
41    N90msRksjH,
42    /// `90ms-RKSJ-V` — Adobe-Japan1, Windows Shift-JIS (code page 932), vertical.
43    N90msRksjV,
44    /// `90msp-RKSJ-H` — Adobe-Japan1, Windows Shift-JIS with proportional Roman, horizontal.
45    N90mspRksjH,
46    /// `90msp-RKSJ-V` — Adobe-Japan1, Windows Shift-JIS with proportional Roman, vertical.
47    N90mspRksjV,
48    /// `90pv-RKSJ-H` — Adobe-Japan1, Mac Shift-JIS (`KanjiTalk` 7), horizontal.
49    N90pvRksjH,
50    /// `Add-RKSJ-H` — Adobe-Japan1, Fujitsu FMR Shift-JIS, horizontal.
51    AddRksjH,
52    /// `Add-RKSJ-V` — Adobe-Japan1, Fujitsu FMR Shift-JIS, vertical.
53    AddRksjV,
54
55    // ── Adobe UCS2 (CID → Unicode) ──
56    // Note that these are not part of the predefined CMaps, but we use them
57    // so we can more easily map CIDs back to Unicode.
58    /// `Adobe-CNS1-UCS2` — Adobe-CNS1, CID-to-Unicode mapping.
59    AdobeCns1Ucs2,
60    /// `Adobe-GB1-UCS2` — Adobe-GB1, CID-to-Unicode mapping.
61    AdobeGb1Ucs2,
62    /// `Adobe-Japan1-UCS2` — Adobe-Japan1, CID-to-Unicode mapping.
63    AdobeJapan1Ucs2,
64    /// `Adobe-Korea1-UCS2` — Adobe-Korea1, CID-to-Unicode mapping.
65    AdobeKorea1Ucs2,
66
67    // ── Adobe-CNS1 (Traditional Chinese) ── Big Five encodings ──
68    /// `B5pc-H` — Adobe-CNS1, Mac Big Five (`ETen` extensions), horizontal.
69    B5pcH,
70    /// `B5pc-V` — Adobe-CNS1, Mac Big Five (`ETen` extensions), vertical.
71    B5pcV,
72    /// `CNS-EUC-H` — Adobe-CNS1, CNS 11643 EUC encoding, horizontal.
73    CnsEucH,
74    /// `CNS-EUC-V` — Adobe-CNS1, CNS 11643 EUC encoding, vertical.
75    CnsEucV,
76    /// `ETen-B5-H` — Adobe-CNS1, `ETen` Big Five extensions, horizontal.
77    ETenB5H,
78    /// `ETen-B5-V` — Adobe-CNS1, `ETen` Big Five extensions, vertical.
79    ETenB5V,
80    /// `ETenms-B5-H` — Adobe-CNS1, `ETen` Big Five with Microsoft symbol extensions, horizontal.
81    ETenmsB5H,
82    /// `ETenms-B5-V` — Adobe-CNS1, `ETen` Big Five with Microsoft symbol extensions, vertical.
83    ETenmsB5V,
84
85    // ── Adobe-Japan1 (Japanese) ── EUC and extended Shift-JIS encodings ──
86    /// `EUC-H` — Adobe-Japan1, JIS X 0208 EUC-JP encoding, horizontal.
87    EucH,
88    /// `EUC-V` — Adobe-Japan1, JIS X 0208 EUC-JP encoding, vertical.
89    EucV,
90    /// `Ext-RKSJ-H` — Adobe-Japan1, Shift-JIS with NEC/IBM extensions, horizontal.
91    ExtRksjH,
92    /// `Ext-RKSJ-V` — Adobe-Japan1, Shift-JIS with NEC/IBM extensions, vertical.
93    ExtRksjV,
94
95    // ── Adobe-GB1 (Simplified Chinese) ──
96    /// `GB-EUC-H` — Adobe-GB1, GB 2312-80 EUC encoding, horizontal.
97    GbEucH,
98    /// `GB-EUC-V` — Adobe-GB1, GB 2312-80 EUC encoding, vertical.
99    GbEucV,
100    /// `GBK-EUC-H` — Adobe-GB1, GBK encoding (Microsoft code page 936), horizontal.
101    GbkEucH,
102    /// `GBK-EUC-V` — Adobe-GB1, GBK encoding (Microsoft code page 936), vertical.
103    GbkEucV,
104    /// `GBK2K-H` — Adobe-GB1, GB 18030-2000 encoding, horizontal.
105    Gbk2kH,
106    /// `GBK2K-V` — Adobe-GB1, GB 18030-2000 encoding, vertical.
107    Gbk2kV,
108    /// `GBKp-EUC-H` — Adobe-GB1, GBK with proportional Roman, horizontal.
109    GbkpEucH,
110    /// `GBKp-EUC-V` — Adobe-GB1, GBK with proportional Roman, vertical.
111    GbkpEucV,
112    /// `GBpc-EUC-H` — Adobe-GB1, Mac GB 2312 (simplified) EUC encoding, horizontal.
113    GbpcEucH,
114    /// `GBpc-EUC-V` — Adobe-GB1, Mac GB 2312 (simplified) EUC encoding, vertical.
115    GbpcEucV,
116
117    // ── Adobe-Japan1 (Japanese) ── JIS encoding ──
118    /// `H` — Adobe-Japan1, JIS X 0208 row-cell encoding, horizontal.
119    H,
120
121    // ── Adobe-CNS1 (Traditional Chinese) ── Hong Kong SCS ──
122    /// `HKscs-B5-H` — Adobe-CNS1, Hong Kong SCS (Big Five with HKSCS extensions), horizontal.
123    HKscsB5H,
124    /// `HKscs-B5-V` — Adobe-CNS1, Hong Kong SCS (Big Five with HKSCS extensions), vertical.
125    HKscsB5V,
126
127    // ── Adobe-Identity ──
128    /// `Identity-H` — Adobe-Identity, two-byte identity mapping, horizontal.
129    /// Character codes map directly to CIDs (i.e. CID = character code).
130    IdentityH,
131    /// `Identity-V` — Adobe-Identity, two-byte identity mapping, vertical.
132    /// Character codes map directly to CIDs (i.e. CID = character code).
133    IdentityV,
134
135    // ── Adobe-Korea1 (Korean) ──
136    /// `KSC-EUC-H` — Adobe-Korea1, KS X 1001:1992 EUC-KR encoding, horizontal.
137    KscEucH,
138    /// `KSC-EUC-V` — Adobe-Korea1, KS X 1001:1992 EUC-KR encoding, vertical.
139    KscEucV,
140    /// `KSCms-UHC-H` — Adobe-Korea1, Microsoft UHC (Unified Hangul Code, code page 949), horizontal.
141    KscmsUhcH,
142    /// `KSCms-UHC-HW-H` — Adobe-Korea1, Microsoft UHC with half-width Roman, horizontal.
143    KscmsUhcHwH,
144    /// `KSCms-UHC-HW-V` — Adobe-Korea1, Microsoft UHC with half-width Roman, vertical.
145    KscmsUhcHwV,
146    /// `KSCms-UHC-V` — Adobe-Korea1, Microsoft UHC (Unified Hangul Code, code page 949), vertical.
147    KscmsUhcV,
148    /// `KSCpc-EUC-H` — Adobe-Korea1, Mac KS X 1001:1992 EUC-KR encoding, horizontal.
149    KscpcEucH,
150
151    // ── Adobe-CNS1 (Traditional Chinese) ── Unicode encodings ──
152    /// `UniCNS-UCS2-H` — Adobe-CNS1, Unicode UCS-2 encoding, horizontal.
153    UniCnsUcs2H,
154    /// `UniCNS-UCS2-V` — Adobe-CNS1, Unicode UCS-2 encoding, vertical.
155    UniCnsUcs2V,
156    /// `UniCNS-UTF16-H` — Adobe-CNS1, Unicode UTF-16 encoding, horizontal.
157    UniCnsUtf16H,
158    /// `UniCNS-UTF16-V` — Adobe-CNS1, Unicode UTF-16 encoding, vertical.
159    UniCnsUtf16V,
160
161    // ── Adobe-GB1 (Simplified Chinese) ── Unicode encodings ──
162    /// `UniGB-UCS2-H` — Adobe-GB1, Unicode UCS-2 encoding, horizontal.
163    UniGbUcs2H,
164    /// `UniGB-UCS2-V` — Adobe-GB1, Unicode UCS-2 encoding, vertical.
165    UniGbUcs2V,
166    /// `UniGB-UTF16-H` — Adobe-GB1, Unicode UTF-16 encoding, horizontal.
167    UniGbUtf16H,
168    /// `UniGB-UTF16-V` — Adobe-GB1, Unicode UTF-16 encoding, vertical.
169    UniGbUtf16V,
170
171    // ── Adobe-Japan1 (Japanese) ── Unicode encodings ──
172    /// `UniJIS-UCS2-H` — Adobe-Japan1, Unicode UCS-2 encoding, horizontal.
173    UniJisUcs2H,
174    /// `UniJIS-UCS2-HW-H` — Adobe-Japan1, Unicode UCS-2 with half-width Roman, horizontal.
175    UniJisUcs2HwH,
176    /// `UniJIS-UCS2-HW-V` — Adobe-Japan1, Unicode UCS-2 with half-width Roman, vertical.
177    UniJisUcs2HwV,
178    /// `UniJIS-UCS2-V` — Adobe-Japan1, Unicode UCS-2 encoding, vertical.
179    UniJisUcs2V,
180    /// `UniJIS-UTF16-H` — Adobe-Japan1, Unicode UTF-16 encoding, horizontal.
181    UniJisUtf16H,
182    /// `UniJIS-UTF16-V` — Adobe-Japan1, Unicode UTF-16 encoding, vertical.
183    UniJisUtf16V,
184
185    // ── Adobe-Korea1 (Korean) ── Unicode encodings ──
186    /// `UniKS-UCS2-H` — Adobe-Korea1, Unicode UCS-2 encoding, horizontal.
187    UniKsUcs2H,
188    /// `UniKS-UCS2-V` — Adobe-Korea1, Unicode UCS-2 encoding, vertical.
189    UniKsUcs2V,
190    /// `UniKS-UTF16-H` — Adobe-Korea1, Unicode UTF-16 encoding, horizontal.
191    UniKsUtf16H,
192    /// `UniKS-UTF16-V` — Adobe-Korea1, Unicode UTF-16 encoding, vertical.
193    UniKsUtf16V,
194
195    // ── Adobe-Japan1 (Japanese) ── JIS encoding ──
196    /// `V` — Adobe-Japan1, JIS X 0208 row-cell encoding, vertical.
197    V,
198
199    /// A custom (non-predefined) `CMap` name.
200    Custom(&'a [u8]),
201}
202
203impl<'a> CMapName<'a> {
204    /// Create a `CMapType` from raw bytes.
205    pub fn from_bytes(name: &'a [u8]) -> Self {
206        match name {
207            b"83pv-RKSJ-H" => Self::N83pvRksjH,
208            b"90ms-RKSJ-H" => Self::N90msRksjH,
209            b"90ms-RKSJ-V" => Self::N90msRksjV,
210            b"90msp-RKSJ-H" => Self::N90mspRksjH,
211            b"90msp-RKSJ-V" => Self::N90mspRksjV,
212            b"90pv-RKSJ-H" => Self::N90pvRksjH,
213            b"Add-RKSJ-H" => Self::AddRksjH,
214            b"Add-RKSJ-V" => Self::AddRksjV,
215            b"Adobe-CNS1-UCS2" => Self::AdobeCns1Ucs2,
216            b"Adobe-GB1-UCS2" => Self::AdobeGb1Ucs2,
217            b"Adobe-Japan1-UCS2" => Self::AdobeJapan1Ucs2,
218            b"Adobe-Korea1-UCS2" => Self::AdobeKorea1Ucs2,
219            b"B5pc-H" => Self::B5pcH,
220            b"B5pc-V" => Self::B5pcV,
221            b"CNS-EUC-H" => Self::CnsEucH,
222            b"CNS-EUC-V" => Self::CnsEucV,
223            b"ETen-B5-H" => Self::ETenB5H,
224            b"ETen-B5-V" => Self::ETenB5V,
225            b"ETenms-B5-H" => Self::ETenmsB5H,
226            b"ETenms-B5-V" => Self::ETenmsB5V,
227            b"EUC-H" => Self::EucH,
228            b"EUC-V" => Self::EucV,
229            b"Ext-RKSJ-H" => Self::ExtRksjH,
230            b"Ext-RKSJ-V" => Self::ExtRksjV,
231            b"GB-EUC-H" => Self::GbEucH,
232            b"GB-EUC-V" => Self::GbEucV,
233            b"GBK-EUC-H" => Self::GbkEucH,
234            b"GBK-EUC-V" => Self::GbkEucV,
235            b"GBK2K-H" => Self::Gbk2kH,
236            b"GBK2K-V" => Self::Gbk2kV,
237            b"GBKp-EUC-H" => Self::GbkpEucH,
238            b"GBKp-EUC-V" => Self::GbkpEucV,
239            b"GBpc-EUC-H" => Self::GbpcEucH,
240            b"GBpc-EUC-V" => Self::GbpcEucV,
241            b"H" => Self::H,
242            b"HKscs-B5-H" => Self::HKscsB5H,
243            b"HKscs-B5-V" => Self::HKscsB5V,
244            b"Identity-H" => Self::IdentityH,
245            b"Identity-V" => Self::IdentityV,
246            b"KSC-EUC-H" => Self::KscEucH,
247            b"KSC-EUC-V" => Self::KscEucV,
248            b"KSCms-UHC-H" => Self::KscmsUhcH,
249            b"KSCms-UHC-HW-H" => Self::KscmsUhcHwH,
250            b"KSCms-UHC-HW-V" => Self::KscmsUhcHwV,
251            b"KSCms-UHC-V" => Self::KscmsUhcV,
252            b"KSCpc-EUC-H" => Self::KscpcEucH,
253            b"UniCNS-UCS2-H" => Self::UniCnsUcs2H,
254            b"UniCNS-UCS2-V" => Self::UniCnsUcs2V,
255            b"UniCNS-UTF16-H" => Self::UniCnsUtf16H,
256            b"UniCNS-UTF16-V" => Self::UniCnsUtf16V,
257            b"UniGB-UCS2-H" => Self::UniGbUcs2H,
258            b"UniGB-UCS2-V" => Self::UniGbUcs2V,
259            b"UniGB-UTF16-H" => Self::UniGbUtf16H,
260            b"UniGB-UTF16-V" => Self::UniGbUtf16V,
261            b"UniJIS-UCS2-H" => Self::UniJisUcs2H,
262            b"UniJIS-UCS2-HW-H" => Self::UniJisUcs2HwH,
263            b"UniJIS-UCS2-HW-V" => Self::UniJisUcs2HwV,
264            b"UniJIS-UCS2-V" => Self::UniJisUcs2V,
265            b"UniJIS-UTF16-H" => Self::UniJisUtf16H,
266            b"UniJIS-UTF16-V" => Self::UniJisUtf16V,
267            b"UniKS-UCS2-H" => Self::UniKsUcs2H,
268            b"UniKS-UCS2-V" => Self::UniKsUcs2V,
269            b"UniKS-UTF16-H" => Self::UniKsUtf16H,
270            b"UniKS-UTF16-V" => Self::UniKsUtf16V,
271            b"V" => Self::V,
272            _ => Self::Custom(name),
273        }
274    }
275
276    /// Convert the `CMapType` back to its raw byte representation.
277    pub fn to_bytes(&self) -> &[u8] {
278        match self {
279            Self::N83pvRksjH => b"83pv-RKSJ-H",
280            Self::N90msRksjH => b"90ms-RKSJ-H",
281            Self::N90msRksjV => b"90ms-RKSJ-V",
282            Self::N90mspRksjH => b"90msp-RKSJ-H",
283            Self::N90mspRksjV => b"90msp-RKSJ-V",
284            Self::N90pvRksjH => b"90pv-RKSJ-H",
285            Self::AddRksjH => b"Add-RKSJ-H",
286            Self::AddRksjV => b"Add-RKSJ-V",
287            Self::AdobeCns1Ucs2 => b"Adobe-CNS1-UCS2",
288            Self::AdobeGb1Ucs2 => b"Adobe-GB1-UCS2",
289            Self::AdobeJapan1Ucs2 => b"Adobe-Japan1-UCS2",
290            Self::AdobeKorea1Ucs2 => b"Adobe-Korea1-UCS2",
291            Self::B5pcH => b"B5pc-H",
292            Self::B5pcV => b"B5pc-V",
293            Self::CnsEucH => b"CNS-EUC-H",
294            Self::CnsEucV => b"CNS-EUC-V",
295            Self::ETenB5H => b"ETen-B5-H",
296            Self::ETenB5V => b"ETen-B5-V",
297            Self::ETenmsB5H => b"ETenms-B5-H",
298            Self::ETenmsB5V => b"ETenms-B5-V",
299            Self::EucH => b"EUC-H",
300            Self::EucV => b"EUC-V",
301            Self::ExtRksjH => b"Ext-RKSJ-H",
302            Self::ExtRksjV => b"Ext-RKSJ-V",
303            Self::GbEucH => b"GB-EUC-H",
304            Self::GbEucV => b"GB-EUC-V",
305            Self::GbkEucH => b"GBK-EUC-H",
306            Self::GbkEucV => b"GBK-EUC-V",
307            Self::Gbk2kH => b"GBK2K-H",
308            Self::Gbk2kV => b"GBK2K-V",
309            Self::GbkpEucH => b"GBKp-EUC-H",
310            Self::GbkpEucV => b"GBKp-EUC-V",
311            Self::GbpcEucH => b"GBpc-EUC-H",
312            Self::GbpcEucV => b"GBpc-EUC-V",
313            Self::H => b"H",
314            Self::HKscsB5H => b"HKscs-B5-H",
315            Self::HKscsB5V => b"HKscs-B5-V",
316            Self::IdentityH => b"Identity-H",
317            Self::IdentityV => b"Identity-V",
318            Self::KscEucH => b"KSC-EUC-H",
319            Self::KscEucV => b"KSC-EUC-V",
320            Self::KscmsUhcH => b"KSCms-UHC-H",
321            Self::KscmsUhcHwH => b"KSCms-UHC-HW-H",
322            Self::KscmsUhcHwV => b"KSCms-UHC-HW-V",
323            Self::KscmsUhcV => b"KSCms-UHC-V",
324            Self::KscpcEucH => b"KSCpc-EUC-H",
325            Self::UniCnsUcs2H => b"UniCNS-UCS2-H",
326            Self::UniCnsUcs2V => b"UniCNS-UCS2-V",
327            Self::UniCnsUtf16H => b"UniCNS-UTF16-H",
328            Self::UniCnsUtf16V => b"UniCNS-UTF16-V",
329            Self::UniGbUcs2H => b"UniGB-UCS2-H",
330            Self::UniGbUcs2V => b"UniGB-UCS2-V",
331            Self::UniGbUtf16H => b"UniGB-UTF16-H",
332            Self::UniGbUtf16V => b"UniGB-UTF16-V",
333            Self::UniJisUcs2H => b"UniJIS-UCS2-H",
334            Self::UniJisUcs2HwH => b"UniJIS-UCS2-HW-H",
335            Self::UniJisUcs2HwV => b"UniJIS-UCS2-HW-V",
336            Self::UniJisUcs2V => b"UniJIS-UCS2-V",
337            Self::UniJisUtf16H => b"UniJIS-UTF16-H",
338            Self::UniJisUtf16V => b"UniJIS-UTF16-V",
339            Self::UniKsUcs2H => b"UniKS-UCS2-H",
340            Self::UniKsUcs2V => b"UniKS-UCS2-V",
341            Self::UniKsUtf16H => b"UniKS-UTF16-H",
342            Self::UniKsUtf16V => b"UniKS-UTF16-V",
343            Self::V => b"V",
344            Self::Custom(name) => name,
345        }
346    }
347}
348
349/// Let's limit the number of nested `usecmap` references to 16.
350const MAX_NESTING_DEPTH: u32 = 16;
351
352/// A parsed cmap.
353#[derive(Debug, Clone)]
354pub struct CMap {
355    metadata: Metadata,
356    // Note that we don't actually use this, because Acrobat _seems_ to ignore
357    // it, too.
358    _codespace_ranges: Vec<CodespaceRange>,
359    cid_ranges: PartitionedRanges,
360    notdef_ranges: PartitionedRanges,
361    bf_entries: Vec<BfRange>,
362    base: Option<Box<Self>>,
363}
364
365impl CMap {
366    /// Parse a cmap from raw bytes.
367    ///
368    /// The `get_cmap` callback is used to recursively resolve cmaps that
369    /// are referenced via `usecmap`.
370    pub fn parse<'a>(
371        data: &[u8],
372        get_cmap: impl Fn(CMapName<'_>) -> Option<&'a [u8]> + Clone + 'a,
373    ) -> Option<Self> {
374        parse::parse_inner(data, get_cmap, 0)
375    }
376
377    /// Create an Identity-H cmap.
378    pub fn identity_h() -> Self {
379        Self::identity(WritingMode::Horizontal, b"Identity-H")
380    }
381
382    /// Create an Identity-V cmap.
383    pub fn identity_v() -> Self {
384        Self::identity(WritingMode::Vertical, b"Identity-V")
385    }
386
387    fn identity(writing_mode: WritingMode, name: &[u8]) -> Self {
388        Self {
389            metadata: Metadata {
390                character_collection: Some(CharacterCollection {
391                    family: CidFamily::AdobeIdentity,
392                    supplement: 0,
393                }),
394                name: Some(Vec::from(name)),
395                writing_mode: Some(writing_mode),
396            },
397            _codespace_ranges: Vec::new(),
398            cid_ranges: {
399                let mut r = PartitionedRanges::new();
400                r.push(
401                    2,
402                    CidRange {
403                        range: Range {
404                            start: 0,
405                            end: 0xFFFF,
406                        },
407                        cid_start: 0,
408                    },
409                );
410                r
411            },
412            notdef_ranges: PartitionedRanges::new(),
413            bf_entries: Vec::new(),
414            base: None,
415        }
416    }
417
418    /// Return the metadata of this cmap.
419    pub fn metadata(&self) -> &Metadata {
420        &self.metadata
421    }
422
423    /// Look up the CID code of a character code.
424    ///
425    /// Returns `None` if the code does not match any range for the given byte length.
426    pub fn lookup_cid_code(&self, code: u32, byte_len: u8) -> Option<Cid> {
427        // Note that, in theory, we are supposed to first check the code space range
428        // whether the entry exists in the first place. However, from my experiments
429        // Acrobat mostly seems to ignore this, so we do that as well.
430
431        let cid_ranges = self.cid_ranges.get(byte_len)?;
432        let notdef_ranges = self.notdef_ranges.get(byte_len)?;
433
434        if let Some(entry) = find_in_ranges(cid_ranges, code) {
435            let offset = code.checked_sub(entry.range.start)?;
436            return entry.cid_start.checked_add(offset);
437        } else if let Some(entry) = find_in_ranges(notdef_ranges, code) {
438            // For `.notdef` ranges, all codes map to the same `.notdef` CID, so
439            // no adding of the offset here.
440            return Some(entry.cid_start);
441        }
442
443        // See pdfjs_bug920426, it uses bf chars for encoding the characters
444        // of a text-showing operator, so try that as well. We don't want to
445        // recurse though, only check the bf chars of this level.
446        if let Some(BfString::Char(lookup)) = self.lookup_bf_string_inner(code, false) {
447            return Some(lookup as u32);
448        }
449
450        // If we still haven't found anything, check the base cmap.
451        self.base
452            .as_ref()
453            .and_then(|b| b.lookup_cid_code(code, byte_len))
454    }
455
456    /// Look up a bf string in the cmap. This is usually
457    /// used for mapping character codes to Unicode codepoints in a
458    /// `ToUnicode` cmap.
459    ///
460    /// Returns `None` if no mapping is available.
461    pub fn lookup_bf_string(&self, code: u32) -> Option<BfString> {
462        self.lookup_bf_string_inner(code, true)
463    }
464
465    fn lookup_bf_string_inner(&self, code: u32, recurse: bool) -> Option<BfString> {
466        if let Some(entry) = find_in_ranges(&self.bf_entries, code) {
467            let offset = u16::try_from(code - entry.range.start).ok()?;
468
469            fn decode_utf16(units: &[u16]) -> Option<BfString> {
470                let mut iter = core::char::decode_utf16(units.iter().copied());
471                let first = iter.next()?.ok()?;
472
473                if iter.next().is_none() {
474                    Some(BfString::Char(first))
475                } else {
476                    let s = String::from_utf16(units).ok()?;
477                    Some(BfString::String(s))
478                }
479            }
480
481            return if offset == 0 {
482                Some(decode_utf16(&entry.dst_base)?)
483            } else {
484                let mut units = entry.dst_base.clone();
485                *units.last_mut()? = units.last()?.checked_add(offset)?;
486                Some(decode_utf16(&units)?)
487            };
488        }
489
490        if recurse {
491            self.base.as_ref()?.lookup_bf_string(code)
492        } else {
493            None
494        }
495    }
496}
497
498trait HasRange {
499    fn range(&self) -> &Range;
500}
501
502fn find_in_ranges<T: HasRange>(entries: &[T], code: u32) -> Option<&T> {
503    let idx = entries
504        .binary_search_by(|entry| {
505            let r = entry.range();
506            if code < r.start {
507                core::cmp::Ordering::Greater
508            } else if code > r.end {
509                core::cmp::Ordering::Less
510            } else {
511                core::cmp::Ordering::Equal
512            }
513        })
514        .ok()?;
515
516    Some(&entries[idx])
517}
518
519/// A range with a start and end code.
520#[derive(Debug, Clone)]
521pub(crate) struct Range {
522    pub(crate) start: u32,
523    pub(crate) end: u32,
524}
525
526/// A range of character codes mapped to CIDs.
527#[derive(Debug, Clone)]
528pub struct CidRange {
529    pub(crate) range: Range,
530    pub(crate) cid_start: Cid,
531}
532
533impl HasRange for CidRange {
534    fn range(&self) -> &Range {
535        &self.range
536    }
537}
538
539#[derive(Debug, Clone)]
540pub(crate) struct PartitionedRanges {
541    buckets: [Vec<CidRange>; 4],
542}
543
544impl PartitionedRanges {
545    pub(crate) fn new() -> Self {
546        Self {
547            buckets: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
548        }
549    }
550
551    pub(crate) fn push(&mut self, byte_len: usize, range: CidRange) {
552        if let Some(bucket) = byte_len
553            .checked_sub(1)
554            .and_then(|i| self.buckets.get_mut(i))
555        {
556            bucket.push(range);
557        }
558    }
559
560    pub(crate) fn get(&self, byte_len: u8) -> Option<&[CidRange]> {
561        let idx = (byte_len as usize).checked_sub(1)?;
562        self.buckets.get(idx).map(|v| v.as_slice())
563    }
564
565    pub(crate) fn sort(&mut self) {
566        for bucket in &mut self.buckets {
567            bucket.sort_by_key(|a| a.range.start);
568        }
569    }
570}
571
572#[derive(Debug, Clone)]
573pub(crate) struct BfRange {
574    pub(crate) range: Range,
575    pub(crate) dst_base: Vec<u16>,
576}
577
578impl HasRange for BfRange {
579    fn range(&self) -> &Range {
580        &self.range
581    }
582}
583
584/// A codespace range defining valid character code byte sequences.
585#[derive(Debug, Clone)]
586#[allow(dead_code)]
587pub(crate) struct CodespaceRange {
588    pub(crate) number_bytes: u8,
589    pub(crate) low: u32,
590    pub(crate) high: u32,
591}
592
593/// A Unicode value decoded from a cmap.
594#[derive(Debug, Clone, PartialEq, Eq)]
595pub enum BfString {
596    /// A single character.
597    Char(char),
598    /// A UTF-8 string.
599    String(String),
600}
601
602/// Metadata extracted from a cmap file.
603#[derive(Debug, Clone)]
604pub struct Metadata {
605    /// The referenced character collection.
606    pub character_collection: Option<CharacterCollection>,
607    /// The cmap name.
608    pub name: Option<Vec<u8>>,
609    /// The writing mode.
610    pub writing_mode: Option<WritingMode>,
611}
612
613/// The registry+ordering family of a CID character collection.
614#[derive(Debug, Clone, PartialEq, Eq)]
615pub enum CidFamily {
616    /// Adobe-Japan1
617    AdobeJapan1,
618    /// Adobe-GB1
619    AdobeGB1,
620    /// Adobe-CNS1
621    AdobeCNS1,
622    /// Adobe-Korea1
623    AdobeKorea1,
624    /// Adobe-Identity
625    AdobeIdentity,
626    /// A non-predefined registry/ordering pair.
627    Custom {
628        /// The registry name.
629        registry: Vec<u8>,
630        /// The ordering name.
631        ordering: Vec<u8>,
632    },
633}
634
635impl CidFamily {
636    /// Create a `CidFamily` from raw registry and ordering byte strings.
637    pub fn from_registry_ordering(registry: &[u8], ordering: &[u8]) -> Self {
638        if registry == b"Adobe" {
639            match ordering {
640                b"Japan1" => Self::AdobeJapan1,
641                b"GB1" => Self::AdobeGB1,
642                b"CNS1" => Self::AdobeCNS1,
643                b"Korea1" => Self::AdobeKorea1,
644                b"Identity" => Self::AdobeIdentity,
645                _ => Self::Custom {
646                    registry: registry.to_vec(),
647                    ordering: ordering.to_vec(),
648                },
649            }
650        } else {
651            Self::Custom {
652                registry: registry.to_vec(),
653                ordering: ordering.to_vec(),
654            }
655        }
656    }
657
658    /// Return the predefined UTF-16 `CMap` that maps Unicode code points to CIDs
659    /// for this family, if one exists.
660    pub fn unicode_cmap(&self) -> Option<CMapName<'static>> {
661        match self {
662            Self::AdobeJapan1 => Some(CMapName::UniJisUtf16H),
663            Self::AdobeGB1 => Some(CMapName::UniGbUtf16H),
664            Self::AdobeCNS1 => Some(CMapName::UniCnsUtf16H),
665            Self::AdobeKorea1 => Some(CMapName::UniKsUtf16H),
666            Self::AdobeIdentity | Self::Custom { .. } => None,
667        }
668    }
669
670    /// Return the `UCS2` cmap corresponding to the given character collection.
671    pub fn ucs2_cmap(&self) -> Option<CMapName<'static>> {
672        match self {
673            Self::AdobeJapan1 => Some(CMapName::AdobeJapan1Ucs2),
674            Self::AdobeGB1 => Some(CMapName::AdobeGb1Ucs2),
675            Self::AdobeCNS1 => Some(CMapName::AdobeCns1Ucs2),
676            Self::AdobeKorea1 => Some(CMapName::AdobeKorea1Ucs2),
677            Self::AdobeIdentity | Self::Custom { .. } => None,
678        }
679    }
680
681    /// Returns `true` for the four predefined CJK character collections
682    /// (Simplified Chinese, Traditional Chinese, Japanese, Korean).
683    pub fn is_cjk(&self) -> bool {
684        matches!(
685            self,
686            Self::AdobeGB1 | Self::AdobeCNS1 | Self::AdobeJapan1 | Self::AdobeKorea1
687        )
688    }
689}
690
691/// A CID character collection identifying the character set and ordering.
692#[derive(Debug, Clone, PartialEq, Eq)]
693pub struct CharacterCollection {
694    /// The registry+ordering family.
695    pub family: CidFamily,
696    /// The supplement number.
697    pub supplement: i32,
698}
699
700/// The writing mode of a cmap.
701#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
702pub enum WritingMode {
703    /// Horizontal writing mode.
704    #[default]
705    Horizontal,
706    /// Vertical writing mode.
707    Vertical,
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713
714    // Note that those cmaps might not be completely valid according to the rules
715    // of cmap/Postscript, but since our parser is very lenient and doesn't run a real
716    // interpreter we can shorten them by a lot.
717
718    const PREAMBLE: &[u8] = br#"/CIDSystemInfo 3 dict dup begin
719  /Registry (Adobe) def
720  /Ordering (Japan1) def
721  /Supplement 0 def
722end def
723/CMapName /Test def
724/WMode 0 def
7252 begincodespacerange
726<00> <FF>
727<0000> <FFFF>
728endcodespacerange
729"#;
730
731    fn parse_with_preamble(body: &[u8]) -> CMap {
732        let mut data = Vec::new();
733        data.extend_from_slice(PREAMBLE);
734        data.extend_from_slice(body);
735        CMap::parse(&data, |_| None).unwrap()
736    }
737
738    /// A `/CMapName` whose value is a file path leaves bare tokens behind, and
739    /// a stray `-` is not a number in any of them. Dropping the whole map over
740    /// one turned every code in `issue7492.pdf` into its own code point (#431).
741    #[test]
742    fn a_token_we_cannot_read_does_not_cost_the_whole_map() {
743        let data = br#"/CIDInit /ProcSet findresource begin
74412 dict begin
745begincmap
746/CMapName /-usr-share-fonts-truetype-Panton-Panton Family-Fontfabric - Panton.otf,000-UTF16 def
7471 begincodespacerange
748<0000> <FFFF>
749endcodespacerange
7501 beginbfchar
751<0055> <0050>
752endbfchar
753endcmap"#;
754
755        let cmap = CMap::parse(data, |_| None).expect("the map survives the file path");
756        assert_eq!(cmap.lookup_bf_string(0x0055), Some(BfString::Char('P')));
757    }
758
759    #[test]
760    fn metadata_parsing() {
761        let data = br#"
762/CIDInit /ProcSet findresource begin
76312 dict begin
764begincmap
765/CIDSystemInfo 3 dict dup begin
766  /Registry (Adobe) def
767  /Ordering (Japan1) def
768  /Supplement 6 def
769end def
770/CMapName /Adobe-Japan1-H def
771/CMapType 1 def
772/WMode 0 def
773endcmap"#;
774
775        let cmap = CMap::parse(data, |_| None).unwrap();
776        let cc = cmap.metadata().character_collection.as_ref().unwrap();
777        assert_eq!(cc.family, CidFamily::AdobeJapan1);
778        assert_eq!(cc.supplement, 6);
779        assert_eq!(
780            cmap.metadata().name.as_deref(),
781            Some(b"Adobe-Japan1-H".as_slice())
782        );
783        assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Horizontal));
784    }
785
786    #[test]
787    fn vertical_writing_mode() {
788        let data = br#"
789/CIDSystemInfo 3 dict dup begin
790  /Registry (Adobe) def
791  /Ordering (Japan1) def
792  /Supplement 6 def
793end def
794/CMapName /Adobe-Japan1-V def
795/WMode 1 def
796"#;
797
798        let cmap = CMap::parse(data, |_| None).unwrap();
799        assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
800        assert_eq!(
801            cmap.metadata().name.as_deref(),
802            Some(b"Adobe-Japan1-V".as_slice())
803        );
804    }
805
806    #[test]
807    fn cid_range_lookup() {
808        let cmap = parse_with_preamble(
809            br#"
8103 begincidrange
811<0000> <00FF> 0
812<0100> <01FF> 256
813<8140> <817E> 633
814endcidrange
815"#,
816        );
817
818        assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
819        assert_eq!(cmap.lookup_cid_code(0x0042, 2), Some(0x42));
820        assert_eq!(cmap.lookup_cid_code(0x00FF, 2), Some(0xFF));
821
822        assert_eq!(cmap.lookup_cid_code(0x0100, 2), Some(256));
823        assert_eq!(cmap.lookup_cid_code(0x01FF, 2), Some(511));
824
825        assert_eq!(cmap.lookup_cid_code(0x8140, 2), Some(633));
826        assert_eq!(cmap.lookup_cid_code(0x817E, 2), Some(633 + 62));
827    }
828
829    #[test]
830    fn cid_char_lookup() {
831        let cmap = parse_with_preamble(
832            br#"
8333 begincidchar
834<03> 1
835<04> 2
836<20> 50
837endcidchar
838"#,
839        );
840
841        assert_eq!(cmap.lookup_cid_code(0x03, 1), Some(1));
842        assert_eq!(cmap.lookup_cid_code(0x04, 1), Some(2));
843        assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(50));
844    }
845
846    #[test]
847    fn lookup_miss() {
848        let cmap = parse_with_preamble(
849            br#"
8501 begincidrange
851<0100> <01FF> 0
852endcidrange
853"#,
854        );
855
856        assert_eq!(cmap.lookup_cid_code(0x00FF, 2), None);
857        assert_eq!(cmap.lookup_cid_code(0x0200, 2), None);
858        assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), None);
859    }
860
861    #[test]
862    fn multiple_sections() {
863        let cmap = parse_with_preamble(
864            br#"
8652 begincidrange
866<0000> <00FF> 0
867<0100> <01FF> 256
868endcidrange
8691 begincidchar
870<0200> 600
871endcidchar
8721 begincidrange
873<8140> <817E> 633
874endcidrange
875"#,
876        );
877
878        assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
879        assert_eq!(cmap.lookup_cid_code(0x0100, 2), Some(256));
880        assert_eq!(cmap.lookup_cid_code(0x0200, 2), Some(600));
881        assert_eq!(cmap.lookup_cid_code(0x8140, 2), Some(633));
882    }
883
884    #[test]
885    fn single_byte_codes() {
886        let cmap = parse_with_preamble(
887            br#"
8882 begincidrange
889<00> <7F> 0
890<80> <FF> 200
891endcidrange
892"#,
893        );
894
895        assert_eq!(cmap.lookup_cid_code(0x00, 1), Some(0));
896        assert_eq!(cmap.lookup_cid_code(0x41, 1), Some(0x41));
897        assert_eq!(cmap.lookup_cid_code(0x80, 1), Some(200));
898        assert_eq!(cmap.lookup_cid_code(0xFF, 1), Some(200 + 127));
899    }
900
901    #[test]
902    fn dict_style_cidsysteminfo() {
903        let data = br#"
904/CIDInit /ProcSet findresource begin
90510 dict begin
906begincmap
907/CIDSystemInfo
908<< /Registry (Adobe)
909/Ordering (UCS)
910/Supplement 0
911>> def
912/CMapName /Adobe-Identity-UCS def
913/CMapType 2 def
9141 begincodespacerange
915<0000> <FFFF>
916endcodespacerange
9172 beginbfchar
918<001F> <F049>
919<002A> <F055>
920endbfchar
921endcmap
922CMapName currentdict /CMap defineresource pop
923end
924end
925"#;
926        let cmap = CMap::parse(data, |_| None).unwrap();
927        let cc = cmap.metadata().character_collection.as_ref().unwrap();
928        assert_eq!(
929            cc.family,
930            CidFamily::Custom {
931                registry: b"Adobe".to_vec(),
932                ordering: b"UCS".to_vec(),
933            }
934        );
935        assert_eq!(cc.supplement, 0);
936        assert_eq!(
937            cmap.metadata().name.as_deref(),
938            Some(b"Adobe-Identity-UCS".as_slice())
939        );
940        assert_eq!(
941            cmap.lookup_bf_string(0x001F),
942            Some(BfString::Char('\u{F049}'))
943        );
944        assert_eq!(
945            cmap.lookup_bf_string(0x002A),
946            Some(BfString::Char('\u{F055}'))
947        );
948    }
949
950    #[test]
951    fn usecmap_chaining() {
952        let base_data = br#"
953/CIDSystemInfo 3 dict dup begin
954  /Registry (Adobe) def
955  /Ordering (Japan1) def
956  /Supplement 0 def
957end def
958/CMapName /Base def
959/WMode 0 def
9601 begincodespacerange
961<0000> <FFFF>
962endcodespacerange
9631 begincidrange
964<0000> <00FF> 0
965endcidrange
966"#;
967
968        let child_data = br#"
969/Base usecmap
970/CIDSystemInfo 3 dict dup begin
971  /Registry (Adobe) def
972  /Ordering (Japan1) def
973  /Supplement 0 def
974end def
975/CMapName /Child def
976/WMode 0 def
9771 begincodespacerange
978<0000> <FFFF>
979endcodespacerange
9801 begincidrange
981<0100> <01FF> 256
982endcidrange
983"#;
984
985        let cmap = CMap::parse(child_data, |name| {
986            if name.to_bytes() == b"Base" {
987                Some(base_data.as_slice())
988            } else {
989                None
990            }
991        })
992        .unwrap();
993
994        assert_eq!(cmap.lookup_cid_code(0x0100, 2), Some(256));
995        assert_eq!(cmap.lookup_cid_code(0x01FF, 2), Some(511));
996        assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
997        assert_eq!(cmap.lookup_cid_code(0x00FF, 2), Some(0xFF));
998
999        assert_eq!(cmap.lookup_cid_code(0x0200, 2), None);
1000    }
1001
1002    #[test]
1003    fn usecmap_partial_override() {
1004        let base_data = br#"
1005/CIDSystemInfo 3 dict dup begin
1006  /Registry (Adobe) def
1007  /Ordering (Japan1) def
1008  /Supplement 0 def
1009end def
1010/CMapName /Base def
1011/WMode 0 def
10121 begincodespacerange
1013<0000> <FFFF>
1014endcodespacerange
10151 begincidrange
1016<0000> <00FF> 0
1017endcidrange
1018"#;
1019
1020        let child_data = br#"
1021/Base usecmap
1022/CIDSystemInfo 3 dict dup begin
1023  /Registry (Adobe) def
1024  /Ordering (Japan1) def
1025  /Supplement 0 def
1026end def
1027/CMapName /Child def
1028/WMode 0 def
10291 begincodespacerange
1030<0000> <FFFF>
1031endcodespacerange
10321 begincidrange
1033<0040> <007F> 500
1034endcidrange
1035"#;
1036
1037        let cmap = CMap::parse(child_data, |name| {
1038            if name.to_bytes() == b"Base" {
1039                Some(base_data.as_slice())
1040            } else {
1041                None
1042            }
1043        })
1044        .unwrap();
1045
1046        assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
1047        assert_eq!(cmap.lookup_cid_code(0x003F, 2), Some(0x3F));
1048        assert_eq!(cmap.lookup_cid_code(0x0040, 2), Some(500));
1049        assert_eq!(cmap.lookup_cid_code(0x007F, 2), Some(563));
1050        assert_eq!(cmap.lookup_cid_code(0x0080, 2), Some(0x80));
1051        assert_eq!(cmap.lookup_cid_code(0x00FF, 2), Some(0xFF));
1052    }
1053
1054    #[test]
1055    fn notdef_char_lookup() {
1056        let cmap = parse_with_preamble(
1057            br#"
10582 beginnotdefchar
1059<03> 10
1060<20> 20
1061endnotdefchar
1062"#,
1063        );
1064
1065        assert_eq!(cmap.lookup_cid_code(0x03, 1), Some(10));
1066        assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(20));
1067        assert_eq!(cmap.lookup_cid_code(0x04, 1), None);
1068    }
1069
1070    #[test]
1071    fn notdef_range_lookup() {
1072        let cmap = parse_with_preamble(
1073            br#"
10741 beginnotdefrange
1075<0000> <001F> 100
1076endnotdefrange
1077"#,
1078        );
1079
1080        assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(100));
1081        assert_eq!(cmap.lookup_cid_code(0x0001, 2), Some(100));
1082        assert_eq!(cmap.lookup_cid_code(0x001F, 2), Some(100));
1083        assert_eq!(cmap.lookup_cid_code(0x0020, 2), None);
1084    }
1085
1086    #[test]
1087    fn bfchar_lookup() {
1088        let cmap = parse_with_preamble(
1089            br#"
10902 beginbfchar
1091<0041> <0048>
1092<0042> <0065>
1093endbfchar
1094"#,
1095        );
1096
1097        assert_eq!(cmap.lookup_bf_string(0x0041), Some(BfString::Char('H')));
1098        assert_eq!(cmap.lookup_bf_string(0x0042), Some(BfString::Char('e')));
1099        assert_eq!(cmap.lookup_bf_string(0x0043), None);
1100    }
1101
1102    #[test]
1103    fn bfchar_ligature() {
1104        let cmap = parse_with_preamble(
1105            br#"
11061 beginbfchar
1107<005F> <00660066>
1108endbfchar
1109"#,
1110        );
1111
1112        assert_eq!(
1113            cmap.lookup_bf_string(0x005F),
1114            Some(BfString::String(String::from("ff")))
1115        );
1116    }
1117
1118    #[test]
1119    fn bfchar_surrogate_pair() {
1120        let cmap = parse_with_preamble(
1121            br#"
11221 beginbfchar
1123<3A51> <D840DC3E>
1124endbfchar
1125"#,
1126        );
1127
1128        assert_eq!(
1129            cmap.lookup_bf_string(0x3A51),
1130            Some(BfString::Char('\u{2003E}'))
1131        );
1132    }
1133
1134    #[test]
1135    fn bfrange_incrementing() {
1136        let cmap = parse_with_preamble(
1137            br#"
11381 beginbfrange
1139<0000> <0004> <0041>
1140endbfrange
1141"#,
1142        );
1143
1144        assert_eq!(cmap.lookup_bf_string(0x0000), Some(BfString::Char('A')));
1145        assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char('B')));
1146        assert_eq!(cmap.lookup_bf_string(0x0004), Some(BfString::Char('E')));
1147        assert_eq!(cmap.lookup_bf_string(0x0005), None);
1148    }
1149
1150    #[test]
1151    fn bfrange_array() {
1152        let cmap = parse_with_preamble(
1153            br#"
11541 beginbfrange
1155<005F> <0061> [<00660066> <00660069> <0066006C>]
1156endbfrange
1157"#,
1158        );
1159
1160        // ff, fi, fl ligatures
1161        assert_eq!(
1162            cmap.lookup_bf_string(0x005F),
1163            Some(BfString::String(String::from("ff")))
1164        );
1165        assert_eq!(
1166            cmap.lookup_bf_string(0x0060),
1167            Some(BfString::String(String::from("fi")))
1168        );
1169        assert_eq!(
1170            cmap.lookup_bf_string(0x0061),
1171            Some(BfString::String(String::from("fl")))
1172        );
1173    }
1174
1175    #[test]
1176    fn unicode_lookup_miss() {
1177        let cmap = parse_with_preamble(
1178            br#"
11791 beginbfchar
1180<0041> <0048>
1181endbfchar
1182"#,
1183        );
1184
1185        assert_eq!(cmap.lookup_bf_string(0x0000), None);
1186        assert_eq!(cmap.lookup_bf_string(0x0042), None);
1187    }
1188
1189    #[test]
1190    fn identity_h() {
1191        let cmap = CMap::identity_h();
1192        assert_eq!(
1193            cmap.metadata().name.as_deref(),
1194            Some(b"Identity-H".as_slice())
1195        );
1196        assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Horizontal));
1197
1198        assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x0041));
1199        assert_eq!(cmap.lookup_cid_code(0x1234, 2), Some(0x1234));
1200        assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1201
1202        assert_eq!(cmap.lookup_cid_code(0x0041, 1), None);
1203        assert_eq!(cmap.lookup_cid_code(0x0041, 3), None);
1204    }
1205
1206    #[test]
1207    fn identity_v() {
1208        let cmap = CMap::identity_v();
1209        assert_eq!(
1210            cmap.metadata().name.as_deref(),
1211            Some(b"Identity-V".as_slice())
1212        );
1213        assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
1214
1215        assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x0041));
1216        assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1217    }
1218
1219    #[test]
1220    fn minimal_cmap_no_name_no_wmode() {
1221        // Extracted from corpus PDF 0500013.
1222        let data = br#"
1223/CIDInit /ProcSet findresource begin
122412 dict begin
1225begincmap
1226/CMapType 2 def
12271 begincodespacerange
1228<00><ff>
1229endcodespacerange
12301 beginbfrange
1231<01><01><0020>
1232endbfrange
1233endcmap
1234"#;
1235        let cmap = CMap::parse(data, |_| None).unwrap();
1236        assert_eq!(cmap.metadata().name, None);
1237        assert_eq!(cmap.metadata().character_collection, None);
1238        assert_eq!(cmap.metadata().writing_mode, None);
1239        assert_eq!(cmap.lookup_bf_string(0x01), Some(BfString::Char(' ')));
1240    }
1241
1242    #[test]
1243    fn registry_as_name() {
1244        // Extracted from corpus PDF 0875241, uses names instead of strings for
1245        // strings for metadata.
1246        let data = br#"
1247/CIDSystemInfo
1248<< /Registry /ABCDEF+SimSun
1249/Ordering (pdfbeaninc)
1250/Supplement 0
1251>> def
1252/CMapName /ABCDEF+SimSun def
1253/CMapType 2 def
12541 begincodespacerange
1255<0000> <FFFF>
1256endcodespacerange
12571 beginbfrange
1258<0000> <0000> <6881>
1259endbfrange
1260"#;
1261        let cmap = CMap::parse(data, |_| None).unwrap();
1262        let cc = cmap.metadata().character_collection.as_ref().unwrap();
1263        assert_eq!(
1264            cc.family,
1265            CidFamily::Custom {
1266                registry: b"ABCDEF+SimSun".to_vec(),
1267                ordering: b"pdfbeaninc".to_vec(),
1268            }
1269        );
1270        assert_eq!(cc.supplement, 0);
1271        assert_eq!(
1272            cmap.lookup_bf_string(0x0000),
1273            Some(BfString::Char('\u{6881}'))
1274        );
1275    }
1276
1277    #[test]
1278    fn single_byte_bfrange_destination() {
1279        // See for example corpus test case 0625248.
1280        let cmap = parse_with_preamble(
1281            br#"
12821 beginbfrange
1283<00> <7f> <00>
1284endbfrange
1285"#,
1286        );
1287
1288        assert_eq!(
1289            cmap.lookup_bf_string(0x00),
1290            Some(BfString::Char('\u{0000}'))
1291        );
1292        assert_eq!(cmap.lookup_bf_string(0x41), Some(BfString::Char('A')));
1293        assert_eq!(
1294            cmap.lookup_bf_string(0x7f),
1295            Some(BfString::Char('\u{007F}'))
1296        );
1297    }
1298}
1299
1300#[cfg(all(test, feature = "embed-cmaps"))]
1301mod bcmap_tests {
1302    use super::*;
1303
1304    fn get_embedded_cmap(name: CMapName<'_>) -> Option<&'static [u8]> {
1305        load_embedded(name)
1306    }
1307
1308    #[test]
1309    fn embedded_h_cmap() {
1310        let data = load_embedded(CMapName::H).expect("embedded H cmap not found");
1311        let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse H cmap");
1312        assert_eq!(cmap.metadata().writing_mode, None);
1313        assert_eq!(
1314            cmap.metadata().character_collection,
1315            Some(CharacterCollection {
1316                family: CidFamily::AdobeJapan1,
1317                supplement: 1,
1318            })
1319        );
1320        assert_eq!(cmap.lookup_cid_code(0x2121, 2), Some(633));
1321        assert_eq!(cmap.lookup_cid_code(0x2221, 2), Some(727));
1322    }
1323
1324    #[test]
1325    fn embedded_90ms_rksj_h() {
1326        let data =
1327            load_embedded(CMapName::N90msRksjH).expect("embedded 90ms-RKSJ-H cmap not found");
1328        let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse 90ms-RKSJ-H cmap");
1329        assert_eq!(
1330            cmap.metadata().character_collection,
1331            Some(CharacterCollection {
1332                family: CidFamily::AdobeJapan1,
1333                supplement: 2,
1334            })
1335        );
1336        assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(231));
1337        assert_eq!(cmap.lookup_cid_code(0x8140, 2), Some(633));
1338    }
1339
1340    #[test]
1341    fn embedded_v_cmap() {
1342        let data = load_embedded(CMapName::V).expect("embedded V cmap not found");
1343        let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse V cmap");
1344        assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
1345        assert_eq!(
1346            cmap.metadata().character_collection,
1347            Some(CharacterCollection {
1348                family: CidFamily::AdobeJapan1,
1349                supplement: 1,
1350            })
1351        );
1352        // Inherited from base "H" via usecmap.
1353        assert_eq!(cmap.lookup_cid_code(0x2121, 2), Some(633));
1354    }
1355
1356    #[test]
1357    fn embedded_gbk_euc_h() {
1358        let data = load_embedded(CMapName::GbkEucH).expect("embedded GBK-EUC-H not found");
1359        let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse GBK-EUC-H");
1360        assert_eq!(
1361            cmap.metadata().character_collection,
1362            Some(CharacterCollection {
1363                family: CidFamily::AdobeGB1,
1364                supplement: 2,
1365            })
1366        );
1367        assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(7716));
1368        assert_eq!(cmap.lookup_cid_code(0x21, 1), Some(814));
1369        assert_eq!(cmap.lookup_cid_code(0x7E, 1), Some(814 + 0x7E - 0x21));
1370        assert_eq!(cmap.lookup_cid_code(0xFE80, 2), Some(22094));
1371        assert_eq!(cmap.lookup_cid_code(0xFEA0, 2), Some(22094 + 0xA0 - 0x80));
1372    }
1373
1374    #[test]
1375    fn embedded_ksc_euc_h() {
1376        let data = load_embedded(CMapName::KscEucH).unwrap();
1377        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1378        assert_eq!(
1379            cmap.metadata().character_collection,
1380            Some(CharacterCollection {
1381                family: CidFamily::AdobeKorea1,
1382                supplement: 0,
1383            })
1384        );
1385        assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(8094));
1386        assert_eq!(cmap.lookup_cid_code(0x41, 1), Some(8094 + 0x41 - 0x20));
1387        assert_eq!(cmap.lookup_cid_code(0xA1A1, 2), Some(101));
1388        assert_eq!(cmap.lookup_cid_code(0xA1FE, 2), Some(101 + 0xFE - 0xA1));
1389        assert_eq!(cmap.lookup_cid_code(0xFDA1, 2), Some(7962));
1390    }
1391
1392    #[test]
1393    fn embedded_b5pc_h() {
1394        let data = load_embedded(CMapName::B5pcH).unwrap();
1395        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1396        assert_eq!(
1397            cmap.metadata().character_collection,
1398            Some(CharacterCollection {
1399                family: CidFamily::AdobeCNS1,
1400                supplement: 0,
1401            })
1402        );
1403        assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(1));
1404        assert_eq!(cmap.lookup_cid_code(0x41, 1), Some(1 + 0x41 - 0x20));
1405        assert_eq!(cmap.lookup_cid_code(0x80, 1), Some(61));
1406        assert_eq!(cmap.lookup_cid_code(0xD140, 2), Some(7251));
1407        assert_eq!(cmap.lookup_cid_code(0xD17E, 2), Some(7251 + 0x7E - 0x40));
1408        assert_eq!(cmap.lookup_cid_code(0xF9D5, 2), Some(13642 + 3));
1409    }
1410
1411    #[test]
1412    fn embedded_unijis_utf16_h() {
1413        let data = load_embedded(CMapName::UniJisUtf16H).unwrap();
1414        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1415        assert_eq!(
1416            cmap.metadata().character_collection,
1417            Some(CharacterCollection {
1418                family: CidFamily::AdobeJapan1,
1419                supplement: 7,
1420            })
1421        );
1422        assert_eq!(cmap.lookup_cid_code(0x20, 2), Some(1));
1423        assert_eq!(cmap.lookup_cid_code(0x41, 2), Some(1 + 0x41 - 0x20));
1424        assert_eq!(cmap.lookup_cid_code(0x5C, 2), Some(97));
1425        assert_eq!(cmap.lookup_cid_code(0x5BCC, 2), Some(3531));
1426        assert_eq!(cmap.lookup_cid_code(0xD884DF50, 4), Some(19130));
1427    }
1428
1429    #[test]
1430    fn embedded_identity_h() {
1431        let data = load_embedded(CMapName::IdentityH).expect("embedded Identity-H not found");
1432        let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse Identity-H cmap");
1433        assert_eq!(cmap.metadata().writing_mode, None);
1434        assert_eq!(
1435            cmap.metadata().character_collection,
1436            Some(CharacterCollection {
1437                family: CidFamily::AdobeIdentity,
1438                supplement: 0,
1439            })
1440        );
1441        assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
1442        assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x41));
1443        assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1444    }
1445
1446    #[test]
1447    fn embedded_identity_v() {
1448        let data = load_embedded(CMapName::IdentityV).expect("embedded Identity-V not found");
1449        let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse Identity-V cmap");
1450        assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
1451        assert_eq!(
1452            cmap.metadata().character_collection,
1453            Some(CharacterCollection {
1454                family: CidFamily::AdobeIdentity,
1455                supplement: 0,
1456            })
1457        );
1458        assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
1459        assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x41));
1460        assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1461    }
1462
1463    // Note: Some tests AI-generated, haven't double-checked them.
1464
1465    #[test]
1466    fn embedded_adobe_gb1_ucs2() {
1467        let data = load_embedded(CMapName::AdobeGb1Ucs2).unwrap();
1468        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1469        assert_eq!(
1470            cmap.metadata().character_collection,
1471            Some(CharacterCollection {
1472                family: CidFamily::Custom {
1473                    registry: b"Adobe".to_vec(),
1474                    ordering: b"Adobe_GB1_UCS2".to_vec(),
1475                },
1476                supplement: 5,
1477            })
1478        );
1479
1480        assert_eq!(
1481            cmap.lookup_bf_string(0x0063),
1482            Some(BfString::Char('\u{00B7}'))
1483        );
1484        assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char(' ')));
1485        assert_eq!(cmap.lookup_bf_string(0x0022), Some(BfString::Char('A')));
1486        assert_eq!(cmap.lookup_bf_string(0x005F), Some(BfString::Char('~')));
1487        assert_eq!(
1488            cmap.lookup_bf_string(0x560F),
1489            Some(BfString::String(String::from("\u{90CE}\u{FE00}")))
1490        );
1491    }
1492
1493    #[test]
1494    fn embedded_adobe_japan1_ucs2() {
1495        let data = load_embedded(CMapName::AdobeJapan1Ucs2).unwrap();
1496        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1497        assert_eq!(
1498            cmap.metadata().character_collection,
1499            Some(CharacterCollection {
1500                family: CidFamily::Custom {
1501                    registry: b"Adobe".to_vec(),
1502                    ordering: b"Adobe_Japan1_UCS2".to_vec(),
1503                },
1504                supplement: 7,
1505            })
1506        );
1507
1508        assert_eq!(
1509            cmap.lookup_bf_string(0x003D),
1510            Some(BfString::Char('\u{00A5}'))
1511        );
1512        assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char(' ')));
1513        assert_eq!(cmap.lookup_bf_string(0x003C), Some(BfString::Char('[')));
1514        assert_eq!(
1515            cmap.lookup_bf_string(0x0476),
1516            Some(BfString::String(String::from("\u{82A6}\u{E0100}")))
1517        );
1518        assert_eq!(
1519            cmap.lookup_bf_string(0x265B),
1520            Some(BfString::String(String::from("10/11")))
1521        );
1522        assert_eq!(
1523            cmap.lookup_bf_string(0x2E6B),
1524            Some(BfString::String(String::from("オングストローム")))
1525        );
1526    }
1527
1528    #[test]
1529    fn embedded_adobe_korea1_ucs2() {
1530        let data = load_embedded(CMapName::AdobeKorea1Ucs2).unwrap();
1531        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1532        assert_eq!(
1533            cmap.metadata().character_collection,
1534            Some(CharacterCollection {
1535                family: CidFamily::Custom {
1536                    registry: b"Adobe".to_vec(),
1537                    ordering: b"Adobe_Korea1_UCS2".to_vec(),
1538                },
1539                supplement: 2,
1540            })
1541        );
1542
1543        assert_eq!(
1544            cmap.lookup_bf_string(0x0060),
1545            Some(BfString::Char('\u{20A9}'))
1546        );
1547        assert_eq!(
1548            cmap.lookup_bf_string(0x2072),
1549            Some(BfString::String(String::from("[10]")))
1550        );
1551        assert_eq!(
1552            cmap.lookup_bf_string(0x2073),
1553            Some(BfString::String(String::from("[11]")))
1554        );
1555    }
1556
1557    #[test]
1558    fn embedded_adobe_cns1_ucs2() {
1559        let data = load_embedded(CMapName::AdobeCns1Ucs2).unwrap();
1560        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1561        assert_eq!(
1562            cmap.metadata().character_collection,
1563            Some(CharacterCollection {
1564                family: CidFamily::Custom {
1565                    registry: b"Adobe".to_vec(),
1566                    ordering: b"Adobe_CNS1_UCS2".to_vec(),
1567                },
1568                supplement: 7,
1569            })
1570        );
1571
1572        assert_eq!(
1573            cmap.lookup_bf_string(0x0060),
1574            Some(BfString::Char('\u{00A9}'))
1575        );
1576        assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char(' ')));
1577        assert_eq!(cmap.lookup_bf_string(0x005F), Some(BfString::Char('~')));
1578        assert_eq!(
1579            cmap.lookup_bf_string(0x36B0),
1580            Some(BfString::Char('\u{200CC}'))
1581        );
1582    }
1583
1584    #[test]
1585    fn embedded_eten_b5_h() {
1586        let data = load_embedded(CMapName::ETenB5H).unwrap();
1587        let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1588        assert_eq!(
1589            cmap.metadata().character_collection,
1590            Some(CharacterCollection {
1591                family: CidFamily::AdobeCNS1,
1592                supplement: 0,
1593            })
1594        );
1595        assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(13648));
1596        assert_eq!(cmap.lookup_cid_code(0x7E, 1), Some(13648 + 0x7E - 0x20));
1597        assert_eq!(cmap.lookup_cid_code(0xA140, 2), Some(99));
1598        assert_eq!(cmap.lookup_cid_code(0xA158, 2), Some(99 + 0x58 - 0x40));
1599        assert_eq!(cmap.lookup_cid_code(0xD040, 2), Some(7094));
1600        assert_eq!(cmap.lookup_cid_code(0xF9FE, 2), Some(14056 + 0xFE - 0xD6));
1601    }
1602}