1use std::collections::HashMap;
8use std::sync::Arc;
9
10use skrifa::MetadataProvider;
11use stet_fonts::cff_parser::{CffFont, parse_cff};
12use stet_fonts::charstring::{execute_charstring, execute_charstring_mm};
13use stet_fonts::encoding::{MACROMAN_ENCODING, STANDARD_ENCODING, WINANSI_ENCODING};
14use stet_fonts::geometry::PathSegment;
15use stet_fonts::geometry::{Matrix, PsPath};
16use stet_fonts::truetype::{
17 get_glyf_data, get_units_per_em, parse_cmap, parse_cmap_with_info, parse_glyf_to_path,
18};
19use stet_fonts::type1_parser::parse_type1;
20use stet_fonts::type2_charstring::execute_type2_charstring;
21
22use crate::FontProvider;
23use crate::error::PdfError;
24use crate::objects::{PdfDict, PdfObj};
25use crate::resolver::Resolver;
26
27pub enum PdfFont {
29 Type1(Type1PdfFont),
30 TrueType(TrueTypePdfFont),
31 Cff(CffPdfFont),
32 CidTrueType(CidTrueTypePdfFont),
34 CidCff(CidCffPdfFont),
36 Type3(Type3PdfFont),
38}
39
40pub struct Type1PdfFont {
41 pub font: stet_fonts::type1_parser::Type1Font,
42 pub encoding: [Option<String>; 256],
43 pub widths: [f64; 256],
44 pub font_matrix: Matrix,
45 pub weight_vector: Option<Vec<f64>>,
47 pub builtin_fallback: bool,
52 pub per_char_width_scale: bool,
58}
59
60pub struct TrueTypePdfFont {
61 pub data: Vec<u8>,
62 pub encoding: [Option<String>; 256],
63 pub widths: [f64; 256],
64 pub cmap: HashMap<u32, u16>,
65 pub cmap_is_unicode: bool,
69 pub post_name_to_gid: HashMap<String, u16>,
72 pub units_per_em: f64,
73 pub to_unicode: HashMap<u16, u32>,
76 pub identity_gid: bool,
80 pub gid_hex: bool,
84}
85
86pub struct CffPdfFont {
87 pub font: CffFont,
88 pub encoding: [Option<String>; 256],
89 pub widths: [f64; 256],
90 pub font_matrix: Matrix,
91}
92
93pub struct CidTrueTypePdfFont {
95 pub data: Vec<u8>,
96 pub default_width: f64,
98 pub cid_widths: HashMap<u16, f64>,
100 pub cmap: HashMap<u32, u16>,
101 pub units_per_em: f64,
102 pub identity_cid_to_gid: bool,
104 pub substituted: bool,
107 pub cid_to_gid_map: Option<Vec<u16>>,
110 pub to_unicode: HashMap<u16, u32>,
113 pub ordering: Vec<u8>,
115 pub ucs2_encoding: bool,
118 pub code_lengths: [u8; 256],
121 pub code_to_cid: HashMap<u32, u32>,
123 pub wmode: u8,
125 pub dw2: [f64; 2],
128 pub w2: HashMap<u16, [f64; 3]>,
131}
132
133pub struct CidCffPdfFont {
135 pub font: CffFont,
136 pub default_width: f64,
138 pub cid_widths: HashMap<u16, f64>,
140 pub cmap: Option<HashMap<u32, u16>>,
143 pub pdf_cid_to_gid: Option<Vec<u16>>,
146 pub identity_cid_to_gid: bool,
149 pub ordering: Vec<u8>,
151 pub font_matrix: Matrix,
152 pub code_lengths: [u8; 256],
154 pub code_to_cid: HashMap<u32, u32>,
156 pub wmode: u8,
158 pub dw2: [f64; 2],
160 pub w2: HashMap<u16, [f64; 3]>,
162 pub type1_paths: Option<HashMap<u16, PsPath>>,
165}
166
167pub struct Type3PdfFont {
169 pub char_procs: HashMap<u8, Vec<u8>>,
171 pub resources: PdfDict,
173 pub widths: [f64; 256],
174 pub font_matrix: Matrix,
175 pub font_bbox: [f64; 4],
176}
177
178pub type FontCache = HashMap<Vec<u8>, Arc<PdfFont>>;
180
181pub fn resolve_font(
183 resolver: &Resolver,
184 font_ref: &PdfObj,
185 font_provider: Option<&FontProvider>,
186) -> Result<PdfFont, PdfError> {
187 let font_obj = resolver.deref(font_ref)?;
188 let font_dict = font_obj
189 .as_dict()
190 .ok_or(PdfError::Other("Font is not a dict".into()))?;
191
192 let subtype = font_dict.get_name(b"Subtype").unwrap_or(b"Type1");
193 if subtype == b"Type0" {
195 return resolve_type0(resolver, font_dict);
196 }
197
198 if subtype == b"Type3" {
200 return resolve_type3(resolver, font_dict);
201 }
202
203 let first_char = font_dict.get_int(b"FirstChar").unwrap_or(0) as usize;
204 let last_char = font_dict.get_int(b"LastChar").unwrap_or(255) as usize;
205
206 let mut widths = [0.0f64; 256];
209 let mut has_pdf_widths = false;
210 let widths_obj = font_dict.get(b"Widths").and_then(|obj| {
211 if obj.as_array().is_some() {
212 Some(obj.clone())
213 } else {
214 resolver.deref(obj).ok()
215 }
216 });
217 if let Some(PdfObj::Array(w_arr)) = &widths_obj {
218 for (i, obj) in w_arr.iter().enumerate() {
219 let code = first_char + i;
220 if code < 256 {
221 let val = if obj.as_f64().is_some() {
223 obj.as_f64().unwrap()
224 } else if let Ok(resolved) = resolver.deref(obj) {
225 resolved.as_f64().unwrap_or(0.0)
226 } else {
227 0.0
228 };
229 widths[code] = val / 1000.0;
230 }
231 }
232 has_pdf_widths = true;
233
234 let descriptor = get_font_descriptor(font_dict, resolver)?;
237 if let Some(ref desc) = descriptor {
238 let missing_w = desc.get_f64(b"MissingWidth").unwrap_or(0.0) / 1000.0;
239 if missing_w != 0.0 {
240 for (code, width) in widths.iter_mut().enumerate() {
241 if code < first_char || code > last_char {
242 *width = missing_w;
243 }
244 }
245 }
246 }
247 }
248
249 let (encoding, has_valid_encoding, differences, no_base_encoding) =
253 resolve_encoding(font_dict, resolver)?;
254 let has_explicit_encoding = has_valid_encoding;
255
256 let descriptor = get_font_descriptor(font_dict, resolver)?;
258
259 let desc_flags = descriptor
261 .as_ref()
262 .and_then(|d| d.get_int(b"Flags"))
263 .unwrap_or(0) as u32;
264
265 let base_font_name = font_dict
266 .get_name(b"BaseFont")
267 .map(|n| String::from_utf8_lossy(n).to_string())
268 .unwrap_or_default();
269
270 if let Some(ref desc) = descriptor {
273 if desc.get(b"FontFile3").is_some() {
274 match resolve_cff(
276 resolver,
277 &descriptor,
278 encoding.clone(),
279 widths,
280 has_explicit_encoding,
281 has_pdf_widths,
282 &differences,
283 no_base_encoding,
284 ) {
285 Ok(font) => return Ok(font),
286 Err(_) => {
287 if let Some(font) = substitute_font(
288 &base_font_name,
289 encoding.clone(),
290 widths,
291 has_pdf_widths,
292 font_provider,
293 desc_flags,
294 first_char,
295 last_char,
296 ) {
297 return Ok(font);
298 }
299 }
300 }
301 }
302 if desc.get(b"FontFile2").is_some() {
303 match resolve_truetype(resolver, &descriptor, encoding.clone(), widths, font_dict) {
305 Ok(font) => return Ok(font),
306 Err(_) => {
307 if let Some(font) = substitute_font(
308 &base_font_name,
309 encoding.clone(),
310 widths,
311 has_pdf_widths,
312 font_provider,
313 desc_flags,
314 first_char,
315 last_char,
316 ) {
317 return Ok(font);
318 }
319 }
320 }
321 }
322 if desc.get(b"FontFile").is_some() {
323 match resolve_type1(
324 resolver,
325 &descriptor,
326 encoding.clone(),
327 widths,
328 has_explicit_encoding,
329 has_pdf_widths,
330 &differences,
331 no_base_encoding,
332 ) {
333 Ok(font) => return Ok(font),
334 Err(_) => {
335 if let Some(font) = substitute_font(
336 &base_font_name,
337 encoding.clone(),
338 widths,
339 has_pdf_widths,
340 font_provider,
341 desc_flags,
342 first_char,
343 last_char,
344 ) {
345 return Ok(font);
346 }
347 }
348 }
349 }
350 }
351 if let Some(font) = substitute_font(
353 &base_font_name,
354 encoding.clone(),
355 widths,
356 has_pdf_widths,
357 font_provider,
358 desc_flags,
359 first_char,
360 last_char,
361 ) {
362 return Ok(font);
363 }
364 if subtype == b"TrueType"
366 && let Ok(data) = load_system_truetype_font(&base_font_name)
367 {
368 let units_per_em = get_units_per_em(&data) as f64;
369 let (cmap, cmap_is_unicode) = parse_cmap_with_info(&data);
370 let post_name_to_gid = stet_fonts::system_fonts::parse_post_table(&data)
371 .map(|gid_to_name| {
372 gid_to_name
373 .into_iter()
374 .map(|(gid, name)| (name, gid))
375 .collect()
376 })
377 .unwrap_or_default();
378 let to_unicode = if let Some(tu_obj) = font_dict.get(b"ToUnicode") {
379 resolver
380 .stream_data_from_obj(tu_obj)
381 .map(|d| parse_to_unicode(&d))
382 .unwrap_or_default()
383 } else {
384 HashMap::new()
385 };
386 let gid_hex = TrueTypePdfFont::detect_gid_hex(&encoding);
387 return Ok(PdfFont::TrueType(TrueTypePdfFont {
388 data,
389 encoding,
390 widths,
391 cmap,
392 cmap_is_unicode,
393 post_name_to_gid,
394 units_per_em,
395 to_unicode,
396 identity_gid: false, gid_hex,
398 }));
399 }
400
401 match subtype {
403 b"TrueType" => resolve_truetype(resolver, &descriptor, encoding, widths, font_dict),
404 _ => resolve_type1(
405 resolver,
406 &descriptor,
407 encoding,
408 widths,
409 has_explicit_encoding,
410 has_pdf_widths,
411 &differences,
412 no_base_encoding,
413 ),
414 }
415}
416
417fn get_font_descriptor(
419 font_dict: &PdfDict,
420 resolver: &Resolver,
421) -> Result<Option<PdfDict>, PdfError> {
422 if let Some(fd_ref) = font_dict.get(b"FontDescriptor") {
423 let fd_obj = resolver.deref(fd_ref)?;
424 if let Some(d) = fd_obj.as_dict() {
425 return Ok(Some(d.clone()));
426 }
427 }
428 Ok(None)
429}
430
431fn resolve_encoding(
445 font_dict: &PdfDict,
446 resolver: &Resolver,
447) -> Result<([Option<String>; 256], bool, Vec<(usize, String)>, bool), PdfError> {
448 let mut encoding: [Option<String>; 256] = std::array::from_fn(|_| None);
449 let mut differences: Vec<(usize, String)> = Vec::new();
450
451 let base_font = font_dict.get_name(b"BaseFont").unwrap_or(b"");
455 let clean_base = if base_font.len() > 7 && base_font.get(6) == Some(&b'+') {
457 &base_font[7..]
458 } else {
459 base_font
460 };
461 let is_symbol_font = clean_base == b"ZapfDingbats" || clean_base == b"Symbol";
462 let mut base_table: &[&str; 256] = if clean_base == b"ZapfDingbats" {
463 &stet_fonts::encoding::ZAPFDINGBATS_ENCODING
464 } else if clean_base == b"Symbol" {
465 &stet_fonts::encoding::SYMBOL_ENCODING
466 } else {
467 &STANDARD_ENCODING
468 };
469
470 let mut has_valid_encoding = is_symbol_font; if let Some(enc_obj) = font_dict.get(b"Encoding") {
472 let enc_resolved = resolver.deref(enc_obj)?;
473 match &enc_resolved {
474 PdfObj::Name(name) => {
475 if !is_symbol_font {
478 if let Some(table) = encoding_table_by_name(name) {
479 base_table = table;
480 has_valid_encoding = true;
481 }
482 }
483 }
484 PdfObj::Dict(enc_dict) => {
485 has_valid_encoding = true;
488 let mut has_base_encoding = false;
489 if !is_symbol_font {
490 if let Some(base_name) = enc_dict.get_name(b"BaseEncoding") {
491 if let Some(table) = encoding_table_by_name(base_name) {
492 base_table = table;
493 has_base_encoding = true;
494 }
495 }
496 }
497 for (i, &name) in base_table.iter().enumerate() {
498 if name != ".notdef" {
499 encoding[i] = Some(name.to_string());
500 }
501 }
502 if let Some(diffs_obj) = enc_dict.get(b"Differences") {
504 let diffs_resolved = resolver.deref(diffs_obj)?;
505 if let Some(diffs) = diffs_resolved.as_array() {
506 let mut code = 0usize;
507 for obj in diffs {
508 let obj = resolver.deref(obj).unwrap_or(obj.clone());
509 match &obj {
510 PdfObj::Int(n) => code = *n as usize,
511 PdfObj::Name(name) => {
512 if code < 256 {
513 let name_str = String::from_utf8_lossy(name).to_string();
514 encoding[code] = Some(name_str.clone());
515 if !has_base_encoding && !is_symbol_font {
519 differences.push((code, name_str));
520 }
521 code += 1;
522 }
523 }
524 _ => {}
525 }
526 }
527 }
528 }
529 return Ok((
533 encoding,
534 has_valid_encoding,
535 differences,
536 !has_base_encoding && !is_symbol_font,
537 ));
538 }
539 _ => {}
540 }
541 }
542
543 for (i, &name) in base_table.iter().enumerate() {
545 if name != ".notdef" {
546 encoding[i] = Some(name.to_string());
547 }
548 }
549
550 Ok((encoding, has_valid_encoding, differences, false))
551}
552
553fn encoding_table_by_name(name: &[u8]) -> Option<&'static [&'static str; 256]> {
554 match name {
555 b"WinAnsiEncoding" => Some(&WINANSI_ENCODING),
556 b"MacRomanEncoding" => Some(&MACROMAN_ENCODING),
557 b"StandardEncoding" => Some(&STANDARD_ENCODING),
558 _ => None,
559 }
560}
561
562pub fn fallback_font(font_provider: Option<&FontProvider>) -> Option<PdfFont> {
564 let encoding: [Option<String>; 256] = std::array::from_fn(|i| {
565 WINANSI_ENCODING.get(i).and_then(|&s| {
566 if s.is_empty() {
567 None
568 } else {
569 Some(s.to_string())
570 }
571 })
572 });
573 let widths = super::standard_fonts::standard_font_widths(b"Helvetica").unwrap_or([0.0f64; 256]);
574 substitute_font(
575 "Helvetica",
576 encoding,
577 widths,
578 false,
579 font_provider,
580 0,
581 0,
582 255,
583 )
584}
585
586fn load_predefined_cmap(name: &[u8]) -> Option<Vec<u8>> {
595 let name_str = std::str::from_utf8(name).ok()?;
596
597 if let Ok(dir) = std::env::var("STET_CMAP_DIR") {
599 let path = format!("{}/{}", dir, name_str);
600 if let Ok(data) = std::fs::read(&path) {
601 return Some(data);
602 }
603 }
604
605 if let Some(home) = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")) {
607 let path = std::path::Path::new(&home)
608 .join(".local/share/stet/CMap")
609 .join(name_str);
610 if let Ok(data) = std::fs::read(&path) {
611 return Some(data);
612 }
613 }
614
615 let poppler_dirs = [
618 "/usr/share/poppler/cMap",
619 "/usr/local/share/poppler/cMap",
620 "/opt/homebrew/share/poppler/cMap", "/usr/local/opt/poppler-data/share/poppler/cMap", ];
623 let collections = [
624 "Adobe-GB1",
625 "Adobe-CNS1",
626 "Adobe-Japan1",
627 "Adobe-Japan2",
628 "Adobe-Korea1",
629 "Adobe-KR",
630 ];
631 for base in &poppler_dirs {
632 for collection in &collections {
633 let path = format!("{}/{}/{}", base, collection, name_str);
634 if let Ok(data) = std::fs::read(&path) {
635 return Some(data);
636 }
637 }
638 }
639
640 let gs_dirs = [
642 "/var/lib/ghostscript/CMap",
643 "/usr/share/ghostscript/Resource/CMap",
644 "/usr/local/share/ghostscript/Resource/CMap",
645 ];
646 for dir in &gs_dirs {
647 let path = format!("{}/{}", dir, name_str);
648 if let Ok(data) = std::fs::read(&path) {
649 return Some(data);
650 }
651 }
652
653 None
654}
655
656fn substitute_font(
657 base_font: &str,
658 encoding: [Option<String>; 256],
659 widths: [f64; 256],
660 has_pdf_widths: bool,
661 font_provider: Option<&FontProvider>,
662 descriptor_flags: u32,
663 first_char: usize,
664 last_char: usize,
665) -> Option<PdfFont> {
666 use stet_fonts::FONT_SUBSTITUTIONS;
667
668 let mut clean_name: &str = base_font;
670 if clean_name.len() > 7 && clean_name.as_bytes().get(6) == Some(&b'+') {
671 clean_name = &clean_name[7..];
672 }
673 if let Some(star_pos) = clean_name.rfind('*') {
675 clean_name = &clean_name[..star_pos];
676 }
677
678 let urw_name = FONT_SUBSTITUTIONS
680 .iter()
681 .find(|&&(ps, _)| ps == clean_name)
682 .map(|&(_, urw)| urw)
683 .or_else(|| fuzzy_font_match(clean_name));
684
685 let font_file_name = urw_name.unwrap_or(clean_name);
686
687 let font_data = if let Some(provider) = font_provider {
689 provider(font_file_name)
690 } else {
691 None
692 };
693
694 let font_data = font_data.or_else(|| {
696 let cache = stet_fonts::system_fonts::get_system_font_cache();
697 let path = cache.get_font_path(font_file_name)?;
698 read_font_file(path, font_file_name).ok()
699 });
700
701 let font_data = font_data.or_else(|| embedded_font(font_file_name));
703
704 let font_data = font_data.or_else(|| {
707 let lower = clean_name.to_ascii_lowercase();
708 let is_bold = lower.contains("bold")
709 || lower.contains("demi")
710 || lower.contains("black")
711 || lower.contains("heavy");
712 let is_italic = lower.contains("italic") || lower.contains("oblique");
713 let is_serif = descriptor_flags & 2 != 0; let default_name = if is_serif {
715 match (is_bold, is_italic) {
716 (true, true) => "NimbusRoman-BoldItalic",
717 (true, false) => "NimbusRoman-Bold",
718 (false, true) => "NimbusRoman-Italic",
719 (false, false) => "NimbusRoman-Regular",
720 }
721 } else {
722 match (is_bold, is_italic) {
723 (true, true) => "NimbusSans-BoldItalic",
724 (true, false) => "NimbusSans-Bold",
725 (false, true) => "NimbusSans-Italic",
726 (false, false) => "NimbusSans-Regular",
727 }
728 };
729 if let Some(provider) = font_provider {
730 if let Some(data) = provider(default_name) {
731 return Some(data);
732 }
733 }
734 embedded_font(default_name)
735 })?;
736
737 let font = parse_type1(&font_data).ok()?;
738 let fm = font.font_matrix;
739 let mut font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
740 let mut per_char_scale = false;
741
742 let widths = if !has_pdf_widths {
747 let mut derived = [0.0f64; 256];
748 let notdef_width = font
750 .charstrings
751 .get(".notdef")
752 .and_then(|cs| execute_charstring(cs, &font.subrs, font.len_iv, false).ok())
753 .map(|r| r.width_x * fm[0])
754 .unwrap_or(0.0);
755 for code in 0..256usize {
756 let glyph_name = encoding[code].as_deref().unwrap_or(".notdef");
757 if let Some(cs) = font.charstrings.get(glyph_name) {
758 if let Ok(result) = execute_charstring(cs, &font.subrs, font.len_iv, false) {
759 derived[code] = result.width_x * fm[0];
761 }
762 } else {
763 derived[code] = notdef_width;
765 }
766 }
767 derived
768 } else {
769 let is_symbol_font = {
778 let lower = clean_name.to_ascii_lowercase();
779 lower.contains("wingding") || lower.contains("webding") || lower.contains("dingbat")
780 };
781 if !is_symbol_font {
782 let mut pdf_sum = 0.0;
783 let mut sub_sum = 0.0;
784 let mut count = 0;
785 for code in first_char..=last_char.min(255) {
789 let pdf_w = widths[code];
790 if pdf_w <= 0.0 {
791 continue;
792 }
793 let glyph_name = match encoding[code].as_deref() {
794 Some(n) if n != ".notdef" && n != "space" => n,
795 _ => continue,
796 };
797 if let Some(cs) = font.charstrings.get(glyph_name)
798 && let Ok(result) = execute_charstring(cs, &font.subrs, font.len_iv, false)
799 {
800 let sub_w = result.width_x * fm[0];
801 if sub_w > 0.0 {
802 let ratio = pdf_w / sub_w;
803 if ratio > 0.5 && ratio < 2.0 {
805 pdf_sum += pdf_w;
806 sub_sum += sub_w;
807 count += 1;
808 }
809 }
810 }
811 }
812 if count >= 3 && sub_sum > 0.0 && !is_standard_14_alias(clean_name) {
813 let ratio = pdf_sum / sub_sum;
826 if (ratio - 1.0).abs() > 0.03 {
827 font_matrix.a *= ratio;
828 per_char_scale = true;
831 }
832 }
833 }
834 widths
835 };
836
837 let weight_vector = font.weight_vector.clone();
839
840 Some(PdfFont::Type1(Type1PdfFont {
841 font,
842 encoding,
843 widths,
844 font_matrix,
845 builtin_fallback: false,
846 weight_vector,
847 per_char_width_scale: per_char_scale,
848 }))
849}
850
851fn is_standard_14_alias(name: &str) -> bool {
863 let normalized = name.replace(',', "-");
864 matches!(
865 normalized.as_str(),
866 "Times-Roman"
867 | "Times-Bold"
868 | "Times-Italic"
869 | "Times-BoldItalic"
870 | "Helvetica"
871 | "Helvetica-Bold"
872 | "Helvetica-Oblique"
873 | "Helvetica-BoldOblique"
874 | "Courier"
875 | "Courier-Bold"
876 | "Courier-Oblique"
877 | "Courier-BoldOblique"
878 | "Symbol"
879 | "ZapfDingbats"
880 )
881}
882
883fn fuzzy_font_match(name: &str) -> Option<&'static str> {
886 let lower = name.to_ascii_lowercase();
887 let is_bold = lower.contains("bold") || lower.contains("demi");
888 let is_italic = lower.contains("italic") || lower.contains("oblique");
889
890 let family = strip_style_suffix(&lower);
896
897 if family.contains("times") || family.contains("serif") {
898 return Some(match (is_bold, is_italic) {
899 (true, true) => "NimbusRoman-BoldItalic",
900 (true, false) => "NimbusRoman-Bold",
901 (false, true) => "NimbusRoman-Italic",
902 (false, false) => "NimbusRoman-Regular",
903 });
904 }
905 if family.contains("helvetica")
906 || family.contains("arial")
907 || family.contains("sans")
908 || family.contains("calibri")
909 || family.contains("verdana")
910 || family.contains("tahoma")
911 {
912 return Some(match (is_bold, is_italic) {
913 (true, true) => "NimbusSans-BoldItalic",
914 (true, false) => "NimbusSans-Bold",
915 (false, true) => "NimbusSans-Italic",
916 (false, false) => "NimbusSans-Regular",
917 });
918 }
919 if family.contains("courier") || family.contains("mono") {
920 return Some(match (is_bold, is_italic) {
921 (true, true) => "NimbusMonoPS-BoldItalic",
922 (true, false) => "NimbusMonoPS-Bold",
923 (false, true) => "NimbusMonoPS-Italic",
924 (false, false) => "NimbusMonoPS-Regular",
925 });
926 }
927 None
928}
929
930fn strip_style_suffix(lower: &str) -> &str {
934 const SUFFIXES: &[&str] = &[
936 "-roman", " roman", "-regular", " regular", "-medium", " medium", "-book", " book",
937 "-normal", " normal", "-light", " light",
938 ];
939 for suffix in SUFFIXES {
940 if let Some(prefix) = lower.strip_suffix(suffix) {
941 return prefix;
942 }
943 }
944 lower
945}
946
947const CID_FONT_SUBSTITUTIONS: &[(&str, &str)] = &[
949 ("ArialUnicodeMS", "DejaVuSans"),
950 ("Arial", "LiberationSans"),
951 ("Arial,Bold", "LiberationSans-Bold"),
952 ("Arial,BoldItalic", "LiberationSans-BoldItalic"),
953 ("Arial,Italic", "LiberationSans-Italic"),
954 ("Arial-BoldMT", "LiberationSans-Bold"),
955 ("Arial-BoldItalicMT", "LiberationSans-BoldItalic"),
956 ("Arial-ItalicMT", "LiberationSans-Italic"),
957 ("Arial-ItalicMT,Italic", "LiberationSans-Italic"),
958 ("ArialMT", "LiberationSans"),
959 ("ArialBlack", "LiberationSans-Bold"),
962 ("ArialBlack,Bold", "LiberationSans-Bold"),
963 ("ArialBlack,Italic", "LiberationSans-BoldItalic"),
964 ("ArialBlack,BoldItalic", "LiberationSans-BoldItalic"),
965 ("Arial-BlackMT", "LiberationSans-Bold"),
966 ("CourierNew", "LiberationMono"),
967 ("CourierNew,Bold", "LiberationMono-Bold"),
968 ("CourierNew,BoldItalic", "LiberationMono-BoldItalic"),
969 ("CourierNew,Italic", "LiberationMono-Italic"),
970 ("CourierNewPS-BoldMT", "LiberationMono-Bold"),
971 ("CourierNewPS-BoldItalicMT", "LiberationMono-BoldItalic"),
972 ("CourierNewPS-ItalicMT", "LiberationMono-Italic"),
973 ("CourierNewPSMT", "LiberationMono"),
974 ("LucidaConsole", "LiberationMono"),
975 ("LucidaConsole,Bold", "LiberationMono-Bold"),
976 ("Calibri", "LiberationSans"),
977 ("Calibri,Bold", "LiberationSans-Bold"),
978 ("Calibri,BoldItalic", "LiberationSans-BoldItalic"),
979 ("Calibri,Italic", "LiberationSans-Italic"),
980 ("CenturyGothic", "LiberationSans"),
981 ("CenturyGothic,Bold", "LiberationSans-Bold"),
982 ("CenturyGothic,BoldItalic", "LiberationSans-BoldItalic"),
983 ("CenturyGothic,Italic", "LiberationSans-Italic"),
984 ("TimesNewRoman", "LiberationSerif"),
985 ("TimesNewRoman,Bold", "LiberationSerif-Bold"),
986 ("TimesNewRoman,BoldItalic", "LiberationSerif-BoldItalic"),
987 ("TimesNewRoman,Italic", "LiberationSerif-Italic"),
988 ("TimesNewRomanPS-BoldMT", "LiberationSerif-Bold"),
989 ("TimesNewRomanPS-BoldItalicMT", "LiberationSerif-BoldItalic"),
990 ("TimesNewRomanPS-ItalicMT", "LiberationSerif-Italic"),
991 ("TimesNewRomanPSMT", "LiberationSerif"),
992 ("HeiseiMin-W3", "NotoSansCJKjp-Regular"),
994 ("HeiseiKakuGo-W5", "NotoSansCJKjp-Regular"),
995 ("KozMinPr6N-Regular", "NotoSansCJKjp-Regular"),
996 ("KozGoPr6N-Medium", "NotoSansCJKjp-Regular"),
997 ("MS-Gothic", "NotoSansCJKjp-Regular"),
998 ("MS-Gothic,Bold", "NotoSansCJKjp-Bold"),
999 ("MS-Gothic,Italic", "NotoSansCJKjp-Regular"),
1000 ("MS-Gothic,BoldItalic", "NotoSansCJKjp-Bold"),
1001 ("MS-PGothic", "NotoSansCJKjp-Regular"),
1002 ("MS-PGothic,Bold", "NotoSansCJKjp-Bold"),
1003 ("MS-PGothic,Italic", "NotoSansCJKjp-Regular"),
1004 ("MS-PGothic,BoldItalic", "NotoSansCJKjp-Bold"),
1005 ("MS-Mincho", "NotoSansCJKjp-Regular"),
1006 ("MS-Mincho,Bold", "NotoSansCJKjp-Bold"),
1007 ("MS-Mincho,Italic", "NotoSansCJKjp-Regular"),
1008 ("MS-Mincho,BoldItalic", "NotoSansCJKjp-Bold"),
1009 ("MS-PMincho", "NotoSansCJKjp-Regular"),
1010 ("MS-PMincho,Bold", "NotoSansCJKjp-Bold"),
1011 ("MS-PMincho,Italic", "NotoSansCJKjp-Regular"),
1012 ("MS-PMincho,BoldItalic", "NotoSansCJKjp-Bold"),
1013 ("MSGothic", "NotoSansCJKjp-Regular"),
1014 ("MSPGothic", "NotoSansCJKjp-Regular"),
1015 ("MSMincho", "NotoSansCJKjp-Regular"),
1016 ("MSPMincho", "NotoSansCJKjp-Regular"),
1017 ("Batang", "NotoSansCJKkr-Regular"),
1019 ("BatangChe", "NotoSansCJKkr-Regular"),
1020 ("Dotum", "NotoSansCJKkr-Regular"),
1021 ("DotumChe", "NotoSansCJKkr-Regular"),
1022 ("Gulim", "NotoSansCJKkr-Regular"),
1023 ("GulimChe", "NotoSansCJKkr-Regular"),
1024 ("STSongStd-Light", "NotoSerifCJKjp-Regular"),
1027 ("STSong-Light", "NotoSerifCJKjp-Regular"),
1028 ("AdobeSongStd-Light", "NotoSerifCJKjp-Regular"),
1029 ("STFangsong-Light", "NotoSerifCJKjp-Regular"),
1030 ("STHeiti-Regular", "NotoSansCJKjp-Regular"),
1031 ("STKaiti-Regular", "NotoSansCJKjp-Regular"),
1032 ("SimSun", "NotoSerifCJKjp-Regular"),
1033 ("SimSunBold", "NotoSerifCJKjp-Bold"),
1034 ("SimHei", "NotoSansCJKjp-Regular"),
1035 ("FangSong", "NotoSerifCJKjp-Regular"),
1036 ("KaiTi", "NotoSansCJKjp-Regular"),
1037 ("MSungStd-Light", "NotoSerifCJKjp-Regular"),
1039 ("MSung-Light", "NotoSerifCJKjp-Regular"),
1040 ("AdobeMingStd-Light", "NotoSerifCJKjp-Regular"),
1041 ("MHei-Medium", "NotoSansCJKjp-Regular"),
1042 ("MingLiU", "NotoSerifCJKjp-Regular"),
1043 ("PMingLiU", "NotoSerifCJKjp-Regular"),
1044];
1045
1046fn cjk_fullwidth_alternative(unicode: u32) -> Option<u32> {
1052 match unicode {
1053 0x00B7 => Some(0x30FB),
1055 _ => None,
1056 }
1057}
1058
1059fn is_cff_cid_keyed(otf_data: &[u8]) -> bool {
1061 use stet_fonts::truetype::find_table;
1062 let Some((cff_off, cff_len)) = find_table(otf_data, b"CFF ") else {
1063 return false;
1064 };
1065 let cff_data = &otf_data[cff_off..cff_off + cff_len];
1066 match parse_cff(cff_data) {
1067 Ok(fonts) => fonts.first().map_or(false, |f| f.is_cid),
1068 Err(_) => false,
1069 }
1070}
1071
1072fn create_cid_cff_from_otf(
1075 otf_data: &[u8],
1076 default_width: f64,
1077 cid_widths: HashMap<u16, f64>,
1078 ordering: &[u8],
1079 pdf_cid_to_gid: Option<Vec<u16>>,
1080 identity_cid_to_gid: bool,
1081 code_lengths: [u8; 256],
1082 code_to_cid: HashMap<u32, u32>,
1083 wmode: u8,
1084 dw2: [f64; 2],
1085 w2: HashMap<u16, [f64; 3]>,
1086) -> Result<PdfFont, PdfError> {
1087 use stet_fonts::truetype::find_table;
1088
1089 let (cff_off, cff_len) = find_table(otf_data, b"CFF ")
1091 .ok_or(PdfError::Other("OpenType font has no CFF table".into()))?;
1092 let cff_data = &otf_data[cff_off..cff_off + cff_len];
1093 let fonts =
1094 parse_cff(cff_data).map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
1095 let font = fonts
1096 .into_iter()
1097 .next()
1098 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
1099 let fm = font.font_matrix;
1100 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
1101
1102 let otf_cmap = parse_cmap(otf_data);
1104 let cmap = if otf_cmap.is_empty() {
1105 None
1106 } else {
1107 Some(otf_cmap)
1108 };
1109
1110 Ok(PdfFont::CidCff(CidCffPdfFont {
1111 font,
1112 default_width,
1113 cid_widths,
1114 font_matrix,
1115 cmap,
1116 pdf_cid_to_gid,
1117 identity_cid_to_gid,
1118 ordering: ordering.to_vec(),
1119 code_lengths,
1120 code_to_cid,
1121 wmode,
1122 dw2,
1123 w2,
1124 type1_paths: None,
1125 }))
1126}
1127
1128fn is_raw_cff(data: &[u8]) -> bool {
1131 data.len() > 4 && data[0] == 1 && data[1] == 0 && data[2] >= 4 && (1..=4).contains(&data[3])
1132}
1133
1134fn create_cid_cff_from_raw(
1136 cff_data: &[u8],
1137 default_width: f64,
1138 cid_widths: HashMap<u16, f64>,
1139 ordering: &[u8],
1140 pdf_cid_to_gid: Option<Vec<u16>>,
1141 identity_cid_to_gid: bool,
1142 code_lengths: [u8; 256],
1143 code_to_cid: HashMap<u32, u32>,
1144 wmode: u8,
1145 dw2: [f64; 2],
1146 w2: HashMap<u16, [f64; 3]>,
1147) -> Result<PdfFont, PdfError> {
1148 let fonts =
1149 parse_cff(cff_data).map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
1150 let font = fonts
1151 .into_iter()
1152 .next()
1153 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
1154 let fm = font.font_matrix;
1155 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
1156
1157 Ok(PdfFont::CidCff(CidCffPdfFont {
1158 font,
1159 default_width,
1160 cid_widths,
1161 font_matrix,
1162 cmap: None, pdf_cid_to_gid,
1164 identity_cid_to_gid,
1165 ordering: ordering.to_vec(),
1166 code_lengths,
1167 code_to_cid,
1168 wmode,
1169 dw2,
1170 w2,
1171 type1_paths: None,
1172 }))
1173}
1174
1175fn create_cid_from_ps_cidfont(
1181 font_data: &[u8],
1182 default_width: f64,
1183 cid_widths: HashMap<u16, f64>,
1184 code_lengths: [u8; 256],
1185 code_to_cid: HashMap<u32, u32>,
1186 wmode: u8,
1187 dw2: [f64; 2],
1188 w2: HashMap<u16, [f64; 3]>,
1189) -> Result<PdfFont, PdfError> {
1190 let text = String::from_utf8_lossy(font_data);
1191
1192 let get_int = |key: &str| -> Option<usize> {
1194 let pat = format!("/{key}");
1195 let idx = text.find(&pat)?;
1196 let rest = &text[idx + pat.len()..];
1197 rest.split_whitespace().next()?.parse().ok()
1198 };
1199
1200 let cid_count = get_int("CIDCount").unwrap_or(0);
1201 let fd_bytes = get_int("FDBytes").unwrap_or(0);
1202 let gd_bytes = get_int("GDBytes").unwrap_or(4);
1203 let subr_map_offset = get_int("SubrMapOffset").unwrap_or(0);
1204 let sd_bytes = get_int("SDBytes").unwrap_or(4);
1205 let subr_count = get_int("SubrCount").unwrap_or(0);
1206 let len_iv = get_int("lenIV").unwrap_or(4) as u16;
1207
1208 let font_matrix = if let Some(fm_idx) = text.find("/FontMatrix") {
1210 let rest = &text[fm_idx..];
1211 if let Some(start) = rest.find('[') {
1212 let end_bracket = rest[start..].find(']').unwrap_or(50) + start;
1213 let vals: Vec<f64> = rest[start + 1..end_bracket]
1214 .split_whitespace()
1215 .filter_map(|s| s.parse().ok())
1216 .collect();
1217 if vals.len() == 6 {
1218 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
1219 } else {
1220 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
1221 }
1222 } else {
1223 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
1224 }
1225 } else {
1226 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
1227 };
1228
1229 let binary_data = {
1237 let sd_marker = b"StartData";
1238 let pos = font_data
1239 .windows(sd_marker.len())
1240 .position(|w| w == sd_marker)
1241 .ok_or(PdfError::Other("PS CIDFont: no StartData found".into()))?;
1242 let after = &font_data[pos + sd_marker.len()..];
1243 let skip = after
1245 .iter()
1246 .position(|&b| !matches!(b, b' ' | b'\t' | b'\r' | b'\n'))
1247 .unwrap_or(0);
1248 &font_data[pos + sd_marker.len() + skip..]
1249 };
1250
1251 let entry_size = fd_bytes + gd_bytes;
1253 let cid_map_size = cid_count * entry_size;
1254 if binary_data.len() < cid_map_size {
1255 return Err(PdfError::Other(
1256 "PS CIDFont: binary data too short for CID map".into(),
1257 ));
1258 }
1259
1260 let read_be = |data: &[u8], off: usize, n: usize| -> usize {
1262 let mut val = 0usize;
1263 for i in 0..n {
1264 if off + i < data.len() {
1265 val = (val << 8) | data[off + i] as usize;
1266 }
1267 }
1268 val
1269 };
1270
1271 let mut cid_offsets: Vec<usize> = Vec::with_capacity(cid_count + 1);
1272 for c in 0..cid_count {
1273 let entry_off = c * entry_size + fd_bytes;
1274 let offset = read_be(binary_data, entry_off, gd_bytes);
1275 cid_offsets.push(offset);
1276 }
1277 cid_offsets.push(subr_map_offset);
1279
1280 let mut subrs: Vec<Vec<u8>> = Vec::with_capacity(subr_count);
1282 if subr_count > 0 && subr_map_offset + (subr_count + 1) * sd_bytes <= binary_data.len() {
1283 let mut sub_offsets: Vec<usize> = Vec::with_capacity(subr_count + 1);
1284 for i in 0..=subr_count {
1285 let off = read_be(binary_data, subr_map_offset + i * sd_bytes, sd_bytes);
1286 sub_offsets.push(off);
1287 }
1288 for i in 0..subr_count {
1289 let start = sub_offsets[i];
1290 let end = sub_offsets[i + 1];
1291 if start < end && end <= binary_data.len() {
1292 subrs.push(binary_data[start..end].to_vec());
1293 } else {
1294 subrs.push(Vec::new());
1295 }
1296 }
1297 }
1298
1299 let mut paths = HashMap::new();
1301 for &cid in cid_widths.keys() {
1302 let c = cid as usize;
1303 if c >= cid_count {
1304 continue;
1305 }
1306 let cs_start = cid_offsets[c];
1307 let cs_end = cid_offsets[c + 1];
1308 if cs_start >= cs_end || cs_end > binary_data.len() {
1309 continue;
1310 }
1311 let charstring = &binary_data[cs_start..cs_end];
1312 if let Ok(result) = execute_charstring(charstring, &subrs, len_iv.into(), false) {
1313 let path = result.path.transform(&font_matrix);
1314 paths.insert(cid, path);
1315 }
1316 }
1317
1318 let dummy_cff = stet_fonts::cff_parser::CffFont {
1320 name: String::new(),
1321 font_matrix: [
1322 font_matrix.a,
1323 font_matrix.b,
1324 font_matrix.c,
1325 font_matrix.d,
1326 font_matrix.tx,
1327 font_matrix.ty,
1328 ],
1329 font_bbox: [0.0; 4],
1330 char_strings: Vec::new(),
1331 global_subrs: Vec::new(),
1332 local_subrs: Vec::new(),
1333 charset: Vec::new(),
1334 encoding: Vec::new(),
1335 default_width_x: 0.0,
1336 nominal_width_x: 0.0,
1337 is_cid: true,
1338 fd_array: Vec::new(),
1339 fd_select: Vec::new(),
1340 ros: None,
1341 cid_to_gid: Vec::new(),
1342 };
1343
1344 Ok(PdfFont::CidCff(CidCffPdfFont {
1345 font: dummy_cff,
1346 default_width,
1347 cid_widths,
1348 font_matrix,
1349 cmap: None,
1350 pdf_cid_to_gid: None,
1351 identity_cid_to_gid: true,
1352 ordering: Vec::new(),
1353 code_lengths,
1354 code_to_cid,
1355 wmode,
1356 dw2,
1357 w2,
1358 type1_paths: Some(paths),
1359 }))
1360}
1361
1362fn create_cid_from_type1(
1367 font_data: &[u8],
1368 default_width: f64,
1369 cid_widths: HashMap<u16, f64>,
1370 _to_unicode: &HashMap<u16, u32>,
1371 code_lengths: [u8; 256],
1372 code_to_cid: HashMap<u32, u32>,
1373 wmode: u8,
1374 dw2: [f64; 2],
1375 w2: HashMap<u16, [f64; 3]>,
1376) -> Result<PdfFont, PdfError> {
1377 let font =
1378 parse_type1(font_data).map_err(|e| PdfError::Other(format!("Type1 parse error: {e}")))?;
1379 let fm = font.font_matrix;
1380 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
1381
1382 let mut paths = HashMap::new();
1386 for (&cid, _) in &cid_widths {
1387 let glyph_name = if (cid as usize) < font.encoding.len() {
1388 font.encoding[cid as usize].as_str()
1389 } else {
1390 ".notdef"
1391 };
1392 if let Some(cs) = font.charstrings.get(glyph_name) {
1393 if let Ok(result) = execute_charstring(cs, &font.subrs, font.len_iv, false) {
1394 let path = result.path.transform(&font_matrix);
1395 paths.insert(cid, path);
1396 }
1397 }
1398 }
1399 for (code, name) in font.encoding.iter().enumerate() {
1401 let cid = code as u16;
1402 if paths.contains_key(&cid) {
1403 continue;
1404 }
1405 {
1406 let name = name.as_str();
1407 if name != ".notdef" {
1408 if let Some(cs) = font.charstrings.get(name) {
1409 if let Ok(result) = execute_charstring(cs, &font.subrs, font.len_iv, false) {
1410 let path = result.path.transform(&font_matrix);
1411 paths.insert(cid, path);
1412 }
1413 }
1414 }
1415 }
1416 }
1417
1418 let dummy_cff = stet_fonts::cff_parser::CffFont {
1420 name: font.font_name.clone(),
1421 font_matrix: fm,
1422 font_bbox: [0.0; 4],
1423 char_strings: Vec::new(),
1424 global_subrs: Vec::new(),
1425 local_subrs: Vec::new(),
1426 charset: Vec::new(),
1427 encoding: Vec::new(),
1428 default_width_x: 0.0,
1429 nominal_width_x: 0.0,
1430 is_cid: false,
1431 fd_array: Vec::new(),
1432 fd_select: Vec::new(),
1433 ros: None,
1434 cid_to_gid: Vec::new(),
1435 };
1436
1437 Ok(PdfFont::CidCff(CidCffPdfFont {
1438 font: dummy_cff,
1439 default_width,
1440 cid_widths,
1441 font_matrix,
1442 cmap: None,
1443 pdf_cid_to_gid: None,
1444 identity_cid_to_gid: true,
1445 ordering: Vec::new(),
1446 code_lengths,
1447 code_to_cid,
1448 wmode,
1449 dw2,
1450 w2,
1451 type1_paths: Some(paths),
1452 }))
1453}
1454
1455fn sanitize_index_to_loc_format(font_data: &mut [u8]) {
1462 use stet_fonts::truetype::{find_table, read_i16, read_u16};
1463
1464 let head = find_table(font_data, b"head");
1465 let loca = find_table(font_data, b"loca");
1466 let maxp = find_table(font_data, b"maxp");
1467 let (head_off, _) = match head {
1468 Some(h) => h,
1469 None => return,
1470 };
1471 if head_off + 52 > font_data.len() {
1472 return;
1473 }
1474 let format = read_i16(font_data, head_off + 50);
1475 if format == 0 || format == 1 {
1476 return; }
1478 let correct = if let (Some((_, loca_len)), Some((maxp_off, _))) = (loca, maxp) {
1480 if maxp_off + 6 <= font_data.len() {
1481 let num_glyphs = read_u16(font_data, maxp_off + 4) as usize;
1482 if loca_len == (num_glyphs + 1) * 4 {
1485 1i16 } else {
1487 0i16 }
1489 } else {
1490 if format != 0 { 1 } else { 0 }
1491 }
1492 } else {
1493 if format != 0 { 1 } else { 0 }
1494 };
1495 font_data[head_off + 50] = (correct >> 8) as u8;
1496 font_data[head_off + 51] = correct as u8;
1497}
1498
1499fn load_system_truetype_font(base_font: &str) -> Result<Vec<u8>, PdfError> {
1504 use stet_fonts::system_fonts::get_system_font_cache;
1505
1506 let cache = get_system_font_cache();
1507
1508 let mut clean_name = base_font;
1510 if clean_name.len() > 7 && clean_name.as_bytes().get(6) == Some(&b'+') {
1511 clean_name = &clean_name[7..];
1512 }
1513
1514 if let Some(path) = cache.get_font_path(clean_name)
1516 && let Ok(data) = read_font_file(path, clean_name)
1517 {
1518 return Ok(data);
1519 }
1520
1521 for &(from, to) in CID_FONT_SUBSTITUTIONS {
1523 if from == clean_name
1524 && let Some(path) = cache.get_font_path(to)
1525 && let Ok(data) = read_font_file(path, to)
1526 {
1527 return Ok(data);
1528 }
1529 }
1530
1531 let lower = clean_name.to_ascii_lowercase();
1533 let is_bold = lower.contains("bold") || lower.contains("demi");
1534 let is_italic = lower.contains("italic") || lower.contains("oblique");
1535
1536 for (ps_name, path) in cache.iter() {
1537 let ps_lower = ps_name.to_ascii_lowercase();
1538 let family = lower.split(&['-', ','][..]).next().unwrap_or(&lower);
1539 if ps_lower.contains(family) || family.contains(ps_lower.split('-').next().unwrap_or("")) {
1540 let name_bold = ps_lower.contains("bold") || ps_lower.contains("demi");
1541 let name_italic = ps_lower.contains("italic") || ps_lower.contains("oblique");
1542 if name_bold == is_bold
1543 && name_italic == is_italic
1544 && let Ok(data) = read_font_file(path, ps_name)
1545 {
1546 return Ok(data);
1547 }
1548 }
1549 }
1550
1551 Err(PdfError::Other(format!(
1552 "font '{}' not found on system",
1553 clean_name
1554 )))
1555}
1556
1557fn load_cjk_fallback_font(ordering: &[u8], base_font: &str) -> Result<Vec<u8>, PdfError> {
1562 use stet_fonts::system_fonts::get_system_font_cache;
1563
1564 if ordering.is_empty() {
1565 return Err(PdfError::Other("no CJK ordering for fallback".into()));
1566 }
1567
1568 let cache = get_system_font_cache();
1569 let lower = base_font.to_ascii_lowercase();
1570 let is_bold = lower.contains("bold") || lower.contains("demi") || lower.contains("black");
1571
1572 let has_cjk_gothic = {
1579 if let Some(pos) = lower.find("gothic") {
1580 pos == 0 || !lower.as_bytes()[pos - 1].is_ascii_alphabetic()
1581 } else {
1582 false
1583 }
1584 };
1585 let is_cjk_name = has_cjk_gothic
1586 || [
1587 "cn", "sc", "jp", "kr", "tc", "hk", "cjk", "ming", "song", "hei", "kai", "fang", "han",
1588 ]
1589 .iter()
1590 .any(|kw| lower.contains(kw));
1591 if ordering == b"Identity" && !is_cjk_name {
1592 let latin_targets: &[&str] = if is_bold {
1593 &["LiberationSans-Bold", "DejaVuSans-Bold"]
1594 } else {
1595 &["LiberationSans", "DejaVuSans"]
1596 };
1597 for &target in latin_targets {
1598 if let Some(path) = cache.get_font_path(target)
1599 && let Ok(data) = read_font_file(path, target)
1600 {
1601 return Ok(data);
1602 }
1603 }
1604 return Err(PdfError::Other(format!(
1605 "Latin fallback font not found for '{}'",
1606 base_font
1607 )));
1608 }
1609
1610 let lang = if lower.contains("cn") || lower.contains("sc") || ordering == b"GB1" {
1614 "sc"
1615 } else if lower.contains("tw") || lower.contains("tc") || ordering == b"CNS1" {
1616 "tc"
1617 } else if lower.contains("kr") || ordering == b"Korea1" {
1618 "kr"
1619 } else if lower.contains("hk") {
1620 "hk"
1621 } else {
1622 "jp" };
1624 let heavy = lower.contains("heavy") || lower.contains("black");
1625 let weight_suffix = if heavy {
1627 "Black"
1628 } else if is_bold {
1629 "Bold"
1630 } else {
1631 "Regular"
1632 };
1633 let targets = [
1634 format!("NotoSansCJK{lang}-{weight_suffix}"),
1635 if is_bold || heavy {
1636 format!("NotoSansCJK{lang}-Bold")
1637 } else {
1638 format!("NotoSansCJK{lang}-Regular")
1639 },
1640 format!("NotoSansCJKjp-{weight_suffix}"),
1641 ];
1642 for target in &targets {
1643 if let Some(path) = cache.get_font_path(target)
1644 && let Ok(data) = read_font_file(path, target)
1645 {
1646 return Ok(data);
1647 }
1648 }
1649
1650 Err(PdfError::Other(format!(
1651 "CJK fallback font not found on system for '{}'",
1652 base_font
1653 )))
1654}
1655
1656const EMBEDDED_FONTS: &[(&str, &[u8])] = &[
1659 (
1661 "NimbusRoman-Regular",
1662 include_bytes!("../../fonts/NimbusRoman-Regular.t1"),
1663 ),
1664 (
1665 "NimbusRoman-Bold",
1666 include_bytes!("../../fonts/NimbusRoman-Bold.t1"),
1667 ),
1668 (
1669 "NimbusRoman-Italic",
1670 include_bytes!("../../fonts/NimbusRoman-Italic.t1"),
1671 ),
1672 (
1673 "NimbusRoman-BoldItalic",
1674 include_bytes!("../../fonts/NimbusRoman-BoldItalic.t1"),
1675 ),
1676 (
1678 "NimbusSans-Regular",
1679 include_bytes!("../../fonts/NimbusSans-Regular.t1"),
1680 ),
1681 (
1682 "NimbusSans-Bold",
1683 include_bytes!("../../fonts/NimbusSans-Bold.t1"),
1684 ),
1685 (
1686 "NimbusSans-Italic",
1687 include_bytes!("../../fonts/NimbusSans-Italic.t1"),
1688 ),
1689 (
1690 "NimbusSans-BoldItalic",
1691 include_bytes!("../../fonts/NimbusSans-BoldItalic.t1"),
1692 ),
1693 (
1695 "NimbusSansNarrow-Regular",
1696 include_bytes!("../../fonts/NimbusSansNarrow-Regular.t1"),
1697 ),
1698 (
1699 "NimbusSansNarrow-Bold",
1700 include_bytes!("../../fonts/NimbusSansNarrow-Bold.t1"),
1701 ),
1702 (
1703 "NimbusSansNarrow-Oblique",
1704 include_bytes!("../../fonts/NimbusSansNarrow-Oblique.t1"),
1705 ),
1706 (
1707 "NimbusSansNarrow-BoldOblique",
1708 include_bytes!("../../fonts/NimbusSansNarrow-BoldOblique.t1"),
1709 ),
1710 (
1712 "NimbusMonoPS-Regular",
1713 include_bytes!("../../fonts/NimbusMonoPS-Regular.t1"),
1714 ),
1715 (
1716 "NimbusMonoPS-Bold",
1717 include_bytes!("../../fonts/NimbusMonoPS-Bold.t1"),
1718 ),
1719 (
1720 "NimbusMonoPS-Italic",
1721 include_bytes!("../../fonts/NimbusMonoPS-Italic.t1"),
1722 ),
1723 (
1724 "NimbusMonoPS-BoldItalic",
1725 include_bytes!("../../fonts/NimbusMonoPS-BoldItalic.t1"),
1726 ),
1727 (
1729 "P052-Roman",
1730 include_bytes!("../../fonts/P052-Roman.t1"),
1731 ),
1732 (
1733 "P052-Bold",
1734 include_bytes!("../../fonts/P052-Bold.t1"),
1735 ),
1736 (
1737 "P052-Italic",
1738 include_bytes!("../../fonts/P052-Italic.t1"),
1739 ),
1740 (
1741 "P052-BoldItalic",
1742 include_bytes!("../../fonts/P052-BoldItalic.t1"),
1743 ),
1744 (
1746 "C059-Roman",
1747 include_bytes!("../../fonts/C059-Roman.t1"),
1748 ),
1749 (
1750 "C059-Bold",
1751 include_bytes!("../../fonts/C059-Bold.t1"),
1752 ),
1753 (
1754 "C059-Italic",
1755 include_bytes!("../../fonts/C059-Italic.t1"),
1756 ),
1757 (
1758 "C059-BdIta",
1759 include_bytes!("../../fonts/C059-BdIta.t1"),
1760 ),
1761 (
1763 "URWBookman-Light",
1764 include_bytes!("../../fonts/URWBookman-Light.t1"),
1765 ),
1766 (
1767 "URWBookman-Demi",
1768 include_bytes!("../../fonts/URWBookman-Demi.t1"),
1769 ),
1770 (
1771 "URWBookman-LightItalic",
1772 include_bytes!("../../fonts/URWBookman-LightItalic.t1"),
1773 ),
1774 (
1775 "URWBookman-DemiItalic",
1776 include_bytes!("../../fonts/URWBookman-DemiItalic.t1"),
1777 ),
1778 (
1780 "URWGothic-Book",
1781 include_bytes!("../../fonts/URWGothic-Book.t1"),
1782 ),
1783 (
1784 "URWGothic-Demi",
1785 include_bytes!("../../fonts/URWGothic-Demi.t1"),
1786 ),
1787 (
1788 "URWGothic-BookOblique",
1789 include_bytes!("../../fonts/URWGothic-BookOblique.t1"),
1790 ),
1791 (
1792 "URWGothic-DemiOblique",
1793 include_bytes!("../../fonts/URWGothic-DemiOblique.t1"),
1794 ),
1795 (
1797 "StandardSymbolsPS",
1798 include_bytes!("../../fonts/StandardSymbolsPS.t1"),
1799 ),
1800 (
1801 "D050000L",
1802 include_bytes!("../../fonts/D050000L.t1"),
1803 ),
1804 (
1805 "Z003-MediumItalic",
1806 include_bytes!("../../fonts/Z003-MediumItalic.t1"),
1807 ),
1808];
1809
1810fn embedded_font(name: &str) -> Option<Vec<u8>> {
1812 EMBEDDED_FONTS
1813 .iter()
1814 .find(|(n, _)| *n == name)
1815 .map(|(_, data)| data.to_vec())
1816}
1817
1818fn read_font_file(path: &std::path::Path, ps_name: &str) -> std::io::Result<Vec<u8>> {
1821 let data = std::fs::read(path)?;
1822 if data.len() > 12 && &data[0..4] == b"ttcf" {
1823 let num_fonts = u32::from_be_bytes([data[8], data[9], data[10], data[11]]) as usize;
1825 let mut best_offset = if num_fonts > 0 {
1827 u32::from_be_bytes([data[12], data[13], data[14], data[15]]) as usize
1828 } else {
1829 0
1830 };
1831 for i in 0..num_fonts {
1832 let off_pos = 12 + i * 4;
1833 if off_pos + 4 > data.len() {
1834 break;
1835 }
1836 let font_offset = u32::from_be_bytes([
1837 data[off_pos],
1838 data[off_pos + 1],
1839 data[off_pos + 2],
1840 data[off_pos + 3],
1841 ]) as usize;
1842 if let Some(name) = extract_ps_name_at_offset(&data, font_offset)
1844 && name == ps_name
1845 {
1846 best_offset = font_offset;
1847 break;
1848 }
1849 }
1850 extract_ttf_from_ttc(&data, best_offset)
1853 } else {
1854 Ok(data)
1855 }
1856}
1857
1858fn extract_ps_name_at_offset(data: &[u8], offset: usize) -> Option<String> {
1860 use stet_fonts::truetype::read_u16;
1861 if offset + 12 > data.len() {
1863 return None;
1864 }
1865 let num_tables = read_u16(data, offset + 4) as usize;
1866 let mut name_off = 0usize;
1867 let mut name_len = 0usize;
1868 for i in 0..num_tables {
1869 let entry = offset + 12 + i * 16;
1870 if entry + 16 > data.len() {
1871 break;
1872 }
1873 if &data[entry..entry + 4] == b"name" {
1874 name_off = u32::from_be_bytes([
1875 data[entry + 8],
1876 data[entry + 9],
1877 data[entry + 10],
1878 data[entry + 11],
1879 ]) as usize;
1880 name_len = u32::from_be_bytes([
1881 data[entry + 12],
1882 data[entry + 13],
1883 data[entry + 14],
1884 data[entry + 15],
1885 ]) as usize;
1886 break;
1887 }
1888 }
1889 if name_off == 0 || name_off + name_len > data.len() {
1890 return None;
1891 }
1892 let nd = &data[name_off..name_off + name_len];
1893 let count = read_u16(nd, 2) as usize;
1894 let string_offset = read_u16(nd, 4) as usize;
1895 for i in 0..count {
1896 let rec = 6 + i * 12;
1897 if rec + 12 > nd.len() {
1898 break;
1899 }
1900 let pid = read_u16(nd, rec);
1901 let name_id = read_u16(nd, rec + 6);
1902 let length = read_u16(nd, rec + 8) as usize;
1903 let str_off = read_u16(nd, rec + 10) as usize;
1904 if name_id == 6 {
1905 let start = string_offset + str_off;
1906 if start + length <= nd.len() {
1907 let raw = &nd[start..start + length];
1908 if pid == 3 {
1909 let s: String = raw
1910 .chunks(2)
1911 .filter_map(|c| {
1912 if c.len() == 2 {
1913 Some(u16::from_be_bytes([c[0], c[1]]) as u8 as char)
1914 } else {
1915 None
1916 }
1917 })
1918 .collect();
1919 return Some(s);
1920 } else {
1921 return Some(String::from_utf8_lossy(raw).to_string());
1922 }
1923 }
1924 }
1925 }
1926 None
1927}
1928
1929fn extract_ttf_from_ttc(ttc_data: &[u8], font_offset: usize) -> std::io::Result<Vec<u8>> {
1934 use stet_fonts::truetype::{read_u16, read_u32};
1935
1936 if font_offset + 12 > ttc_data.len() {
1937 return Err(std::io::Error::other("TTC font offset out of range"));
1938 }
1939
1940 let num_tables = read_u16(ttc_data, font_offset + 4) as usize;
1941 let header_size = 12 + num_tables * 16;
1942
1943 let mut tables = Vec::with_capacity(num_tables);
1945 for i in 0..num_tables {
1946 let entry = font_offset + 12 + i * 16;
1947 if entry + 16 > ttc_data.len() {
1948 break;
1949 }
1950 let tag = &ttc_data[entry..entry + 4];
1951 let offset = read_u32(ttc_data, entry + 8) as usize;
1952 let length = read_u32(ttc_data, entry + 12) as usize;
1953 tables.push((tag.to_vec(), offset, length));
1954 }
1955
1956 let mut result = Vec::with_capacity(
1958 header_size + tables.iter().map(|(_, _, l)| (l + 3) & !3).sum::<usize>(),
1959 );
1960
1961 result.extend_from_slice(&ttc_data[font_offset..font_offset + 12]);
1963
1964 let mut data_offset = header_size as u32;
1966 let mut new_offsets = Vec::with_capacity(num_tables);
1967 for (_, _, length) in &tables {
1968 new_offsets.push(data_offset);
1969 data_offset += ((*length as u32) + 3) & !3; }
1971
1972 for (i, (tag, _, length)) in tables.iter().enumerate() {
1974 let entry = font_offset + 12 + i * 16;
1975 result.extend_from_slice(tag); result.extend_from_slice(&ttc_data[entry + 4..entry + 8]); result.extend_from_slice(&new_offsets[i].to_be_bytes()); result.extend_from_slice(&(*length as u32).to_be_bytes()); }
1980
1981 for (_, ttc_offset, length) in &tables {
1983 let end = (*ttc_offset + *length).min(ttc_data.len());
1984 if *ttc_offset < ttc_data.len() {
1985 result.extend_from_slice(&ttc_data[*ttc_offset..end]);
1986 let pad = (4 - (length % 4)) % 4;
1988 result.extend(std::iter::repeat_n(0u8, pad));
1989 }
1990 }
1991
1992 Ok(result)
1993}
1994
1995fn resolve_type3(resolver: &Resolver, font_dict: &PdfDict) -> Result<PdfFont, PdfError> {
1997 let first_char = font_dict.get_int(b"FirstChar").unwrap_or(0) as usize;
1998
1999 let mut widths = [0.0f64; 256];
2002 let widths_resolved = font_dict
2003 .get(b"Widths")
2004 .and_then(|obj| resolver.deref(obj).ok());
2005 if let Some(ref w_obj) = widths_resolved
2006 && let Some(w_arr) = w_obj.as_array()
2007 {
2008 for (i, obj) in w_arr.iter().enumerate() {
2009 let code = first_char + i;
2010 if code < 256 {
2011 let val = if obj.as_f64().is_some() {
2013 obj.as_f64().unwrap()
2014 } else if let Ok(resolved) = resolver.deref(obj) {
2015 resolved.as_f64().unwrap_or(0.0)
2016 } else {
2017 0.0
2018 };
2019 widths[code] = val;
2020 }
2021 }
2022 }
2023
2024 let font_matrix = font_dict
2026 .get_array(b"FontMatrix")
2027 .map(|a| {
2028 let v: Vec<f64> = a.iter().filter_map(|o| o.as_f64()).collect();
2029 if v.len() >= 6 {
2030 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
2031 } else {
2032 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
2033 }
2034 })
2035 .unwrap_or_else(|| Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0));
2036
2037 let font_bbox = font_dict
2038 .get_array(b"FontBBox")
2039 .map(|a| {
2040 let v: Vec<f64> = a.iter().filter_map(|o| o.as_f64()).collect();
2041 if v.len() >= 4 {
2042 [v[0], v[1], v[2], v[3]]
2043 } else {
2044 [0.0, 0.0, 1.0, 1.0]
2045 }
2046 })
2047 .unwrap_or([0.0, 0.0, 1.0, 1.0]);
2048
2049 let (encoding, _, _, _) = resolve_encoding(font_dict, resolver)?;
2051
2052 let char_procs_dict = if let Some(obj) = font_dict.get(b"CharProcs") {
2055 match resolver.deref(obj)? {
2056 PdfObj::Dict(d) => d,
2057 _ => return Err(PdfError::Other("Type3 CharProcs is not a dict".into())),
2058 }
2059 } else {
2060 return Err(PdfError::Other("Type3 font missing CharProcs".into()));
2061 };
2062
2063 let resources = if let Some(res_ref) = font_dict.get(b"Resources") {
2065 match resolver.deref(res_ref)? {
2066 PdfObj::Dict(d) => d,
2067 _ => PdfDict::new(),
2068 }
2069 } else {
2070 PdfDict::new()
2071 };
2072
2073 let mut char_procs = HashMap::new();
2075 for code in 0..256u16 {
2076 if let Some(glyph_name) = &encoding[code as usize]
2077 && let Some(proc_ref) = char_procs_dict.get(glyph_name.as_bytes())
2078 && let Ok(data) = resolver.stream_data_from_obj(proc_ref)
2079 {
2080 char_procs.insert(code as u8, data);
2081 }
2082 }
2083 Ok(PdfFont::Type3(Type3PdfFont {
2084 char_procs,
2085 resources,
2086 widths,
2087 font_matrix,
2088 font_bbox,
2089 }))
2090}
2091
2092fn resolve_type1(
2093 resolver: &Resolver,
2094 descriptor: &Option<PdfDict>,
2095 encoding: [Option<String>; 256],
2096 widths: [f64; 256],
2097 has_explicit_encoding: bool,
2098 has_pdf_widths: bool,
2099 differences: &[(usize, String)],
2100 no_base_encoding: bool,
2101) -> Result<PdfFont, PdfError> {
2102 let desc = descriptor
2103 .as_ref()
2104 .ok_or(PdfError::Other("Type1 font missing FontDescriptor".into()))?;
2105 if let Some(ff3_ref) = desc.get(b"FontFile3") {
2107 let ff3_obj = resolver.deref(ff3_ref)?;
2109 let ff3_dict = ff3_obj.as_dict();
2110 let subtype = ff3_dict.and_then(|d| d.get_name(b"Subtype")).unwrap_or(b"");
2111 if subtype == b"Type1C" || subtype == b"CIDFontType0C" || subtype == b"OpenType" {
2112 let raw_data = resolver.stream_data_from_obj(ff3_ref)?;
2113 let font_data = if raw_data.starts_with(b"OTTO") {
2115 use stet_fonts::truetype::find_table;
2116 let (offset, length) = find_table(&raw_data, b"CFF ")
2117 .ok_or(PdfError::Other("OpenType font has no CFF table".into()))?;
2118 raw_data[offset..offset + length].to_vec()
2119 } else {
2120 raw_data
2121 };
2122 let fonts = parse_cff(&font_data)
2123 .map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
2124 let font = fonts
2125 .into_iter()
2126 .next()
2127 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
2128
2129 let fm = font.font_matrix;
2130 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
2131
2132 return Ok(PdfFont::Cff(CffPdfFont {
2133 font,
2134 encoding,
2135 widths,
2136 font_matrix,
2137 }));
2138 }
2139 }
2140
2141 let ff_ref = desc
2142 .get(b"FontFile")
2143 .or_else(|| desc.get(b"FontFile3"))
2144 .ok_or(PdfError::Other("Type1 font missing FontFile".into()))?;
2145 let font_data = resolver.stream_data_from_obj(ff_ref)?;
2146
2147 let font_data = strip_pfb(&font_data);
2149
2150 let font =
2151 parse_type1(&font_data).map_err(|e| PdfError::Other(format!("Type1 parse error: {e}")))?;
2152
2153 let encoding = if no_base_encoding && font.encoding.len() == 256 {
2158 let mut builtin: [Option<String>; 256] = std::array::from_fn(|_| None);
2161 for (i, name) in font.encoding.iter().enumerate() {
2162 if name != ".notdef" {
2163 builtin[i] = Some(name.clone());
2164 }
2165 }
2166 for (code, name) in differences {
2167 if *code < 256 {
2168 builtin[*code] = Some(name.clone());
2169 }
2170 }
2171 builtin
2172 } else if !has_explicit_encoding {
2173 let flags = desc.get_int(b"Flags").unwrap_or(0) as u32;
2174 let is_symbolic = flags & 4 != 0;
2175 if is_symbolic && font.encoding.len() == 256 {
2176 let mut builtin: [Option<String>; 256] = std::array::from_fn(|_| None);
2177 for (i, name) in font.encoding.iter().enumerate() {
2178 if name != ".notdef" {
2179 builtin[i] = Some(name.clone());
2180 }
2181 }
2182 builtin
2183 } else {
2184 encoding
2185 }
2186 } else {
2187 encoding
2188 };
2189
2190 let builtin_fallback = {
2194 let flags = desc.get_int(b"Flags").unwrap_or(0) as u32;
2195 let is_sym = flags & 4 != 0;
2196 let builtin_useful =
2197 is_sym && font.encoding.len() == 256 && font.encoding.iter().any(|n| n != ".notdef");
2198 if builtin_useful {
2199 !encoding[32..127].iter().any(|slot| {
2200 slot.as_ref()
2201 .is_some_and(|name| font.charstrings.contains_key(name.as_str()))
2202 })
2203 } else {
2204 false
2205 }
2206 };
2207
2208 let fm = font.font_matrix;
2209 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
2210
2211 let widths = if !has_pdf_widths {
2213 let mut derived = [0.0f64; 256];
2214 for code in 0..256usize {
2215 let glyph_name = encoding[code].as_deref().unwrap_or(".notdef");
2216 if glyph_name == ".notdef" {
2217 continue;
2218 }
2219 if let Some(charstring) = font.charstrings.get(glyph_name) {
2220 let cs_lookup =
2221 |name: &str| -> Option<Vec<u8>> { font.charstrings.get(name).cloned() };
2222 if let Ok(result) = execute_charstring_mm(
2223 charstring,
2224 &font.subrs,
2225 font.len_iv,
2226 false,
2227 Some(&cs_lookup),
2228 font.weight_vector.as_deref(),
2229 ) {
2230 derived[code] = result.width_x * fm[0];
2231 }
2232 }
2233 }
2234 derived
2235 } else {
2236 widths
2237 };
2238
2239 let weight_vector = font.weight_vector.clone();
2240 Ok(PdfFont::Type1(Type1PdfFont {
2241 font,
2242 encoding,
2243 widths,
2244 font_matrix,
2245 weight_vector,
2246 builtin_fallback,
2247 per_char_width_scale: false,
2248 }))
2249}
2250
2251fn try_raw_deflate_if_truncated(resolver: &Resolver, ff_ref: &PdfObj, data: Vec<u8>) -> Vec<u8> {
2256 if data.len() < 12 {
2258 return data;
2259 }
2260 let num_tables = u16::from_be_bytes([data[4], data[5]]) as usize;
2261 let mut max_end = 0usize;
2262 for i in 0..num_tables {
2263 let e = 12 + i * 16;
2264 if e + 16 > data.len() {
2265 break;
2266 }
2267 let off =
2268 u32::from_be_bytes([data[e + 8], data[e + 9], data[e + 10], data[e + 11]]) as usize;
2269 let len =
2270 u32::from_be_bytes([data[e + 12], data[e + 13], data[e + 14], data[e + 15]]) as usize;
2271 max_end = max_end.max(off.saturating_add(len));
2272 }
2273 if max_end <= data.len() {
2274 return data; }
2276 let raw_bytes = match resolver.raw_stream_bytes(ff_ref) {
2278 Some(b) if b.len() > 2 => b,
2279 _ => return data,
2280 };
2281 let cinfo = raw_bytes[0] >> 4;
2283 let cm = raw_bytes[0] & 0xF;
2284 if cm != 8 || cinfo >= 7 {
2285 return data;
2286 }
2287 let mut decoder = flate2::Decompress::new(false);
2289 let mut output = Vec::with_capacity(data.len() * 2);
2290 let mut buf = [0u8; 8192];
2291 let input = &raw_bytes[2..];
2292 let mut input_offset = 0;
2293 loop {
2294 let before_in = decoder.total_in() as usize;
2295 let before_out = decoder.total_out() as usize;
2296 let result = decoder.decompress(
2297 &input[input_offset..],
2298 &mut buf,
2299 flate2::FlushDecompress::None,
2300 );
2301 let consumed = decoder.total_in() as usize - before_in;
2302 let produced = decoder.total_out() as usize - before_out;
2303 input_offset += consumed;
2304 output.extend_from_slice(&buf[..produced]);
2305 match result {
2306 Ok(flate2::Status::StreamEnd) => break,
2307 Ok(_) => {
2308 if consumed == 0 && produced == 0 {
2309 break;
2310 }
2311 }
2312 Err(_) => break,
2313 }
2314 }
2315 if output.len() <= data.len() {
2316 return data;
2317 }
2318 let mut raw_max_end = 0usize;
2320 for i in 0..num_tables {
2321 let e = 12 + i * 16;
2322 if e + 16 > output.len() {
2323 return data;
2324 }
2325 let off = u32::from_be_bytes([output[e + 8], output[e + 9], output[e + 10], output[e + 11]])
2326 as usize;
2327 let len = u32::from_be_bytes([
2328 output[e + 12],
2329 output[e + 13],
2330 output[e + 14],
2331 output[e + 15],
2332 ]) as usize;
2333 raw_max_end = raw_max_end.max(off.saturating_add(len));
2334 }
2335 if raw_max_end > output.len() {
2336 return data; }
2338 if stet_fonts::truetype::get_units_per_em(&output) == 0 {
2341 return data;
2342 }
2343 output
2344}
2345
2346fn resolve_truetype(
2347 resolver: &Resolver,
2348 descriptor: &Option<PdfDict>,
2349 encoding: [Option<String>; 256],
2350 widths: [f64; 256],
2351 font_dict: &PdfDict,
2352) -> Result<PdfFont, PdfError> {
2353 let desc = descriptor.as_ref().ok_or(PdfError::Other(
2354 "TrueType font missing FontDescriptor".into(),
2355 ))?;
2356 let ff_ref = desc
2357 .get(b"FontFile2")
2358 .ok_or(PdfError::Other("TrueType font missing FontFile2".into()))?;
2359 let data = resolver.stream_data_from_obj(ff_ref)?;
2360
2361 let data = try_raw_deflate_if_truncated(resolver, ff_ref, data);
2365
2366 use stet_fonts::truetype::find_table;
2368 let has_glyf = find_table(&data, b"glyf").is_some();
2369 let has_usable_glyx = if let Some((off, len)) = find_table(&data, b"glyx") {
2370 off + len <= data.len()
2371 } else {
2372 false
2373 };
2374 if !has_glyf && !has_usable_glyx {
2375 let is_otf = data.starts_with(b"OTTO");
2378 let is_cff = is_raw_cff(&data);
2379 if is_otf || is_cff {
2380 let has_explicit_encoding = font_dict.get(b"Encoding").is_some();
2381 let has_pdf_widths = font_dict.get(b"Widths").is_some();
2382 return build_cff_font(
2383 data,
2384 encoding,
2385 widths,
2386 has_explicit_encoding,
2387 has_pdf_widths,
2388 &[],
2389 false,
2390 );
2391 }
2392 return Err(PdfError::Other(
2393 "TrueType font has no usable glyph outline data".into(),
2394 ));
2395 }
2396
2397 if let Some((off, _)) = find_table(&data, b"head") {
2401 if off + 54 > data.len() {
2402 return Err(PdfError::Other(
2403 "TrueType font head table is out of bounds (truncated data)".into(),
2404 ));
2405 }
2406 }
2407
2408 let units_per_em = get_units_per_em(&data) as f64;
2409
2410 if units_per_em < 16.0 {
2414 return Err(PdfError::Other(
2415 "TrueType font has degenerate unitsPerEm (placeholder outlines)".into(),
2416 ));
2417 }
2418
2419 let (cmap, cmap_is_unicode) = parse_cmap_with_info(&data);
2420
2421 let post_name_to_gid = stet_fonts::system_fonts::parse_post_table(&data)
2423 .map(|gid_to_name| {
2424 gid_to_name
2425 .into_iter()
2426 .map(|(gid, name)| (name, gid))
2427 .collect()
2428 })
2429 .unwrap_or_default();
2430
2431 let flags = desc.get_int(b"Flags").unwrap_or(0) as u32;
2435 let is_symbolic = flags & 4 != 0;
2436 let has_encoding = font_dict.get(b"Encoding").is_some();
2437 let identity_gid = is_symbolic && !has_encoding && cmap_is_unicode;
2438 let gid_hex = TrueTypePdfFont::detect_gid_hex(&encoding);
2439
2440 Ok(PdfFont::TrueType(TrueTypePdfFont {
2441 data,
2442 encoding,
2443 widths,
2444 cmap,
2445 cmap_is_unicode,
2446 post_name_to_gid,
2447 units_per_em,
2448 to_unicode: if let Some(tu_obj) = font_dict.get(b"ToUnicode") {
2449 resolver
2450 .stream_data_from_obj(tu_obj)
2451 .map(|d| parse_to_unicode(&d))
2452 .unwrap_or_default()
2453 } else {
2454 HashMap::new()
2455 },
2456 identity_gid,
2457 gid_hex,
2458 }))
2459}
2460
2461fn resolve_cff(
2463 resolver: &Resolver,
2464 descriptor: &Option<PdfDict>,
2465 encoding: [Option<String>; 256],
2466 widths: [f64; 256],
2467 has_explicit_encoding: bool,
2468 has_pdf_widths: bool,
2469 differences: &[(usize, String)],
2470 no_base_encoding: bool,
2471) -> Result<PdfFont, PdfError> {
2472 let desc = descriptor
2473 .as_ref()
2474 .ok_or(PdfError::Other("CFF font missing FontDescriptor".into()))?;
2475 let ff_ref = desc
2476 .get(b"FontFile3")
2477 .ok_or(PdfError::Other("CFF font missing FontFile3".into()))?;
2478 let raw_data = resolver.stream_data_from_obj(ff_ref)?;
2479 build_cff_font(
2480 raw_data,
2481 encoding,
2482 widths,
2483 has_explicit_encoding,
2484 has_pdf_widths,
2485 differences,
2486 no_base_encoding,
2487 )
2488}
2489
2490fn build_cff_font(
2492 raw_data: Vec<u8>,
2493 encoding: [Option<String>; 256],
2494 widths: [f64; 256],
2495 has_explicit_encoding: bool,
2496 has_pdf_widths: bool,
2497 differences: &[(usize, String)],
2498 no_base_encoding: bool,
2499) -> Result<PdfFont, PdfError> {
2500 let font_data = if raw_data.starts_with(b"OTTO") {
2502 use stet_fonts::truetype::find_table;
2503 let (offset, length) = find_table(&raw_data, b"CFF ")
2504 .ok_or(PdfError::Other("OpenType font has no CFF table".into()))?;
2505 raw_data[offset..offset + length].to_vec()
2506 } else {
2507 raw_data
2508 };
2509
2510 let fonts =
2511 parse_cff(&font_data).map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
2512 let font = fonts
2513 .into_iter()
2514 .next()
2515 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
2516
2517 let build_cff_encoding = |font: &stet_fonts::cff_parser::CffFont| -> [Option<String>; 256] {
2526 let mut enc: [Option<String>; 256] = std::array::from_fn(|_| None);
2527 let name_to_gid: std::collections::HashMap<&str, u16> = font
2528 .charset
2529 .iter()
2530 .enumerate()
2531 .map(|(gid, name)| (name.as_str(), gid as u16))
2532 .collect();
2533 #[allow(clippy::needless_range_loop)]
2534 for code in 0..256 {
2535 let gid = font.encoding[code] as usize;
2536 if gid > 0 && gid < font.charset.len() && font.charset[gid] != ".notdef" {
2537 enc[code] = Some(font.charset[gid].clone());
2538 }
2539 }
2540 if name_to_gid.contains_key("Asmall") {
2542 for &(code, sid) in &stet_fonts::cff_parser::EXPERT_ENCODING_MAP {
2543 if enc[code as usize].is_none() {
2544 let name = stet_fonts::cff_parser::get_sid_string(sid, &[]);
2545 if let Some(&gid) = name_to_gid.get(name.as_str()) {
2546 if gid > 0 {
2547 enc[code as usize] = Some(font.charset[gid as usize].clone());
2548 }
2549 }
2550 }
2551 }
2552 for code in b'a'..=b'z' {
2555 if enc[code as usize].is_none() {
2556 let small_name = format!("{}small", (code - b'a' + b'A') as char);
2557 if name_to_gid.contains_key(small_name.as_str()) {
2558 enc[code as usize] = Some(small_name);
2559 }
2560 }
2561 }
2562 }
2563 enc
2564 };
2565
2566 let encoding = if no_base_encoding || !differences.is_empty() {
2567 let mut enc = build_cff_encoding(&font);
2570 for (code, name) in differences {
2571 if *code < 256 {
2572 enc[*code] = Some(name.clone());
2573 }
2574 }
2575 enc
2576 } else if !has_explicit_encoding {
2577 build_cff_encoding(&font)
2579 } else {
2580 encoding
2581 };
2582
2583 let fm = font.font_matrix;
2584 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
2585
2586 let widths = if !has_pdf_widths {
2588 use stet_fonts::type2_charstring::execute_type2_charstring;
2589 let mut derived = [0.0f64; 256];
2590 for code in 0..256usize {
2591 let glyph_name = encoding[code].as_deref().unwrap_or(".notdef");
2592 let gid = font
2593 .charset
2594 .iter()
2595 .position(|name| name == glyph_name)
2596 .unwrap_or(0);
2597 if gid > 0 && gid < font.char_strings.len() {
2598 if let Ok(result) = execute_type2_charstring(
2599 &font.char_strings[gid],
2600 &font.local_subrs,
2601 &font.global_subrs,
2602 font.default_width_x,
2603 font.nominal_width_x,
2604 true, ) {
2606 derived[code] = result.width_x * fm[0];
2607 }
2608 }
2609 }
2610 derived
2611 } else {
2612 widths
2613 };
2614
2615 Ok(PdfFont::Cff(CffPdfFont {
2616 font,
2617 encoding,
2618 widths,
2619 font_matrix,
2620 }))
2621}
2622
2623fn resolve_type0(resolver: &Resolver, font_dict: &PdfDict) -> Result<PdfFont, PdfError> {
2625 let encoding_obj = font_dict.get(b"Encoding");
2627 let encoding_name = font_dict.get_name(b"Encoding").unwrap_or(b"");
2628 let ucs2_encoding = encoding_name.windows(4).any(|w| w == b"UCS2");
2629
2630 let (code_lengths, code_to_cid, mut wmode) = if let Some(enc_obj) = encoding_obj {
2637 if let Ok(cmap_data) = resolver.stream_data_from_obj(enc_obj) {
2638 let cmap = super::cmap::CMap::parse_with_loader(
2640 &cmap_data,
2641 Some(&|name| load_predefined_cmap(name)),
2642 );
2643 (cmap.code_lengths, cmap.code_to_cid, cmap.wmode)
2644 } else if !encoding_name.is_empty() && !encoding_name.starts_with(b"Identity") {
2645 if let Some(cmap_data) = load_predefined_cmap(encoding_name) {
2647 let cmap = super::cmap::CMap::parse_with_loader(
2648 &cmap_data,
2649 Some(&|name| load_predefined_cmap(name)),
2650 );
2651 (cmap.code_lengths, cmap.code_to_cid, cmap.wmode)
2652 } else {
2653 eprintln!(
2654 "warning: predefined CMap '{}' not found; \
2655 set STET_CMAP_DIR or install poppler-data for CJK support",
2656 String::from_utf8_lossy(encoding_name)
2657 );
2658 ([2u8; 256], HashMap::new(), 0)
2659 }
2660 } else {
2661 ([2u8; 256], HashMap::new(), 0) }
2663 } else {
2664 ([2u8; 256], HashMap::new(), 0)
2665 };
2666 if encoding_name.ends_with(b"-V") {
2668 wmode = 1;
2669 } else if encoding_name.ends_with(b"-H") {
2670 wmode = 0;
2671 }
2672
2673 let descendants_obj = font_dict
2676 .get(b"DescendantFonts")
2677 .ok_or(PdfError::Other("Type0 font missing DescendantFonts".into()))?;
2678 let descendants_resolved = resolver.deref(descendants_obj)?;
2679 let descendants = descendants_resolved
2680 .as_array()
2681 .ok_or(PdfError::Other("DescendantFonts is not an array".into()))?;
2682 let cid_font_ref = descendants
2683 .first()
2684 .ok_or(PdfError::Other("DescendantFonts is empty".into()))?;
2685 let cid_font_obj = resolver.deref(cid_font_ref)?;
2686 let cid_font_dict = cid_font_obj
2687 .as_dict()
2688 .ok_or(PdfError::Other("CIDFont is not a dict".into()))?;
2689
2690 let cid_subtype = cid_font_dict.get_name(b"Subtype").unwrap_or(b"");
2691
2692 let descriptor = get_font_descriptor(cid_font_dict, resolver)?;
2694 let desc = descriptor
2695 .as_ref()
2696 .ok_or(PdfError::Other("CIDFont missing FontDescriptor".into()))?;
2697
2698 let default_width = cid_font_dict.get_f64(b"DW").unwrap_or(1000.0) / 1000.0;
2700
2701 let dw2 = cid_font_dict
2704 .get_array(b"DW2")
2705 .and_then(|arr| {
2706 let v: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
2707 if v.len() >= 2 {
2708 Some([v[0], v[1]])
2709 } else {
2710 None
2711 }
2712 })
2713 .unwrap_or([880.0, -1000.0]);
2714
2715 let cid_widths = parse_cid_widths(cid_font_dict, resolver);
2717
2718 let w2 = parse_cid_w2(cid_font_dict, resolver);
2720
2721 let code_to_cid = if code_to_cid.is_empty()
2728 && code_lengths[0] == 2
2729 && encoding_name.windows(4).any(|w| w == b"UCS2")
2730 {
2731 let mut map = HashMap::new();
2732 for unicode in 0x0020u32..=0x007Eu32 {
2733 let cid = unicode - 0x001F;
2734 map.insert(unicode, cid);
2735 }
2736 map
2737 } else {
2738 code_to_cid
2739 };
2740
2741 let to_unicode = if let Some(tu_obj) = font_dict.get(b"ToUnicode") {
2743 match resolver.stream_data_from_obj(tu_obj) {
2744 Ok(data) => parse_to_unicode(&data),
2745 Err(_) => HashMap::new(),
2746 }
2747 } else {
2748 HashMap::new()
2749 };
2750
2751 let ordering = {
2754 let si_dict = cid_font_dict
2755 .get_dict(b"CIDSystemInfo")
2756 .cloned()
2757 .or_else(|| {
2758 cid_font_dict
2759 .get(b"CIDSystemInfo")
2760 .and_then(|obj| resolver.deref(obj).ok())
2761 .and_then(|obj| obj.as_dict().cloned())
2762 });
2763 si_dict
2764 .and_then(|d| {
2765 d.get(b"Ordering").and_then(|v| match v {
2766 PdfObj::Str(s) => Some(s.clone()),
2767 PdfObj::Name(n) => Some(n.clone()),
2768 _ => None,
2769 })
2770 })
2771 .unwrap_or_default()
2772 };
2773
2774 match cid_subtype {
2775 b"CIDFontType2" => {
2776 let mut substituted;
2777 let mut data = if let Some(ff_ref) = desc
2778 .get(b"FontFile2")
2779 .or_else(|| {
2782 desc.get(b"FontFile").filter(|obj| {
2783 resolver
2784 .stream_data_from_obj(obj)
2785 .ok()
2786 .is_some_and(|d| d.len() > 4 && d[..4] == [0, 1, 0, 0])
2787 })
2788 })
2789 .or_else(|| {
2794 desc.get(b"FontFile3").filter(|obj| {
2795 resolver.stream_data_from_obj(obj).ok().is_some_and(|d| {
2796 d.len() > 4
2797 && (d[..4] == [0, 1, 0, 0]
2798 || &d[..4] == b"true"
2799 || &d[..4] == b"OTTO")
2800 })
2801 })
2802 }) {
2803 substituted = false;
2804 let mut font_data = resolver.stream_data_from_obj(ff_ref)?;
2805 sanitize_index_to_loc_format(&mut font_data);
2806 let is_otf_cff = font_data.len() > 4 && &font_data[0..4] == b"OTTO";
2809 let is_raw = is_raw_cff(&font_data);
2810 if is_otf_cff || is_raw {
2811 let cid_to_gid_map = if let Some(map_obj) = cid_font_dict.get(b"CIDToGIDMap") {
2813 if cid_font_dict.get_name(b"CIDToGIDMap") != Some(b"Identity") {
2814 resolver.stream_data_from_obj(map_obj).ok().map(|d| {
2815 d.chunks_exact(2)
2816 .map(|p| u16::from_be_bytes([p[0], p[1]]))
2817 .collect()
2818 })
2819 } else {
2820 None
2821 }
2822 } else {
2823 None
2824 };
2825 let is_cid_keyed = {
2831 use stet_fonts::truetype::find_table;
2832 let cff_range = if is_otf_cff {
2833 find_table(&font_data, b"CFF ")
2834 } else {
2835 Some((0, font_data.len()))
2836 };
2837 cff_range
2838 .and_then(|(off, len)| parse_cff(&font_data[off..off + len]).ok())
2839 .and_then(|fonts| fonts.into_iter().next())
2840 .is_some_and(|f| f.is_cid)
2841 };
2842 let (cid_to_gid_map, identity) = if is_cid_keyed {
2843 (None, true) } else {
2845 let id = cid_to_gid_map.is_none();
2846 (cid_to_gid_map, id)
2847 };
2848 if is_otf_cff {
2849 return create_cid_cff_from_otf(
2850 &font_data,
2851 default_width,
2852 cid_widths,
2853 &ordering,
2854 cid_to_gid_map,
2855 identity,
2856 code_lengths,
2857 code_to_cid.clone(),
2858 wmode,
2859 dw2,
2860 w2.clone(),
2861 );
2862 } else {
2863 return create_cid_cff_from_raw(
2864 &font_data,
2865 default_width,
2866 cid_widths,
2867 &ordering,
2868 cid_to_gid_map,
2869 identity,
2870 code_lengths,
2871 code_to_cid.clone(),
2872 wmode,
2873 dw2,
2874 w2.clone(),
2875 );
2876 }
2877 }
2878 font_data
2879 } else {
2880 substituted = true;
2882 let base_font = cid_font_dict
2883 .get_name(b"BaseFont")
2884 .map(|n| {
2885 let s = String::from_utf8_lossy(n);
2886 if s.len() > 7 && s.as_bytes().get(6) == Some(&b'+') {
2887 s[7..].to_string()
2888 } else {
2889 s.to_string()
2890 }
2891 })
2892 .unwrap_or_default();
2893 let sys_data = load_system_truetype_font(&base_font)
2894 .or_else(|_| load_cjk_fallback_font(&ordering, &base_font))?;
2895 if sys_data.len() > 4 && &sys_data[0..4] == b"OTTO" {
2897 return create_cid_cff_from_otf(
2898 &sys_data,
2899 default_width,
2900 cid_widths,
2901 &ordering,
2902 None,
2903 false, code_lengths,
2905 code_to_cid.clone(),
2906 wmode,
2907 dw2,
2908 w2.clone(),
2909 );
2910 }
2911 sys_data
2912 };
2913
2914 let has_cid_to_gid_map = cid_font_dict
2921 .get(b"CIDToGIDMap")
2922 .is_some_and(|v| v.as_name().is_none_or(|n| n != b"Identity"));
2923 if !substituted && !cid_widths.is_empty() && !has_cid_to_gid_map {
2924 let upm_f = get_units_per_em(&data) as f64;
2925 let any_glyph = cid_widths
2926 .keys()
2927 .any(|&cid| skrifa_glyph_path(&data, cid, upm_f).is_some());
2928 if !any_glyph {
2929 let base_font = cid_font_dict
2930 .get_name(b"BaseFont")
2931 .map(|n| {
2932 let s = String::from_utf8_lossy(n);
2933 if s.len() > 7 && s.as_bytes().get(6) == Some(&b'+') {
2934 s[7..].to_string()
2935 } else {
2936 s.to_string()
2937 }
2938 })
2939 .unwrap_or_default();
2940 if let Ok(sys_data) = load_system_truetype_font(&base_font) {
2941 data = sys_data;
2942 substituted = true;
2943 }
2944 }
2945 }
2946 let units_per_em = get_units_per_em(&data) as f64;
2947 let cmap = parse_cmap(&data);
2948
2949 let (identity_cid_to_gid, cid_to_gid_map) =
2951 if let Some(name) = cid_font_dict.get_name(b"CIDToGIDMap") {
2952 (name == b"Identity", None)
2953 } else if let Some(map_obj) = cid_font_dict.get(b"CIDToGIDMap") {
2954 match resolver.stream_data_from_obj(map_obj) {
2955 Ok(stream_data) => {
2956 let mut gid_map = Vec::with_capacity(stream_data.len() / 2);
2957 for pair in stream_data.chunks_exact(2) {
2958 gid_map.push(u16::from_be_bytes([pair[0], pair[1]]));
2959 }
2960 (false, Some(gid_map))
2961 }
2962 Err(_) => (true, None), }
2964 } else {
2965 (true, None) };
2967
2968 let cid_to_gid_map = if substituted && cid_to_gid_map.is_some() {
2974 None
2975 } else {
2976 cid_to_gid_map
2977 };
2978 let to_unicode = if substituted
2988 && identity_cid_to_gid
2989 && to_unicode.is_empty()
2990 && encoding_name.starts_with(b"Identity")
2991 {
2992 let base_name = cid_font_dict.get_name(b"BaseFont").unwrap_or(b"");
2993 let name_str = String::from_utf8_lossy(base_name);
2994 let clean = if name_str.len() > 7 && name_str.as_bytes().get(6) == Some(&b'+') {
2996 &name_str[7..]
2997 } else {
2998 &name_str
2999 };
3000 let mut family = clean
3004 .split(&[',', '-'][..])
3005 .next()
3006 .unwrap_or(clean)
3007 .to_ascii_lowercase();
3008 for suffix in &["psmt", "ps", "mt"] {
3009 if family.len() > suffix.len() && family.ends_with(suffix) {
3010 family.truncate(family.len() - suffix.len());
3011 break;
3012 }
3013 }
3014 super::gid_maps::get_gid_to_unicode_map(&family).unwrap_or(to_unicode)
3015 } else {
3016 to_unicode
3017 };
3018
3019 Ok(PdfFont::CidTrueType(CidTrueTypePdfFont {
3020 data,
3021 default_width,
3022 cid_widths,
3023 cmap,
3024 units_per_em,
3025 identity_cid_to_gid,
3026 substituted,
3027 cid_to_gid_map,
3028 to_unicode,
3029 ordering: ordering.clone(),
3030 ucs2_encoding,
3031 code_lengths,
3032 code_to_cid: code_to_cid.clone(),
3033 wmode,
3034 dw2,
3035 w2: w2.clone(),
3036 }))
3037 }
3038 b"CIDFontType0" => {
3039 if let Some(ff_ref) = desc.get(b"FontFile3").or_else(|| desc.get(b"FontFile")) {
3042 let font_data = resolver.stream_data_from_obj(ff_ref)?;
3043 let is_truetype = font_data.len() > 4 && &font_data[0..4] == b"\x00\x01\x00\x00";
3046 if is_truetype {
3047 let mut font_data = font_data;
3048 sanitize_index_to_loc_format(&mut font_data);
3049 let units_per_em = get_units_per_em(&font_data) as f64;
3050 let cmap = parse_cmap(&font_data);
3051 let (identity_cid_to_gid, cid_to_gid_map) =
3052 if let Some(name) = cid_font_dict.get_name(b"CIDToGIDMap") {
3053 (name == b"Identity", None)
3054 } else {
3055 (true, None)
3056 };
3057 return Ok(PdfFont::CidTrueType(CidTrueTypePdfFont {
3058 data: font_data,
3059 default_width,
3060 cid_widths,
3061 cmap,
3062 units_per_em,
3063 identity_cid_to_gid,
3064 substituted: false,
3065 cid_to_gid_map,
3066 to_unicode,
3067 ordering: ordering.clone(),
3068 ucs2_encoding,
3069 code_lengths,
3070 code_to_cid: code_to_cid.clone(),
3071 wmode,
3072 dw2,
3073 w2: w2.clone(),
3074 }));
3075 }
3076 if font_data.len() > 4 && &font_data[0..4] == b"OTTO" {
3078 let pdf_cid_to_gid = if let Some(map_obj) = cid_font_dict.get(b"CIDToGIDMap") {
3080 match resolver.stream_data_from_obj(map_obj) {
3081 Ok(stream_data) => {
3082 let mut gid_map = Vec::with_capacity(stream_data.len() / 2);
3083 for pair in stream_data.chunks_exact(2) {
3084 gid_map.push(u16::from_be_bytes([pair[0], pair[1]]));
3085 }
3086 Some(gid_map)
3087 }
3088 Err(_) => None,
3089 }
3090 } else {
3091 None
3092 };
3093 let cff_is_cid = is_cff_cid_keyed(&font_data);
3097 return create_cid_cff_from_otf(
3098 &font_data,
3099 default_width,
3100 cid_widths,
3101 &ordering,
3102 pdf_cid_to_gid,
3103 !cff_is_cid,
3104 code_lengths,
3105 code_to_cid.clone(),
3106 wmode,
3107 dw2,
3108 w2.clone(),
3109 );
3110 }
3111 if font_data.starts_with(b"%!")
3114 && font_data.windows(16).any(|w| w == b"Resource-CIDFont")
3115 {
3116 return create_cid_from_ps_cidfont(
3117 &font_data,
3118 default_width,
3119 cid_widths,
3120 code_lengths,
3121 code_to_cid.clone(),
3122 wmode,
3123 dw2,
3124 w2.clone(),
3125 );
3126 }
3127 let is_type1 = font_data.starts_with(b"%!") || font_data.first() == Some(&0x80);
3131 if is_type1 {
3132 return create_cid_from_type1(
3133 &font_data,
3134 default_width,
3135 cid_widths,
3136 &to_unicode,
3137 code_lengths,
3138 code_to_cid.clone(),
3139 wmode,
3140 dw2,
3141 w2.clone(),
3142 );
3143 }
3144 let fonts = parse_cff(&font_data)
3145 .map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
3146 let font = fonts
3147 .into_iter()
3148 .next()
3149 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
3150 let cs_count = font.char_strings.len();
3164 let is_adobe_cjk_registry = matches!(
3165 ordering.as_slice(),
3166 b"GB1" | b"CNS1" | b"Japan1" | b"Japan2" | b"Korea1" | b"KR"
3167 );
3168 if cs_count > 0 && cid_widths.len() > cs_count * 4 && is_adobe_cjk_registry {
3169 } else {
3171 let fm = font.font_matrix;
3172 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
3173 return Ok(PdfFont::CidCff(CidCffPdfFont {
3174 font,
3175 default_width,
3176 cid_widths,
3177 font_matrix,
3178 cmap: None,
3179 pdf_cid_to_gid: None,
3180 identity_cid_to_gid: false,
3181 ordering: ordering.clone(),
3182 code_lengths,
3183 code_to_cid: code_to_cid.clone(),
3184 wmode,
3185 dw2,
3186 w2: w2.clone(),
3187 type1_paths: None,
3188 }));
3189 }
3190 }
3191 {
3193 let base_font = cid_font_dict
3194 .get_name(b"BaseFont")
3195 .map(|n| String::from_utf8_lossy(n).to_string())
3196 .unwrap_or_default();
3197 let sys_data = if ucs2_encoding {
3198 load_system_truetype_font(&base_font)
3206 .or_else(|_| load_cjk_fallback_font(&ordering, &base_font))
3207 .or_else(|_| load_system_truetype_font("DejaVuSans"))
3208 .or_else(|_| load_system_truetype_font("LiberationSans"))
3209 .or_else(|_| load_system_truetype_font("NimbusSans"))?
3210 } else {
3211 load_system_truetype_font(&base_font)
3212 .or_else(|_| load_cjk_fallback_font(&ordering, &base_font))?
3213 };
3214 let identity = ordering == b"Identity";
3219 let is_otto = sys_data.len() > 4 && &sys_data[0..4] == b"OTTO";
3223 let is_ttc_cff = sys_data.len() > 16 && &sys_data[0..4] == b"ttcf" && {
3224 let off = u32::from_be_bytes([
3225 sys_data[12],
3226 sys_data[13],
3227 sys_data[14],
3228 sys_data[15],
3229 ]) as usize;
3230 off + 4 <= sys_data.len() && &sys_data[off..off + 4] == b"OTTO"
3231 };
3232 if is_otto || is_ttc_cff {
3233 return create_cid_cff_from_otf(
3234 &sys_data,
3235 default_width,
3236 cid_widths,
3237 &ordering,
3238 None,
3239 identity,
3240 code_lengths,
3241 code_to_cid.clone(),
3242 wmode,
3243 dw2,
3244 w2.clone(),
3245 );
3246 }
3247 let data = sys_data;
3248 let units_per_em = get_units_per_em(&data) as f64;
3249 let cmap = parse_cmap(&data);
3250 Ok(PdfFont::CidTrueType(CidTrueTypePdfFont {
3251 data,
3252 default_width,
3253 cid_widths,
3254 cmap,
3255 units_per_em,
3256 identity_cid_to_gid: false,
3257 substituted: true,
3258 cid_to_gid_map: None,
3259 to_unicode,
3260 ordering: ordering.clone(),
3261 ucs2_encoding,
3262 code_lengths,
3263 code_to_cid: code_to_cid.clone(),
3264 wmode,
3265 dw2,
3266 w2,
3267 }))
3268 }
3269 }
3270 _ => Err(PdfError::Other(format!(
3271 "Unsupported CIDFont subtype: {}",
3272 String::from_utf8_lossy(cid_subtype)
3273 ))),
3274 }
3275}
3276
3277fn extract_hex_tokens(s: &str) -> Vec<&str> {
3282 let mut tokens = Vec::new();
3283 let mut rest = s;
3284 while let Some(start) = rest.find('<') {
3285 rest = &rest[start + 1..];
3286 if let Some(end) = rest.find('>') {
3287 let hex = rest[..end].trim();
3288 if !hex.is_empty() {
3289 tokens.push(hex);
3290 }
3291 rest = &rest[end + 1..];
3292 } else {
3293 break;
3294 }
3295 }
3296 tokens
3297}
3298
3299fn hex_to_unicode(hex: &str) -> Option<u32> {
3302 if hex.len() <= 4 {
3303 u32::from_str_radix(hex, 16).ok()
3304 } else {
3305 match hex {
3308 "00660066" => Some(0xFB00), "00660069" => Some(0xFB01), "0066006C" => Some(0xFB02), "006600660069" => Some(0xFB03), "00660066006C" => Some(0xFB04), "017F0074" => Some(0xFB05), "00730074" => Some(0xFB06), _ => {
3316 u32::from_str_radix(&hex[..hex.len().min(4)], 16).ok()
3318 }
3319 }
3320 }
3321}
3322
3323fn parse_to_unicode(data: &[u8]) -> HashMap<u16, u32> {
3328 let mut map = HashMap::new();
3329 let text = String::from_utf8_lossy(data);
3330
3331 let mut in_bfchar = false;
3334 let mut in_bfrange = false;
3335 let mut range_tokens: Vec<&str> = Vec::new();
3336
3337 for line in text.lines() {
3338 let trimmed = line.trim();
3339 if trimmed.ends_with("beginbfchar") {
3340 in_bfchar = true;
3341 continue;
3342 }
3343 if trimmed == "endbfchar" {
3344 in_bfchar = false;
3345 continue;
3346 }
3347 if trimmed.ends_with("beginbfrange") {
3348 in_bfrange = true;
3349 range_tokens.clear();
3350 continue;
3351 }
3352 if trimmed == "endbfrange" {
3353 in_bfrange = false;
3354 range_tokens.clear();
3355 continue;
3356 }
3357
3358 if in_bfchar {
3359 let tokens = extract_hex_tokens(trimmed);
3360 if tokens.len() >= 2
3361 && let Ok(cid) = u32::from_str_radix(tokens[0], 16)
3362 && let Some(unicode) = hex_to_unicode(tokens[1])
3363 {
3364 map.insert(cid as u16, unicode);
3365 }
3366 }
3367
3368 if in_bfrange {
3369 let line_tokens = extract_hex_tokens(trimmed);
3370 if trimmed.contains('[') {
3372 let all_before_bracket: Vec<&str> = {
3374 let before = trimmed.split('[').next().unwrap_or("");
3375 extract_hex_tokens(before)
3376 };
3377 let in_bracket = {
3378 let after_open = trimmed.split('[').nth(1).unwrap_or("");
3379 let before_close = after_open.split(']').next().unwrap_or(after_open);
3380 extract_hex_tokens(before_close)
3381 };
3382 if all_before_bracket.len() >= 2
3383 && let (Some(start), Some(end)) = (
3384 u32::from_str_radix(all_before_bracket[0], 16).ok(),
3385 u32::from_str_radix(all_before_bracket[1], 16).ok(),
3386 )
3387 {
3388 for (j, cid) in (start..=end).enumerate() {
3389 if j < in_bracket.len()
3390 && let Some(u) = hex_to_unicode(in_bracket[j])
3391 {
3392 map.insert(cid as u16, u);
3393 }
3394 }
3395 }
3396 } else if line_tokens.len() >= 3 {
3397 if let (Some(start), Some(end), Some(mut dst)) = (
3399 u32::from_str_radix(line_tokens[0], 16).ok(),
3400 u32::from_str_radix(line_tokens[1], 16).ok(),
3401 hex_to_unicode(line_tokens[2]),
3402 ) {
3403 for cid in start..=end {
3404 map.insert(cid as u16, dst);
3405 dst += 1;
3406 }
3407 }
3408 }
3409 }
3410 }
3411
3412 map
3413}
3414
3415fn parse_cid_widths(cid_font_dict: &PdfDict, resolver: &Resolver) -> HashMap<u16, f64> {
3416 let mut widths = HashMap::new();
3417 let w_obj = match cid_font_dict.get(b"W") {
3419 Some(obj) => match resolver.deref(obj) {
3420 Ok(resolved) => resolved,
3421 Err(_) => return widths,
3422 },
3423 None => return widths,
3424 };
3425 let w_arr = match w_obj.as_array() {
3426 Some(arr) => arr,
3427 None => return widths,
3428 };
3429 let mut i = 0;
3430 while i < w_arr.len() {
3431 let first_cid = match &w_arr[i] {
3432 PdfObj::Int(n) => *n as u16,
3433 _ => break,
3434 };
3435 i += 1;
3436 if i >= w_arr.len() {
3437 break;
3438 }
3439 let next = resolver.deref(&w_arr[i]).unwrap_or(w_arr[i].clone());
3441 match &next {
3442 PdfObj::Array(arr) => {
3443 for (j, w_obj) in arr.iter().enumerate() {
3445 let w_val = w_obj
3447 .as_f64()
3448 .or_else(|| resolver.deref(w_obj).ok().and_then(|r| r.as_f64()))
3449 .unwrap_or(0.0);
3450 widths.insert(first_cid + j as u16, w_val / 1000.0);
3451 }
3452 i += 1;
3453 }
3454 _ => {
3455 let last_cid = match &next {
3457 PdfObj::Int(n) => *n as u16,
3458 _ => first_cid,
3459 };
3460 i += 1;
3461 let w = if i < w_arr.len() {
3462 let obj = &w_arr[i];
3463 obj.as_f64()
3464 .or_else(|| resolver.deref(obj).ok().and_then(|r| r.as_f64()))
3465 .unwrap_or(0.0)
3466 / 1000.0
3467 } else {
3468 0.0
3469 };
3470 i += 1;
3471 for cid in first_cid..=last_cid {
3472 widths.insert(cid, w);
3473 }
3474 }
3475 }
3476 }
3477 widths
3478}
3479
3480fn parse_cid_w2(cid_font_dict: &PdfDict, resolver: &Resolver) -> HashMap<u16, [f64; 3]> {
3486 let mut metrics = HashMap::new();
3487 let w2_obj = match cid_font_dict.get(b"W2") {
3488 Some(obj) => match resolver.deref(obj) {
3489 Ok(resolved) => resolved,
3490 Err(_) => return metrics,
3491 },
3492 None => return metrics,
3493 };
3494 let arr = match w2_obj.as_array() {
3495 Some(a) => a,
3496 None => return metrics,
3497 };
3498 let mut i = 0;
3499 while i < arr.len() {
3500 let first_cid = match &arr[i] {
3501 PdfObj::Int(n) => *n as u16,
3502 _ => break,
3503 };
3504 i += 1;
3505 if i >= arr.len() {
3506 break;
3507 }
3508 let next = resolver.deref(&arr[i]).unwrap_or(arr[i].clone());
3509 match &next {
3510 PdfObj::Array(sub) => {
3511 let vals: Vec<f64> = sub.iter().filter_map(|o| o.as_f64()).collect();
3513 for (j, chunk) in vals.chunks(3).enumerate() {
3514 if chunk.len() == 3 {
3515 metrics.insert(first_cid + j as u16, [chunk[0], chunk[1], chunk[2]]);
3516 }
3517 }
3518 i += 1;
3519 }
3520 _ => {
3521 let last_cid = match &next {
3523 PdfObj::Int(n) => *n as u16,
3524 _ => first_cid,
3525 };
3526 i += 1;
3527 if i + 2 < arr.len() {
3528 let w1 = arr[i].as_f64().unwrap_or(-1000.0);
3529 let vx = arr[i + 1].as_f64().unwrap_or(0.0);
3530 let vy = arr[i + 2].as_f64().unwrap_or(880.0);
3531 i += 3;
3532 for cid in first_cid..=last_cid {
3533 metrics.insert(cid, [w1, vx, vy]);
3534 }
3535 } else {
3536 break;
3537 }
3538 }
3539 }
3540 }
3541 metrics
3542}
3543
3544fn strip_pfb(data: &[u8]) -> Vec<u8> {
3550 if data.len() < 2 || data[0] != 0x80 {
3551 return data.to_vec();
3552 }
3553 let mut result = Vec::with_capacity(data.len());
3554 let mut pos = 0;
3555 while pos + 6 <= data.len() && data[pos] == 0x80 {
3556 let segment_type = data[pos + 1];
3557 if segment_type == 3 {
3558 break; }
3560 let len = u32::from_le_bytes([data[pos + 2], data[pos + 3], data[pos + 4], data[pos + 5]])
3561 as usize;
3562 pos += 6;
3563 let end = (pos + len).min(data.len());
3564 result.extend_from_slice(&data[pos..end]);
3565 pos = end;
3566 }
3567 result
3568}
3569
3570impl PdfFont {
3573 pub fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
3576 match self {
3577 PdfFont::Type1(f) => f.glyph_path(char_code),
3578 PdfFont::TrueType(f) => f.glyph_path(char_code),
3579 PdfFont::Cff(f) => f.glyph_path(char_code),
3580 PdfFont::CidTrueType(f) => f.glyph_path_cid(char_code as u16),
3581 PdfFont::CidCff(f) => f.glyph_path_cid(char_code as u16),
3582 PdfFont::Type3(_) => None,
3583 }
3584 }
3585
3586 pub fn glyph_path_cid(&self, cid: u16) -> Option<PsPath> {
3588 match self {
3589 PdfFont::CidTrueType(f) => f.glyph_path_cid(cid),
3590 PdfFont::CidCff(f) => f.glyph_path_cid(cid),
3591 _ => self.glyph_path(cid as u8),
3592 }
3593 }
3594
3595 pub fn glyph_path_unicode(&self, unicode: u16) -> Option<PsPath> {
3598 match self {
3599 PdfFont::CidTrueType(f) => f.glyph_path_unicode(unicode),
3600 PdfFont::CidCff(f) => f.glyph_path_unicode(unicode),
3601 _ => None,
3602 }
3603 }
3604
3605 pub fn glyph_width_unicode(&self, unicode: u16) -> f64 {
3607 match self {
3608 PdfFont::CidTrueType(f) => f.glyph_width_unicode(unicode),
3609 _ => 0.0,
3610 }
3611 }
3612
3613 pub fn glyph_width(&self, char_code: u8) -> f64 {
3615 match self {
3616 PdfFont::Type1(f) => f.widths[char_code as usize],
3617 PdfFont::TrueType(f) => f.widths[char_code as usize],
3618 PdfFont::Cff(f) => f.widths[char_code as usize],
3619 PdfFont::CidTrueType(f) => f.glyph_width_cid(char_code as u16),
3620 PdfFont::CidCff(f) => f.glyph_width_cid(char_code as u16),
3621 PdfFont::Type3(f) => f.widths[char_code as usize],
3622 }
3623 }
3624
3625 pub fn glyph_width_cid(&self, cid: u16) -> f64 {
3627 match self {
3628 PdfFont::CidTrueType(f) => f.glyph_width_cid(cid),
3629 PdfFont::CidCff(f) => f.glyph_width_cid(cid),
3630 _ => self.glyph_width(cid as u8),
3631 }
3632 }
3633
3634 pub fn font_matrix(&self) -> Matrix {
3639 match self {
3640 PdfFont::Type1(f) => f.font_matrix,
3641 PdfFont::TrueType(_) | PdfFont::CidTrueType(_) => Matrix::identity(),
3642 PdfFont::Cff(f) => f.font_matrix,
3643 PdfFont::CidCff(_) => Matrix::identity(),
3644 PdfFont::Type3(f) => f.font_matrix,
3645 }
3646 }
3647
3648 pub fn is_composite(&self) -> bool {
3650 matches!(self, PdfFont::CidTrueType(_) | PdfFont::CidCff(_))
3651 }
3652
3653 pub fn wmode(&self) -> u8 {
3655 match self {
3656 PdfFont::CidTrueType(f) => f.wmode,
3657 PdfFont::CidCff(f) => f.wmode,
3658 _ => 0,
3659 }
3660 }
3661
3662 pub fn dw2(&self) -> [f64; 2] {
3665 match self {
3666 PdfFont::CidTrueType(f) => f.dw2,
3667 PdfFont::CidCff(f) => f.dw2,
3668 _ => [880.0, -1000.0],
3669 }
3670 }
3671
3672 pub fn vertical_metrics_cid(&self, cid: u16) -> [f64; 3] {
3675 match self {
3676 PdfFont::CidTrueType(f) => {
3677 if let Some(&m) = f.w2.get(&cid) {
3678 m
3679 } else {
3680 let w0 = f.cid_widths.get(&cid).copied().unwrap_or(f.default_width) * 1000.0;
3682 [f.dw2[1], w0 / 2.0, f.dw2[0]]
3683 }
3684 }
3685 PdfFont::CidCff(f) => {
3686 if let Some(&m) = f.w2.get(&cid) {
3687 m
3688 } else {
3689 let w0 = f.cid_widths.get(&cid).copied().unwrap_or(f.default_width) * 1000.0;
3690 [f.dw2[1], w0 / 2.0, f.dw2[0]]
3691 }
3692 }
3693 _ => [-1000.0, 500.0, 880.0],
3694 }
3695 }
3696
3697 pub fn has_cid_glyph(&self, cid: u16) -> bool {
3701 match self {
3702 PdfFont::CidTrueType(f) => f.has_glyph(cid),
3703 PdfFont::CidCff(_) => true, _ => false,
3705 }
3706 }
3707
3708 pub fn resolve_code_to_cid(&self, code: u32) -> u32 {
3711 match self {
3712 PdfFont::CidTrueType(f) => f.code_to_cid.get(&code).copied().unwrap_or(code),
3713 PdfFont::CidCff(f) => f.code_to_cid.get(&code).copied().unwrap_or(code),
3714 _ => code,
3715 }
3716 }
3717
3718 pub fn code_width(&self, first_byte: u8) -> usize {
3721 match self {
3722 PdfFont::CidTrueType(f) => {
3723 let w = f.code_lengths[first_byte as usize];
3724 if w == 0 { 2 } else { w as usize }
3725 }
3726 PdfFont::CidCff(f) => {
3727 let w = f.code_lengths[first_byte as usize];
3728 if w == 0 { 2 } else { w as usize }
3729 }
3730 _ => 1,
3731 }
3732 }
3733
3734 pub fn is_type3(&self) -> bool {
3736 matches!(self, PdfFont::Type3(_))
3737 }
3738
3739 pub fn type3_char_proc(&self, char_code: u8) -> Option<&[u8]> {
3741 match self {
3742 PdfFont::Type3(f) => f.char_procs.get(&char_code).map(|v| v.as_slice()),
3743 _ => None,
3744 }
3745 }
3746
3747 pub fn type3_resources(&self) -> Option<&PdfDict> {
3749 match self {
3750 PdfFont::Type3(f) => Some(&f.resources),
3751 _ => None,
3752 }
3753 }
3754}
3755
3756impl Type1PdfFont {
3757 fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
3758 let glyph_name = self.encoding[char_code as usize].as_deref();
3759 let charstring = glyph_name
3760 .and_then(|name| self.font.charstrings.get(name))
3761 .or_else(|| {
3762 if !self.builtin_fallback {
3763 return None;
3764 }
3765 let builtin = self.font.encoding.get(char_code as usize)?;
3766 if builtin != ".notdef" && glyph_name.map_or(true, |n| n != builtin) {
3767 self.font.charstrings.get(builtin.as_str())
3768 } else {
3769 None
3770 }
3771 })?;
3772 let cs_lookup =
3774 |name: &str| -> Option<Vec<u8>> { self.font.charstrings.get(name).cloned() };
3775 let result = execute_charstring_mm(
3776 charstring,
3777 &self.font.subrs,
3778 self.font.len_iv,
3779 false,
3780 Some(&cs_lookup),
3781 self.weight_vector.as_deref(),
3782 )
3783 .ok()?;
3784 if self.per_char_width_scale {
3788 let pdf_w = self.widths[char_code as usize];
3789 let font_w = result.width_x * self.font_matrix.a;
3790 if font_w.abs() > 0.001 && pdf_w > 0.001 && (pdf_w / font_w - 1.0).abs() > 0.01 {
3791 return Some(result.path.transform(&Matrix::scale(pdf_w / font_w, 1.0)));
3792 }
3793 }
3794 Some(result.path)
3795 }
3796}
3797
3798impl TrueTypePdfFont {
3799 fn detect_gid_hex(encoding: &[Option<String>; 256]) -> bool {
3802 encoding.iter().any(|name| {
3803 if let Some(n) = name {
3804 n.starts_with('g')
3805 && n.len() > 1
3806 && n[1..].bytes().all(|b| b.is_ascii_hexdigit())
3807 && n[1..]
3808 .bytes()
3809 .any(|b| b.is_ascii_hexdigit() && !b.is_ascii_digit())
3810 } else {
3811 false
3812 }
3813 })
3814 }
3815
3816 fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
3817 let gid = self.char_code_to_gid(char_code);
3818 let gid = gid?;
3819 let path = skrifa_glyph_path(&self.data, gid, self.units_per_em).or_else(|| {
3820 let glyf_data = get_glyf_data(&self.data, gid)?;
3822 let data_ref = &self.data;
3823 let p = parse_glyf_to_path(&glyf_data, &|cid| get_glyf_data(data_ref, cid));
3824 if p.is_empty() { None } else { Some(p) }
3825 })?;
3826 let scale = 1.0 / self.units_per_em;
3827 let m = Matrix::scale(scale, scale);
3828 Some(path.transform(&m))
3829 }
3830
3831 fn char_code_to_gid(&self, char_code: u8) -> Option<u16> {
3832 if self.identity_gid {
3836 if let Some(&gid) = self.cmap.get(&(char_code as u32)) {
3837 return Some(gid);
3838 }
3839 if let Some(&gid) = self.cmap.get(&(0xF000 + char_code as u32)) {
3840 return Some(gid);
3841 }
3842 return Some(char_code as u16);
3843 }
3844 if let Some(glyph_name) = &self.encoding[char_code as usize] {
3845 if self.cmap_is_unicode {
3850 if let Some(unicode) = stet_fonts::agl::glyph_name_to_unicode(glyph_name)
3851 && let Some(&gid) = self.cmap.get(&(unicode as u32))
3852 {
3853 return Some(gid);
3854 }
3855 }
3856 }
3857 if self.cmap_is_unicode {
3863 if let Some(&unicode) = self.to_unicode.get(&(char_code as u16))
3864 && let Some(&gid) = self.cmap.get(&unicode)
3865 {
3866 return Some(gid);
3867 }
3868 }
3869 if let Some(glyph_name) = &self.encoding[char_code as usize] {
3870 if glyph_name.starts_with('g')
3874 && glyph_name.len() > 1
3875 && glyph_name[1..].bytes().all(|b| b.is_ascii_hexdigit())
3876 {
3877 let suffix = &glyph_name[1..];
3878 let gid = if self.gid_hex {
3879 u16::from_str_radix(suffix, 16).ok()
3880 } else {
3881 suffix.parse::<u16>().ok()
3882 };
3883 if let Some(gid) = gid {
3884 return Some(gid);
3885 }
3886 }
3887 }
3888 if let Some(&gid) = self.cmap.get(&(char_code as u32)) {
3891 return Some(gid);
3892 }
3893 if let Some(glyph_name) = &self.encoding[char_code as usize] {
3894 if let Some(&gid) = self.post_name_to_gid.get(glyph_name.as_str()) {
3896 return Some(gid);
3897 }
3898 }
3899 if let Some(&gid) = self.cmap.get(&(0xF000 + char_code as u32)) {
3901 return Some(gid);
3902 }
3903 if let Some(&unicode) = self.to_unicode.get(&(char_code as u16)) {
3908 if let Some(name) = stet_fonts::system_fonts::unicode_to_glyph_name(unicode) {
3909 if let Some(&gid) = self.post_name_to_gid.get(name) {
3910 return Some(gid);
3911 }
3912 }
3913 if let Ok(font_ref) = skrifa::FontRef::new(&self.data) {
3914 let charmap = font_ref.charmap();
3915 if let Some(gid) = charmap.map(unicode) {
3916 return Some(gid.to_u32() as u16);
3917 }
3918 }
3919 if !self.cmap.is_empty() {
3924 use stet_fonts::truetype::{find_table, read_u16};
3925 let mapped: std::collections::HashSet<u16> = self.cmap.values().copied().collect();
3926 let num_glyphs = find_table(&self.data, b"maxp")
3927 .map(|(off, _)| read_u16(&self.data, off + 4))
3928 .unwrap_or(0);
3929 for gid in 0..num_glyphs {
3933 if mapped.contains(&gid) {
3934 continue;
3935 }
3936 if let Some(glyf_data) = get_glyf_data(&self.data, gid) {
3937 if glyf_data.len() >= 2 {
3938 let num_contours = stet_fonts::truetype::read_i16(&glyf_data, 0);
3939 if num_contours < 0 {
3940 return Some(gid);
3941 }
3942 }
3943 }
3944 }
3945 }
3946 }
3947 if self.cmap.is_empty() {
3948 Some(char_code as u16)
3950 } else {
3951 None
3952 }
3953 }
3954}
3955
3956impl CidTrueTypePdfFont {
3957 fn resolve_cid(&self, code: u16) -> u16 {
3959 if self.ucs2_encoding && !self.ordering.is_empty() && self.code_to_cid.is_empty() {
3963 super::cid_unicode::unicode_to_cid(&self.ordering, code as u32).unwrap_or(code)
3964 } else {
3965 code
3966 }
3967 }
3968
3969 fn glyph_path_cid(&self, cid: u16) -> Option<PsPath> {
3970 if std::env::var("STET_DEBUG_TEXT").is_ok() {
3971 eprintln!(
3972 "[cid_tt] cid={} sub={} ordering={} identity={} to_unicode={} cmap={} cid_to_gid_map={}",
3973 cid,
3974 self.substituted,
3975 String::from_utf8_lossy(&self.ordering),
3976 self.identity_cid_to_gid,
3977 !self.to_unicode.is_empty(),
3978 !self.cmap.is_empty(),
3979 self.cid_to_gid_map.is_some()
3980 );
3981 }
3982 let gid = if self.ucs2_encoding && !self.cmap.is_empty() && self.code_to_cid.is_empty() {
3983 if let Some(&g) = self.cmap.get(&(cid as u32)) {
3985 g
3986 } else {
3987 return None;
3988 }
3989 } else if self.ucs2_encoding && self.substituted && !self.ordering.is_empty() {
3990 let unicode = super::cid_unicode::cid_to_unicode(&self.ordering, cid)?;
3992 *self.cmap.get(&unicode)?
3993 } else if let Some(ref map) = self.cid_to_gid_map {
3994 *map.get(cid as usize).unwrap_or(&0)
3996 } else if self.substituted && !self.to_unicode.is_empty() {
3997 if let Some(&unicode) = self.to_unicode.get(&cid) {
3999 *self.cmap.get(&unicode)?
4000 } else {
4001 cid
4005 }
4006 } else if self.substituted && !self.ordering.is_empty() && self.ordering != b"Identity" {
4007 let unicode = super::cid_unicode::cid_to_unicode(&self.ordering, cid)?;
4009 *self.cmap.get(&unicode)?
4010 } else if self.identity_cid_to_gid {
4011 cid
4013 } else if self.substituted && !self.cmap.is_empty() {
4014 if let Some(&g) = self.cmap.get(&(cid as u32)) {
4017 g
4018 } else {
4019 cid
4020 }
4021 } else if !self.cmap.is_empty() {
4022 *self.cmap.get(&(cid as u32))?
4024 } else {
4025 cid
4026 };
4027 let path = skrifa_glyph_path(&self.data, gid, self.units_per_em).or_else(|| {
4028 let glyf_data = get_glyf_data(&self.data, gid)?;
4031 let data_ref = &self.data;
4032 let p = parse_glyf_to_path(&glyf_data, &|cid| get_glyf_data(data_ref, cid));
4033 if p.is_empty() || p.segments.len() > 10_000 {
4036 None
4037 } else {
4038 Some(p)
4039 }
4040 });
4041 let path = path?;
4042 let scale = 1.0 / self.units_per_em;
4043 let m = if self.substituted {
4047 let pdf_w = self.cid_widths.get(&cid).copied();
4052 let font_w =
4053 hmtx_advance_width(&self.data, gid, self.units_per_em).unwrap_or(0.0) / 1000.0;
4054 if let Some(pw) = pdf_w {
4055 if font_w > 0.001 && pw > 0.001 {
4056 Matrix::new(scale * pw / font_w, 0.0, 0.0, scale, 0.0, 0.0)
4057 } else {
4058 Matrix::scale(scale, scale)
4059 }
4060 } else {
4061 Matrix::scale(scale, scale)
4062 }
4063 } else {
4064 Matrix::scale(scale, scale)
4065 };
4066 Some(path.transform(&m))
4067 }
4068
4069 fn has_glyph(&self, cid: u16) -> bool {
4073 if self.cid_widths.contains_key(&cid) {
4077 return true;
4078 }
4079 let gid = if let Some(ref map) = self.cid_to_gid_map {
4081 *map.get(cid as usize).unwrap_or(&0)
4082 } else if self.substituted && !self.to_unicode.is_empty() {
4083 if let Some(&unicode) = self.to_unicode.get(&cid) {
4085 if let Some(&g) = self.cmap.get(&unicode) {
4086 g
4087 } else {
4088 return false;
4089 }
4090 } else {
4091 return false;
4092 }
4093 } else if self.identity_cid_to_gid {
4094 cid
4095 } else {
4096 return true; };
4098 let num_glyphs = stet_fonts::truetype::get_num_glyphs(&self.data);
4100 (gid as u32) < num_glyphs
4101 }
4102
4103 fn glyph_width_cid(&self, cid: u16) -> f64 {
4104 let resolved = self.resolve_cid(cid);
4105 if let Some(&w) = self.cid_widths.get(&resolved) {
4106 return w;
4107 }
4108 if self.substituted && !self.to_unicode.is_empty() {
4113 if let Some(&unicode) = self.to_unicode.get(&cid) {
4114 if let Some(&gid) = self.cmap.get(&unicode) {
4115 if let Some(w) = hmtx_advance_width(&self.data, gid, self.units_per_em) {
4116 return w / 1000.0;
4117 }
4118 }
4119 }
4120 }
4121 self.default_width
4122 }
4123
4124 fn glyph_path_unicode(&self, unicode: u16) -> Option<PsPath> {
4127 let &gid = self.cmap.get(&(unicode as u32))?;
4128 let path = skrifa_glyph_path(&self.data, gid, self.units_per_em)?;
4129 let scale = 1.0 / self.units_per_em;
4130 let m = Matrix::scale(scale, scale);
4131 Some(path.transform(&m))
4132 }
4133
4134 fn glyph_width_unicode(&self, unicode: u16) -> f64 {
4137 if let Some(&gid) = self.cmap.get(&(unicode as u32)) {
4138 hmtx_advance_width(&self.data, gid, self.units_per_em)
4141 .map(|w| w / 1000.0)
4142 .unwrap_or(self.default_width)
4143 } else {
4144 self.default_width
4145 }
4146 }
4147}
4148
4149impl CidCffPdfFont {
4150 fn glyph_path_at_gid(&self, gid: usize) -> Option<PsPath> {
4152 if gid >= self.font.char_strings.len() {
4153 return None;
4154 }
4155 let (default_width_x, nominal_width_x, local_subrs, fd_font_matrix) = if self.font.is_cid
4156 && !self.font.fd_select.is_empty()
4157 && !self.font.fd_array.is_empty()
4158 {
4159 let fd_idx = *self.font.fd_select.get(gid).unwrap_or(&0) as usize;
4160 if let Some(fd) = self.font.fd_array.get(fd_idx) {
4161 (
4162 fd.default_width_x,
4163 fd.nominal_width_x,
4164 &fd.local_subrs,
4165 fd.font_matrix,
4166 )
4167 } else {
4168 (
4169 self.font.default_width_x,
4170 self.font.nominal_width_x,
4171 &self.font.local_subrs,
4172 None,
4173 )
4174 }
4175 } else {
4176 (
4177 self.font.default_width_x,
4178 self.font.nominal_width_x,
4179 &self.font.local_subrs,
4180 None,
4181 )
4182 };
4183 let result = execute_type2_charstring(
4184 &self.font.char_strings[gid],
4185 local_subrs,
4186 &self.font.global_subrs,
4187 default_width_x,
4188 nominal_width_x,
4189 false,
4190 )
4191 .ok()?;
4192 let effective_fm = if let Some(fd_fm) = fd_font_matrix {
4193 let fd = Matrix::new(fd_fm[0], fd_fm[1], fd_fm[2], fd_fm[3], fd_fm[4], fd_fm[5]);
4194 if fd.a.abs() < 0.01 || fd.d.abs() < 0.01 {
4195 fd
4196 } else {
4197 self.font_matrix.concat(&fd)
4198 }
4199 } else {
4200 self.font_matrix
4201 };
4202 Some(result.path.transform(&effective_fm))
4203 }
4204
4205 fn glyph_path_unicode(&self, unicode: u16) -> Option<PsPath> {
4207 let cmap = self.cmap.as_ref()?;
4208 let &gid = cmap.get(&(unicode as u32))?;
4209 self.glyph_path_at_gid(gid as usize)
4210 }
4211
4212 fn glyph_path_cid(&self, cid: u16) -> Option<PsPath> {
4213 if let Some(ref paths) = self.type1_paths {
4215 return paths.get(&cid).cloned();
4216 }
4217 let gid = if let Some(ref map) = self.pdf_cid_to_gid {
4221 *map.get(cid as usize).unwrap_or(&0) as usize
4223 } else if self.identity_cid_to_gid {
4224 cid as usize
4227 } else if let Some(ref cmap) = self.cmap {
4228 if !self.ordering.is_empty() && self.ordering != b"Identity" {
4233 let unicode = super::cid_unicode::cid_to_unicode(&self.ordering, cid)?;
4234 let gid_opt = cjk_fullwidth_alternative(unicode)
4239 .and_then(|alt| cmap.get(&alt))
4240 .or_else(|| cmap.get(&unicode));
4241 *gid_opt? as usize
4242 } else {
4243 *cmap.get(&(cid as u32))? as usize
4244 }
4245 } else if !self.font.cid_to_gid.is_empty() {
4246 let g = *self.font.cid_to_gid.get(cid as usize)?;
4247 if g == 0xFFFF {
4248 return None;
4249 }
4250 g as usize
4251 } else {
4252 cid as usize
4253 };
4254 self.glyph_path_at_gid(gid)
4255 }
4256
4257 fn glyph_width_cid(&self, cid: u16) -> f64 {
4258 self.cid_widths
4260 .get(&cid)
4261 .copied()
4262 .unwrap_or(self.default_width)
4263 }
4264}
4265
4266impl CffPdfFont {
4267 fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
4268 let glyph_name = self.encoding[char_code as usize].as_deref()?;
4269 let gid = self
4276 .font
4277 .charset
4278 .iter()
4279 .position(|name| name == glyph_name)
4280 .or_else(|| {
4281 let cff_gid = self
4282 .font
4283 .encoding
4284 .get(char_code as usize)
4285 .copied()
4286 .unwrap_or(0) as usize;
4287 if cff_gid > 0 && cff_gid < self.font.char_strings.len() {
4288 Some(cff_gid)
4289 } else {
4290 None
4291 }
4292 });
4293 let gid = gid?;
4294 if gid >= self.font.char_strings.len() {
4295 return None;
4296 }
4297 let result = execute_type2_charstring(
4298 &self.font.char_strings[gid],
4299 &self.font.local_subrs,
4300 &self.font.global_subrs,
4301 self.font.default_width_x,
4302 self.font.nominal_width_x,
4303 false,
4304 )
4305 .ok()?;
4306
4307 if let Some((adx, ady, bchar, achar)) = result.seac {
4309 return self.compose_seac(adx, ady, bchar, achar);
4310 }
4311
4312 Some(result.path)
4313 }
4314
4315 fn compose_seac(&self, adx: f64, ady: f64, bchar: u8, achar: u8) -> Option<PsPath> {
4318 use stet_fonts::encoding::STANDARD_ENCODING;
4319
4320 let base_name = STANDARD_ENCODING.get(bchar as usize).copied().unwrap_or("");
4321 let accent_name = STANDARD_ENCODING.get(achar as usize).copied().unwrap_or("");
4322
4323 let base_gid = self.font.charset.iter().position(|n| n == base_name)?;
4324 let accent_gid = self.font.charset.iter().position(|n| n == accent_name)?;
4325
4326 let base_result = execute_type2_charstring(
4327 &self.font.char_strings[base_gid],
4328 &self.font.local_subrs,
4329 &self.font.global_subrs,
4330 self.font.default_width_x,
4331 self.font.nominal_width_x,
4332 false,
4333 )
4334 .ok()?;
4335
4336 let accent_result = execute_type2_charstring(
4337 &self.font.char_strings[accent_gid],
4338 &self.font.local_subrs,
4339 &self.font.global_subrs,
4340 self.font.default_width_x,
4341 self.font.nominal_width_x,
4342 false,
4343 )
4344 .ok()?;
4345
4346 let mut combined = base_result.path;
4348 let offset = Matrix::translate(adx, ady);
4349 let shifted_accent = accent_result.path.transform(&offset);
4350 combined
4351 .segments
4352 .extend_from_slice(&shifted_accent.segments);
4353 Some(combined)
4354 }
4355}
4356
4357struct PsPathPen {
4359 path: PsPath,
4360 cur_x: f64,
4361 cur_y: f64,
4362}
4363
4364impl skrifa::outline::OutlinePen for PsPathPen {
4365 fn move_to(&mut self, x: f32, y: f32) {
4366 self.cur_x = x as f64;
4367 self.cur_y = y as f64;
4368 self.path
4369 .segments
4370 .push(PathSegment::MoveTo(self.cur_x, self.cur_y));
4371 }
4372 fn line_to(&mut self, x: f32, y: f32) {
4373 self.cur_x = x as f64;
4374 self.cur_y = y as f64;
4375 self.path
4376 .segments
4377 .push(PathSegment::LineTo(self.cur_x, self.cur_y));
4378 }
4379 fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
4380 let cx = cx as f64;
4381 let cy = cy as f64;
4382 let ex = x as f64;
4383 let ey = y as f64;
4384 let cp1x = self.cur_x + 2.0 / 3.0 * (cx - self.cur_x);
4386 let cp1y = self.cur_y + 2.0 / 3.0 * (cy - self.cur_y);
4387 let cp2x = ex + 2.0 / 3.0 * (cx - ex);
4388 let cp2y = ey + 2.0 / 3.0 * (cy - ey);
4389 self.cur_x = ex;
4390 self.cur_y = ey;
4391 self.path.segments.push(PathSegment::CurveTo {
4392 x1: cp1x,
4393 y1: cp1y,
4394 x2: cp2x,
4395 y2: cp2y,
4396 x3: ex,
4397 y3: ey,
4398 });
4399 }
4400 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
4401 self.cur_x = x as f64;
4402 self.cur_y = y as f64;
4403 self.path.segments.push(PathSegment::CurveTo {
4404 x1: cx0 as f64,
4405 y1: cy0 as f64,
4406 x2: cx1 as f64,
4407 y2: cy1 as f64,
4408 x3: self.cur_x,
4409 y3: self.cur_y,
4410 });
4411 }
4412 fn close(&mut self) {
4413 self.path.segments.push(PathSegment::ClosePath);
4414 }
4415}
4416
4417pub(crate) fn winansi_byte_to_unicode(byte: u8) -> u16 {
4426 match byte {
4427 0x80 => 0x20AC, 0x82 => 0x201A, 0x83 => 0x0192, 0x84 => 0x201E, 0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02C6, 0x89 => 0x2030, 0x8A => 0x0160, 0x8B => 0x2039, 0x8C => 0x0152, 0x8E => 0x017D, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201C, 0x94 => 0x201D, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014, 0x98 => 0x02DC, 0x99 => 0x2122, 0x9A => 0x0161, 0x9B => 0x203A, 0x9C => 0x0153, 0x9E => 0x017E, 0x9F => 0x0178, _ => byte as u16,
4455 }
4456}
4457
4458fn hmtx_advance_width(font_data: &[u8], gid: u16, units_per_em: f64) -> Option<f64> {
4461 use stet_fonts::truetype::{find_table, read_u16};
4462 let (hhea_off, _) = find_table(font_data, b"hhea")?;
4463 let (hmtx_off, _) = find_table(font_data, b"hmtx")?;
4464 if hhea_off + 36 > font_data.len() {
4465 return None;
4466 }
4467 let num_h_metrics = read_u16(font_data, hhea_off + 34) as usize;
4468 let gid = gid as usize;
4469 let advance = if gid < num_h_metrics {
4470 let offset = hmtx_off + gid * 4;
4471 if offset + 2 > font_data.len() {
4472 return None;
4473 }
4474 read_u16(font_data, offset)
4475 } else {
4476 if num_h_metrics == 0 {
4478 return None;
4479 }
4480 let offset = hmtx_off + (num_h_metrics - 1) * 4;
4481 if offset + 2 > font_data.len() {
4482 return None;
4483 }
4484 read_u16(font_data, offset)
4485 };
4486 Some(advance as f64 / units_per_em * 1000.0)
4488}
4489
4490fn skrifa_glyph_path(font_data: &[u8], gid: u16, units_per_em: f64) -> Option<PsPath> {
4491 let font_ref = skrifa::FontRef::from_index(font_data, 0).ok()?;
4493 let outlines = font_ref.outline_glyphs();
4494 let glyph = outlines.get(skrifa::GlyphId::new(gid as u32))?;
4495
4496 let hinting = skrifa::outline::HintingInstance::new(
4500 &outlines,
4501 skrifa::prelude::Size::new(units_per_em as f32),
4502 skrifa::instance::LocationRef::default(),
4503 skrifa::outline::HintingOptions {
4504 engine: skrifa::outline::Engine::Interpreter,
4505 target: skrifa::outline::Target::Mono,
4506 },
4507 )
4508 .ok();
4509
4510 let mut pen = PsPathPen {
4511 path: PsPath::new(),
4512 cur_x: 0.0,
4513 cur_y: 0.0,
4514 };
4515
4516 let result = if let Some(ref instance) = hinting {
4517 glyph.draw(instance, &mut pen)
4518 } else {
4519 glyph.draw(
4520 skrifa::outline::DrawSettings::unhinted(
4521 skrifa::prelude::Size::new(units_per_em as f32),
4522 skrifa::instance::LocationRef::default(),
4523 ),
4524 &mut pen,
4525 )
4526 };
4527
4528 result.ok()?;
4529 if pen.path.is_empty() {
4530 None
4531 } else {
4532 Some(pen.path)
4533 }
4534}