1use crate::encoding::{FontEncoding, adobe_char_name};
10use crate::ids::GlyphName;
11use crate::tounicode::{self, ToUnicode};
12use crate::{CharCode, CharItem, FontCache, FontId, names, simple};
13use pdfrum_common::kurbo::{Affine, Rect};
14use pdfrum_common::{Diagnostics, Limits};
15use pdfrum_object::{Dict, Resolve, Stream};
16use smallvec::SmallVec;
17
18pub const MAX_TYPE3_DEPTH: u32 = 4;
21
22#[derive(Debug)]
24pub struct Type3Font {
25 pub(crate) id: FontId,
27 pub font_matrix: Affine,
29 pub(crate) char_procs: Dict,
31 pub resources: Option<Dict>,
33 pub(crate) encoding: [Option<GlyphName>; 256],
37 pub(crate) encoding_kind: FontEncoding,
39 pub(crate) widths: [i32; 256],
44 pub(crate) font_bbox: Rect,
46 pub(crate) to_unicode: Option<ToUnicode>,
48}
49
50impl Type3Font {
51 #[must_use]
57 pub fn char_proc(&self, code: CharCode, r: &impl Resolve) -> Option<Stream> {
58 let name = self.char_proc_name(code)?;
59 let key = pdfrum_object::Name::new(name.to_vec());
60 self.char_procs.stream(&key, r)
61 }
62
63 #[must_use]
65 pub(crate) fn char_proc_name(&self, code: CharCode) -> Option<&[u8]> {
66 adobe_char_name(self.encoding_kind, &self.encoding, code.0)
67 }
68
69 #[must_use]
76 pub(crate) fn char_width(&self, code: CharCode) -> f32 {
77 let code = if code.0 >= 256 { 0 } else { code.0 as usize };
78 self.widths.get(code).copied().unwrap_or(0) as f32
79 }
80
81 #[must_use]
84 pub(crate) fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
85 self.to_unicode
86 .as_ref()
87 .map(|tu| tu.lookup(code))
88 .unwrap_or_default()
89 }
90
91 #[must_use]
96 pub(crate) fn char_code_from_unicode(&self, unicode: char) -> Option<CharCode> {
97 let code = self.to_unicode.as_ref()?.reverse(unicode);
98 (code.0 != 0).then_some(code)
99 }
100
101 pub(crate) fn char_item(&self, code: CharCode) -> CharItem {
102 CharItem {
103 code,
104 cid: None,
105 gid: None,
108 unicode: self.unicode_from_charcode(code),
109 width: self.char_width(code),
110 vertical_glyph: false,
111 }
112 }
113}
114
115pub(crate) fn load(
121 dict: &Dict,
122 r: &impl Resolve,
123 cache: &FontCache,
124 limits: &Limits,
125 diags: &mut Diagnostics,
126) -> Type3Font {
127 let (font_matrix, xscale, yscale) = match dict.raw(names::FONT_MATRIX) {
128 Some(_) => {
129 let m = dict.matrix(names::FONT_MATRIX, r);
130 let c = m.as_coeffs();
131 (m, c[0], c[3])
132 }
133 None => (Affine::IDENTITY, 1.0, 1.0),
134 };
135
136 let font_bbox = match dict.array(names::FONT_BBOX, r) {
137 Some(b) => {
138 Rect::new(
140 f64::from(b.number_at_or_zero(0)) * xscale * 1000.0,
141 f64::from(b.number_at_or_zero(1)) * yscale * 1000.0,
142 f64::from(b.number_at_or_zero(2)) * xscale * 1000.0,
143 f64::from(b.number_at_or_zero(3)) * yscale * 1000.0,
144 )
145 }
146 None => Rect::ZERO,
147 };
148
149 let mut widths = [0i32; 256];
150 let start = dict.int(names::FIRST_CHAR, r).unwrap_or(0);
151 if let (Ok(start), Some(w)) = (usize::try_from(start), dict.array(names::WIDTHS, r))
152 && start < 256
153 {
154 let count = w.len().min(256).min(256 - start);
155 for i in 0..count {
156 let value = f64::from(w.number_at_or_zero(i)) * xscale * 1000.0;
157 if let Some(slot) = widths.get_mut(start + i) {
158 *slot = round_f32(value);
160 }
161 }
162 }
163
164 let mut encoding: [Option<GlyphName>; 256] = [const { None }; 256];
165 let mut encoding_kind = FontEncoding::Builtin;
166 if dict.raw(names::ENCODING).is_some() {
167 simple::load_pdf_encoding(
168 dict,
169 r,
170 b"",
171 crate::FontFlags::DEFAULT,
172 false,
173 false,
174 &mut encoding_kind,
175 &mut encoding,
176 );
177 }
178
179 let to_unicode = dict
180 .stream(names::TO_UNICODE, r)
181 .map(|s| {
182 let bytes = pdfrum_filters::decode_chain(&s, 0, r, limits, diags).data;
183 tounicode::parse(&bytes, limits, diags)
184 })
185 .filter(|m| !m.is_empty());
186
187 Type3Font {
188 id: cache.next_id(),
189 font_matrix,
190 char_procs: dict.dict(names::CHAR_PROCS, r).unwrap_or_default(),
191 resources: dict.dict(names::RESOURCES, r),
192 encoding,
193 encoding_kind,
194 widths,
195 font_bbox,
196 to_unicode,
197 }
198}
199
200fn round_f32(v: f64) -> i32 {
201 let r = v.round();
202 if r.is_nan() {
203 0
204 } else if r >= f64::from(i32::MAX) {
205 i32::MAX
206 } else if r <= f64::from(i32::MIN) {
207 i32::MIN
208 } else {
209 r as i32
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 #![allow(clippy::float_cmp)]
217 use super::*;
218 use pdfrum_object::{Array, Name, NoResolve, Object};
219
220 fn font_dict(pairs: Vec<(&Name, Object)>) -> Dict {
221 Dict::from_pairs(pairs.into_iter().map(|(k, v)| (k.clone(), v)))
222 }
223
224 fn load_it(dict: &Dict) -> Type3Font {
225 load(
226 dict,
227 &NoResolve,
228 &FontCache::new(),
229 &Limits::default(),
230 &mut Diagnostics::default(),
231 )
232 }
233
234 #[test]
235 fn without_an_encoding_no_code_names_anything() {
236 let f = load_it(&Dict::new());
239 assert_eq!(f.encoding_kind, FontEncoding::Builtin);
240 for code in 0..256u32 {
241 assert!(f.char_proc_name(CharCode(code)).is_none(), "code {code}");
242 }
243 }
244
245 #[test]
246 fn differences_name_the_procedures() {
247 let enc = font_dict(vec![(
248 names::DIFFERENCES,
249 Object::Array(Array::of([
250 Object::Int(97),
251 Object::Name(Name::from("square")),
252 Object::Name(Name::from("triangle")),
253 ])),
254 )]);
255 let f = load_it(&font_dict(vec![(names::ENCODING, Object::Dict(enc))]));
256 assert_eq!(f.char_proc_name(CharCode(97)), Some(&b"square"[..]));
257 assert_eq!(f.char_proc_name(CharCode(98)), Some(&b"triangle"[..]));
258 assert_eq!(f.encoding_kind, FontEncoding::Standard);
263 assert_eq!(f.char_proc_name(CharCode(99)), Some(&b"c"[..]));
264 }
265
266 #[test]
267 fn the_font_matrix_defaults_to_identity() {
268 let f = load_it(&Dict::new());
269 assert_eq!(f.font_matrix, Affine::IDENTITY);
270 }
271
272 #[test]
273 fn widths_are_scaled_by_the_matrix_and_by_a_thousand() {
274 let f = load_it(&font_dict(vec![
275 (
276 names::FONT_MATRIX,
277 Object::Array(Array::of([
278 Object::Real(0.01),
279 Object::Int(0),
280 Object::Int(0),
281 Object::Real(0.01),
282 Object::Int(0),
283 Object::Int(0),
284 ])),
285 ),
286 (names::FIRST_CHAR, Object::Int(97)),
287 (
288 names::WIDTHS,
289 Object::Array(Array::of([Object::Int(50), Object::Int(75)])),
290 ),
291 ]));
292 assert_eq!(f.char_width(CharCode(97)), 500.0);
294 assert_eq!(f.char_width(CharCode(98)), 750.0);
295 assert_eq!(f.char_width(CharCode(99)), 0.0);
297 }
298
299 #[test]
300 fn a_code_at_or_above_256_reads_code_zero() {
301 let f = load_it(&font_dict(vec![
302 (names::FIRST_CHAR, Object::Int(0)),
303 (names::WIDTHS, Object::Array(Array::of([Object::Int(1)]))),
304 ]));
305 assert_eq!(f.char_width(CharCode(0)), 1000.0);
307 assert_eq!(f.char_width(CharCode(256)), 1000.0);
308 assert_eq!(f.char_width(CharCode(u32::MAX)), 1000.0);
309 }
310
311 #[test]
312 fn widths_beyond_the_table_are_dropped_without_wrapping() {
313 let f = load_it(&font_dict(vec![
314 (names::FIRST_CHAR, Object::Int(254)),
315 (
316 names::WIDTHS,
317 Object::Array(Array::of([
318 Object::Int(1),
319 Object::Int(2),
320 Object::Int(3),
321 Object::Int(4),
322 ])),
323 ),
324 ]));
325 assert_eq!(f.char_width(CharCode(254)), 1000.0);
326 assert_eq!(f.char_width(CharCode(255)), 2000.0);
327 assert_eq!(f.char_width(CharCode(0)), 0.0);
329 }
330
331 #[test]
332 fn a_first_char_past_the_table_drops_every_width() {
333 let f = load_it(&font_dict(vec![
334 (names::FIRST_CHAR, Object::Int(300)),
335 (names::WIDTHS, Object::Array(Array::of([Object::Int(9)]))),
336 ]));
337 assert!(f.widths.iter().all(|&w| w == 0));
338 }
339
340 #[test]
341 fn the_bbox_is_scaled_into_glyph_units() {
342 let f = load_it(&font_dict(vec![
343 (
344 names::FONT_MATRIX,
345 Object::Array(Array::of([
346 Object::Real(0.001),
347 Object::Int(0),
348 Object::Int(0),
349 Object::Real(0.001),
350 Object::Int(0),
351 Object::Int(0),
352 ])),
353 ),
354 (
355 names::FONT_BBOX,
356 Object::Array(Array::of([
357 Object::Int(0),
358 Object::Int(0),
359 Object::Int(1000),
360 Object::Int(1000),
361 ])),
362 ),
363 ]));
364 assert!((f.font_bbox.x1 - 1000.0).abs() < 1.0, "{:?}", f.font_bbox);
367 assert!((f.font_bbox.y1 - 1000.0).abs() < 1.0, "{:?}", f.font_bbox);
368 assert_eq!((f.font_bbox.x0, f.font_bbox.y0), (0.0, 0.0));
369 }
370
371 #[test]
372 fn a_type3_font_has_no_glyphs_at_all() {
373 let f = load_it(&Dict::new());
374 let item = f.char_item(CharCode(65));
375 assert_eq!(item.gid, None);
376 }
377
378 #[test]
379 fn the_depth_cap_is_four() {
380 assert_eq!(MAX_TYPE3_DEPTH, 4);
381 }
382}