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