hayro_interpret/font/
mod.rs

1//! Interacting with the different kinds of PDF fonts.
2
3use crate::cache::Cache;
4use crate::context::Context;
5use crate::device::Device;
6use crate::font::cid::Type0Font;
7use crate::font::generated::{mac_expert, mac_os_roman, mac_roman, standard, win_ansi};
8use crate::font::true_type::TrueTypeFont;
9use crate::font::type1::Type1Font;
10use crate::font::type3::Type3;
11use crate::interpret::state::State;
12use crate::{FontResolverFn, InterpreterSettings, Paint, WarningSinkFn};
13use bitflags::bitflags;
14use hayro_syntax::object::Dict;
15use hayro_syntax::object::Name;
16use hayro_syntax::object::dict::keys::SUBTYPE;
17use hayro_syntax::object::dict::keys::*;
18use hayro_syntax::page::Resources;
19use hayro_syntax::xref::XRef;
20use kurbo::{Affine, BezPath, Vec2};
21use log::warn;
22use outline::OutlineFont;
23use skrifa::GlyphId;
24use std::fmt::Debug;
25use std::ops::Deref;
26use std::rc::Rc;
27use std::sync::Arc;
28
29mod blob;
30mod cid;
31mod generated;
32mod glyph_simulator;
33pub(crate) mod outline;
34mod standard_font;
35mod true_type;
36mod type1;
37pub(crate) mod type3;
38
39pub(crate) const UNITS_PER_EM: f32 = 1000.0;
40
41/// A container for the bytes of a PDF file.
42pub type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
43
44pub use standard_font::StandardFont;
45
46/// A glyph that can be drawn.
47pub enum Glyph<'a> {
48    /// A glyph defined by an outline.
49    Outline(OutlineGlyph),
50    /// A type3 glyph, defined by PDF drawing instructions.
51    Type3(Box<Type3Glyph<'a>>),
52}
53
54impl Glyph<'_> {
55    pub(crate) fn glyph_transform(&self) -> Affine {
56        match self {
57            Glyph::Outline(o) => o.glyph_transform,
58            Glyph::Type3(s) => s.glyph_transform,
59        }
60    }
61}
62
63/// A glyph defined by an outline.
64#[derive(Clone, Debug)]
65pub struct OutlineGlyph {
66    pub(crate) id: GlyphId,
67    pub(crate) font: OutlineFont,
68    /// A transform that should be applied to the glyph before drawing.
69    pub glyph_transform: Affine,
70}
71
72impl OutlineGlyph {
73    /// Return the outline of the glyph, assuming an upem value of 1000.
74    pub fn outline(&self) -> BezPath {
75        self.font.outline_glyph(self.id)
76    }
77}
78
79/// A type3 glyph.
80pub struct Type3Glyph<'a> {
81    pub(crate) font: Rc<Type3<'a>>,
82    pub(crate) glyph_id: GlyphId,
83    pub(crate) state: State<'a>,
84    pub(crate) parent_resources: Resources<'a>,
85    pub(crate) cache: Cache,
86    pub(crate) glyph_transform: Affine,
87    pub(crate) xref: &'a XRef,
88    pub(crate) settings: InterpreterSettings,
89}
90
91/// A glyph defined by PDF drawing instructions.
92impl<'a> Type3Glyph<'a> {
93    /// Draw the type3 glyph to the given device.
94    pub fn interpret(&self, device: &mut impl Device, paint: &Paint) {
95        self.font.render_glyph(self, paint, device);
96    }
97}
98
99#[derive(Clone, Debug)]
100pub(crate) struct Font<'a>(FontType<'a>);
101
102impl<'a> Font<'a> {
103    pub(crate) fn new(
104        dict: &Dict<'a>,
105        resolver: &FontResolverFn,
106        warning_sink: &WarningSinkFn,
107    ) -> Option<Self> {
108        let f_type = match dict.get::<Name>(SUBTYPE)?.deref() {
109            TYPE1 | MM_TYPE1 => FontType::Type1(Rc::new(Type1Font::new(dict, resolver)?)),
110            TRUE_TYPE => TrueTypeFont::new(dict)
111                .map(Rc::new)
112                .map(FontType::TrueType)
113                .or_else(|| {
114                    Type1Font::new(dict, resolver)
115                        .map(Rc::new)
116                        .map(FontType::Type1)
117                })?,
118            TYPE0 => FontType::Type0(Rc::new(Type0Font::new(dict, warning_sink)?)),
119            TYPE3 => FontType::Type3(Rc::new(Type3::new(dict))),
120            f => {
121                warn!(
122                    "unimplemented font type {:?}",
123                    std::str::from_utf8(f).unwrap_or("unknown type")
124                );
125
126                return None;
127            }
128        };
129
130        Some(Self(f_type))
131    }
132
133    pub(crate) fn map_code(&self, code: u16) -> GlyphId {
134        match &self.0 {
135            FontType::Type1(f) => {
136                debug_assert!(code <= u8::MAX as u16);
137
138                f.map_code(code as u8)
139            }
140            FontType::TrueType(t) => {
141                debug_assert!(code <= u8::MAX as u16);
142
143                t.map_code(code as u8)
144            }
145            FontType::Type0(t) => t.map_code(code),
146            FontType::Type3(t) => {
147                debug_assert!(code <= u8::MAX as u16);
148
149                t.map_code(code as u8)
150            }
151        }
152    }
153
154    pub(crate) fn get_glyph(
155        &self,
156        glyph: GlyphId,
157        ctx: &mut Context<'a>,
158        resources: &Resources<'a>,
159        origin_displacement: Vec2,
160    ) -> Glyph<'a> {
161        let glyph_transform = ctx.get().text_state.full_transform()
162            * Affine::scale(1.0 / UNITS_PER_EM as f64)
163            * Affine::translate(origin_displacement);
164
165        match &self.0 {
166            FontType::Type1(t) => {
167                let font = OutlineFont::Type1(t.clone());
168                Glyph::Outline(OutlineGlyph {
169                    id: glyph,
170                    font,
171                    glyph_transform,
172                })
173            }
174            FontType::TrueType(t) => {
175                let font = OutlineFont::TrueType(t.clone());
176                Glyph::Outline(OutlineGlyph {
177                    id: glyph,
178                    font,
179                    glyph_transform,
180                })
181            }
182            FontType::Type0(t) => {
183                let font = OutlineFont::Type0(t.clone());
184                Glyph::Outline(OutlineGlyph {
185                    id: glyph,
186                    font,
187                    glyph_transform,
188                })
189            }
190            FontType::Type3(t) => {
191                let shape_glyph = Type3Glyph {
192                    font: t.clone(),
193                    glyph_id: glyph,
194                    state: ctx.get().clone(),
195                    parent_resources: resources.clone(),
196                    cache: ctx.object_cache.clone(),
197                    xref: ctx.xref,
198                    settings: ctx.settings.clone(),
199                    glyph_transform,
200                };
201
202                Glyph::Type3(Box::new(shape_glyph))
203            }
204        }
205    }
206
207    pub(crate) fn code_advance(&self, code: u16) -> Vec2 {
208        match &self.0 {
209            FontType::Type1(t) => {
210                debug_assert!(code <= u8::MAX as u16);
211
212                Vec2::new(t.glyph_width(code as u8).unwrap_or(0.0) as f64, 0.0)
213            }
214            FontType::TrueType(t) => {
215                debug_assert!(code <= u8::MAX as u16);
216
217                Vec2::new(t.glyph_width(code as u8) as f64, 0.0)
218            }
219            FontType::Type0(t) => t.code_advance(code),
220            FontType::Type3(t) => {
221                debug_assert!(code <= u8::MAX as u16);
222
223                Vec2::new(t.glyph_width(code as u8) as f64, 0.0)
224            }
225        }
226    }
227
228    pub(crate) fn origin_displacement(&self, code: u16) -> Vec2 {
229        match &self.0 {
230            FontType::Type1(_) => Vec2::default(),
231            FontType::TrueType(_) => Vec2::default(),
232            FontType::Type0(t) => t.origin_displacement(code),
233            FontType::Type3(_) => Vec2::default(),
234        }
235    }
236
237    pub(crate) fn code_len(&self) -> usize {
238        match &self.0 {
239            FontType::Type1(_) => 1,
240            FontType::TrueType(_) => 1,
241            FontType::Type0(t) => t.code_len(),
242            FontType::Type3(_) => 1,
243        }
244    }
245
246    pub(crate) fn is_horizontal(&self) -> bool {
247        match &self.0 {
248            FontType::Type1(_) => true,
249            FontType::TrueType(_) => true,
250            FontType::Type0(t) => t.is_horizontal(),
251            FontType::Type3(_) => true,
252        }
253    }
254}
255
256#[derive(Clone, Debug)]
257enum FontType<'a> {
258    Type1(Rc<Type1Font>),
259    TrueType(Rc<TrueTypeFont>),
260    Type0(Rc<Type0Font>),
261    Type3(Rc<Type3<'a>>),
262}
263
264#[derive(Debug)]
265enum Encoding {
266    Standard,
267    MacRoman,
268    WinAnsi,
269    MacExpert,
270    BuiltIn,
271}
272
273impl Encoding {
274    fn map_code(&self, code: u8) -> Option<&'static str> {
275        if code == 0 {
276            return Some(".notdef");
277        }
278        match self {
279            Encoding::Standard => standard::get(code),
280            Encoding::MacRoman => mac_roman::get(code).or_else(|| mac_os_roman::get(code)),
281            Encoding::WinAnsi => win_ansi::get(code),
282            Encoding::MacExpert => mac_expert::get(code),
283            Encoding::BuiltIn => None,
284        }
285    }
286}
287
288/// The font stretch.
289#[derive(Debug, Copy, Clone)]
290pub enum FontStretch {
291    /// Normal.
292    Normal,
293    /// Ultra condensed.
294    UltraCondensed,
295    /// Extra condensed.
296    ExtraCondensed,
297    /// Condensed.
298    Condensed,
299    /// Semi condensed.
300    SemiCondensed,
301    /// Semi expanded.
302    SemiExpanded,
303    /// Expanded.
304    Expanded,
305    /// Extra expanded.
306    ExtraExpanded,
307    /// Ultra expanded.
308    UltraExpanded,
309}
310
311impl FontStretch {
312    fn from_string(s: &str) -> Self {
313        match s {
314            "UltraCondensed" => FontStretch::UltraCondensed,
315            "ExtraCondensed" => FontStretch::ExtraCondensed,
316            "Condensed" => FontStretch::Condensed,
317            "SemiCondensed" => FontStretch::SemiCondensed,
318            "SemiExpanded" => FontStretch::SemiExpanded,
319            "Expanded" => FontStretch::Expanded,
320            "ExtraExpanded" => FontStretch::ExtraExpanded,
321            "UltraExpanded" => FontStretch::UltraExpanded,
322            _ => FontStretch::Normal,
323        }
324    }
325}
326
327bitflags! {
328    /// Bitflags describing various characteristics of fonts.
329    #[derive(Debug)]
330    pub(crate) struct FontFlags: u32 {
331        const FIXED_PITCH = 1 << 0;
332        const SERIF = 1 << 1;
333        const SYMBOLIC = 1 << 2;
334        const SCRIPT = 1 << 3;
335        const NON_SYMBOLIC = 1 << 5;
336        const ITALIC = 1 << 6;
337        const ALL_CAP = 1 << 16;
338        const SMALL_CAP = 1 << 17;
339        const FORCE_BOLD = 1 << 18;
340    }
341}
342
343/// A query for a font.
344pub enum FontQuery {
345    /// A query for one of the 14 PDF standard fonts.
346    Standard(StandardFont),
347    /// A query for a font that is not embedded in the PDF file.
348    ///
349    /// Note that this type of query is currently not supported,
350    /// but will be implemented in the future.
351    Fallback(FallbackFontQuery),
352}
353
354/// A query for a font with specific properties.
355#[derive(Debug, Clone)]
356pub struct FallbackFontQuery {
357    /// The postscript name of the font.
358    pub post_script_name: Option<String>,
359    /// The name of the font.
360    pub font_name: Option<String>,
361    /// The family of the font.
362    pub font_family: Option<String>,
363    /// The stretch of the font.
364    pub font_stretch: FontStretch,
365    /// The weight of the font.
366    pub font_weight: u32,
367    /// Whether the font is monospaced.
368    pub is_fixed_pitch: bool,
369    /// Whether the font is serif.
370    pub is_serif: bool,
371    /// Whether the font is italic.
372    pub is_italic: bool,
373    /// Whether the font is bold.
374    pub is_bold: bool,
375    /// Whether the font is small cap.
376    pub is_small_cap: bool,
377}
378
379impl FallbackFontQuery {
380    pub(crate) fn new(dict: &Dict) -> Self {
381        let mut data = Self::default();
382
383        let remove_subset_prefix = |s: String| {
384            if s.contains("+") {
385                s.chars().skip(7).collect()
386            } else {
387                s
388            }
389        };
390
391        data.post_script_name = dict
392            .get::<Name>(BASE_FONT)
393            .map(|n| remove_subset_prefix(n.as_str().to_string()));
394
395        if let Some(descriptor) = dict.get::<Dict>(FONT_DESC) {
396            data.font_name = dict
397                .get::<Name>(FONT_NAME)
398                .map(|n| remove_subset_prefix(n.as_str().to_string()));
399            data.font_family = descriptor
400                .get::<Name>(FONT_FAMILY)
401                .map(|n| n.as_str().to_string());
402            data.font_stretch = descriptor
403                .get::<Name>(FONT_STRETCH)
404                .map(|n| FontStretch::from_string(n.as_str()))
405                .unwrap_or(FontStretch::Normal);
406            data.font_weight = descriptor.get::<u32>(FONT_WEIGHT).unwrap_or(400);
407
408            if let Some(flags) = descriptor
409                .get::<u32>(FLAGS)
410                .map(FontFlags::from_bits_truncate)
411            {
412                data.is_serif = flags.contains(FontFlags::SERIF);
413                data.is_italic = flags.contains(FontFlags::ITALIC)
414                    || data
415                        .post_script_name
416                        .as_ref()
417                        .is_some_and(|s| s.contains("Italic"));
418                data.is_small_cap = flags.contains(FontFlags::SMALL_CAP);
419                data.is_bold = data
420                    .post_script_name
421                    .as_ref()
422                    .is_some_and(|s| s.contains("Bold"));
423            }
424        }
425
426        data
427    }
428
429    /// Do a best-effort fallback to the 14 standard fonts based on the query.
430    pub fn pick_standard_font(&self) -> StandardFont {
431        if self.is_fixed_pitch {
432            match (self.is_bold, self.is_italic) {
433                (true, true) => StandardFont::CourierBoldOblique,
434                (true, false) => StandardFont::CourierBold,
435                (false, true) => StandardFont::CourierOblique,
436                (false, false) => StandardFont::Courier,
437            }
438        } else if !self.is_serif {
439            match (self.is_bold, self.is_italic) {
440                (true, true) => StandardFont::HelveticaBoldOblique,
441                (true, false) => StandardFont::HelveticaBold,
442                (false, true) => StandardFont::HelveticaOblique,
443                (false, false) => StandardFont::Helvetica,
444            }
445        } else {
446            match (self.is_bold, self.is_italic) {
447                (true, true) => StandardFont::TimesBoldItalic,
448                (true, false) => StandardFont::TimesBold,
449                (false, true) => StandardFont::TimesItalic,
450                (false, false) => StandardFont::TimesRoman,
451            }
452        }
453    }
454}
455
456impl Default for FallbackFontQuery {
457    fn default() -> Self {
458        Self {
459            post_script_name: None,
460            font_name: None,
461            font_family: None,
462            font_stretch: FontStretch::Normal,
463            font_weight: 400,
464            is_fixed_pitch: false,
465            is_serif: false,
466            is_italic: false,
467            is_bold: false,
468            is_small_cap: false,
469        }
470    }
471}