1use std::collections::HashMap;
12use std::path::Path;
13
14use crate::cff_parser;
15use crate::context::Context;
16use crate::dict::DictKey;
17use crate::object::{ObjFlags, PsObject, PsValue};
18use crate::system_fonts::{
19 extract_ps_name_from_name_table, parse_cmap_table, parse_post_table, unicode_to_glyph_name,
20};
21use crate::truetype::{find_table, get_units_per_em, read_i16};
22
23pub fn load_otf_cff(ctx: &mut Context, path: &Path) -> bool {
28 let data = match std::fs::read(path) {
29 Ok(d) => d,
30 Err(_) => return false,
31 };
32
33 let (cff_offset, cff_length) = match find_table(&data, b"CFF ") {
35 Some(t) => t,
36 None => return false,
37 };
38
39 if cff_offset + cff_length > data.len() {
40 return false;
41 }
42
43 let cff_data = &data[cff_offset..cff_offset + cff_length];
44
45 let cff_fonts = match cff_parser::parse_cff(cff_data) {
46 Ok(f) => f,
47 Err(_) => return false,
48 };
49
50 let saved_vm_mode = ctx.vm_alloc_mode;
52 ctx.vm_alloc_mode = true;
53
54 let sl = ctx.save_stack.current_level();
56 let cs = ctx.save_stack.last_save_id();
57 let cff_str_entity = ctx.strings.allocate_from_with(cff_data, sl, true, cs);
58 let cff_data_len = cff_data.len() as u32;
59
60 let mut success = true;
61 for cff_font in &cff_fonts {
62 if register_cff_font(ctx, cff_font, cff_str_entity, cff_data_len).is_err() {
63 success = false;
64 break;
65 }
66 }
67
68 ctx.vm_alloc_mode = saved_vm_mode;
69 success
70}
71
72pub fn load_ttf(ctx: &mut Context, path: &Path) -> bool {
76 let data = match std::fs::read(path) {
77 Ok(d) => d,
78 Err(_) => return false,
79 };
80
81 load_ttf_from_data(ctx, &data)
82}
83
84pub fn load_ttf_from_data(ctx: &mut Context, data: &[u8]) -> bool {
86 if data.len() < 12 {
87 return false;
88 }
89
90 let font_name = match extract_ps_name_from_name_table(data) {
92 Some(n) => n,
93 None => return false,
94 };
95
96 let units_per_em = get_units_per_em(data);
98 let em_scale = 1.0 / units_per_em as f64;
99
100 let (x_min, y_min, x_max, y_max) = get_font_bbox(data);
101 let bbox = [
102 x_min as f64 * em_scale,
103 y_min as f64 * em_scale,
104 x_max as f64 * em_scale,
105 y_max as f64 * em_scale,
106 ];
107
108 let unicode_to_gid = match parse_cmap_table(data) {
110 Some(m) => m,
111 None => return false,
112 };
113
114 let gid_to_name = parse_post_table(data);
116
117 let resolve_glyph_name = |gid: u16, unicode: Option<u32>| -> String {
120 if let Some(ref post_map) = gid_to_name
122 && let Some(name) = post_map.get(&gid)
123 {
124 return name.clone();
125 }
126 if let Some(cp) = unicode {
128 if let Some(name) = unicode_to_glyph_name(cp) {
129 return name.to_string();
130 }
131 return format!("uni{:04X}", cp);
132 }
133 format!("glyph{}", gid)
134 };
135
136 let mut encoding = vec![".notdef".to_string(); 256];
138 let mut charstrings: HashMap<String, u16> = HashMap::new();
139 charstrings.insert(".notdef".to_string(), 0);
140
141 for code in 0u32..256 {
143 if let Some(&gid) = unicode_to_gid.get(&code) {
144 let name = resolve_glyph_name(gid, Some(code));
145 encoding[code as usize] = name.clone();
146 charstrings.insert(name, gid);
147 }
148 }
149
150 for (&unicode, &gid) in &unicode_to_gid {
152 if unicode >= 256 {
153 let name = resolve_glyph_name(gid, Some(unicode));
154 charstrings.entry(name).or_insert(gid);
155 }
156 }
157
158 let saved_vm_mode = ctx.vm_alloc_mode;
160 ctx.vm_alloc_mode = true;
161
162 let success = build_type42_font(ctx, &font_name, &bbox, &encoding, &charstrings, data);
163
164 ctx.vm_alloc_mode = saved_vm_mode;
165 success
166}
167
168fn build_type42_font(
171 ctx: &mut Context,
172 font_name: &str,
173 bbox: &[f64; 4],
174 encoding: &[String],
175 charstrings: &HashMap<String, u16>,
176 font_data: &[u8],
177) -> bool {
178 let sl = ctx.save_stack.current_level();
179 let cs = ctx.save_stack.last_save_id();
180
181 let font_entity = ctx.dicts.allocate_with(16, b"Type42", sl, true, cs);
182
183 ctx.dicts.put(
185 font_entity,
186 DictKey::Name(ctx.name_cache.n_font_type),
187 PsObject::int(42),
188 );
189
190 let font_name_id = ctx.names.intern(font_name.as_bytes());
192 ctx.dicts.put(
193 font_entity,
194 DictKey::Name(ctx.name_cache.n_font_name),
195 PsObject::name_lit(font_name_id),
196 );
197
198 let fm_items = [
200 PsObject::real(1.0),
201 PsObject::real(0.0),
202 PsObject::real(0.0),
203 PsObject::real(1.0),
204 PsObject::real(0.0),
205 PsObject::real(0.0),
206 ];
207 let fm_entity = ctx.arrays.allocate_from_with(&fm_items, sl, true, cs);
208 ctx.dicts.put(
209 font_entity,
210 DictKey::Name(ctx.name_cache.n_font_matrix),
211 PsObject::array(fm_entity, 6),
212 );
213
214 let bb_items = [
216 PsObject::real(bbox[0]),
217 PsObject::real(bbox[1]),
218 PsObject::real(bbox[2]),
219 PsObject::real(bbox[3]),
220 ];
221 let bb_entity = ctx.arrays.allocate_from_with(&bb_items, sl, true, cs);
222 ctx.dicts.put(
223 font_entity,
224 DictKey::Name(ctx.name_cache.n_font_bbox),
225 PsObject::array(bb_entity, 4),
226 );
227
228 ctx.dicts.put(
230 font_entity,
231 DictKey::Name(ctx.name_cache.n_paint_type),
232 PsObject::int(0),
233 );
234
235 let enc_entity = ctx.arrays.allocate_with(256, sl, true, cs);
237 for (i, name) in encoding.iter().enumerate() {
238 let name_id = ctx.names.intern(name.as_bytes());
239 ctx.arrays
240 .set_element(enc_entity, i as u32, PsObject::name_lit(name_id));
241 }
242 ctx.dicts.put(
243 font_entity,
244 DictKey::Name(ctx.name_cache.n_encoding),
245 PsObject::array(enc_entity, 256),
246 );
247
248 let cs_entity =
250 ctx.dicts
251 .allocate_with(charstrings.len().max(10), b"CharStrings", sl, true, cs);
252 for (name, &gid) in charstrings {
253 let name_id = ctx.names.intern(name.as_bytes());
254 ctx.dicts
255 .put(cs_entity, DictKey::Name(name_id), PsObject::int(gid as i32));
256 }
257 ctx.dicts.put(
258 font_entity,
259 DictKey::Name(ctx.name_cache.n_char_strings),
260 PsObject::dict(cs_entity),
261 );
262
263 let sfnts_entity = ctx.arrays.allocate_with(1, sl, true, cs);
265 let str_entity = ctx.strings.allocate_from_with(font_data, sl, true, cs);
266 ctx.arrays.set_element(
267 sfnts_entity,
268 0,
269 PsObject::string(str_entity, font_data.len() as u32),
270 );
271 let sfnts_key = DictKey::Name(ctx.names.intern(b"sfnts"));
272 ctx.dicts
273 .put(font_entity, sfnts_key, PsObject::array(sfnts_entity, 1));
274
275 let upem_key = DictKey::Name(ctx.names.intern(b"_unitsPerEm"));
277 let upem = get_units_per_em(font_data);
278 ctx.dicts
279 .put(font_entity, upem_key, PsObject::int(upem as i32));
280
281 let fid = ctx.next_fid;
283 ctx.next_fid += 1;
284 ctx.dicts.put(
285 font_entity,
286 DictKey::Name(ctx.name_cache.n_fid),
287 PsObject {
288 value: PsValue::FontID(fid),
289 flags: ObjFlags::literal(),
290 },
291 );
292
293 let font_dict_obj = PsObject::dict(font_entity);
295
296 let definefont_id = ctx.names.intern(b"definefont");
297 let key = DictKey::Name(definefont_id);
298 if let Some(op_obj) = ctx.dict_load(&key)
299 && let PsValue::Operator(opcode) = op_obj.value
300 {
301 if ctx.o_stack.push(PsObject::name_lit(font_name_id)).is_err() {
302 return false;
303 }
304 if ctx.o_stack.push(font_dict_obj).is_err() {
305 return false;
306 }
307 let func = ctx.operators[opcode.0 as usize].func;
308 if func(ctx).is_err() {
309 return false;
310 }
311 let _ = ctx.o_stack.pop();
312 } else {
313 ctx.dicts.put(
315 ctx.font_directory,
316 DictKey::Name(font_name_id),
317 font_dict_obj,
318 );
319 }
320
321 true
322}
323
324fn get_font_bbox(font_data: &[u8]) -> (i16, i16, i16, i16) {
326 if let Some((head_off, _)) = find_table(font_data, b"head")
327 && head_off + 54 <= font_data.len()
328 {
329 let x_min = read_i16(font_data, head_off + 36);
330 let y_min = read_i16(font_data, head_off + 38);
331 let x_max = read_i16(font_data, head_off + 40);
332 let y_max = read_i16(font_data, head_off + 42);
333 return (x_min, y_min, x_max, y_max);
334 }
335 (0, 0, 1000, 1000)
336}
337
338fn register_cff_font(
341 ctx: &mut Context,
342 cff_font: &cff_parser::CffFont,
343 cff_str_entity: crate::object::EntityId,
344 cff_data_len: u32,
345) -> Result<(), crate::error::PsError> {
346 let sl = ctx.save_stack.current_level();
347 let cs = ctx.save_stack.last_save_id();
348
349 let font_entity = ctx.dicts.allocate_with(16, b"CFF", sl, true, cs);
350
351 ctx.dicts.put(
353 font_entity,
354 DictKey::Name(ctx.name_cache.n_font_type),
355 PsObject::int(2),
356 );
357
358 let font_name_id = ctx.names.intern(cff_font.name.as_bytes());
360 ctx.dicts.put(
361 font_entity,
362 DictKey::Name(ctx.name_cache.n_font_name),
363 PsObject::name_lit(font_name_id),
364 );
365
366 let fm_entity = ctx.arrays.allocate_with(6, sl, true, cs);
368 for (i, &val) in cff_font.font_matrix.iter().enumerate() {
369 ctx.arrays
370 .set_element(fm_entity, i as u32, PsObject::real(val));
371 }
372 ctx.dicts.put(
373 font_entity,
374 DictKey::Name(ctx.name_cache.n_font_matrix),
375 PsObject::array(fm_entity, 6),
376 );
377
378 let bbox_entity = ctx.arrays.allocate_with(4, sl, true, cs);
380 for (i, &val) in cff_font.font_bbox.iter().enumerate() {
381 ctx.arrays
382 .set_element(bbox_entity, i as u32, PsObject::real(val));
383 }
384 ctx.dicts.put(
385 font_entity,
386 DictKey::Name(ctx.name_cache.n_font_bbox),
387 PsObject::array(bbox_entity, 4),
388 );
389
390 let enc_entity = ctx.arrays.allocate_with(256, sl, true, cs);
392 for code in 0..256u32 {
393 let gid = cff_font.encoding[code as usize] as usize;
394 let glyph_name = if gid < cff_font.charset.len() {
395 &cff_font.charset[gid]
396 } else {
397 ".notdef"
398 };
399 let name_id = ctx.names.intern(glyph_name.as_bytes());
400 ctx.arrays
401 .set_element(enc_entity, code, PsObject::name_lit(name_id));
402 }
403 ctx.dicts.put(
404 font_entity,
405 DictKey::Name(ctx.name_cache.n_encoding),
406 PsObject::array(enc_entity, 256),
407 );
408
409 let cs_entity = ctx.dicts.allocate_with(16, b"CFF", sl, true, cs);
411 for (gid, cs_data) in cff_font.char_strings.iter().enumerate() {
412 let str_entity = ctx.strings.allocate_from_with(cs_data, sl, true, cs);
413 let cs_obj = PsObject::string(str_entity, cs_data.len() as u32);
414
415 if gid < cff_font.charset.len() {
416 let glyph_name = &cff_font.charset[gid];
417 let name_id = ctx.names.intern(glyph_name.as_bytes());
418 ctx.dicts.put(cs_entity, DictKey::Name(name_id), cs_obj);
419 }
420
421 if cff_font.is_cid {
422 ctx.dicts.put(cs_entity, DictKey::Int(gid as i64), cs_obj);
423 }
424 }
425 ctx.dicts.put(
426 font_entity,
427 DictKey::Name(ctx.name_cache.n_char_strings),
428 PsObject::dict(cs_entity),
429 );
430
431 let priv_entity = ctx.dicts.allocate_with(16, b"CFF", sl, true, cs);
433 ctx.dicts.put(
434 priv_entity,
435 DictKey::Name(ctx.names.intern(b"defaultWidthX")),
436 PsObject::real(cff_font.default_width_x),
437 );
438 ctx.dicts.put(
439 priv_entity,
440 DictKey::Name(ctx.names.intern(b"nominalWidthX")),
441 PsObject::real(cff_font.nominal_width_x),
442 );
443
444 if !cff_font.local_subrs.is_empty() {
445 let subrs_len = cff_font.local_subrs.len() as u32;
446 let subrs_entity = ctx.arrays.allocate_with(subrs_len as usize, sl, true, cs);
447 for (i, subr_data) in cff_font.local_subrs.iter().enumerate() {
448 let str_entity = ctx.strings.allocate_from_with(subr_data, sl, true, cs);
449 ctx.arrays.set_element(
450 subrs_entity,
451 i as u32,
452 PsObject::string(str_entity, subr_data.len() as u32),
453 );
454 }
455 ctx.dicts.put(
456 priv_entity,
457 DictKey::Name(ctx.name_cache.n_subrs),
458 PsObject {
459 value: PsValue::Array {
460 entity: subrs_entity,
461 start: 0,
462 len: subrs_len,
463 },
464 flags: ObjFlags::literal_composite(),
465 },
466 );
467 }
468 ctx.dicts.put(
469 font_entity,
470 DictKey::Name(ctx.name_cache.n_private),
471 PsObject::dict(priv_entity),
472 );
473
474 if !cff_font.global_subrs.is_empty() {
476 let gs_key = DictKey::Name(ctx.names.intern(b"_cff_global_subrs"));
477 let gs_len = cff_font.global_subrs.len() as u32;
478 let gs_entity = ctx.arrays.allocate_with(gs_len as usize, sl, true, cs);
479 for (i, subr_data) in cff_font.global_subrs.iter().enumerate() {
480 let str_entity = ctx.strings.allocate_from_with(subr_data, sl, true, cs);
481 ctx.arrays.set_element(
482 gs_entity,
483 i as u32,
484 PsObject::string(str_entity, subr_data.len() as u32),
485 );
486 }
487 ctx.dicts.put(
488 font_entity,
489 gs_key,
490 PsObject {
491 value: PsValue::Array {
492 entity: gs_entity,
493 start: 0,
494 len: gs_len,
495 },
496 flags: ObjFlags::literal_composite(),
497 },
498 );
499 }
500
501 let cff_data_key = DictKey::Name(ctx.names.intern(b"_CFFData"));
503 ctx.dicts.put(
504 font_entity,
505 cff_data_key,
506 PsObject::string(cff_str_entity, cff_data_len),
507 );
508
509 let fid = ctx.next_fid;
511 ctx.next_fid += 1;
512 ctx.dicts.put(
513 font_entity,
514 DictKey::Name(ctx.name_cache.n_fid),
515 PsObject {
516 value: PsValue::FontID(fid),
517 flags: ObjFlags::literal(),
518 },
519 );
520
521 let font_dict_obj = PsObject::dict(font_entity);
523
524 let definefont_id = ctx.names.intern(b"definefont");
525 let key = DictKey::Name(definefont_id);
526 if let Some(op_obj) = ctx.dict_load(&key)
527 && let PsValue::Operator(opcode) = op_obj.value
528 {
529 ctx.o_stack.push(PsObject::name_lit(font_name_id))?;
530 ctx.o_stack.push(font_dict_obj)?;
531 let func = ctx.operators[opcode.0 as usize].func;
532 func(ctx)?;
533 let _ = ctx.o_stack.pop();
534 } else {
535 ctx.dicts.put(
536 ctx.font_directory,
537 DictKey::Name(font_name_id),
538 font_dict_obj,
539 );
540 }
541
542 Ok(())
543}
544
545pub fn is_cff_font(data: &[u8]) -> bool {
547 data.len() >= 4 && &data[0..4] == b"OTTO"
548}
549
550pub fn load_binary_font(ctx: &mut Context, path: &Path) -> bool {
552 let data = match std::fs::read(path) {
553 Ok(d) => d,
554 Err(_) => return false,
555 };
556
557 if data.len() < 4 {
558 return false;
559 }
560
561 if is_cff_font(&data) {
562 load_otf_cff(ctx, path)
564 } else {
565 load_ttf_from_data(ctx, &data)
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn test_is_cff_font() {
576 assert!(is_cff_font(b"OTTO\x00\x00\x00\x00"));
577 assert!(!is_cff_font(b"\x00\x01\x00\x00"));
578 assert!(!is_cff_font(b"tru"));
579 }
580
581 #[test]
582 fn test_load_system_ttf() {
583 let test_fonts = [
585 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
586 "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
587 ];
588 for path_str in &test_fonts {
589 let path = Path::new(path_str);
590 if path.exists() {
591 let mut ctx = Context::new();
592 let ok = load_ttf(&mut ctx, path);
593 assert!(ok, "Failed to load {}", path_str);
594
595 let name = extract_ps_name_from_name_table(&std::fs::read(path).unwrap());
597 if let Some(font_name) = name {
598 let name_id = ctx.names.intern(font_name.as_bytes());
599 let key = DictKey::Name(name_id);
600 assert!(
601 ctx.dicts.get(ctx.font_directory, &key).is_some(),
602 "Font '{}' not in FontDirectory",
603 font_name
604 );
605 }
606 return;
607 }
608 }
609 eprintln!("Skipping TTF loading test — no test font found");
610 }
611
612 #[test]
613 fn test_load_system_otf() {
614 let test_fonts = ["/usr/share/fonts/opentype/urw-base35/C059-Italic.otf"];
616 for path_str in &test_fonts {
617 let path = Path::new(path_str);
618 if path.exists() {
619 let mut ctx = Context::new();
620 let ok = load_otf_cff(&mut ctx, path);
621 assert!(ok, "Failed to load CFF from {}", path_str);
622 return;
623 }
624 }
625 eprintln!("Skipping OTF loading test — no test font found");
626 }
627}