Skip to main content

pdfrum_cmap/
ids.rs

1//! The identifier vocabulary of CID-keyed text: character codes, CIDs, the
2//! character collection a CID belongs to, and the two ways a CMap describes
3//! how bytes become codes.
4//!
5//! The ordinals of [`CidSet`] and [`CidCoding`] are load-bearing — the static
6//! tables are addressed by `CidSet` and a file's `/Ordering` string resolves
7//! through the same numbering — so both are `#[repr(u8)]` with explicit
8//! discriminants.
9
10/// A character code: one unit of a PDF string as split by a CMap's decoder
11/// (ISO 32000-1 §9.7.5). Between 1 and 4 bytes wide depending on the coding
12/// scheme, so the value alone does not say how many bytes it came from; ask
13/// [`CMap::char_size`](crate::CMap::char_size).
14///
15/// ```
16/// use pdfrum_cmap::CharCode;
17///
18/// let code = CharCode(0x8140);
19/// assert_eq!(u32::from(code), 0x8140);
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
22pub struct CharCode(pub u32);
23
24impl From<CharCode> for u32 {
25    fn from(c: CharCode) -> Self {
26        c.0
27    }
28}
29
30impl From<u32> for CharCode {
31    fn from(v: u32) -> Self {
32        Self(v)
33    }
34}
35
36/// A character identifier: an index into a character collection, which a
37/// `CIDFont` turns into a glyph (ISO 32000-1 §9.7.4). CID 0 is `.notdef` and is
38/// also what an unmapped character code yields.
39///
40/// ```
41/// use pdfrum_cmap::Cid;
42///
43/// assert_eq!(Cid::default(), Cid(0)); // .notdef
44/// ```
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
46pub struct Cid(pub u16);
47
48impl From<Cid> for u16 {
49    fn from(c: Cid) -> Self {
50        c.0
51    }
52}
53
54impl From<u16> for Cid {
55    fn from(v: u16) -> Self {
56        Self(v)
57    }
58}
59
60/// The character collection a CID belongs to — the `/Registry`–`/Ordering`
61/// pair of a `/CIDSystemInfo` (ISO 32000-1 §9.7.3), reduced to the five
62/// collections that have built-in tables plus "none of them".
63///
64/// The discriminants index the static blob's registry directory, so they are
65/// part of the format, not an implementation detail.
66///
67/// ```
68/// use pdfrum_cmap::{CidSet, charset_from_ordering};
69///
70/// assert_eq!(charset_from_ordering(b"Japan1"), CidSet::Japan1);
71/// assert_eq!(charset_from_ordering(b"Latin1"), CidSet::Unknown);
72/// ```
73#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
74#[repr(u8)]
75pub enum CidSet {
76    /// No recognised collection: the CMap has no CID table and no CID→Unicode
77    /// table.
78    #[default]
79    Unknown = 0,
80    /// Adobe-GB1 — Simplified Chinese.
81    Gb1 = 1,
82    /// Adobe-CNS1 — Traditional Chinese.
83    Cns1 = 2,
84    /// Adobe-Japan1 — Japanese.
85    Japan1 = 3,
86    /// Adobe-Korea1 — Korean.
87    Korea1 = 4,
88    /// Adobe-Identity / `UCS`: the CID *is* the Unicode scalar value.
89    Unicode = 5,
90}
91
92impl CidSet {
93    /// The registry's ordinal, as the blob and the `/Ordering` table use it.
94    #[must_use]
95    pub fn ordinal(self) -> u8 {
96        self as u8
97    }
98
99    /// Index into the blob's four-registry directory, or `None` for the two
100    /// collections that have no static tables.
101    pub(crate) fn registry_index(self) -> Option<usize> {
102        match self {
103            Self::Gb1 => Some(0),
104            Self::Cns1 => Some(1),
105            Self::Japan1 => Some(2),
106            Self::Korea1 => Some(3),
107            Self::Unknown | Self::Unicode => None,
108        }
109    }
110}
111
112/// How a predefined CMap's character codes relate to a legacy encoding. Purely
113/// descriptive at this layer — nothing in this crate branches on it — but
114/// `pdfrum-font` uses it to pick a code page when a CID font falls back to a
115/// system face, so it is part of the CMap's observable identity.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
117#[repr(u8)]
118pub enum CidCoding {
119    /// An unrecognised `/Encoding` name, or any embedded CMap.
120    #[default]
121    Unknown = 0,
122    /// GB 2312 / GBK family.
123    Gb = 1,
124    /// Big5 family.
125    Big5 = 2,
126    /// Shift-JIS / EUC-JP family.
127    Jis = 3,
128    /// KS X 1001 / UHC family.
129    Korea = 4,
130    /// UCS-2 code points.
131    Ucs2 = 5,
132    /// `Identity-H` / `Identity-V`: the code *is* the CID.
133    Cid = 6,
134    /// UTF-16 code units.
135    Utf16 = 7,
136}
137
138/// How a byte string splits into character codes (ISO 32000-1 §9.7.6.2).
139///
140/// The default is [`TwoBytes`](CodingScheme::TwoBytes), and that default is
141/// load-bearing: an unrecognised predefined name and a CMap stream with no
142/// usable `codespacerange` both decode as fixed 2-byte codes.
143///
144/// ```
145/// use pdfrum_cmap::CodingScheme;
146///
147/// assert_eq!(CodingScheme::default(), CodingScheme::TwoBytes);
148/// ```
149#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
150pub enum CodingScheme {
151    /// Every byte is its own code.
152    OneByte,
153    /// Every code is exactly two bytes, big-endian.
154    #[default]
155    TwoBytes,
156    /// A set of leading bytes starts a two-byte code; every other byte is a
157    /// one-byte code.
158    MixedTwoBytes,
159    /// Codespace ranges decide the width of each code, one to four bytes.
160    MixedFourBytes,
161}
162
163#[cfg(test)]
164mod tests {
165    use super::{CharCode, Cid, CidCoding, CidSet, CodingScheme};
166
167    #[test]
168    fn ordinals_match_the_blob_numbering() {
169        assert_eq!(CidSet::Unknown.ordinal(), 0);
170        assert_eq!(CidSet::Gb1.ordinal(), 1);
171        assert_eq!(CidSet::Cns1.ordinal(), 2);
172        assert_eq!(CidSet::Japan1.ordinal(), 3);
173        assert_eq!(CidSet::Korea1.ordinal(), 4);
174        assert_eq!(CidSet::Unicode.ordinal(), 5);
175        assert_eq!(CidCoding::Unknown as u8, 0);
176        assert_eq!(CidCoding::Utf16 as u8, 7);
177    }
178
179    #[test]
180    fn only_the_four_cjk_registries_have_a_blob_index() {
181        assert_eq!(CidSet::Gb1.registry_index(), Some(0));
182        assert_eq!(CidSet::Korea1.registry_index(), Some(3));
183        assert_eq!(CidSet::Unicode.registry_index(), None);
184        assert_eq!(CidSet::Unknown.registry_index(), None);
185    }
186
187    #[test]
188    fn defaults_are_the_fallback_state() {
189        assert_eq!(CodingScheme::default(), CodingScheme::TwoBytes);
190        assert_eq!(CidSet::default(), CidSet::Unknown);
191        assert_eq!(CidCoding::default(), CidCoding::Unknown);
192        assert_eq!(CharCode::default().0, 0);
193        assert_eq!(Cid::default().0, 0);
194    }
195}