1use std::sync::OnceLock;
18
19use crate::glyphs::{Charmap, GlyphSource, SynthGlyph};
20use crate::ids::{CharCode, FontFlags, FontId, Gid};
21use crate::subst::{self, CodePage, FontRequest, SubstFont, SubstitutionOptions};
22use pdfrum_common::Diagnostics;
23use pdfrum_common::kurbo::BezPath;
24
25#[derive(Debug)]
27pub struct GlyphFallback {
28 pub(crate) glyphs: GlyphSource,
30 pub(crate) subst: SubstFont,
32 pub(crate) id: FontId,
35}
36
37impl GlyphFallback {
38 #[must_use]
40 pub fn id(&self) -> FontId {
41 self.id
42 }
43
44 #[must_use]
51 pub fn gid(&self, unicode: &[char], code: CharCode) -> Option<Gid> {
52 let u = unicode.first().copied().map_or(code.0, u32::from);
53 let gid = self.glyphs.char_index(Charmap::Unicode, u);
54 (gid != 0).then_some(Gid(gid))
55 }
56
57 #[must_use]
59 pub fn hinted_path(&self, gid: Gid) -> Option<BezPath> {
60 self.glyphs.hinted_outline(gid)
61 }
62
63 #[must_use]
66 pub fn advance(&self, gid: Gid) -> i32 {
67 self.glyphs
68 .advance(gid, crate::glyphs::GlyphParams::default())
69 }
70
71 #[must_use]
74 pub fn render_synth(&self, xx: i32, xy: i32, vertical: bool) -> Option<SynthGlyph> {
75 let level = self.subst.embolden_level_for_render(false, xx, xy)?;
76 Some(SynthGlyph {
77 skew: self.subst.effective_skew(false),
78 vertical,
79 embolden: f64::from(level) / 64.0,
80 })
81 }
82}
83
84#[must_use]
90pub(crate) fn should_use_own_glyph(
91 embedded: bool,
92 is_truetype: bool,
93 has_to_unicode: bool,
94 gid: Option<Gid>,
95) -> bool {
96 let Some(gid) = gid else {
97 return false;
98 };
99 if embedded {
100 return true;
101 }
102 if !is_truetype {
103 return true;
104 }
105 gid != Gid(0) || has_to_unicode
106}
107
108pub(crate) fn ensure(
114 slot: &OnceLock<Option<GlyphFallback>>,
115 host_id: FontId,
116 is_truetype: bool,
117 flags: FontFlags,
118 stem_v: i32,
119 italic_angle: i32,
120 vertical: bool,
121) -> Option<&GlyphFallback> {
122 slot.get_or_init(|| {
123 let weight = i32::try_from(i64::from(stem_v).saturating_mul(5)).unwrap_or(400);
124 let request = FontRequest {
125 name: b"Arial".to_vec(),
126 is_truetype,
127 flags,
128 weight,
129 italic_angle,
130 code_page: CodePage::DefAnsi,
131 vertical,
132 };
133 let resolved = subst::resolve_with_options(
134 &request,
135 &SubstitutionOptions::default(),
136 &mut Diagnostics::with_limit(0),
137 );
138 if !resolved.glyphs.is_some() {
139 return None;
140 }
141 Some(GlyphFallback {
142 glyphs: resolved.glyphs,
143 subst: resolved.subst,
144 id: FontId(host_id.0 | (1 << 63)),
145 })
146 })
147 .as_ref()
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn missing_glyph_always_falls_back() {
156 assert!(!should_use_own_glyph(true, true, true, None));
157 assert!(!should_use_own_glyph(false, false, false, None));
158 }
159
160 #[test]
161 fn embedded_notdef_is_kept() {
162 assert!(should_use_own_glyph(true, true, false, Some(Gid(0))));
163 }
164
165 #[test]
166 fn non_embedded_truetype_notdef_without_tounicode_falls_back() {
167 assert!(!should_use_own_glyph(false, true, false, Some(Gid(0))));
168 }
169
170 #[test]
171 fn tounicode_keeps_a_truetype_notdef() {
172 assert!(should_use_own_glyph(false, true, true, Some(Gid(0))));
173 }
174
175 #[test]
176 fn type1_notdef_is_kept() {
177 assert!(should_use_own_glyph(false, false, false, Some(Gid(0))));
178 }
179
180 #[test]
181 fn a_real_glyph_is_kept() {
182 assert!(should_use_own_glyph(false, true, false, Some(Gid(1))));
183 }
184}