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 kurbo::BezPath;
10use pdf_syntax::object::Dict;
11use pdf_syntax::object::Name;
12use pdf_syntax::object::dict::keys::{
13 BASE_FONT, FONT_DESC, FONT_FAMILY, FONT_WEIGHT, ITALIC_ANGLE, MISSING_WIDTH,
14};
15use skrifa::raw::TableProvider;
16use skrifa::{GlyphId, GlyphId16};
17use std::cell::RefCell;
18use std::collections::HashMap;
19
20#[derive(Copy, Clone, Debug)]
22pub enum StandardFont {
23 Helvetica,
25 HelveticaBold,
27 HelveticaOblique,
29 HelveticaBoldOblique,
31 Courier,
33 CourierBold,
35 CourierOblique,
37 CourierBoldOblique,
39 TimesRoman,
41 TimesBold,
43 TimesItalic,
45 TimesBoldItalic,
47 ZapfDingBats,
49 Symbol,
51}
52
53impl StandardFont {
54 pub(crate) fn code_to_name(&self, code: u8) -> Option<&'static str> {
55 match self {
56 Self::Symbol => symbol::get(code),
57 Self::ZapfDingBats => zapf_dings::get(code),
60 _ => standard::get(code),
61 }
62 }
63
64 pub(crate) fn get_width(&self, mut name: &str) -> Option<f32> {
65 if name == ".notdef" {
67 return Some(250.0);
68 }
69
70 name = normalized_glyph_name(name);
71
72 match self {
73 Self::Helvetica => metrics::HELVETICA.get(name).copied(),
74 Self::HelveticaBold => metrics::HELVETICA_BOLD.get(name).copied(),
75 Self::HelveticaOblique => metrics::HELVETICA_OBLIQUE.get(name).copied(),
76 Self::HelveticaBoldOblique => metrics::HELVETICA_BOLD_OBLIQUE.get(name).copied(),
77 Self::Courier => metrics::COURIER.get(name).copied(),
78 Self::CourierBold => metrics::COURIER_BOLD.get(name).copied(),
79 Self::CourierOblique => metrics::COURIER_OBLIQUE.get(name).copied(),
80 Self::CourierBoldOblique => metrics::COURIER_BOLD_OBLIQUE.get(name).copied(),
81 Self::TimesRoman => metrics::TIMES_ROMAN.get(name).copied(),
82 Self::TimesBold => metrics::TIMES_BOLD.get(name).copied(),
83 Self::TimesItalic => metrics::TIMES_ITALIC.get(name).copied(),
84 Self::TimesBoldItalic => metrics::TIMES_BOLD_ITALIC.get(name).copied(),
85 Self::ZapfDingBats => metrics::ZAPF_DING_BATS.get(name).copied(),
86 Self::Symbol => metrics::SYMBOL.get(name).copied(),
87 }
88 }
89
90 pub(crate) fn as_str(&self) -> &'static str {
91 match self {
92 Self::Helvetica => "Helvetica",
93 Self::HelveticaBold => "Helvetica Bold",
94 Self::HelveticaOblique => "Helvetica Oblique",
95 Self::HelveticaBoldOblique => "Helvetica Bold Oblique",
96 Self::Courier => "Courier",
97 Self::CourierBold => "Courier Bold",
98 Self::CourierOblique => "Courier Oblique",
99 Self::CourierBoldOblique => "Courier Bold Oblique",
100 Self::TimesRoman => "Times Roman",
101 Self::TimesBold => "Times Bold",
102 Self::TimesItalic => "Times Italic",
103 Self::TimesBoldItalic => "Times Bold Italic",
104 Self::ZapfDingBats => "Zapf Dingbats",
105 Self::Symbol => "Symbol",
106 }
107 }
108
109 pub fn postscript_name(&self) -> &'static str {
111 match self {
112 Self::Helvetica => "Helvetica",
113 Self::HelveticaBold => "Helvetica-Bold",
114 Self::HelveticaOblique => "Helvetica-Oblique",
115 Self::HelveticaBoldOblique => "Helvetica-BoldOblique",
116 Self::Courier => "Courier",
117 Self::CourierBold => "Courier-Bold",
118 Self::CourierOblique => "Courier-Oblique",
119 Self::CourierBoldOblique => "Courier-BoldOblique",
120 Self::TimesRoman => "Times-Roman",
121 Self::TimesBold => "Times-Bold",
122 Self::TimesItalic => "Times-Italic",
123 Self::TimesBoldItalic => "Times-BoldItalic",
124 Self::ZapfDingBats => "ZapfDingbats",
125 Self::Symbol => "Symbol",
126 }
127 }
128
129 pub(crate) fn is_bold(&self) -> bool {
130 matches!(
131 self,
132 Self::HelveticaBold
133 | Self::HelveticaBoldOblique
134 | Self::CourierBold
135 | Self::CourierBoldOblique
136 | Self::TimesBold
137 | Self::TimesBoldItalic
138 )
139 }
140
141 pub(crate) fn is_italic(&self) -> bool {
142 matches!(
143 self,
144 Self::HelveticaOblique
145 | Self::HelveticaBoldOblique
146 | Self::CourierOblique
147 | Self::CourierBoldOblique
148 | Self::TimesItalic
149 | Self::TimesBoldItalic
150 )
151 }
152
153 pub(crate) fn is_serif(&self) -> bool {
154 matches!(
155 self,
156 Self::TimesRoman | Self::TimesBold | Self::TimesItalic | Self::TimesBoldItalic
157 )
158 }
159
160 pub(crate) fn is_monospace(&self) -> bool {
161 matches!(
162 self,
163 Self::Courier | Self::CourierBold | Self::CourierOblique | Self::CourierBoldOblique
164 )
165 }
166
167 #[cfg(feature = "embed-fonts")]
176 pub fn get_font_data(&self) -> (FontData, u32) {
177 use std::sync::Arc;
178
179 let data = match self {
180 Self::Helvetica => &include_bytes!("../../assets/FoxitSans.pfb")[..],
181 Self::HelveticaBold => &include_bytes!("../../assets/FoxitSansBold.pfb")[..],
182 Self::HelveticaOblique => &include_bytes!("../../assets/FoxitSansItalic.pfb")[..],
183 Self::HelveticaBoldOblique => {
184 &include_bytes!("../../assets/FoxitSansBoldItalic.pfb")[..]
185 }
186 Self::Courier => &include_bytes!("../../assets/FoxitFixed.pfb")[..],
187 Self::CourierBold => &include_bytes!("../../assets/FoxitFixedBold.pfb")[..],
188 Self::CourierOblique => &include_bytes!("../../assets/FoxitFixedItalic.pfb")[..],
189 Self::CourierBoldOblique => {
190 &include_bytes!("../../assets/FoxitFixedBoldItalic.pfb")[..]
191 }
192 Self::TimesRoman => &include_bytes!("../../assets/FoxitSerif.pfb")[..],
193 Self::TimesBold => &include_bytes!("../../assets/FoxitSerifBold.pfb")[..],
194 Self::TimesItalic => &include_bytes!("../../assets/FoxitSerifItalic.pfb")[..],
195 Self::TimesBoldItalic => &include_bytes!("../../assets/FoxitSerifBoldItalic.pfb")[..],
196 Self::ZapfDingBats => &include_bytes!("../../assets/FoxitDingbats.pfb")[..],
197 Self::Symbol => {
198 include_bytes!("../../assets/FoxitSymbol.pfb")
199 }
200 };
201
202 (Arc::new(data), 0)
203 }
204}
205
206enum StandardFontFamily {
207 Helvetica,
208 Courier,
209 Times,
210}
211
212fn standard_font_alias(name: &str) -> Option<StandardFont> {
220 match name {
221 "ArialMT" | "Arial" => Some(StandardFont::Helvetica),
223 "Arial-BoldMT" | "Arial,Bold" | "Arial-Bold" => Some(StandardFont::HelveticaBold),
224 "Arial-ItalicMT" | "Arial,Italic" | "Arial-Italic" => Some(StandardFont::HelveticaOblique),
225 "Arial-BoldItalicMT" | "Arial,BoldItalic" | "Arial-BoldItalic" => {
226 Some(StandardFont::HelveticaBoldOblique)
227 }
228 "TimesNewRomanPSMT" | "TimesNewRoman" | "TimesNewRomanPS" => Some(StandardFont::TimesRoman),
230 "TimesNewRomanPS-BoldMT"
231 | "TimesNewRoman-Bold"
232 | "TimesNewRomanPS-Bold"
233 | "TimesNewRoman,Bold" => Some(StandardFont::TimesBold),
234 "TimesNewRomanPS-ItalicMT"
235 | "TimesNewRoman-Italic"
236 | "TimesNewRomanPS-Italic"
237 | "TimesNewRoman,Italic" => Some(StandardFont::TimesItalic),
238 "TimesNewRomanPS-BoldItalicMT"
239 | "TimesNewRoman-BoldItalic"
240 | "TimesNewRomanPS-BoldItalic"
241 | "TimesNewRoman,BoldItalic" => Some(StandardFont::TimesBoldItalic),
242 "CourierNewPSMT" | "CourierNew" => Some(StandardFont::Courier),
244 "CourierNewPS-BoldMT" | "CourierNew-Bold" | "CourierNewPS-Bold" => {
245 Some(StandardFont::CourierBold)
246 }
247 "CourierNewPS-ItalicMT" | "CourierNew-Italic" | "CourierNewPS-Italic" => {
248 Some(StandardFont::CourierOblique)
249 }
250 "CourierNewPS-BoldItalicMT" | "CourierNew-BoldItalic" | "CourierNewPS-BoldItalic" => {
251 Some(StandardFont::CourierBoldOblique)
252 }
253 _ => None,
254 }
255}
256
257pub(crate) fn select_standard_font(
258 dict: &Dict<'_>,
259 descriptor: &Dict<'_>,
260) -> Option<(StandardFont, bool)> {
261 let base_font = dict.get::<Name>(BASE_FONT)?;
262 let name = strip_subset_prefix(base_font.as_str());
263
264 match name {
266 "Helvetica" => return Some((StandardFont::Helvetica, true)),
267 "Helvetica-Bold" => return Some((StandardFont::HelveticaBold, true)),
268 "Helvetica-Oblique" => return Some((StandardFont::HelveticaOblique, true)),
269 "Helvetica-BoldOblique" => return Some((StandardFont::HelveticaBoldOblique, true)),
270 "Courier" => return Some((StandardFont::Courier, true)),
271 "Courier-Bold" => return Some((StandardFont::CourierBold, true)),
272 "Courier-Oblique" => return Some((StandardFont::CourierOblique, true)),
273 "Courier-BoldOblique" => return Some((StandardFont::CourierBoldOblique, true)),
274 "Times-Roman" => return Some((StandardFont::TimesRoman, true)),
275 "Times-Bold" => return Some((StandardFont::TimesBold, true)),
276 "Times-Italic" => return Some((StandardFont::TimesItalic, true)),
277 "Times-BoldItalic" => return Some((StandardFont::TimesBoldItalic, true)),
278 "Symbol" => return Some((StandardFont::Symbol, true)),
279 "ZapfDingbats" => return Some((StandardFont::ZapfDingBats, true)),
280 _ => {}
281 }
282
283 if let Some(alias) = standard_font_alias(name) {
288 return Some((alias, false));
289 }
290
291 let lower = name.to_ascii_lowercase();
294
295 let family_field = descriptor
300 .get::<Name>(FONT_FAMILY)
301 .map(|n| n.as_str().to_ascii_lowercase())
302 .unwrap_or_default();
303
304 let is_bold = descriptor.get::<u32>(FONT_WEIGHT).is_some_and(|w| w >= 600)
310 || lower.contains("bold")
311 || lower.contains("demi")
312 || family_field.contains("bold")
313 || family_field.contains("demi");
314 let is_italic = descriptor
321 .get::<f32>(ITALIC_ANGLE)
322 .is_some_and(|a| a < -5.0 || a > 5.0)
323 || lower.contains("italic")
324 || lower.contains("oblique")
325 || family_field.contains("italic")
326 || family_field.contains("oblique");
327
328 let haystack = if family_field.is_empty() {
330 lower.clone()
331 } else {
332 format!("{lower} {family_field}")
333 };
334
335 let (family, exact) = if haystack.contains("helvetica") {
354 (Some(StandardFontFamily::Helvetica), true) } else if haystack.contains("arial") || haystack.contains("sans") {
356 (Some(StandardFontFamily::Helvetica), false) } else if haystack.contains("courier") {
358 (Some(StandardFontFamily::Courier), true) } else if haystack.contains("mono") {
360 (Some(StandardFontFamily::Courier), false) } else if haystack.contains("times") {
362 (Some(StandardFontFamily::Times), true) } else if haystack.contains("serif") {
364 (Some(StandardFontFamily::Times), false) } else if haystack.contains("zapfdingbats") || haystack.contains("dingbats") {
366 return Some((StandardFont::ZapfDingBats, false));
367 } else {
368 (None, false)
369 };
370
371 let font = match (family?, is_bold, is_italic) {
372 (StandardFontFamily::Helvetica, false, false) => StandardFont::Helvetica,
373 (StandardFontFamily::Helvetica, true, false) => StandardFont::HelveticaBold,
374 (StandardFontFamily::Helvetica, false, true) => StandardFont::HelveticaOblique,
375 (StandardFontFamily::Helvetica, true, true) => StandardFont::HelveticaBoldOblique,
376 (StandardFontFamily::Courier, false, false) => StandardFont::Courier,
377 (StandardFontFamily::Courier, true, false) => StandardFont::CourierBold,
378 (StandardFontFamily::Courier, false, true) => StandardFont::CourierOblique,
379 (StandardFontFamily::Courier, true, true) => StandardFont::CourierBoldOblique,
380 (StandardFontFamily::Times, false, false) => StandardFont::TimesRoman,
381 (StandardFontFamily::Times, true, false) => StandardFont::TimesBold,
382 (StandardFontFamily::Times, false, true) => StandardFont::TimesItalic,
383 (StandardFontFamily::Times, true, true) => StandardFont::TimesBoldItalic,
384 };
385
386 Some((font, exact))
387}
388
389#[derive(Debug)]
390pub(crate) enum StandardFontBlob {
391 Cff(CffFontBlob),
392 Otf(OpenTypeFontBlob, HashMap<String, GlyphId>),
393}
394
395impl StandardFontBlob {
396 pub(crate) fn from_data(data: FontData, index: u32) -> Option<Self> {
397 if let Some(blob) = CffFontBlob::new(data.clone()) {
398 Some(Self::new_cff(blob))
399 } else {
400 OpenTypeFontBlob::new(data, index).map(Self::new_otf)
401 }
402 }
403
404 pub(crate) fn new_cff(blob: CffFontBlob) -> Self {
405 Self::Cff(blob)
406 }
407
408 pub(crate) fn new_otf(blob: OpenTypeFontBlob) -> Self {
409 let mut glyph_names = HashMap::new();
410
411 if let Ok(post) = blob.font_ref().post() {
412 for i in 0..blob.num_glyphs() {
413 if let Some(str) = post.glyph_name(GlyphId16::new(i)) {
414 glyph_names.insert(str.to_string(), GlyphId::new(i as u32));
415 }
416 }
417 }
418
419 Self::Otf(blob, glyph_names)
420 }
421}
422
423impl StandardFontBlob {
424 pub(crate) fn name_to_glyph(&self, name: &str) -> Option<GlyphId> {
425 match self {
426 Self::Cff(blob) => blob
427 .table()
428 .glyph_index_by_name(name)
429 .map(|g| GlyphId::new(g.0 as u32)),
430 Self::Otf(_, glyph_names) => glyph_names.get(name).copied(),
431 }
432 }
433
434 pub(crate) fn unicode_to_glyph(&self, code: u32) -> Option<GlyphId> {
435 match self {
436 Self::Cff(_) => None,
437 Self::Otf(blob, _) => blob
438 .font_ref()
439 .cmap()
440 .ok()
441 .and_then(|c| c.map_codepoint(code)),
442 }
443 }
444
445 pub(crate) fn advance_width(&self, glyph: GlyphId) -> Option<f32> {
446 match self {
447 Self::Cff(_) => None,
448 Self::Otf(blob, _) => blob.glyph_metrics().advance_width(glyph),
449 }
450 }
451
452 pub(crate) fn outline_glyph(&self, glyph: GlyphId) -> BezPath {
453 if glyph == GlyphId::NOTDEF {
456 return BezPath::new();
457 }
458
459 match self {
460 Self::Cff(blob) => blob.outline_glyph(glyph),
461 Self::Otf(blob, _) => blob.outline_glyph(glyph),
462 }
463 }
464}
465
466#[derive(Debug)]
467pub(crate) struct StandardKind {
468 base_font: StandardFont,
469 base_font_blob: StandardFontBlob,
470 encoding: Encoding,
471 widths: Vec<Width>,
472 missing_width: Option<f32>,
473 fallback: bool,
474 glyph_to_code: RefCell<HashMap<GlyphId, u8>>,
475 encodings: HashMap<u8, String>,
476}
477
478impl StandardKind {
479 pub(crate) fn new(dict: &Dict<'_>, resolver: &FontResolverFn) -> Option<Self> {
480 let descriptor = dict.get::<Dict<'_>>(FONT_DESC).unwrap_or_default();
481 let (font, exact) = select_standard_font(dict, &descriptor)?;
482 Self::new_with_standard(dict, font, !exact, resolver)
483 }
484
485 pub(crate) fn new_with_standard(
486 dict: &Dict<'_>,
487 base_font: StandardFont,
488 fallback: bool,
489 resolver: &FontResolverFn,
490 ) -> Option<Self> {
491 let descriptor = dict.get::<Dict<'_>>(FONT_DESC).unwrap_or_default();
492 let (widths, missing_width) = read_widths(dict, &descriptor)?;
493 let missing_width = descriptor
494 .contains_key(MISSING_WIDTH)
495 .then_some(missing_width);
496
497 let (mut encoding, encoding_map) = read_encoding(dict);
498
499 if matches!(base_font, StandardFont::Symbol | StandardFont::ZapfDingBats) {
501 encoding = Encoding::BuiltIn;
502 }
503
504 let (blob, index) = resolver(&FontQuery::Standard(base_font))?;
505 let base_font_blob = StandardFontBlob::from_data(blob, index)?;
506
507 Some(Self {
508 base_font,
509 base_font_blob,
510 widths,
511 missing_width,
512 encodings: encoding_map,
513 glyph_to_code: RefCell::new(HashMap::new()),
514 fallback,
515 encoding,
516 })
517 }
518
519 fn code_to_ps_name(&self, code: u8) -> Option<&str> {
520 let bf = self.base_font;
521
522 self.encodings
523 .get(&code)
524 .map(String::as_str)
525 .or_else(|| match self.encoding {
526 Encoding::BuiltIn => bf.code_to_name(code),
527 _ => self.encoding.map_code(code),
528 })
529 }
530
531 pub(crate) fn map_code(&self, code: u8) -> GlyphId {
532 let result = self
533 .code_to_ps_name(code)
534 .and_then(|c| {
535 self.base_font_blob.name_to_glyph(c).or_else(|| {
536 glyph_names::get(c).and_then(|c| {
538 self.base_font_blob
539 .unicode_to_glyph(c.chars().nth(0).unwrap() as u32)
540 })
541 })
542 })
543 .unwrap_or(GlyphId::NOTDEF);
544 self.glyph_to_code.borrow_mut().insert(result, code);
545
546 result
547 }
548
549 pub(crate) fn outline_glyph(&self, glyph: GlyphId) -> BezPath {
550 let path = self.base_font_blob.outline_glyph(glyph);
551
552 if let Some(code) = self.glyph_to_code.borrow().get(&glyph).copied()
556 && let Some(actual_width) = self.base_font_blob.advance_width(glyph).or_else(|| {
557 self.code_to_ps_name(code)
558 .and_then(|name| self.base_font.get_width(name))
559 })
560 {
561 let should_width = if self.fallback {
567 if let Some(Width::Value(w)) = self.widths.get(code as usize).copied() {
568 w
569 } else {
570 return path;
571 }
572 } else if let Some(w) = self
573 .code_to_ps_name(code)
574 .and_then(|name| self.base_font.get_width(name))
575 {
576 w
577 } else {
578 return path;
579 };
580
581 return stretch_glyph(path, should_width, actual_width);
582 }
583
584 path
585 }
586
587 pub(crate) fn glyph_width(&self, code: u8) -> Option<f32> {
588 match self.widths.get(code as usize).copied() {
589 Some(Width::Value(w)) => Some(w),
590 Some(Width::Missing) => self.missing_width.or_else(|| {
591 self.code_to_ps_name(code)
592 .and_then(|c| self.base_font.get_width(c))
593 }),
594 _ => self
595 .code_to_ps_name(code)
596 .and_then(|c| self.base_font.get_width(c)),
597 }
598 }
599
600 pub(crate) fn char_code_to_unicode(&self, code: u8) -> Option<char> {
601 self.code_to_ps_name(code).and_then(glyph_name_to_unicode)
602 }
603
604 pub(crate) fn is_italic(&self) -> bool {
605 self.base_font.is_italic()
606 }
607
608 pub(crate) fn is_bold(&self) -> bool {
609 self.base_font.is_bold()
610 }
611
612 pub(crate) fn is_serif(&self) -> bool {
613 self.base_font.is_serif()
614 }
615
616 pub(crate) fn is_monospace(&self) -> bool {
617 self.base_font.is_monospace()
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 fn build_widths(entries: &[(u8, f32)]) -> Vec<Width> {
626 let mut widths = vec![Width::Missing; 256];
627 for (code, width) in entries {
628 widths[*code as usize] = Width::Value(*width);
629 }
630 widths
631 }
632
633 fn build_standard_kind(widths: Vec<Width>, missing_width: Option<f32>) -> StandardKind {
634 let (data, index) = StandardFont::Helvetica.get_font_data();
635 let base_font_blob =
636 StandardFontBlob::from_data(data, index).expect("standard font data should parse");
637
638 StandardKind {
639 base_font: StandardFont::Helvetica,
640 base_font_blob,
641 encoding: Encoding::WinAnsi,
642 widths,
643 missing_width,
644 fallback: true,
645 glyph_to_code: RefCell::new(HashMap::new()),
646 encodings: HashMap::new(),
647 }
648 }
649
650 #[test]
651 fn glyph_width_falls_back_to_base_metrics_when_missing_width_is_absent() {
652 let font = build_standard_kind(build_widths(&[(b'A', 600.0)]), None);
653
654 assert_eq!(font.glyph_width(b'A'), Some(600.0));
655 assert_eq!(
656 font.glyph_width(b'B'),
657 StandardFont::Helvetica.get_width("B")
658 );
659 }
660
661 #[test]
662 fn glyph_width_respects_explicit_zero_missing_width() {
663 let font = build_standard_kind(build_widths(&[(b'A', 600.0)]), Some(0.0));
664
665 assert_eq!(font.glyph_width(b'A'), Some(600.0));
666 assert_eq!(font.glyph_width(b'B'), Some(0.0));
667 }
668
669 #[test]
670 fn arial_aliases_resolve_to_helvetica_family() {
671 assert!(matches!(
672 standard_font_alias("ArialMT"),
673 Some(StandardFont::Helvetica)
674 ));
675 assert!(matches!(
676 standard_font_alias("Arial-BoldMT"),
677 Some(StandardFont::HelveticaBold)
678 ));
679 assert!(matches!(
680 standard_font_alias("Arial-ItalicMT"),
681 Some(StandardFont::HelveticaOblique)
682 ));
683 assert!(matches!(
684 standard_font_alias("Arial-BoldItalicMT"),
685 Some(StandardFont::HelveticaBoldOblique)
686 ));
687 }
688
689 #[test]
690 fn times_new_roman_aliases_resolve_to_times_family() {
691 assert!(matches!(
692 standard_font_alias("TimesNewRomanPSMT"),
693 Some(StandardFont::TimesRoman)
694 ));
695 assert!(matches!(
696 standard_font_alias("TimesNewRomanPS-BoldMT"),
697 Some(StandardFont::TimesBold)
698 ));
699 assert!(matches!(
700 standard_font_alias("TimesNewRomanPS-ItalicMT"),
701 Some(StandardFont::TimesItalic)
702 ));
703 assert!(matches!(
704 standard_font_alias("TimesNewRomanPS-BoldItalicMT"),
705 Some(StandardFont::TimesBoldItalic)
706 ));
707 }
708
709 #[test]
710 fn courier_new_aliases_resolve_to_courier_family() {
711 assert!(matches!(
712 standard_font_alias("CourierNewPSMT"),
713 Some(StandardFont::Courier)
714 ));
715 assert!(matches!(
716 standard_font_alias("CourierNewPS-BoldMT"),
717 Some(StandardFont::CourierBold)
718 ));
719 assert!(matches!(
720 standard_font_alias("CourierNewPS-ItalicMT"),
721 Some(StandardFont::CourierOblique)
722 ));
723 assert!(matches!(
724 standard_font_alias("CourierNewPS-BoldItalicMT"),
725 Some(StandardFont::CourierBoldOblique)
726 ));
727 }
728
729 #[test]
730 fn unknown_names_do_not_alias() {
731 assert!(standard_font_alias("LiberationSans").is_none());
732 assert!(standard_font_alias("CenturySchoolbook").is_none());
733 assert!(standard_font_alias("").is_none());
734 }
735}