1use crate::FontResolverFn;
2use crate::cache::Cache;
3use crate::font::blob::{CffFontBlob, OpenTypeFontBlob};
4use crate::font::generated::{glyph_names, metrics, standard, symbol, zapf_dings};
5use crate::font::true_type::{Width, read_encoding, read_widths};
6use crate::font::{
7 Encoding, FontData, FontQuery, glyph_name_to_bfstring, normalized_glyph_name, stretch_glyph,
8 strip_subset_prefix,
9};
10use kurbo::BezPath;
11use pdf_font::cmap::BfString;
12use pdf_syntax::object::Dict;
13use pdf_syntax::object::Name;
14use pdf_syntax::object::dict::keys::{
15 BASE_FONT, FONT_DESC, FONT_FAMILY, FONT_WEIGHT, ITALIC_ANGLE, MISSING_WIDTH,
16};
17use skrifa::raw::TableProvider;
18use skrifa::{GlyphId, GlyphId16};
19use std::cell::RefCell;
20use std::collections::HashMap;
21
22#[derive(Copy, Clone, Debug)]
24pub enum StandardFont {
25 Helvetica,
27 HelveticaBold,
29 HelveticaOblique,
31 HelveticaBoldOblique,
33 Courier,
35 CourierBold,
37 CourierOblique,
39 CourierBoldOblique,
41 TimesRoman,
43 TimesBold,
45 TimesItalic,
47 TimesBoldItalic,
49 ZapfDingBats,
51 Symbol,
53}
54
55impl StandardFont {
56 pub fn char_width(&self, ch: char) -> Option<f32> {
62 let code = u8::try_from(ch as u32).ok()?;
63 let name = self.code_to_name(code)?;
64 self.get_width(name)
65 }
66
67 pub(crate) fn code_to_name(&self, code: u8) -> Option<&'static str> {
68 match self {
69 Self::Symbol => symbol::get(code),
70 Self::ZapfDingBats => zapf_dings::get(code),
73 _ => standard::get(code),
74 }
75 }
76
77 pub(crate) fn get_width(&self, mut name: &str) -> Option<f32> {
78 if name == ".notdef" {
80 return Some(250.0);
81 }
82
83 name = normalized_glyph_name(name);
84
85 match self {
86 Self::Helvetica => metrics::HELVETICA.get(name).copied(),
87 Self::HelveticaBold => metrics::HELVETICA_BOLD.get(name).copied(),
88 Self::HelveticaOblique => metrics::HELVETICA_OBLIQUE.get(name).copied(),
89 Self::HelveticaBoldOblique => metrics::HELVETICA_BOLD_OBLIQUE.get(name).copied(),
90 Self::Courier => metrics::COURIER.get(name).copied(),
91 Self::CourierBold => metrics::COURIER_BOLD.get(name).copied(),
92 Self::CourierOblique => metrics::COURIER_OBLIQUE.get(name).copied(),
93 Self::CourierBoldOblique => metrics::COURIER_BOLD_OBLIQUE.get(name).copied(),
94 Self::TimesRoman => metrics::TIMES_ROMAN.get(name).copied(),
95 Self::TimesBold => metrics::TIMES_BOLD.get(name).copied(),
96 Self::TimesItalic => metrics::TIMES_ITALIC.get(name).copied(),
97 Self::TimesBoldItalic => metrics::TIMES_BOLD_ITALIC.get(name).copied(),
98 Self::ZapfDingBats => metrics::ZAPF_DING_BATS.get(name).copied(),
99 Self::Symbol => metrics::SYMBOL.get(name).copied(),
100 }
101 }
102
103 pub(crate) fn as_str(&self) -> &'static str {
104 match self {
105 Self::Helvetica => "Helvetica",
106 Self::HelveticaBold => "Helvetica Bold",
107 Self::HelveticaOblique => "Helvetica Oblique",
108 Self::HelveticaBoldOblique => "Helvetica Bold Oblique",
109 Self::Courier => "Courier",
110 Self::CourierBold => "Courier Bold",
111 Self::CourierOblique => "Courier Oblique",
112 Self::CourierBoldOblique => "Courier Bold Oblique",
113 Self::TimesRoman => "Times Roman",
114 Self::TimesBold => "Times Bold",
115 Self::TimesItalic => "Times Italic",
116 Self::TimesBoldItalic => "Times Bold Italic",
117 Self::ZapfDingBats => "Zapf Dingbats",
118 Self::Symbol => "Symbol",
119 }
120 }
121
122 pub fn postscript_name(&self) -> &'static str {
124 match self {
125 Self::Helvetica => "Helvetica",
126 Self::HelveticaBold => "Helvetica-Bold",
127 Self::HelveticaOblique => "Helvetica-Oblique",
128 Self::HelveticaBoldOblique => "Helvetica-BoldOblique",
129 Self::Courier => "Courier",
130 Self::CourierBold => "Courier-Bold",
131 Self::CourierOblique => "Courier-Oblique",
132 Self::CourierBoldOblique => "Courier-BoldOblique",
133 Self::TimesRoman => "Times-Roman",
134 Self::TimesBold => "Times-Bold",
135 Self::TimesItalic => "Times-Italic",
136 Self::TimesBoldItalic => "Times-BoldItalic",
137 Self::ZapfDingBats => "ZapfDingbats",
138 Self::Symbol => "Symbol",
139 }
140 }
141
142 pub(crate) fn is_bold(&self) -> bool {
143 matches!(
144 self,
145 Self::HelveticaBold
146 | Self::HelveticaBoldOblique
147 | Self::CourierBold
148 | Self::CourierBoldOblique
149 | Self::TimesBold
150 | Self::TimesBoldItalic
151 )
152 }
153
154 pub(crate) fn is_italic(&self) -> bool {
155 matches!(
156 self,
157 Self::HelveticaOblique
158 | Self::HelveticaBoldOblique
159 | Self::CourierOblique
160 | Self::CourierBoldOblique
161 | Self::TimesItalic
162 | Self::TimesBoldItalic
163 )
164 }
165
166 pub(crate) fn is_serif(&self) -> bool {
167 matches!(
168 self,
169 Self::TimesRoman | Self::TimesBold | Self::TimesItalic | Self::TimesBoldItalic
170 )
171 }
172
173 pub(crate) fn is_monospace(&self) -> bool {
174 matches!(
175 self,
176 Self::Courier | Self::CourierBold | Self::CourierOblique | Self::CourierBoldOblique
177 )
178 }
179
180 pub(crate) fn canonical_metrics(&self) -> Option<(f64, f64, Option<f64>, Option<f64>)> {
193 match self {
194 Self::Helvetica => Some((718.0, -207.0, Some(718.0), Some(523.0))),
195 Self::HelveticaBold => Some((718.0, -207.0, Some(718.0), Some(532.0))),
196 Self::HelveticaOblique => Some((718.0, -207.0, Some(718.0), Some(523.0))),
197 Self::HelveticaBoldOblique => Some((718.0, -207.0, Some(718.0), Some(532.0))),
198 Self::Courier => Some((629.0, -157.0, Some(562.0), Some(426.0))),
199 Self::CourierBold => Some((629.0, -157.0, Some(562.0), Some(439.0))),
200 Self::CourierOblique => Some((629.0, -157.0, Some(562.0), Some(426.0))),
201 Self::CourierBoldOblique => Some((629.0, -157.0, Some(562.0), Some(439.0))),
202 Self::TimesRoman => Some((683.0, -217.0, Some(662.0), Some(450.0))),
203 Self::TimesBold => Some((683.0, -217.0, Some(676.0), Some(461.0))),
204 Self::TimesItalic => Some((683.0, -217.0, Some(653.0), Some(441.0))),
205 Self::TimesBoldItalic => Some((683.0, -217.0, Some(669.0), Some(462.0))),
206 Self::ZapfDingBats => Some((820.0, -143.0, None, None)),
207 Self::Symbol => Some((1010.0, -293.0, None, None)),
208 }
209 }
210
211 #[cfg(feature = "embed-fonts")]
220 pub fn get_font_data(&self) -> (FontData, u32) {
221 use std::sync::Arc;
222
223 let data = match self {
224 Self::Helvetica => &include_bytes!("../../assets/FoxitSans.pfb")[..],
225 Self::HelveticaBold => &include_bytes!("../../assets/FoxitSansBold.pfb")[..],
226 Self::HelveticaOblique => &include_bytes!("../../assets/FoxitSansItalic.pfb")[..],
227 Self::HelveticaBoldOblique => {
228 &include_bytes!("../../assets/FoxitSansBoldItalic.pfb")[..]
229 }
230 Self::Courier => &include_bytes!("../../assets/FoxitFixed.pfb")[..],
231 Self::CourierBold => &include_bytes!("../../assets/FoxitFixedBold.pfb")[..],
232 Self::CourierOblique => &include_bytes!("../../assets/FoxitFixedItalic.pfb")[..],
233 Self::CourierBoldOblique => {
234 &include_bytes!("../../assets/FoxitFixedBoldItalic.pfb")[..]
235 }
236 Self::TimesRoman => &include_bytes!("../../assets/FoxitSerif.pfb")[..],
237 Self::TimesBold => &include_bytes!("../../assets/FoxitSerifBold.pfb")[..],
238 Self::TimesItalic => &include_bytes!("../../assets/FoxitSerifItalic.pfb")[..],
239 Self::TimesBoldItalic => &include_bytes!("../../assets/FoxitSerifBoldItalic.pfb")[..],
240 Self::ZapfDingBats => &include_bytes!("../../assets/FoxitDingbats.pfb")[..],
241 Self::Symbol => {
242 include_bytes!("../../assets/FoxitSymbol.pfb")
243 }
244 };
245
246 (Arc::new(data), 0)
247 }
248}
249
250enum StandardFontFamily {
251 Helvetica,
252 Courier,
253 Times,
254}
255
256fn standard_font_alias(name: &str) -> Option<StandardFont> {
264 match name {
265 "ArialMT" | "Arial" => Some(StandardFont::Helvetica),
267 "Arial-BoldMT" | "Arial,Bold" | "Arial-Bold" => Some(StandardFont::HelveticaBold),
268 "Arial-ItalicMT" | "Arial,Italic" | "Arial-Italic" => Some(StandardFont::HelveticaOblique),
269 "Arial-BoldItalicMT" | "Arial,BoldItalic" | "Arial-BoldItalic" => {
270 Some(StandardFont::HelveticaBoldOblique)
271 }
272 "TimesNewRomanPSMT" | "TimesNewRoman" | "TimesNewRomanPS" => Some(StandardFont::TimesRoman),
274 "TimesNewRomanPS-BoldMT"
275 | "TimesNewRoman-Bold"
276 | "TimesNewRomanPS-Bold"
277 | "TimesNewRoman,Bold" => Some(StandardFont::TimesBold),
278 "TimesNewRomanPS-ItalicMT"
279 | "TimesNewRoman-Italic"
280 | "TimesNewRomanPS-Italic"
281 | "TimesNewRoman,Italic" => Some(StandardFont::TimesItalic),
282 "TimesNewRomanPS-BoldItalicMT"
283 | "TimesNewRoman-BoldItalic"
284 | "TimesNewRomanPS-BoldItalic"
285 | "TimesNewRoman,BoldItalic" => Some(StandardFont::TimesBoldItalic),
286 "CourierNewPSMT" | "CourierNew" => Some(StandardFont::Courier),
288 "CourierNewPS-BoldMT" | "CourierNew-Bold" | "CourierNewPS-Bold" => {
289 Some(StandardFont::CourierBold)
290 }
291 "CourierNewPS-ItalicMT" | "CourierNew-Italic" | "CourierNewPS-Italic" => {
292 Some(StandardFont::CourierOblique)
293 }
294 "CourierNewPS-BoldItalicMT" | "CourierNew-BoldItalic" | "CourierNewPS-BoldItalic" => {
295 Some(StandardFont::CourierBoldOblique)
296 }
297 _ => None,
298 }
299}
300
301pub(crate) fn select_standard_font(
302 dict: &Dict<'_>,
303 descriptor: &Dict<'_>,
304) -> Option<(StandardFont, bool)> {
305 let base_font = dict.get::<Name>(BASE_FONT)?;
306 let name = strip_subset_prefix(base_font.as_str());
307
308 match name {
310 "Helvetica" => return Some((StandardFont::Helvetica, true)),
311 "Helvetica-Bold" => return Some((StandardFont::HelveticaBold, true)),
312 "Helvetica-Oblique" => return Some((StandardFont::HelveticaOblique, true)),
313 "Helvetica-BoldOblique" => return Some((StandardFont::HelveticaBoldOblique, true)),
314 "Courier" => return Some((StandardFont::Courier, true)),
315 "Courier-Bold" => return Some((StandardFont::CourierBold, true)),
316 "Courier-Oblique" => return Some((StandardFont::CourierOblique, true)),
317 "Courier-BoldOblique" => return Some((StandardFont::CourierBoldOblique, true)),
318 "Times-Roman" => return Some((StandardFont::TimesRoman, true)),
319 "Times-Bold" => return Some((StandardFont::TimesBold, true)),
320 "Times-Italic" => return Some((StandardFont::TimesItalic, true)),
321 "Times-BoldItalic" => return Some((StandardFont::TimesBoldItalic, true)),
322 "Symbol" => return Some((StandardFont::Symbol, true)),
323 "ZapfDingbats" => return Some((StandardFont::ZapfDingBats, true)),
324 _ => {}
325 }
326
327 if let Some(alias) = standard_font_alias(name) {
332 return Some((alias, false));
333 }
334
335 let lower = name.to_ascii_lowercase();
338
339 let family_field = descriptor
344 .get::<Name>(FONT_FAMILY)
345 .map(|n| n.as_str().to_ascii_lowercase())
346 .unwrap_or_default();
347
348 let is_bold = descriptor.get::<u32>(FONT_WEIGHT).is_some_and(|w| w >= 600)
354 || lower.contains("bold")
355 || lower.contains("demi")
356 || family_field.contains("bold")
357 || family_field.contains("demi");
358 let is_italic = descriptor
365 .get::<f32>(ITALIC_ANGLE)
366 .is_some_and(|a| !(-5.0..=5.0).contains(&a))
367 || lower.contains("italic")
368 || lower.contains("oblique")
369 || family_field.contains("italic")
370 || family_field.contains("oblique");
371
372 let haystack = if family_field.is_empty() {
374 lower.clone()
375 } else {
376 format!("{lower} {family_field}")
377 };
378
379 let (family, exact) = if haystack.contains("helvetica") {
398 (Some(StandardFontFamily::Helvetica), true) } else if haystack.contains("arial") || haystack.contains("sans") {
400 (Some(StandardFontFamily::Helvetica), false) } else if haystack.contains("courier") {
402 (Some(StandardFontFamily::Courier), true) } else if haystack.contains("mono") {
404 (Some(StandardFontFamily::Courier), false) } else if haystack.contains("times") {
406 (Some(StandardFontFamily::Times), true) } else if haystack.contains("serif") {
408 (Some(StandardFontFamily::Times), false) } else if haystack.contains("zapfdingbats") || haystack.contains("dingbats") {
410 return Some((StandardFont::ZapfDingBats, false));
411 } else {
412 (None, false)
413 };
414
415 let font = match (family?, is_bold, is_italic) {
416 (StandardFontFamily::Helvetica, false, false) => StandardFont::Helvetica,
417 (StandardFontFamily::Helvetica, true, false) => StandardFont::HelveticaBold,
418 (StandardFontFamily::Helvetica, false, true) => StandardFont::HelveticaOblique,
419 (StandardFontFamily::Helvetica, true, true) => StandardFont::HelveticaBoldOblique,
420 (StandardFontFamily::Courier, false, false) => StandardFont::Courier,
421 (StandardFontFamily::Courier, true, false) => StandardFont::CourierBold,
422 (StandardFontFamily::Courier, false, true) => StandardFont::CourierOblique,
423 (StandardFontFamily::Courier, true, true) => StandardFont::CourierBoldOblique,
424 (StandardFontFamily::Times, false, false) => StandardFont::TimesRoman,
425 (StandardFontFamily::Times, true, false) => StandardFont::TimesBold,
426 (StandardFontFamily::Times, false, true) => StandardFont::TimesItalic,
427 (StandardFontFamily::Times, true, true) => StandardFont::TimesBoldItalic,
428 };
429
430 Some((font, exact))
431}
432
433#[derive(Debug, Clone)]
434pub(crate) enum StandardFontBlob {
435 Cff(CffFontBlob),
436 Otf(OpenTypeFontBlob, HashMap<String, GlyphId>),
437}
438
439impl StandardFontBlob {
440 pub(crate) fn from_data(data: FontData, index: u32) -> Option<Self> {
441 if let Some(blob) = CffFontBlob::new(data.clone()) {
442 Some(Self::new_cff(blob))
443 } else {
444 OpenTypeFontBlob::new(data, index).map(Self::new_otf)
445 }
446 }
447
448 pub(crate) fn new_cff(blob: CffFontBlob) -> Self {
449 Self::Cff(blob)
450 }
451
452 pub(crate) fn new_otf(blob: OpenTypeFontBlob) -> Self {
453 let mut glyph_names = HashMap::new();
454
455 if let Ok(post) = blob.font_ref().post() {
456 for i in 0..blob.num_glyphs() {
457 if let Some(str) = post.glyph_name(GlyphId16::new(i)) {
458 glyph_names.insert(str.to_string(), GlyphId::new(i as u32));
459 }
460 }
461 }
462
463 Self::Otf(blob, glyph_names)
464 }
465}
466
467impl StandardFontBlob {
468 pub(crate) fn name_to_glyph(&self, name: &str) -> Option<GlyphId> {
469 match self {
470 Self::Cff(blob) => blob
471 .table()
472 .glyph_index_by_name(name)
473 .map(|g| GlyphId::new(g.0 as u32)),
474 Self::Otf(_, glyph_names) => glyph_names.get(name).copied(),
475 }
476 }
477
478 pub(crate) fn unicode_to_glyph(&self, code: u32) -> Option<GlyphId> {
479 match self {
480 Self::Cff(_) => None,
481 Self::Otf(blob, _) => blob
482 .font_ref()
483 .cmap()
484 .ok()
485 .and_then(|c| c.map_codepoint(code)),
486 }
487 }
488
489 pub(crate) fn advance_width(&self, glyph: GlyphId) -> Option<f32> {
490 match self {
491 Self::Cff(_) => None,
492 Self::Otf(blob, _) => blob.glyph_metrics().advance_width(glyph),
493 }
494 }
495
496 pub(crate) fn outline_glyph(&self, glyph: GlyphId) -> BezPath {
497 if glyph == GlyphId::NOTDEF {
500 return BezPath::new();
501 }
502
503 match self {
504 Self::Cff(blob) => blob.outline_glyph(glyph),
505 Self::Otf(blob, _) => blob.outline_glyph(glyph),
506 }
507 }
508}
509
510#[derive(Debug)]
511pub(crate) struct StandardKind {
512 base_font: StandardFont,
513 base_font_blob: StandardFontBlob,
514 encoding: Encoding,
515 widths: Vec<Width>,
516 missing_width: Option<f32>,
517 fallback: bool,
518 glyph_to_code: RefCell<HashMap<GlyphId, u8>>,
519 encodings: HashMap<u8, String>,
520 named: Option<String>,
522}
523
524impl StandardKind {
525 pub(crate) fn new(dict: &Dict<'_>, resolver: &FontResolverFn, cache: &Cache) -> Option<Self> {
526 let descriptor = dict.get::<Dict<'_>>(FONT_DESC).unwrap_or_default();
527 let (font, exact) = select_standard_font(dict, &descriptor)?;
528 Self::new_with_standard(dict, font, !exact, resolver, cache)
529 }
530
531 pub(crate) fn new_with_standard(
532 dict: &Dict<'_>,
533 base_font: StandardFont,
534 fallback: bool,
535 resolver: &FontResolverFn,
536 cache: &Cache,
537 ) -> Option<Self> {
538 let descriptor = dict.get::<Dict<'_>>(FONT_DESC).unwrap_or_default();
539 let (widths, missing_width) = read_widths(dict, &descriptor)?;
540 let missing_width = descriptor
541 .contains_key(MISSING_WIDTH)
542 .then_some(missing_width);
543
544 let (mut encoding, encoding_map) = read_encoding(dict);
545
546 if matches!(base_font, StandardFont::Symbol | StandardFont::ZapfDingBats) {
548 encoding = Encoding::BuiltIn;
549 }
550
551 let (blob, index) = resolver(&FontQuery::Standard(base_font))?;
552 let key = Cache::font_data_key(b"standard", index, blob.as_ref().as_ref());
555 let base_font_blob =
556 cache.get_or_insert_some(key, || StandardFontBlob::from_data(blob.clone(), index))?;
557
558 let named = dict
559 .get::<Name>(BASE_FONT)
560 .map(|name| name.as_str().to_string());
561
562 Some(Self {
563 base_font,
564 base_font_blob,
565 widths,
566 missing_width,
567 encodings: encoding_map,
568 glyph_to_code: RefCell::new(HashMap::new()),
569 fallback,
570 encoding,
571 named,
572 })
573 }
574
575 fn code_to_ps_name(&self, code: u8) -> Option<&str> {
576 let bf = self.base_font;
577
578 self.encodings
579 .get(&code)
580 .map(String::as_str)
581 .or_else(|| match self.encoding {
582 Encoding::BuiltIn => bf.code_to_name(code),
583 _ => self.encoding.map_code(code),
584 })
585 }
586
587 pub(crate) fn map_code(&self, code: u8) -> GlyphId {
588 let result = self
589 .code_to_ps_name(code)
590 .and_then(|c| {
591 self.base_font_blob.name_to_glyph(c).or_else(|| {
592 glyph_names::get(c).and_then(|c| {
594 self.base_font_blob
595 .unicode_to_glyph(c.chars().nth(0).unwrap() as u32)
596 })
597 })
598 })
599 .unwrap_or(GlyphId::NOTDEF);
600 self.glyph_to_code.borrow_mut().insert(result, code);
601
602 result
603 }
604
605 pub(crate) fn outline_glyph(&self, glyph: GlyphId) -> BezPath {
606 let path = self.base_font_blob.outline_glyph(glyph);
607
608 if let Some(code) = self.glyph_to_code.borrow().get(&glyph).copied()
612 && let Some(actual_width) = self.base_font_blob.advance_width(glyph).or_else(|| {
613 self.code_to_ps_name(code)
614 .and_then(|name| self.base_font.get_width(name))
615 })
616 {
617 let should_width = if self.fallback {
623 if let Some(Width::Value(w)) = self.widths.get(code as usize).copied() {
624 w
625 } else {
626 return path;
627 }
628 } else if let Some(w) = self
629 .code_to_ps_name(code)
630 .and_then(|name| self.base_font.get_width(name))
631 {
632 w
633 } else {
634 return path;
635 };
636
637 return stretch_glyph(path, should_width, actual_width);
638 }
639
640 path
641 }
642
643 pub(crate) fn glyph_width(&self, code: u8) -> Option<f32> {
644 match self.widths.get(code as usize).copied() {
645 Some(Width::Value(w)) => Some(w),
646 Some(Width::Missing) => self.missing_width.or_else(|| {
647 self.code_to_ps_name(code)
648 .and_then(|c| self.base_font.get_width(c))
649 }),
650 _ => self
651 .code_to_ps_name(code)
652 .and_then(|c| self.base_font.get_width(c)),
653 }
654 }
655
656 pub(crate) fn char_code_to_unicode(&self, code: u8) -> Option<BfString> {
657 self.code_to_ps_name(code).and_then(glyph_name_to_bfstring)
658 }
659
660 pub(crate) fn is_italic(&self) -> bool {
661 self.base_font.is_italic()
662 }
663
664 pub(crate) fn is_bold(&self) -> bool {
665 self.base_font.is_bold()
666 }
667
668 pub(crate) fn is_serif(&self) -> bool {
669 self.base_font.is_serif()
670 }
671
672 pub(crate) fn is_monospace(&self) -> bool {
673 self.base_font.is_monospace()
674 }
675
676 pub(crate) fn postscript_name(&self) -> &'static str {
683 self.base_font.postscript_name()
684 }
685
686 pub(crate) fn declared_name(&self) -> Option<&str> {
689 self.named.as_deref()
690 }
691
692 pub(crate) fn font_metrics(&self) -> Option<(f64, f64, Option<f64>, Option<f64>)> {
694 self.base_font.canonical_metrics()
695 }
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 fn build_widths(entries: &[(u8, f32)]) -> Vec<Width> {
703 let mut widths = vec![Width::Missing; 256];
704 for (code, width) in entries {
705 widths[*code as usize] = Width::Value(*width);
706 }
707 widths
708 }
709
710 fn build_standard_kind(widths: Vec<Width>, missing_width: Option<f32>) -> StandardKind {
711 let (data, index) = StandardFont::Helvetica.get_font_data();
712 let base_font_blob =
713 StandardFontBlob::from_data(data, index).expect("standard font data should parse");
714
715 StandardKind {
716 named: None,
717 base_font: StandardFont::Helvetica,
718 base_font_blob,
719 encoding: Encoding::WinAnsi,
720 widths,
721 missing_width,
722 fallback: true,
723 glyph_to_code: RefCell::new(HashMap::new()),
724 encodings: HashMap::new(),
725 }
726 }
727
728 #[test]
729 fn glyph_width_falls_back_to_base_metrics_when_missing_width_is_absent() {
730 let font = build_standard_kind(build_widths(&[(b'A', 600.0)]), None);
731
732 assert_eq!(font.glyph_width(b'A'), Some(600.0));
733 assert_eq!(
734 font.glyph_width(b'B'),
735 StandardFont::Helvetica.get_width("B")
736 );
737 }
738
739 #[test]
740 fn glyph_width_respects_explicit_zero_missing_width() {
741 let font = build_standard_kind(build_widths(&[(b'A', 600.0)]), Some(0.0));
742
743 assert_eq!(font.glyph_width(b'A'), Some(600.0));
744 assert_eq!(font.glyph_width(b'B'), Some(0.0));
745 }
746
747 #[test]
748 fn arial_aliases_resolve_to_helvetica_family() {
749 assert!(matches!(
750 standard_font_alias("ArialMT"),
751 Some(StandardFont::Helvetica)
752 ));
753 assert!(matches!(
754 standard_font_alias("Arial-BoldMT"),
755 Some(StandardFont::HelveticaBold)
756 ));
757 assert!(matches!(
758 standard_font_alias("Arial-ItalicMT"),
759 Some(StandardFont::HelveticaOblique)
760 ));
761 assert!(matches!(
762 standard_font_alias("Arial-BoldItalicMT"),
763 Some(StandardFont::HelveticaBoldOblique)
764 ));
765 }
766
767 #[test]
768 fn times_new_roman_aliases_resolve_to_times_family() {
769 assert!(matches!(
770 standard_font_alias("TimesNewRomanPSMT"),
771 Some(StandardFont::TimesRoman)
772 ));
773 assert!(matches!(
774 standard_font_alias("TimesNewRomanPS-BoldMT"),
775 Some(StandardFont::TimesBold)
776 ));
777 assert!(matches!(
778 standard_font_alias("TimesNewRomanPS-ItalicMT"),
779 Some(StandardFont::TimesItalic)
780 ));
781 assert!(matches!(
782 standard_font_alias("TimesNewRomanPS-BoldItalicMT"),
783 Some(StandardFont::TimesBoldItalic)
784 ));
785 }
786
787 #[test]
788 fn courier_new_aliases_resolve_to_courier_family() {
789 assert!(matches!(
790 standard_font_alias("CourierNewPSMT"),
791 Some(StandardFont::Courier)
792 ));
793 assert!(matches!(
794 standard_font_alias("CourierNewPS-BoldMT"),
795 Some(StandardFont::CourierBold)
796 ));
797 assert!(matches!(
798 standard_font_alias("CourierNewPS-ItalicMT"),
799 Some(StandardFont::CourierOblique)
800 ));
801 assert!(matches!(
802 standard_font_alias("CourierNewPS-BoldItalicMT"),
803 Some(StandardFont::CourierBoldOblique)
804 ));
805 }
806
807 #[test]
808 fn unknown_names_do_not_alias() {
809 assert!(standard_font_alias("LiberationSans").is_none());
810 assert!(standard_font_alias("CenturySchoolbook").is_none());
811 assert!(standard_font_alias("").is_none());
812 }
813
814 #[test]
817 fn helvetica_metrics() {
818 let m = StandardFont::Helvetica.canonical_metrics().unwrap();
819 assert_eq!(m.0, 718.0);
820 assert_eq!(m.1, -207.0);
821 assert_eq!(m.2, Some(718.0));
822 assert_eq!(m.3, Some(523.0));
823 }
824
825 #[test]
826 fn helvetica_bold_metrics() {
827 let m = StandardFont::HelveticaBold.canonical_metrics().unwrap();
828 assert_eq!(m.0, 718.0);
829 assert_eq!(m.1, -207.0);
830 assert_eq!(m.2, Some(718.0));
831 assert_eq!(m.3, Some(532.0));
832 }
833
834 #[test]
835 fn helvetica_oblique_metrics() {
836 let m = StandardFont::HelveticaOblique.canonical_metrics().unwrap();
837 assert_eq!(m.0, 718.0);
838 assert_eq!(m.1, -207.0);
839 assert_eq!(m.2, Some(718.0));
840 assert_eq!(m.3, Some(523.0));
841 }
842
843 #[test]
844 fn helvetica_bold_oblique_metrics() {
845 let m = StandardFont::HelveticaBoldOblique
846 .canonical_metrics()
847 .unwrap();
848 assert_eq!(m.0, 718.0);
849 assert_eq!(m.1, -207.0);
850 assert_eq!(m.2, Some(718.0));
851 assert_eq!(m.3, Some(532.0));
852 }
853
854 #[test]
855 fn courier_metrics() {
856 let m = StandardFont::Courier.canonical_metrics().unwrap();
857 assert_eq!(m.0, 629.0);
858 assert_eq!(m.1, -157.0);
859 assert_eq!(m.2, Some(562.0));
860 assert_eq!(m.3, Some(426.0));
861 }
862
863 #[test]
864 fn courier_bold_metrics() {
865 let m = StandardFont::CourierBold.canonical_metrics().unwrap();
866 assert_eq!(m.0, 629.0);
867 assert_eq!(m.1, -157.0);
868 assert_eq!(m.2, Some(562.0));
869 assert_eq!(m.3, Some(439.0));
870 }
871
872 #[test]
873 fn courier_oblique_metrics() {
874 let m = StandardFont::CourierOblique.canonical_metrics().unwrap();
875 assert_eq!(m.0, 629.0);
876 assert_eq!(m.1, -157.0);
877 assert_eq!(m.2, Some(562.0));
878 assert_eq!(m.3, Some(426.0));
879 }
880
881 #[test]
882 fn courier_bold_oblique_metrics() {
883 let m = StandardFont::CourierBoldOblique
884 .canonical_metrics()
885 .unwrap();
886 assert_eq!(m.0, 629.0);
887 assert_eq!(m.1, -157.0);
888 assert_eq!(m.2, Some(562.0));
889 assert_eq!(m.3, Some(439.0));
890 }
891
892 #[test]
893 fn times_roman_metrics() {
894 let m = StandardFont::TimesRoman.canonical_metrics().unwrap();
895 assert_eq!(m.0, 683.0);
896 assert_eq!(m.1, -217.0);
897 assert_eq!(m.2, Some(662.0));
898 assert_eq!(m.3, Some(450.0));
899 }
900
901 #[test]
902 fn times_bold_metrics() {
903 let m = StandardFont::TimesBold.canonical_metrics().unwrap();
904 assert_eq!(m.0, 683.0);
905 assert_eq!(m.1, -217.0);
906 assert_eq!(m.2, Some(676.0));
907 assert_eq!(m.3, Some(461.0));
908 }
909
910 #[test]
911 fn times_italic_metrics() {
912 let m = StandardFont::TimesItalic.canonical_metrics().unwrap();
913 assert_eq!(m.0, 683.0);
914 assert_eq!(m.1, -217.0);
915 assert_eq!(m.2, Some(653.0));
916 assert_eq!(m.3, Some(441.0));
917 }
918
919 #[test]
920 fn times_bold_italic_metrics() {
921 let m = StandardFont::TimesBoldItalic.canonical_metrics().unwrap();
922 assert_eq!(m.0, 683.0);
923 assert_eq!(m.1, -217.0);
924 assert_eq!(m.2, Some(669.0));
925 assert_eq!(m.3, Some(462.0));
926 }
927
928 #[test]
929 fn symbol_metrics_no_letterform_values() {
930 let m = StandardFont::Symbol.canonical_metrics().unwrap();
931 assert_eq!(m.0, 1010.0);
932 assert_eq!(m.1, -293.0);
933 assert!(m.2.is_none(), "Symbol has no cap_height");
934 assert!(m.3.is_none(), "Symbol has no x_height");
935 }
936
937 #[test]
938 fn zapf_dingbats_metrics_no_letterform_values() {
939 let m = StandardFont::ZapfDingBats.canonical_metrics().unwrap();
940 assert_eq!(m.0, 820.0);
941 assert_eq!(m.1, -143.0);
942 assert!(m.2.is_none(), "ZapfDingbats has no cap_height");
943 assert!(m.3.is_none(), "ZapfDingbats has no x_height");
944 }
945
946 #[test]
947 fn all_14_fonts_return_some() {
948 let all = &[
949 StandardFont::Helvetica,
950 StandardFont::HelveticaBold,
951 StandardFont::HelveticaOblique,
952 StandardFont::HelveticaBoldOblique,
953 StandardFont::Courier,
954 StandardFont::CourierBold,
955 StandardFont::CourierOblique,
956 StandardFont::CourierBoldOblique,
957 StandardFont::TimesRoman,
958 StandardFont::TimesBold,
959 StandardFont::TimesItalic,
960 StandardFont::TimesBoldItalic,
961 StandardFont::Symbol,
962 StandardFont::ZapfDingBats,
963 ];
964 for font in all {
965 assert!(
966 font.canonical_metrics().is_some(),
967 "font {:?} returned None",
968 font
969 );
970 }
971 }
972
973 #[test]
974 fn ascent_always_positive_descent_always_negative() {
975 let all = &[
976 StandardFont::Helvetica,
977 StandardFont::HelveticaBold,
978 StandardFont::HelveticaOblique,
979 StandardFont::HelveticaBoldOblique,
980 StandardFont::Courier,
981 StandardFont::CourierBold,
982 StandardFont::CourierOblique,
983 StandardFont::CourierBoldOblique,
984 StandardFont::TimesRoman,
985 StandardFont::TimesBold,
986 StandardFont::TimesItalic,
987 StandardFont::TimesBoldItalic,
988 StandardFont::Symbol,
989 StandardFont::ZapfDingBats,
990 ];
991 for font in all {
992 let (ascent, descent, _, _) = font.canonical_metrics().unwrap();
993 assert!(
994 ascent > 0.0,
995 "font {:?} ascent {} not positive",
996 font,
997 ascent
998 );
999 assert!(
1000 descent < 0.0,
1001 "font {:?} descent {} not negative",
1002 font,
1003 descent
1004 );
1005 }
1006 }
1007}