1#[cfg(feature = "embed-cmaps")]
13mod bcmap;
14mod parse;
15
16#[cfg(feature = "embed-cmaps")]
17pub use bcmap::load_embedded;
18
19#[cfg(not(feature = "embed-cmaps"))]
23pub fn load_embedded(_name: CMapName<'_>) -> Option<&'static [u8]> {
24 None
25}
26
27use alloc::boxed::Box;
28use alloc::string::String;
29use alloc::vec::Vec;
30
31pub type Cid = u32;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum CMapName<'a> {
37 N83pvRksjH,
40 N90msRksjH,
42 N90msRksjV,
44 N90mspRksjH,
46 N90mspRksjV,
48 N90pvRksjH,
50 AddRksjH,
52 AddRksjV,
54
55 AdobeCns1Ucs2,
60 AdobeGb1Ucs2,
62 AdobeJapan1Ucs2,
64 AdobeKorea1Ucs2,
66
67 B5pcH,
70 B5pcV,
72 CnsEucH,
74 CnsEucV,
76 ETenB5H,
78 ETenB5V,
80 ETenmsB5H,
82 ETenmsB5V,
84
85 EucH,
88 EucV,
90 ExtRksjH,
92 ExtRksjV,
94
95 GbEucH,
98 GbEucV,
100 GbkEucH,
102 GbkEucV,
104 Gbk2kH,
106 Gbk2kV,
108 GbkpEucH,
110 GbkpEucV,
112 GbpcEucH,
114 GbpcEucV,
116
117 H,
120
121 HKscsB5H,
124 HKscsB5V,
126
127 IdentityH,
131 IdentityV,
134
135 KscEucH,
138 KscEucV,
140 KscmsUhcH,
142 KscmsUhcHwH,
144 KscmsUhcHwV,
146 KscmsUhcV,
148 KscpcEucH,
150
151 UniCnsUcs2H,
154 UniCnsUcs2V,
156 UniCnsUtf16H,
158 UniCnsUtf16V,
160
161 UniGbUcs2H,
164 UniGbUcs2V,
166 UniGbUtf16H,
168 UniGbUtf16V,
170
171 UniJisUcs2H,
174 UniJisUcs2HwH,
176 UniJisUcs2HwV,
178 UniJisUcs2V,
180 UniJisUtf16H,
182 UniJisUtf16V,
184
185 UniKsUcs2H,
188 UniKsUcs2V,
190 UniKsUtf16H,
192 UniKsUtf16V,
194
195 V,
198
199 Custom(&'a [u8]),
201}
202
203impl<'a> CMapName<'a> {
204 pub fn from_bytes(name: &'a [u8]) -> Self {
206 match name {
207 b"83pv-RKSJ-H" => Self::N83pvRksjH,
208 b"90ms-RKSJ-H" => Self::N90msRksjH,
209 b"90ms-RKSJ-V" => Self::N90msRksjV,
210 b"90msp-RKSJ-H" => Self::N90mspRksjH,
211 b"90msp-RKSJ-V" => Self::N90mspRksjV,
212 b"90pv-RKSJ-H" => Self::N90pvRksjH,
213 b"Add-RKSJ-H" => Self::AddRksjH,
214 b"Add-RKSJ-V" => Self::AddRksjV,
215 b"Adobe-CNS1-UCS2" => Self::AdobeCns1Ucs2,
216 b"Adobe-GB1-UCS2" => Self::AdobeGb1Ucs2,
217 b"Adobe-Japan1-UCS2" => Self::AdobeJapan1Ucs2,
218 b"Adobe-Korea1-UCS2" => Self::AdobeKorea1Ucs2,
219 b"B5pc-H" => Self::B5pcH,
220 b"B5pc-V" => Self::B5pcV,
221 b"CNS-EUC-H" => Self::CnsEucH,
222 b"CNS-EUC-V" => Self::CnsEucV,
223 b"ETen-B5-H" => Self::ETenB5H,
224 b"ETen-B5-V" => Self::ETenB5V,
225 b"ETenms-B5-H" => Self::ETenmsB5H,
226 b"ETenms-B5-V" => Self::ETenmsB5V,
227 b"EUC-H" => Self::EucH,
228 b"EUC-V" => Self::EucV,
229 b"Ext-RKSJ-H" => Self::ExtRksjH,
230 b"Ext-RKSJ-V" => Self::ExtRksjV,
231 b"GB-EUC-H" => Self::GbEucH,
232 b"GB-EUC-V" => Self::GbEucV,
233 b"GBK-EUC-H" => Self::GbkEucH,
234 b"GBK-EUC-V" => Self::GbkEucV,
235 b"GBK2K-H" => Self::Gbk2kH,
236 b"GBK2K-V" => Self::Gbk2kV,
237 b"GBKp-EUC-H" => Self::GbkpEucH,
238 b"GBKp-EUC-V" => Self::GbkpEucV,
239 b"GBpc-EUC-H" => Self::GbpcEucH,
240 b"GBpc-EUC-V" => Self::GbpcEucV,
241 b"H" => Self::H,
242 b"HKscs-B5-H" => Self::HKscsB5H,
243 b"HKscs-B5-V" => Self::HKscsB5V,
244 b"Identity-H" => Self::IdentityH,
245 b"Identity-V" => Self::IdentityV,
246 b"KSC-EUC-H" => Self::KscEucH,
247 b"KSC-EUC-V" => Self::KscEucV,
248 b"KSCms-UHC-H" => Self::KscmsUhcH,
249 b"KSCms-UHC-HW-H" => Self::KscmsUhcHwH,
250 b"KSCms-UHC-HW-V" => Self::KscmsUhcHwV,
251 b"KSCms-UHC-V" => Self::KscmsUhcV,
252 b"KSCpc-EUC-H" => Self::KscpcEucH,
253 b"UniCNS-UCS2-H" => Self::UniCnsUcs2H,
254 b"UniCNS-UCS2-V" => Self::UniCnsUcs2V,
255 b"UniCNS-UTF16-H" => Self::UniCnsUtf16H,
256 b"UniCNS-UTF16-V" => Self::UniCnsUtf16V,
257 b"UniGB-UCS2-H" => Self::UniGbUcs2H,
258 b"UniGB-UCS2-V" => Self::UniGbUcs2V,
259 b"UniGB-UTF16-H" => Self::UniGbUtf16H,
260 b"UniGB-UTF16-V" => Self::UniGbUtf16V,
261 b"UniJIS-UCS2-H" => Self::UniJisUcs2H,
262 b"UniJIS-UCS2-HW-H" => Self::UniJisUcs2HwH,
263 b"UniJIS-UCS2-HW-V" => Self::UniJisUcs2HwV,
264 b"UniJIS-UCS2-V" => Self::UniJisUcs2V,
265 b"UniJIS-UTF16-H" => Self::UniJisUtf16H,
266 b"UniJIS-UTF16-V" => Self::UniJisUtf16V,
267 b"UniKS-UCS2-H" => Self::UniKsUcs2H,
268 b"UniKS-UCS2-V" => Self::UniKsUcs2V,
269 b"UniKS-UTF16-H" => Self::UniKsUtf16H,
270 b"UniKS-UTF16-V" => Self::UniKsUtf16V,
271 b"V" => Self::V,
272 _ => Self::Custom(name),
273 }
274 }
275
276 pub fn to_bytes(&self) -> &[u8] {
278 match self {
279 Self::N83pvRksjH => b"83pv-RKSJ-H",
280 Self::N90msRksjH => b"90ms-RKSJ-H",
281 Self::N90msRksjV => b"90ms-RKSJ-V",
282 Self::N90mspRksjH => b"90msp-RKSJ-H",
283 Self::N90mspRksjV => b"90msp-RKSJ-V",
284 Self::N90pvRksjH => b"90pv-RKSJ-H",
285 Self::AddRksjH => b"Add-RKSJ-H",
286 Self::AddRksjV => b"Add-RKSJ-V",
287 Self::AdobeCns1Ucs2 => b"Adobe-CNS1-UCS2",
288 Self::AdobeGb1Ucs2 => b"Adobe-GB1-UCS2",
289 Self::AdobeJapan1Ucs2 => b"Adobe-Japan1-UCS2",
290 Self::AdobeKorea1Ucs2 => b"Adobe-Korea1-UCS2",
291 Self::B5pcH => b"B5pc-H",
292 Self::B5pcV => b"B5pc-V",
293 Self::CnsEucH => b"CNS-EUC-H",
294 Self::CnsEucV => b"CNS-EUC-V",
295 Self::ETenB5H => b"ETen-B5-H",
296 Self::ETenB5V => b"ETen-B5-V",
297 Self::ETenmsB5H => b"ETenms-B5-H",
298 Self::ETenmsB5V => b"ETenms-B5-V",
299 Self::EucH => b"EUC-H",
300 Self::EucV => b"EUC-V",
301 Self::ExtRksjH => b"Ext-RKSJ-H",
302 Self::ExtRksjV => b"Ext-RKSJ-V",
303 Self::GbEucH => b"GB-EUC-H",
304 Self::GbEucV => b"GB-EUC-V",
305 Self::GbkEucH => b"GBK-EUC-H",
306 Self::GbkEucV => b"GBK-EUC-V",
307 Self::Gbk2kH => b"GBK2K-H",
308 Self::Gbk2kV => b"GBK2K-V",
309 Self::GbkpEucH => b"GBKp-EUC-H",
310 Self::GbkpEucV => b"GBKp-EUC-V",
311 Self::GbpcEucH => b"GBpc-EUC-H",
312 Self::GbpcEucV => b"GBpc-EUC-V",
313 Self::H => b"H",
314 Self::HKscsB5H => b"HKscs-B5-H",
315 Self::HKscsB5V => b"HKscs-B5-V",
316 Self::IdentityH => b"Identity-H",
317 Self::IdentityV => b"Identity-V",
318 Self::KscEucH => b"KSC-EUC-H",
319 Self::KscEucV => b"KSC-EUC-V",
320 Self::KscmsUhcH => b"KSCms-UHC-H",
321 Self::KscmsUhcHwH => b"KSCms-UHC-HW-H",
322 Self::KscmsUhcHwV => b"KSCms-UHC-HW-V",
323 Self::KscmsUhcV => b"KSCms-UHC-V",
324 Self::KscpcEucH => b"KSCpc-EUC-H",
325 Self::UniCnsUcs2H => b"UniCNS-UCS2-H",
326 Self::UniCnsUcs2V => b"UniCNS-UCS2-V",
327 Self::UniCnsUtf16H => b"UniCNS-UTF16-H",
328 Self::UniCnsUtf16V => b"UniCNS-UTF16-V",
329 Self::UniGbUcs2H => b"UniGB-UCS2-H",
330 Self::UniGbUcs2V => b"UniGB-UCS2-V",
331 Self::UniGbUtf16H => b"UniGB-UTF16-H",
332 Self::UniGbUtf16V => b"UniGB-UTF16-V",
333 Self::UniJisUcs2H => b"UniJIS-UCS2-H",
334 Self::UniJisUcs2HwH => b"UniJIS-UCS2-HW-H",
335 Self::UniJisUcs2HwV => b"UniJIS-UCS2-HW-V",
336 Self::UniJisUcs2V => b"UniJIS-UCS2-V",
337 Self::UniJisUtf16H => b"UniJIS-UTF16-H",
338 Self::UniJisUtf16V => b"UniJIS-UTF16-V",
339 Self::UniKsUcs2H => b"UniKS-UCS2-H",
340 Self::UniKsUcs2V => b"UniKS-UCS2-V",
341 Self::UniKsUtf16H => b"UniKS-UTF16-H",
342 Self::UniKsUtf16V => b"UniKS-UTF16-V",
343 Self::V => b"V",
344 Self::Custom(name) => name,
345 }
346 }
347}
348
349const MAX_NESTING_DEPTH: u32 = 16;
351
352#[derive(Debug, Clone)]
354pub struct CMap {
355 metadata: Metadata,
356 _codespace_ranges: Vec<CodespaceRange>,
359 cid_ranges: PartitionedRanges,
360 notdef_ranges: PartitionedRanges,
361 bf_entries: Vec<BfRange>,
362 base: Option<Box<Self>>,
363}
364
365impl CMap {
366 pub fn parse<'a>(
371 data: &[u8],
372 get_cmap: impl Fn(CMapName<'_>) -> Option<&'a [u8]> + Clone + 'a,
373 ) -> Option<Self> {
374 parse::parse_inner(data, get_cmap, 0)
375 }
376
377 pub fn identity_h() -> Self {
379 Self::identity(WritingMode::Horizontal, b"Identity-H")
380 }
381
382 pub fn identity_v() -> Self {
384 Self::identity(WritingMode::Vertical, b"Identity-V")
385 }
386
387 fn identity(writing_mode: WritingMode, name: &[u8]) -> Self {
388 Self {
389 metadata: Metadata {
390 character_collection: Some(CharacterCollection {
391 family: CidFamily::AdobeIdentity,
392 supplement: 0,
393 }),
394 name: Some(Vec::from(name)),
395 writing_mode: Some(writing_mode),
396 },
397 _codespace_ranges: Vec::new(),
398 cid_ranges: {
399 let mut r = PartitionedRanges::new();
400 r.push(
401 2,
402 CidRange {
403 range: Range {
404 start: 0,
405 end: 0xFFFF,
406 },
407 cid_start: 0,
408 },
409 );
410 r
411 },
412 notdef_ranges: PartitionedRanges::new(),
413 bf_entries: Vec::new(),
414 base: None,
415 }
416 }
417
418 pub fn metadata(&self) -> &Metadata {
420 &self.metadata
421 }
422
423 pub fn lookup_cid_code(&self, code: u32, byte_len: u8) -> Option<Cid> {
427 let cid_ranges = self.cid_ranges.get(byte_len)?;
432 let notdef_ranges = self.notdef_ranges.get(byte_len)?;
433
434 if let Some(entry) = find_in_ranges(cid_ranges, code) {
435 let offset = code.checked_sub(entry.range.start)?;
436 return entry.cid_start.checked_add(offset);
437 } else if let Some(entry) = find_in_ranges(notdef_ranges, code) {
438 return Some(entry.cid_start);
441 }
442
443 if let Some(BfString::Char(lookup)) = self.lookup_bf_string_inner(code, false) {
447 return Some(lookup as u32);
448 }
449
450 self.base
452 .as_ref()
453 .and_then(|b| b.lookup_cid_code(code, byte_len))
454 }
455
456 pub fn lookup_bf_string(&self, code: u32) -> Option<BfString> {
462 self.lookup_bf_string_inner(code, true)
463 }
464
465 fn lookup_bf_string_inner(&self, code: u32, recurse: bool) -> Option<BfString> {
466 if let Some(entry) = find_in_ranges(&self.bf_entries, code) {
467 let offset = u16::try_from(code - entry.range.start).ok()?;
468
469 fn decode_utf16(units: &[u16]) -> Option<BfString> {
470 let mut iter = core::char::decode_utf16(units.iter().copied());
471 let first = iter.next()?.ok()?;
472
473 if iter.next().is_none() {
474 Some(BfString::Char(first))
475 } else {
476 let s = String::from_utf16(units).ok()?;
477 Some(BfString::String(s))
478 }
479 }
480
481 return if offset == 0 {
482 Some(decode_utf16(&entry.dst_base)?)
483 } else {
484 let mut units = entry.dst_base.clone();
485 *units.last_mut()? = units.last()?.checked_add(offset)?;
486 Some(decode_utf16(&units)?)
487 };
488 }
489
490 if recurse {
491 self.base.as_ref()?.lookup_bf_string(code)
492 } else {
493 None
494 }
495 }
496}
497
498trait HasRange {
499 fn range(&self) -> &Range;
500}
501
502fn find_in_ranges<T: HasRange>(entries: &[T], code: u32) -> Option<&T> {
503 let idx = entries
504 .binary_search_by(|entry| {
505 let r = entry.range();
506 if code < r.start {
507 core::cmp::Ordering::Greater
508 } else if code > r.end {
509 core::cmp::Ordering::Less
510 } else {
511 core::cmp::Ordering::Equal
512 }
513 })
514 .ok()?;
515
516 Some(&entries[idx])
517}
518
519#[derive(Debug, Clone)]
521pub(crate) struct Range {
522 pub(crate) start: u32,
523 pub(crate) end: u32,
524}
525
526#[derive(Debug, Clone)]
528pub struct CidRange {
529 pub(crate) range: Range,
530 pub(crate) cid_start: Cid,
531}
532
533impl HasRange for CidRange {
534 fn range(&self) -> &Range {
535 &self.range
536 }
537}
538
539#[derive(Debug, Clone)]
540pub(crate) struct PartitionedRanges {
541 buckets: [Vec<CidRange>; 4],
542}
543
544impl PartitionedRanges {
545 pub(crate) fn new() -> Self {
546 Self {
547 buckets: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
548 }
549 }
550
551 pub(crate) fn push(&mut self, byte_len: usize, range: CidRange) {
552 if let Some(bucket) = byte_len
553 .checked_sub(1)
554 .and_then(|i| self.buckets.get_mut(i))
555 {
556 bucket.push(range);
557 }
558 }
559
560 pub(crate) fn get(&self, byte_len: u8) -> Option<&[CidRange]> {
561 let idx = (byte_len as usize).checked_sub(1)?;
562 self.buckets.get(idx).map(|v| v.as_slice())
563 }
564
565 pub(crate) fn sort(&mut self) {
566 for bucket in &mut self.buckets {
567 bucket.sort_by_key(|a| a.range.start);
568 }
569 }
570}
571
572#[derive(Debug, Clone)]
573pub(crate) struct BfRange {
574 pub(crate) range: Range,
575 pub(crate) dst_base: Vec<u16>,
576}
577
578impl HasRange for BfRange {
579 fn range(&self) -> &Range {
580 &self.range
581 }
582}
583
584#[derive(Debug, Clone)]
586#[allow(dead_code)]
587pub(crate) struct CodespaceRange {
588 pub(crate) number_bytes: u8,
589 pub(crate) low: u32,
590 pub(crate) high: u32,
591}
592
593#[derive(Debug, Clone, PartialEq, Eq)]
595pub enum BfString {
596 Char(char),
598 String(String),
600}
601
602#[derive(Debug, Clone)]
604pub struct Metadata {
605 pub character_collection: Option<CharacterCollection>,
607 pub name: Option<Vec<u8>>,
609 pub writing_mode: Option<WritingMode>,
611}
612
613#[derive(Debug, Clone, PartialEq, Eq)]
615pub enum CidFamily {
616 AdobeJapan1,
618 AdobeGB1,
620 AdobeCNS1,
622 AdobeKorea1,
624 AdobeIdentity,
626 Custom {
628 registry: Vec<u8>,
630 ordering: Vec<u8>,
632 },
633}
634
635impl CidFamily {
636 pub fn from_registry_ordering(registry: &[u8], ordering: &[u8]) -> Self {
638 if registry == b"Adobe" {
639 match ordering {
640 b"Japan1" => Self::AdobeJapan1,
641 b"GB1" => Self::AdobeGB1,
642 b"CNS1" => Self::AdobeCNS1,
643 b"Korea1" => Self::AdobeKorea1,
644 b"Identity" => Self::AdobeIdentity,
645 _ => Self::Custom {
646 registry: registry.to_vec(),
647 ordering: ordering.to_vec(),
648 },
649 }
650 } else {
651 Self::Custom {
652 registry: registry.to_vec(),
653 ordering: ordering.to_vec(),
654 }
655 }
656 }
657
658 pub fn unicode_cmap(&self) -> Option<CMapName<'static>> {
661 match self {
662 Self::AdobeJapan1 => Some(CMapName::UniJisUtf16H),
663 Self::AdobeGB1 => Some(CMapName::UniGbUtf16H),
664 Self::AdobeCNS1 => Some(CMapName::UniCnsUtf16H),
665 Self::AdobeKorea1 => Some(CMapName::UniKsUtf16H),
666 Self::AdobeIdentity | Self::Custom { .. } => None,
667 }
668 }
669
670 pub fn ucs2_cmap(&self) -> Option<CMapName<'static>> {
672 match self {
673 Self::AdobeJapan1 => Some(CMapName::AdobeJapan1Ucs2),
674 Self::AdobeGB1 => Some(CMapName::AdobeGb1Ucs2),
675 Self::AdobeCNS1 => Some(CMapName::AdobeCns1Ucs2),
676 Self::AdobeKorea1 => Some(CMapName::AdobeKorea1Ucs2),
677 Self::AdobeIdentity | Self::Custom { .. } => None,
678 }
679 }
680
681 pub fn is_cjk(&self) -> bool {
684 matches!(
685 self,
686 Self::AdobeGB1 | Self::AdobeCNS1 | Self::AdobeJapan1 | Self::AdobeKorea1
687 )
688 }
689}
690
691#[derive(Debug, Clone, PartialEq, Eq)]
693pub struct CharacterCollection {
694 pub family: CidFamily,
696 pub supplement: i32,
698}
699
700#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
702pub enum WritingMode {
703 #[default]
705 Horizontal,
706 Vertical,
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713
714 const PREAMBLE: &[u8] = br#"/CIDSystemInfo 3 dict dup begin
719 /Registry (Adobe) def
720 /Ordering (Japan1) def
721 /Supplement 0 def
722end def
723/CMapName /Test def
724/WMode 0 def
7252 begincodespacerange
726<00> <FF>
727<0000> <FFFF>
728endcodespacerange
729"#;
730
731 fn parse_with_preamble(body: &[u8]) -> CMap {
732 let mut data = Vec::new();
733 data.extend_from_slice(PREAMBLE);
734 data.extend_from_slice(body);
735 CMap::parse(&data, |_| None).unwrap()
736 }
737
738 #[test]
742 fn a_token_we_cannot_read_does_not_cost_the_whole_map() {
743 let data = br#"/CIDInit /ProcSet findresource begin
74412 dict begin
745begincmap
746/CMapName /-usr-share-fonts-truetype-Panton-Panton Family-Fontfabric - Panton.otf,000-UTF16 def
7471 begincodespacerange
748<0000> <FFFF>
749endcodespacerange
7501 beginbfchar
751<0055> <0050>
752endbfchar
753endcmap"#;
754
755 let cmap = CMap::parse(data, |_| None).expect("the map survives the file path");
756 assert_eq!(cmap.lookup_bf_string(0x0055), Some(BfString::Char('P')));
757 }
758
759 #[test]
760 fn metadata_parsing() {
761 let data = br#"
762/CIDInit /ProcSet findresource begin
76312 dict begin
764begincmap
765/CIDSystemInfo 3 dict dup begin
766 /Registry (Adobe) def
767 /Ordering (Japan1) def
768 /Supplement 6 def
769end def
770/CMapName /Adobe-Japan1-H def
771/CMapType 1 def
772/WMode 0 def
773endcmap"#;
774
775 let cmap = CMap::parse(data, |_| None).unwrap();
776 let cc = cmap.metadata().character_collection.as_ref().unwrap();
777 assert_eq!(cc.family, CidFamily::AdobeJapan1);
778 assert_eq!(cc.supplement, 6);
779 assert_eq!(
780 cmap.metadata().name.as_deref(),
781 Some(b"Adobe-Japan1-H".as_slice())
782 );
783 assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Horizontal));
784 }
785
786 #[test]
787 fn vertical_writing_mode() {
788 let data = br#"
789/CIDSystemInfo 3 dict dup begin
790 /Registry (Adobe) def
791 /Ordering (Japan1) def
792 /Supplement 6 def
793end def
794/CMapName /Adobe-Japan1-V def
795/WMode 1 def
796"#;
797
798 let cmap = CMap::parse(data, |_| None).unwrap();
799 assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
800 assert_eq!(
801 cmap.metadata().name.as_deref(),
802 Some(b"Adobe-Japan1-V".as_slice())
803 );
804 }
805
806 #[test]
807 fn cid_range_lookup() {
808 let cmap = parse_with_preamble(
809 br#"
8103 begincidrange
811<0000> <00FF> 0
812<0100> <01FF> 256
813<8140> <817E> 633
814endcidrange
815"#,
816 );
817
818 assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
819 assert_eq!(cmap.lookup_cid_code(0x0042, 2), Some(0x42));
820 assert_eq!(cmap.lookup_cid_code(0x00FF, 2), Some(0xFF));
821
822 assert_eq!(cmap.lookup_cid_code(0x0100, 2), Some(256));
823 assert_eq!(cmap.lookup_cid_code(0x01FF, 2), Some(511));
824
825 assert_eq!(cmap.lookup_cid_code(0x8140, 2), Some(633));
826 assert_eq!(cmap.lookup_cid_code(0x817E, 2), Some(633 + 62));
827 }
828
829 #[test]
830 fn cid_char_lookup() {
831 let cmap = parse_with_preamble(
832 br#"
8333 begincidchar
834<03> 1
835<04> 2
836<20> 50
837endcidchar
838"#,
839 );
840
841 assert_eq!(cmap.lookup_cid_code(0x03, 1), Some(1));
842 assert_eq!(cmap.lookup_cid_code(0x04, 1), Some(2));
843 assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(50));
844 }
845
846 #[test]
847 fn lookup_miss() {
848 let cmap = parse_with_preamble(
849 br#"
8501 begincidrange
851<0100> <01FF> 0
852endcidrange
853"#,
854 );
855
856 assert_eq!(cmap.lookup_cid_code(0x00FF, 2), None);
857 assert_eq!(cmap.lookup_cid_code(0x0200, 2), None);
858 assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), None);
859 }
860
861 #[test]
862 fn multiple_sections() {
863 let cmap = parse_with_preamble(
864 br#"
8652 begincidrange
866<0000> <00FF> 0
867<0100> <01FF> 256
868endcidrange
8691 begincidchar
870<0200> 600
871endcidchar
8721 begincidrange
873<8140> <817E> 633
874endcidrange
875"#,
876 );
877
878 assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
879 assert_eq!(cmap.lookup_cid_code(0x0100, 2), Some(256));
880 assert_eq!(cmap.lookup_cid_code(0x0200, 2), Some(600));
881 assert_eq!(cmap.lookup_cid_code(0x8140, 2), Some(633));
882 }
883
884 #[test]
885 fn single_byte_codes() {
886 let cmap = parse_with_preamble(
887 br#"
8882 begincidrange
889<00> <7F> 0
890<80> <FF> 200
891endcidrange
892"#,
893 );
894
895 assert_eq!(cmap.lookup_cid_code(0x00, 1), Some(0));
896 assert_eq!(cmap.lookup_cid_code(0x41, 1), Some(0x41));
897 assert_eq!(cmap.lookup_cid_code(0x80, 1), Some(200));
898 assert_eq!(cmap.lookup_cid_code(0xFF, 1), Some(200 + 127));
899 }
900
901 #[test]
902 fn dict_style_cidsysteminfo() {
903 let data = br#"
904/CIDInit /ProcSet findresource begin
90510 dict begin
906begincmap
907/CIDSystemInfo
908<< /Registry (Adobe)
909/Ordering (UCS)
910/Supplement 0
911>> def
912/CMapName /Adobe-Identity-UCS def
913/CMapType 2 def
9141 begincodespacerange
915<0000> <FFFF>
916endcodespacerange
9172 beginbfchar
918<001F> <F049>
919<002A> <F055>
920endbfchar
921endcmap
922CMapName currentdict /CMap defineresource pop
923end
924end
925"#;
926 let cmap = CMap::parse(data, |_| None).unwrap();
927 let cc = cmap.metadata().character_collection.as_ref().unwrap();
928 assert_eq!(
929 cc.family,
930 CidFamily::Custom {
931 registry: b"Adobe".to_vec(),
932 ordering: b"UCS".to_vec(),
933 }
934 );
935 assert_eq!(cc.supplement, 0);
936 assert_eq!(
937 cmap.metadata().name.as_deref(),
938 Some(b"Adobe-Identity-UCS".as_slice())
939 );
940 assert_eq!(
941 cmap.lookup_bf_string(0x001F),
942 Some(BfString::Char('\u{F049}'))
943 );
944 assert_eq!(
945 cmap.lookup_bf_string(0x002A),
946 Some(BfString::Char('\u{F055}'))
947 );
948 }
949
950 #[test]
951 fn usecmap_chaining() {
952 let base_data = br#"
953/CIDSystemInfo 3 dict dup begin
954 /Registry (Adobe) def
955 /Ordering (Japan1) def
956 /Supplement 0 def
957end def
958/CMapName /Base def
959/WMode 0 def
9601 begincodespacerange
961<0000> <FFFF>
962endcodespacerange
9631 begincidrange
964<0000> <00FF> 0
965endcidrange
966"#;
967
968 let child_data = br#"
969/Base usecmap
970/CIDSystemInfo 3 dict dup begin
971 /Registry (Adobe) def
972 /Ordering (Japan1) def
973 /Supplement 0 def
974end def
975/CMapName /Child def
976/WMode 0 def
9771 begincodespacerange
978<0000> <FFFF>
979endcodespacerange
9801 begincidrange
981<0100> <01FF> 256
982endcidrange
983"#;
984
985 let cmap = CMap::parse(child_data, |name| {
986 if name.to_bytes() == b"Base" {
987 Some(base_data.as_slice())
988 } else {
989 None
990 }
991 })
992 .unwrap();
993
994 assert_eq!(cmap.lookup_cid_code(0x0100, 2), Some(256));
995 assert_eq!(cmap.lookup_cid_code(0x01FF, 2), Some(511));
996 assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
997 assert_eq!(cmap.lookup_cid_code(0x00FF, 2), Some(0xFF));
998
999 assert_eq!(cmap.lookup_cid_code(0x0200, 2), None);
1000 }
1001
1002 #[test]
1003 fn usecmap_partial_override() {
1004 let base_data = br#"
1005/CIDSystemInfo 3 dict dup begin
1006 /Registry (Adobe) def
1007 /Ordering (Japan1) def
1008 /Supplement 0 def
1009end def
1010/CMapName /Base def
1011/WMode 0 def
10121 begincodespacerange
1013<0000> <FFFF>
1014endcodespacerange
10151 begincidrange
1016<0000> <00FF> 0
1017endcidrange
1018"#;
1019
1020 let child_data = br#"
1021/Base usecmap
1022/CIDSystemInfo 3 dict dup begin
1023 /Registry (Adobe) def
1024 /Ordering (Japan1) def
1025 /Supplement 0 def
1026end def
1027/CMapName /Child def
1028/WMode 0 def
10291 begincodespacerange
1030<0000> <FFFF>
1031endcodespacerange
10321 begincidrange
1033<0040> <007F> 500
1034endcidrange
1035"#;
1036
1037 let cmap = CMap::parse(child_data, |name| {
1038 if name.to_bytes() == b"Base" {
1039 Some(base_data.as_slice())
1040 } else {
1041 None
1042 }
1043 })
1044 .unwrap();
1045
1046 assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
1047 assert_eq!(cmap.lookup_cid_code(0x003F, 2), Some(0x3F));
1048 assert_eq!(cmap.lookup_cid_code(0x0040, 2), Some(500));
1049 assert_eq!(cmap.lookup_cid_code(0x007F, 2), Some(563));
1050 assert_eq!(cmap.lookup_cid_code(0x0080, 2), Some(0x80));
1051 assert_eq!(cmap.lookup_cid_code(0x00FF, 2), Some(0xFF));
1052 }
1053
1054 #[test]
1055 fn notdef_char_lookup() {
1056 let cmap = parse_with_preamble(
1057 br#"
10582 beginnotdefchar
1059<03> 10
1060<20> 20
1061endnotdefchar
1062"#,
1063 );
1064
1065 assert_eq!(cmap.lookup_cid_code(0x03, 1), Some(10));
1066 assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(20));
1067 assert_eq!(cmap.lookup_cid_code(0x04, 1), None);
1068 }
1069
1070 #[test]
1071 fn notdef_range_lookup() {
1072 let cmap = parse_with_preamble(
1073 br#"
10741 beginnotdefrange
1075<0000> <001F> 100
1076endnotdefrange
1077"#,
1078 );
1079
1080 assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(100));
1081 assert_eq!(cmap.lookup_cid_code(0x0001, 2), Some(100));
1082 assert_eq!(cmap.lookup_cid_code(0x001F, 2), Some(100));
1083 assert_eq!(cmap.lookup_cid_code(0x0020, 2), None);
1084 }
1085
1086 #[test]
1087 fn bfchar_lookup() {
1088 let cmap = parse_with_preamble(
1089 br#"
10902 beginbfchar
1091<0041> <0048>
1092<0042> <0065>
1093endbfchar
1094"#,
1095 );
1096
1097 assert_eq!(cmap.lookup_bf_string(0x0041), Some(BfString::Char('H')));
1098 assert_eq!(cmap.lookup_bf_string(0x0042), Some(BfString::Char('e')));
1099 assert_eq!(cmap.lookup_bf_string(0x0043), None);
1100 }
1101
1102 #[test]
1103 fn bfchar_ligature() {
1104 let cmap = parse_with_preamble(
1105 br#"
11061 beginbfchar
1107<005F> <00660066>
1108endbfchar
1109"#,
1110 );
1111
1112 assert_eq!(
1113 cmap.lookup_bf_string(0x005F),
1114 Some(BfString::String(String::from("ff")))
1115 );
1116 }
1117
1118 #[test]
1119 fn bfchar_surrogate_pair() {
1120 let cmap = parse_with_preamble(
1121 br#"
11221 beginbfchar
1123<3A51> <D840DC3E>
1124endbfchar
1125"#,
1126 );
1127
1128 assert_eq!(
1129 cmap.lookup_bf_string(0x3A51),
1130 Some(BfString::Char('\u{2003E}'))
1131 );
1132 }
1133
1134 #[test]
1135 fn bfrange_incrementing() {
1136 let cmap = parse_with_preamble(
1137 br#"
11381 beginbfrange
1139<0000> <0004> <0041>
1140endbfrange
1141"#,
1142 );
1143
1144 assert_eq!(cmap.lookup_bf_string(0x0000), Some(BfString::Char('A')));
1145 assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char('B')));
1146 assert_eq!(cmap.lookup_bf_string(0x0004), Some(BfString::Char('E')));
1147 assert_eq!(cmap.lookup_bf_string(0x0005), None);
1148 }
1149
1150 #[test]
1151 fn bfrange_array() {
1152 let cmap = parse_with_preamble(
1153 br#"
11541 beginbfrange
1155<005F> <0061> [<00660066> <00660069> <0066006C>]
1156endbfrange
1157"#,
1158 );
1159
1160 assert_eq!(
1162 cmap.lookup_bf_string(0x005F),
1163 Some(BfString::String(String::from("ff")))
1164 );
1165 assert_eq!(
1166 cmap.lookup_bf_string(0x0060),
1167 Some(BfString::String(String::from("fi")))
1168 );
1169 assert_eq!(
1170 cmap.lookup_bf_string(0x0061),
1171 Some(BfString::String(String::from("fl")))
1172 );
1173 }
1174
1175 #[test]
1176 fn unicode_lookup_miss() {
1177 let cmap = parse_with_preamble(
1178 br#"
11791 beginbfchar
1180<0041> <0048>
1181endbfchar
1182"#,
1183 );
1184
1185 assert_eq!(cmap.lookup_bf_string(0x0000), None);
1186 assert_eq!(cmap.lookup_bf_string(0x0042), None);
1187 }
1188
1189 #[test]
1190 fn identity_h() {
1191 let cmap = CMap::identity_h();
1192 assert_eq!(
1193 cmap.metadata().name.as_deref(),
1194 Some(b"Identity-H".as_slice())
1195 );
1196 assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Horizontal));
1197
1198 assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x0041));
1199 assert_eq!(cmap.lookup_cid_code(0x1234, 2), Some(0x1234));
1200 assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1201
1202 assert_eq!(cmap.lookup_cid_code(0x0041, 1), None);
1203 assert_eq!(cmap.lookup_cid_code(0x0041, 3), None);
1204 }
1205
1206 #[test]
1207 fn identity_v() {
1208 let cmap = CMap::identity_v();
1209 assert_eq!(
1210 cmap.metadata().name.as_deref(),
1211 Some(b"Identity-V".as_slice())
1212 );
1213 assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
1214
1215 assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x0041));
1216 assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1217 }
1218
1219 #[test]
1220 fn minimal_cmap_no_name_no_wmode() {
1221 let data = br#"
1223/CIDInit /ProcSet findresource begin
122412 dict begin
1225begincmap
1226/CMapType 2 def
12271 begincodespacerange
1228<00><ff>
1229endcodespacerange
12301 beginbfrange
1231<01><01><0020>
1232endbfrange
1233endcmap
1234"#;
1235 let cmap = CMap::parse(data, |_| None).unwrap();
1236 assert_eq!(cmap.metadata().name, None);
1237 assert_eq!(cmap.metadata().character_collection, None);
1238 assert_eq!(cmap.metadata().writing_mode, None);
1239 assert_eq!(cmap.lookup_bf_string(0x01), Some(BfString::Char(' ')));
1240 }
1241
1242 #[test]
1243 fn registry_as_name() {
1244 let data = br#"
1247/CIDSystemInfo
1248<< /Registry /ABCDEF+SimSun
1249/Ordering (pdfbeaninc)
1250/Supplement 0
1251>> def
1252/CMapName /ABCDEF+SimSun def
1253/CMapType 2 def
12541 begincodespacerange
1255<0000> <FFFF>
1256endcodespacerange
12571 beginbfrange
1258<0000> <0000> <6881>
1259endbfrange
1260"#;
1261 let cmap = CMap::parse(data, |_| None).unwrap();
1262 let cc = cmap.metadata().character_collection.as_ref().unwrap();
1263 assert_eq!(
1264 cc.family,
1265 CidFamily::Custom {
1266 registry: b"ABCDEF+SimSun".to_vec(),
1267 ordering: b"pdfbeaninc".to_vec(),
1268 }
1269 );
1270 assert_eq!(cc.supplement, 0);
1271 assert_eq!(
1272 cmap.lookup_bf_string(0x0000),
1273 Some(BfString::Char('\u{6881}'))
1274 );
1275 }
1276
1277 #[test]
1278 fn single_byte_bfrange_destination() {
1279 let cmap = parse_with_preamble(
1281 br#"
12821 beginbfrange
1283<00> <7f> <00>
1284endbfrange
1285"#,
1286 );
1287
1288 assert_eq!(
1289 cmap.lookup_bf_string(0x00),
1290 Some(BfString::Char('\u{0000}'))
1291 );
1292 assert_eq!(cmap.lookup_bf_string(0x41), Some(BfString::Char('A')));
1293 assert_eq!(
1294 cmap.lookup_bf_string(0x7f),
1295 Some(BfString::Char('\u{007F}'))
1296 );
1297 }
1298}
1299
1300#[cfg(all(test, feature = "embed-cmaps"))]
1301mod bcmap_tests {
1302 use super::*;
1303
1304 fn get_embedded_cmap(name: CMapName<'_>) -> Option<&'static [u8]> {
1305 load_embedded(name)
1306 }
1307
1308 #[test]
1309 fn embedded_h_cmap() {
1310 let data = load_embedded(CMapName::H).expect("embedded H cmap not found");
1311 let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse H cmap");
1312 assert_eq!(cmap.metadata().writing_mode, None);
1313 assert_eq!(
1314 cmap.metadata().character_collection,
1315 Some(CharacterCollection {
1316 family: CidFamily::AdobeJapan1,
1317 supplement: 1,
1318 })
1319 );
1320 assert_eq!(cmap.lookup_cid_code(0x2121, 2), Some(633));
1321 assert_eq!(cmap.lookup_cid_code(0x2221, 2), Some(727));
1322 }
1323
1324 #[test]
1325 fn embedded_90ms_rksj_h() {
1326 let data =
1327 load_embedded(CMapName::N90msRksjH).expect("embedded 90ms-RKSJ-H cmap not found");
1328 let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse 90ms-RKSJ-H cmap");
1329 assert_eq!(
1330 cmap.metadata().character_collection,
1331 Some(CharacterCollection {
1332 family: CidFamily::AdobeJapan1,
1333 supplement: 2,
1334 })
1335 );
1336 assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(231));
1337 assert_eq!(cmap.lookup_cid_code(0x8140, 2), Some(633));
1338 }
1339
1340 #[test]
1341 fn embedded_v_cmap() {
1342 let data = load_embedded(CMapName::V).expect("embedded V cmap not found");
1343 let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse V cmap");
1344 assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
1345 assert_eq!(
1346 cmap.metadata().character_collection,
1347 Some(CharacterCollection {
1348 family: CidFamily::AdobeJapan1,
1349 supplement: 1,
1350 })
1351 );
1352 assert_eq!(cmap.lookup_cid_code(0x2121, 2), Some(633));
1354 }
1355
1356 #[test]
1357 fn embedded_gbk_euc_h() {
1358 let data = load_embedded(CMapName::GbkEucH).expect("embedded GBK-EUC-H not found");
1359 let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse GBK-EUC-H");
1360 assert_eq!(
1361 cmap.metadata().character_collection,
1362 Some(CharacterCollection {
1363 family: CidFamily::AdobeGB1,
1364 supplement: 2,
1365 })
1366 );
1367 assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(7716));
1368 assert_eq!(cmap.lookup_cid_code(0x21, 1), Some(814));
1369 assert_eq!(cmap.lookup_cid_code(0x7E, 1), Some(814 + 0x7E - 0x21));
1370 assert_eq!(cmap.lookup_cid_code(0xFE80, 2), Some(22094));
1371 assert_eq!(cmap.lookup_cid_code(0xFEA0, 2), Some(22094 + 0xA0 - 0x80));
1372 }
1373
1374 #[test]
1375 fn embedded_ksc_euc_h() {
1376 let data = load_embedded(CMapName::KscEucH).unwrap();
1377 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1378 assert_eq!(
1379 cmap.metadata().character_collection,
1380 Some(CharacterCollection {
1381 family: CidFamily::AdobeKorea1,
1382 supplement: 0,
1383 })
1384 );
1385 assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(8094));
1386 assert_eq!(cmap.lookup_cid_code(0x41, 1), Some(8094 + 0x41 - 0x20));
1387 assert_eq!(cmap.lookup_cid_code(0xA1A1, 2), Some(101));
1388 assert_eq!(cmap.lookup_cid_code(0xA1FE, 2), Some(101 + 0xFE - 0xA1));
1389 assert_eq!(cmap.lookup_cid_code(0xFDA1, 2), Some(7962));
1390 }
1391
1392 #[test]
1393 fn embedded_b5pc_h() {
1394 let data = load_embedded(CMapName::B5pcH).unwrap();
1395 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1396 assert_eq!(
1397 cmap.metadata().character_collection,
1398 Some(CharacterCollection {
1399 family: CidFamily::AdobeCNS1,
1400 supplement: 0,
1401 })
1402 );
1403 assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(1));
1404 assert_eq!(cmap.lookup_cid_code(0x41, 1), Some(1 + 0x41 - 0x20));
1405 assert_eq!(cmap.lookup_cid_code(0x80, 1), Some(61));
1406 assert_eq!(cmap.lookup_cid_code(0xD140, 2), Some(7251));
1407 assert_eq!(cmap.lookup_cid_code(0xD17E, 2), Some(7251 + 0x7E - 0x40));
1408 assert_eq!(cmap.lookup_cid_code(0xF9D5, 2), Some(13642 + 3));
1409 }
1410
1411 #[test]
1412 fn embedded_unijis_utf16_h() {
1413 let data = load_embedded(CMapName::UniJisUtf16H).unwrap();
1414 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1415 assert_eq!(
1416 cmap.metadata().character_collection,
1417 Some(CharacterCollection {
1418 family: CidFamily::AdobeJapan1,
1419 supplement: 7,
1420 })
1421 );
1422 assert_eq!(cmap.lookup_cid_code(0x20, 2), Some(1));
1423 assert_eq!(cmap.lookup_cid_code(0x41, 2), Some(1 + 0x41 - 0x20));
1424 assert_eq!(cmap.lookup_cid_code(0x5C, 2), Some(97));
1425 assert_eq!(cmap.lookup_cid_code(0x5BCC, 2), Some(3531));
1426 assert_eq!(cmap.lookup_cid_code(0xD884DF50, 4), Some(19130));
1427 }
1428
1429 #[test]
1430 fn embedded_identity_h() {
1431 let data = load_embedded(CMapName::IdentityH).expect("embedded Identity-H not found");
1432 let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse Identity-H cmap");
1433 assert_eq!(cmap.metadata().writing_mode, None);
1434 assert_eq!(
1435 cmap.metadata().character_collection,
1436 Some(CharacterCollection {
1437 family: CidFamily::AdobeIdentity,
1438 supplement: 0,
1439 })
1440 );
1441 assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
1442 assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x41));
1443 assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1444 }
1445
1446 #[test]
1447 fn embedded_identity_v() {
1448 let data = load_embedded(CMapName::IdentityV).expect("embedded Identity-V not found");
1449 let cmap = CMap::parse(data, get_embedded_cmap).expect("failed to parse Identity-V cmap");
1450 assert_eq!(cmap.metadata().writing_mode, Some(WritingMode::Vertical));
1451 assert_eq!(
1452 cmap.metadata().character_collection,
1453 Some(CharacterCollection {
1454 family: CidFamily::AdobeIdentity,
1455 supplement: 0,
1456 })
1457 );
1458 assert_eq!(cmap.lookup_cid_code(0x0000, 2), Some(0));
1459 assert_eq!(cmap.lookup_cid_code(0x0041, 2), Some(0x41));
1460 assert_eq!(cmap.lookup_cid_code(0xFFFF, 2), Some(0xFFFF));
1461 }
1462
1463 #[test]
1466 fn embedded_adobe_gb1_ucs2() {
1467 let data = load_embedded(CMapName::AdobeGb1Ucs2).unwrap();
1468 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1469 assert_eq!(
1470 cmap.metadata().character_collection,
1471 Some(CharacterCollection {
1472 family: CidFamily::Custom {
1473 registry: b"Adobe".to_vec(),
1474 ordering: b"Adobe_GB1_UCS2".to_vec(),
1475 },
1476 supplement: 5,
1477 })
1478 );
1479
1480 assert_eq!(
1481 cmap.lookup_bf_string(0x0063),
1482 Some(BfString::Char('\u{00B7}'))
1483 );
1484 assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char(' ')));
1485 assert_eq!(cmap.lookup_bf_string(0x0022), Some(BfString::Char('A')));
1486 assert_eq!(cmap.lookup_bf_string(0x005F), Some(BfString::Char('~')));
1487 assert_eq!(
1488 cmap.lookup_bf_string(0x560F),
1489 Some(BfString::String(String::from("\u{90CE}\u{FE00}")))
1490 );
1491 }
1492
1493 #[test]
1494 fn embedded_adobe_japan1_ucs2() {
1495 let data = load_embedded(CMapName::AdobeJapan1Ucs2).unwrap();
1496 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1497 assert_eq!(
1498 cmap.metadata().character_collection,
1499 Some(CharacterCollection {
1500 family: CidFamily::Custom {
1501 registry: b"Adobe".to_vec(),
1502 ordering: b"Adobe_Japan1_UCS2".to_vec(),
1503 },
1504 supplement: 7,
1505 })
1506 );
1507
1508 assert_eq!(
1509 cmap.lookup_bf_string(0x003D),
1510 Some(BfString::Char('\u{00A5}'))
1511 );
1512 assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char(' ')));
1513 assert_eq!(cmap.lookup_bf_string(0x003C), Some(BfString::Char('[')));
1514 assert_eq!(
1515 cmap.lookup_bf_string(0x0476),
1516 Some(BfString::String(String::from("\u{82A6}\u{E0100}")))
1517 );
1518 assert_eq!(
1519 cmap.lookup_bf_string(0x265B),
1520 Some(BfString::String(String::from("10/11")))
1521 );
1522 assert_eq!(
1523 cmap.lookup_bf_string(0x2E6B),
1524 Some(BfString::String(String::from("オングストローム")))
1525 );
1526 }
1527
1528 #[test]
1529 fn embedded_adobe_korea1_ucs2() {
1530 let data = load_embedded(CMapName::AdobeKorea1Ucs2).unwrap();
1531 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1532 assert_eq!(
1533 cmap.metadata().character_collection,
1534 Some(CharacterCollection {
1535 family: CidFamily::Custom {
1536 registry: b"Adobe".to_vec(),
1537 ordering: b"Adobe_Korea1_UCS2".to_vec(),
1538 },
1539 supplement: 2,
1540 })
1541 );
1542
1543 assert_eq!(
1544 cmap.lookup_bf_string(0x0060),
1545 Some(BfString::Char('\u{20A9}'))
1546 );
1547 assert_eq!(
1548 cmap.lookup_bf_string(0x2072),
1549 Some(BfString::String(String::from("[10]")))
1550 );
1551 assert_eq!(
1552 cmap.lookup_bf_string(0x2073),
1553 Some(BfString::String(String::from("[11]")))
1554 );
1555 }
1556
1557 #[test]
1558 fn embedded_adobe_cns1_ucs2() {
1559 let data = load_embedded(CMapName::AdobeCns1Ucs2).unwrap();
1560 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1561 assert_eq!(
1562 cmap.metadata().character_collection,
1563 Some(CharacterCollection {
1564 family: CidFamily::Custom {
1565 registry: b"Adobe".to_vec(),
1566 ordering: b"Adobe_CNS1_UCS2".to_vec(),
1567 },
1568 supplement: 7,
1569 })
1570 );
1571
1572 assert_eq!(
1573 cmap.lookup_bf_string(0x0060),
1574 Some(BfString::Char('\u{00A9}'))
1575 );
1576 assert_eq!(cmap.lookup_bf_string(0x0001), Some(BfString::Char(' ')));
1577 assert_eq!(cmap.lookup_bf_string(0x005F), Some(BfString::Char('~')));
1578 assert_eq!(
1579 cmap.lookup_bf_string(0x36B0),
1580 Some(BfString::Char('\u{200CC}'))
1581 );
1582 }
1583
1584 #[test]
1585 fn embedded_eten_b5_h() {
1586 let data = load_embedded(CMapName::ETenB5H).unwrap();
1587 let cmap = CMap::parse(data, get_embedded_cmap).unwrap();
1588 assert_eq!(
1589 cmap.metadata().character_collection,
1590 Some(CharacterCollection {
1591 family: CidFamily::AdobeCNS1,
1592 supplement: 0,
1593 })
1594 );
1595 assert_eq!(cmap.lookup_cid_code(0x20, 1), Some(13648));
1596 assert_eq!(cmap.lookup_cid_code(0x7E, 1), Some(13648 + 0x7E - 0x20));
1597 assert_eq!(cmap.lookup_cid_code(0xA140, 2), Some(99));
1598 assert_eq!(cmap.lookup_cid_code(0xA158, 2), Some(99 + 0x58 - 0x40));
1599 assert_eq!(cmap.lookup_cid_code(0xD040, 2), Some(7094));
1600 assert_eq!(cmap.lookup_cid_code(0xF9FE, 2), Some(14056 + 0xFE - 0xD6));
1601 }
1602}