1use crate::FontResolverFn;
2use crate::font::blob::{CffFontBlob, OpenTypeFontBlob};
3use crate::font::generated::{glyph_names, metrics, standard, symbol, zapf_dings};
4use crate::font::true_type::{Width, read_encoding, read_widths};
5use crate::font::{
6 Encoding, FontData, FontQuery, glyph_name_to_unicode, normalized_glyph_name, stretch_glyph,
7 strip_subset_prefix,
8};
9use hayro_syntax::object::Dict;
10use hayro_syntax::object::Name;
11use hayro_syntax::object::dict::keys::{BASE_FONT, FONT_DESC, FONT_WEIGHT, ITALIC_ANGLE};
12use kurbo::BezPath;
13use skrifa::GlyphId;
14use skrifa::raw::TableProvider;
15use std::cell::RefCell;
16use std::collections::HashMap;
17
18#[derive(Copy, Clone, Debug)]
20pub enum StandardFont {
21 Helvetica,
23 HelveticaBold,
25 HelveticaOblique,
27 HelveticaBoldOblique,
29 Courier,
31 CourierBold,
33 CourierOblique,
35 CourierBoldOblique,
37 TimesRoman,
39 TimesBold,
41 TimesItalic,
43 TimesBoldItalic,
45 ZapfDingBats,
47 Symbol,
49}
50
51impl StandardFont {
52 pub(crate) fn code_to_name(&self, code: u8) -> Option<&'static str> {
53 match self {
54 Self::Symbol => symbol::get(code),
55 Self::ZapfDingBats => zapf_dings::get(code),
58 _ => standard::get(code),
59 }
60 }
61
62 pub(crate) fn get_width(&self, mut name: &str) -> Option<f32> {
63 if name == ".notdef" {
65 return Some(250.0);
66 }
67
68 name = normalized_glyph_name(name);
69
70 match self {
71 Self::Helvetica => metrics::HELVETICA.get(name).copied(),
72 Self::HelveticaBold => metrics::HELVETICA_BOLD.get(name).copied(),
73 Self::HelveticaOblique => metrics::HELVETICA_OBLIQUE.get(name).copied(),
74 Self::HelveticaBoldOblique => metrics::HELVETICA_BOLD_OBLIQUE.get(name).copied(),
75 Self::Courier => metrics::COURIER.get(name).copied(),
76 Self::CourierBold => metrics::COURIER_BOLD.get(name).copied(),
77 Self::CourierOblique => metrics::COURIER_OBLIQUE.get(name).copied(),
78 Self::CourierBoldOblique => metrics::COURIER_BOLD_OBLIQUE.get(name).copied(),
79 Self::TimesRoman => metrics::TIMES_ROMAN.get(name).copied(),
80 Self::TimesBold => metrics::TIMES_BOLD.get(name).copied(),
81 Self::TimesItalic => metrics::TIMES_ITALIC.get(name).copied(),
82 Self::TimesBoldItalic => metrics::TIMES_BOLD_ITALIC.get(name).copied(),
83 Self::ZapfDingBats => metrics::ZAPF_DING_BATS.get(name).copied(),
84 Self::Symbol => metrics::SYMBOL.get(name).copied(),
85 }
86 }
87
88 pub(crate) fn as_str(&self) -> &'static str {
89 match self {
90 Self::Helvetica => "Helvetica",
91 Self::HelveticaBold => "Helvetica Bold",
92 Self::HelveticaOblique => "Helvetica Oblique",
93 Self::HelveticaBoldOblique => "Helvetica Bold Oblique",
94 Self::Courier => "Courier",
95 Self::CourierBold => "Courier Bold",
96 Self::CourierOblique => "Courier Oblique",
97 Self::CourierBoldOblique => "Courier Bold Oblique",
98 Self::TimesRoman => "Times Roman",
99 Self::TimesBold => "Times Bold",
100 Self::TimesItalic => "Times Italic",
101 Self::TimesBoldItalic => "Times Bold Italic",
102 Self::ZapfDingBats => "Zapf Dingbats",
103 Self::Symbol => "Symbol",
104 }
105 }
106
107 pub fn postscript_name(&self) -> &'static str {
109 match self {
110 Self::Helvetica => "Helvetica",
111 Self::HelveticaBold => "Helvetica-Bold",
112 Self::HelveticaOblique => "Helvetica-Oblique",
113 Self::HelveticaBoldOblique => "Helvetica-BoldOblique",
114 Self::Courier => "Courier",
115 Self::CourierBold => "Courier-Bold",
116 Self::CourierOblique => "Courier-Oblique",
117 Self::CourierBoldOblique => "Courier-BoldOblique",
118 Self::TimesRoman => "Times-Roman",
119 Self::TimesBold => "Times-Bold",
120 Self::TimesItalic => "Times-Italic",
121 Self::TimesBoldItalic => "Times-BoldItalic",
122 Self::ZapfDingBats => "ZapfDingbats",
123 Self::Symbol => "Symbol",
124 }
125 }
126
127 pub(crate) fn is_bold(&self) -> bool {
128 matches!(
129 self,
130 Self::HelveticaBold
131 | Self::HelveticaBoldOblique
132 | Self::CourierBold
133 | Self::CourierBoldOblique
134 | Self::TimesBold
135 | Self::TimesBoldItalic
136 )
137 }
138
139 pub(crate) fn is_italic(&self) -> bool {
140 matches!(
141 self,
142 Self::HelveticaOblique
143 | Self::HelveticaBoldOblique
144 | Self::CourierOblique
145 | Self::CourierBoldOblique
146 | Self::TimesItalic
147 | Self::TimesBoldItalic
148 )
149 }
150
151 pub(crate) fn is_serif(&self) -> bool {
152 matches!(
153 self,
154 Self::TimesRoman | Self::TimesBold | Self::TimesItalic | Self::TimesBoldItalic
155 )
156 }
157
158 pub(crate) fn is_monospace(&self) -> bool {
159 matches!(
160 self,
161 Self::Courier | Self::CourierBold | Self::CourierOblique | Self::CourierBoldOblique
162 )
163 }
164
165 #[cfg(feature = "embed-fonts")]
174 pub fn get_font_data(&self) -> (FontData, u32) {
175 use std::sync::Arc;
176
177 let data = match self {
178 Self::Helvetica => &include_bytes!("../../assets/FoxitSans.pfb")[..],
179 Self::HelveticaBold => &include_bytes!("../../assets/FoxitSansBold.pfb")[..],
180 Self::HelveticaOblique => &include_bytes!("../../assets/FoxitSansItalic.pfb")[..],
181 Self::HelveticaBoldOblique => {
182 &include_bytes!("../../assets/FoxitSansBoldItalic.pfb")[..]
183 }
184 Self::Courier => &include_bytes!("../../assets/FoxitFixed.pfb")[..],
185 Self::CourierBold => &include_bytes!("../../assets/FoxitFixedBold.pfb")[..],
186 Self::CourierOblique => &include_bytes!("../../assets/FoxitFixedItalic.pfb")[..],
187 Self::CourierBoldOblique => {
188 &include_bytes!("../../assets/FoxitFixedBoldItalic.pfb")[..]
189 }
190 Self::TimesRoman => &include_bytes!("../../assets/FoxitSerif.pfb")[..],
191 Self::TimesBold => &include_bytes!("../../assets/FoxitSerifBold.pfb")[..],
192 Self::TimesItalic => &include_bytes!("../../assets/FoxitSerifItalic.pfb")[..],
193 Self::TimesBoldItalic => &include_bytes!("../../assets/FoxitSerifBoldItalic.pfb")[..],
194 Self::ZapfDingBats => &include_bytes!("../../assets/FoxitDingbats.pfb")[..],
195 Self::Symbol => {
196 include_bytes!("../../assets/FoxitSymbol.pfb")
197 }
198 };
199
200 (Arc::new(data), 0)
201 }
202}
203
204enum StandardFontFamily {
205 Helvetica,
206 Courier,
207 Times,
208}
209
210pub(crate) fn select_standard_font(
211 dict: &Dict<'_>,
212 descriptor: &Dict<'_>,
213) -> Option<(StandardFont, bool)> {
214 let base_font = dict.get::<Name<'_>>(BASE_FONT)?;
215 let name = strip_subset_prefix(base_font.as_str());
216
217 match name {
219 "Helvetica" => return Some((StandardFont::Helvetica, true)),
220 "Helvetica-Bold" => return Some((StandardFont::HelveticaBold, true)),
221 "Helvetica-Oblique" => return Some((StandardFont::HelveticaOblique, true)),
222 "Helvetica-BoldOblique" => return Some((StandardFont::HelveticaBoldOblique, true)),
223 "Courier" => return Some((StandardFont::Courier, true)),
224 "Courier-Bold" => return Some((StandardFont::CourierBold, true)),
225 "Courier-Oblique" => return Some((StandardFont::CourierOblique, true)),
226 "Courier-BoldOblique" => return Some((StandardFont::CourierBoldOblique, true)),
227 "Times-Roman" => return Some((StandardFont::TimesRoman, true)),
228 "Times-Bold" => return Some((StandardFont::TimesBold, true)),
229 "Times-Italic" => return Some((StandardFont::TimesItalic, true)),
230 "Times-BoldItalic" => return Some((StandardFont::TimesBoldItalic, true)),
231 "Symbol" => return Some((StandardFont::Symbol, true)),
232 "ZapfDingbats" => return Some((StandardFont::ZapfDingBats, true)),
233 _ => {}
234 }
235
236 let lower = name.to_ascii_lowercase();
239
240 let is_bold = descriptor.get::<u32>(FONT_WEIGHT).is_some_and(|w| w >= 700)
241 || lower.contains("bold")
242 || lower.contains("demi");
243 let is_italic = descriptor
244 .get::<f32>(ITALIC_ANGLE)
245 .is_some_and(|a| a != 0.0)
246 || lower.contains("italic")
247 || lower.contains("oblique");
248
249 let (family, exact) = if lower.contains("helvetica") {
250 (Some(StandardFontFamily::Helvetica), true)
251 } else if lower.contains("arial") || lower.contains("sans") {
252 (Some(StandardFontFamily::Helvetica), false)
253 } else if lower.contains("courier") {
254 (Some(StandardFontFamily::Courier), true)
255 } else if lower.contains("mono") {
256 (Some(StandardFontFamily::Courier), false)
257 } else if lower.contains("times") {
258 (Some(StandardFontFamily::Times), true)
259 } else if lower.contains("serif") {
260 (Some(StandardFontFamily::Times), false)
261 } else if lower.contains("zapfdingbats") || lower.contains("dingbats") {
262 return Some((StandardFont::ZapfDingBats, false));
263 } else {
264 (None, false)
265 };
266
267 let font = match (family?, is_bold, is_italic) {
268 (StandardFontFamily::Helvetica, false, false) => StandardFont::Helvetica,
269 (StandardFontFamily::Helvetica, true, false) => StandardFont::HelveticaBold,
270 (StandardFontFamily::Helvetica, false, true) => StandardFont::HelveticaOblique,
271 (StandardFontFamily::Helvetica, true, true) => StandardFont::HelveticaBoldOblique,
272 (StandardFontFamily::Courier, false, false) => StandardFont::Courier,
273 (StandardFontFamily::Courier, true, false) => StandardFont::CourierBold,
274 (StandardFontFamily::Courier, false, true) => StandardFont::CourierOblique,
275 (StandardFontFamily::Courier, true, true) => StandardFont::CourierBoldOblique,
276 (StandardFontFamily::Times, false, false) => StandardFont::TimesRoman,
277 (StandardFontFamily::Times, true, false) => StandardFont::TimesBold,
278 (StandardFontFamily::Times, false, true) => StandardFont::TimesItalic,
279 (StandardFontFamily::Times, true, true) => StandardFont::TimesBoldItalic,
280 };
281
282 Some((font, exact))
283}
284
285#[derive(Debug)]
286pub(crate) enum StandardFontBlob {
287 Cff(CffFontBlob),
288 Otf(OpenTypeFontBlob, HashMap<String, GlyphId>),
289}
290
291impl StandardFontBlob {
292 pub(crate) fn from_data(data: FontData, index: u32) -> Option<Self> {
293 if let Some(blob) = CffFontBlob::new(data.clone()) {
294 Some(Self::new_cff(blob))
295 } else {
296 OpenTypeFontBlob::new(data, index).map(Self::new_otf)
297 }
298 }
299
300 pub(crate) fn new_cff(blob: CffFontBlob) -> Self {
301 Self::Cff(blob)
302 }
303
304 pub(crate) fn new_otf(blob: OpenTypeFontBlob) -> Self {
305 let glyph_names = blob.glyph_names();
306 Self::Otf(blob, glyph_names)
307 }
308}
309
310impl StandardFontBlob {
311 pub(crate) fn name_to_glyph(&self, name: &str) -> Option<GlyphId> {
312 match self {
313 Self::Cff(blob) => blob.glyph_index_by_name(name),
314 Self::Otf(_, glyph_names) => glyph_names.get(name).copied(),
315 }
316 }
317
318 pub(crate) fn unicode_to_glyph(&self, code: u32) -> Option<GlyphId> {
319 match self {
320 Self::Cff(_) => None,
321 Self::Otf(blob, _) => blob
322 .font_ref()
323 .cmap()
324 .ok()
325 .and_then(|c| c.map_codepoint(code)),
326 }
327 }
328
329 pub(crate) fn advance_width(&self, glyph: GlyphId) -> Option<f32> {
330 match self {
331 Self::Cff(_) => None,
332 Self::Otf(blob, _) => blob.glyph_metrics().advance_width(glyph),
333 }
334 }
335
336 pub(crate) fn outline_glyph(&self, glyph: GlyphId) -> BezPath {
337 if glyph == GlyphId::NOTDEF {
340 return BezPath::new();
341 }
342
343 match self {
344 Self::Cff(blob) => blob.outline_glyph(glyph),
345 Self::Otf(blob, _) => blob.outline_glyph(glyph),
346 }
347 }
348}
349
350#[derive(Debug)]
351pub(crate) struct StandardKind {
352 base_font: StandardFont,
353 base_font_blob: StandardFontBlob,
354 encoding: Encoding,
355 widths: Vec<Width>,
356 missing_width: f32,
357 fallback: bool,
358 glyph_to_code: RefCell<HashMap<GlyphId, u8>>,
359 encodings: HashMap<u8, String>,
360}
361
362impl StandardKind {
363 pub(crate) fn new(dict: &Dict<'_>, resolver: &FontResolverFn) -> Option<Self> {
364 let descriptor = dict.get::<Dict<'_>>(FONT_DESC).unwrap_or_default();
365 let (font, exact) = select_standard_font(dict, &descriptor)?;
366 Self::new_with_standard(dict, font, !exact, resolver)
367 }
368
369 pub(crate) fn new_with_standard(
370 dict: &Dict<'_>,
371 base_font: StandardFont,
372 fallback: bool,
373 resolver: &FontResolverFn,
374 ) -> Option<Self> {
375 let descriptor = dict.get::<Dict<'_>>(FONT_DESC).unwrap_or_default();
376 let (widths, missing_width) = read_widths(dict, &descriptor)?;
377
378 let (mut encoding, encoding_map) = read_encoding(dict);
379
380 if matches!(base_font, StandardFont::Symbol | StandardFont::ZapfDingBats) {
382 encoding = Encoding::BuiltIn;
383 }
384
385 let (blob, index) = resolver(&FontQuery::Standard(base_font))?;
386 let base_font_blob = StandardFontBlob::from_data(blob, index)?;
387
388 Some(Self {
389 base_font,
390 base_font_blob,
391 widths,
392 missing_width,
393 encodings: encoding_map,
394 glyph_to_code: RefCell::new(HashMap::new()),
395 fallback,
396 encoding,
397 })
398 }
399
400 fn code_to_ps_name(&self, code: u8) -> Option<&str> {
401 let bf = self.base_font;
402
403 self.encodings
404 .get(&code)
405 .map(String::as_str)
406 .or_else(|| match self.encoding {
407 Encoding::BuiltIn => bf.code_to_name(code),
408 _ => self.encoding.map_code(code),
409 })
410 }
411
412 pub(crate) fn map_code(&self, code: u8) -> GlyphId {
413 let result = self
414 .code_to_ps_name(code)
415 .and_then(|c| {
416 self.base_font_blob.name_to_glyph(c).or_else(|| {
417 glyph_names::get(c).and_then(|c| {
419 self.base_font_blob
420 .unicode_to_glyph(c.chars().nth(0).unwrap() as u32)
421 })
422 })
423 })
424 .unwrap_or(GlyphId::NOTDEF);
425 self.glyph_to_code.borrow_mut().insert(result, code);
426
427 result
428 }
429
430 pub(crate) fn outline_glyph(&self, glyph: GlyphId) -> BezPath {
431 let path = self.base_font_blob.outline_glyph(glyph);
432
433 if let Some(code) = self.glyph_to_code.borrow().get(&glyph).copied()
437 && let Some(actual_width) = self.base_font_blob.advance_width(glyph).or_else(|| {
438 self.code_to_ps_name(code)
439 .and_then(|name| self.base_font.get_width(name))
440 })
441 {
442 let should_width = if self.fallback {
448 if let Some(Width::Value(w)) = self.widths.get(code as usize).copied() {
449 w
450 } else {
451 return path;
452 }
453 } else if let Some(w) = self
454 .code_to_ps_name(code)
455 .and_then(|name| self.base_font.get_width(name))
456 {
457 w
458 } else {
459 return path;
460 };
461
462 return stretch_glyph(path, should_width, actual_width);
463 }
464
465 path
466 }
467
468 pub(crate) fn glyph_width(&self, code: u8) -> Option<f32> {
469 match self.widths.get(code as usize).copied() {
470 Some(Width::Value(w)) => Some(w),
471 Some(Width::Missing) => Some(self.missing_width),
472 None => self
473 .code_to_ps_name(code)
474 .and_then(|c| self.base_font.get_width(c)),
475 }
476 }
477
478 pub(crate) fn char_code_to_unicode(&self, code: u8) -> Option<char> {
479 self.code_to_ps_name(code).and_then(glyph_name_to_unicode)
480 }
481
482 pub(crate) fn is_italic(&self) -> bool {
483 self.base_font.is_italic()
484 }
485
486 pub(crate) fn is_bold(&self) -> bool {
487 self.base_font.is_bold()
488 }
489
490 pub(crate) fn is_serif(&self) -> bool {
491 self.base_font.is_serif()
492 }
493
494 pub(crate) fn is_monospace(&self) -> bool {
495 self.base_font.is_monospace()
496 }
497}