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::glyphs::{Charmap, Face, GlyphSource};
16use crate::subst::{self, CodePage, FontRequest, SubstFont, SubstitutionOptions};
17use crate::tounicode::ToUnicode;
18use crate::widths::CidWidths;
19use crate::{CharCode, CharItem, Cid, Error, FontCache, FontId, Gid, names, widths};
20use pdfrum_cmap::{CMap, CidCoding, CidSet};
21use pdfrum_common::kurbo::Rect;
22use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
23use pdfrum_object::{Dict, Object, Resolve};
24use smallvec::SmallVec;
25
26pub use crate::widths::VerticalMetrics;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum CidToGid {
31 Identity,
34 Stream(Box<[u8]>),
36 ViaCharmap,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum CidFontKind {
43 Type1,
45 TrueType,
47}
48
49#[derive(Debug)]
51pub struct Type0Font {
52 pub(crate) id: FontId,
54 pub(crate) cmap: CMap,
56 pub(crate) glyphs: GlyphSource,
58 pub(crate) charset: CidSet,
60 pub(crate) cid_to_gid: CidToGid,
62 pub(crate) widths: CidWidths,
64 pub(crate) vertical: Option<VerticalMetrics>,
66 pub(crate) to_unicode: Option<ToUnicode>,
68 pub(crate) descriptor: FontDescriptor,
70 pub(crate) subst: Option<SubstFont>,
72 pub(crate) kind: CidFontKind,
74 pub(crate) embedded: bool,
76 pub(crate) base_font_name: Vec<u8>,
78 pub(crate) adobe_courier_std: bool,
81 #[cfg(test)]
83 pub(crate) ansi_widths_fixed: bool,
84 gsub: gsub::VerticalSubst,
86}
87
88impl Type0Font {
89 #[must_use]
91 pub(crate) fn cid_from_charcode(&self, code: CharCode) -> Cid {
92 self.cmap.cid(code)
93 }
94
95 #[must_use]
100 pub(crate) fn glyph_from_charcode(&self, code: CharCode) -> (Option<Gid>, bool) {
101 glyph::resolve(self, code)
102 }
103
104 #[must_use]
106 pub(crate) fn char_width(&self, code: CharCode) -> f32 {
107 self.widths.width(code, self.cid_from_charcode(code))
108 }
109
110 #[must_use]
112 pub(crate) fn vert_width(&self, code: CharCode) -> f32 {
113 match &self.vertical {
114 Some(v) => v.width(self.cid_from_charcode(code)),
115 None => -1000.0,
116 }
117 }
118
119 #[must_use]
124 pub(crate) fn vert_origin(&self, code: CharCode) -> (f32, f32) {
125 let cid = self.cid_from_charcode(code);
126 match &self.vertical {
127 Some(v) => v.origin(cid, &self.widths),
128 None => ((self.widths.width(code, cid) / 2.0).trunc(), 880.0),
129 }
130 }
131
132 #[must_use]
134 pub(crate) fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
135 if let Some(tu) = &self.to_unicode {
136 let chars = tu.lookup(code);
137 if !chars.is_empty() {
138 return chars;
139 }
140 }
141 match self.scalar_unicode(code) {
142 0 => SmallVec::new(),
143 u => char::from_u32(u32::from(u))
144 .map(|c| SmallVec::from_slice(&[c]))
145 .unwrap_or_default(),
146 }
147 }
148
149 #[must_use]
156 pub(crate) fn scalar_unicode(&self, code: CharCode) -> u16 {
157 match self.cmap.coding() {
158 CidCoding::Ucs2 | CidCoding::Utf16 => return (code.0 & 0xffff) as u16,
159 CidCoding::Cid => {
160 if !pdfrum_cmap::has_cid2unicode(self.charset) {
161 return 0;
162 }
163 let cid = Cid((code.0 & 0xffff) as u16);
164 return pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16);
165 }
166 _ => {}
167 }
168 if pdfrum_cmap::has_cid2unicode(self.charset) && self.cmap.is_loaded() {
169 let cid = self.cid_from_charcode(code);
170 return pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16);
171 }
172 if !self.cmap.has_static_map() {
175 return 0;
176 }
177 let cid = self.cid_from_charcode(code);
178 if cid.0 == 0 {
179 return 0;
180 }
181 pdfrum_cmap::unicode_from_cid(self.charset, cid).map_or(0, |c| c as u16)
182 }
183
184 #[must_use]
186 pub(crate) fn charcode_from_unicode(&self, unicode: char) -> CharCode {
187 if let Some(tu) = &self.to_unicode {
188 let c = tu.reverse(unicode);
189 if c.0 != 0 {
190 return c;
191 }
192 }
193 match self.cmap.coding() {
194 CidCoding::Unknown => return CharCode(0),
195 CidCoding::Ucs2 | CidCoding::Utf16 => return CharCode(unicode as u32),
196 CidCoding::Cid => {
197 if !pdfrum_cmap::has_cid2unicode(self.charset) {
198 return CharCode(0);
199 }
200 for cid in 0..=u16::MAX {
203 if pdfrum_cmap::unicode_from_cid(self.charset, Cid(cid)) == Some(unicode) {
204 return CharCode(u32::from(cid));
205 }
206 }
207 }
208 _ => {}
209 }
210 if (unicode as u32) < 0x80 {
211 return CharCode(unicode as u32);
212 }
213 if self.cmap.coding() == CidCoding::Cid {
214 return CharCode(0);
215 }
216 pdfrum_cmap::charcode_from_unicode(&self.cmap, unicode)
217 }
218
219 #[must_use]
221 pub(crate) fn is_unicode_compatible(&self) -> bool {
222 if pdfrum_cmap::has_cid2unicode(self.charset) && self.cmap.is_loaded() {
223 return true;
224 }
225 self.cmap.coding() != CidCoding::Unknown
226 }
227
228 #[must_use]
230 pub(crate) fn is_vertical(&self) -> bool {
231 self.cmap.is_vertical()
232 }
233
234 #[must_use]
237 pub(crate) fn char_bbox(&self, code: CharCode) -> Rect {
238 let (gid, vertical) = self.glyph_from_charcode(code);
239 let Some(gid) = gid else { return Rect::ZERO };
240 let Some(bbox) = self.glyphs.glyph_bbox(gid) else {
241 return Rect::ZERO;
242 };
243 if vertical {
246 return bbox;
247 }
248 match self.japan1_transform(code) {
249 Some(t) => transform::apply(t, bbox),
250 None => bbox,
251 }
252 }
253
254 #[must_use]
259 pub(crate) fn japan1_transform(&self, code: CharCode) -> Option<CidTransform> {
260 if self.charset != CidSet::Japan1 || self.embedded {
261 return None;
262 }
263 japan1_transform(self.cid_from_charcode(code))
264 }
265
266 pub(crate) fn char_item(&self, code: CharCode) -> CharItem {
268 let (gid, vertical_glyph) = self.glyph_from_charcode(code);
269 CharItem {
270 code,
271 cid: Some(self.cid_from_charcode(code)),
272 gid,
273 unicode: self.unicode_from_charcode(code),
274 width: if self.is_vertical() {
275 self.vert_width(code)
276 } else {
277 self.char_width(code)
278 },
279 vertical_glyph,
280 }
281 }
282
283 fn gsub(&self) -> &gsub::VerticalSubst {
284 &self.gsub
285 }
286}
287
288fn use_cmap_parent(
308 dict: &Dict,
309 r: &impl Resolve,
310 limits: &Limits,
311 diags: &mut Diagnostics,
312) -> Option<CMap> {
313 if let Some(stream) = dict.stream(names::USE_CMAP, r) {
314 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
315 return Some(pdfrum_cmap::parse_embedded(&bytes, limits, diags));
319 }
320 let resolved = dict.get(names::USE_CMAP, r)?;
321 let name = resolved.as_name()?;
322 Some(pdfrum_cmap::from_encoding_name(name, diags))
323}
324
325pub(crate) fn load(
334 dict: &Dict,
335 r: &impl Resolve,
336 cache: &FontCache,
337 opts: &SubstitutionOptions,
338 limits: &Limits,
339 diags: &mut Diagnostics,
340) -> Result<Type0Font, Error> {
341 let descendants = dict
342 .array(names::DESCENDANT_FONTS, r)
343 .ok_or(Error::BadDescendantFonts)?;
344 if descendants.len() != 1 {
345 return Err(Error::BadDescendantFonts);
346 }
347 let cid_dict = descendants.dict_at(0, r).ok_or(Error::BadDescendantFonts)?;
348
349 let base_font_name = cid_dict
350 .name(names::BASE_FONT)
351 .map(|n| n.as_bytes().to_vec())
352 .unwrap_or_default();
353
354 let encoding = dict.raw(names::ENCODING).ok_or(Error::BadCidEncoding)?;
355 let kind = match cid_dict.name(names::SUBTYPE).map(|n| n.as_bytes().to_vec()) {
356 Some(s) if s == b"CIDFontType0" => CidFontKind::Type1,
357 _ => CidFontKind::TrueType,
358 };
359
360 let cmap = match encoding {
363 Object::Name(name) => pdfrum_cmap::from_encoding_name(name, diags),
364 Object::Stream(_) | Object::Ref(_) => {
365 if let Some(stream) = dict.stream(names::ENCODING, r) {
366 let bytes = pdfrum_filters::decode_chain(&stream, 0, r, limits, diags).data;
367 let cmap = pdfrum_cmap::parse_embedded(&bytes, limits, diags);
368 match use_cmap_parent(&stream.dict, r, limits, diags) {
369 Some(parent) => pdfrum_cmap::inherit_from(cmap, parent, 0, limits, diags),
370 None => cmap,
371 }
372 } else {
373 let resolved = dict.get(names::ENCODING, r);
376 match resolved.as_ref().and_then(|o| o.as_name()) {
377 Some(name) => pdfrum_cmap::from_encoding_name(name, diags),
378 None => return Err(Error::BadCidEncoding),
379 }
380 }
381 }
382 _ => return Err(Error::BadCidEncoding),
383 };
384
385 Ok(build(
386 &cid_dict,
387 dict,
388 cmap,
389 kind,
390 base_font_name,
391 r,
392 cache,
393 opts,
394 limits,
395 diags,
396 false,
397 ))
398}
399
400pub(crate) fn load_gb2312(
405 dict: &Dict,
406 r: &impl Resolve,
407 cache: &FontCache,
408 opts: &SubstitutionOptions,
409 limits: &Limits,
410 diags: &mut Diagnostics,
411) -> Result<Type0Font, Error> {
412 let cmap = pdfrum_cmap::predefined(&pdfrum_object::Name::from("GBK-EUC-H"))
413 .ok_or(Error::BadCidEncoding)?;
414 let base_font_name = dict
415 .name(names::BASE_FONT)
416 .map(|n| n.as_bytes().to_vec())
417 .unwrap_or_default();
418 Ok(build(
419 dict,
420 dict,
421 cmap,
422 CidFontKind::TrueType,
423 base_font_name,
424 r,
425 cache,
426 opts,
427 limits,
428 diags,
429 true,
430 ))
431}
432
433#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
437fn build(
438 cid_dict: &Dict,
439 font_dict: &Dict,
440 cmap: CMap,
441 kind: CidFontKind,
442 base_font_name: Vec<u8>,
443 r: &impl Resolve,
444 cache: &FontCache,
445 opts: &SubstitutionOptions,
446 limits: &Limits,
447 diags: &mut Diagnostics,
448 gb2312: bool,
449) -> Type0Font {
450 let adobe_courier_std = matches!(
453 base_font_name.as_slice(),
454 b"CourierStd" | b"CourierStd-Bold" | b"CourierStd-BoldOblique" | b"CourierStd-Oblique"
455 );
456
457 let desc = cid_dict.dict(names::FONT_DESCRIPTOR, r);
458 let mut descriptor = FontDescriptor::default();
459 if let Some(d) = &desc {
460 descriptor = descriptor::load(d, r);
461 }
462 let (mut glyphs, mut embedded) =
463 crate::simple::load_font_program(desc.as_ref(), r, limits, diags);
464
465 let mut charset = if gb2312 { CidSet::Gb1 } else { cmap.charset() };
467 if charset == CidSet::Unknown
468 && let Some(info) = cid_dict.dict(names::CID_SYSTEM_INFO, r)
469 && let Some(ordering) = info.byte_string(names::ORDERING, r)
470 {
471 charset = pdfrum_cmap::charset_from_ordering(&ordering);
472 }
473
474 let mut widths_table = CidWidths::load(cid_dict, r, diags);
475 if gb2312 {
476 widths_table.set_ansi_widths_fixed();
477 }
478
479 let mut subst_font = None;
480 if !embedded {
481 let request = FontRequest {
482 name: base_font_name.clone(),
483 is_truetype: kind == CidFontKind::TrueType,
484 flags: descriptor.flags,
485 weight: descriptor
488 .stem_v
489 .checked_mul(5)
490 .filter(|w| *w > 0)
491 .unwrap_or(400),
492 italic_angle: descriptor.italic_angle,
493 code_page: CodePage::for_cid_set(charset),
494 vertical: cmap.is_vertical(),
495 };
496 let s = substitute(&request, opts, diags);
497 glyphs = s.glyphs;
498 subst_font = Some(s.subst);
499 }
500 if !glyphs.is_some() {
501 embedded = false;
502 }
503
504 let cid_to_gid = match cid_dict.raw(names::CID_TO_GID_MAP) {
508 Some(Object::Name(n)) if n.as_bytes() == b"Identity" && embedded => CidToGid::Identity,
509 Some(Object::Stream(_) | Object::Ref(_)) => {
510 match cid_dict.stream(names::CID_TO_GID_MAP, r) {
511 Some(s) => {
512 let bytes = pdfrum_filters::decode_chain(&s, 0, r, limits, diags).data;
513 if bytes.len() < glyphs.num_glyphs() as usize * 2 {
516 diags.record(Severity::Suspicious, DiagKind::CidToGidStreamShort, None);
517 }
518 CidToGid::Stream(bytes.into_boxed_slice())
519 }
520 None => CidToGid::ViaCharmap,
521 }
522 }
523 _ => CidToGid::ViaCharmap,
524 };
525
526 let vertical = if cmap.is_vertical() {
527 Some(widths::VerticalMetrics::load(cid_dict, r, diags))
528 } else {
529 None
530 };
531
532 let to_unicode = crate::simple::load_to_unicode(font_dict, r, limits, diags);
533
534 let metrics = match &glyphs {
535 GlyphSource::Fontations(f) => f.metrics(),
536 GlyphSource::Type1(f) => Some(descriptor::FaceMetrics {
537 upem: f.units_per_em(),
538 bbox_left: f.bbox().x0 as i64,
539 bbox_top: f.bbox().y1 as i64,
540 bbox_right: f.bbox().x1 as i64,
541 bbox_bottom: f.bbox().y0 as i64,
542 ascender: f.bbox().y1 as i64,
543 descender: f.bbox().y0 as i64,
544 }),
545 GlyphSource::None => None,
546 };
547 descriptor::check_font_metrics(&mut descriptor, metrics, |_| Rect::ZERO);
548
549 let gsub = if cmap.is_vertical() {
550 gsub::VerticalSubst::parse(&glyphs, diags)
551 } else {
552 gsub::VerticalSubst::none()
553 };
554
555 Type0Font {
556 id: cache.next_id(),
557 cmap,
558 glyphs,
559 charset,
560 cid_to_gid,
561 widths: widths_table,
562 vertical,
563 to_unicode,
564 descriptor,
565 subst: subst_font,
566 kind,
567 embedded,
568 base_font_name,
569 adobe_courier_std,
570 #[cfg(test)]
571 ansi_widths_fixed: gb2312,
572 gsub,
573 }
574}
575
576fn substitute(
577 request: &FontRequest,
578 opts: &SubstitutionOptions,
579 diags: &mut Diagnostics,
580) -> subst::Substitution {
581 subst::resolve_with_options(request, opts, diags)
582}
583
584pub(crate) fn cid_charmap(glyphs: &GlyphSource, coding: CidCoding) -> Charmap {
596 let charmaps = glyphs.charmaps();
597
598 if let Some(wanted) = legacy_encoding_id(coding)
600 && let Some(i) = charmaps
601 .iter()
602 .position(|c| c.platform == 3 && c.encoding == wanted)
603 {
604 return Charmap::Index(i);
605 }
606 if let Some(i) = charmaps.iter().position(|c| c.is_unicode()) {
608 return Charmap::Index(i);
609 }
610 if charmaps.is_empty() {
612 Charmap::None
613 } else {
614 Charmap::Index(0)
615 }
616}
617
618fn legacy_encoding_id(coding: CidCoding) -> Option<u16> {
624 Some(match coding {
625 CidCoding::Gb => 3,
626 CidCoding::Big5 => 4,
627 CidCoding::Jis => 2,
628 CidCoding::Korea => 6,
629 CidCoding::Unknown | CidCoding::Ucs2 | CidCoding::Cid | CidCoding::Utf16 => return None,
631 })
632}
633
634pub(crate) fn face_of(glyphs: &GlyphSource) -> Option<&Face> {
636 match glyphs {
637 GlyphSource::Fontations(f) => Some(f),
638 GlyphSource::Type1(_) | GlyphSource::None => None,
639 }
640}
641
642#[cfg(test)]
643#[path = "cid_tests.rs"]
644mod tests;