1use super::CidCmap;
11use std::sync::{Arc, OnceLock};
12
13#[cfg(feature = "predefined-cmaps")]
15#[derive(Clone, Copy)]
16enum Collection {
17 Japan1,
18 Gb1,
19 Cns1,
20 Korea1,
21 Kr,
22}
23
24#[cfg(feature = "predefined-cmaps")]
27fn collection_of(name: &str) -> Option<Collection> {
28 match name {
29 "83pv-RKSJ-H" | "90ms-RKSJ-H" | "90ms-RKSJ-V" | "90msp-RKSJ-H" | "90msp-RKSJ-V"
30 | "90pv-RKSJ-H" | "Add-RKSJ-H" | "Add-RKSJ-V" | "EUC-H" | "EUC-V" | "Ext-RKSJ-H"
31 | "Ext-RKSJ-V" | "H" | "V" | "UniJIS-UCS2-H" | "UniJIS-UCS2-V" | "UniJIS-UCS2-HW-H"
32 | "UniJIS-UCS2-HW-V" | "UniJIS-UTF16-H" | "UniJIS-UTF16-V" => Some(Collection::Japan1),
33 "GB-EUC-H" | "GB-EUC-V" | "GBpc-EUC-H" | "GBpc-EUC-V" | "GBK-EUC-H" | "GBK-EUC-V"
34 | "GBKp-EUC-H" | "GBKp-EUC-V" | "GBK2K-H" | "GBK2K-V" | "UniGB-UCS2-H" | "UniGB-UCS2-V"
35 | "UniGB-UTF16-H" | "UniGB-UTF16-V" => Some(Collection::Gb1),
36 "B5pc-H" | "B5pc-V" | "HKscs-B5-H" | "HKscs-B5-V" | "ETen-B5-H" | "ETen-B5-V"
37 | "ETenms-B5-H" | "ETenms-B5-V" | "CNS-EUC-H" | "CNS-EUC-V" | "UniCNS-UCS2-H"
38 | "UniCNS-UCS2-V" | "UniCNS-UTF16-H" | "UniCNS-UTF16-V" => Some(Collection::Cns1),
39 "KSC-EUC-H" | "KSC-EUC-V" | "KSCms-UHC-H" | "KSCms-UHC-V" | "KSCms-UHC-HW-H"
40 | "KSCms-UHC-HW-V" | "KSCpc-EUC-H" | "UniKS-UCS2-H" | "UniKS-UCS2-V" | "UniKS-UTF16-H"
41 | "UniKS-UTF16-V" => Some(Collection::Korea1),
42 "UniAKR-UTF16-H" => Some(Collection::Kr),
43 _ => None,
44 }
45}
46
47pub fn predefined(name: &str) -> Option<Arc<CidCmap>> {
51 match name {
52 "Identity-H" => Some(Arc::clone(identity(false))),
53 "Identity-V" => Some(Arc::clone(identity(true))),
54 _ => load(name, 0),
55 }
56}
57
58fn identity(vertical: bool) -> &'static Arc<CidCmap> {
59 static H: OnceLock<Arc<CidCmap>> = OnceLock::new();
60 static V: OnceLock<Arc<CidCmap>> = OnceLock::new();
61 let cell = if vertical { &V } else { &H };
62 cell.get_or_init(|| Arc::new(CidCmap::identity(vertical)))
63}
64
65pub struct CidToUnicode {
71 map: crate::hash::FastMap<u32, char>,
72}
73
74impl CidToUnicode {
75 pub fn lookup(&self, cid: u32) -> Option<char> {
77 self.map.get(&cid).copied()
78 }
79}
80
81pub fn cid_to_unicode(ordering: &str) -> Option<Arc<CidToUnicode>> {
89 let (slot, names): (usize, &[&str]) = match ordering {
90 "Japan1" => (0, &["UniJIS-UTF16-H", "UniJIS-UCS2-HW-H"]),
91 "GB1" => (1, &["UniGB-UTF16-H"]),
92 "CNS1" => (2, &["UniCNS-UTF16-H"]),
93 "Korea1" => (3, &["UniKS-UTF16-H"]),
94 "KR" => (4, &["UniAKR-UTF16-H"]),
95 _ => return None,
96 };
97 static INVERSES: [OnceLock<Option<Arc<CidToUnicode>>>; 5] = [const { OnceLock::new() }; 5];
98 INVERSES[slot]
99 .get_or_init(|| {
100 let cmaps: Vec<_> = names.iter().filter_map(|n| predefined(n)).collect();
101 (!cmaps.is_empty()).then(|| Arc::new(invert(&cmaps)))
102 })
103 .clone()
104}
105
106fn invert(cmaps: &[Arc<CidCmap>]) -> CidToUnicode {
113 let radical = |c: char| ('\u{2E80}'..='\u{2FDF}').contains(&c);
114 let mut map = crate::hash::FastMap::default();
115 for cmap in cmaps {
116 cmap.mappings(&mut |len, lo, hi, cid| {
117 for offset in 0..=hi.saturating_sub(lo) {
118 let Some(c) = unicode_of(lo + offset, len) else {
119 continue;
120 };
121 map.entry(cid.saturating_add(offset))
122 .and_modify(|held| {
123 if radical(*held) && !radical(c) {
124 *held = c;
125 }
126 })
127 .or_insert(c);
128 }
129 });
130 }
131 CidToUnicode { map }
132}
133
134fn unicode_of(code: u32, len: u8) -> Option<char> {
137 if len != 4 {
138 return char::from_u32(code);
139 }
140 let units = [(code >> 16) as u16, code as u16];
141 char::decode_utf16(units).next()?.ok()
142}
143
144#[cfg(feature = "predefined-cmaps")]
145mod packed {
146 use super::{collection_of, Collection};
147 use crate::cmap::CidCmap;
148 use crate::hash::FastMap;
149 use std::io::Read;
150 use std::ops::Range;
151 use std::sync::{Arc, Mutex, OnceLock};
152
153 static BLOBS: [&[u8]; 5] = [
154 include_bytes!("../../assets/cmaps/adobe-japan1.bin"),
155 include_bytes!("../../assets/cmaps/adobe-gb1.bin"),
156 include_bytes!("../../assets/cmaps/adobe-cns1.bin"),
157 include_bytes!("../../assets/cmaps/adobe-korea1.bin"),
158 include_bytes!("../../assets/cmaps/adobe-kr.bin"),
159 ];
160
161 struct Archive {
164 data: Vec<u8>,
165 index: FastMap<String, Range<usize>>,
166 }
167
168 fn archive(collection: Collection) -> Option<&'static Archive> {
172 static ARCHIVES: [OnceLock<Option<Archive>>; 5] = [const { OnceLock::new() }; 5];
173 ARCHIVES[collection as usize]
174 .get_or_init(|| unpack(BLOBS[collection as usize]))
175 .as_ref()
176 }
177
178 fn unpack(blob: &[u8]) -> Option<Archive> {
182 let mut data = Vec::new();
183 flate2::read::ZlibDecoder::new(blob)
184 .read_to_end(&mut data)
185 .ok()?;
186 let count = u32::from_le_bytes(data.get(..4)?.try_into().ok()?) as usize;
187 let mut pos = 4;
188 let mut sizes = Vec::with_capacity(count);
189 for _ in 0..count {
190 let name_len = u16::from_le_bytes(data.get(pos..pos + 2)?.try_into().ok()?) as usize;
191 let name = String::from_utf8(data.get(pos + 2..pos + 2 + name_len)?.to_vec()).ok()?;
192 pos += 2 + name_len;
193 let size = u32::from_le_bytes(data.get(pos..pos + 4)?.try_into().ok()?) as usize;
194 pos += 4;
195 sizes.push((name, size));
196 }
197 let mut index = FastMap::default();
198 for (name, size) in sizes {
199 let end = pos.checked_add(size)?;
200 data.get(pos..end)?;
201 index.insert(name, pos..end);
202 pos = end;
203 }
204 Some(Archive { data, index })
205 }
206
207 pub fn load(name: &str, depth: usize) -> Option<Arc<CidCmap>> {
211 if depth > 4 {
212 return None;
213 }
214 let collection = collection_of(name)?;
215 static PARSED: OnceLock<Mutex<FastMap<String, Arc<CidCmap>>>> = OnceLock::new();
216 let cache = PARSED.get_or_init(|| Mutex::new(FastMap::default()));
217 if let Some(cmap) = cache.lock().unwrap().get(name) {
218 return Some(Arc::clone(cmap));
219 }
220 let archive = archive(collection)?;
221 let span = archive.index.get(name)?.clone();
222 let mut resolve = |n: &str| super::predefined_at(n, depth + 1);
223 let cmap = Arc::new(CidCmap::parse_with(&archive.data[span], None, &mut resolve));
224 cache
225 .lock()
226 .unwrap()
227 .insert(name.to_owned(), Arc::clone(&cmap));
228 Some(cmap)
229 }
230}
231
232#[cfg(feature = "predefined-cmaps")]
233use packed::load;
234
235#[cfg(not(feature = "predefined-cmaps"))]
236fn load(_name: &str, _depth: usize) -> Option<Arc<CidCmap>> {
237 None
238}
239
240#[cfg(feature = "predefined-cmaps")]
242fn predefined_at(name: &str, depth: usize) -> Option<Arc<CidCmap>> {
243 match name {
244 "Identity-H" => Some(Arc::clone(identity(false))),
245 "Identity-V" => Some(Arc::clone(identity(true))),
246 _ => load(name, depth),
247 }
248}
249
250#[cfg(all(test, feature = "predefined-cmaps"))]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn rksj_h_splits_and_maps_hiragana() {
256 let c = predefined("90ms-RKSJ-H").unwrap();
257 assert!(!c.vertical());
258 let bytes = [0x41, 0x82, 0xA0];
261 assert_eq!(c.code_at(&bytes, 0), (0x41, 1));
262 assert_eq!(c.code_at(&bytes, 1), (0x82A0, 2));
263 assert_eq!(c.cid(0x82A0, 2), Some(843));
264 assert!(c.single_byte(0x20));
265 }
266
267 #[test]
268 fn rksj_v_layers_vertical_variants_over_the_h_base() {
269 let v = predefined("90ms-RKSJ-V").unwrap();
270 assert!(v.vertical());
271 assert_eq!(v.cid(0x8141, 2), Some(7887));
273 assert_eq!(v.cid(0x82A0, 2), Some(843));
275 let h = v.parent().expect("usecmap parent");
276 assert_eq!(h.cid(0x8141, 2), Some(634));
277 }
278
279 #[test]
280 fn every_shipped_name_loads_and_identity_needs_no_data() {
281 for name in [
282 "83pv-RKSJ-H",
283 "90msp-RKSJ-V",
284 "90pv-RKSJ-H",
285 "Add-RKSJ-V",
286 "EUC-H",
287 "Ext-RKSJ-V",
288 "H",
289 "V",
290 "UniJIS-UCS2-H",
291 "UniJIS-UCS2-HW-V",
292 "UniJIS-UTF16-V",
293 "GB-EUC-V",
294 "GBpc-EUC-H",
295 "GBK-EUC-H",
296 "GBKp-EUC-V",
297 "GBK2K-H",
298 "UniGB-UCS2-V",
299 "UniGB-UTF16-H",
300 "B5pc-V",
301 "HKscs-B5-H",
302 "ETen-B5-V",
303 "ETenms-B5-H",
304 "CNS-EUC-H",
305 "UniCNS-UCS2-V",
306 "UniCNS-UTF16-H",
307 "KSC-EUC-H",
308 "KSCms-UHC-V",
309 "KSCms-UHC-HW-H",
310 "KSCpc-EUC-H",
311 "UniKS-UCS2-V",
312 "UniKS-UTF16-H",
313 "UniAKR-UTF16-H",
314 ] {
315 let c = predefined(name).unwrap_or_else(|| panic!("{name} must load"));
316 assert!(!c.is_empty(), "{name} parsed to nothing");
317 assert_eq!(c.vertical(), name.ends_with("-V") || name == "V", "{name}");
318 }
319 assert!(predefined("Identity-H").is_some());
320 assert!(predefined("Identity-V").unwrap().vertical());
321 assert!(predefined("UniJIS2004-UTF16-H").is_none()); assert!(predefined("WinAnsiEncoding").is_none());
323 }
324
325 #[test]
326 fn japan1_cids_read_back_as_unicode() {
327 let inv = cid_to_unicode("Japan1").unwrap();
328 assert_eq!(inv.lookup(843), Some('あ'));
329 assert_eq!(inv.lookup(1), Some(' '));
331 assert_eq!(inv.lookup(248), Some('1'));
334 assert_eq!(inv.lookup(3814), Some('木'));
337 assert!(cid_to_unicode("Identity").is_none());
338 assert!(cid_to_unicode("Unknown").is_none());
339 }
340}