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 ) -> Option<Self> {
431 let f_type = match dict.get::<Name>(SUBTYPE)?.deref() {
432 TYPE1 | MM_TYPE1 => FontType::Type1(Rc::new(Type1Font::new(
433 dict,
434 font_resolver,
435 cmap_resolver,
436 warning_sink,
437 )?)),
438 TRUE_TYPE | OPEN_TYPE => FontType::TrueType(Rc::new(TrueTypeFont::new(
440 dict,
441 font_resolver,
442 cmap_resolver,
443 warning_sink,
444 )?)),
445 TYPE0 => FontType::Type0(Rc::new(Type0Font::new(
446 dict,
447 font_resolver,
448 cmap_resolver,
449 warning_sink,
450 )?)),
451 TYPE3 => FontType::Type3(Rc::new(Type3::new(dict, cmap_resolver, warning_sink)?)),
452 f => {
453 warn!(
454 "unimplemented font type {:?}",
455 std::str::from_utf8(f).unwrap_or("unknown type")
456 );
457
458 return None;
459 }
460 };
461
462 let cache_key = dict.cache_key();
463
464 Some(Self(cache_key, f_type, None))
465 }
466
467 pub(crate) fn new_standard(
468 standard_font: StandardFont,
469 font_resolver: &FontResolverFn,
470 ) -> Option<Self> {
471 let font = Type1Font::new_standard(standard_font, font_resolver)?;
472
473 Some(Self(0, FontType::Type1(Rc::new(font)), None))
474 }
475
476 pub(crate) fn set_resource_name(&mut self, name: &str) {
482 self.2 = Some(Rc::from(name));
483 }
484
485 pub(crate) fn map_code(&self, code: u32) -> GlyphId {
486 match &self.1 {
487 FontType::Type1(f) => {
488 debug_assert!(code <= u8::MAX as u32);
489
490 f.map_code(code as u8)
491 }
492 FontType::TrueType(t) => {
493 debug_assert!(code <= u8::MAX as u32);
494
495 t.map_code(code as u8)
496 }
497 FontType::Type0(t) => t.map_code(code),
498 FontType::Type3(t) => {
499 debug_assert!(code <= u8::MAX as u32);
500
501 t.map_code(code as u8)
502 }
503 }
504 }
505
506 pub(crate) fn get_glyph(
507 &self,
508 glyph: GlyphId,
509 char_code: u32,
510 ctx: &mut Context<'a>,
511 resources: &Resources<'a>,
512 origin_displacement: Vec2,
513 ) -> (Glyph<'a>, Affine) {
514 let glyph_transform = ctx.get().text_state.full_transform()
515 * Affine::scale(1.0 / UNITS_PER_EM as f64)
516 * Affine::translate(origin_displacement);
517
518 let glyph = match &self.1 {
519 FontType::Type1(t) => {
520 let font = OutlineFont::Type1(t.clone());
521 let text = font.char_code_to_unicode(char_code);
522 Glyph::Outline(OutlineGlyph {
523 id: glyph,
524 font,
525 char_code,
526 resource_name: self.2.clone(),
527 marks: Some(ctx.marks.site()),
528 text,
529 })
530 }
531 FontType::TrueType(t) => {
532 let font = OutlineFont::TrueType(t.clone());
533 let text = font.char_code_to_unicode(char_code);
534 Glyph::Outline(OutlineGlyph {
535 id: glyph,
536 font,
537 char_code,
538 resource_name: self.2.clone(),
539 marks: Some(ctx.marks.site()),
540 text,
541 })
542 }
543 FontType::Type0(t) => {
544 let font = OutlineFont::Type0(t.clone());
545 let text = font.char_code_to_unicode(char_code);
546 Glyph::Outline(OutlineGlyph {
547 id: glyph,
548 font,
549 char_code,
550 resource_name: self.2.clone(),
551 marks: Some(ctx.marks.site()),
552 text,
553 })
554 }
555 FontType::Type3(t) => {
556 let shape_glyph = Type3Glyph {
557 nesting_depth: ctx.nesting_depth(),
558 font: t.clone(),
559 glyph_id: glyph,
560 state: ctx.get().clone(),
561 parent_resources: resources.clone(),
562 cache: ctx.object_cache.clone(),
563 xref: ctx.xref,
564 settings: ctx.settings.clone(),
565 char_code,
566 };
567
568 Glyph::Type3(Box::new(shape_glyph))
569 }
570 };
571
572 (glyph, glyph_transform)
573 }
574
575 pub(crate) fn code_advance(&self, code: u32) -> Vec2 {
576 match &self.1 {
577 FontType::Type1(t) => {
578 debug_assert!(code <= u8::MAX as u32);
579
580 Vec2::new(t.glyph_width(code as u8).unwrap_or(0.0) as f64, 0.0)
581 }
582 FontType::TrueType(t) => {
583 debug_assert!(code <= u8::MAX as u32);
584
585 Vec2::new(t.glyph_width(code as u8) as f64, 0.0)
586 }
587 FontType::Type0(t) => t.code_advance(code),
588 FontType::Type3(t) => {
589 debug_assert!(code <= u8::MAX as u32);
590
591 Vec2::new(t.glyph_width(code as u8) as f64, 0.0)
592 }
593 }
594 }
595
596 pub(crate) fn origin_displacement(&self, code: u32) -> Vec2 {
597 match &self.1 {
598 FontType::Type1(_) => Vec2::default(),
599 FontType::TrueType(_) => Vec2::default(),
600 FontType::Type0(t) => t.origin_displacement(code),
601 FontType::Type3(_) => Vec2::default(),
602 }
603 }
604
605 pub(crate) fn read_code(&self, bytes: &[u8], offset: usize) -> (u32, usize) {
606 match &self.1 {
607 FontType::Type1(_) => (bytes[offset] as u32, 1),
608 FontType::TrueType(_) => (bytes[offset] as u32, 1),
609 FontType::Type0(t) => t.read_code(bytes, offset),
610 FontType::Type3(_) => (bytes[offset] as u32, 1),
611 }
612 }
613
614 pub(crate) fn is_horizontal(&self) -> bool {
615 match &self.1 {
616 FontType::Type1(_) => true,
617 FontType::TrueType(_) => true,
618 FontType::Type0(t) => t.is_horizontal(),
619 FontType::Type3(_) => true,
620 }
621 }
622}
623
624impl CacheKey for Font<'_> {
625 fn cache_key(&self) -> u128 {
626 self.0
627 }
628}
629
630#[derive(Clone, Debug)]
631enum FontType<'a> {
632 Type1(Rc<Type1Font>),
633 TrueType(Rc<TrueTypeFont>),
634 Type0(Rc<Type0Font>),
635 Type3(Rc<Type3<'a>>),
636}
637
638#[derive(Debug)]
639enum Encoding {
640 Standard,
641 MacRoman,
642 WinAnsi,
643 MacExpert,
644 BuiltIn,
645}
646
647impl Encoding {
648 fn map_code(&self, code: u8) -> Option<&'static str> {
649 if code == 0 {
650 return Some(".notdef");
651 }
652 match self {
653 Self::Standard => standard::get(code),
654 Self::MacRoman => mac_roman::get(code).or_else(|| mac_os_roman::get(code)),
655 Self::WinAnsi => win_ansi::get(code),
656 Self::MacExpert => mac_expert::get(code),
657 Self::BuiltIn => None,
658 }
659 }
660}
661
662#[derive(Debug, Copy, Clone)]
664pub enum FontStretch {
665 Normal,
667 UltraCondensed,
669 ExtraCondensed,
671 Condensed,
673 SemiCondensed,
675 SemiExpanded,
677 Expanded,
679 ExtraExpanded,
681 UltraExpanded,
683}
684
685impl FontStretch {
686 fn from_string(s: &str) -> Self {
687 match s {
688 "UltraCondensed" => Self::UltraCondensed,
689 "ExtraCondensed" => Self::ExtraCondensed,
690 "Condensed" => Self::Condensed,
691 "SemiCondensed" => Self::SemiCondensed,
692 "SemiExpanded" => Self::SemiExpanded,
693 "Expanded" => Self::Expanded,
694 "ExtraExpanded" => Self::ExtraExpanded,
695 "UltraExpanded" => Self::UltraExpanded,
696 _ => Self::Normal,
697 }
698 }
699}
700
701bitflags! {
702 #[derive(Debug)]
704 pub(crate) struct FontFlags: u32 {
705 const FIXED_PITCH = 1 << 0;
706 const SERIF = 1 << 1;
707 const SYMBOLIC = 1 << 2;
708 const SCRIPT = 1 << 3;
709 const NON_SYMBOLIC = 1 << 5;
710 const ITALIC = 1 << 6;
711 const ALL_CAP = 1 << 16;
712 const SMALL_CAP = 1 << 17;
713 const FORCE_BOLD = 1 << 18;
714 }
715}
716
717pub enum FontQuery {
719 Standard(StandardFont),
721 Fallback(FallbackFontQuery),
726}
727
728#[derive(Debug, Clone)]
730pub struct FallbackFontQuery {
731 pub post_script_name: Option<String>,
733 pub font_name: Option<String>,
735 pub font_family: Option<String>,
737 pub font_stretch: FontStretch,
739 pub font_weight: u32,
741 pub is_fixed_pitch: bool,
743 pub is_serif: bool,
745 pub is_italic: bool,
747 pub is_bold: bool,
749 pub is_small_cap: bool,
751 pub character_collection: Option<CharacterCollection>,
753}
754
755impl FallbackFontQuery {
756 pub(crate) fn new(dict: &Dict<'_>) -> Self {
757 let post_script_name = dict
758 .get::<Name>(BASE_FONT)
759 .map(|n| strip_subset_prefix(n.as_str()).to_string());
760
761 let mut data = Self {
762 post_script_name,
763 ..Default::default()
764 };
765
766 if let Some(descriptor) = dict.get::<Dict<'_>>(FONT_DESC) {
767 data.font_name = dict
768 .get::<Name>(FONT_NAME)
769 .map(|n| strip_subset_prefix(n.as_str()).to_string());
770 data.font_family = descriptor
771 .get::<Name>(FONT_FAMILY)
772 .map(|n| n.as_str().to_string());
773 data.font_stretch = descriptor
774 .get::<Name>(FONT_STRETCH)
775 .map(|n| FontStretch::from_string(n.as_str()))
776 .unwrap_or(FontStretch::Normal);
777 data.font_weight = descriptor.get::<u32>(FONT_WEIGHT).unwrap_or(400);
778
779 if let Some(flags) = descriptor
780 .get::<u32>(FLAGS)
781 .map(FontFlags::from_bits_truncate)
782 {
783 data.is_fixed_pitch = flags.contains(FontFlags::FIXED_PITCH);
784 data.is_serif = flags.contains(FontFlags::SERIF);
785 data.is_italic = flags.contains(FontFlags::ITALIC);
786 data.is_small_cap = flags.contains(FontFlags::SMALL_CAP);
787 }
788 }
789
790 data.is_bold |= data.font_weight >= 700;
791
792 if let Some(name) = &data.post_script_name {
793 let lower = name.to_ascii_lowercase();
794 data.is_italic |=
795 lower.contains("italic") || lower.contains("oblique") || lower.contains("slant");
796 data.is_bold |= lower.contains("bold")
797 || lower.contains("demi")
798 || lower.contains("semibold")
799 || lower.contains("heavy")
800 || lower.contains("black");
801 }
802
803 data
804 }
805
806 pub fn pick_standard_font(&self) -> StandardFont {
808 if self.is_fixed_pitch {
809 match (self.is_bold, self.is_italic) {
810 (true, true) => StandardFont::CourierBoldOblique,
811 (true, false) => StandardFont::CourierBold,
812 (false, true) => StandardFont::CourierOblique,
813 (false, false) => StandardFont::Courier,
814 }
815 } else if !self.is_serif {
816 match (self.is_bold, self.is_italic) {
817 (true, true) => StandardFont::HelveticaBoldOblique,
818 (true, false) => StandardFont::HelveticaBold,
819 (false, true) => StandardFont::HelveticaOblique,
820 (false, false) => StandardFont::Helvetica,
821 }
822 } else {
823 match (self.is_bold, self.is_italic) {
824 (true, true) => StandardFont::TimesBoldItalic,
825 (true, false) => StandardFont::TimesBold,
826 (false, true) => StandardFont::TimesItalic,
827 (false, false) => StandardFont::TimesRoman,
828 }
829 }
830 }
831}
832
833impl Default for FallbackFontQuery {
834 fn default() -> Self {
835 Self {
836 post_script_name: None,
837 font_name: None,
838 font_family: None,
839 font_stretch: FontStretch::Normal,
840 font_weight: 400,
841 is_fixed_pitch: false,
842 is_serif: false,
843 is_italic: false,
844 is_bold: false,
845 is_small_cap: false,
846 character_collection: None,
847 }
848 }
849}
850
851pub(crate) fn glyph_name_to_unicode(name: &str) -> Option<char> {
856 if let Some(unicode_str) = glyph_names::get(name) {
858 return unicode_str.chars().next();
859 }
860
861 if let Some(c) = unicode_from_name(name) {
863 return Some(c);
864 }
865
866 if let Some(dot_pos) = name.find('.') {
870 let base = &name[..dot_pos];
871 if !base.is_empty() {
872 if let Some(c) = glyph_names::get(base).and_then(|s| s.chars().next()) {
873 return Some(c);
874 }
875 if let Some(c) = unicode_from_name(base) {
876 return Some(c);
877 }
878 }
879 }
880
881 if name.starts_with('a')
884 && name.len() >= 2
885 && let Ok(code) = name[1..].parse::<u32>()
886 && let Some(c) = char::from_u32(code)
887 && (!c.is_control() || c == ' ')
888 {
889 return Some(c);
890 }
891
892 warn!("failed to map glyph name {} to unicode", name);
893 None
894}
895
896pub(crate) fn glyph_name_to_string(name: &str) -> Option<String> {
915 glyph_name_reading(name).map(|(text, _)| text)
916}
917
918fn glyph_name_reading(name: &str) -> Option<(String, bool)> {
921 if let Some(text) = agl_name_to_string(name) {
922 return Some((text, false));
923 }
924 let c = ams_glyph_names::get(name)?;
925 if is_combining_mark(c) {
926 if mark_rows_set_aside() {
927 return None;
928 }
929 return Some((String::from(c), true));
930 }
931 Some((String::from(c), false))
932}
933
934fn agl_name_to_string(name: &str) -> Option<String> {
936 if let Some(s) = unicode_string_from_name(name) {
940 return Some(s);
941 }
942
943 if let Some(c) = glyph_name_to_unicode(name) {
946 return Some(c.to_string());
947 }
948
949 let base = name.split_once('.').map(|(b, _)| b).unwrap_or(name);
950 if !base.contains('_') {
951 return None;
952 }
953
954 let mut out = String::new();
955 for part in base.split('_') {
956 if part.is_empty() {
957 return None;
958 }
959 let text = unicode_string_from_name(part)
960 .or_else(|| glyph_name_to_unicode(part).map(|c| c.to_string()))?;
961 out.push_str(&text);
962 }
963 (!out.is_empty()).then_some(out)
964}
965
966pub(crate) fn unicode_string_from_name(name: &str) -> Option<String> {
979 let base = name.split_once('.').map(|(b, _)| b).unwrap_or(name);
980 if let Some(rest) = base.strip_prefix("uni") {
981 if rest.len() < 4 || !rest.len().is_multiple_of(4) {
982 return None;
983 }
984 let mut out = String::new();
985 for chunk in rest.as_bytes().chunks(4) {
986 let digits = core::str::from_utf8(chunk).ok()?;
987 let value = u32::from_str_radix(digits, 16).ok()?;
988 out.push(char::from_u32(value)?);
989 }
990 return Some(out);
991 }
992 if let Some(rest) = base.strip_prefix('u') {
993 if !(4..=6).contains(&rest.len()) {
994 return None;
995 }
996 let value = u32::from_str_radix(rest, 16).ok()?;
997 return Some(char::from_u32(value)?.to_string());
998 }
999 None
1000}
1001
1002pub(crate) fn unicode_from_name(name: &str) -> Option<char> {
1003 let convert = |input: &str| u32::from_str_radix(input, 16).ok().and_then(char::from_u32);
1004
1005 name.starts_with("uni")
1006 .then(|| name.get(3..).and_then(convert))
1007 .or_else(|| {
1008 name.starts_with("u")
1009 .then(|| name.get(1..).and_then(convert))
1010 })
1011 .flatten()
1012}
1013
1014pub(crate) fn read_to_unicode(
1015 dict: &Dict<'_>,
1016 cmap_resolver: &CMapResolverFn,
1017 warning_sink: &WarningSinkFn,
1018) -> Option<CMap> {
1019 dict.get::<Stream<'_>>(TO_UNICODE)
1020 .and_then(|s| decode_or_warn(&s, warning_sink))
1021 .or_else(|| {
1024 dict.get::<Name>(TO_UNICODE)
1025 .and_then(|name| (cmap_resolver)(CMapName::from_bytes(name.as_ref())))
1026 .map(|d| d.to_vec())
1027 })
1028 .and_then(|data| {
1029 let cmap_resolver = cmap_resolver.clone();
1030 CMap::parse(&data, move |name| (cmap_resolver)(name))
1031 })
1032}
1033
1034pub(crate) fn glyph_name_to_bfstring(name: &str) -> Option<BfString> {
1041 glyph_name_to_string(name).map(bfstring_of)
1042}
1043
1044fn bfstring_of(text: String) -> BfString {
1046 let mut chars = text.chars();
1047 match (chars.next(), chars.next()) {
1048 (Some(c), None) => BfString::Char(c),
1049 _ => BfString::String(text),
1050 }
1051}
1052
1053pub(crate) fn is_control_only(value: &BfString) -> bool {
1058 let is_control = |c: char| matches!(c, '\u{0}'..='\u{1F}' | '\u{7F}'..='\u{9F}');
1059 match value {
1060 BfString::Char(c) => is_control(*c),
1061 BfString::String(s) => !s.is_empty() && s.chars().all(is_control),
1062 }
1063}
1064
1065pub(crate) fn synthesize_unicode_map_from_encoding(
1070 dict: &Dict<'_>,
1071) -> Option<[Option<NamedEntry>; 256]> {
1072 let (base_encoding, differences) = true_type::read_encoding(dict);
1073 if matches!(base_encoding, Encoding::BuiltIn) && differences.is_empty() {
1076 return None;
1077 }
1078 let mut table: [Option<NamedEntry>; 256] = std::array::from_fn(|_| None);
1079 for code in 0u8..=255 {
1080 let glyph_name = differences
1081 .get(&code)
1082 .map(String::as_str)
1083 .or_else(|| base_encoding.map_code(code));
1084 if let Some(name) = glyph_name {
1085 table[code as usize] =
1086 glyph_name_reading(name).map(|(text, from_mark_row)| NamedEntry {
1087 text: bfstring_of(text),
1088 from_mark_row,
1089 });
1090 }
1091 }
1092 Some(table)
1093}
1094
1095pub(crate) fn normalized_glyph_name(mut name: &str) -> &str {
1100 if name == "nbspace" || name == "nonbreakingspace" {
1101 name = "space";
1102 }
1103
1104 if name == "sfthyphen" || name == "softhyphen" {
1105 name = "hyphen";
1106 }
1107
1108 name
1109}
1110
1111#[cfg(test)]
1112mod normalized_glyph_name_tests {
1113 use super::normalized_glyph_name;
1114
1115 #[test]
1116 fn maps_space_aliases() {
1117 assert_eq!(normalized_glyph_name("nbspace"), "space");
1118 assert_eq!(normalized_glyph_name("nonbreakingspace"), "space");
1119 }
1120
1121 #[test]
1122 fn maps_hyphen_aliases() {
1123 assert_eq!(normalized_glyph_name("sfthyphen"), "hyphen");
1124 assert_eq!(normalized_glyph_name("softhyphen"), "hyphen");
1125 }
1126
1127 #[test]
1128 fn preserves_unrelated_names() {
1129 assert_eq!(normalized_glyph_name("A"), "A");
1130 assert_eq!(normalized_glyph_name(".notdef"), ".notdef");
1131 assert_eq!(normalized_glyph_name("hyphen"), "hyphen");
1132 assert_eq!(normalized_glyph_name("space"), "space");
1133 }
1134}
1135
1136#[cfg(test)]
1137mod glyph_name_to_unicode_tests {
1138 use super::{
1139 BfString, glyph_name_to_bfstring, glyph_name_to_string, glyph_name_to_unicode,
1140 unicode_string_from_name,
1141 };
1142
1143 #[test]
1144 fn standard_agl_name() {
1145 assert_eq!(glyph_name_to_unicode("A"), Some('A'));
1146 assert_eq!(glyph_name_to_unicode("space"), Some(' '));
1147 assert_eq!(glyph_name_to_unicode("hyphen"), Some('-'));
1148 }
1149
1150 #[test]
1151 fn ligature_name_underscore_joined() {
1152 assert_eq!(glyph_name_to_string("f_i"), Some("fi".to_string()));
1156 assert_eq!(glyph_name_to_string("f_f_i"), Some("ffi".to_string()));
1157 assert_eq!(glyph_name_to_string("A_B_C"), Some("ABC".to_string()));
1158 }
1159
1160 #[test]
1161 fn ligature_name_with_suffix() {
1162 assert_eq!(glyph_name_to_string("f_i.alt"), Some("fi".to_string()));
1164 }
1165
1166 #[test]
1167 fn ligature_name_falls_back_to_single_char_path() {
1168 assert_eq!(glyph_name_to_string("fi"), Some("\u{FB01}".to_string()));
1170 assert_eq!(glyph_name_to_string("A"), Some("A".to_string()));
1171 }
1172
1173 #[test]
1174 fn ligature_name_rejects_unresolvable_component() {
1175 assert!(glyph_name_to_string("A_totallyUnknownGlyph").is_none());
1178 assert!(glyph_name_to_string("_").is_none());
1179 assert!(glyph_name_to_string("A__B").is_none());
1180 }
1181
1182 #[test]
1183 fn uni_prefix() {
1184 assert_eq!(glyph_name_to_unicode("uni0041"), Some('A'));
1185 assert_eq!(glyph_name_to_unicode("uni00E9"), Some('é'));
1186 }
1187
1188 #[test]
1189 fn u_prefix() {
1190 assert_eq!(glyph_name_to_unicode("u0041"), Some('A'));
1191 assert_eq!(glyph_name_to_unicode("u2022"), Some('•'));
1192 }
1193
1194 #[test]
1195 fn variant_suffix_stripped() {
1196 assert_eq!(glyph_name_to_unicode("A.swash"), Some('A'));
1197 assert_eq!(glyph_name_to_unicode("comma.alt"), Some(','));
1198 assert_eq!(glyph_name_to_unicode("space.narrow"), Some(' '));
1199 }
1200
1201 #[test]
1202 fn variant_suffix_with_uni_prefix() {
1203 assert_eq!(glyph_name_to_unicode("uni0041.ss01"), Some('A'));
1204 }
1205
1206 #[test]
1207 fn a_decimal_glyph_name() {
1208 assert_eq!(glyph_name_to_unicode("a65"), Some('A'));
1209 assert_eq!(glyph_name_to_unicode("a32"), Some(' '));
1210 assert_eq!(glyph_name_to_unicode("a97"), Some('a'));
1211 }
1212
1213 #[test]
1214 fn a_decimal_rejects_control_chars() {
1215 assert_eq!(glyph_name_to_unicode("a0"), None);
1216 assert_eq!(glyph_name_to_unicode("a7"), None);
1217 }
1218
1219 #[test]
1220 fn unknown_name_returns_none() {
1221 assert_eq!(glyph_name_to_unicode("xyzzynonexistent"), None);
1222 }
1223
1224 #[test]
1225 fn bfstring_keeps_single_chars_and_strings_their_shape() {
1226 assert_eq!(glyph_name_to_bfstring("A"), Some(BfString::Char('A')));
1227 assert_eq!(
1228 glyph_name_to_bfstring("f_i"),
1229 Some(BfString::String("fi".to_string()))
1230 );
1231 assert_eq!(glyph_name_to_bfstring("xyzzynonexistent"), None);
1232 }
1233
1234 #[test]
1239 fn the_angle_bracket_rows_read_as_plain_characters() {
1240 for (name, bracket) in [
1241 ("angbracketleft", '\u{27E8}'),
1242 ("angbracketright", '\u{27E9}'),
1243 ] {
1244 assert_eq!(super::agl_name_to_string(name), None, "{name}");
1245 assert_eq!(
1246 glyph_name_to_bfstring(name),
1247 Some(BfString::Char(bracket)),
1248 "{name}"
1249 );
1250 assert_eq!(super::glyph_name_reading(name).map(|r| r.1), Some(false));
1251 assert_eq!(
1252 super::without_mark_rows(|| glyph_name_to_bfstring(name)),
1253 Some(BfString::Char(bracket))
1254 );
1255 }
1256 }
1257
1258 #[test]
1263 fn the_visiblespace_row_reads_as_the_open_box() {
1264 let name = "visiblespace";
1265 assert_eq!(super::agl_name_to_string(name), None);
1266 assert_eq!(
1267 glyph_name_to_bfstring(name),
1268 Some(BfString::Char('\u{2423}'))
1269 );
1270 assert_eq!(super::glyph_name_reading(name).map(|r| r.1), Some(false));
1271 assert_eq!(
1272 super::without_mark_rows(|| glyph_name_to_bfstring(name)),
1273 Some(BfString::Char('\u{2423}'))
1274 );
1275 }
1276
1277 #[test]
1282 fn a_combining_mark_row_carries_its_origin() {
1283 for (name, mark, uni) in [
1284 ("circlecopyrt", '\u{20DD}', "uni20DD"),
1285 ("negationslash", '\u{0338}', "uni0338"),
1286 ] {
1287 let named = glyph_name_to_bfstring(name);
1288 assert_eq!(named, Some(BfString::Char(mark)), "{name}");
1289 assert_eq!(super::glyph_name_reading(name).map(|r| r.1), Some(true));
1290 assert_eq!(
1291 super::row_mark_of(&named, || glyph_name_to_bfstring(name)),
1292 Some(mark)
1293 );
1294 let elsewhere = glyph_name_to_bfstring(uni);
1295 assert_eq!(elsewhere, Some(BfString::Char(mark)));
1296 assert_eq!(
1297 super::row_mark_of(&elsewhere, || glyph_name_to_bfstring(uni)),
1298 None
1299 );
1300 assert_eq!(
1301 super::without_mark_rows(|| glyph_name_to_bfstring(name)),
1302 None
1303 );
1304 let entry = Some(super::NamedEntry {
1306 text: BfString::Char(mark),
1307 from_mark_row: true,
1308 });
1309 assert_eq!(super::table_entry(&entry), named);
1310 assert_eq!(
1311 super::without_mark_rows(|| super::table_entry(&entry)),
1312 None
1313 );
1314 }
1315 assert_eq!(
1316 super::without_mark_rows(|| glyph_name_to_bfstring("square")),
1317 Some(BfString::Char('\u{25A1}'))
1318 );
1319 assert_eq!(
1320 glyph_name_to_bfstring("square"),
1321 Some(BfString::Char('\u{25A1}'))
1322 );
1323 }
1324
1325 #[test]
1326 fn uni_sequence_spells_each_group() {
1327 assert_eq!(
1328 unicode_string_from_name("uni00410042"),
1329 Some("AB".to_string())
1330 );
1331 assert_eq!(unicode_string_from_name("uni20AC"), Some("€".to_string()));
1332 assert_eq!(glyph_name_to_string("uni00410042"), Some("AB".to_string()));
1333 }
1334
1335 #[test]
1336 fn uni_sequence_strips_the_variant_suffix_first() {
1337 assert_eq!(
1340 glyph_name_to_string("uni00410042.tnum_lnum"),
1341 Some("AB".to_string())
1342 );
1343 assert_eq!(glyph_name_to_string("uni20AC.tnum"), Some("€".to_string()));
1344 }
1345
1346 #[test]
1347 fn u_prefix_holds_four_to_six_digits() {
1348 assert_eq!(unicode_string_from_name("u0041"), Some("A".to_string()));
1349 assert_eq!(unicode_string_from_name("u1F600"), Some("😀".to_string()));
1350 assert_eq!(unicode_string_from_name("u01F600"), Some("😀".to_string()));
1351 assert_eq!(unicode_string_from_name("u041"), None);
1352 assert_eq!(unicode_string_from_name("u00410042"), None);
1353 assert_eq!(glyph_name_to_string("u0041_u0042"), Some("AB".to_string()));
1354 }
1355
1356 #[test]
1357 fn malformed_uni_names_are_refused() {
1358 assert_eq!(unicode_string_from_name("uni"), None);
1359 assert_eq!(unicode_string_from_name("uni00"), None);
1360 assert_eq!(unicode_string_from_name("uni00410"), None);
1361 assert_eq!(unicode_string_from_name("uni00ZZ"), None);
1362 assert_eq!(unicode_string_from_name("uniD800"), None);
1363 assert_eq!(glyph_name_to_string("uni00ZZ"), None);
1364 assert_eq!(glyph_name_to_string("uniD800"), None);
1365 }
1366
1367 #[test]
1368 fn ligature_components_may_spell_sequences() {
1369 assert_eq!(
1372 glyph_name_to_string("uni00410042_uni0043"),
1373 Some("ABC".to_string())
1374 );
1375 assert_eq!(
1376 glyph_name_to_string("uni00410042_X"),
1377 Some("ABX".to_string())
1378 );
1379 assert_eq!(
1380 glyph_name_to_string("uni0041_uni0042"),
1381 Some("AB".to_string())
1382 );
1383 assert_eq!(glyph_name_to_string("A_uni0042"), Some("AB".to_string()));
1384 assert_eq!(glyph_name_to_string("A_u0042"), Some("AB".to_string()));
1385 }
1386}
1387
1388#[cfg(test)]
1389mod fallback_font_query_tests {
1390 use super::*;
1391
1392 fn query_with(name: &str, flags: u32, weight: u32) -> FallbackFontQuery {
1393 let mut q = FallbackFontQuery {
1394 post_script_name: Some(name.to_string()),
1395 font_weight: weight,
1396 ..Default::default()
1397 };
1398
1399 let font_flags = FontFlags::from_bits_truncate(flags);
1400 q.is_fixed_pitch = font_flags.contains(FontFlags::FIXED_PITCH);
1401 q.is_serif = font_flags.contains(FontFlags::SERIF);
1402 q.is_italic = font_flags.contains(FontFlags::ITALIC);
1403 q.is_small_cap = font_flags.contains(FontFlags::SMALL_CAP);
1404
1405 q.is_bold |= q.font_weight >= 700;
1406
1407 if let Some(name) = &q.post_script_name {
1408 let lower = name.to_ascii_lowercase();
1409 q.is_italic |=
1410 lower.contains("italic") || lower.contains("oblique") || lower.contains("slant");
1411 q.is_bold |= lower.contains("bold")
1412 || lower.contains("demi")
1413 || lower.contains("semibold")
1414 || lower.contains("heavy")
1415 || lower.contains("black");
1416 }
1417 q
1418 }
1419
1420 #[test]
1421 fn fixed_pitch_flag_selects_courier() {
1422 let q = query_with("LetterGothic", FontFlags::FIXED_PITCH.bits(), 400);
1423 assert!(matches!(q.pick_standard_font(), StandardFont::Courier));
1424 }
1425
1426 #[test]
1427 fn demi_in_name_selects_bold() {
1428 let q = query_with("FranklinGothic-Demi", FontFlags::SERIF.bits(), 400);
1429 assert!(q.is_bold);
1430 assert!(matches!(q.pick_standard_font(), StandardFont::TimesBold));
1431 }
1432
1433 #[test]
1434 fn oblique_detected_as_italic() {
1435 let q = query_with("HelveticaNeue-LightOblique", 0, 400);
1436 assert!(q.is_italic);
1437 assert!(matches!(
1438 q.pick_standard_font(),
1439 StandardFont::HelveticaOblique
1440 ));
1441 }
1442
1443 #[test]
1444 fn font_weight_700_detected_as_bold() {
1445 let q = query_with("CustomFont", 0, 700);
1446 assert!(q.is_bold);
1447 assert!(matches!(
1448 q.pick_standard_font(),
1449 StandardFont::HelveticaBold
1450 ));
1451 }
1452
1453 #[test]
1454 fn semibold_detected_as_bold() {
1455 let q = query_with("AGaramond-Semibold", FontFlags::SERIF.bits(), 400);
1456 assert!(q.is_bold);
1457 assert!(matches!(q.pick_standard_font(), StandardFont::TimesBold));
1458 }
1459}