1use crate::cache::Cache;
4use crate::context::Context;
5use crate::device::Device;
6use crate::font::cid::Type0Font;
7use crate::font::generated::{
8 ams_glyph_names, glyph_names, mac_expert, mac_os_roman, mac_roman, standard, win_ansi,
9};
10use crate::font::true_type::TrueTypeFont;
11use crate::font::type1::Type1Font;
12use crate::font::type3::Type3;
13use crate::interpret::state::State;
14use crate::util::decode_or_warn;
15use crate::{CMapResolverFn, CacheKey, FontResolverFn, InterpreterSettings, Paint, WarningSinkFn};
16use bitflags::bitflags;
17use kurbo::{Affine, BezPath, Vec2};
18use log::warn;
19use outline::OutlineFont;
20use pdf_syntax::object::Name;
21use pdf_syntax::object::dict::keys::SUBTYPE;
22use pdf_syntax::object::dict::keys::*;
23use pdf_syntax::object::{Dict, Stream};
24use pdf_syntax::page::Resources;
25use pdf_syntax::xref::XRef;
26use skrifa::GlyphId;
27use std::fmt::Debug;
28use std::ops::Deref;
29use std::rc::Rc;
30use std::sync::Arc;
31
32mod blob;
33mod cid;
34mod generated;
35mod glyph_simulator;
36mod marks;
37pub(crate) mod outline;
38mod standard_font;
39mod true_type;
40mod type1;
41pub(crate) mod type3;
42
43pub(crate) const UNITS_PER_EM: f32 = 1000.0;
44
45pub(crate) fn stretch_glyph(path: BezPath, expected_width: f32, actual_width: f32) -> BezPath {
46 if actual_width != 0.0 && actual_width != expected_width {
47 let stretch_factor = expected_width / actual_width;
48 Affine::scale_non_uniform(stretch_factor as f64, 1.0) * path
49 } else {
50 path
51 }
52}
53
54pub type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
56
57pub(crate) fn strip_subset_prefix(name: &str) -> &str {
63 match name.split_once('+') {
64 Some((prefix, rest)) if prefix.len() == 6 => rest,
65 _ => name,
66 }
67}
68
69use crate::util::hash128;
70pub(crate) use marks::{MarkPlacement, NamedEntry, table_entry};
71use marks::{
72 MarkSite, is_combining_mark, mark_rows_set_aside, may_be_row_mark, placements_held,
73 row_mark_of, without_mark_rows,
74};
75pub use outline::OutlineFontData;
76use pdf_font::cmap::{BfString, CMap, CMapName, CharacterCollection};
77pub use standard_font::StandardFont;
78
79pub enum Glyph<'a> {
81 Outline(OutlineGlyph),
83 Type3(Box<Type3Glyph<'a>>),
85}
86
87impl Glyph<'_> {
88 pub fn as_unicode(&self) -> Option<BfString> {
112 match self {
113 Glyph::Outline(g) => g.as_unicode(),
114 Glyph::Type3(g) => g.as_unicode(),
115 }
116 }
117
118 pub fn font_resource_name(&self) -> Option<&str> {
121 match self {
122 Glyph::Outline(g) => g.font_resource_name(),
123 Glyph::Type3(_) => None,
124 }
125 }
126}
127
128#[derive(Clone, Debug)]
130pub struct GlyphIdentifier {
131 id: GlyphId,
132 font: OutlineFont,
133}
134
135impl CacheKey for GlyphIdentifier {
136 fn cache_key(&self) -> u128 {
137 hash128(&(self.id, self.font.cache_key()))
138 }
139}
140
141#[derive(Debug)]
143pub struct OutlineGlyph {
144 pub(crate) id: GlyphId,
145 pub(crate) font: OutlineFont,
146 pub(crate) char_code: u32,
147 pub(crate) resource_name: Option<Rc<str>>,
148 pub(crate) marks: Option<MarkSite>,
152 pub(crate) text: Option<BfString>,
157}
158
159impl Clone for OutlineGlyph {
160 fn clone(&self) -> Self {
161 Self {
162 id: self.id,
163 font: self.font.clone(),
164 char_code: self.char_code,
165 resource_name: self.resource_name.clone(),
166 marks: self.marks,
167 text: self.text.clone(),
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
176pub struct GlyphPlace(MarkSite);
177
178impl OutlineGlyph {
179 pub fn place(&self) -> Option<GlyphPlace> {
182 self.marks.map(GlyphPlace)
183 }
184
185 pub fn reread_previous(&self, previous: GlyphPlace) -> Option<Option<BfString>> {
202 self.marks?.reread_previous(previous.0)
203 }
204
205 pub fn font_resource_name(&self) -> Option<&str> {
209 self.resource_name.as_deref()
210 }
211
212 pub fn outline(&self) -> BezPath {
214 self.font.outline_glyph(self.id, self.char_code)
215 }
216
217 pub fn identifier(&self) -> GlyphIdentifier {
223 GlyphIdentifier {
224 id: self.id,
225 font: self.font.clone(),
226 }
227 }
228
229 pub fn as_unicode(&self) -> Option<BfString> {
242 if !may_be_row_mark(&self.text) && !placements_held() {
246 return self.text.clone();
247 }
248 self.placed_text()
249 }
250
251 #[inline(never)]
254 fn placed_text(&self) -> Option<BfString> {
255 let text = self.cached_text();
256 if row_mark_of(text, || self.font_text()).is_some() {
257 if self.marks.as_ref().is_some_and(MarkSite::mark_taken) {
258 return None;
259 }
260 return without_mark_rows(|| self.font_text());
261 }
262 let text = text.clone();
263 let marks = self.marks.as_ref().map(MarkSite::base_marks);
264 match marks.filter(|marks| !marks.is_empty()) {
265 Some(marks) => {
266 let mut placed = match text {
267 Some(BfString::Char(c)) => c.to_string(),
268 Some(BfString::String(s)) => s,
269 None => String::new(),
270 };
271 placed.push_str(&marks);
272 Some(BfString::String(placed))
273 }
274 None => text,
275 }
276 }
277
278 pub(crate) fn cached_text(&self) -> &Option<BfString> {
285 &self.text
286 }
287
288 pub(crate) fn font_text(&self) -> Option<BfString> {
290 self.font.char_code_to_unicode(self.char_code)
291 }
292
293 pub fn font_data(&self) -> Option<OutlineFontData> {
297 self.font.font_data()
298 }
299
300 pub fn postscript_name(&self) -> Option<String> {
310 self.font.postscript_name()
311 }
312
313 pub fn declared_name(&self) -> Option<String> {
318 self.font.declared_name()
319 }
320
321 pub fn document_name(&self) -> Option<String> {
325 self.font.document_name()
326 }
327
328 pub fn glyph_id(&self) -> GlyphId {
330 self.id
331 }
332
333 pub fn advance_width(&self) -> Option<f32> {
338 self.font.glyph_advance_width(self.char_code)
339 }
340
341 pub fn font_metrics(&self) -> Option<(f64, f64, Option<f64>, Option<f64>)> {
349 self.font.font_metrics()
350 }
351
352 pub fn substitute_metrics(&self) -> Option<(f64, f64, Option<f64>, Option<f64>)> {
356 self.font.substitute_metrics()
357 }
358
359 pub fn font_cache_key(&self) -> u128 {
364 self.font.cache_key()
365 }
366}
367
368#[derive(Clone)]
370pub struct Type3Glyph<'a> {
371 pub(crate) font: Rc<Type3<'a>>,
372 pub(crate) glyph_id: GlyphId,
373 pub(crate) state: State<'a>,
374 pub(crate) parent_resources: Resources<'a>,
375 pub(crate) cache: Cache,
376 pub(crate) xref: &'a XRef,
377 pub(crate) settings: InterpreterSettings,
378 pub(crate) char_code: u32,
379 pub(crate) nesting_depth: u32,
384}
385
386impl<'a> Type3Glyph<'a> {
388 pub fn interpret(
390 &self,
391 device: &mut impl Device<'a>,
392 transform: Affine,
393 glyph_transform: Affine,
394 paint: &Paint<'a>,
395 ) {
396 self.font
397 .render_glyph(self, transform, glyph_transform, paint, device);
398 }
399
400 pub fn as_unicode(&self) -> Option<BfString> {
407 let text = self.font.char_code_to_unicode(self.char_code);
408 if row_mark_of(&text, || self.font.char_code_to_unicode(self.char_code)).is_some() {
409 return without_mark_rows(|| self.font.char_code_to_unicode(self.char_code));
410 }
411 text
412 }
413}
414
415impl CacheKey for Type3Glyph<'_> {
416 fn cache_key(&self) -> u128 {
417 hash128(&(self.font.cache_key(), self.glyph_id))
418 }
419}
420
421#[derive(Clone, Debug)]
422pub(crate) struct Font<'a>(u128, FontType<'a>, Option<Rc<str>>);
423
424impl<'a> Font<'a> {
425 pub(crate) fn new(
426 dict: &Dict<'a>,
427 font_resolver: &FontResolverFn,
428 cmap_resolver: &CMapResolverFn,
429 warning_sink: &WarningSinkFn,
430 cache: &Cache,
431 ) -> Option<Self> {
432 let f_type = match dict.get::<Name>(SUBTYPE)?.deref() {
433 TYPE1 | MM_TYPE1 => FontType::Type1(Rc::new(Type1Font::new(
434 dict,
435 font_resolver,
436 cmap_resolver,
437 warning_sink,
438 cache,
439 )?)),
440 TRUE_TYPE | OPEN_TYPE => FontType::TrueType(Rc::new(TrueTypeFont::new(
442 dict,
443 font_resolver,
444 cmap_resolver,
445 warning_sink,
446 cache,
447 )?)),
448 TYPE0 => FontType::Type0(Rc::new(Type0Font::new(
449 dict,
450 font_resolver,
451 cmap_resolver,
452 warning_sink,
453 cache,
454 )?)),
455 TYPE3 => FontType::Type3(Rc::new(Type3::new(
456 dict,
457 cmap_resolver,
458 warning_sink,
459 cache,
460 )?)),
461 f => {
462 warn!(
463 "unimplemented font type {:?}",
464 std::str::from_utf8(f).unwrap_or("unknown type")
465 );
466
467 return None;
468 }
469 };
470
471 let cache_key = dict.cache_key();
472
473 Some(Self(cache_key, f_type, None))
474 }
475
476 pub(crate) fn new_standard(
477 standard_font: StandardFont,
478 font_resolver: &FontResolverFn,
479 cache: &Cache,
480 ) -> Option<Self> {
481 let font = Type1Font::new_standard(standard_font, font_resolver, cache)?;
482
483 Some(Self(0, FontType::Type1(Rc::new(font)), None))
484 }
485
486 pub(crate) fn set_resource_name(&mut self, name: &str) {
492 self.2 = Some(Rc::from(name));
493 }
494
495 pub(crate) fn map_code(&self, code: u32) -> GlyphId {
496 match &self.1 {
497 FontType::Type1(f) => {
498 debug_assert!(code <= u8::MAX as u32);
499
500 f.map_code(code as u8)
501 }
502 FontType::TrueType(t) => {
503 debug_assert!(code <= u8::MAX as u32);
504
505 t.map_code(code as u8)
506 }
507 FontType::Type0(t) => t.map_code(code),
508 FontType::Type3(t) => {
509 debug_assert!(code <= u8::MAX as u32);
510
511 t.map_code(code as u8)
512 }
513 }
514 }
515
516 pub(crate) fn get_glyph(
517 &self,
518 glyph: GlyphId,
519 char_code: u32,
520 ctx: &mut Context<'a>,
521 resources: &Resources<'a>,
522 origin_displacement: Vec2,
523 ) -> (Glyph<'a>, Affine) {
524 let glyph_transform = ctx.get().text_state.full_transform()
525 * Affine::scale(1.0 / UNITS_PER_EM as f64)
526 * Affine::translate(origin_displacement);
527
528 let glyph = match &self.1 {
529 FontType::Type1(t) => {
530 let font = OutlineFont::Type1(t.clone());
531 let text = font.char_code_to_unicode(char_code);
532 Glyph::Outline(OutlineGlyph {
533 id: glyph,
534 font,
535 char_code,
536 resource_name: self.2.clone(),
537 marks: Some(ctx.marks.site()),
538 text,
539 })
540 }
541 FontType::TrueType(t) => {
542 let font = OutlineFont::TrueType(t.clone());
543 let text = font.char_code_to_unicode(char_code);
544 Glyph::Outline(OutlineGlyph {
545 id: glyph,
546 font,
547 char_code,
548 resource_name: self.2.clone(),
549 marks: Some(ctx.marks.site()),
550 text,
551 })
552 }
553 FontType::Type0(t) => {
554 let font = OutlineFont::Type0(t.clone());
555 let text = font.char_code_to_unicode(char_code);
556 Glyph::Outline(OutlineGlyph {
557 id: glyph,
558 font,
559 char_code,
560 resource_name: self.2.clone(),
561 marks: Some(ctx.marks.site()),
562 text,
563 })
564 }
565 FontType::Type3(t) => {
566 let shape_glyph = Type3Glyph {
567 nesting_depth: ctx.nesting_depth(),
568 font: t.clone(),
569 glyph_id: glyph,
570 state: ctx.get().clone(),
571 parent_resources: resources.clone(),
572 cache: ctx.object_cache.clone(),
573 xref: ctx.xref,
574 settings: ctx.settings.clone(),
575 char_code,
576 };
577
578 Glyph::Type3(Box::new(shape_glyph))
579 }
580 };
581
582 (glyph, glyph_transform)
583 }
584
585 pub(crate) fn code_advance(&self, code: u32) -> Vec2 {
586 match &self.1 {
587 FontType::Type1(t) => {
588 debug_assert!(code <= u8::MAX as u32);
589
590 Vec2::new(t.glyph_width(code as u8).unwrap_or(0.0) as f64, 0.0)
591 }
592 FontType::TrueType(t) => {
593 debug_assert!(code <= u8::MAX as u32);
594
595 Vec2::new(t.glyph_width(code as u8) as f64, 0.0)
596 }
597 FontType::Type0(t) => t.code_advance(code),
598 FontType::Type3(t) => {
599 debug_assert!(code <= u8::MAX as u32);
600
601 Vec2::new(t.glyph_width(code as u8) as f64, 0.0)
602 }
603 }
604 }
605
606 pub(crate) fn origin_displacement(&self, code: u32) -> Vec2 {
607 match &self.1 {
608 FontType::Type1(_) => Vec2::default(),
609 FontType::TrueType(_) => Vec2::default(),
610 FontType::Type0(t) => t.origin_displacement(code),
611 FontType::Type3(_) => Vec2::default(),
612 }
613 }
614
615 pub(crate) fn read_code(&self, bytes: &[u8], offset: usize) -> (u32, usize) {
616 match &self.1 {
617 FontType::Type1(_) => (bytes[offset] as u32, 1),
618 FontType::TrueType(_) => (bytes[offset] as u32, 1),
619 FontType::Type0(t) => t.read_code(bytes, offset),
620 FontType::Type3(_) => (bytes[offset] as u32, 1),
621 }
622 }
623
624 pub(crate) fn is_horizontal(&self) -> bool {
625 match &self.1 {
626 FontType::Type1(_) => true,
627 FontType::TrueType(_) => true,
628 FontType::Type0(t) => t.is_horizontal(),
629 FontType::Type3(_) => true,
630 }
631 }
632}
633
634impl CacheKey for Font<'_> {
635 fn cache_key(&self) -> u128 {
636 self.0
637 }
638}
639
640#[derive(Clone, Debug)]
641enum FontType<'a> {
642 Type1(Rc<Type1Font>),
643 TrueType(Rc<TrueTypeFont>),
644 Type0(Rc<Type0Font>),
645 Type3(Rc<Type3<'a>>),
646}
647
648#[derive(Debug)]
649enum Encoding {
650 Standard,
651 MacRoman,
652 WinAnsi,
653 MacExpert,
654 BuiltIn,
655}
656
657impl Encoding {
658 fn map_code(&self, code: u8) -> Option<&'static str> {
659 if code == 0 {
660 return Some(".notdef");
661 }
662 match self {
663 Self::Standard => standard::get(code),
664 Self::MacRoman => mac_roman::get(code).or_else(|| mac_os_roman::get(code)),
665 Self::WinAnsi => win_ansi::get(code),
666 Self::MacExpert => mac_expert::get(code),
667 Self::BuiltIn => None,
668 }
669 }
670}
671
672#[derive(Debug, Copy, Clone)]
674pub enum FontStretch {
675 Normal,
677 UltraCondensed,
679 ExtraCondensed,
681 Condensed,
683 SemiCondensed,
685 SemiExpanded,
687 Expanded,
689 ExtraExpanded,
691 UltraExpanded,
693}
694
695impl FontStretch {
696 fn from_string(s: &str) -> Self {
697 match s {
698 "UltraCondensed" => Self::UltraCondensed,
699 "ExtraCondensed" => Self::ExtraCondensed,
700 "Condensed" => Self::Condensed,
701 "SemiCondensed" => Self::SemiCondensed,
702 "SemiExpanded" => Self::SemiExpanded,
703 "Expanded" => Self::Expanded,
704 "ExtraExpanded" => Self::ExtraExpanded,
705 "UltraExpanded" => Self::UltraExpanded,
706 _ => Self::Normal,
707 }
708 }
709}
710
711bitflags! {
712 #[derive(Debug)]
714 pub(crate) struct FontFlags: u32 {
715 const FIXED_PITCH = 1 << 0;
716 const SERIF = 1 << 1;
717 const SYMBOLIC = 1 << 2;
718 const SCRIPT = 1 << 3;
719 const NON_SYMBOLIC = 1 << 5;
720 const ITALIC = 1 << 6;
721 const ALL_CAP = 1 << 16;
722 const SMALL_CAP = 1 << 17;
723 const FORCE_BOLD = 1 << 18;
724 }
725}
726
727pub enum FontQuery {
729 Standard(StandardFont),
731 Fallback(FallbackFontQuery),
736}
737
738#[derive(Debug, Clone)]
740pub struct FallbackFontQuery {
741 pub post_script_name: Option<String>,
743 pub font_name: Option<String>,
745 pub font_family: Option<String>,
747 pub font_stretch: FontStretch,
749 pub font_weight: u32,
751 pub is_fixed_pitch: bool,
753 pub is_serif: bool,
755 pub is_italic: bool,
757 pub is_bold: bool,
759 pub is_small_cap: bool,
761 pub character_collection: Option<CharacterCollection>,
763}
764
765impl FallbackFontQuery {
766 pub(crate) fn new(dict: &Dict<'_>) -> Self {
767 let post_script_name = dict
768 .get::<Name>(BASE_FONT)
769 .map(|n| strip_subset_prefix(n.as_str()).to_string());
770
771 let mut data = Self {
772 post_script_name,
773 ..Default::default()
774 };
775
776 if let Some(descriptor) = dict.get::<Dict<'_>>(FONT_DESC) {
777 data.font_name = dict
778 .get::<Name>(FONT_NAME)
779 .map(|n| strip_subset_prefix(n.as_str()).to_string());
780 data.font_family = descriptor
781 .get::<Name>(FONT_FAMILY)
782 .map(|n| n.as_str().to_string());
783 data.font_stretch = descriptor
784 .get::<Name>(FONT_STRETCH)
785 .map(|n| FontStretch::from_string(n.as_str()))
786 .unwrap_or(FontStretch::Normal);
787 data.font_weight = descriptor.get::<u32>(FONT_WEIGHT).unwrap_or(400);
788
789 if let Some(flags) = descriptor
790 .get::<u32>(FLAGS)
791 .map(FontFlags::from_bits_truncate)
792 {
793 data.is_fixed_pitch = flags.contains(FontFlags::FIXED_PITCH);
794 data.is_serif = flags.contains(FontFlags::SERIF);
795 data.is_italic = flags.contains(FontFlags::ITALIC);
796 data.is_small_cap = flags.contains(FontFlags::SMALL_CAP);
797 }
798 }
799
800 data.is_bold |= data.font_weight >= 700;
801
802 if let Some(name) = &data.post_script_name {
803 let lower = name.to_ascii_lowercase();
804 data.is_italic |=
805 lower.contains("italic") || lower.contains("oblique") || lower.contains("slant");
806 data.is_bold |= lower.contains("bold")
807 || lower.contains("demi")
808 || lower.contains("semibold")
809 || lower.contains("heavy")
810 || lower.contains("black");
811 }
812
813 data
814 }
815
816 pub fn pick_standard_font(&self) -> StandardFont {
818 if self.is_fixed_pitch {
819 match (self.is_bold, self.is_italic) {
820 (true, true) => StandardFont::CourierBoldOblique,
821 (true, false) => StandardFont::CourierBold,
822 (false, true) => StandardFont::CourierOblique,
823 (false, false) => StandardFont::Courier,
824 }
825 } else if !self.is_serif {
826 match (self.is_bold, self.is_italic) {
827 (true, true) => StandardFont::HelveticaBoldOblique,
828 (true, false) => StandardFont::HelveticaBold,
829 (false, true) => StandardFont::HelveticaOblique,
830 (false, false) => StandardFont::Helvetica,
831 }
832 } else {
833 match (self.is_bold, self.is_italic) {
834 (true, true) => StandardFont::TimesBoldItalic,
835 (true, false) => StandardFont::TimesBold,
836 (false, true) => StandardFont::TimesItalic,
837 (false, false) => StandardFont::TimesRoman,
838 }
839 }
840 }
841}
842
843impl Default for FallbackFontQuery {
844 fn default() -> Self {
845 Self {
846 post_script_name: None,
847 font_name: None,
848 font_family: None,
849 font_stretch: FontStretch::Normal,
850 font_weight: 400,
851 is_fixed_pitch: false,
852 is_serif: false,
853 is_italic: false,
854 is_bold: false,
855 is_small_cap: false,
856 character_collection: None,
857 }
858 }
859}
860
861pub(crate) fn glyph_name_to_unicode(name: &str) -> Option<char> {
866 if let Some(unicode_str) = glyph_names::get(name) {
868 return unicode_str.chars().next();
869 }
870
871 if let Some(c) = unicode_from_name(name) {
873 return Some(c);
874 }
875
876 if let Some(dot_pos) = name.find('.') {
880 let base = &name[..dot_pos];
881 if !base.is_empty() {
882 if let Some(c) = glyph_names::get(base).and_then(|s| s.chars().next()) {
883 return Some(c);
884 }
885 if let Some(c) = unicode_from_name(base) {
886 return Some(c);
887 }
888 }
889 }
890
891 if name.starts_with('a')
894 && name.len() >= 2
895 && let Ok(code) = name[1..].parse::<u32>()
896 && let Some(c) = char::from_u32(code)
897 && (!c.is_control() || c == ' ')
898 {
899 return Some(c);
900 }
901
902 warn!("failed to map glyph name {} to unicode", name);
903 None
904}
905
906pub(crate) fn glyph_name_to_string(name: &str) -> Option<String> {
925 glyph_name_reading(name).map(|(text, _)| text)
926}
927
928fn glyph_name_reading(name: &str) -> Option<(String, bool)> {
931 if let Some(text) = agl_name_to_string(name) {
932 return Some((text, false));
933 }
934 let c = ams_glyph_names::get(name)?;
935 if is_combining_mark(c) {
936 if mark_rows_set_aside() {
937 return None;
938 }
939 return Some((String::from(c), true));
940 }
941 Some((String::from(c), false))
942}
943
944fn agl_name_to_string(name: &str) -> Option<String> {
946 if let Some(s) = unicode_string_from_name(name) {
950 return Some(s);
951 }
952
953 if let Some(c) = glyph_name_to_unicode(name) {
956 return Some(c.to_string());
957 }
958
959 let base = name.split_once('.').map(|(b, _)| b).unwrap_or(name);
960 if !base.contains('_') {
961 return None;
962 }
963
964 let mut out = String::new();
965 for part in base.split('_') {
966 if part.is_empty() {
967 return None;
968 }
969 let text = unicode_string_from_name(part)
970 .or_else(|| glyph_name_to_unicode(part).map(|c| c.to_string()))?;
971 out.push_str(&text);
972 }
973 (!out.is_empty()).then_some(out)
974}
975
976pub(crate) fn unicode_string_from_name(name: &str) -> Option<String> {
989 let base = name.split_once('.').map(|(b, _)| b).unwrap_or(name);
990 if let Some(rest) = base.strip_prefix("uni") {
991 if rest.len() < 4 || !rest.len().is_multiple_of(4) {
992 return None;
993 }
994 let mut out = String::new();
995 for chunk in rest.as_bytes().chunks(4) {
996 let digits = core::str::from_utf8(chunk).ok()?;
997 let value = u32::from_str_radix(digits, 16).ok()?;
998 out.push(char::from_u32(value)?);
999 }
1000 return Some(out);
1001 }
1002 if let Some(rest) = base.strip_prefix('u') {
1003 if !(4..=6).contains(&rest.len()) {
1004 return None;
1005 }
1006 let value = u32::from_str_radix(rest, 16).ok()?;
1007 return Some(char::from_u32(value)?.to_string());
1008 }
1009 None
1010}
1011
1012pub(crate) fn unicode_from_name(name: &str) -> Option<char> {
1013 let convert = |input: &str| u32::from_str_radix(input, 16).ok().and_then(char::from_u32);
1014
1015 name.starts_with("uni")
1016 .then(|| name.get(3..).and_then(convert))
1017 .or_else(|| {
1018 name.starts_with("u")
1019 .then(|| name.get(1..).and_then(convert))
1020 })
1021 .flatten()
1022}
1023
1024pub(crate) fn read_to_unicode(
1025 dict: &Dict<'_>,
1026 cmap_resolver: &CMapResolverFn,
1027 warning_sink: &WarningSinkFn,
1028 cache: &Cache,
1029) -> Option<CMap> {
1030 if let Some(s) = dict.get::<Stream<'_>>(TO_UNICODE) {
1034 let key = Cache::font_stream_key(b"tounicode", &s);
1035 let parsed = cache.get_or_insert_some(key, || {
1036 let data = decode_or_warn(&s, warning_sink)?;
1037 let cmap_resolver = cmap_resolver.clone();
1038 CMap::parse(&data, move |name| (cmap_resolver)(name))
1039 });
1040 if parsed.is_some() {
1041 return parsed;
1042 }
1043 }
1044 let name = dict.get::<Name>(TO_UNICODE)?;
1047 let data = (cmap_resolver)(CMapName::from_bytes(name.as_ref()))?;
1048 let key = Cache::font_data_key(b"tounicode-name", 0, data);
1049 cache.get_or_insert_some(key, || {
1050 let cmap_resolver = cmap_resolver.clone();
1051 CMap::parse(data, move |name| (cmap_resolver)(name))
1052 })
1053}
1054
1055pub(crate) fn glyph_name_to_bfstring(name: &str) -> Option<BfString> {
1062 glyph_name_to_string(name).map(bfstring_of)
1063}
1064
1065fn bfstring_of(text: String) -> BfString {
1067 let mut chars = text.chars();
1068 match (chars.next(), chars.next()) {
1069 (Some(c), None) => BfString::Char(c),
1070 _ => BfString::String(text),
1071 }
1072}
1073
1074pub(crate) fn is_control_only(value: &BfString) -> bool {
1079 let is_control = |c: char| matches!(c, '\u{0}'..='\u{1F}' | '\u{7F}'..='\u{9F}');
1080 match value {
1081 BfString::Char(c) => is_control(*c),
1082 BfString::String(s) => !s.is_empty() && s.chars().all(is_control),
1083 }
1084}
1085
1086pub(crate) fn synthesize_unicode_map_from_encoding(
1091 dict: &Dict<'_>,
1092) -> Option<[Option<NamedEntry>; 256]> {
1093 let (base_encoding, differences) = true_type::read_encoding(dict);
1094 if matches!(base_encoding, Encoding::BuiltIn) && differences.is_empty() {
1097 return None;
1098 }
1099 let mut table: [Option<NamedEntry>; 256] = std::array::from_fn(|_| None);
1100 for code in 0u8..=255 {
1101 let glyph_name = differences
1102 .get(&code)
1103 .map(String::as_str)
1104 .or_else(|| base_encoding.map_code(code));
1105 if let Some(name) = glyph_name {
1106 table[code as usize] =
1107 glyph_name_reading(name).map(|(text, from_mark_row)| NamedEntry {
1108 text: bfstring_of(text),
1109 from_mark_row,
1110 });
1111 }
1112 }
1113 Some(table)
1114}
1115
1116pub(crate) fn normalized_glyph_name(mut name: &str) -> &str {
1121 if name == "nbspace" || name == "nonbreakingspace" {
1122 name = "space";
1123 }
1124
1125 if name == "sfthyphen" || name == "softhyphen" {
1126 name = "hyphen";
1127 }
1128
1129 name
1130}
1131
1132#[cfg(test)]
1133mod normalized_glyph_name_tests {
1134 use super::normalized_glyph_name;
1135
1136 #[test]
1137 fn maps_space_aliases() {
1138 assert_eq!(normalized_glyph_name("nbspace"), "space");
1139 assert_eq!(normalized_glyph_name("nonbreakingspace"), "space");
1140 }
1141
1142 #[test]
1143 fn maps_hyphen_aliases() {
1144 assert_eq!(normalized_glyph_name("sfthyphen"), "hyphen");
1145 assert_eq!(normalized_glyph_name("softhyphen"), "hyphen");
1146 }
1147
1148 #[test]
1149 fn preserves_unrelated_names() {
1150 assert_eq!(normalized_glyph_name("A"), "A");
1151 assert_eq!(normalized_glyph_name(".notdef"), ".notdef");
1152 assert_eq!(normalized_glyph_name("hyphen"), "hyphen");
1153 assert_eq!(normalized_glyph_name("space"), "space");
1154 }
1155}
1156
1157#[cfg(test)]
1158mod glyph_name_to_unicode_tests {
1159 use super::{
1160 BfString, glyph_name_to_bfstring, glyph_name_to_string, glyph_name_to_unicode,
1161 unicode_string_from_name,
1162 };
1163
1164 #[test]
1165 fn standard_agl_name() {
1166 assert_eq!(glyph_name_to_unicode("A"), Some('A'));
1167 assert_eq!(glyph_name_to_unicode("space"), Some(' '));
1168 assert_eq!(glyph_name_to_unicode("hyphen"), Some('-'));
1169 }
1170
1171 #[test]
1172 fn ligature_name_underscore_joined() {
1173 assert_eq!(glyph_name_to_string("f_i"), Some("fi".to_string()));
1177 assert_eq!(glyph_name_to_string("f_f_i"), Some("ffi".to_string()));
1178 assert_eq!(glyph_name_to_string("A_B_C"), Some("ABC".to_string()));
1179 }
1180
1181 #[test]
1182 fn ligature_name_with_suffix() {
1183 assert_eq!(glyph_name_to_string("f_i.alt"), Some("fi".to_string()));
1185 }
1186
1187 #[test]
1188 fn ligature_name_falls_back_to_single_char_path() {
1189 assert_eq!(glyph_name_to_string("fi"), Some("\u{FB01}".to_string()));
1191 assert_eq!(glyph_name_to_string("A"), Some("A".to_string()));
1192 }
1193
1194 #[test]
1195 fn ligature_name_rejects_unresolvable_component() {
1196 assert!(glyph_name_to_string("A_totallyUnknownGlyph").is_none());
1199 assert!(glyph_name_to_string("_").is_none());
1200 assert!(glyph_name_to_string("A__B").is_none());
1201 }
1202
1203 #[test]
1204 fn uni_prefix() {
1205 assert_eq!(glyph_name_to_unicode("uni0041"), Some('A'));
1206 assert_eq!(glyph_name_to_unicode("uni00E9"), Some('é'));
1207 }
1208
1209 #[test]
1210 fn u_prefix() {
1211 assert_eq!(glyph_name_to_unicode("u0041"), Some('A'));
1212 assert_eq!(glyph_name_to_unicode("u2022"), Some('•'));
1213 }
1214
1215 #[test]
1216 fn variant_suffix_stripped() {
1217 assert_eq!(glyph_name_to_unicode("A.swash"), Some('A'));
1218 assert_eq!(glyph_name_to_unicode("comma.alt"), Some(','));
1219 assert_eq!(glyph_name_to_unicode("space.narrow"), Some(' '));
1220 }
1221
1222 #[test]
1223 fn variant_suffix_with_uni_prefix() {
1224 assert_eq!(glyph_name_to_unicode("uni0041.ss01"), Some('A'));
1225 }
1226
1227 #[test]
1228 fn a_decimal_glyph_name() {
1229 assert_eq!(glyph_name_to_unicode("a65"), Some('A'));
1230 assert_eq!(glyph_name_to_unicode("a32"), Some(' '));
1231 assert_eq!(glyph_name_to_unicode("a97"), Some('a'));
1232 }
1233
1234 #[test]
1235 fn a_decimal_rejects_control_chars() {
1236 assert_eq!(glyph_name_to_unicode("a0"), None);
1237 assert_eq!(glyph_name_to_unicode("a7"), None);
1238 }
1239
1240 #[test]
1241 fn unknown_name_returns_none() {
1242 assert_eq!(glyph_name_to_unicode("xyzzynonexistent"), None);
1243 }
1244
1245 #[test]
1246 fn bfstring_keeps_single_chars_and_strings_their_shape() {
1247 assert_eq!(glyph_name_to_bfstring("A"), Some(BfString::Char('A')));
1248 assert_eq!(
1249 glyph_name_to_bfstring("f_i"),
1250 Some(BfString::String("fi".to_string()))
1251 );
1252 assert_eq!(glyph_name_to_bfstring("xyzzynonexistent"), None);
1253 }
1254
1255 #[test]
1260 fn the_angle_bracket_rows_read_as_plain_characters() {
1261 for (name, bracket) in [
1262 ("angbracketleft", '\u{27E8}'),
1263 ("angbracketright", '\u{27E9}'),
1264 ] {
1265 assert_eq!(super::agl_name_to_string(name), None, "{name}");
1266 assert_eq!(
1267 glyph_name_to_bfstring(name),
1268 Some(BfString::Char(bracket)),
1269 "{name}"
1270 );
1271 assert_eq!(super::glyph_name_reading(name).map(|r| r.1), Some(false));
1272 assert_eq!(
1273 super::without_mark_rows(|| glyph_name_to_bfstring(name)),
1274 Some(BfString::Char(bracket))
1275 );
1276 }
1277 }
1278
1279 #[test]
1284 fn the_visiblespace_row_reads_as_the_open_box() {
1285 let name = "visiblespace";
1286 assert_eq!(super::agl_name_to_string(name), None);
1287 assert_eq!(
1288 glyph_name_to_bfstring(name),
1289 Some(BfString::Char('\u{2423}'))
1290 );
1291 assert_eq!(super::glyph_name_reading(name).map(|r| r.1), Some(false));
1292 assert_eq!(
1293 super::without_mark_rows(|| glyph_name_to_bfstring(name)),
1294 Some(BfString::Char('\u{2423}'))
1295 );
1296 }
1297
1298 #[test]
1303 fn a_combining_mark_row_carries_its_origin() {
1304 for (name, mark, uni) in [
1305 ("circlecopyrt", '\u{20DD}', "uni20DD"),
1306 ("negationslash", '\u{0338}', "uni0338"),
1307 ] {
1308 let named = glyph_name_to_bfstring(name);
1309 assert_eq!(named, Some(BfString::Char(mark)), "{name}");
1310 assert_eq!(super::glyph_name_reading(name).map(|r| r.1), Some(true));
1311 assert_eq!(
1312 super::row_mark_of(&named, || glyph_name_to_bfstring(name)),
1313 Some(mark)
1314 );
1315 let elsewhere = glyph_name_to_bfstring(uni);
1316 assert_eq!(elsewhere, Some(BfString::Char(mark)));
1317 assert_eq!(
1318 super::row_mark_of(&elsewhere, || glyph_name_to_bfstring(uni)),
1319 None
1320 );
1321 assert_eq!(
1322 super::without_mark_rows(|| glyph_name_to_bfstring(name)),
1323 None
1324 );
1325 let entry = Some(super::NamedEntry {
1327 text: BfString::Char(mark),
1328 from_mark_row: true,
1329 });
1330 assert_eq!(super::table_entry(&entry), named);
1331 assert_eq!(
1332 super::without_mark_rows(|| super::table_entry(&entry)),
1333 None
1334 );
1335 }
1336 assert_eq!(
1337 super::without_mark_rows(|| glyph_name_to_bfstring("square")),
1338 Some(BfString::Char('\u{25A1}'))
1339 );
1340 assert_eq!(
1341 glyph_name_to_bfstring("square"),
1342 Some(BfString::Char('\u{25A1}'))
1343 );
1344 }
1345
1346 #[test]
1347 fn uni_sequence_spells_each_group() {
1348 assert_eq!(
1349 unicode_string_from_name("uni00410042"),
1350 Some("AB".to_string())
1351 );
1352 assert_eq!(unicode_string_from_name("uni20AC"), Some("€".to_string()));
1353 assert_eq!(glyph_name_to_string("uni00410042"), Some("AB".to_string()));
1354 }
1355
1356 #[test]
1357 fn uni_sequence_strips_the_variant_suffix_first() {
1358 assert_eq!(
1361 glyph_name_to_string("uni00410042.tnum_lnum"),
1362 Some("AB".to_string())
1363 );
1364 assert_eq!(glyph_name_to_string("uni20AC.tnum"), Some("€".to_string()));
1365 }
1366
1367 #[test]
1368 fn u_prefix_holds_four_to_six_digits() {
1369 assert_eq!(unicode_string_from_name("u0041"), Some("A".to_string()));
1370 assert_eq!(unicode_string_from_name("u1F600"), Some("😀".to_string()));
1371 assert_eq!(unicode_string_from_name("u01F600"), Some("😀".to_string()));
1372 assert_eq!(unicode_string_from_name("u041"), None);
1373 assert_eq!(unicode_string_from_name("u00410042"), None);
1374 assert_eq!(glyph_name_to_string("u0041_u0042"), Some("AB".to_string()));
1375 }
1376
1377 #[test]
1378 fn malformed_uni_names_are_refused() {
1379 assert_eq!(unicode_string_from_name("uni"), None);
1380 assert_eq!(unicode_string_from_name("uni00"), None);
1381 assert_eq!(unicode_string_from_name("uni00410"), None);
1382 assert_eq!(unicode_string_from_name("uni00ZZ"), None);
1383 assert_eq!(unicode_string_from_name("uniD800"), None);
1384 assert_eq!(glyph_name_to_string("uni00ZZ"), None);
1385 assert_eq!(glyph_name_to_string("uniD800"), None);
1386 }
1387
1388 #[test]
1389 fn ligature_components_may_spell_sequences() {
1390 assert_eq!(
1393 glyph_name_to_string("uni00410042_uni0043"),
1394 Some("ABC".to_string())
1395 );
1396 assert_eq!(
1397 glyph_name_to_string("uni00410042_X"),
1398 Some("ABX".to_string())
1399 );
1400 assert_eq!(
1401 glyph_name_to_string("uni0041_uni0042"),
1402 Some("AB".to_string())
1403 );
1404 assert_eq!(glyph_name_to_string("A_uni0042"), Some("AB".to_string()));
1405 assert_eq!(glyph_name_to_string("A_u0042"), Some("AB".to_string()));
1406 }
1407}
1408
1409#[cfg(test)]
1410mod fallback_font_query_tests {
1411 use super::*;
1412
1413 fn query_with(name: &str, flags: u32, weight: u32) -> FallbackFontQuery {
1414 let mut q = FallbackFontQuery {
1415 post_script_name: Some(name.to_string()),
1416 font_weight: weight,
1417 ..Default::default()
1418 };
1419
1420 let font_flags = FontFlags::from_bits_truncate(flags);
1421 q.is_fixed_pitch = font_flags.contains(FontFlags::FIXED_PITCH);
1422 q.is_serif = font_flags.contains(FontFlags::SERIF);
1423 q.is_italic = font_flags.contains(FontFlags::ITALIC);
1424 q.is_small_cap = font_flags.contains(FontFlags::SMALL_CAP);
1425
1426 q.is_bold |= q.font_weight >= 700;
1427
1428 if let Some(name) = &q.post_script_name {
1429 let lower = name.to_ascii_lowercase();
1430 q.is_italic |=
1431 lower.contains("italic") || lower.contains("oblique") || lower.contains("slant");
1432 q.is_bold |= lower.contains("bold")
1433 || lower.contains("demi")
1434 || lower.contains("semibold")
1435 || lower.contains("heavy")
1436 || lower.contains("black");
1437 }
1438 q
1439 }
1440
1441 #[test]
1442 fn fixed_pitch_flag_selects_courier() {
1443 let q = query_with("LetterGothic", FontFlags::FIXED_PITCH.bits(), 400);
1444 assert!(matches!(q.pick_standard_font(), StandardFont::Courier));
1445 }
1446
1447 #[test]
1448 fn demi_in_name_selects_bold() {
1449 let q = query_with("FranklinGothic-Demi", FontFlags::SERIF.bits(), 400);
1450 assert!(q.is_bold);
1451 assert!(matches!(q.pick_standard_font(), StandardFont::TimesBold));
1452 }
1453
1454 #[test]
1455 fn oblique_detected_as_italic() {
1456 let q = query_with("HelveticaNeue-LightOblique", 0, 400);
1457 assert!(q.is_italic);
1458 assert!(matches!(
1459 q.pick_standard_font(),
1460 StandardFont::HelveticaOblique
1461 ));
1462 }
1463
1464 #[test]
1465 fn font_weight_700_detected_as_bold() {
1466 let q = query_with("CustomFont", 0, 700);
1467 assert!(q.is_bold);
1468 assert!(matches!(
1469 q.pick_standard_font(),
1470 StandardFont::HelveticaBold
1471 ));
1472 }
1473
1474 #[test]
1475 fn semibold_detected_as_bold() {
1476 let q = query_with("AGaramond-Semibold", FontFlags::SERIF.bits(), 400);
1477 assert!(q.is_bold);
1478 assert!(matches!(q.pick_standard_font(), StandardFont::TimesBold));
1479 }
1480}