Skip to main content

pdf_interpret/font/
outline.rs

1use crate::CacheKey;
2use crate::font::cid::Type0Font;
3use crate::font::true_type::TrueTypeFont;
4use crate::font::type1::Type1Font;
5use kurbo::BezPath;
6use pdf_font::OutlineBuilder;
7use pdf_font::cmap::BfString;
8use skrifa::GlyphId;
9use skrifa::outline::OutlinePen;
10use std::rc::Rc;
11
12/// Font data and metadata for downstream use.
13#[derive(Clone)]
14pub struct OutlineFontData {
15    /// Raw font bytes (TrueType/OpenType/CFF data).
16    pub data: crate::font::FontData,
17    /// Cache key for font deduplication.
18    pub cache_key: u128,
19    /// PostScript name (e.g., "TimesNewRomanPS-BoldMT").
20    pub postscript_name: Option<String>,
21    /// Font weight (100-900, 400=normal, 700=bold).
22    pub weight: Option<u32>,
23    /// Whether the font is italic/oblique.
24    pub is_italic: bool,
25    /// Whether the font is serif (vs sans-serif).
26    pub is_serif: bool,
27    /// Whether the font is monospace.
28    pub is_monospace: bool,
29}
30
31pub(crate) struct OutlinePath(BezPath);
32
33impl OutlinePath {
34    pub(crate) fn new() -> Self {
35        Self(BezPath::new())
36    }
37
38    pub(crate) fn take(self) -> BezPath {
39        self.0
40    }
41}
42
43impl OutlinePen for OutlinePath {
44    #[inline]
45    fn move_to(&mut self, x: f32, y: f32) {
46        self.0.move_to((x, y));
47    }
48
49    #[inline]
50    fn line_to(&mut self, x: f32, y: f32) {
51        if !self.0.elements().is_empty() {
52            self.0.line_to((x, y));
53        }
54    }
55
56    #[inline]
57    fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
58        if !self.0.elements().is_empty() {
59            self.0.quad_to((cx, cy), (x, y));
60        }
61    }
62
63    #[inline]
64    fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
65        if !self.0.elements().is_empty() {
66            self.0.curve_to((cx0, cy0), (cx1, cy1), (x, y));
67        }
68    }
69
70    #[inline]
71    fn close(&mut self) {
72        if !self.0.elements().is_empty() {
73            self.0.close_path();
74        }
75    }
76}
77
78impl OutlineBuilder for OutlinePath {
79    fn move_to(&mut self, x: f32, y: f32) {
80        self.0.move_to((x, y));
81    }
82
83    fn line_to(&mut self, x: f32, y: f32) {
84        if !self.0.elements().is_empty() {
85            self.0.line_to((x, y));
86        }
87    }
88
89    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
90        if !self.0.elements().is_empty() {
91            self.0.quad_to((x1, y1), (x, y));
92        }
93    }
94
95    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
96        if !self.0.elements().is_empty() {
97            self.0.curve_to((x1, y1), (x2, y2), (x, y));
98        }
99    }
100
101    fn close(&mut self) {
102        if !self.0.elements().is_empty() {
103            self.0.close_path();
104        }
105    }
106}
107
108#[derive(Debug, Clone)]
109pub(crate) enum OutlineFont {
110    Type1(Rc<Type1Font>),
111    TrueType(Rc<TrueTypeFont>),
112    Type0(Rc<Type0Font>),
113}
114
115impl CacheKey for OutlineFont {
116    fn cache_key(&self) -> u128 {
117        match self {
118            Self::Type1(f) => f.cache_key(),
119            Self::TrueType(t) => t.cache_key(),
120            Self::Type0(t0) => t0.cache_key(),
121        }
122    }
123}
124
125impl OutlineFont {
126    pub(crate) fn outline_glyph(&self, glyph: GlyphId, code: u32) -> BezPath {
127        match self {
128            Self::Type1(t) => t.outline_glyph(glyph),
129            Self::TrueType(t) => t.outline_glyph(glyph),
130            Self::Type0(t) => t.outline_glyph(glyph, code),
131        }
132    }
133
134    pub(crate) fn char_code_to_unicode(&self, char_code: u32) -> Option<BfString> {
135        match self {
136            Self::Type1(t) => t.char_code_to_unicode(char_code),
137            Self::TrueType(t) => t.char_code_to_unicode(char_code),
138            Self::Type0(t) => t.char_code_to_unicode(char_code),
139        }
140    }
141
142    /// Get the advance width for a glyph by character code.
143    pub(crate) fn glyph_advance_width(&self, char_code: u32) -> Option<f32> {
144        match self {
145            Self::Type1(t) => t.glyph_width(char_code as u8),
146            Self::TrueType(t) => Some(t.glyph_width(char_code as u8)),
147            Self::Type0(t) => Some(t.code_advance(char_code).x as f32),
148        }
149    }
150
151    /// Get raw font bytes and metadata.
152    ///
153    /// Returns None for Type1 fonts and non-embedded TrueType fonts.
154    pub(crate) fn font_data(&self) -> Option<OutlineFontData> {
155        match self {
156            Self::Type1(_) => None,
157            Self::TrueType(t) => Some(OutlineFontData {
158                data: t.font_data()?,
159                cache_key: t.cache_key(),
160                postscript_name: t.postscript_name().map(|s| s.to_string()),
161                weight: t.weight(),
162                is_italic: t.is_italic(),
163                is_serif: t.is_serif(),
164                is_monospace: t.is_monospace(),
165            }),
166            Self::Type0(t) => Some(OutlineFontData {
167                data: t.font_data()?,
168                cache_key: t.cache_key(),
169                postscript_name: t.postscript_name().map(|s| s.to_string()),
170                weight: t.weight(),
171                is_italic: t.is_italic(),
172                is_serif: t.is_serif(),
173                is_monospace: t.is_monospace(),
174            }),
175        }
176    }
177}