Skip to main content

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