rust_pdf_box/font/
truetype.rs1use crate::cos::document::COSDocument;
2use crate::cos::object::{COSArray, COSDictionary, COSName, COSStream, ObjectId};
3use crate::error::{PdfError, PdfResult};
4use ttf_parser::Face;
5
6#[derive(Debug, Clone)]
8pub struct PDTrueTypeFont {
9 pub font_name: String,
10 pub units_per_em: u16,
11 pub ascent: i16,
12 pub descent: i16,
13 pub font_id: ObjectId,
14}
15
16impl PDTrueTypeFont {
17 pub fn load(cos_doc: &mut COSDocument, font_bytes: &[u8]) -> PdfResult<Self> {
19 let face = Face::parse(font_bytes, 0)
20 .map_err(|e| PdfError::Font(format!("Failed parsing TrueType font: {e}")))?;
21
22 let font_name = face
23 .names()
24 .into_iter()
25 .find(|name| name.name_id == ttf_parser::name_id::POST_SCRIPT_NAME)
26 .and_then(|name| name.to_string())
27 .unwrap_or_else(|| "CustomFont".to_string());
28
29 let units_per_em = face.units_per_em();
30 let ascent = face.ascender();
31 let descent = face.descender();
32
33 let mut font_file_dict = COSDictionary::new();
35 font_file_dict.set("Length1", font_bytes.len() as i64);
36 let mut font_stream = COSStream::new(font_file_dict, font_bytes.to_vec());
37 let _ = font_stream.encode_flate();
38 let font_file_id = cos_doc.add_object(font_stream);
39
40 let mut desc = COSDictionary::new();
42 desc.set("Type", COSName::new("FontDescriptor"));
43 desc.set("FontName", COSName::new(&font_name));
44 desc.set("Flags", 32); let mut bbox = COSArray::new();
46 bbox.push(face.global_bounding_box().x_min as i64);
47 bbox.push(face.global_bounding_box().y_min as i64);
48 bbox.push(face.global_bounding_box().x_max as i64);
49 bbox.push(face.global_bounding_box().y_max as i64);
50 desc.set("FontBBox", bbox);
51 desc.set("ItalicAngle", face.italic_angle() as f64);
52 desc.set("Ascent", ascent as i64);
53 desc.set("Descent", descent as i64);
54 desc.set("CapHeight", face.capital_height().unwrap_or(ascent) as i64);
55 desc.set("StemV", 80);
56 desc.set("FontFile2", font_file_id);
57 let desc_id = cos_doc.add_object(desc);
58
59 let mut font_dict = COSDictionary::new();
61 font_dict.set("Type", COSName::new("Font"));
62 font_dict.set("Subtype", COSName::new("TrueType"));
63 font_dict.set("BaseFont", COSName::new(&font_name));
64 font_dict.set("FontDescriptor", desc_id);
65 font_dict.set("Encoding", COSName::new("WinAnsiEncoding"));
66 font_dict.set("FirstChar", 32);
67 font_dict.set("LastChar", 255);
68
69 let mut widths = COSArray::new();
71 let scale = 1000.0 / (units_per_em as f64);
72 for byte_val in 32u8..=255u8 {
73 let ch = byte_val as char;
74 let width = if let Some(glyph_id) = face.glyph_index(ch) {
75 let advance = face.glyph_hor_advance(glyph_id).unwrap_or(0);
76 (advance as f64 * scale).round() as i64
77 } else {
78 500
79 };
80 widths.push(width);
81 }
82 font_dict.set("Widths", widths);
83
84 let font_id = cos_doc.add_object(font_dict);
85
86 Ok(Self {
87 font_name,
88 units_per_em,
89 ascent,
90 descent,
91 font_id,
92 })
93 }
94}