1mod glyph;
9mod gsub;
10mod transform;
11
12pub use transform::{CidTransform, cid_transform_to_float, japan1_transform};
13
14use crate::descriptor::{self, FontDescriptor};
15use crate::fallback::GlyphFallback;
16use crate::glyphs::{Charmap, Face, GlyphSource};
17use crate::subst::{self, CodePage, FontRequest, SubstFont, SubstitutionOptions};
18use crate::tounicode::ToUnicode;
19use crate::widths::CidWidths;
20use crate::{CharCode, CharItem, Cid, Error, FontCache, FontId, Gid, names, widths};
21use pdfrum_cmap::{CMap, CidCoding, CidSet};
22use pdfrum_common::kurbo::Rect;
23use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
24use pdfrum_object::{Dict, Object, Resolve, Resolved};
25use smallvec::SmallVec;
26use std::sync::OnceLock;
27
28pub use crate::widths::VerticalMetrics;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum CidToGid {
33 Identity,
36 Stream(Box<[u8]>),
38 ViaCharmap,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum CidFontKind {
45 Type1,
47 TrueType,
49}
50
51#[derive(Debug)]
53pub struct Type0Font {
54 pub(crate) id: FontId,
56 pub(crate) cmap: CMap,
58 pub(crate) glyphs: GlyphSource,
60 pub(crate) charset: CidSet,
62 pub(crate) cid_to_gid: CidToGid,
64 pub(crate) widths: CidWidths,
66 pub(crate) vertical: Option<VerticalMetrics>,
68 pub(crate) to_unicode: Option<ToUnicode>,
70 pub(crate) descriptor: FontDescriptor,
72 pub(crate) subst: Option<SubstFont>,
74 pub(crate) kind: CidFontKind,
76 pub(crate) embedded: bool,
78 pub(crate) base_font_name: Vec<u8>,
80 pub(crate) adobe_courier_std: bool,
83 #[cfg(test)]
85 pub(crate) ansi_widths_fixed: bool,
86 gsub: gsub::VerticalSubst,
88 pub(crate) fallback: OnceLock<Option<GlyphFallback>>,
91}
92
93impl Type0Font {
94 #[must_use]
96 pub(crate) fn cid_from_charcode(&self, code: CharCode) -> Cid {
97 self.cmap.cid(code)
98 }
99
100 #[must_use]
105 pub(crate) fn glyph_from_charcode(&self, code: CharCode) -> (Option<Gid>, bool) {
106 glyph::resolve(self, code)
107 }
108
109 #[must_use]
111 pub(crate) fn char_width(&self, code: CharCode) -> f32 {
112 self.widths.width(code, self.cid_from_charcode(code))
113 }
114
115 #[must_use]
117 pub(crate) fn vert_width(&self, code: CharCode) -> f32 {
118 match &self.vertical {
119 Some(v) => v.width(self.cid_from_charcode(code)),
120 None => -1000.0,
121 }
122 }
123
124 #[must_use]
129 pub(crate) fn vert_origin(&self, code: CharCode) -> (f32, f32) {
130 let cid = self.cid_from_charcode(code);
131 match &self.vertical {
132 Some(v) => v.origin(cid, &self.widths),
133 None => ((self.widths.width(code, cid) / 2.0).trunc(), 880.0),
134 }
135 }
136
137 #[must_use]
139 pub(crate) fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
140 if let Some(tu) = &self.to_unicode {
141 let chars = tu.lookup(code);
142 if !chars.is_empty() {
143 return chars;
144 }
145 }
146 match self.scalar_unicode(code) {
147 0 => SmallVec::new(),
148 u => char::from_u32(u32::from(u))
149 .map(|c| SmallVec::from_slice(&[c]))
150 .unwrap_or_default(),
151 }
152 }
153
154 #[must_use]
161 pub(crate) fn scalar_unicode(&self, code: CharCode) -> u16 {
162 match self.cmap.coding() {
163 CidCoding::Ucs2 | CidCoding::Utf16 => return (code.0 & 0xffff) as u16,
164 CidCoding::Cid => {
165 if !pdfrum_cmap::has_cid2unicode(self.charset) {
166 return 0;
167 }
168 let cid = Cid((code.0 & 0xffff) as u16);
169 return pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16);
170 }
171 _ => {}
172 }
173 if pdfrum_cmap::has_cid2unicode(self.charset) && self.cmap.is_loaded() {
174 let cid = self.cid_from_charcode(code);
175 return pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16);
176 }
177 if !self.cmap.has_static_map() {
180 return 0;
181 }
182 let cid = self.cid_from_charcode(code);
183 if cid.0 == 0 {
184 return 0;
185 }
186 pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16)
187 }
188
189 #[must_use]
191 pub(crate) fn charcode_from_unicode(&self, unicode: char) -> CharCode {
192 if let Some(tu) = &self.to_unicode {
193 let c = tu.reverse(unicode);
194 if c.0 != 0 {
195 return c;
196 }
197 }
198 match self.cmap.coding() {
199 CidCoding::Unknown => return CharCode(0),
200 CidCoding::Ucs2 | CidCoding::Utf16 => return CharCode(unicode as u32),
201 CidCoding::Cid => {
202 if !pdfrum_cmap::has_cid2unicode(self.charset) {
203 return CharCode(0);
204 }
205 for cid in 0..=u16::MAX {
208 if pdfrum_cmap::unicode_from_cid(self.charset, Cid(cid)) == Some(unicode) {
209 return CharCode(u32::from(cid));
210 }
211 }
212 }
213 _ => {}
214 }
215 if (unicode as u32) < 0x80 {
216 return CharCode(unicode as u32);
217 }
218 if self.cmap.coding() == CidCoding::Cid {
219 return CharCode(0);
220 }
221 pdfrum_cmap::charcode_from_unicode(&self.cmap, unicode)
222 }
223
224 #[must_use]
226 pub(crate) fn is_unicode_compatible(&self) -> bool {
227 if pdfrum_cmap::has_cid2unicode(self.charset) && self.cmap.is_loaded() {
228 return true;
229 }
230 self.cmap.coding() != CidCoding::Unknown
231 }
232
233 #[must_use]
235 pub(crate) fn is_vertical(&self) -> bool {
236 self.cmap.is_vertical()
237 }
238
239 #[must_use]
242 pub(crate) fn char_bbox(&self, code: CharCode) -> Rect {
243 let (gid, vertical) = self.glyph_from_charcode(code);
244 let Some(gid) = gid else { return Rect::ZERO };
245 let Some(bbox) = self.glyphs.glyph_bbox(gid) else {
246 return Rect::ZERO;
247 };
248 let bbox = grow_top(bbox);
249 if vertical {
252 return bbox;
253 }
254 match self.japan1_transform(code) {
255 Some(t) => transform::apply(t, bbox),
256 None => bbox,
257 }
258 }
259
260 #[must_use]
265 pub(crate) fn japan1_transform(&self, code: CharCode) -> Option<CidTransform> {
266 if self.charset != CidSet::Japan1 || self.embedded {
267 return None;
268 }
269 japan1_transform(self.cid_from_charcode(code))
270 }
271
272 pub(crate) fn char_item(&self, code: CharCode) -> CharItem {
274 let (gid, vertical_glyph) = self.glyph_from_charcode(code);
275 CharItem {
276 code,
277 cid: Some(self.cid_from_charcode(code)),
278 gid,
279 unicode: self.unicode_from_charcode(code),
280 width: if self.is_vertical() {
281 self.vert_width(code)
282 } else {
283 self.char_width(code)
284 },
285 vertical_glyph,
286 }
287 }
288
289 fn gsub(&self) -> &gsub::VerticalSubst {
290 &self.gsub
291 }
292}
293
294fn grow_top(bbox: Rect) -> Rect {
324 const MAX_RECT_TOP: i32 = 2_114_445_437;
326
327 let top = bbox.y1 as i32;
328 let grown = if top <= MAX_RECT_TOP {
329 top + top / 64
330 } else {
331 i32::MAX
332 };
333 Rect::new(bbox.x0, bbox.y0, bbox.x1, f64::from(grown))
334}
335
336fn use_cmap_parent(
337 dict: &Dict,
338 r: &impl Resolve,
339 limits: &Limits,
340 diags: &mut Diagnostics,
341) -> Option<CMap> {
342 if let Some(stream) = dict.stream(names::USE_CMAP, r) {
343 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
344 return Some(pdfrum_cmap::parse_embedded(&bytes, limits, diags));
348 }
349 let resolved = dict.get(names::USE_CMAP, r)?;
350 let name = resolved.as_name()?;
351 Some(pdfrum_cmap::from_encoding_name(name, diags))
352}
353
354pub(crate) fn load(
363 dict: &Dict,
364 r: &impl Resolve,
365 cache: &FontCache,
366 opts: &SubstitutionOptions,
367 limits: &Limits,
368 diags: &mut Diagnostics,
369) -> Result<Type0Font, Error> {
370 let descendants = dict
371 .array(names::DESCENDANT_FONTS, r)
372 .ok_or(Error::BadDescendantFonts)?;
373 if descendants.len() != 1 {
374 return Err(Error::BadDescendantFonts);
375 }
376 let cid_dict = descendants.dict_at(0, r).ok_or(Error::BadDescendantFonts)?;
377
378 let base_font_name = cid_dict
379 .name(names::BASE_FONT)
380 .map(|n| n.as_bytes().to_vec())
381 .unwrap_or_default();
382
383 let encoding = dict.raw(names::ENCODING).ok_or(Error::BadCidEncoding)?;
384 let kind = match cid_dict.name(names::SUBTYPE).map(|n| n.as_bytes().to_vec()) {
385 Some(s) if s == b"CIDFontType0" => CidFontKind::Type1,
386 _ => CidFontKind::TrueType,
387 };
388
389 let cmap = match encoding {
392 Object::Name(name) => pdfrum_cmap::from_encoding_name(name, diags),
393 Object::Stream(_) | Object::Ref(_) => {
394 if let Some(stream) = dict.stream(names::ENCODING, r) {
395 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
396 let cmap = pdfrum_cmap::parse_embedded(&bytes, limits, diags);
397 match use_cmap_parent(&stream.dict, r, limits, diags) {
398 Some(parent) => pdfrum_cmap::inherit_from(cmap, parent, 0, limits, diags),
399 None => cmap,
400 }
401 } else {
402 let resolved = dict.get(names::ENCODING, r);
405 match resolved.as_ref().and_then(|o| o.as_name()) {
406 Some(name) => pdfrum_cmap::from_encoding_name(name, diags),
407 None => return Err(Error::BadCidEncoding),
408 }
409 }
410 }
411 _ => return Err(Error::BadCidEncoding),
412 };
413
414 Ok(build(
415 &cid_dict,
416 dict,
417 cmap,
418 kind,
419 base_font_name,
420 r,
421 cache,
422 opts,
423 limits,
424 diags,
425 false,
426 ))
427}
428
429pub(crate) fn load_gb2312(
434 dict: &Dict,
435 r: &impl Resolve,
436 cache: &FontCache,
437 opts: &SubstitutionOptions,
438 limits: &Limits,
439 diags: &mut Diagnostics,
440) -> Result<Type0Font, Error> {
441 let cmap = pdfrum_cmap::predefined(&pdfrum_object::Name::from("GBK-EUC-H"))
442 .ok_or(Error::BadCidEncoding)?;
443 let base_font_name = dict
444 .name(names::BASE_FONT)
445 .map(|n| n.as_bytes().to_vec())
446 .unwrap_or_default();
447 Ok(build(
448 dict,
449 dict,
450 cmap,
451 CidFontKind::TrueType,
452 base_font_name,
453 r,
454 cache,
455 opts,
456 limits,
457 diags,
458 true,
459 ))
460}
461
462#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
466fn build(
467 cid_dict: &Dict,
468 font_dict: &Dict,
469 cmap: CMap,
470 kind: CidFontKind,
471 base_font_name: Vec<u8>,
472 r: &impl Resolve,
473 cache: &FontCache,
474 opts: &SubstitutionOptions,
475 limits: &Limits,
476 diags: &mut Diagnostics,
477 gb2312: bool,
478) -> Type0Font {
479 let adobe_courier_std = matches!(
482 base_font_name.as_slice(),
483 b"CourierStd" | b"CourierStd-Bold" | b"CourierStd-BoldOblique" | b"CourierStd-Oblique"
484 );
485
486 let desc = cid_dict.dict(names::FONT_DESCRIPTOR, r);
487 let mut descriptor = FontDescriptor::default();
488 if let Some(d) = &desc {
489 descriptor = descriptor::load(d, r);
490 }
491 let (mut glyphs, mut embedded) =
492 crate::simple::load_font_program(desc.as_ref(), r, limits, diags);
493
494 let mut charset = if gb2312 { CidSet::Gb1 } else { cmap.charset() };
496 if charset == CidSet::Unknown
497 && let Some(info) = cid_dict.dict(names::CID_SYSTEM_INFO, r)
498 && let Some(ordering) = info.byte_string(names::ORDERING, r)
499 {
500 charset = pdfrum_cmap::charset_from_ordering(&ordering);
501 }
502
503 let mut widths_table = CidWidths::load(cid_dict, r, diags);
504 if gb2312 {
505 widths_table.set_ansi_widths_fixed();
506 }
507
508 let mut subst_font = None;
509 if !embedded {
510 let request = FontRequest {
511 name: base_font_name.clone(),
512 is_truetype: kind == CidFontKind::TrueType,
513 flags: descriptor.flags,
514 weight: descriptor
517 .stem_v
518 .checked_mul(5)
519 .filter(|w| *w > 0)
520 .unwrap_or(400),
521 italic_angle: descriptor.italic_angle,
522 code_page: CodePage::for_cid_set(charset),
523 vertical: cmap.is_vertical(),
524 };
525 let s = substitute(&request, opts, diags);
526 glyphs = s.glyphs;
527 subst_font = Some(s.subst);
528 }
529 if !glyphs.is_some() {
530 embedded = false;
531 }
532
533 let cid_to_gid = match cid_dict
540 .get(names::CID_TO_GID_MAP, r)
541 .as_ref()
542 .map(Resolved::get)
543 {
544 Some(Object::Name(n)) if n.as_bytes() == b"Identity" && embedded => CidToGid::Identity,
545 Some(Object::Stream(_)) => {
546 match cid_dict.stream(names::CID_TO_GID_MAP, r) {
547 Some(s) => {
548 let bytes = pdfrum_filters::decode_chain(&s, 0, r, limits, diags).data;
549 if bytes.len() < glyphs.num_glyphs() as usize * 2 {
552 diags.record(Severity::Suspicious, DiagKind::CidToGidStreamShort, None);
553 }
554 CidToGid::Stream(bytes.into_boxed_slice())
555 }
556 None => CidToGid::ViaCharmap,
557 }
558 }
559 _ => CidToGid::ViaCharmap,
560 };
561
562 let vertical = if cmap.is_vertical() {
563 Some(widths::VerticalMetrics::load(cid_dict, r, diags))
564 } else {
565 None
566 };
567
568 let to_unicode = crate::simple::load_to_unicode(font_dict, r, limits, diags);
569
570 let metrics = match &glyphs {
571 GlyphSource::Fontations(f) => f.metrics(),
572 GlyphSource::Type1(f) => Some(descriptor::FaceMetrics {
573 upem: f.units_per_em(),
574 bbox_left: f.bbox().x0 as i64,
575 bbox_top: f.bbox().y1 as i64,
576 bbox_right: f.bbox().x1 as i64,
577 bbox_bottom: f.bbox().y0 as i64,
578 ascender: f.bbox().y1 as i64,
579 descender: f.bbox().y0 as i64,
580 }),
581 GlyphSource::None => None,
582 };
583 descriptor::check_font_metrics(&mut descriptor, metrics, |_| Rect::ZERO);
584
585 let gsub = if cmap.is_vertical() {
586 gsub::VerticalSubst::parse(&glyphs, diags)
587 } else {
588 gsub::VerticalSubst::none()
589 };
590
591 Type0Font {
592 id: cache.next_id(),
593 cmap,
594 glyphs,
595 charset,
596 cid_to_gid,
597 widths: widths_table,
598 vertical,
599 to_unicode,
600 descriptor,
601 subst: subst_font,
602 kind,
603 embedded,
604 base_font_name,
605 adobe_courier_std,
606 #[cfg(test)]
607 ansi_widths_fixed: gb2312,
608 gsub,
609 fallback: OnceLock::new(),
610 }
611}
612
613fn substitute(
614 request: &FontRequest,
615 opts: &SubstitutionOptions,
616 diags: &mut Diagnostics,
617) -> subst::Substitution {
618 subst::resolve_with_options(request, opts, diags)
619}
620
621pub(crate) fn cid_charmap(glyphs: &GlyphSource, coding: CidCoding) -> Charmap {
633 let charmaps = glyphs.charmaps();
634
635 if let Some(wanted) = legacy_encoding_id(coding)
637 && let Some(i) = charmaps
638 .iter()
639 .position(|c| c.platform == 3 && c.encoding == wanted)
640 {
641 return Charmap::Index(i);
642 }
643 if let Some(i) = charmaps.iter().position(|c| c.is_unicode()) {
645 return Charmap::Index(i);
646 }
647 if charmaps.is_empty() {
649 Charmap::None
650 } else {
651 Charmap::Index(0)
652 }
653}
654
655fn legacy_encoding_id(coding: CidCoding) -> Option<u16> {
661 Some(match coding {
662 CidCoding::Gb => 3,
663 CidCoding::Big5 => 4,
664 CidCoding::Jis => 2,
665 CidCoding::Korea => 6,
666 CidCoding::Unknown | CidCoding::Ucs2 | CidCoding::Cid | CidCoding::Utf16 => return None,
668 })
669}
670
671pub(crate) fn face_of(glyphs: &GlyphSource) -> Option<&Face> {
673 match glyphs {
674 GlyphSource::Fontations(f) => Some(f),
675 GlyphSource::Type1(_) | GlyphSource::None => None,
676 }
677}
678
679#[cfg(test)]
680#[path = "cid_tests.rs"]
681mod tests;