1use std::collections::HashMap;
5use std::sync::{Arc, Mutex};
6
7use otf_pdf_content::{Font, FontLoader};
8use otf_pdf_doc::Store;
9use otf_pdf_syntax::{Dict, ObjId, Object};
10use skrifa::{FontRef, MetadataProvider};
11
12use crate::cmap::CMap;
13use crate::glyphs::{encoding_by_name, glyph_to_unicode, standard_font, standard_width};
14use crate::tables::{
15 STANDARD_ENCODING, SYMBOL_ENCODING, WIN_ANSI_ENCODING, ZAPF_DINGBATS_ENCODING,
16};
17
18fn base_name(dict: &Dict) -> String {
20 let raw = dict.get_name(b"BaseFont").unwrap_or_default();
21 let name = String::from_utf8_lossy(raw).into_owned();
22 match name.split_once('+') {
23 Some((tag, rest)) if tag.len() == 6 && tag.bytes().all(|b| b.is_ascii_uppercase()) => {
24 rest.to_owned()
25 }
26 _ => name,
27 }
28}
29
30fn font_file(store: &Store, descriptor: Option<&Dict>) -> Option<(Vec<u8>, &'static [u8])> {
32 let descriptor = descriptor?;
33 for key in [&b"FontFile2"[..], b"FontFile3", b"FontFile"] {
34 if let Some(Object::Stream(s)) = descriptor.get(key).map(|f| store.resolve(f)) {
35 let data = store.decode_stream(&s).ok()?.data;
36 let kind: &'static [u8] = match key {
37 b"FontFile2" => b"TrueType",
38 b"FontFile" => b"Type1",
39 _ => match s.dict.get_name(b"Subtype") {
40 Some(b"OpenType") => b"OpenType",
41 _ => b"CFF",
42 },
43 };
44 return Some((data, kind));
45 }
46 }
47 None
48}
49
50fn reverse_cmap(data: &[u8]) -> HashMap<u32, char> {
52 let mut out = HashMap::new();
53 if let Ok(font) = FontRef::new(data) {
54 for (ch, gid) in font.charmap().mappings() {
55 if let Some(c) = char::from_u32(ch) {
56 out.entry(u32::from(gid)).or_insert(c);
57 }
58 }
59 }
60 out
61}
62
63#[derive(Debug)]
65struct SimpleFont {
66 name: String,
67 names: Vec<Option<String>>,
68 widths: Vec<f64>,
69 to_unicode: Option<CMap>,
70 program_unicode: HashMap<u32, char>,
72 dingbats: bool,
73 symbolic: bool,
74}
75
76impl Font for SimpleFont {
77 fn codes(&self, bytes: &[u8]) -> Vec<(u32, usize)> {
78 bytes.iter().map(|&b| (u32::from(b), 1)).collect()
79 }
80
81 fn width(&self, code: u32) -> f64 {
82 self.widths.get(code as usize).copied().unwrap_or(0.0)
83 }
84
85 fn unicode(&self, code: u32) -> Option<String> {
86 if let Some(s) = self.to_unicode.as_ref().and_then(|c| c.unicode(code)) {
87 return Some(s);
88 }
89 if let Some(s) = self
90 .names
91 .get(code as usize)
92 .and_then(Option::as_ref)
93 .and_then(|n| glyph_to_unicode(n, self.dingbats))
94 {
95 return Some(s);
96 }
97 if let Some(c) = self.program_unicode.get(&code) {
98 return Some(c.to_string());
99 }
100 let c = char::from_u32(code)?;
103 (!self.symbolic && (' '..='~').contains(&c) || (!self.symbolic && code >= 0xa0))
104 .then(|| c.to_string())
105 }
106
107 fn name(&self) -> &str {
108 &self.name
109 }
110}
111
112#[derive(Debug)]
114struct CompositeFont {
115 name: String,
116 cmap: CMap,
117 to_unicode: Option<CMap>,
118 widths: Vec<(u32, u32, f64)>,
120 default_width: f64,
121 vertical_widths: Vec<(u32, u32, f64)>,
123 default_vertical: f64,
124 program_unicode: HashMap<u32, char>,
126 cid_to_gid: Option<Vec<u16>>,
127 collection: Option<&'static [(u32, u32, u16)]>,
129}
130
131fn collection_unicode(table: &[(u32, u32, u16)], cid: u32) -> Option<char> {
133 let i = table
134 .partition_point(|(start, _, _)| *start <= cid)
135 .checked_sub(1)?;
136 let (start, cp, len) = *table.get(i)?;
137 (cid < start + u32::from(len))
138 .then(|| char::from_u32(cp + (cid - start)))
139 .flatten()
140}
141
142impl Font for CompositeFont {
143 fn codes(&self, bytes: &[u8]) -> Vec<(u32, usize)> {
144 self.cmap.codes(bytes)
145 }
146
147 fn width(&self, code: u32) -> f64 {
148 let Some(cid) = self.cmap.cid(code) else {
149 return self.default_width / 1000.0;
150 };
151 self.widths
152 .iter()
153 .rev()
154 .find(|(lo, hi, _)| *lo <= cid && cid <= *hi)
155 .map_or(self.default_width, |(_, _, w)| *w)
156 / 1000.0
157 }
158
159 fn is_vertical(&self) -> bool {
160 self.cmap.vertical
161 }
162
163 fn vertical_advance(&self, code: u32) -> f64 {
164 let cid = self.cmap.cid(code).unwrap_or(code);
165 self.vertical_widths
166 .iter()
167 .rev()
168 .find(|(lo, hi, _)| *lo <= cid && cid <= *hi)
169 .map_or(self.default_vertical, |(_, _, w)| *w)
170 / 1000.0
171 }
172
173 fn unicode(&self, code: u32) -> Option<String> {
174 if let Some(s) = self.to_unicode.as_ref().and_then(|c| c.unicode(code)) {
175 return Some(s);
176 }
177 if let Some(s) = self.cmap.unicode(code) {
178 return Some(s);
179 }
180 let cid = self.cmap.cid(code)?;
181 if let Some(c) = self.collection.and_then(|t| collection_unicode(t, cid)) {
182 return Some(c.to_string());
183 }
184 let gid = match &self.cid_to_gid {
185 Some(map) => u32::from(*map.get(cid as usize)?),
186 None => cid,
187 };
188 self.program_unicode.get(&gid).map(|c| c.to_string())
189 }
190
191 fn name(&self) -> &str {
192 &self.name
193 }
194}
195
196fn number(o: &Object) -> Option<f64> {
197 o.as_f64().filter(|v| v.is_finite())
198}
199
200fn cid_widths(store: &Store, array: &[Object], triple: bool) -> Vec<(u32, u32, f64)> {
204 let step = if triple { 3 } else { 1 };
205 let mut out = Vec::new();
206 let mut i = 0;
207 while i < array.len() && out.len() < 1 << 20 {
208 let Some(first) = array.get(i).and_then(Object::as_int) else {
209 break;
210 };
211 let first = first.clamp(0, i64::from(u32::MAX)) as u32;
212 match array.get(i + 1).map(|o| store.resolve(o)) {
213 Some(Object::Array(ws)) => {
214 for (k, chunk) in ws.chunks(step).enumerate() {
215 if let Some(w) = chunk.first().and_then(number) {
216 let cid = first.saturating_add(k as u32);
217 out.push((cid, cid, w));
218 }
219 }
220 i += 2;
221 }
222 Some(last) => {
223 let last = last.as_int().unwrap_or(0).clamp(0, i64::from(u32::MAX)) as u32;
224 if let Some(w) = array.get(i + 2).and_then(number) {
225 out.push((first, last, w));
226 }
227 i += 2 + step;
228 }
229 None => break,
230 }
231 }
232 out
233}
234
235fn load_simple(store: &Store, dict: &Dict) -> SimpleFont {
236 let name = base_name(dict);
237 let subtype = dict.get_name(b"Subtype").unwrap_or_default();
238 let descriptor = dict
239 .get(b"FontDescriptor")
240 .and_then(|d| store.resolve_dict(d));
241 let flags = descriptor
242 .as_ref()
243 .and_then(|d| d.get_int(b"Flags"))
244 .unwrap_or(0);
245 let standard = standard_font(&name);
246 let symbol_like = matches!(standard, Some("Symbol" | "ZapfDingbats"));
247 let symbolic = (flags & 4 != 0 && flags & 32 == 0) || symbol_like;
248 let program = font_file(store, descriptor.as_ref());
249
250 let encoding = dict.get(b"Encoding").map(|e| store.resolve(e));
252 let named_base = match &encoding {
253 Some(Object::Name(n)) => encoding_by_name(n.as_bytes()),
254 Some(Object::Dict(d)) => d.get_name(b"BaseEncoding").and_then(encoding_by_name),
255 _ => None,
256 };
257 let builtin = match &program {
258 Some((data, b"Type1")) => crate::type1::builtin_encoding(data),
259 _ => None,
260 };
261 let mut names: Vec<Option<String>> = vec![None; 256];
262 let fallback: &[&str; 256] = match standard {
263 Some("Symbol") => &SYMBOL_ENCODING,
264 Some("ZapfDingbats") => &ZAPF_DINGBATS_ENCODING,
265 _ if subtype == b"TrueType" && !symbolic => &WIN_ANSI_ENCODING,
266 _ => &STANDARD_ENCODING,
267 };
268 for (code, slot) in names.iter_mut().enumerate() {
269 let from_builtin = builtin
270 .as_ref()
271 .filter(|_| named_base.is_none())
272 .and_then(|b| b.get(code).cloned().flatten());
273 let base = named_base.unwrap_or(fallback);
274 *slot = from_builtin.or_else(|| {
275 base.get(code)
276 .filter(|n| !n.is_empty())
277 .map(|n| (*n).to_owned())
278 });
279 if slot.is_none() && named_base.is_none() && builtin.is_none() && !symbolic {
281 *slot = WIN_ANSI_ENCODING
282 .get(code)
283 .filter(|n| !n.is_empty())
284 .map(|n| (*n).to_owned());
285 }
286 }
287 if let Some(Object::Dict(enc)) = &encoding
288 && let Some(diffs) = enc.get_array(b"Differences")
289 {
290 let mut code = 0usize;
291 for item in diffs {
292 match item {
293 Object::Int(n) => code = (*n).clamp(0, 255) as usize,
294 Object::Name(n) => {
295 if let Some(slot) = names.get_mut(code) {
296 *slot = Some(String::from_utf8_lossy(n.as_bytes()).into_owned());
297 }
298 code += 1;
299 }
300 _ => {}
301 }
302 }
303 }
304
305 let font_matrix_scale = if subtype == b"Type3" {
307 dict.get_array(b"FontMatrix")
308 .and_then(|m| m.first())
309 .and_then(number)
310 .unwrap_or(0.001)
311 } else {
312 0.001
313 };
314 let missing = descriptor
315 .as_ref()
316 .and_then(|d| d.get(b"MissingWidth"))
317 .and_then(number)
318 .unwrap_or(0.0);
319 let first = dict.get_int(b"FirstChar").unwrap_or(0).clamp(0, 255) as usize;
320 let declared = dict.get(b"Widths").map(|w| store.resolve(w));
321 let mut widths = vec![missing * font_matrix_scale; 256];
322 match declared.as_ref().and_then(Object::as_array) {
323 Some(ws) => {
324 for (i, w) in ws.iter().enumerate() {
325 if let (Some(slot), Some(w)) =
326 (widths.get_mut(first + i), number(&store.resolve(w)))
327 {
328 *slot = w * font_matrix_scale;
329 }
330 }
331 }
332 None => {
333 let std = standard.unwrap_or("Helvetica");
334 for (code, slot) in widths.iter_mut().enumerate() {
335 if let Some(w) = names
336 .get(code)
337 .and_then(Option::as_ref)
338 .and_then(|n| standard_width(std, n))
339 {
340 *slot = w / 1000.0;
341 } else if *slot == 0.0 {
342 *slot = 0.5;
343 }
344 }
345 }
346 }
347
348 let program_unicode = match &program {
350 Some((data, b"TrueType" | b"OpenType")) => {
351 let by_gid = reverse_cmap(data);
352 let mut out = HashMap::new();
353 if let Ok(font) = FontRef::new(data) {
354 let charmap = font.charmap();
355 for code in 0u32..256 {
356 let gid = charmap.map(code).or_else(|| charmap.map(0xf000 + code));
357 if let Some(c) = gid.and_then(|g| by_gid.get(&u32::from(g)))
358 && u32::from(*c) != code + 0xf000
359 {
360 out.insert(code, *c);
361 }
362 }
363 }
364 out
365 }
366 _ => HashMap::new(),
367 };
368
369 SimpleFont {
370 dingbats: standard == Some("ZapfDingbats"),
371 name,
372 names,
373 widths,
374 to_unicode: to_unicode(store, dict),
375 program_unicode,
376 symbolic,
377 }
378}
379
380fn to_unicode(store: &Store, dict: &Dict) -> Option<CMap> {
381 match dict.get(b"ToUnicode").map(|t| store.resolve(t)) {
382 Some(Object::Stream(s)) => {
383 let cmap = CMap::parse(&store.decode_stream(&s).ok()?.data);
384 cmap.has_unicode().then_some(cmap)
385 }
386 Some(Object::Name(n)) if n.as_bytes().starts_with(b"Identity") => {
388 Some(CMap::predefined(b"UniGB-UCS2-H"))
389 }
390 _ => None,
391 }
392}
393
394fn load_composite(store: &Store, dict: &Dict) -> CompositeFont {
395 let cmap = match dict.get(b"Encoding").map(|e| store.resolve(e)) {
396 Some(Object::Name(n)) => CMap::predefined(n.as_bytes()),
397 Some(Object::Stream(s)) => {
398 let parsed = store
399 .decode_stream(&s)
400 .map(|d| CMap::parse(&d.data))
401 .unwrap_or_default();
402 if parsed.has_codespace() {
403 parsed
404 } else {
405 CMap::predefined(b"Identity-H")
406 }
407 }
408 _ => CMap::predefined(b"Identity-H"),
409 };
410 let descendant = dict
411 .get(b"DescendantFonts")
412 .map(|d| store.resolve(d))
413 .and_then(|d| d.as_array().and_then(|a| a.first().cloned()))
414 .and_then(|d| store.resolve_dict(&d))
415 .unwrap_or_default();
416 let descriptor = descendant
417 .get(b"FontDescriptor")
418 .and_then(|d| store.resolve_dict(d));
419 let widths = descendant
420 .get(b"W")
421 .map(|w| store.resolve(w))
422 .and_then(|w| w.as_array().map(|a| cid_widths(store, a, false)))
423 .unwrap_or_default();
424 let vertical_widths = descendant
425 .get(b"W2")
426 .map(|w| store.resolve(w))
427 .and_then(|w| w.as_array().map(|a| cid_widths(store, a, true)))
428 .unwrap_or_default();
429 let default_vertical = descendant
430 .get_array(b"DW2")
431 .and_then(|a| a.get(1))
432 .and_then(number)
433 .unwrap_or(-1000.0);
434 let cid_to_gid = match descendant.get(b"CIDToGIDMap").map(|m| store.resolve(m)) {
435 Some(Object::Stream(s)) => store.decode_stream(&s).ok().map(|d| {
436 d.data
437 .as_chunks::<2>()
438 .0
439 .iter()
440 .map(|c| {
441 u16::from_be_bytes([
442 c.first().copied().unwrap_or(0),
443 c.get(1).copied().unwrap_or(0),
444 ])
445 })
446 .collect()
447 }),
448 _ => None,
449 };
450 let program_unicode = match font_file(store, descriptor.as_ref()) {
451 Some((data, b"TrueType" | b"OpenType")) => reverse_cmap(&data),
452 _ => HashMap::new(),
453 };
454 let collection = descendant
455 .get(b"CIDSystemInfo")
456 .and_then(|i| store.resolve_dict(i))
457 .and_then(
458 |info| match info.get(b"Ordering").map(|o| store.resolve(o)) {
459 Some(Object::String(s)) => Some(s.bytes),
460 _ => None,
461 },
462 )
463 .and_then(|ordering| match ordering.as_slice() {
464 b"Japan1" => Some(crate::cid_tables::JAPAN1),
465 b"GB1" => Some(crate::cid_tables::GB1),
466 b"CNS1" => Some(crate::cid_tables::CNS1),
467 b"Korea1" | b"KR" => Some(crate::cid_tables::KOREA1),
468 _ => None,
469 });
470 CompositeFont {
471 collection,
472 name: base_name(dict),
473 cmap,
474 to_unicode: to_unicode(store, dict),
475 widths,
476 default_width: descendant.get(b"DW").and_then(number).unwrap_or(1000.0),
477 vertical_widths,
478 default_vertical,
479 program_unicode,
480 cid_to_gid,
481 }
482}
483
484pub fn load_font(store: &Store, dict: &Dict) -> Arc<dyn Font> {
486 match dict.get_name(b"Subtype") {
487 Some(b"Type0") => Arc::new(load_composite(store, dict)),
488 _ => Arc::new(load_simple(store, dict)),
489 }
490}
491
492#[derive(Debug, Default)]
495pub struct FontCache {
496 fonts: Mutex<HashMap<ObjId, Arc<dyn Font>>>,
497}
498
499impl FontCache {
500 pub fn new() -> Self {
502 Self::default()
503 }
504}
505
506impl FontLoader for FontCache {
507 fn load(&self, store: &Store, font: &Object) -> Option<Arc<dyn Font>> {
508 let id = font.as_ref();
509 if let Some(id) = id
510 && let Some(f) = self.fonts.lock().ok().and_then(|m| m.get(&id).cloned())
511 {
512 return Some(f);
513 }
514 let dict = store.resolve_dict(font)?;
515 let loaded = load_font(store, &dict);
516 if let (Some(id), Ok(mut m)) = (id, self.fonts.lock()) {
517 m.insert(id, Arc::clone(&loaded));
518 }
519 Some(loaded)
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526
527 #[test]
528 fn collections() {
529 assert_eq!(
531 collection_unicode(crate::cid_tables::JAPAN1, 843),
532 Some('あ')
533 );
534 assert_eq!(collection_unicode(crate::cid_tables::JAPAN1, 34), Some('A'));
536 assert_eq!(
537 collection_unicode(crate::cid_tables::JAPAN1, u32::MAX),
538 None
539 );
540 assert_eq!(collection_unicode(&[], 5), None);
541 }
542}