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