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