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