Skip to main content

pdfboss_core/cmap/
mod.rs

1//! CID CMap parsing and code splitting for composite (Type0) fonts
2//! (ISO 32000-1 §9.7.5-9.7.6): `begincodespacerange` drives variable-width
3//! code splitting, `begincidrange`/`begincidchar` map codes to CIDs,
4//! `usecmap` layers a CMap over another, and `/WMode` selects the writing
5//! mode. The value domain is codes to CID integers — distinct from the
6//! ToUnicode CMaps parsed in `pdfboss-text`, whose destinations are text.
7
8/// Covers ISO 32000-1 §9.7.6.2.
9mod predefined;
10
11pub use predefined::{cid_to_unicode, predefined, CidToUnicode};
12
13use crate::document::decoded_stream_data_with;
14use crate::hash::FastMap;
15use crate::lexer::{decode_hex, decode_hex_fixed, Lexer, RawToken, Token};
16use crate::object::{Dict, Object, Stream};
17use crate::source::AsyncObjectSource;
18use std::sync::Arc;
19
20/// One `begincodespacerange` entry: byte-wise lower and upper bounds for
21/// codes of `len` bytes (ISO 32000-1 §9.7.6.2 — a code matches when every
22/// byte lies within the bounds at its position, not when the folded value
23/// does).
24///
25/// Covers ISO 32000-1 §9.7.5.1.
26#[derive(Clone, Copy)]
27struct Codespace {
28    len: u8,
29    lo: [u8; 4],
30    hi: [u8; 4],
31}
32
33impl Codespace {
34    fn contains(&self, code: &[u8]) -> bool {
35        code.iter()
36            .zip(self.lo.iter().zip(&self.hi))
37            .all(|(&b, (&lo, &hi))| lo <= b && b <= hi)
38    }
39}
40
41/// One `begincidrange` (or `beginnotdefrange`) entry: codes of `len` bytes
42/// from `lo` to `hi` map to consecutive CIDs from `cid`.
43#[derive(Clone, Copy)]
44struct CidRange {
45    len: u8,
46    lo: u32,
47    hi: u32,
48    cid: u32,
49}
50
51/// A parsed CID CMap. Parsing is lenient: unrecognized tokens are skipped
52/// and malformed sections contribute what they parsed so far, so
53/// [`CidCmap::parse`] never fails.
54pub struct CidCmap {
55    wmode: u8,
56    /// Own plus inherited codespaces, sorted by length so the shortest
57    /// match wins at [`CidCmap::code_at`].
58    codespaces: Vec<Codespace>,
59    /// `begincidchar` singletons, keyed by (byte length, code value).
60    singles: FastMap<(u8, u32), u32>,
61    /// `begincidrange` entries, sorted by (length, low) for binary search.
62    ranges: Vec<CidRange>,
63    /// `beginnotdefrange`/`beginnotdefchar` entries, consulted only after
64    /// every real mapping in the chain has missed.
65    notdefs: Vec<CidRange>,
66    /// The `usecmap` layer underneath: any mapping here loses to any
67    /// mapping above, exactly the child-overrides-parent the operator asks.
68    parent: Option<Arc<CidCmap>>,
69}
70
71/// Folds up to the last 4 bytes of a code, big-endian.
72fn code_value(bytes: &[u8]) -> u32 {
73    bytes.iter().fold(0u32, |acc, &b| (acc << 8) | u32::from(b))
74}
75
76/// The range covering `code` within `ranges` (sorted by `(len, lo)`), if any.
77fn covering_range(ranges: &[CidRange], code: u32, len: u8) -> Option<&CidRange> {
78    let idx = ranges.partition_point(|r| (r.len, r.lo) <= (len, code));
79    let r = ranges.get(idx.checked_sub(1)?)?;
80    (r.len == len && code <= r.hi).then_some(r)
81}
82
83impl CidCmap {
84    /// The Identity mapping (ISO 32000-1 Table 118, `Identity-H`/`-V`):
85    /// 2-byte codes, CID == code.
86    pub fn identity(vertical: bool) -> CidCmap {
87        CidCmap {
88            wmode: u8::from(vertical),
89            codespaces: vec![Codespace {
90                len: 2,
91                lo: [0; 4],
92                hi: [0xFF; 4],
93            }],
94            singles: FastMap::default(),
95            ranges: vec![CidRange {
96                len: 2,
97                lo: 0,
98                hi: 0xFFFF,
99                cid: 0,
100            }],
101            notdefs: Vec::new(),
102            parent: None,
103        }
104    }
105
106    /// Parses a decoded CMap with no way to resolve `usecmap` names.
107    pub fn parse(data: &[u8]) -> CidCmap {
108        CidCmap::parse_with(data, None, &mut |_| None)
109    }
110
111    /// Parses a decoded CMap. `parent` is the layer named by the stream
112    /// dictionary's `/UseCMap`, if any; an in-content `usecmap` operator
113    /// resolves through `resolve` and fills the parent slot only when it is
114    /// still empty (a CMap has one parent).
115    ///
116    /// Covers ISO 32000-1 §9.7.5.4.
117    pub fn parse_with(
118        data: &[u8],
119        parent: Option<Arc<CidCmap>>,
120        resolve: &mut dyn FnMut(&str) -> Option<Arc<CidCmap>>,
121    ) -> CidCmap {
122        let mut out = CidCmap {
123            wmode: 0,
124            codespaces: Vec::new(),
125            singles: FastMap::default(),
126            ranges: Vec::new(),
127            notdefs: Vec::new(),
128            parent,
129        };
130        let mut lx = Lexer::new(data);
131        let mut pending_name: Option<String> = None;
132        let mut wmode_pending = false;
133        loop {
134            match next_or_skip(&mut lx, data.len()) {
135                None => break,
136                Some(RawToken::Keyword(kw)) => {
137                    match kw {
138                        b"begincodespacerange" => out.parse_codespaces(&mut lx, data.len()),
139                        b"begincidchar" => out.parse_cidchars(&mut lx, data.len(), false),
140                        b"begincidrange" => out.parse_cidranges(&mut lx, data.len(), false),
141                        b"beginnotdefchar" => out.parse_cidchars(&mut lx, data.len(), true),
142                        b"beginnotdefrange" => out.parse_cidranges(&mut lx, data.len(), true),
143                        b"usecmap" => {
144                            if let (None, Some(name)) = (&out.parent, pending_name.take()) {
145                                out.parent = resolve(&name);
146                            }
147                        }
148                        _ => {}
149                    }
150                    pending_name = None;
151                    wmode_pending = false;
152                }
153                Some(RawToken::Owned(Token::Name(n))) => {
154                    wmode_pending = n.0 == "WMode";
155                    pending_name = (!wmode_pending).then_some(n.0);
156                }
157                Some(RawToken::Owned(Token::Int(i))) => {
158                    if wmode_pending {
159                        out.wmode = u8::from(i == 1);
160                    }
161                    wmode_pending = false;
162                }
163                Some(_) => {
164                    pending_name = None;
165                    wmode_pending = false;
166                }
167            }
168        }
169        out.finish();
170        out
171    }
172
173    /// Sorts the lookup tables and folds the parent's codespaces in, so
174    /// splitting sees the union while CID lookups stay layered.
175    fn finish(&mut self) {
176        if let Some(parent) = &self.parent {
177            self.codespaces.extend_from_slice(&parent.codespaces);
178        }
179        self.codespaces.sort_by_key(|c| c.len);
180        self.ranges.sort_by_key(|r| (r.len, r.lo));
181        self.notdefs.sort_by_key(|r| (r.len, r.lo));
182    }
183
184    /// True when nothing at all was mapped — the caller's cue to treat an
185    /// embedded CMap stream as unreadable rather than as "maps everything
186    /// to CID 0".
187    pub fn is_empty(&self) -> bool {
188        self.singles.is_empty() && self.ranges.is_empty() && self.parent.is_none()
189    }
190
191    /// Writing mode: true for vertical (`/WMode 1`).
192    pub fn vertical(&self) -> bool {
193        self.wmode == 1
194    }
195
196    /// The `usecmap` layer underneath, if any. A vertical CMap's parent is
197    /// its horizontal base, whose CIDs name the unrotated glyphs — the ones
198    /// a CID-to-Unicode inversion actually knows.
199    pub fn parent(&self) -> Option<&Arc<CidCmap>> {
200        self.parent.as_ref()
201    }
202
203    /// True when the codespaces read single byte `b` as a complete code —
204    /// the ISO 32000-1 §9.3.3 condition for word spacing to apply to a
205    /// composite font's code 32.
206    pub fn single_byte(&self, b: u8) -> bool {
207        self.codespaces
208            .iter()
209            .any(|c| c.len == 1 && c.contains(&[b]))
210    }
211
212    /// Splits the next code from `bytes` at `pos` (which must be in
213    /// bounds), returning `(value, length)`. The shortest codespace range
214    /// that matches byte-wise wins; when none matches fully, the shortest
215    /// range whose first byte fits still decides the length (the code maps
216    /// to nothing and will read as notdef); when even that fails, one raw
217    /// byte is consumed. Always consumes at least one byte. With no
218    /// codespaces at all, codes are two bytes — the Type0 default this
219    /// module's callers otherwise assume.
220    ///
221    /// Covers ISO 32000-1 §9.7.5.1, §9.7.6.2 and §9.7.6.3.
222    pub fn code_at(&self, bytes: &[u8], pos: usize) -> (u32, u8) {
223        let rest = &bytes[pos..];
224        if self.codespaces.is_empty() {
225            let n = rest.len().min(2);
226            return (code_value(&rest[..n]), n as u8);
227        }
228        for cs in &self.codespaces {
229            let n = usize::from(cs.len);
230            if rest.len() >= n && cs.contains(&rest[..n]) {
231                return (code_value(&rest[..n]), cs.len);
232            }
233        }
234        for cs in &self.codespaces {
235            if cs.lo[0] <= rest[0] && rest[0] <= cs.hi[0] {
236                let n = usize::from(cs.len).min(rest.len());
237                return (code_value(&rest[..n]), n as u8);
238            }
239        }
240        (u32::from(rest[0]), 1)
241    }
242
243    /// The CID for a code of `len` bytes: this layer's `cidchar` singletons,
244    /// then its `cidrange`s, then the parent chain, and only after every
245    /// real mapping missed, the notdef entries. `None` reads as CID 0.
246    pub fn cid(&self, code: u32, len: u8) -> Option<u32> {
247        self.mapped(code, len).or_else(|| self.notdef(code, len))
248    }
249
250    fn mapped(&self, code: u32, len: u8) -> Option<u32> {
251        if let Some(&cid) = self.singles.get(&(len, code)) {
252            return Some(cid);
253        }
254        // Consecutive CIDs across the range (ISO 32000-1 §9.7.6.3).
255        if let Some(r) = covering_range(&self.ranges, code, len) {
256            return Some(r.cid.saturating_add(code - r.lo));
257        }
258        self.parent.as_ref()?.mapped(code, len)
259    }
260
261    // Every code of a notdef range maps to the one stated CID.
262    fn notdef(&self, code: u32, len: u8) -> Option<u32> {
263        covering_range(&self.notdefs, code, len)
264            .map(|r| r.cid)
265            .or_else(|| self.parent.as_ref()?.notdef(code, len))
266    }
267
268    /// Feeds every real mapping in the chain to `push` as
269    /// `(len, lo, hi, first_cid)`, shallowest layer first and ascending by
270    /// code within a layer — the iteration order that lets an inversion
271    /// keep the lowest code for each CID.
272    fn mappings(&self, push: &mut impl FnMut(u8, u32, u32, u32)) {
273        let mut singles: Vec<(u8, u32, u32)> = self
274            .singles
275            .iter()
276            .map(|(&(len, code), &cid)| (len, code, cid))
277            .collect();
278        singles.sort_unstable();
279        let mut singles = singles.into_iter().peekable();
280        let mut ranges = self.ranges.iter().peekable();
281        loop {
282            let single_first = match (singles.peek(), ranges.peek()) {
283                (None, None) => break,
284                (Some(_), None) => true,
285                (None, Some(_)) => false,
286                (Some(&(len, code, _)), Some(r)) => (len, code) <= (r.len, r.lo),
287            };
288            if single_first {
289                let (len, code, cid) = singles.next().unwrap();
290                push(len, code, code, cid);
291            } else {
292                let r = ranges.next().unwrap();
293                push(r.len, r.lo, r.hi, r.cid);
294            }
295        }
296        if let Some(parent) = &self.parent {
297            parent.mappings(push);
298        }
299    }
300
301    /// Reads `<lo> <hi>` pairs until `endcodespacerange`.
302    fn parse_codespaces(&mut self, lx: &mut Lexer<'_>, len: usize) {
303        loop {
304            let lo = match next_or_skip(lx, len) {
305                Some(RawToken::Hex(span)) => span,
306                Some(_) | None => return, // `endcodespacerange` or junk
307            };
308            let Some(RawToken::Hex(hi)) = next_or_skip(lx, len) else {
309                return;
310            };
311            // Over-long or empty codes are skipped, exactly like the
312            // over-long check the owned form made after decoding.
313            let (Some((lo, lo_len)), Some((hi, hi_len))) =
314                (decode_hex_fixed::<4>(lo), decode_hex_fixed::<4>(hi))
315            else {
316                continue;
317            };
318            if lo_len == 0 || hi_len != lo_len {
319                continue;
320            }
321            self.codespaces.push(Codespace {
322                len: lo_len as u8,
323                lo,
324                hi,
325            });
326        }
327    }
328
329    /// Reads `<code> cid` pairs until `endcidchar`/`endnotdefchar`.
330    fn parse_cidchars(&mut self, lx: &mut Lexer<'_>, len: usize, notdef: bool) {
331        loop {
332            let code = match next_or_skip(lx, len) {
333                Some(RawToken::Hex(span)) => span,
334                Some(_) | None => return,
335            };
336            let Some(RawToken::Owned(Token::Int(cid))) = next_or_skip(lx, len) else {
337                return;
338            };
339            let Some((code, code_len)) = decode_hex_fixed::<4>(code) else {
340                continue;
341            };
342            if code_len == 0 {
343                continue;
344            }
345            let (value, width) = (code_value(&code[..code_len]), code_len as u8);
346            let cid = cid.max(0) as u32;
347            if notdef {
348                self.notdefs.push(CidRange {
349                    len: width,
350                    lo: value,
351                    hi: value,
352                    cid,
353                });
354            } else {
355                self.singles.insert((width, value), cid);
356            }
357        }
358    }
359
360    /// Reads `<lo> <hi> cid` triples until `endcidrange`/`endnotdefrange`.
361    fn parse_cidranges(&mut self, lx: &mut Lexer<'_>, len: usize, notdef: bool) {
362        loop {
363            let lo = match next_or_skip(lx, len) {
364                Some(RawToken::Hex(span)) => span,
365                Some(_) | None => return,
366            };
367            let Some(RawToken::Hex(hi)) = next_or_skip(lx, len) else {
368                return;
369            };
370            let Some(RawToken::Owned(Token::Int(cid))) = next_or_skip(lx, len) else {
371                return;
372            };
373            let Some((lo, lo_len)) = decode_hex_fixed::<4>(lo) else {
374                continue;
375            };
376            if lo_len == 0 {
377                continue;
378            }
379            // An over-long `hi` still folds through all its bytes, exactly
380            // as the owned form's fold did (the low 32 bits win).
381            let hi_v = match decode_hex_fixed::<4>(hi) {
382                Some((hi, hi_len)) => code_value(&hi[..hi_len]),
383                None => code_value(&decode_hex(hi)),
384            };
385            let lo_v = code_value(&lo[..lo_len]);
386            if hi_v < lo_v {
387                continue;
388            }
389            let range = CidRange {
390                len: lo_len as u8,
391                lo: lo_v,
392                hi: hi_v,
393                cid: cid.max(0) as u32,
394            };
395            if notdef {
396                self.notdefs.push(range);
397            } else {
398                self.ranges.push(range);
399            }
400        }
401    }
402}
403
404/// How a Type0 font's `/Encoding` maps show-string bytes to CIDs.
405pub struct Type0Encoding {
406    /// The CMap when one resolved; `None` means the Identity assumption
407    /// (2-byte codes, CID == code) — either stated (`/Identity-H`/`-V`) or
408    /// the fallback for anything unresolvable.
409    pub cmap: Option<Arc<CidCmap>>,
410    /// Writing mode 1 (top-to-bottom).
411    pub vertical: bool,
412    /// False when `/Encoding` named a CMap that could not be resolved (or
413    /// was absent), so the Identity fallback is a guess rather than what
414    /// the file states.
415    pub known: bool,
416}
417
418/// Resolves `dict[key]`, treating resolution failures and `null` as absent.
419async fn rv<S: AsyncObjectSource>(src: &S, dict: &Dict, key: &str) -> Option<Object> {
420    let obj = dict.get(key)?;
421    let resolved = src.resolve(obj).await.ok()?;
422    (!resolved.is_null()).then_some(resolved)
423}
424
425/// Reads a Type0 font dictionary's `/Encoding` (ISO 32000-1 §9.7.5): the
426/// two Identity names map straight through; any other name resolves via
427/// [`predefined`]; a stream is parsed as an embedded CMap, its dictionary's
428/// `/UseCMap` chain (streams or predefined names, bounded depth) layered
429/// underneath and its `/WMode` overriding the content's. Whatever fails
430/// resolves to the Identity assumption with `known` false.
431///
432/// Covers ISO 32000-1 §9.7.5.3.
433pub async fn type0_encoding<S: AsyncObjectSource>(src: &S, font: &Dict) -> Type0Encoding {
434    let identity = |vertical: bool, known: bool| Type0Encoding {
435        cmap: None,
436        vertical,
437        known,
438    };
439    let Some(enc) = rv(src, font, "Encoding").await else {
440        return identity(false, false);
441    };
442    match enc {
443        Object::Name(n) if n.0 == "Identity-H" => identity(false, true),
444        Object::Name(n) if n.0 == "Identity-V" => identity(true, true),
445        Object::Name(n) => match predefined(&n.0) {
446            Some(cmap) => Type0Encoding {
447                vertical: cmap.vertical(),
448                cmap: Some(cmap),
449                known: true,
450            },
451            None => identity(n.0.ends_with("-V"), false),
452        },
453        Object::Stream(stream) => match embedded_cmap(src, &stream).await {
454            Some(cmap) => Type0Encoding {
455                vertical: cmap.vertical(),
456                cmap: Some(cmap),
457                known: true,
458            },
459            None => identity(false, false),
460        },
461        _ => identity(false, false),
462    }
463}
464
465/// Parses an embedded CMap stream with its `/UseCMap` ancestry. `None` when
466/// the stream will not read or parses to nothing.
467///
468/// Covers ISO 32000-1 §9.7.5.3.
469async fn embedded_cmap<S: AsyncObjectSource>(src: &S, stream: &Stream) -> Option<Arc<CidCmap>> {
470    // Walk the /UseCMap chain outward first (bounded), then parse from the
471    // deepest layer up so each child wraps its parent.
472    let mut layers: Vec<(Vec<u8>, Option<i64>)> = Vec::new();
473    let mut parent: Option<Arc<CidCmap>> = None;
474    let mut current = stream.clone();
475    for _ in 0..4 {
476        // Through the checked fetch: a CMap labelled with an image codec is
477        // a passthrough codestream, refused rather than token-scanned.
478        let data = decoded_stream_data_with(src, &current).await.ok()?;
479        let wmode = rv(src, &current.dict, "WMode")
480            .await
481            .and_then(|o| o.as_int());
482        layers.push((data, wmode));
483        match rv(src, &current.dict, "UseCMap").await {
484            Some(Object::Name(n)) => {
485                parent = predefined(&n.0);
486                break;
487            }
488            Some(Object::Stream(s)) => current = s,
489            _ => break,
490        }
491    }
492    for (data, wmode) in layers.into_iter().rev() {
493        let mut resolve = |n: &str| predefined(n);
494        let mut cmap = CidCmap::parse_with(&data, parent.take(), &mut resolve);
495        if let Some(w) = wmode {
496            cmap.wmode = u8::from(w == 1);
497        }
498        parent = Some(Arc::new(cmap));
499    }
500    parent.filter(|c| !c.is_empty())
501}
502
503/// Fetches the next token, force-advancing past unlexable bytes; `None` at
504/// end of input.
505fn next_or_skip<'a>(lx: &mut Lexer<'a>, len: usize) -> Option<RawToken<'a>> {
506    loop {
507        let before = lx.pos();
508        match lx.next_raw_token() {
509            Ok(RawToken::Owned(Token::Eof)) => return None,
510            Ok(t) => return Some(t),
511            Err(_) => {
512                if lx.pos() <= before {
513                    if before + 1 >= len {
514                        return None;
515                    }
516                    lx.seek(before + 1);
517                }
518            }
519        }
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526
527    /// The codespace block of Adobe's 90ms-RKSJ-H, transcribed from the
528    /// BSD-licensed data file: 1-byte and 2-byte ranges interleaved.
529    const RKSJ_CODESPACES: &str = "4 begincodespacerange\n\
530         <00>   <80>\n\
531         <8140> <9FFC>\n\
532         <A0>   <DF>\n\
533         <E040> <FCFC>\n\
534         endcodespacerange\n";
535
536    fn rksj() -> CidCmap {
537        let data = format!(
538            "{RKSJ_CODESPACES}\
539             1 beginnotdefrange\n<00> <1f> 231\nendnotdefrange\n\
540             3 begincidrange\n\
541             <20> <7d> 231\n\
542             <8140> <817e> 633\n\
543             <e040> <e07e> 100\n\
544             endcidrange\n\
545             1 begincidchar\n<a1> 9000\nendcidchar\n"
546        );
547        CidCmap::parse(data.as_bytes())
548    }
549
550    // Covers ISO 32000-1 §9.7.5.1 and §9.7.6.2.
551    #[test]
552    fn rksj_codespaces_split_mixed_widths() {
553        let c = rksj();
554        // 1-byte ASCII, then a 2-byte code, then 1-byte katakana.
555        let bytes = [0x41, 0x81, 0x40, 0xA1];
556        assert_eq!(c.code_at(&bytes, 0), (0x41, 1));
557        assert_eq!(c.code_at(&bytes, 1), (0x8140, 2));
558        assert_eq!(c.code_at(&bytes, 3), (0xA1, 1));
559    }
560
561    // Covers ISO 32000-1 §9.7.5.4.
562    #[test]
563    fn cidrange_arithmetic_offsets_within_the_range() {
564        let c = rksj();
565        assert_eq!(c.cid(0x20, 1), Some(231));
566        assert_eq!(c.cid(0x7d, 1), Some(324));
567        assert_eq!(c.cid(0x8140, 2), Some(633));
568        assert_eq!(c.cid(0x8163, 2), Some(633 + 0x23));
569        assert_eq!(c.cid(0xe041, 2), Some(101));
570        assert_eq!(c.cid(0x82FF, 2), None);
571    }
572
573    // Covers ISO 32000-1 §9.7.5.4.
574    #[test]
575    fn cidchar_singletons_map() {
576        let c = rksj();
577        assert_eq!(c.cid(0xA1, 1), Some(9000));
578    }
579
580    // Covers ISO 32000-1 §9.7.5.4 and §9.7.6.3.
581    #[test]
582    fn notdef_ranges_lose_to_real_mappings() {
583        let data = format!(
584            "{RKSJ_CODESPACES}\
585             1 beginnotdefrange\n<00> <1f> 231\nendnotdefrange\n\
586             1 begincidrange\n<10> <11> 5\nendcidrange\n"
587        );
588        let c = CidCmap::parse(data.as_bytes());
589        assert_eq!(c.cid(0x10, 1), Some(5)); // real mapping wins
590        assert_eq!(c.cid(0x12, 1), Some(231)); // notdef fills the rest
591        assert_eq!(c.cid(0x20, 1), None);
592    }
593
594    // Covers ISO 32000-1 §9.7.6.2.
595    #[test]
596    fn a_one_byte_code_and_a_two_byte_code_with_equal_values_stay_apart() {
597        let data = "2 begincodespacerange <00> <20> <4000> <41FF> endcodespacerange\n\
598                    2 begincidrange <20> <20> 7 <0020> <0020> 9 endcidrange";
599        let c = CidCmap::parse(data.as_bytes());
600        assert_eq!(c.cid(0x20, 1), Some(7));
601        assert_eq!(c.cid(0x20, 2), Some(9));
602    }
603
604    // Covers ISO 32000-1 §9.7.5.3.
605    #[test]
606    fn usecmap_layers_child_over_parent() {
607        let parent = Arc::new(CidCmap::parse(
608            format!(
609                "{RKSJ_CODESPACES}\
610                 2 begincidrange <8140> <817e> 633 <20> <7d> 231 endcidrange"
611            )
612            .as_bytes(),
613        ));
614        let mut resolve = |name: &str| (name == "90ms-RKSJ-H").then(|| Arc::clone(&parent));
615        let child = CidCmap::parse_with(
616            b"/90ms-RKSJ-H usecmap\n\
617              /WMode 1 def\n\
618              1 begincidrange <8141> <8142> 7887 endcidrange",
619            None,
620            &mut resolve,
621        );
622        assert!(child.vertical());
623        assert_eq!(child.cid(0x8141, 2), Some(7887)); // the vertical variant
624        assert_eq!(child.cid(0x8140, 2), Some(633)); // inherited
625        assert_eq!(child.cid(0x21, 1), Some(232)); // inherited
626        assert_eq!(child.code_at(&[0x81, 0x40], 0), (0x8140, 2)); // codespaces inherited
627        assert_eq!(child.parent().map(|p| p.cid(0x8141, 2)), Some(Some(634)));
628    }
629
630    // Covers ISO 32000-1 §9.7.5.1.
631    #[test]
632    fn wmode_reads_and_defaults_horizontal() {
633        assert!(!CidCmap::parse(b"/WMode 0 def").vertical());
634        assert!(CidCmap::parse(b"/WMode 1 def").vertical());
635        assert!(!CidCmap::parse(b"").vertical());
636        assert!(CidCmap::identity(true).vertical());
637    }
638
639    // Covers ISO 32000-1 §9.7.5.1.
640    #[test]
641    fn identity_maps_code_to_cid() {
642        let c = CidCmap::identity(false);
643        assert_eq!(c.code_at(&[0x12, 0x34], 0), (0x1234, 2));
644        assert_eq!(c.cid(0x1234, 2), Some(0x1234));
645        assert!(!c.single_byte(0x20));
646    }
647
648    // Covers ISO 32000-1 §9.3.3.
649    #[test]
650    fn word_spacing_evidence_is_a_one_byte_codespace() {
651        assert!(rksj().single_byte(0x20));
652        assert!(!rksj().single_byte(0x81));
653    }
654
655    /// The never-stall invariant: whatever the bytes, `code_at` consumes at
656    /// least one and never reads past the end.
657    // Covers ISO 32000-1 §9.7.6.2 and §9.7.6.3.
658    #[test]
659    fn splitting_always_consumes_at_least_one_byte() {
660        let cmaps = [rksj(), CidCmap::identity(false), CidCmap::parse(b"")];
661        for c in &cmaps {
662            for bytes in [&[0x81][..], &[0xFF][..], &[0x00, 0x81][..]] {
663                let mut pos = 0;
664                let mut codes = 0;
665                while pos < bytes.len() {
666                    let (_, n) = c.code_at(bytes, pos);
667                    assert!(n >= 1);
668                    pos += usize::from(n).min(bytes.len() - pos);
669                    codes += 1;
670                }
671                assert!(codes >= 1);
672            }
673        }
674        // A truncated 2-byte tail folds what is there.
675        assert_eq!(rksj().code_at(&[0x81], 0), (0x81, 1));
676    }
677
678    // Covers ISO 32000-1 §9.7.5.4.
679    #[test]
680    fn a_truncated_section_keeps_what_parsed_so_far() {
681        let c = CidCmap::parse(b"2 begincidrange <20> <7d> 231 <8140> <81");
682        assert_eq!(c.cid(0x20, 1), Some(231));
683        assert_eq!(c.cid(0x8140, 2), None);
684        let garbage = CidCmap::parse(b"\xFF\xFE ) ] >> begincidchar <41> 12 endcidchar");
685        assert_eq!(garbage.cid(0x41, 1), Some(12));
686        assert!(CidCmap::parse(b"").is_empty());
687    }
688}