1pub mod cmap;
18pub mod dotsection;
19pub mod encodings;
20pub mod fallback;
21pub mod type1;
22
23use std::cell::RefCell;
24use std::collections::HashMap;
25use std::rc::Rc;
26use std::sync::Arc;
27
28use lopdf::{Dictionary, Document, Object, ObjectId};
29use ttf_parser::GlyphId;
30
31use super::geom::Mat;
32use super::objects::{
33 as_dict, as_stream, deref, get, get_dict, get_int, get_name, get_num, name, num, nums,
34};
35use cmap::CMap;
36
37pub struct GlyphPath {
39 pub path: Option<tiny_skia::Path>,
40 pub advance: f64,
41}
42
43enum Program {
45 Sfnt {
47 data: Vec<u8>,
48 matrix: Mat,
50 cid_to_gid: Option<HashMap<u16, u16>>,
52 has_cmap: bool,
53 has_glyf: bool,
54 names: HashMap<String, u16>,
56 },
57 Cff {
59 data: Vec<u8>,
60 matrix: Mat,
61 cid_to_gid: Option<HashMap<u16, u16>>,
62 names: HashMap<String, u16>,
65 },
66 Type1 {
67 font: type1::Type1Font,
68 matrix: Mat,
69 },
70 Fallback {
72 face: Arc<fallback::FallbackFace>,
73 matrix: Mat,
74 },
75 None,
76}
77
78pub struct Type3 {
80 pub font_matrix: Mat,
81 pub char_procs: HashMap<String, ObjectId>,
82 pub resources: Option<Dictionary>,
83}
84
85pub struct LoadedFont {
86 pub composite: bool,
87 pub type3: Option<Type3>,
88 program: Program,
89 encoding_names: HashMap<u8, String>,
92 has_base_encoding: bool,
94 symbolic: bool,
96 cmap: Option<CMap>,
98 cid_to_gid: CidToGid,
99 widths: HashMap<u32, f64>,
101 default_width: Option<f64>,
102 to_unicode: HashMap<u32, String>,
105 std14: Option<crate::std14::Std14Widths>,
107 cache: RefCell<HashMap<u32, Option<Rc<tiny_skia::Path>>>>,
109 advance_cache: RefCell<HashMap<u32, f64>>,
110 pub vertical: bool,
111 is_dingbats: bool,
112 is_symbol_face: bool,
113}
114
115enum CidToGid {
116 Identity,
117 Map(Vec<u8>),
118}
119
120fn descriptor<'a>(
121 doc: &'a Document,
122 fdict: &'a Dictionary,
123 composite: bool,
124) -> Option<&'a Dictionary> {
125 let owner = if composite {
126 descendant(doc, fdict)?
127 } else {
128 fdict
129 };
130 get_dict(doc, owner, b"FontDescriptor")
131}
132
133fn descendant<'a>(doc: &'a Document, fdict: &'a Dictionary) -> Option<&'a Dictionary> {
134 match get(doc, fdict, b"DescendantFonts")? {
135 Object::Array(a) => a.first().and_then(|o| as_dict(doc, o)),
136 Object::Dictionary(d) => Some(d),
137 _ => None,
138 }
139}
140
141fn base_font_name(doc: &Document, fdict: &Dictionary) -> String {
142 let raw = get_name(doc, fdict, b"BaseFont")
143 .map(|n| String::from_utf8_lossy(n).into_owned())
144 .unwrap_or_default();
145 match raw.split_once('+') {
147 Some((pre, rest)) if pre.len() == 6 && pre.bytes().all(|b| b.is_ascii_uppercase()) => {
148 rest.to_string()
149 }
150 _ => raw,
151 }
152}
153
154impl LoadedFont {
155 pub fn load(doc: &Document, fdict: &Dictionary) -> LoadedFont {
156 let subtype = get_name(doc, fdict, b"Subtype").unwrap_or(b"");
157 let composite = subtype == b"Type0";
158 let base_font = base_font_name(doc, fdict);
159 let desc = descriptor(doc, fdict, composite);
160 let flags = desc.and_then(|d| get_int(doc, d, b"Flags"));
161 let symbolic_flag = flags.is_some_and(|f| f & 4 != 0 && f & 32 == 0);
162
163 let mut program = Program::None;
165 if let Some(d) = desc {
166 if let Some(s) = d.get(b"FontFile2").ok().and_then(|o| as_stream(doc, o)) {
167 if let Ok(data) = s.decompressed_content() {
168 program = sfnt_program(data).unwrap_or(Program::None);
169 }
170 }
171 if matches!(program, Program::None) {
172 if let Some(s) = d.get(b"FontFile3").ok().and_then(|o| as_stream(doc, o)) {
173 if let Ok(data) = s.decompressed_content() {
174 let st = get_name(doc, &s.dict, b"Subtype").unwrap_or(b"");
175 program = if st == b"OpenType"
176 || data.starts_with(b"OTTO")
177 || data.starts_with(&[0, 1, 0, 0])
178 || data.starts_with(b"true")
179 {
180 sfnt_program(data.clone())
181 .or_else(|| cff_program(data))
182 .unwrap_or(Program::None)
183 } else {
184 cff_program(data.clone())
185 .or_else(|| sfnt_program(data))
186 .unwrap_or(Program::None)
187 };
188 }
189 }
190 }
191 if matches!(program, Program::None) {
192 if let Some(s) = d.get(b"FontFile").ok().and_then(|o| as_stream(doc, o)) {
193 if let Ok(data) = s.decompressed_content() {
194 if let Some(f) = type1::Type1Font::parse(&data) {
195 let m =
196 Mat::from_slice(&f.font_matrix).unwrap_or(Mat::scale(0.001, 0.001));
197 program = Program::Type1 { font: f, matrix: m };
198 } else if let Some(p) =
199 cff_program(data.clone()).or_else(|| sfnt_program(data))
200 {
201 program = p;
203 }
204 }
205 }
206 }
207 }
208
209 let type3 = if subtype == b"Type3" {
211 let fm = get(doc, fdict, b"FontMatrix")
212 .and_then(|o| nums(doc, o))
213 .and_then(|v| Mat::from_slice(&v))
214 .unwrap_or(Mat::scale(0.001, 0.001));
215 let mut char_procs = HashMap::new();
216 if let Some(cp) = get_dict(doc, fdict, b"CharProcs") {
217 for (k, v) in cp.iter() {
218 if let Object::Reference(id) = v {
219 char_procs.insert(String::from_utf8_lossy(k).into_owned(), *id);
220 }
221 }
222 }
223 let resources = get_dict(doc, fdict, b"Resources").cloned();
224 Some(Type3 {
225 font_matrix: fm,
226 char_procs,
227 resources,
228 })
229 } else {
230 None
231 };
232
233 let lower = base_font.to_ascii_lowercase();
235 let is_symbol_face = lower.starts_with("symbol");
236 let is_dingbats = lower.contains("dingbat");
237 let mut encoding_names: HashMap<u8, String> = HashMap::new();
238 let mut has_base_encoding = false;
239 let enc = get(doc, fdict, b"Encoding");
240 let mut cmap = None;
241 if composite {
242 cmap = Some(match enc {
243 Some(Object::Name(n)) => CMap::predefined(n),
244 Some(Object::Stream(s)) => {
245 let mut c = s
246 .decompressed_content()
247 .ok()
248 .map(|d| CMap::parse(&d))
249 .unwrap_or_else(CMap::identity_h);
250 if let Some(Object::Name(n)) = get(doc, &s.dict, b"UseCMap") {
251 if n.starts_with(b"Identity") && !c.identity {
252 }
254 }
255 if get_int(doc, &s.dict, b"WMode") == Some(1) {
256 c.vertical = true;
257 }
258 c
259 }
260 _ => CMap::identity_h(),
261 });
262 } else {
263 let base_table: Option<&'static [(u8, &'static str)]> = match enc {
264 Some(Object::Name(n)) => {
265 has_base_encoding = true;
266 encodings::by_name(n)
267 }
268 Some(Object::Dictionary(d)) => match get_name(doc, d, b"BaseEncoding") {
269 Some(n) => {
270 has_base_encoding = true;
271 encodings::by_name(n)
272 }
273 None => None,
274 },
275 _ => None,
276 };
277 let implicit: Option<&'static [(u8, &'static str)]> = if is_symbol_face
281 && matches!(program, Program::None)
282 {
283 Some(encodings::SYMBOL)
284 } else if is_dingbats && matches!(program, Program::None) {
285 Some(encodings::ZAPF_DINGBATS)
286 } else if !symbolic_flag || matches!(program, Program::None | Program::Fallback { .. })
287 {
288 Some(encodings::STANDARD)
289 } else {
290 None
291 };
292 if let Some(t) = base_table.or(implicit) {
293 for &(c, n) in t {
294 encoding_names.insert(c, n.to_string());
295 }
296 }
297 if let Some(Object::Dictionary(d)) = enc {
298 if let Some(Object::Array(diffs)) = get(doc, d, b"Differences") {
299 let mut code: i64 = 0;
300 for el in diffs {
301 match deref(doc, el) {
302 Object::Integer(i) => code = *i,
303 Object::Real(r) => code = *r as i64,
304 Object::Name(n) => {
305 if (0..=255).contains(&code) {
306 encoding_names.insert(
307 code as u8,
308 String::from_utf8_lossy(n).into_owned(),
309 );
310 }
311 code += 1;
312 }
313 _ => {}
314 }
315 }
316 }
317 }
318 }
319
320 let mut widths = HashMap::new();
322 let mut default_width = None;
323 if composite {
324 if let Some(dd) = descendant(doc, fdict) {
325 default_width = Some(get_num(doc, dd, b"DW").unwrap_or(1000.0));
326 if let Some(Object::Array(w)) = get(doc, dd, b"W") {
327 let mut i = 0;
328 while i < w.len() {
329 let c = w.get(i).map(|o| deref(doc, o)).and_then(num);
330 match (c, w.get(i + 1).map(|o| deref(doc, o))) {
331 (Some(c), Some(Object::Array(list))) => {
332 for (k, wv) in list.iter().enumerate() {
333 if let Some(wv) = num(deref(doc, wv)) {
334 widths.insert(c as u32 + k as u32, wv);
335 }
336 }
337 i += 2;
338 }
339 (Some(c1), Some(o2)) => {
340 if let (Some(c2), Some(wv)) =
341 (num(o2), w.get(i + 2).map(|o| deref(doc, o)).and_then(num))
342 {
343 let (c1, c2) = (c1.max(0.0) as u32, c2.max(0.0) as u32);
344 if c2 >= c1 && c2 - c1 < 65536 {
345 for cid in c1..=c2 {
346 widths.insert(cid, wv);
347 }
348 }
349 }
350 i += 3;
351 }
352 _ => break,
353 }
354 }
355 }
356 }
357 } else {
358 let first = get_int(doc, fdict, b"FirstChar").unwrap_or(0);
359 if let Some(Object::Array(w)) = get(doc, fdict, b"Widths") {
360 for (k, wv) in w.iter().enumerate() {
361 if let Some(wv) = num(deref(doc, wv)) {
362 let code = first + k as i64;
363 if (0..=255).contains(&code) {
364 widths.insert(code as u32, wv);
365 }
366 }
367 }
368 if !w.is_empty() {
369 default_width = Some(
370 desc.and_then(|d| get_num(doc, d, b"MissingWidth"))
371 .unwrap_or(0.0),
372 );
373 }
374 }
375 if widths.is_empty() {
376 if let Some(mw) = desc.and_then(|d| get_num(doc, d, b"MissingWidth")) {
377 if mw > 0.0 {
378 default_width = Some(mw);
379 }
380 }
381 }
382 }
383 let std14 = if widths.is_empty() && !composite && type3.is_none() {
384 crate::std14::widths_for(base_font.as_bytes())
385 } else {
386 None
387 };
388
389 let mut cid_to_gid = CidToGid::Identity;
391 if composite {
392 if let Some(dd) = descendant(doc, fdict) {
393 if let Some(Object::Stream(s)) = get(doc, dd, b"CIDToGIDMap") {
394 if let Ok(data) = s.decompressed_content() {
395 cid_to_gid = CidToGid::Map(data);
396 }
397 }
398 }
399 }
400
401 if matches!(program, Program::None) && type3.is_none() {
403 let ordering_cjk = composite
404 && descendant(doc, fdict)
405 .and_then(|dd| get_dict(doc, dd, b"CIDSystemInfo"))
406 .and_then(|si| get(doc, si, b"Ordering"))
407 .and_then(|o| match o {
408 Object::String(s, _) => Some(s.clone()),
409 _ => None,
410 })
411 .is_some_and(|o| {
412 matches!(
413 o.as_slice(),
414 b"Japan1" | b"GB1" | b"CNS1" | b"Korea1" | b"KR"
415 )
416 });
417 let mut style = fallback::style_for(&base_font, flags, None);
418 if ordering_cjk {
419 style.family = fallback::Family::Cjk;
420 }
421 if let Some(face) = fallback::face(style) {
422 let upem = ttf_parser::Face::parse(&face.data, 0)
423 .ok()
424 .map(|f| f64::from(f.units_per_em()))
425 .filter(|u| *u > 0.0)
426 .unwrap_or(1000.0);
427 program = Program::Fallback {
428 face,
429 matrix: Mat::scale(1.0 / upem, 1.0 / upem),
430 };
431 }
432 }
433
434 let to_unicode = get(doc, fdict, b"ToUnicode")
435 .and_then(|o| match o {
436 Object::Stream(s) => s.decompressed_content().ok(),
437 _ => None,
438 })
439 .map(|d| crate::textparse::parse_tounicode(&d))
440 .unwrap_or_default();
441
442 let vertical = cmap.as_ref().is_some_and(|c| c.vertical);
443 LoadedFont {
444 composite,
445 type3,
446 program,
447 encoding_names,
448 has_base_encoding,
449 symbolic: symbolic_flag && enc.is_none(),
450 cmap,
451 cid_to_gid,
452 widths,
453 default_width,
454 to_unicode,
455 std14,
456 cache: RefCell::new(HashMap::new()),
457 advance_cache: RefCell::new(HashMap::new()),
458 vertical,
459 is_dingbats,
460 is_symbol_face,
461 }
462 }
463
464 pub fn decode(&self, bytes: &[u8]) -> Vec<(u32, u32, bool)> {
467 match &self.cmap {
468 Some(c) => c
469 .split(bytes)
470 .into_iter()
471 .map(|(code, n)| (code, c.cid(code), n == 1))
472 .collect(),
473 None => bytes
474 .iter()
475 .map(|&b| (u32::from(b), u32::from(b), true))
476 .collect(),
477 }
478 }
479
480 pub fn advance(&self, code: u32, cid: u32) -> f64 {
483 let key = if self.composite { cid } else { code };
484 if let Some(a) = self.advance_cache.borrow().get(&key) {
485 return *a;
486 }
487 let a = self.advance_uncached(code, cid);
488 self.advance_cache.borrow_mut().insert(key, a);
489 a
490 }
491
492 fn advance_uncached(&self, code: u32, cid: u32) -> f64 {
493 let key = if self.composite { cid } else { code };
494 if let Some(t3) = &self.type3 {
495 let w = self.widths.get(&key).copied().unwrap_or(0.0);
496 let (ax, _) = t3.font_matrix.apply_vec(w, 0.0);
497 return ax;
498 }
499 if let Some(w) = self.widths.get(&key) {
500 return w / 1000.0;
501 }
502 if let Some(std) = &self.std14 {
503 if let Some(ch) = self.unicode_of(code, cid) {
504 if let Some(w) = std.width(ch) {
505 return w / 1000.0;
506 }
507 }
508 }
509 if let Some(dw) = self.default_width {
510 if dw > 0.0 || !self.widths.is_empty() {
511 return dw / 1000.0;
512 }
513 }
514 if let Some(adv) = self.program_advance(code, cid) {
516 return adv;
517 }
518 if self.default_width.is_some() {
519 return self.default_width.unwrap_or(0.0) / 1000.0;
520 }
521 0.5
522 }
523
524 fn unicode_of(&self, code: u32, cid: u32) -> Option<char> {
525 let key = if self.composite { cid } else { code };
526 if let Some(s) = self
527 .to_unicode
528 .get(&key)
529 .or_else(|| self.to_unicode.get(&code))
530 {
531 return s.chars().next();
532 }
533 if !self.composite {
534 if let Some(n) = self.encoding_names.get(&(code as u8)) {
535 return crate::textparse::glyph_name_to_char(n.as_bytes());
536 }
537 return char::from_u32(code).filter(|c| c.is_ascii_graphic() || *c == ' ');
538 }
539 if self.cmap.as_ref().is_some_and(|c| c.unicode_codes) {
540 return char::from_u32(code);
541 }
542 None
543 }
544
545 pub fn type3_glyph_name(&self, code: u32) -> Option<String> {
547 self.encoding_names.get(&(code as u8)).cloned()
548 }
549
550 fn glyph_name(&self, code: u32) -> Option<&str> {
552 self.encoding_names.get(&(code as u8)).map(String::as_str)
553 }
554
555 pub fn glyph(&self, code: u32, cid: u32) -> Option<Rc<tiny_skia::Path>> {
557 let key = if self.composite { cid } else { code };
558 if let Some(p) = self.cache.borrow().get(&key) {
559 return p.clone();
560 }
561 let built = self.build_glyph(code, cid).and_then(|g| {
562 let m = self.program_matrix();
563 g.path.and_then(|p| p.transform(m.to_ts())).map(Rc::new)
564 });
565 self.cache.borrow_mut().insert(key, built.clone());
566 built
567 }
568
569 fn program_matrix(&self) -> Mat {
570 match &self.program {
571 Program::Sfnt { matrix, .. }
572 | Program::Cff { matrix, .. }
573 | Program::Type1 { matrix, .. }
574 | Program::Fallback { matrix, .. } => *matrix,
575 Program::None => Mat::scale(0.001, 0.001),
576 }
577 }
578
579 pub fn has_program(&self) -> bool {
580 !matches!(self.program, Program::None)
581 }
582
583 fn program_advance(&self, code: u32, cid: u32) -> Option<f64> {
584 let g = self.build_glyph(code, cid)?;
585 let (ax, _) = self.program_matrix().apply_vec(g.advance, 0.0);
586 Some(ax)
587 }
588
589 fn build_glyph(&self, code: u32, cid: u32) -> Option<GlyphPath> {
590 match &self.program {
591 Program::None => None,
592 Program::Type1 { font, .. } => {
593 let name = self.type1_glyph_name(font, code)?;
594 font.glyph(&name)
595 }
596 Program::Cff {
597 data,
598 cid_to_gid,
599 names,
600 ..
601 } => {
602 let table = ttf_parser::cff::Table::parse(data)?;
603 let gid = if self.composite {
604 let gid = match &self.cid_to_gid {
605 CidToGid::Map(m) => map_cid(m, cid),
606 CidToGid::Identity => cid,
607 };
608 match cid_to_gid {
609 Some(map) => GlyphId(*map.get(&(gid as u16))?),
610 None => GlyphId(gid as u16),
611 }
612 } else {
613 self.cff_simple_gid(&table, names, code)?
614 };
615 outline_cff(&table, gid)
616 }
617 Program::Sfnt {
618 data,
619 cid_to_gid,
620 has_cmap,
621 has_glyf,
622 names,
623 ..
624 } => {
625 let face = ttf_parser::Face::parse(data, 0).ok()?;
626 let gid = if self.composite {
627 let gid = match &self.cid_to_gid {
628 CidToGid::Map(m) => map_cid(m, cid),
629 CidToGid::Identity => cid,
630 };
631 match cid_to_gid {
632 Some(map) => GlyphId(*map.get(&(gid as u16))?),
633 None => GlyphId(gid as u16),
634 }
635 } else {
636 self.sfnt_simple_gid(&face, names, code, *has_cmap, *has_glyf)?
637 };
638 outline_face(&face, gid)
639 }
640 Program::Fallback { face, .. } => {
641 let f = ttf_parser::Face::parse(&face.data, 0).ok()?;
642 let gid = self.fallback_gid(&f, code, cid)?;
643 outline_face(&f, gid)
644 }
645 }
646 }
647
648 fn type1_glyph_name(&self, font: &type1::Type1Font, code: u32) -> Option<String> {
649 let c = code as u8;
650 let pdf_name = self.glyph_name(code);
653 let builtin = font.encoding.get(&c).cloned();
654 let candidates: Vec<String> = if self.symbolic && !self.has_base_encoding {
655 [pdf_name.map(str::to_string), builtin.clone()]
658 .into_iter()
659 .flatten()
660 .collect()
661 } else {
662 [pdf_name.map(str::to_string), builtin.clone()]
663 .into_iter()
664 .flatten()
665 .collect()
666 };
667 for cand in &candidates {
668 if font.has_glyph(cand) {
669 return Some(cand.clone());
670 }
671 if let Some(ch) = crate::textparse::glyph_name_to_char(cand.as_bytes()) {
673 if let Some(n) = self.type1_name_for_char(font, ch) {
674 return Some(n);
675 }
676 }
677 }
678 if let Some(n) = encodings::lookup(encodings::STANDARD, c) {
679 if font.has_glyph(n) {
680 return Some(n.to_string());
681 }
682 }
683 if let Some(b) = builtin {
684 return Some(b);
685 }
686 None
687 }
688
689 fn type1_name_for_char(&self, font: &type1::Type1Font, ch: char) -> Option<String> {
690 font.glyph_names()
692 .find(|n| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
693 .cloned()
694 }
695
696 fn cff_simple_gid(
697 &self,
698 table: &ttf_parser::cff::Table,
699 names: &HashMap<String, u16>,
700 code: u32,
701 ) -> Option<GlyphId> {
702 let c = code as u8;
703 let by_name = |n: &str| -> Option<GlyphId> {
704 names
705 .get(n)
706 .map(|g| GlyphId(*g))
707 .or_else(|| table.glyph_index_by_name(n))
708 };
709 if let Some(name) = self.glyph_name(code) {
710 if let Some(g) = by_name(name) {
711 return Some(g);
712 }
713 if let Some(ch) = crate::textparse::glyph_name_to_char(name.as_bytes()) {
714 if let Some(std_name) = agl_name(ch) {
716 if let Some(g) = by_name(std_name) {
717 return Some(g);
718 }
719 }
720 let uni = format!("uni{:04X}", ch as u32);
721 if let Some(g) = by_name(&uni) {
722 return Some(g);
723 }
724 if let Some(g) = names
725 .iter()
726 .find(|(n, _)| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
727 .map(|(_, g)| GlyphId(*g))
728 {
729 return Some(g);
730 }
731 }
732 }
733 if let Some(g) = table.glyph_index(c) {
735 if g.0 != 0 {
736 return Some(g);
737 }
738 }
739 if let Some(n) = encodings::lookup(encodings::STANDARD, c) {
740 if let Some(g) = by_name(n) {
741 return Some(g);
742 }
743 }
744 None
745 }
746
747 fn sfnt_simple_gid(
748 &self,
749 face: &ttf_parser::Face,
750 names: &HashMap<String, u16>,
751 code: u32,
752 has_cmap: bool,
753 has_glyf: bool,
754 ) -> Option<GlyphId> {
755 let c = code as u8;
756 let cmap = face.tables().cmap.as_ref();
757 let symbol_lookup = |code: u32| -> Option<GlyphId> {
758 let cm = cmap?;
759 for sub in cm.subtables {
760 if sub.platform_id == ttf_parser::PlatformId::Windows && sub.encoding_id == 0 {
761 for probe in [code, 0xF000 + code, 0xF100 + code, 0xF200 + code] {
762 if let Some(g) = sub.glyph_index(probe) {
763 if g.0 != 0 {
764 return Some(g);
765 }
766 }
767 }
768 }
769 }
770 None
771 };
772 let mac_lookup = |code: u32| -> Option<GlyphId> {
773 let cm = cmap?;
774 for sub in cm.subtables {
775 if sub.platform_id == ttf_parser::PlatformId::Macintosh {
776 if let Some(g) = sub.glyph_index(code) {
777 if g.0 != 0 {
778 return Some(g);
779 }
780 }
781 }
782 }
783 None
784 };
785 let unicode_lookup = |ch: char| -> Option<GlyphId> {
786 let cm = cmap?;
787 for sub in cm.subtables {
788 if sub.is_unicode() {
789 if let Some(g) = sub.glyph_index(ch as u32) {
790 if g.0 != 0 {
791 return Some(g);
792 }
793 }
794 }
795 }
796 None
797 };
798 let name = self.glyph_name(code);
799 let symbolic_path = self.symbolic || !self.has_base_encoding && name.is_none();
800 if symbolic_path {
801 if let Some(g) = symbol_lookup(code) {
802 return Some(g);
803 }
804 if let Some(g) = mac_lookup(code) {
805 return Some(g);
806 }
807 }
808 if let Some(n) = name {
809 if let Some(ch) = crate::textparse::glyph_name_to_char(n.as_bytes()) {
810 if let Some(g) = unicode_lookup(ch) {
811 return Some(g);
812 }
813 if let Some(g) = symbol_lookup(ch as u32) {
814 return Some(g);
815 }
816 }
817 if let Some(g) = names
818 .get(n)
819 .map(|g| GlyphId(*g))
820 .or_else(|| face.glyph_index_by_name(n))
821 {
822 if g.0 != 0 {
823 return Some(g);
824 }
825 }
826 if let Some(g) = gid_from_name(n) {
828 return Some(GlyphId(g));
829 }
830 }
831 if !symbolic_path {
832 if let Some(g) = symbol_lookup(code) {
833 return Some(g);
834 }
835 if let Some(g) = mac_lookup(code) {
836 return Some(g);
837 }
838 }
839 if let Some(std) = encodings::lookup(encodings::STANDARD, c) {
840 if let Some(ch) = crate::textparse::glyph_name_to_char(std.as_bytes()) {
841 if let Some(g) = unicode_lookup(ch) {
842 return Some(g);
843 }
844 }
845 }
846 if !has_cmap || face.tables().cmap.is_none() {
847 let _ = has_glyf;
849 return Some(GlyphId(code as u16));
850 }
851 None
852 }
853
854 fn fallback_gid(&self, face: &ttf_parser::Face, code: u32, cid: u32) -> Option<GlyphId> {
855 if !self.composite && (self.is_symbol_face || self.is_dingbats) {
857 if let Some(n) = self.glyph_name(code) {
858 if let Some(g) = face.glyph_index_by_name(n) {
859 return Some(g);
860 }
861 }
862 }
863 if !self.composite {
864 if let Some(n) = self.glyph_name(code) {
865 if let Some(ch) = crate::textparse::glyph_name_to_char(n.as_bytes()) {
866 if let Some(g) = face.glyph_index(ch) {
867 return Some(g);
868 }
869 }
870 if let Some(g) = face.glyph_index_by_name(n) {
871 return Some(g);
872 }
873 }
874 }
875 let ch = self.unicode_of(code, cid)?;
876 face.glyph_index(ch)
877 }
878}
879
880fn map_cid(map: &[u8], cid: u32) -> u32 {
881 let i = cid as usize * 2;
882 match (map.get(i), map.get(i + 1)) {
883 (Some(hi), Some(lo)) => (u32::from(*hi) << 8) | u32::from(*lo),
884 _ => 0,
885 }
886}
887
888fn gid_from_name(n: &str) -> Option<u16> {
889 for prefix in ["glyph", "index", "cid", "g", "G"] {
890 if let Some(rest) = n.strip_prefix(prefix) {
891 if !rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit()) {
892 return rest.parse().ok();
893 }
894 }
895 }
896 None
897}
898
899fn agl_name(ch: char) -> Option<&'static str> {
902 encodings::STANDARD
903 .iter()
904 .chain(encodings::WIN_ANSI.iter())
905 .find(|(_, n)| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
906 .map(|(_, n)| *n)
907}
908
909struct Builder(tiny_skia::PathBuilder);
910
911impl ttf_parser::OutlineBuilder for Builder {
912 fn move_to(&mut self, x: f32, y: f32) {
913 self.0.move_to(x, y);
914 }
915 fn line_to(&mut self, x: f32, y: f32) {
916 self.0.line_to(x, y);
917 }
918 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
919 self.0.quad_to(x1, y1, x, y);
920 }
921 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
922 self.0.cubic_to(x1, y1, x2, y2, x, y);
923 }
924 fn close(&mut self) {
925 self.0.close();
926 }
927}
928
929fn outline_face(face: &ttf_parser::Face, gid: GlyphId) -> Option<GlyphPath> {
930 let mut b = Builder(tiny_skia::PathBuilder::new());
931 let bbox = face.outline_glyph(gid, &mut b);
932 let advance = face
933 .glyph_hor_advance(gid)
934 .map(f64::from)
935 .or_else(|| {
936 face.tables()
937 .cff
938 .as_ref()
939 .and_then(|c| c.glyph_width(gid))
940 .map(f64::from)
941 })
942 .unwrap_or(0.0);
943 if bbox.is_none() && gid.0 >= face.number_of_glyphs() {
944 return None;
945 }
946 Some(GlyphPath {
947 path: b.0.finish(),
948 advance,
949 })
950}
951
952fn outline_cff(table: &ttf_parser::cff::Table, gid: GlyphId) -> Option<GlyphPath> {
953 let mut b = Builder(tiny_skia::PathBuilder::new());
954 let ok = table.outline(gid, &mut b).is_ok();
955 let advance = table.glyph_width(gid).map(f64::from).unwrap_or(0.0);
956 if !ok && gid.0 >= table.number_of_glyphs() {
957 return None;
958 }
959 Some(GlyphPath {
960 path: b.0.finish(),
961 advance,
962 })
963}
964
965fn cff_matrix(m: ttf_parser::cff::Matrix) -> Mat {
966 let mat = Mat::new(
967 f64::from(m.sx),
968 f64::from(m.ky),
969 f64::from(m.kx),
970 f64::from(m.sy),
971 f64::from(m.tx),
972 f64::from(m.ty),
973 );
974 if mat.is_finite() && mat.det().abs() > 1e-12 {
975 mat
976 } else {
977 Mat::scale(0.001, 0.001)
978 }
979}
980
981fn sfnt_program(mut data: Vec<u8>) -> Option<Program> {
982 dotsection::strip_sfnt(&mut data);
983 let face = ttf_parser::Face::parse(&data, 0).ok()?;
984 let tables = face.tables();
985 let has_glyf = tables.glyf.is_some();
986 let has_cmap = tables.cmap.is_some();
987 let (matrix, cid_to_gid, names) = match &tables.cff {
988 Some(cff) if !has_glyf => (cff_matrix(cff.matrix()), cid_map(cff), cff_names(cff)),
989 _ => {
990 let upem = f64::from(face.units_per_em()).max(1.0);
991 (Mat::scale(1.0 / upem, 1.0 / upem), None, HashMap::new())
992 }
993 };
994 Some(Program::Sfnt {
995 data,
996 matrix,
997 cid_to_gid,
998 has_cmap,
999 has_glyf,
1000 names,
1001 })
1002}
1003
1004fn cff_program(mut data: Vec<u8>) -> Option<Program> {
1005 dotsection::strip(&mut data);
1006 let table = ttf_parser::cff::Table::parse(&data)?;
1007 let matrix = cff_matrix(table.matrix());
1008 let cid_to_gid = cid_map(&table);
1009 let names = cff_names(&table);
1010 Some(Program::Cff {
1011 data,
1012 matrix,
1013 cid_to_gid,
1014 names,
1015 })
1016}
1017
1018fn cff_names(table: &ttf_parser::cff::Table) -> HashMap<String, u16> {
1020 let mut map = HashMap::new();
1021 if table.glyph_cid(GlyphId(0)).is_some() {
1022 return map;
1023 }
1024 for g in 0..table.number_of_glyphs() {
1025 if let Some(n) = table.glyph_name(GlyphId(g)) {
1026 map.entry(n.to_string()).or_insert(g);
1027 }
1028 }
1029 map
1030}
1031
1032fn cid_map(table: &ttf_parser::cff::Table) -> Option<HashMap<u16, u16>> {
1034 let n = table.number_of_glyphs();
1035 table.glyph_cid(GlyphId(0))?;
1037 let mut map = HashMap::with_capacity(n as usize);
1038 for g in 0..n {
1039 if let Some(cid) = table.glyph_cid(GlyphId(g)) {
1040 map.entry(cid).or_insert(g);
1041 }
1042 }
1043 Some(map)
1044}
1045
1046#[derive(Default)]
1048pub struct FontCache {
1049 by_id: HashMap<ObjectId, Rc<LoadedFont>>,
1050 by_repr: HashMap<String, Rc<LoadedFont>>,
1052}
1053
1054impl FontCache {
1055 pub fn get(&mut self, doc: &Document, obj: &Object) -> Option<Rc<LoadedFont>> {
1056 match obj {
1057 Object::Reference(id) => {
1058 if let Some(f) = self.by_id.get(id) {
1059 return Some(f.clone());
1060 }
1061 let d = as_dict(doc, obj)?;
1062 let f = Rc::new(LoadedFont::load(doc, d));
1063 self.by_id.insert(*id, f.clone());
1064 Some(f)
1065 }
1066 Object::Dictionary(d) => {
1067 let key = format!("{d:?}");
1068 if let Some(f) = self.by_repr.get(&key) {
1069 return Some(f.clone());
1070 }
1071 let f = Rc::new(LoadedFont::load(doc, d));
1072 self.by_repr.insert(key, f.clone());
1073 Some(f)
1074 }
1075 _ => None,
1076 }
1077 }
1078}
1079
1080#[allow(dead_code)]
1081fn _unused(_: &Dictionary) -> Option<&[u8]> {
1082 None.and_then(name)
1083}