hayro_interpret/font/
standard_font.rs

1use crate::font::FontData;
2use crate::font::blob::{CffFontBlob, OpenTypeFontBlob};
3use crate::font::generated::{metrics, standard, symbol, zapf_dings};
4use hayro_syntax::object::Dict;
5use hayro_syntax::object::Name;
6use hayro_syntax::object::dict::keys::BASE_FONT;
7use kurbo::BezPath;
8use skrifa::GlyphId16;
9use skrifa::raw::TableProvider;
10use std::collections::HashMap;
11use std::ops::Deref;
12
13/// The 14 standard fonts of PDF.
14#[derive(Copy, Clone, Debug)]
15pub enum StandardFont {
16    /// Helvetica.
17    Helvetica,
18    /// Helvetica Bold.
19    HelveticaBold,
20    /// Helvetica Oblique.
21    HelveticaOblique,
22    /// Helvetica Bold Oblique.
23    HelveticaBoldOblique,
24    /// Courier.
25    Courier,
26    /// Courier Bold.
27    CourierBold,
28    /// Courier Oblique.
29    CourierOblique,
30    /// Courier Bold Oblique.
31    CourierBoldOblique,
32    /// Times Roman.
33    TimesRoman,
34    /// Times Bold.
35    TimesBold,
36    /// Times Italic.
37    TimesItalic,
38    /// Times Bold Italic.
39    TimesBoldItalic,
40    /// Zapf Dingbats - a decorative symbol font.
41    ZapfDingBats,
42    /// Symbol - a mathematical symbol font.
43    Symbol,
44}
45
46impl StandardFont {
47    pub(crate) fn code_to_name(&self, code: u8) -> Option<&'static str> {
48        match self {
49            Self::Symbol => symbol::get(code),
50            // Note that this font does not return postscript character names,
51            // but instead has a custom encoding.
52            Self::ZapfDingBats => zapf_dings::get(code),
53            _ => standard::get(code),
54        }
55    }
56
57    pub(crate) fn get_width(&self, mut name: &str) -> Option<f32> {
58        // <https://github.com/apache/pdfbox/blob/129aafe26548c1ff935af9c55cb40a996186c35f/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java#L340>
59        if name == ".notdef" {
60            return Some(250.0);
61        }
62
63        if name == "nbspace" {
64            name = "space";
65        }
66
67        if name == "sfthyphen" {
68            name = "hyphen"
69        }
70
71        match self {
72            StandardFont::Helvetica => metrics::HELVETICA.get(name).copied(),
73            StandardFont::HelveticaBold => metrics::HELVETICA_BOLD.get(name).copied(),
74            StandardFont::HelveticaOblique => metrics::HELVETICA_OBLIQUE.get(name).copied(),
75            StandardFont::HelveticaBoldOblique => {
76                metrics::HELVETICA_BOLD_OBLIQUE.get(name).copied()
77            }
78            StandardFont::Courier => metrics::COURIER.get(name).copied(),
79            StandardFont::CourierBold => metrics::COURIER_BOLD.get(name).copied(),
80            StandardFont::CourierOblique => metrics::COURIER_OBLIQUE.get(name).copied(),
81            StandardFont::CourierBoldOblique => metrics::COURIER_BOLD_OBLIQUE.get(name).copied(),
82            StandardFont::TimesRoman => metrics::TIMES_ROMAN.get(name).copied(),
83            StandardFont::TimesBold => metrics::TIMES_BOLD.get(name).copied(),
84            StandardFont::TimesItalic => metrics::TIMES_ITALIC.get(name).copied(),
85            StandardFont::TimesBoldItalic => metrics::TIMES_BOLD_ITALIC.get(name).copied(),
86            StandardFont::ZapfDingBats => metrics::ZAPF_DING_BATS.get(name).copied(),
87            StandardFont::Symbol => metrics::SYMBOL.get(name).copied(),
88        }
89    }
90
91    pub(crate) fn as_str(&self) -> &'static str {
92        match self {
93            StandardFont::Helvetica => "Helvetica",
94            StandardFont::HelveticaBold => "Helvetica Bold",
95            StandardFont::HelveticaOblique => "Helvetica Oblique",
96            StandardFont::HelveticaBoldOblique => "Helvetica Bold Oblique",
97            StandardFont::Courier => "Courier",
98            StandardFont::CourierBold => "Courier Bold",
99            StandardFont::CourierOblique => "Courier Oblique",
100            StandardFont::CourierBoldOblique => "Courier Bold Oblique",
101            StandardFont::TimesRoman => "Times Roman",
102            StandardFont::TimesBold => "Times Bold",
103            StandardFont::TimesItalic => "Times Italic",
104            StandardFont::TimesBoldItalic => "Times Bold Italic",
105            StandardFont::ZapfDingBats => "Zapf Dingbats",
106            StandardFont::Symbol => "Symbol",
107        }
108    }
109
110    /// Return the postscrit name of the font.
111    pub fn postscript_name(&self) -> &'static str {
112        match self {
113            StandardFont::Helvetica => "Helvetica",
114            StandardFont::HelveticaBold => "Helvetica-Bold",
115            StandardFont::HelveticaOblique => "Helvetica-Oblique",
116            StandardFont::HelveticaBoldOblique => "Helvetica-BoldOblique",
117            StandardFont::Courier => "Courier",
118            StandardFont::CourierBold => "Courier-Bold",
119            StandardFont::CourierOblique => "Courier-Oblique",
120            StandardFont::CourierBoldOblique => "Courier-BoldOblique",
121            StandardFont::TimesRoman => "Times-Roman",
122            StandardFont::TimesBold => "Times-Bold",
123            StandardFont::TimesItalic => "Times-Italic",
124            StandardFont::TimesBoldItalic => "Times-BoldItalic",
125            StandardFont::ZapfDingBats => "ZapfDingbats",
126            StandardFont::Symbol => "Symbol",
127        }
128    }
129
130    /// Return suitable font data for the given standard font.
131    ///
132    /// Currently, this will return the corresponding Foxit font, which is a set of permissibly
133    /// licensed fonts that is also very light-weight.
134    ///
135    /// You can use the result of this method in your implementation of [`FontResolverFn`].
136    ///
137    /// [`FontResolverFn`]: crate::FontResolverFn
138    #[cfg(feature = "embed-fonts")]
139    pub fn get_font_data(&self) -> (FontData, u32) {
140        use std::sync::Arc;
141
142        let data = match self {
143            StandardFont::Helvetica => &include_bytes!("../../assets/FoxitSans.pfb")[..],
144            StandardFont::HelveticaBold => &include_bytes!("../../assets/FoxitSansBold.pfb")[..],
145            StandardFont::HelveticaOblique => {
146                &include_bytes!("../../assets/FoxitSansItalic.pfb")[..]
147            }
148            StandardFont::HelveticaBoldOblique => {
149                &include_bytes!("../../assets/FoxitSansBoldItalic.pfb")[..]
150            }
151            StandardFont::Courier => &include_bytes!("../../assets/FoxitFixed.pfb")[..],
152            StandardFont::CourierBold => &include_bytes!("../../assets/FoxitFixedBold.pfb")[..],
153            StandardFont::CourierOblique => {
154                &include_bytes!("../../assets/FoxitFixedItalic.pfb")[..]
155            }
156            StandardFont::CourierBoldOblique => {
157                &include_bytes!("../../assets/FoxitFixedBoldItalic.pfb")[..]
158            }
159            StandardFont::TimesRoman => &include_bytes!("../../assets/FoxitSerif.pfb")[..],
160            StandardFont::TimesBold => &include_bytes!("../../assets/FoxitSerifBold.pfb")[..],
161            StandardFont::TimesItalic => &include_bytes!("../../assets/FoxitSerifItalic.pfb")[..],
162            StandardFont::TimesBoldItalic => {
163                &include_bytes!("../../assets/FoxitSerifBoldItalic.pfb")[..]
164            }
165            StandardFont::ZapfDingBats => &include_bytes!("../../assets/FoxitDingbats.pfb")[..],
166            StandardFont::Symbol => {
167                include_bytes!("../../assets/FoxitSymbol.pfb")
168            }
169        };
170
171        (Arc::new(data), 0)
172    }
173}
174
175pub(crate) fn select_standard_font(dict: &Dict) -> Option<StandardFont> {
176    // See <https://github.com/apache/pdfbox/blob/4438b8fdc67a3a9ebfb194595d0e81f88b708a37/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/FontMapperImpl.java#L62-L102>
177    match dict.get::<Name>(BASE_FONT)?.deref() {
178        b"Helvetica" | b"ArialMT" | b"Arial" | b"LiberationSans" | b"NimbusSanL-Regu" => {
179            Some(StandardFont::Helvetica)
180        }
181        b"Helvetica-Bold"
182        | b"Arial-BoldMT"
183        | b"Arial-Bold"
184        | b"Arial,Bold"
185        | b"LiberationSans-Bold"
186        | b"NimbusSanL-Bold" => Some(StandardFont::HelveticaBold),
187        b"Helvetica-Oblique"
188        | b"Arial-ItalicMT"
189        | b"Arial-Italic"
190        | b"Helvetica-Italic"
191        | b"LiberationSans-Italic"
192        | b"NimbusSanL-ReguItal" => Some(StandardFont::HelveticaOblique),
193        b"Helvetica-BoldOblique"
194        | b"Arial-BoldItalicMT"
195        | b"Helvetica-BoldItalic"
196        | b"LiberationSans-BoldItalic"
197        | b"NimbusSanL-BoldItal" => Some(StandardFont::HelveticaBoldOblique),
198        b"Courier" | b"CourierNew" | b"CourierNewPSMT" | b"LiberationMono" | b"NimbusMonL-Regu" => {
199            Some(StandardFont::Courier)
200        }
201        b"Courier-Bold"
202        | b"CourierNewPS-BoldMT"
203        | b"CourierNew-Bold"
204        | b"LiberationMono-Bold"
205        | b"NimbusMonL-Bold" => Some(StandardFont::CourierBold),
206        b"Courier-Oblique"
207        | b"CourierNewPS-ItalicMT"
208        | b"CourierNew-Italic"
209        | b"LiberationMono-Italic"
210        | b"NimbusMonL-ReguObli" => Some(StandardFont::CourierOblique),
211        b"Courier-BoldOblique"
212        | b"CourierNewPS-BoldItalicMT"
213        | b"CourierNew-BoldItalic"
214        | b"LiberationMono-BoldItalic"
215        | b"NimbusMonL-BoldObli" => Some(StandardFont::CourierBoldOblique),
216        b"Times-Roman"
217        | b"Times New Roman"
218        | b"TimesNewRomanPSMT"
219        | b"TimesNewRoman"
220        | b"TimesNewRomanPS"
221        | b"LiberationSerif"
222        | b"NimbusRomNo9L-Regu" => Some(StandardFont::TimesRoman),
223        b"Times-Bold"
224        | b"TimesNewRomanPS-BoldMT"
225        | b"TimesNewRomanPS-Bold"
226        | b"TimesNewRoman-Bold"
227        | b"LiberationSerif-Bold"
228        | b"NimbusRomNo9L-Medi" => Some(StandardFont::TimesBold),
229        b"Times-Italic"
230        | b"TimesNewRomanPS-ItalicMT"
231        | b"TimesNewRomanPS-Italic"
232        | b"TimesNewRoman-Italic"
233        | b"LiberationSerif-Italic"
234        | b"NimbusRomNo9L-ReguItal" => Some(StandardFont::TimesItalic),
235        b"Times-BoldItalic"
236        | b"TimesNewRomanPS-BoldItalicMT"
237        | b"TimesNewRomanPS-BoldItalic"
238        | b"TimesNewRoman-BoldItalic"
239        | b"LiberationSerif-BoldItalic"
240        | b"NimbusRomNo9L-MediItal" => Some(StandardFont::TimesBoldItalic),
241        b"Symbol" | b"SymbolMT" | b"StandardSymL" => Some(StandardFont::Symbol),
242        b"ZapfDingbats"
243        | b"ZapfDingbatsITCbyBT-Regular"
244        | b"ZapfDingbatsITC"
245        | b"Dingbats"
246        | b"MS-Gothic" => Some(StandardFont::ZapfDingBats),
247        _ => None,
248    }
249}
250
251#[derive(Debug)]
252pub(crate) enum StandardFontBlob {
253    Cff(CffFontBlob),
254    Otf(OpenTypeFontBlob, HashMap<String, skrifa::GlyphId>),
255}
256
257impl StandardFontBlob {
258    pub(crate) fn from_data(data: FontData, index: u32) -> Option<Self> {
259        if let Some(blob) = CffFontBlob::new(data.clone()) {
260            Some(Self::new_cff(blob))
261        } else {
262            OpenTypeFontBlob::new(data, index).map(Self::new_otf)
263        }
264    }
265
266    pub(crate) fn new_cff(blob: CffFontBlob) -> Self {
267        Self::Cff(blob)
268    }
269
270    pub(crate) fn new_otf(blob: OpenTypeFontBlob) -> Self {
271        let mut glyph_names = HashMap::new();
272
273        if let Ok(post) = blob.font_ref().post() {
274            for i in 0..blob.num_glyphs() {
275                if let Some(str) = post.glyph_name(GlyphId16::new(i)) {
276                    glyph_names.insert(str.to_string(), skrifa::GlyphId::new(i as u32));
277                }
278            }
279        }
280
281        Self::Otf(blob, glyph_names)
282    }
283}
284
285impl StandardFontBlob {
286    pub(crate) fn name_to_glyph(&self, name: &str) -> Option<skrifa::GlyphId> {
287        match self {
288            Self::Cff(blob) => blob
289                .table()
290                .glyph_index_by_name(name)
291                .map(|g| skrifa::GlyphId::new(g.0 as u32)),
292            Self::Otf(_, glyph_names) => glyph_names.get(name).copied(),
293        }
294    }
295
296    pub(crate) fn unicode_to_glyph(&self, code: u32) -> Option<skrifa::GlyphId> {
297        match self {
298            Self::Cff(_) => None,
299            Self::Otf(blob, _) => blob
300                .font_ref()
301                .cmap()
302                .ok()
303                .and_then(|c| c.map_codepoint(code)),
304        }
305    }
306
307    pub(crate) fn outline_glyph(&self, glyph: skrifa::GlyphId) -> BezPath {
308        // Standard fonts have empty outlines for these, but in Liberation Sans
309        // they are a .notdef rectangle.
310        if glyph == skrifa::GlyphId::NOTDEF {
311            return BezPath::new();
312        }
313
314        match self {
315            StandardFontBlob::Cff(blob) => blob.outline_glyph(glyph),
316            StandardFontBlob::Otf(blob, _) => blob.outline_glyph(glyph),
317        }
318    }
319}