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}