1use crate::context::Context;
11use crate::dict::DictKey;
12use crate::object::{PsObject, PsValue};
13
14pub use stet_fonts::{FONT_SUBSTITUTIONS, find_substitution};
16
17pub fn load_font_file(
20 font_name: &str,
21 resource_path: &str,
22 files: &crate::file_store::FileStore,
23) -> Option<Vec<u8>> {
24 let direct_path = format!("{}/{}.t1", resource_path, font_name);
26 if let Some(data) = files.get_embedded_file(&direct_path) {
27 return Some(data.to_vec());
28 }
29 if let Some(urw_name) = find_substitution(font_name) {
30 let sub_path = format!("{}/{}.t1", resource_path, urw_name);
31 if let Some(data) = files.get_embedded_file(&sub_path) {
32 return Some(data.to_vec());
33 }
34 }
35
36 if let Ok(data) = std::fs::read(&direct_path) {
38 return Some(data);
39 }
40 if let Some(urw_name) = find_substitution(font_name) {
41 let sub_path = format!("{}/{}.t1", resource_path, urw_name);
42 if let Ok(data) = std::fs::read(&sub_path) {
43 return Some(data);
44 }
45 }
46
47 None
48}
49
50pub fn load_type1_font(ctx: &mut Context, font_data: &[u8]) -> Result<PsObject, String> {
53 let font = crate::type1_parser::parse_type1(font_data)?;
54
55 let save_level = ctx.save_stack.current_level();
56 let global = ctx.vm_alloc_mode;
57 let created = ctx.save_stack.last_save_id();
58
59 let font_dict =
61 ctx.dicts
62 .allocate_with(30, font.font_name.as_bytes(), save_level, global, created);
63
64 let name_id = ctx.names.intern(font.font_name.as_bytes());
66 ctx.dicts.put(
67 font_dict,
68 DictKey::Name(ctx.name_cache.n_font_name),
69 PsObject::name_lit(name_id),
70 );
71
72 ctx.dicts.put(
74 font_dict,
75 DictKey::Name(ctx.name_cache.n_font_type),
76 PsObject::int(1),
77 );
78
79 let fm_items: Vec<PsObject> = font
81 .font_matrix
82 .iter()
83 .map(|&v| PsObject::real(v))
84 .collect();
85 let fm_entity = ctx
86 .arrays
87 .allocate_from_with(&fm_items, save_level, global, created);
88 ctx.dicts.put(
89 font_dict,
90 DictKey::Name(ctx.name_cache.n_font_matrix),
91 PsObject::array(fm_entity, 6),
92 );
93
94 let bb_items: Vec<PsObject> = font.font_bbox.iter().map(|&v| PsObject::real(v)).collect();
96 let bb_entity = ctx
97 .arrays
98 .allocate_from_with(&bb_items, save_level, global, created);
99 ctx.dicts.put(
100 font_dict,
101 DictKey::Name(ctx.name_cache.n_font_bbox),
102 PsObject::array(bb_entity, 4),
103 );
104
105 ctx.dicts.put(
107 font_dict,
108 DictKey::Name(ctx.name_cache.n_paint_type),
109 PsObject::int(font.paint_type),
110 );
111
112 let enc_items: Vec<PsObject> = font
114 .encoding
115 .iter()
116 .map(|name| {
117 let id = ctx.names.intern(name.as_bytes());
118 PsObject::name_lit(id)
119 })
120 .collect();
121 let enc_entity = ctx
122 .arrays
123 .allocate_from_with(&enc_items, save_level, global, created);
124 ctx.dicts.put(
125 font_dict,
126 DictKey::Name(ctx.name_cache.n_encoding),
127 PsObject::array(enc_entity, 256),
128 );
129
130 let cs_dict = ctx.dicts.allocate_with(
132 font.charstrings.len().max(10),
133 b"CharStrings",
134 save_level,
135 global,
136 created,
137 );
138 for (glyph_name, bytes) in &font.charstrings {
139 let glyph_name_id = ctx.names.intern(glyph_name.as_bytes());
140 let str_entity = ctx
141 .strings
142 .allocate_from_with(bytes, save_level, global, created);
143 let str_obj = PsObject::string(str_entity, bytes.len() as u32);
144 ctx.dicts
145 .put(cs_dict, DictKey::Name(glyph_name_id), str_obj);
146 }
147 ctx.dicts.put(
148 font_dict,
149 DictKey::Name(ctx.name_cache.n_char_strings),
150 PsObject::dict(cs_dict),
151 );
152
153 let priv_dict = ctx
155 .dicts
156 .allocate_with(10, b"Private", save_level, global, created);
157 ctx.dicts.put(
158 priv_dict,
159 DictKey::Name(ctx.name_cache.n_len_iv),
160 PsObject::int(font.len_iv as i32),
161 );
162
163 let subr_items: Vec<PsObject> = font
165 .subrs
166 .iter()
167 .map(|bytes| {
168 let entity = ctx
169 .strings
170 .allocate_from_with(bytes, save_level, global, created);
171 PsObject::string(entity, bytes.len() as u32)
172 })
173 .collect();
174 let subrs_entity = ctx
175 .arrays
176 .allocate_from_with(&subr_items, save_level, global, created);
177 ctx.dicts.put(
178 priv_dict,
179 DictKey::Name(ctx.name_cache.n_subrs),
180 PsObject::array(subrs_entity, font.subrs.len() as u32),
181 );
182
183 ctx.dicts.put(
184 font_dict,
185 DictKey::Name(ctx.name_cache.n_private),
186 PsObject::dict(priv_dict),
187 );
188
189 let fid = ctx.next_fid;
191 ctx.next_fid += 1;
192 ctx.dicts.put(
193 font_dict,
194 DictKey::Name(ctx.name_cache.n_fid),
195 PsObject::int(fid),
196 );
197
198 let font_obj = PsObject::dict(font_dict);
200 let font_directory = ctx.font_directory;
201 ctx.dict_put_cow(font_directory, DictKey::Name(name_id), font_obj);
202
203 Ok(font_obj)
204}
205
206pub fn find_font(ctx: &mut Context, name_bytes: &[u8]) -> Result<PsObject, String> {
208 let name_id = ctx.names.intern(name_bytes);
209 let key = DictKey::Name(name_id);
210
211 if let Some(font_obj) = ctx.dicts.get(ctx.font_directory, &key) {
213 return Ok(font_obj);
214 }
215
216 let font_name = String::from_utf8_lossy(name_bytes);
218 let resource_path = ctx
219 .font_resource_path
220 .clone()
221 .ok_or_else(|| format!("Font '{}' not found and no resource path set", font_name))?;
222
223 let font_data = load_font_file(&font_name, &resource_path, &ctx.files)
224 .ok_or_else(|| format!("Font '{}' not found in {}", font_name, resource_path))?;
225
226 let font_obj = load_type1_font(ctx, &font_data)?;
227
228 let actual_name = get_font_name(ctx, font_obj);
231 if actual_name.as_deref() != Some(&*font_name) {
232 let font_directory = ctx.font_directory;
233 ctx.dict_put_cow(font_directory, key, font_obj);
234 }
235
236 Ok(font_obj)
237}
238
239fn get_font_name(ctx: &Context, font_obj: PsObject) -> Option<String> {
241 if let PsValue::Dict(entity) = font_obj.value
242 && let Some(name_obj) = ctx
243 .dicts
244 .get(entity, &DictKey::Name(ctx.name_cache.n_font_name))
245 && let PsValue::Name(id) = name_obj.value
246 {
247 return Some(String::from_utf8_lossy(ctx.names.get_bytes(id)).to_string());
248 }
249 None
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_font_substitution() {
258 assert_eq!(find_substitution("Helvetica"), Some("NimbusSans-Regular"));
259 assert_eq!(
260 find_substitution("Times-Roman"),
261 Some("NimbusRoman-Regular")
262 );
263 assert_eq!(find_substitution("Courier"), Some("NimbusMonoPS-Regular"));
264 assert_eq!(find_substitution("Symbol"), Some("StandardSymbolsPS"));
265 assert_eq!(find_substitution("NoSuchFont"), None);
266 }
267
268 #[test]
269 fn test_load_real_font() {
270 let font_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
271 .join("../../resources/Font/NimbusSans-Regular.t1");
272 if !font_path.exists() {
273 eprintln!("Skipping test — font file not found");
274 return;
275 }
276
277 let mut ctx = Context::new();
278 let font_data = std::fs::read(&font_path).unwrap();
279 let font_obj = load_type1_font(&mut ctx, &font_data).unwrap();
280
281 assert!(matches!(font_obj.value, PsValue::Dict(_)));
283
284 if let PsValue::Dict(entity) = font_obj.value {
286 let name_obj = ctx
287 .dicts
288 .get(entity, &DictKey::Name(ctx.name_cache.n_font_name))
289 .unwrap();
290 if let PsValue::Name(id) = name_obj.value {
291 assert_eq!(ctx.names.get_bytes(id), b"NimbusSans-Regular");
292 }
293
294 let type_obj = ctx
296 .dicts
297 .get(entity, &DictKey::Name(ctx.name_cache.n_font_type))
298 .unwrap();
299 assert_eq!(type_obj.as_i32(), Some(1));
300
301 let cs_obj = ctx
303 .dicts
304 .get(entity, &DictKey::Name(ctx.name_cache.n_char_strings))
305 .unwrap();
306 if let PsValue::Dict(cs_entity) = cs_obj.value {
307 assert!(ctx.dicts.length(cs_entity) > 100);
308 } else {
309 panic!("CharStrings should be a dict");
310 }
311 }
312
313 let name_id = ctx.names.intern(b"NimbusSans-Regular");
315 assert!(
316 ctx.dicts
317 .get(ctx.font_directory, &DictKey::Name(name_id))
318 .is_some()
319 );
320 }
321}