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
1175const MAX_OFFSET_BYTES: usize = 8;
1184
1185fn create_cid_from_ps_cidfont(
1191 font_data: &[u8],
1192 default_width: f64,
1193 cid_widths: HashMap<u16, f64>,
1194 code_lengths: [u8; 256],
1195 code_to_cid: HashMap<u32, u32>,
1196 wmode: u8,
1197 dw2: [f64; 2],
1198 w2: HashMap<u16, [f64; 3]>,
1199) -> Result<PdfFont, PdfError> {
1200 let text = String::from_utf8_lossy(font_data);
1201
1202 let get_int = |key: &str| -> Option<usize> {
1204 let pat = format!("/{key}");
1205 let idx = text.find(&pat)?;
1206 let rest = &text[idx + pat.len()..];
1207 rest.split_whitespace().next()?.parse().ok()
1208 };
1209
1210 let cid_count = get_int("CIDCount").unwrap_or(0);
1218 let fd_bytes = get_int("FDBytes").unwrap_or(0);
1219 let gd_bytes = get_int("GDBytes").unwrap_or(4);
1220 let subr_map_offset = get_int("SubrMapOffset").unwrap_or(0);
1221 let sd_bytes = get_int("SDBytes").unwrap_or(4);
1222 let subr_count = get_int("SubrCount").unwrap_or(0);
1223 if fd_bytes > MAX_OFFSET_BYTES || gd_bytes > MAX_OFFSET_BYTES || sd_bytes > MAX_OFFSET_BYTES {
1224 return Err(PdfError::Other(
1225 "PS CIDFont: implausible FDBytes/GDBytes/SDBytes".into(),
1226 ));
1227 }
1228
1229 let len_iv = get_int("lenIV").unwrap_or(4) as u16;
1230
1231 let font_matrix = if let Some(fm_idx) = text.find("/FontMatrix") {
1233 let rest = &text[fm_idx..];
1234 if let Some(start) = rest.find('[') {
1235 let end_bracket = rest[start..].find(']').unwrap_or(50) + start;
1236 let vals: Vec<f64> = rest[start + 1..end_bracket]
1237 .split_whitespace()
1238 .filter_map(|s| s.parse().ok())
1239 .collect();
1240 if vals.len() == 6 {
1241 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
1242 } else {
1243 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
1244 }
1245 } else {
1246 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
1247 }
1248 } else {
1249 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
1250 };
1251
1252 let binary_data = {
1260 let sd_marker = b"StartData";
1261 let pos = font_data
1262 .windows(sd_marker.len())
1263 .position(|w| w == sd_marker)
1264 .ok_or(PdfError::Other("PS CIDFont: no StartData found".into()))?;
1265 let after = &font_data[pos + sd_marker.len()..];
1266 let skip = after
1268 .iter()
1269 .position(|&b| !matches!(b, b' ' | b'\t' | b'\r' | b'\n'))
1270 .unwrap_or(0);
1271 &font_data[pos + sd_marker.len() + skip..]
1272 };
1273
1274 let entry_size = fd_bytes + gd_bytes;
1281 if entry_size == 0 {
1282 return Err(PdfError::Other(
1283 "PS CIDFont: FDBytes + GDBytes is zero".into(),
1284 ));
1285 }
1286 let Some(cid_map_size) = cid_count.checked_mul(entry_size) else {
1287 return Err(PdfError::Other("PS CIDFont: CID map size overflows".into()));
1288 };
1289 if binary_data.len() < cid_map_size {
1290 return Err(PdfError::Other(
1291 "PS CIDFont: binary data too short for CID map".into(),
1292 ));
1293 }
1294 let read_be = |data: &[u8], off: usize, n: usize| -> usize {
1299 let mut val = 0usize;
1300 for i in 0..n {
1301 if off + i < data.len() {
1302 val = (val << 8) | data[off + i] as usize;
1303 }
1304 }
1305 val
1306 };
1307
1308 let mut cid_offsets: Vec<usize> = Vec::with_capacity(cid_count + 1);
1309 for c in 0..cid_count {
1310 let entry_off = c * entry_size + fd_bytes;
1311 let offset = read_be(binary_data, entry_off, gd_bytes);
1312 cid_offsets.push(offset);
1313 }
1314 cid_offsets.push(subr_map_offset);
1316
1317 let subr_map_fits = sd_bytes > 0
1327 && subr_count
1328 .checked_add(1)
1329 .and_then(|n| n.checked_mul(sd_bytes))
1330 .and_then(|n| n.checked_add(subr_map_offset))
1331 .is_some_and(|end| end <= binary_data.len());
1332
1333 let mut subrs: Vec<Vec<u8>> = Vec::new();
1334 if subr_count > 0 && subr_map_fits {
1335 subrs.reserve(subr_count);
1336 let mut sub_offsets: Vec<usize> = Vec::with_capacity(subr_count + 1);
1337 for i in 0..=subr_count {
1338 let off = read_be(binary_data, subr_map_offset + i * sd_bytes, sd_bytes);
1339 sub_offsets.push(off);
1340 }
1341 for i in 0..subr_count {
1342 let start = sub_offsets[i];
1343 let end = sub_offsets[i + 1];
1344 if start < end && end <= binary_data.len() {
1345 subrs.push(binary_data[start..end].to_vec());
1346 } else {
1347 subrs.push(Vec::new());
1348 }
1349 }
1350 }
1351
1352 let mut paths = HashMap::new();
1354 for &cid in cid_widths.keys() {
1355 let c = cid as usize;
1356 if c >= cid_count {
1357 continue;
1358 }
1359 let cs_start = cid_offsets[c];
1360 let cs_end = cid_offsets[c + 1];
1361 if cs_start >= cs_end || cs_end > binary_data.len() {
1362 continue;
1363 }
1364 let charstring = &binary_data[cs_start..cs_end];
1365 if let Ok(result) = execute_charstring(charstring, &subrs, len_iv.into(), false) {
1366 let path = result.path.transform(&font_matrix);
1367 paths.insert(cid, path);
1368 }
1369 }
1370
1371 let dummy_cff = stet_fonts::cff_parser::CffFont {
1373 name: String::new(),
1374 font_matrix: [
1375 font_matrix.a,
1376 font_matrix.b,
1377 font_matrix.c,
1378 font_matrix.d,
1379 font_matrix.tx,
1380 font_matrix.ty,
1381 ],
1382 font_bbox: [0.0; 4],
1383 char_strings: Vec::new(),
1384 global_subrs: Vec::new(),
1385 local_subrs: Vec::new(),
1386 charset: Vec::new(),
1387 encoding: Vec::new(),
1388 default_width_x: 0.0,
1389 nominal_width_x: 0.0,
1390 is_cid: true,
1391 fd_array: Vec::new(),
1392 fd_select: Vec::new(),
1393 ros: None,
1394 cid_to_gid: Vec::new(),
1395 };
1396
1397 Ok(PdfFont::CidCff(CidCffPdfFont {
1398 font: dummy_cff,
1399 default_width,
1400 cid_widths,
1401 font_matrix,
1402 cmap: None,
1403 pdf_cid_to_gid: None,
1404 identity_cid_to_gid: true,
1405 ordering: Vec::new(),
1406 code_lengths,
1407 code_to_cid,
1408 wmode,
1409 dw2,
1410 w2,
1411 type1_paths: Some(paths),
1412 }))
1413}
1414
1415fn create_cid_from_type1(
1420 font_data: &[u8],
1421 default_width: f64,
1422 cid_widths: HashMap<u16, f64>,
1423 _to_unicode: &HashMap<u16, u32>,
1424 code_lengths: [u8; 256],
1425 code_to_cid: HashMap<u32, u32>,
1426 wmode: u8,
1427 dw2: [f64; 2],
1428 w2: HashMap<u16, [f64; 3]>,
1429) -> Result<PdfFont, PdfError> {
1430 let font =
1431 parse_type1(font_data).map_err(|e| PdfError::Other(format!("Type1 parse error: {e}")))?;
1432 let fm = font.font_matrix;
1433 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
1434
1435 let mut paths = HashMap::new();
1439 for (&cid, _) in &cid_widths {
1440 let glyph_name = if (cid as usize) < font.encoding.len() {
1441 font.encoding[cid as usize].as_str()
1442 } else {
1443 ".notdef"
1444 };
1445 if let Some(cs) = font.charstrings.get(glyph_name) {
1446 if let Ok(result) = execute_charstring(cs, &font.subrs, font.len_iv, false) {
1447 let path = result.path.transform(&font_matrix);
1448 paths.insert(cid, path);
1449 }
1450 }
1451 }
1452 for (code, name) in font.encoding.iter().enumerate() {
1454 let cid = code as u16;
1455 if paths.contains_key(&cid) {
1456 continue;
1457 }
1458 {
1459 let name = name.as_str();
1460 if name != ".notdef" {
1461 if let Some(cs) = font.charstrings.get(name) {
1462 if let Ok(result) = execute_charstring(cs, &font.subrs, font.len_iv, false) {
1463 let path = result.path.transform(&font_matrix);
1464 paths.insert(cid, path);
1465 }
1466 }
1467 }
1468 }
1469 }
1470
1471 let dummy_cff = stet_fonts::cff_parser::CffFont {
1473 name: font.font_name.clone(),
1474 font_matrix: fm,
1475 font_bbox: [0.0; 4],
1476 char_strings: Vec::new(),
1477 global_subrs: Vec::new(),
1478 local_subrs: Vec::new(),
1479 charset: Vec::new(),
1480 encoding: Vec::new(),
1481 default_width_x: 0.0,
1482 nominal_width_x: 0.0,
1483 is_cid: false,
1484 fd_array: Vec::new(),
1485 fd_select: Vec::new(),
1486 ros: None,
1487 cid_to_gid: Vec::new(),
1488 };
1489
1490 Ok(PdfFont::CidCff(CidCffPdfFont {
1491 font: dummy_cff,
1492 default_width,
1493 cid_widths,
1494 font_matrix,
1495 cmap: None,
1496 pdf_cid_to_gid: None,
1497 identity_cid_to_gid: true,
1498 ordering: Vec::new(),
1499 code_lengths,
1500 code_to_cid,
1501 wmode,
1502 dw2,
1503 w2,
1504 type1_paths: Some(paths),
1505 }))
1506}
1507
1508fn sanitize_index_to_loc_format(font_data: &mut [u8]) {
1515 use stet_fonts::truetype::{find_table, read_i16, read_u16};
1516
1517 let head = find_table(font_data, b"head");
1518 let loca = find_table(font_data, b"loca");
1519 let maxp = find_table(font_data, b"maxp");
1520 let (head_off, _) = match head {
1521 Some(h) => h,
1522 None => return,
1523 };
1524 if head_off + 52 > font_data.len() {
1525 return;
1526 }
1527 let format = read_i16(font_data, head_off + 50);
1528 if format == 0 || format == 1 {
1529 return; }
1531 let correct = if let (Some((_, loca_len)), Some((maxp_off, _))) = (loca, maxp) {
1533 if maxp_off + 6 <= font_data.len() {
1534 let num_glyphs = read_u16(font_data, maxp_off + 4) as usize;
1535 if loca_len == (num_glyphs + 1) * 4 {
1538 1i16 } else {
1540 0i16 }
1542 } else {
1543 if format != 0 { 1 } else { 0 }
1544 }
1545 } else {
1546 if format != 0 { 1 } else { 0 }
1547 };
1548 font_data[head_off + 50] = (correct >> 8) as u8;
1549 font_data[head_off + 51] = correct as u8;
1550}
1551
1552fn load_system_truetype_font(base_font: &str) -> Result<Vec<u8>, PdfError> {
1557 use stet_fonts::system_fonts::get_system_font_cache;
1558
1559 let cache = get_system_font_cache();
1560
1561 let mut clean_name = base_font;
1563 if clean_name.len() > 7 && clean_name.as_bytes().get(6) == Some(&b'+') {
1564 clean_name = &clean_name[7..];
1565 }
1566
1567 if let Some(path) = cache.get_font_path(clean_name)
1569 && let Ok(data) = read_font_file(path, clean_name)
1570 {
1571 return Ok(data);
1572 }
1573
1574 for &(from, to) in CID_FONT_SUBSTITUTIONS {
1576 if from == clean_name
1577 && let Some(path) = cache.get_font_path(to)
1578 && let Ok(data) = read_font_file(path, to)
1579 {
1580 return Ok(data);
1581 }
1582 }
1583
1584 let lower = clean_name.to_ascii_lowercase();
1586 let is_bold = lower.contains("bold") || lower.contains("demi");
1587 let is_italic = lower.contains("italic") || lower.contains("oblique");
1588
1589 for (ps_name, path) in cache.iter() {
1590 let ps_lower = ps_name.to_ascii_lowercase();
1591 let family = lower.split(&['-', ','][..]).next().unwrap_or(&lower);
1592 if ps_lower.contains(family) || family.contains(ps_lower.split('-').next().unwrap_or("")) {
1593 let name_bold = ps_lower.contains("bold") || ps_lower.contains("demi");
1594 let name_italic = ps_lower.contains("italic") || ps_lower.contains("oblique");
1595 if name_bold == is_bold
1596 && name_italic == is_italic
1597 && let Ok(data) = read_font_file(path, ps_name)
1598 {
1599 return Ok(data);
1600 }
1601 }
1602 }
1603
1604 Err(PdfError::Other(format!(
1605 "font '{}' not found on system",
1606 clean_name
1607 )))
1608}
1609
1610fn load_cjk_fallback_font(ordering: &[u8], base_font: &str) -> Result<Vec<u8>, PdfError> {
1615 use stet_fonts::system_fonts::get_system_font_cache;
1616
1617 if ordering.is_empty() {
1618 return Err(PdfError::Other("no CJK ordering for fallback".into()));
1619 }
1620
1621 let cache = get_system_font_cache();
1622 let lower = base_font.to_ascii_lowercase();
1623 let is_bold = lower.contains("bold") || lower.contains("demi") || lower.contains("black");
1624
1625 let has_cjk_gothic = {
1632 if let Some(pos) = lower.find("gothic") {
1633 pos == 0 || !lower.as_bytes()[pos - 1].is_ascii_alphabetic()
1634 } else {
1635 false
1636 }
1637 };
1638 let is_cjk_name = has_cjk_gothic
1639 || [
1640 "cn", "sc", "jp", "kr", "tc", "hk", "cjk", "ming", "song", "hei", "kai", "fang", "han",
1641 ]
1642 .iter()
1643 .any(|kw| lower.contains(kw));
1644 if ordering == b"Identity" && !is_cjk_name {
1645 let latin_targets: &[&str] = if is_bold {
1646 &["LiberationSans-Bold", "DejaVuSans-Bold"]
1647 } else {
1648 &["LiberationSans", "DejaVuSans"]
1649 };
1650 for &target in latin_targets {
1651 if let Some(path) = cache.get_font_path(target)
1652 && let Ok(data) = read_font_file(path, target)
1653 {
1654 return Ok(data);
1655 }
1656 }
1657 return Err(PdfError::Other(format!(
1658 "Latin fallback font not found for '{}'",
1659 base_font
1660 )));
1661 }
1662
1663 let lang = if lower.contains("cn") || lower.contains("sc") || ordering == b"GB1" {
1667 "sc"
1668 } else if lower.contains("tw") || lower.contains("tc") || ordering == b"CNS1" {
1669 "tc"
1670 } else if lower.contains("kr") || ordering == b"Korea1" {
1671 "kr"
1672 } else if lower.contains("hk") {
1673 "hk"
1674 } else {
1675 "jp" };
1677 let heavy = lower.contains("heavy") || lower.contains("black");
1678 let weight_suffix = if heavy {
1680 "Black"
1681 } else if is_bold {
1682 "Bold"
1683 } else {
1684 "Regular"
1685 };
1686 let targets = [
1687 format!("NotoSansCJK{lang}-{weight_suffix}"),
1688 if is_bold || heavy {
1689 format!("NotoSansCJK{lang}-Bold")
1690 } else {
1691 format!("NotoSansCJK{lang}-Regular")
1692 },
1693 format!("NotoSansCJKjp-{weight_suffix}"),
1694 ];
1695 for target in &targets {
1696 if let Some(path) = cache.get_font_path(target)
1697 && let Ok(data) = read_font_file(path, target)
1698 {
1699 return Ok(data);
1700 }
1701 }
1702
1703 Err(PdfError::Other(format!(
1704 "CJK fallback font not found on system for '{}'",
1705 base_font
1706 )))
1707}
1708
1709const EMBEDDED_FONTS: &[(&str, &[u8])] = &[
1712 (
1714 "NimbusRoman-Regular",
1715 include_bytes!("../../fonts/NimbusRoman-Regular.t1"),
1716 ),
1717 (
1718 "NimbusRoman-Bold",
1719 include_bytes!("../../fonts/NimbusRoman-Bold.t1"),
1720 ),
1721 (
1722 "NimbusRoman-Italic",
1723 include_bytes!("../../fonts/NimbusRoman-Italic.t1"),
1724 ),
1725 (
1726 "NimbusRoman-BoldItalic",
1727 include_bytes!("../../fonts/NimbusRoman-BoldItalic.t1"),
1728 ),
1729 (
1731 "NimbusSans-Regular",
1732 include_bytes!("../../fonts/NimbusSans-Regular.t1"),
1733 ),
1734 (
1735 "NimbusSans-Bold",
1736 include_bytes!("../../fonts/NimbusSans-Bold.t1"),
1737 ),
1738 (
1739 "NimbusSans-Italic",
1740 include_bytes!("../../fonts/NimbusSans-Italic.t1"),
1741 ),
1742 (
1743 "NimbusSans-BoldItalic",
1744 include_bytes!("../../fonts/NimbusSans-BoldItalic.t1"),
1745 ),
1746 (
1748 "NimbusSansNarrow-Regular",
1749 include_bytes!("../../fonts/NimbusSansNarrow-Regular.t1"),
1750 ),
1751 (
1752 "NimbusSansNarrow-Bold",
1753 include_bytes!("../../fonts/NimbusSansNarrow-Bold.t1"),
1754 ),
1755 (
1756 "NimbusSansNarrow-Oblique",
1757 include_bytes!("../../fonts/NimbusSansNarrow-Oblique.t1"),
1758 ),
1759 (
1760 "NimbusSansNarrow-BoldOblique",
1761 include_bytes!("../../fonts/NimbusSansNarrow-BoldOblique.t1"),
1762 ),
1763 (
1765 "NimbusMonoPS-Regular",
1766 include_bytes!("../../fonts/NimbusMonoPS-Regular.t1"),
1767 ),
1768 (
1769 "NimbusMonoPS-Bold",
1770 include_bytes!("../../fonts/NimbusMonoPS-Bold.t1"),
1771 ),
1772 (
1773 "NimbusMonoPS-Italic",
1774 include_bytes!("../../fonts/NimbusMonoPS-Italic.t1"),
1775 ),
1776 (
1777 "NimbusMonoPS-BoldItalic",
1778 include_bytes!("../../fonts/NimbusMonoPS-BoldItalic.t1"),
1779 ),
1780 ("P052-Roman", include_bytes!("../../fonts/P052-Roman.t1")),
1782 ("P052-Bold", include_bytes!("../../fonts/P052-Bold.t1")),
1783 ("P052-Italic", include_bytes!("../../fonts/P052-Italic.t1")),
1784 (
1785 "P052-BoldItalic",
1786 include_bytes!("../../fonts/P052-BoldItalic.t1"),
1787 ),
1788 ("C059-Roman", include_bytes!("../../fonts/C059-Roman.t1")),
1790 ("C059-Bold", include_bytes!("../../fonts/C059-Bold.t1")),
1791 ("C059-Italic", include_bytes!("../../fonts/C059-Italic.t1")),
1792 ("C059-BdIta", include_bytes!("../../fonts/C059-BdIta.t1")),
1793 (
1795 "URWBookman-Light",
1796 include_bytes!("../../fonts/URWBookman-Light.t1"),
1797 ),
1798 (
1799 "URWBookman-Demi",
1800 include_bytes!("../../fonts/URWBookman-Demi.t1"),
1801 ),
1802 (
1803 "URWBookman-LightItalic",
1804 include_bytes!("../../fonts/URWBookman-LightItalic.t1"),
1805 ),
1806 (
1807 "URWBookman-DemiItalic",
1808 include_bytes!("../../fonts/URWBookman-DemiItalic.t1"),
1809 ),
1810 (
1812 "URWGothic-Book",
1813 include_bytes!("../../fonts/URWGothic-Book.t1"),
1814 ),
1815 (
1816 "URWGothic-Demi",
1817 include_bytes!("../../fonts/URWGothic-Demi.t1"),
1818 ),
1819 (
1820 "URWGothic-BookOblique",
1821 include_bytes!("../../fonts/URWGothic-BookOblique.t1"),
1822 ),
1823 (
1824 "URWGothic-DemiOblique",
1825 include_bytes!("../../fonts/URWGothic-DemiOblique.t1"),
1826 ),
1827 (
1829 "StandardSymbolsPS",
1830 include_bytes!("../../fonts/StandardSymbolsPS.t1"),
1831 ),
1832 ("D050000L", include_bytes!("../../fonts/D050000L.t1")),
1833 (
1834 "Z003-MediumItalic",
1835 include_bytes!("../../fonts/Z003-MediumItalic.t1"),
1836 ),
1837];
1838
1839fn embedded_font(name: &str) -> Option<Vec<u8>> {
1841 EMBEDDED_FONTS
1842 .iter()
1843 .find(|(n, _)| *n == name)
1844 .map(|(_, data)| data.to_vec())
1845}
1846
1847fn read_font_file(path: &std::path::Path, ps_name: &str) -> std::io::Result<Vec<u8>> {
1850 let data = std::fs::read(path)?;
1851 if data.len() > 12 && &data[0..4] == b"ttcf" {
1852 let num_fonts = u32::from_be_bytes([data[8], data[9], data[10], data[11]]) as usize;
1854 let mut best_offset = if num_fonts > 0 {
1856 u32::from_be_bytes([data[12], data[13], data[14], data[15]]) as usize
1857 } else {
1858 0
1859 };
1860 for i in 0..num_fonts {
1861 let off_pos = 12 + i * 4;
1862 if off_pos + 4 > data.len() {
1863 break;
1864 }
1865 let font_offset = u32::from_be_bytes([
1866 data[off_pos],
1867 data[off_pos + 1],
1868 data[off_pos + 2],
1869 data[off_pos + 3],
1870 ]) as usize;
1871 if let Some(name) = extract_ps_name_at_offset(&data, font_offset)
1873 && name == ps_name
1874 {
1875 best_offset = font_offset;
1876 break;
1877 }
1878 }
1879 extract_ttf_from_ttc(&data, best_offset)
1882 } else {
1883 Ok(data)
1884 }
1885}
1886
1887fn extract_ps_name_at_offset(data: &[u8], offset: usize) -> Option<String> {
1889 use stet_fonts::truetype::read_u16;
1890 if offset + 12 > data.len() {
1892 return None;
1893 }
1894 let num_tables = read_u16(data, offset + 4) as usize;
1895 let mut name_off = 0usize;
1896 let mut name_len = 0usize;
1897 for i in 0..num_tables {
1898 let entry = offset + 12 + i * 16;
1899 if entry + 16 > data.len() {
1900 break;
1901 }
1902 if &data[entry..entry + 4] == b"name" {
1903 name_off = u32::from_be_bytes([
1904 data[entry + 8],
1905 data[entry + 9],
1906 data[entry + 10],
1907 data[entry + 11],
1908 ]) as usize;
1909 name_len = u32::from_be_bytes([
1910 data[entry + 12],
1911 data[entry + 13],
1912 data[entry + 14],
1913 data[entry + 15],
1914 ]) as usize;
1915 break;
1916 }
1917 }
1918 if name_off == 0 || name_off + name_len > data.len() {
1919 return None;
1920 }
1921 let nd = &data[name_off..name_off + name_len];
1922 let count = read_u16(nd, 2) as usize;
1923 let string_offset = read_u16(nd, 4) as usize;
1924 for i in 0..count {
1925 let rec = 6 + i * 12;
1926 if rec + 12 > nd.len() {
1927 break;
1928 }
1929 let pid = read_u16(nd, rec);
1930 let name_id = read_u16(nd, rec + 6);
1931 let length = read_u16(nd, rec + 8) as usize;
1932 let str_off = read_u16(nd, rec + 10) as usize;
1933 if name_id == 6 {
1934 let start = string_offset + str_off;
1935 if start + length <= nd.len() {
1936 let raw = &nd[start..start + length];
1937 if pid == 3 {
1938 let s: String = raw
1939 .chunks(2)
1940 .filter_map(|c| {
1941 if c.len() == 2 {
1942 Some(u16::from_be_bytes([c[0], c[1]]) as u8 as char)
1943 } else {
1944 None
1945 }
1946 })
1947 .collect();
1948 return Some(s);
1949 } else {
1950 return Some(String::from_utf8_lossy(raw).to_string());
1951 }
1952 }
1953 }
1954 }
1955 None
1956}
1957
1958fn extract_ttf_from_ttc(ttc_data: &[u8], font_offset: usize) -> std::io::Result<Vec<u8>> {
1963 use stet_fonts::truetype::{read_u16, read_u32};
1964
1965 if font_offset + 12 > ttc_data.len() {
1966 return Err(std::io::Error::other("TTC font offset out of range"));
1967 }
1968
1969 let num_tables = read_u16(ttc_data, font_offset + 4) as usize;
1970 let header_size = 12 + num_tables * 16;
1971
1972 let mut tables = Vec::with_capacity(num_tables);
1974 for i in 0..num_tables {
1975 let entry = font_offset + 12 + i * 16;
1976 if entry + 16 > ttc_data.len() {
1977 break;
1978 }
1979 let tag = &ttc_data[entry..entry + 4];
1980 let offset = read_u32(ttc_data, entry + 8) as usize;
1981 let length = read_u32(ttc_data, entry + 12) as usize;
1982 tables.push((tag.to_vec(), offset, length));
1983 }
1984
1985 let mut result = Vec::with_capacity(
1987 header_size + tables.iter().map(|(_, _, l)| (l + 3) & !3).sum::<usize>(),
1988 );
1989
1990 result.extend_from_slice(&ttc_data[font_offset..font_offset + 12]);
1992
1993 let mut data_offset = header_size as u32;
1995 let mut new_offsets = Vec::with_capacity(num_tables);
1996 for (_, _, length) in &tables {
1997 new_offsets.push(data_offset);
1998 data_offset += ((*length as u32) + 3) & !3; }
2000
2001 for (i, (tag, _, length)) in tables.iter().enumerate() {
2003 let entry = font_offset + 12 + i * 16;
2004 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()); }
2009
2010 for (_, ttc_offset, length) in &tables {
2012 let end = (*ttc_offset + *length).min(ttc_data.len());
2013 if *ttc_offset < ttc_data.len() {
2014 result.extend_from_slice(&ttc_data[*ttc_offset..end]);
2015 let pad = (4 - (length % 4)) % 4;
2017 result.extend(std::iter::repeat_n(0u8, pad));
2018 }
2019 }
2020
2021 Ok(result)
2022}
2023
2024fn resolve_type3(resolver: &Resolver, font_dict: &PdfDict) -> Result<PdfFont, PdfError> {
2026 let first_char = font_dict.get_int(b"FirstChar").unwrap_or(0) as usize;
2027
2028 let mut widths = [0.0f64; 256];
2031 let widths_resolved = font_dict
2032 .get(b"Widths")
2033 .and_then(|obj| resolver.deref(obj).ok());
2034 if let Some(ref w_obj) = widths_resolved
2035 && let Some(w_arr) = w_obj.as_array()
2036 {
2037 for (i, obj) in w_arr.iter().enumerate() {
2038 let code = first_char + i;
2039 if code < 256 {
2040 let val = if obj.as_f64().is_some() {
2042 obj.as_f64().unwrap()
2043 } else if let Ok(resolved) = resolver.deref(obj) {
2044 resolved.as_f64().unwrap_or(0.0)
2045 } else {
2046 0.0
2047 };
2048 widths[code] = val;
2049 }
2050 }
2051 }
2052
2053 let font_matrix = font_dict
2055 .get_array(b"FontMatrix")
2056 .map(|a| {
2057 let v: Vec<f64> = a.iter().filter_map(|o| o.as_f64()).collect();
2058 if v.len() >= 6 {
2059 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
2060 } else {
2061 Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0)
2062 }
2063 })
2064 .unwrap_or_else(|| Matrix::new(0.001, 0.0, 0.0, 0.001, 0.0, 0.0));
2065
2066 let font_bbox = font_dict
2067 .get_array(b"FontBBox")
2068 .map(|a| {
2069 let v: Vec<f64> = a.iter().filter_map(|o| o.as_f64()).collect();
2070 if v.len() >= 4 {
2071 [v[0], v[1], v[2], v[3]]
2072 } else {
2073 [0.0, 0.0, 1.0, 1.0]
2074 }
2075 })
2076 .unwrap_or([0.0, 0.0, 1.0, 1.0]);
2077
2078 let (encoding, _, _, _) = resolve_encoding(font_dict, resolver)?;
2080
2081 let char_procs_dict = if let Some(obj) = font_dict.get(b"CharProcs") {
2084 match resolver.deref(obj)? {
2085 PdfObj::Dict(d) => d,
2086 _ => return Err(PdfError::Other("Type3 CharProcs is not a dict".into())),
2087 }
2088 } else {
2089 return Err(PdfError::Other("Type3 font missing CharProcs".into()));
2090 };
2091
2092 let resources = if let Some(res_ref) = font_dict.get(b"Resources") {
2094 match resolver.deref(res_ref)? {
2095 PdfObj::Dict(d) => d,
2096 _ => PdfDict::new(),
2097 }
2098 } else {
2099 PdfDict::new()
2100 };
2101
2102 let mut char_procs = HashMap::new();
2104 for code in 0..256u16 {
2105 if let Some(glyph_name) = &encoding[code as usize]
2106 && let Some(proc_ref) = char_procs_dict.get(glyph_name.as_bytes())
2107 && let Ok(data) = resolver.stream_data_from_obj(proc_ref)
2108 {
2109 char_procs.insert(code as u8, data);
2110 }
2111 }
2112 Ok(PdfFont::Type3(Type3PdfFont {
2113 char_procs,
2114 resources,
2115 widths,
2116 font_matrix,
2117 font_bbox,
2118 }))
2119}
2120
2121fn resolve_type1(
2122 resolver: &Resolver,
2123 descriptor: &Option<PdfDict>,
2124 encoding: [Option<String>; 256],
2125 widths: [f64; 256],
2126 has_explicit_encoding: bool,
2127 has_pdf_widths: bool,
2128 differences: &[(usize, String)],
2129 no_base_encoding: bool,
2130) -> Result<PdfFont, PdfError> {
2131 let desc = descriptor
2132 .as_ref()
2133 .ok_or(PdfError::Other("Type1 font missing FontDescriptor".into()))?;
2134 if let Some(ff3_ref) = desc.get(b"FontFile3") {
2136 let ff3_obj = resolver.deref(ff3_ref)?;
2138 let ff3_dict = ff3_obj.as_dict();
2139 let subtype = ff3_dict.and_then(|d| d.get_name(b"Subtype")).unwrap_or(b"");
2140 if subtype == b"Type1C" || subtype == b"CIDFontType0C" || subtype == b"OpenType" {
2141 let raw_data = resolver.stream_data_from_obj(ff3_ref)?;
2142 let font_data = if raw_data.starts_with(b"OTTO") {
2144 use stet_fonts::truetype::find_table;
2145 let (offset, length) = find_table(&raw_data, b"CFF ")
2146 .ok_or(PdfError::Other("OpenType font has no CFF table".into()))?;
2147 raw_data[offset..offset + length].to_vec()
2148 } else {
2149 raw_data
2150 };
2151 let fonts = parse_cff(&font_data)
2152 .map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
2153 let font = fonts
2154 .into_iter()
2155 .next()
2156 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
2157
2158 let fm = font.font_matrix;
2159 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
2160
2161 return Ok(PdfFont::Cff(CffPdfFont {
2162 font,
2163 encoding,
2164 widths,
2165 font_matrix,
2166 }));
2167 }
2168 }
2169
2170 let ff_ref = desc
2171 .get(b"FontFile")
2172 .or_else(|| desc.get(b"FontFile3"))
2173 .ok_or(PdfError::Other("Type1 font missing FontFile".into()))?;
2174 let font_data = resolver.stream_data_from_obj(ff_ref)?;
2175
2176 let font_data = strip_pfb(&font_data);
2178
2179 let font =
2180 parse_type1(&font_data).map_err(|e| PdfError::Other(format!("Type1 parse error: {e}")))?;
2181
2182 let encoding = if no_base_encoding && font.encoding.len() == 256 {
2187 let mut builtin: [Option<String>; 256] = std::array::from_fn(|_| None);
2190 for (i, name) in font.encoding.iter().enumerate() {
2191 if name != ".notdef" {
2192 builtin[i] = Some(name.clone());
2193 }
2194 }
2195 for (code, name) in differences {
2196 if *code < 256 {
2197 builtin[*code] = Some(name.clone());
2198 }
2199 }
2200 builtin
2201 } else if !has_explicit_encoding {
2202 let flags = desc.get_int(b"Flags").unwrap_or(0) as u32;
2203 let is_symbolic = flags & 4 != 0;
2204 if is_symbolic && font.encoding.len() == 256 {
2205 let mut builtin: [Option<String>; 256] = std::array::from_fn(|_| None);
2206 for (i, name) in font.encoding.iter().enumerate() {
2207 if name != ".notdef" {
2208 builtin[i] = Some(name.clone());
2209 }
2210 }
2211 builtin
2212 } else {
2213 encoding
2214 }
2215 } else {
2216 encoding
2217 };
2218
2219 let builtin_fallback = {
2223 let flags = desc.get_int(b"Flags").unwrap_or(0) as u32;
2224 let is_sym = flags & 4 != 0;
2225 let builtin_useful =
2226 is_sym && font.encoding.len() == 256 && font.encoding.iter().any(|n| n != ".notdef");
2227 if builtin_useful {
2228 !encoding[32..127].iter().any(|slot| {
2229 slot.as_ref()
2230 .is_some_and(|name| font.charstrings.contains_key(name.as_str()))
2231 })
2232 } else {
2233 false
2234 }
2235 };
2236
2237 let fm = font.font_matrix;
2238 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
2239
2240 let widths = if !has_pdf_widths {
2242 let mut derived = [0.0f64; 256];
2243 for code in 0..256usize {
2244 let glyph_name = encoding[code].as_deref().unwrap_or(".notdef");
2245 if glyph_name == ".notdef" {
2246 continue;
2247 }
2248 if let Some(charstring) = font.charstrings.get(glyph_name) {
2249 let cs_lookup =
2250 |name: &str| -> Option<Vec<u8>> { font.charstrings.get(name).cloned() };
2251 if let Ok(result) = execute_charstring_mm(
2252 charstring,
2253 &font.subrs,
2254 font.len_iv,
2255 false,
2256 Some(&cs_lookup),
2257 font.weight_vector.as_deref(),
2258 ) {
2259 derived[code] = result.width_x * fm[0];
2260 }
2261 }
2262 }
2263 derived
2264 } else {
2265 widths
2266 };
2267
2268 let weight_vector = font.weight_vector.clone();
2269 Ok(PdfFont::Type1(Type1PdfFont {
2270 font,
2271 encoding,
2272 widths,
2273 font_matrix,
2274 weight_vector,
2275 builtin_fallback,
2276 per_char_width_scale: false,
2277 }))
2278}
2279
2280fn try_raw_deflate_if_truncated(resolver: &Resolver, ff_ref: &PdfObj, data: Vec<u8>) -> Vec<u8> {
2285 if data.len() < 12 {
2287 return data;
2288 }
2289 let num_tables = u16::from_be_bytes([data[4], data[5]]) as usize;
2290 let mut max_end = 0usize;
2291 for i in 0..num_tables {
2292 let e = 12 + i * 16;
2293 if e + 16 > data.len() {
2294 break;
2295 }
2296 let off =
2297 u32::from_be_bytes([data[e + 8], data[e + 9], data[e + 10], data[e + 11]]) as usize;
2298 let len =
2299 u32::from_be_bytes([data[e + 12], data[e + 13], data[e + 14], data[e + 15]]) as usize;
2300 max_end = max_end.max(off.saturating_add(len));
2301 }
2302 if max_end <= data.len() {
2303 return data; }
2305 let raw_bytes = match resolver.raw_stream_bytes(ff_ref) {
2307 Some(b) if b.len() > 2 => b,
2308 _ => return data,
2309 };
2310 let cinfo = raw_bytes[0] >> 4;
2312 let cm = raw_bytes[0] & 0xF;
2313 if cm != 8 || cinfo >= 7 {
2314 return data;
2315 }
2316 let mut decoder = flate2::Decompress::new(false);
2318 let mut output = Vec::with_capacity(data.len() * 2);
2319 let mut buf = [0u8; 8192];
2320 let input = &raw_bytes[2..];
2321 let mut input_offset = 0;
2322 loop {
2323 let before_in = decoder.total_in() as usize;
2324 let before_out = decoder.total_out() as usize;
2325 let result = decoder.decompress(
2326 &input[input_offset..],
2327 &mut buf,
2328 flate2::FlushDecompress::None,
2329 );
2330 let consumed = decoder.total_in() as usize - before_in;
2331 let produced = decoder.total_out() as usize - before_out;
2332 input_offset += consumed;
2333 output.extend_from_slice(&buf[..produced]);
2334 match result {
2335 Ok(flate2::Status::StreamEnd) => break,
2336 Ok(_) => {
2337 if consumed == 0 && produced == 0 {
2338 break;
2339 }
2340 }
2341 Err(_) => break,
2342 }
2343 }
2344 if output.len() <= data.len() {
2345 return data;
2346 }
2347 let mut raw_max_end = 0usize;
2349 for i in 0..num_tables {
2350 let e = 12 + i * 16;
2351 if e + 16 > output.len() {
2352 return data;
2353 }
2354 let off = u32::from_be_bytes([output[e + 8], output[e + 9], output[e + 10], output[e + 11]])
2355 as usize;
2356 let len = u32::from_be_bytes([
2357 output[e + 12],
2358 output[e + 13],
2359 output[e + 14],
2360 output[e + 15],
2361 ]) as usize;
2362 raw_max_end = raw_max_end.max(off.saturating_add(len));
2363 }
2364 if raw_max_end > output.len() {
2365 return data; }
2367 if stet_fonts::truetype::get_units_per_em(&output) == 0 {
2370 return data;
2371 }
2372 output
2373}
2374
2375fn resolve_truetype(
2376 resolver: &Resolver,
2377 descriptor: &Option<PdfDict>,
2378 encoding: [Option<String>; 256],
2379 widths: [f64; 256],
2380 font_dict: &PdfDict,
2381) -> Result<PdfFont, PdfError> {
2382 let desc = descriptor.as_ref().ok_or(PdfError::Other(
2383 "TrueType font missing FontDescriptor".into(),
2384 ))?;
2385 let ff_ref = desc
2386 .get(b"FontFile2")
2387 .ok_or(PdfError::Other("TrueType font missing FontFile2".into()))?;
2388 let data = resolver.stream_data_from_obj(ff_ref)?;
2389
2390 let data = try_raw_deflate_if_truncated(resolver, ff_ref, data);
2394
2395 use stet_fonts::truetype::find_table;
2397 let has_glyf = find_table(&data, b"glyf").is_some();
2398 let has_usable_glyx = if let Some((off, len)) = find_table(&data, b"glyx") {
2399 off + len <= data.len()
2400 } else {
2401 false
2402 };
2403 if !has_glyf && !has_usable_glyx {
2404 let is_otf = data.starts_with(b"OTTO");
2407 let is_cff = is_raw_cff(&data);
2408 if is_otf || is_cff {
2409 let has_explicit_encoding = font_dict.get(b"Encoding").is_some();
2410 let has_pdf_widths = font_dict.get(b"Widths").is_some();
2411 return build_cff_font(
2412 data,
2413 encoding,
2414 widths,
2415 has_explicit_encoding,
2416 has_pdf_widths,
2417 &[],
2418 false,
2419 );
2420 }
2421 return Err(PdfError::Other(
2422 "TrueType font has no usable glyph outline data".into(),
2423 ));
2424 }
2425
2426 if let Some((off, _)) = find_table(&data, b"head") {
2430 if off + 54 > data.len() {
2431 return Err(PdfError::Other(
2432 "TrueType font head table is out of bounds (truncated data)".into(),
2433 ));
2434 }
2435 }
2436
2437 let units_per_em = get_units_per_em(&data) as f64;
2438
2439 if units_per_em < 16.0 {
2443 return Err(PdfError::Other(
2444 "TrueType font has degenerate unitsPerEm (placeholder outlines)".into(),
2445 ));
2446 }
2447
2448 let (cmap, cmap_is_unicode) = parse_cmap_with_info(&data);
2449
2450 let post_name_to_gid = stet_fonts::system_fonts::parse_post_table(&data)
2452 .map(|gid_to_name| {
2453 gid_to_name
2454 .into_iter()
2455 .map(|(gid, name)| (name, gid))
2456 .collect()
2457 })
2458 .unwrap_or_default();
2459
2460 let flags = desc.get_int(b"Flags").unwrap_or(0) as u32;
2464 let is_symbolic = flags & 4 != 0;
2465 let has_encoding = font_dict.get(b"Encoding").is_some();
2466 let identity_gid = is_symbolic && !has_encoding && cmap_is_unicode;
2467 let gid_hex = TrueTypePdfFont::detect_gid_hex(&encoding);
2468
2469 Ok(PdfFont::TrueType(TrueTypePdfFont {
2470 data,
2471 encoding,
2472 widths,
2473 cmap,
2474 cmap_is_unicode,
2475 post_name_to_gid,
2476 units_per_em,
2477 to_unicode: if let Some(tu_obj) = font_dict.get(b"ToUnicode") {
2478 resolver
2479 .stream_data_from_obj(tu_obj)
2480 .map(|d| parse_to_unicode(&d))
2481 .unwrap_or_default()
2482 } else {
2483 HashMap::new()
2484 },
2485 identity_gid,
2486 gid_hex,
2487 }))
2488}
2489
2490fn resolve_cff(
2492 resolver: &Resolver,
2493 descriptor: &Option<PdfDict>,
2494 encoding: [Option<String>; 256],
2495 widths: [f64; 256],
2496 has_explicit_encoding: bool,
2497 has_pdf_widths: bool,
2498 differences: &[(usize, String)],
2499 no_base_encoding: bool,
2500) -> Result<PdfFont, PdfError> {
2501 let desc = descriptor
2502 .as_ref()
2503 .ok_or(PdfError::Other("CFF font missing FontDescriptor".into()))?;
2504 let ff_ref = desc
2505 .get(b"FontFile3")
2506 .ok_or(PdfError::Other("CFF font missing FontFile3".into()))?;
2507 let raw_data = resolver.stream_data_from_obj(ff_ref)?;
2508 build_cff_font(
2509 raw_data,
2510 encoding,
2511 widths,
2512 has_explicit_encoding,
2513 has_pdf_widths,
2514 differences,
2515 no_base_encoding,
2516 )
2517}
2518
2519fn build_cff_font(
2521 raw_data: Vec<u8>,
2522 encoding: [Option<String>; 256],
2523 widths: [f64; 256],
2524 has_explicit_encoding: bool,
2525 has_pdf_widths: bool,
2526 differences: &[(usize, String)],
2527 no_base_encoding: bool,
2528) -> Result<PdfFont, PdfError> {
2529 let font_data = if raw_data.starts_with(b"OTTO") {
2531 use stet_fonts::truetype::find_table;
2532 let (offset, length) = find_table(&raw_data, b"CFF ")
2533 .ok_or(PdfError::Other("OpenType font has no CFF table".into()))?;
2534 raw_data[offset..offset + length].to_vec()
2535 } else {
2536 raw_data
2537 };
2538
2539 let fonts =
2540 parse_cff(&font_data).map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
2541 let font = fonts
2542 .into_iter()
2543 .next()
2544 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
2545
2546 let build_cff_encoding = |font: &stet_fonts::cff_parser::CffFont| -> [Option<String>; 256] {
2555 let mut enc: [Option<String>; 256] = std::array::from_fn(|_| None);
2556 let name_to_gid: std::collections::HashMap<&str, u16> = font
2557 .charset
2558 .iter()
2559 .enumerate()
2560 .map(|(gid, name)| (name.as_str(), gid as u16))
2561 .collect();
2562 #[allow(clippy::needless_range_loop)]
2563 for code in 0..256 {
2564 let gid = font.encoding[code] as usize;
2565 if gid > 0 && gid < font.charset.len() && font.charset[gid] != ".notdef" {
2566 enc[code] = Some(font.charset[gid].clone());
2567 }
2568 }
2569 if name_to_gid.contains_key("Asmall") {
2571 for &(code, sid) in &stet_fonts::cff_parser::EXPERT_ENCODING_MAP {
2572 if enc[code as usize].is_none() {
2573 let name = stet_fonts::cff_parser::get_sid_string(sid, &[]);
2574 if let Some(&gid) = name_to_gid.get(name.as_str()) {
2575 if gid > 0 {
2576 enc[code as usize] = Some(font.charset[gid as usize].clone());
2577 }
2578 }
2579 }
2580 }
2581 for code in b'a'..=b'z' {
2584 if enc[code as usize].is_none() {
2585 let small_name = format!("{}small", (code - b'a' + b'A') as char);
2586 if name_to_gid.contains_key(small_name.as_str()) {
2587 enc[code as usize] = Some(small_name);
2588 }
2589 }
2590 }
2591 }
2592 enc
2593 };
2594
2595 let encoding = if no_base_encoding || !differences.is_empty() {
2596 let mut enc = build_cff_encoding(&font);
2599 for (code, name) in differences {
2600 if *code < 256 {
2601 enc[*code] = Some(name.clone());
2602 }
2603 }
2604 enc
2605 } else if !has_explicit_encoding {
2606 build_cff_encoding(&font)
2608 } else {
2609 encoding
2610 };
2611
2612 let fm = font.font_matrix;
2613 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
2614
2615 let widths = if !has_pdf_widths {
2617 use stet_fonts::type2_charstring::execute_type2_charstring;
2618 let mut derived = [0.0f64; 256];
2619 for code in 0..256usize {
2620 let glyph_name = encoding[code].as_deref().unwrap_or(".notdef");
2621 let gid = font
2622 .charset
2623 .iter()
2624 .position(|name| name == glyph_name)
2625 .unwrap_or(0);
2626 if gid > 0 && gid < font.char_strings.len() {
2627 if let Ok(result) = execute_type2_charstring(
2628 &font.char_strings[gid],
2629 &font.local_subrs,
2630 &font.global_subrs,
2631 font.default_width_x,
2632 font.nominal_width_x,
2633 true, ) {
2635 derived[code] = result.width_x * fm[0];
2636 }
2637 }
2638 }
2639 derived
2640 } else {
2641 widths
2642 };
2643
2644 Ok(PdfFont::Cff(CffPdfFont {
2645 font,
2646 encoding,
2647 widths,
2648 font_matrix,
2649 }))
2650}
2651
2652fn resolve_type0(resolver: &Resolver, font_dict: &PdfDict) -> Result<PdfFont, PdfError> {
2654 let encoding_obj = font_dict.get(b"Encoding");
2656 let encoding_name = font_dict.get_name(b"Encoding").unwrap_or(b"");
2657 let ucs2_encoding = encoding_name.windows(4).any(|w| w == b"UCS2");
2658
2659 let (code_lengths, code_to_cid, mut wmode) = if let Some(enc_obj) = encoding_obj {
2666 if let Ok(cmap_data) = resolver.stream_data_from_obj(enc_obj) {
2667 let cmap = super::cmap::CMap::parse_with_loader(
2669 &cmap_data,
2670 Some(&|name| load_predefined_cmap(name)),
2671 );
2672 (cmap.code_lengths, cmap.code_to_cid, cmap.wmode)
2673 } else if !encoding_name.is_empty() && !encoding_name.starts_with(b"Identity") {
2674 if let Some(cmap_data) = load_predefined_cmap(encoding_name) {
2676 let cmap = super::cmap::CMap::parse_with_loader(
2677 &cmap_data,
2678 Some(&|name| load_predefined_cmap(name)),
2679 );
2680 (cmap.code_lengths, cmap.code_to_cid, cmap.wmode)
2681 } else {
2682 eprintln!(
2683 "warning: predefined CMap '{}' not found; \
2684 set STET_CMAP_DIR or install poppler-data for CJK support",
2685 String::from_utf8_lossy(encoding_name)
2686 );
2687 ([2u8; 256], HashMap::new(), 0)
2688 }
2689 } else {
2690 ([2u8; 256], HashMap::new(), 0) }
2692 } else {
2693 ([2u8; 256], HashMap::new(), 0)
2694 };
2695 if encoding_name.ends_with(b"-V") {
2697 wmode = 1;
2698 } else if encoding_name.ends_with(b"-H") {
2699 wmode = 0;
2700 }
2701
2702 let descendants_obj = font_dict
2705 .get(b"DescendantFonts")
2706 .ok_or(PdfError::Other("Type0 font missing DescendantFonts".into()))?;
2707 let descendants_resolved = resolver.deref(descendants_obj)?;
2708 let descendants = descendants_resolved
2709 .as_array()
2710 .ok_or(PdfError::Other("DescendantFonts is not an array".into()))?;
2711 let cid_font_ref = descendants
2712 .first()
2713 .ok_or(PdfError::Other("DescendantFonts is empty".into()))?;
2714 let cid_font_obj = resolver.deref(cid_font_ref)?;
2715 let cid_font_dict = cid_font_obj
2716 .as_dict()
2717 .ok_or(PdfError::Other("CIDFont is not a dict".into()))?;
2718
2719 let cid_subtype = cid_font_dict.get_name(b"Subtype").unwrap_or(b"");
2720
2721 let descriptor = get_font_descriptor(cid_font_dict, resolver)?;
2723 let desc = descriptor
2724 .as_ref()
2725 .ok_or(PdfError::Other("CIDFont missing FontDescriptor".into()))?;
2726
2727 let default_width = cid_font_dict.get_f64(b"DW").unwrap_or(1000.0) / 1000.0;
2729
2730 let dw2 = cid_font_dict
2733 .get_array(b"DW2")
2734 .and_then(|arr| {
2735 let v: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
2736 if v.len() >= 2 {
2737 Some([v[0], v[1]])
2738 } else {
2739 None
2740 }
2741 })
2742 .unwrap_or([880.0, -1000.0]);
2743
2744 let cid_widths = parse_cid_widths(cid_font_dict, resolver);
2746
2747 let w2 = parse_cid_w2(cid_font_dict, resolver);
2749
2750 let code_to_cid = if code_to_cid.is_empty()
2757 && code_lengths[0] == 2
2758 && encoding_name.windows(4).any(|w| w == b"UCS2")
2759 {
2760 let mut map = HashMap::new();
2761 for unicode in 0x0020u32..=0x007Eu32 {
2762 let cid = unicode - 0x001F;
2763 map.insert(unicode, cid);
2764 }
2765 map
2766 } else {
2767 code_to_cid
2768 };
2769
2770 let to_unicode = if let Some(tu_obj) = font_dict.get(b"ToUnicode") {
2772 match resolver.stream_data_from_obj(tu_obj) {
2773 Ok(data) => parse_to_unicode(&data),
2774 Err(_) => HashMap::new(),
2775 }
2776 } else {
2777 HashMap::new()
2778 };
2779
2780 let ordering = {
2783 let si_dict = cid_font_dict
2784 .get_dict(b"CIDSystemInfo")
2785 .cloned()
2786 .or_else(|| {
2787 cid_font_dict
2788 .get(b"CIDSystemInfo")
2789 .and_then(|obj| resolver.deref(obj).ok())
2790 .and_then(|obj| obj.as_dict().cloned())
2791 });
2792 si_dict
2793 .and_then(|d| {
2794 d.get(b"Ordering").and_then(|v| match v {
2795 PdfObj::Str(s) => Some(s.clone()),
2796 PdfObj::Name(n) => Some(n.clone()),
2797 _ => None,
2798 })
2799 })
2800 .unwrap_or_default()
2801 };
2802
2803 match cid_subtype {
2804 b"CIDFontType2" => {
2805 let mut substituted;
2806 let mut data = if let Some(ff_ref) = desc
2807 .get(b"FontFile2")
2808 .or_else(|| {
2811 desc.get(b"FontFile").filter(|obj| {
2812 resolver
2813 .stream_data_from_obj(obj)
2814 .ok()
2815 .is_some_and(|d| d.len() > 4 && d[..4] == [0, 1, 0, 0])
2816 })
2817 })
2818 .or_else(|| {
2823 desc.get(b"FontFile3").filter(|obj| {
2824 resolver.stream_data_from_obj(obj).ok().is_some_and(|d| {
2825 d.len() > 4
2826 && (d[..4] == [0, 1, 0, 0]
2827 || &d[..4] == b"true"
2828 || &d[..4] == b"OTTO")
2829 })
2830 })
2831 }) {
2832 substituted = false;
2833 let mut font_data = resolver.stream_data_from_obj(ff_ref)?;
2834 sanitize_index_to_loc_format(&mut font_data);
2835 let is_otf_cff = font_data.len() > 4 && &font_data[0..4] == b"OTTO";
2838 let is_raw = is_raw_cff(&font_data);
2839 if is_otf_cff || is_raw {
2840 let cid_to_gid_map = if let Some(map_obj) = cid_font_dict.get(b"CIDToGIDMap") {
2842 if cid_font_dict.get_name(b"CIDToGIDMap") != Some(b"Identity") {
2843 resolver.stream_data_from_obj(map_obj).ok().map(|d| {
2844 d.chunks_exact(2)
2845 .map(|p| u16::from_be_bytes([p[0], p[1]]))
2846 .collect()
2847 })
2848 } else {
2849 None
2850 }
2851 } else {
2852 None
2853 };
2854 let is_cid_keyed = {
2860 use stet_fonts::truetype::find_table;
2861 let cff_range = if is_otf_cff {
2862 find_table(&font_data, b"CFF ")
2863 } else {
2864 Some((0, font_data.len()))
2865 };
2866 cff_range
2867 .and_then(|(off, len)| parse_cff(&font_data[off..off + len]).ok())
2868 .and_then(|fonts| fonts.into_iter().next())
2869 .is_some_and(|f| f.is_cid)
2870 };
2871 let (cid_to_gid_map, identity) = if is_cid_keyed {
2872 (None, true) } else {
2874 let id = cid_to_gid_map.is_none();
2875 (cid_to_gid_map, id)
2876 };
2877 if is_otf_cff {
2878 return create_cid_cff_from_otf(
2879 &font_data,
2880 default_width,
2881 cid_widths,
2882 &ordering,
2883 cid_to_gid_map,
2884 identity,
2885 code_lengths,
2886 code_to_cid.clone(),
2887 wmode,
2888 dw2,
2889 w2.clone(),
2890 );
2891 } else {
2892 return create_cid_cff_from_raw(
2893 &font_data,
2894 default_width,
2895 cid_widths,
2896 &ordering,
2897 cid_to_gid_map,
2898 identity,
2899 code_lengths,
2900 code_to_cid.clone(),
2901 wmode,
2902 dw2,
2903 w2.clone(),
2904 );
2905 }
2906 }
2907 font_data
2908 } else {
2909 substituted = true;
2911 let base_font = cid_font_dict
2912 .get_name(b"BaseFont")
2913 .map(|n| {
2914 let s = String::from_utf8_lossy(n);
2915 if s.len() > 7 && s.as_bytes().get(6) == Some(&b'+') {
2916 s[7..].to_string()
2917 } else {
2918 s.to_string()
2919 }
2920 })
2921 .unwrap_or_default();
2922 let sys_data = load_system_truetype_font(&base_font)
2923 .or_else(|_| load_cjk_fallback_font(&ordering, &base_font))?;
2924 if sys_data.len() > 4 && &sys_data[0..4] == b"OTTO" {
2926 return create_cid_cff_from_otf(
2927 &sys_data,
2928 default_width,
2929 cid_widths,
2930 &ordering,
2931 None,
2932 false, code_lengths,
2934 code_to_cid.clone(),
2935 wmode,
2936 dw2,
2937 w2.clone(),
2938 );
2939 }
2940 sys_data
2941 };
2942
2943 let has_cid_to_gid_map = cid_font_dict
2950 .get(b"CIDToGIDMap")
2951 .is_some_and(|v| v.as_name().is_none_or(|n| n != b"Identity"));
2952 if !substituted && !cid_widths.is_empty() && !has_cid_to_gid_map {
2953 let upm_f = get_units_per_em(&data) as f64;
2954 let any_glyph = cid_widths
2955 .keys()
2956 .any(|&cid| skrifa_glyph_path(&data, cid, upm_f).is_some());
2957 if !any_glyph {
2958 let base_font = cid_font_dict
2959 .get_name(b"BaseFont")
2960 .map(|n| {
2961 let s = String::from_utf8_lossy(n);
2962 if s.len() > 7 && s.as_bytes().get(6) == Some(&b'+') {
2963 s[7..].to_string()
2964 } else {
2965 s.to_string()
2966 }
2967 })
2968 .unwrap_or_default();
2969 if let Ok(sys_data) = load_system_truetype_font(&base_font) {
2970 data = sys_data;
2971 substituted = true;
2972 }
2973 }
2974 }
2975 let units_per_em = get_units_per_em(&data) as f64;
2976 let cmap = parse_cmap(&data);
2977
2978 let (identity_cid_to_gid, cid_to_gid_map) =
2980 if let Some(name) = cid_font_dict.get_name(b"CIDToGIDMap") {
2981 (name == b"Identity", None)
2982 } else if let Some(map_obj) = cid_font_dict.get(b"CIDToGIDMap") {
2983 match resolver.stream_data_from_obj(map_obj) {
2984 Ok(stream_data) => {
2985 let mut gid_map = Vec::with_capacity(stream_data.len() / 2);
2986 for pair in stream_data.chunks_exact(2) {
2987 gid_map.push(u16::from_be_bytes([pair[0], pair[1]]));
2988 }
2989 (false, Some(gid_map))
2990 }
2991 Err(_) => (true, None), }
2993 } else {
2994 (true, None) };
2996
2997 let cid_to_gid_map = if substituted && cid_to_gid_map.is_some() {
3003 None
3004 } else {
3005 cid_to_gid_map
3006 };
3007 let to_unicode = if substituted
3017 && identity_cid_to_gid
3018 && to_unicode.is_empty()
3019 && encoding_name.starts_with(b"Identity")
3020 {
3021 let base_name = cid_font_dict.get_name(b"BaseFont").unwrap_or(b"");
3022 let name_str = String::from_utf8_lossy(base_name);
3023 let clean = if name_str.len() > 7 && name_str.as_bytes().get(6) == Some(&b'+') {
3025 &name_str[7..]
3026 } else {
3027 &name_str
3028 };
3029 let mut family = clean
3033 .split(&[',', '-'][..])
3034 .next()
3035 .unwrap_or(clean)
3036 .to_ascii_lowercase();
3037 for suffix in &["psmt", "ps", "mt"] {
3038 if family.len() > suffix.len() && family.ends_with(suffix) {
3039 family.truncate(family.len() - suffix.len());
3040 break;
3041 }
3042 }
3043 super::gid_maps::get_gid_to_unicode_map(&family).unwrap_or(to_unicode)
3044 } else {
3045 to_unicode
3046 };
3047
3048 Ok(PdfFont::CidTrueType(CidTrueTypePdfFont {
3049 data,
3050 default_width,
3051 cid_widths,
3052 cmap,
3053 units_per_em,
3054 identity_cid_to_gid,
3055 substituted,
3056 cid_to_gid_map,
3057 to_unicode,
3058 ordering: ordering.clone(),
3059 ucs2_encoding,
3060 code_lengths,
3061 code_to_cid: code_to_cid.clone(),
3062 wmode,
3063 dw2,
3064 w2: w2.clone(),
3065 }))
3066 }
3067 b"CIDFontType0" => {
3068 if let Some(ff_ref) = desc.get(b"FontFile3").or_else(|| desc.get(b"FontFile")) {
3071 let font_data = resolver.stream_data_from_obj(ff_ref)?;
3072 let is_truetype = font_data.len() > 4 && &font_data[0..4] == b"\x00\x01\x00\x00";
3075 if is_truetype {
3076 let mut font_data = font_data;
3077 sanitize_index_to_loc_format(&mut font_data);
3078 let units_per_em = get_units_per_em(&font_data) as f64;
3079 let cmap = parse_cmap(&font_data);
3080 let (identity_cid_to_gid, cid_to_gid_map) =
3081 if let Some(name) = cid_font_dict.get_name(b"CIDToGIDMap") {
3082 (name == b"Identity", None)
3083 } else {
3084 (true, None)
3085 };
3086 return Ok(PdfFont::CidTrueType(CidTrueTypePdfFont {
3087 data: font_data,
3088 default_width,
3089 cid_widths,
3090 cmap,
3091 units_per_em,
3092 identity_cid_to_gid,
3093 substituted: false,
3094 cid_to_gid_map,
3095 to_unicode,
3096 ordering: ordering.clone(),
3097 ucs2_encoding,
3098 code_lengths,
3099 code_to_cid: code_to_cid.clone(),
3100 wmode,
3101 dw2,
3102 w2: w2.clone(),
3103 }));
3104 }
3105 if font_data.len() > 4 && &font_data[0..4] == b"OTTO" {
3107 let pdf_cid_to_gid = if let Some(map_obj) = cid_font_dict.get(b"CIDToGIDMap") {
3109 match resolver.stream_data_from_obj(map_obj) {
3110 Ok(stream_data) => {
3111 let mut gid_map = Vec::with_capacity(stream_data.len() / 2);
3112 for pair in stream_data.chunks_exact(2) {
3113 gid_map.push(u16::from_be_bytes([pair[0], pair[1]]));
3114 }
3115 Some(gid_map)
3116 }
3117 Err(_) => None,
3118 }
3119 } else {
3120 None
3121 };
3122 let cff_is_cid = is_cff_cid_keyed(&font_data);
3126 return create_cid_cff_from_otf(
3127 &font_data,
3128 default_width,
3129 cid_widths,
3130 &ordering,
3131 pdf_cid_to_gid,
3132 !cff_is_cid,
3133 code_lengths,
3134 code_to_cid.clone(),
3135 wmode,
3136 dw2,
3137 w2.clone(),
3138 );
3139 }
3140 if font_data.starts_with(b"%!")
3143 && font_data.windows(16).any(|w| w == b"Resource-CIDFont")
3144 {
3145 return create_cid_from_ps_cidfont(
3146 &font_data,
3147 default_width,
3148 cid_widths,
3149 code_lengths,
3150 code_to_cid.clone(),
3151 wmode,
3152 dw2,
3153 w2.clone(),
3154 );
3155 }
3156 let is_type1 = font_data.starts_with(b"%!") || font_data.first() == Some(&0x80);
3160 if is_type1 {
3161 return create_cid_from_type1(
3162 &font_data,
3163 default_width,
3164 cid_widths,
3165 &to_unicode,
3166 code_lengths,
3167 code_to_cid.clone(),
3168 wmode,
3169 dw2,
3170 w2.clone(),
3171 );
3172 }
3173 let fonts = parse_cff(&font_data)
3174 .map_err(|e| PdfError::Other(format!("CFF parse error: {e}")))?;
3175 let font = fonts
3176 .into_iter()
3177 .next()
3178 .ok_or(PdfError::Other("CFF contains no fonts".into()))?;
3179 let cs_count = font.char_strings.len();
3193 let is_adobe_cjk_registry = matches!(
3194 ordering.as_slice(),
3195 b"GB1" | b"CNS1" | b"Japan1" | b"Japan2" | b"Korea1" | b"KR"
3196 );
3197 if cs_count > 0 && cid_widths.len() > cs_count * 4 && is_adobe_cjk_registry {
3198 } else {
3200 let fm = font.font_matrix;
3201 let font_matrix = Matrix::new(fm[0], fm[1], fm[2], fm[3], fm[4], fm[5]);
3202 return Ok(PdfFont::CidCff(CidCffPdfFont {
3203 font,
3204 default_width,
3205 cid_widths,
3206 font_matrix,
3207 cmap: None,
3208 pdf_cid_to_gid: None,
3209 identity_cid_to_gid: false,
3210 ordering: ordering.clone(),
3211 code_lengths,
3212 code_to_cid: code_to_cid.clone(),
3213 wmode,
3214 dw2,
3215 w2: w2.clone(),
3216 type1_paths: None,
3217 }));
3218 }
3219 }
3220 {
3222 let base_font = cid_font_dict
3223 .get_name(b"BaseFont")
3224 .map(|n| String::from_utf8_lossy(n).to_string())
3225 .unwrap_or_default();
3226 let sys_data = if ucs2_encoding {
3227 load_system_truetype_font(&base_font)
3235 .or_else(|_| load_cjk_fallback_font(&ordering, &base_font))
3236 .or_else(|_| load_system_truetype_font("DejaVuSans"))
3237 .or_else(|_| load_system_truetype_font("LiberationSans"))
3238 .or_else(|_| load_system_truetype_font("NimbusSans"))?
3239 } else {
3240 load_system_truetype_font(&base_font)
3241 .or_else(|_| load_cjk_fallback_font(&ordering, &base_font))?
3242 };
3243 let identity = ordering == b"Identity";
3248 let is_otto = sys_data.len() > 4 && &sys_data[0..4] == b"OTTO";
3252 let is_ttc_cff = sys_data.len() > 16 && &sys_data[0..4] == b"ttcf" && {
3253 let off = u32::from_be_bytes([
3254 sys_data[12],
3255 sys_data[13],
3256 sys_data[14],
3257 sys_data[15],
3258 ]) as usize;
3259 off + 4 <= sys_data.len() && &sys_data[off..off + 4] == b"OTTO"
3260 };
3261 if is_otto || is_ttc_cff {
3262 return create_cid_cff_from_otf(
3263 &sys_data,
3264 default_width,
3265 cid_widths,
3266 &ordering,
3267 None,
3268 identity,
3269 code_lengths,
3270 code_to_cid.clone(),
3271 wmode,
3272 dw2,
3273 w2.clone(),
3274 );
3275 }
3276 let data = sys_data;
3277 let units_per_em = get_units_per_em(&data) as f64;
3278 let cmap = parse_cmap(&data);
3279 Ok(PdfFont::CidTrueType(CidTrueTypePdfFont {
3280 data,
3281 default_width,
3282 cid_widths,
3283 cmap,
3284 units_per_em,
3285 identity_cid_to_gid: false,
3286 substituted: true,
3287 cid_to_gid_map: None,
3288 to_unicode,
3289 ordering: ordering.clone(),
3290 ucs2_encoding,
3291 code_lengths,
3292 code_to_cid: code_to_cid.clone(),
3293 wmode,
3294 dw2,
3295 w2,
3296 }))
3297 }
3298 }
3299 _ => Err(PdfError::Other(format!(
3300 "Unsupported CIDFont subtype: {}",
3301 String::from_utf8_lossy(cid_subtype)
3302 ))),
3303 }
3304}
3305
3306fn extract_hex_tokens(s: &str) -> Vec<&str> {
3311 let mut tokens = Vec::new();
3312 let mut rest = s;
3313 while let Some(start) = rest.find('<') {
3314 rest = &rest[start + 1..];
3315 if let Some(end) = rest.find('>') {
3316 let hex = rest[..end].trim();
3317 if !hex.is_empty() {
3318 tokens.push(hex);
3319 }
3320 rest = &rest[end + 1..];
3321 } else {
3322 break;
3323 }
3324 }
3325 tokens
3326}
3327
3328fn hex_to_unicode(hex: &str) -> Option<u32> {
3331 if hex.len() <= 4 {
3332 u32::from_str_radix(hex, 16).ok()
3333 } else {
3334 match hex {
3337 "00660066" => Some(0xFB00), "00660069" => Some(0xFB01), "0066006C" => Some(0xFB02), "006600660069" => Some(0xFB03), "00660066006C" => Some(0xFB04), "017F0074" => Some(0xFB05), "00730074" => Some(0xFB06), _ => {
3345 u32::from_str_radix(&hex[..hex.len().min(4)], 16).ok()
3347 }
3348 }
3349 }
3350}
3351
3352fn parse_to_unicode(data: &[u8]) -> HashMap<u16, u32> {
3357 let mut map = HashMap::new();
3358 let text = String::from_utf8_lossy(data);
3359
3360 let mut in_bfchar = false;
3363 let mut in_bfrange = false;
3364 let mut range_tokens: Vec<&str> = Vec::new();
3365
3366 for line in text.lines() {
3367 let trimmed = line.trim();
3368 if trimmed.ends_with("beginbfchar") {
3369 in_bfchar = true;
3370 continue;
3371 }
3372 if trimmed == "endbfchar" {
3373 in_bfchar = false;
3374 continue;
3375 }
3376 if trimmed.ends_with("beginbfrange") {
3377 in_bfrange = true;
3378 range_tokens.clear();
3379 continue;
3380 }
3381 if trimmed == "endbfrange" {
3382 in_bfrange = false;
3383 range_tokens.clear();
3384 continue;
3385 }
3386
3387 if in_bfchar {
3388 let tokens = extract_hex_tokens(trimmed);
3389 if tokens.len() >= 2
3390 && let Ok(cid) = u32::from_str_radix(tokens[0], 16)
3391 && let Some(unicode) = hex_to_unicode(tokens[1])
3392 {
3393 map.insert(cid as u16, unicode);
3394 }
3395 }
3396
3397 if in_bfrange {
3398 let line_tokens = extract_hex_tokens(trimmed);
3399 if trimmed.contains('[') {
3401 let all_before_bracket: Vec<&str> = {
3403 let before = trimmed.split('[').next().unwrap_or("");
3404 extract_hex_tokens(before)
3405 };
3406 let in_bracket = {
3407 let after_open = trimmed.split('[').nth(1).unwrap_or("");
3408 let before_close = after_open.split(']').next().unwrap_or(after_open);
3409 extract_hex_tokens(before_close)
3410 };
3411 if all_before_bracket.len() >= 2
3412 && let (Some(start), Some(end)) = (
3413 u32::from_str_radix(all_before_bracket[0], 16).ok(),
3414 u32::from_str_radix(all_before_bracket[1], 16).ok(),
3415 )
3416 {
3417 for (j, cid) in (start..=end).enumerate() {
3418 if j < in_bracket.len()
3419 && let Some(u) = hex_to_unicode(in_bracket[j])
3420 {
3421 map.insert(cid as u16, u);
3422 }
3423 }
3424 }
3425 } else if line_tokens.len() >= 3 {
3426 if let (Some(start), Some(end), Some(mut dst)) = (
3428 u32::from_str_radix(line_tokens[0], 16).ok(),
3429 u32::from_str_radix(line_tokens[1], 16).ok(),
3430 hex_to_unicode(line_tokens[2]),
3431 ) {
3432 for cid in start..=end {
3433 map.insert(cid as u16, dst);
3434 dst += 1;
3435 }
3436 }
3437 }
3438 }
3439 }
3440
3441 map
3442}
3443
3444fn parse_cid_widths(cid_font_dict: &PdfDict, resolver: &Resolver) -> HashMap<u16, f64> {
3445 let mut widths = HashMap::new();
3446 let w_obj = match cid_font_dict.get(b"W") {
3448 Some(obj) => match resolver.deref(obj) {
3449 Ok(resolved) => resolved,
3450 Err(_) => return widths,
3451 },
3452 None => return widths,
3453 };
3454 let w_arr = match w_obj.as_array() {
3455 Some(arr) => arr,
3456 None => return widths,
3457 };
3458 let mut i = 0;
3459 while i < w_arr.len() {
3460 let first_cid = match &w_arr[i] {
3461 PdfObj::Int(n) => *n as u16,
3462 _ => break,
3463 };
3464 i += 1;
3465 if i >= w_arr.len() {
3466 break;
3467 }
3468 let next = resolver.deref(&w_arr[i]).unwrap_or(w_arr[i].clone());
3470 match &next {
3471 PdfObj::Array(arr) => {
3472 for (j, w_obj) in arr.iter().enumerate() {
3474 let w_val = w_obj
3476 .as_f64()
3477 .or_else(|| resolver.deref(w_obj).ok().and_then(|r| r.as_f64()))
3478 .unwrap_or(0.0);
3479 widths.insert(first_cid + j as u16, w_val / 1000.0);
3480 }
3481 i += 1;
3482 }
3483 _ => {
3484 let last_cid = match &next {
3486 PdfObj::Int(n) => *n as u16,
3487 _ => first_cid,
3488 };
3489 i += 1;
3490 let w = if i < w_arr.len() {
3491 let obj = &w_arr[i];
3492 obj.as_f64()
3493 .or_else(|| resolver.deref(obj).ok().and_then(|r| r.as_f64()))
3494 .unwrap_or(0.0)
3495 / 1000.0
3496 } else {
3497 0.0
3498 };
3499 i += 1;
3500 for cid in first_cid..=last_cid {
3501 widths.insert(cid, w);
3502 }
3503 }
3504 }
3505 }
3506 widths
3507}
3508
3509fn parse_cid_w2(cid_font_dict: &PdfDict, resolver: &Resolver) -> HashMap<u16, [f64; 3]> {
3515 let mut metrics = HashMap::new();
3516 let w2_obj = match cid_font_dict.get(b"W2") {
3517 Some(obj) => match resolver.deref(obj) {
3518 Ok(resolved) => resolved,
3519 Err(_) => return metrics,
3520 },
3521 None => return metrics,
3522 };
3523 let arr = match w2_obj.as_array() {
3524 Some(a) => a,
3525 None => return metrics,
3526 };
3527 let mut i = 0;
3528 while i < arr.len() {
3529 let first_cid = match &arr[i] {
3530 PdfObj::Int(n) => *n as u16,
3531 _ => break,
3532 };
3533 i += 1;
3534 if i >= arr.len() {
3535 break;
3536 }
3537 let next = resolver.deref(&arr[i]).unwrap_or(arr[i].clone());
3538 match &next {
3539 PdfObj::Array(sub) => {
3540 let vals: Vec<f64> = sub.iter().filter_map(|o| o.as_f64()).collect();
3542 for (j, chunk) in vals.chunks(3).enumerate() {
3543 if chunk.len() == 3 {
3544 metrics.insert(first_cid + j as u16, [chunk[0], chunk[1], chunk[2]]);
3545 }
3546 }
3547 i += 1;
3548 }
3549 _ => {
3550 let last_cid = match &next {
3552 PdfObj::Int(n) => *n as u16,
3553 _ => first_cid,
3554 };
3555 i += 1;
3556 if i + 2 < arr.len() {
3557 let w1 = arr[i].as_f64().unwrap_or(-1000.0);
3558 let vx = arr[i + 1].as_f64().unwrap_or(0.0);
3559 let vy = arr[i + 2].as_f64().unwrap_or(880.0);
3560 i += 3;
3561 for cid in first_cid..=last_cid {
3562 metrics.insert(cid, [w1, vx, vy]);
3563 }
3564 } else {
3565 break;
3566 }
3567 }
3568 }
3569 }
3570 metrics
3571}
3572
3573fn strip_pfb(data: &[u8]) -> Vec<u8> {
3579 if data.len() < 2 || data[0] != 0x80 {
3580 return data.to_vec();
3581 }
3582 let mut result = Vec::with_capacity(data.len());
3583 let mut pos = 0;
3584 while pos + 6 <= data.len() && data[pos] == 0x80 {
3585 let segment_type = data[pos + 1];
3586 if segment_type == 3 {
3587 break; }
3589 let len = u32::from_le_bytes([data[pos + 2], data[pos + 3], data[pos + 4], data[pos + 5]])
3590 as usize;
3591 pos += 6;
3592 let end = (pos + len).min(data.len());
3593 result.extend_from_slice(&data[pos..end]);
3594 pos = end;
3595 }
3596 result
3597}
3598
3599impl PdfFont {
3602 pub fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
3605 match self {
3606 PdfFont::Type1(f) => f.glyph_path(char_code),
3607 PdfFont::TrueType(f) => f.glyph_path(char_code),
3608 PdfFont::Cff(f) => f.glyph_path(char_code),
3609 PdfFont::CidTrueType(f) => f.glyph_path_cid(char_code as u16),
3610 PdfFont::CidCff(f) => f.glyph_path_cid(char_code as u16),
3611 PdfFont::Type3(_) => None,
3612 }
3613 }
3614
3615 pub fn glyph_path_cid(&self, cid: u16) -> Option<PsPath> {
3617 match self {
3618 PdfFont::CidTrueType(f) => f.glyph_path_cid(cid),
3619 PdfFont::CidCff(f) => f.glyph_path_cid(cid),
3620 _ => self.glyph_path(cid as u8),
3621 }
3622 }
3623
3624 pub fn glyph_path_unicode(&self, unicode: u16) -> Option<PsPath> {
3627 match self {
3628 PdfFont::CidTrueType(f) => f.glyph_path_unicode(unicode),
3629 PdfFont::CidCff(f) => f.glyph_path_unicode(unicode),
3630 _ => None,
3631 }
3632 }
3633
3634 pub fn glyph_width_unicode(&self, unicode: u16) -> f64 {
3636 match self {
3637 PdfFont::CidTrueType(f) => f.glyph_width_unicode(unicode),
3638 _ => 0.0,
3639 }
3640 }
3641
3642 pub fn glyph_width(&self, char_code: u8) -> f64 {
3644 match self {
3645 PdfFont::Type1(f) => f.widths[char_code as usize],
3646 PdfFont::TrueType(f) => f.widths[char_code as usize],
3647 PdfFont::Cff(f) => f.widths[char_code as usize],
3648 PdfFont::CidTrueType(f) => f.glyph_width_cid(char_code as u16),
3649 PdfFont::CidCff(f) => f.glyph_width_cid(char_code as u16),
3650 PdfFont::Type3(f) => f.widths[char_code as usize],
3651 }
3652 }
3653
3654 pub fn glyph_width_cid(&self, cid: u16) -> f64 {
3656 match self {
3657 PdfFont::CidTrueType(f) => f.glyph_width_cid(cid),
3658 PdfFont::CidCff(f) => f.glyph_width_cid(cid),
3659 _ => self.glyph_width(cid as u8),
3660 }
3661 }
3662
3663 pub fn font_matrix(&self) -> Matrix {
3668 match self {
3669 PdfFont::Type1(f) => f.font_matrix,
3670 PdfFont::TrueType(_) | PdfFont::CidTrueType(_) => Matrix::identity(),
3671 PdfFont::Cff(f) => f.font_matrix,
3672 PdfFont::CidCff(_) => Matrix::identity(),
3673 PdfFont::Type3(f) => f.font_matrix,
3674 }
3675 }
3676
3677 pub fn is_composite(&self) -> bool {
3679 matches!(self, PdfFont::CidTrueType(_) | PdfFont::CidCff(_))
3680 }
3681
3682 pub fn wmode(&self) -> u8 {
3684 match self {
3685 PdfFont::CidTrueType(f) => f.wmode,
3686 PdfFont::CidCff(f) => f.wmode,
3687 _ => 0,
3688 }
3689 }
3690
3691 pub fn dw2(&self) -> [f64; 2] {
3694 match self {
3695 PdfFont::CidTrueType(f) => f.dw2,
3696 PdfFont::CidCff(f) => f.dw2,
3697 _ => [880.0, -1000.0],
3698 }
3699 }
3700
3701 pub fn vertical_metrics_cid(&self, cid: u16) -> [f64; 3] {
3704 match self {
3705 PdfFont::CidTrueType(f) => {
3706 if let Some(&m) = f.w2.get(&cid) {
3707 m
3708 } else {
3709 let w0 = f.cid_widths.get(&cid).copied().unwrap_or(f.default_width) * 1000.0;
3711 [f.dw2[1], w0 / 2.0, f.dw2[0]]
3712 }
3713 }
3714 PdfFont::CidCff(f) => {
3715 if let Some(&m) = f.w2.get(&cid) {
3716 m
3717 } else {
3718 let w0 = f.cid_widths.get(&cid).copied().unwrap_or(f.default_width) * 1000.0;
3719 [f.dw2[1], w0 / 2.0, f.dw2[0]]
3720 }
3721 }
3722 _ => [-1000.0, 500.0, 880.0],
3723 }
3724 }
3725
3726 pub fn has_cid_glyph(&self, cid: u16) -> bool {
3730 match self {
3731 PdfFont::CidTrueType(f) => f.has_glyph(cid),
3732 PdfFont::CidCff(_) => true, _ => false,
3734 }
3735 }
3736
3737 pub fn resolve_code_to_cid(&self, code: u32) -> u32 {
3740 match self {
3741 PdfFont::CidTrueType(f) => f.code_to_cid.get(&code).copied().unwrap_or(code),
3742 PdfFont::CidCff(f) => f.code_to_cid.get(&code).copied().unwrap_or(code),
3743 _ => code,
3744 }
3745 }
3746
3747 pub fn code_width(&self, first_byte: u8) -> usize {
3750 match self {
3751 PdfFont::CidTrueType(f) => {
3752 let w = f.code_lengths[first_byte as usize];
3753 if w == 0 { 2 } else { w as usize }
3754 }
3755 PdfFont::CidCff(f) => {
3756 let w = f.code_lengths[first_byte as usize];
3757 if w == 0 { 2 } else { w as usize }
3758 }
3759 _ => 1,
3760 }
3761 }
3762
3763 pub fn is_type3(&self) -> bool {
3765 matches!(self, PdfFont::Type3(_))
3766 }
3767
3768 pub fn type3_char_proc(&self, char_code: u8) -> Option<&[u8]> {
3770 match self {
3771 PdfFont::Type3(f) => f.char_procs.get(&char_code).map(|v| v.as_slice()),
3772 _ => None,
3773 }
3774 }
3775
3776 pub fn type3_resources(&self) -> Option<&PdfDict> {
3778 match self {
3779 PdfFont::Type3(f) => Some(&f.resources),
3780 _ => None,
3781 }
3782 }
3783}
3784
3785impl Type1PdfFont {
3786 fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
3787 let glyph_name = self.encoding[char_code as usize].as_deref();
3788 let charstring = glyph_name
3789 .and_then(|name| self.font.charstrings.get(name))
3790 .or_else(|| {
3791 if !self.builtin_fallback {
3792 return None;
3793 }
3794 let builtin = self.font.encoding.get(char_code as usize)?;
3795 if builtin != ".notdef" && glyph_name.map_or(true, |n| n != builtin) {
3796 self.font.charstrings.get(builtin.as_str())
3797 } else {
3798 None
3799 }
3800 })?;
3801 let cs_lookup =
3803 |name: &str| -> Option<Vec<u8>> { self.font.charstrings.get(name).cloned() };
3804 let result = execute_charstring_mm(
3805 charstring,
3806 &self.font.subrs,
3807 self.font.len_iv,
3808 false,
3809 Some(&cs_lookup),
3810 self.weight_vector.as_deref(),
3811 )
3812 .ok()?;
3813 if self.per_char_width_scale {
3817 let pdf_w = self.widths[char_code as usize];
3818 let font_w = result.width_x * self.font_matrix.a;
3819 if font_w.abs() > 0.001 && pdf_w > 0.001 && (pdf_w / font_w - 1.0).abs() > 0.01 {
3820 return Some(result.path.transform(&Matrix::scale(pdf_w / font_w, 1.0)));
3821 }
3822 }
3823 Some(result.path)
3824 }
3825}
3826
3827impl TrueTypePdfFont {
3828 fn detect_gid_hex(encoding: &[Option<String>; 256]) -> bool {
3831 encoding.iter().any(|name| {
3832 if let Some(n) = name {
3833 n.starts_with('g')
3834 && n.len() > 1
3835 && n[1..].bytes().all(|b| b.is_ascii_hexdigit())
3836 && n[1..]
3837 .bytes()
3838 .any(|b| b.is_ascii_hexdigit() && !b.is_ascii_digit())
3839 } else {
3840 false
3841 }
3842 })
3843 }
3844
3845 fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
3846 let gid = self.char_code_to_gid(char_code);
3847 let gid = gid?;
3848 let path = skrifa_glyph_path(&self.data, gid, self.units_per_em).or_else(|| {
3849 let glyf_data = get_glyf_data(&self.data, gid)?;
3851 let data_ref = &self.data;
3852 let p = parse_glyf_to_path(&glyf_data, &|cid| get_glyf_data(data_ref, cid));
3853 if p.is_empty() { None } else { Some(p) }
3854 })?;
3855 let scale = 1.0 / self.units_per_em;
3856 let m = Matrix::scale(scale, scale);
3857 Some(path.transform(&m))
3858 }
3859
3860 fn char_code_to_gid(&self, char_code: u8) -> Option<u16> {
3861 if self.identity_gid {
3865 if let Some(&gid) = self.cmap.get(&(char_code as u32)) {
3866 return Some(gid);
3867 }
3868 if let Some(&gid) = self.cmap.get(&(0xF000 + char_code as u32)) {
3869 return Some(gid);
3870 }
3871 return Some(char_code as u16);
3872 }
3873 if let Some(glyph_name) = &self.encoding[char_code as usize] {
3874 if self.cmap_is_unicode {
3879 if let Some(unicode) = stet_fonts::agl::glyph_name_to_unicode(glyph_name)
3880 && let Some(&gid) = self.cmap.get(&(unicode as u32))
3881 {
3882 return Some(gid);
3883 }
3884 }
3885 }
3886 if self.cmap_is_unicode {
3892 if let Some(&unicode) = self.to_unicode.get(&(char_code as u16))
3893 && let Some(&gid) = self.cmap.get(&unicode)
3894 {
3895 return Some(gid);
3896 }
3897 }
3898 if let Some(glyph_name) = &self.encoding[char_code as usize] {
3899 if glyph_name.starts_with('g')
3903 && glyph_name.len() > 1
3904 && glyph_name[1..].bytes().all(|b| b.is_ascii_hexdigit())
3905 {
3906 let suffix = &glyph_name[1..];
3907 let gid = if self.gid_hex {
3908 u16::from_str_radix(suffix, 16).ok()
3909 } else {
3910 suffix.parse::<u16>().ok()
3911 };
3912 if let Some(gid) = gid {
3913 return Some(gid);
3914 }
3915 }
3916 }
3917 if let Some(&gid) = self.cmap.get(&(char_code as u32)) {
3920 return Some(gid);
3921 }
3922 if let Some(glyph_name) = &self.encoding[char_code as usize] {
3923 if let Some(&gid) = self.post_name_to_gid.get(glyph_name.as_str()) {
3925 return Some(gid);
3926 }
3927 }
3928 if let Some(&gid) = self.cmap.get(&(0xF000 + char_code as u32)) {
3930 return Some(gid);
3931 }
3932 if let Some(&unicode) = self.to_unicode.get(&(char_code as u16)) {
3937 if let Some(name) = stet_fonts::system_fonts::unicode_to_glyph_name(unicode) {
3938 if let Some(&gid) = self.post_name_to_gid.get(name) {
3939 return Some(gid);
3940 }
3941 }
3942 if let Ok(font_ref) = skrifa::FontRef::new(&self.data) {
3943 let charmap = font_ref.charmap();
3944 if let Some(gid) = charmap.map(unicode) {
3945 return Some(gid.to_u32() as u16);
3946 }
3947 }
3948 if !self.cmap.is_empty() {
3953 use stet_fonts::truetype::{find_table, read_u16};
3954 let mapped: std::collections::HashSet<u16> = self.cmap.values().copied().collect();
3955 let num_glyphs = find_table(&self.data, b"maxp")
3956 .map(|(off, _)| read_u16(&self.data, off + 4))
3957 .unwrap_or(0);
3958 for gid in 0..num_glyphs {
3962 if mapped.contains(&gid) {
3963 continue;
3964 }
3965 if let Some(glyf_data) = get_glyf_data(&self.data, gid) {
3966 if glyf_data.len() >= 2 {
3967 let num_contours = stet_fonts::truetype::read_i16(&glyf_data, 0);
3968 if num_contours < 0 {
3969 return Some(gid);
3970 }
3971 }
3972 }
3973 }
3974 }
3975 }
3976 if self.cmap.is_empty() {
3977 Some(char_code as u16)
3979 } else {
3980 None
3981 }
3982 }
3983}
3984
3985impl CidTrueTypePdfFont {
3986 fn resolve_cid(&self, code: u16) -> u16 {
3988 if self.ucs2_encoding && !self.ordering.is_empty() && self.code_to_cid.is_empty() {
3992 super::cid_unicode::unicode_to_cid(&self.ordering, code as u32).unwrap_or(code)
3993 } else {
3994 code
3995 }
3996 }
3997
3998 fn glyph_path_cid(&self, cid: u16) -> Option<PsPath> {
3999 if std::env::var("STET_DEBUG_TEXT").is_ok() {
4000 eprintln!(
4001 "[cid_tt] cid={} sub={} ordering={} identity={} to_unicode={} cmap={} cid_to_gid_map={}",
4002 cid,
4003 self.substituted,
4004 String::from_utf8_lossy(&self.ordering),
4005 self.identity_cid_to_gid,
4006 !self.to_unicode.is_empty(),
4007 !self.cmap.is_empty(),
4008 self.cid_to_gid_map.is_some()
4009 );
4010 }
4011 let gid = if self.ucs2_encoding && !self.cmap.is_empty() && self.code_to_cid.is_empty() {
4012 if let Some(&g) = self.cmap.get(&(cid as u32)) {
4014 g
4015 } else {
4016 return None;
4017 }
4018 } else if self.ucs2_encoding && self.substituted && !self.ordering.is_empty() {
4019 let unicode = super::cid_unicode::cid_to_unicode(&self.ordering, cid)?;
4021 *self.cmap.get(&unicode)?
4022 } else if let Some(ref map) = self.cid_to_gid_map {
4023 *map.get(cid as usize).unwrap_or(&0)
4025 } else if self.substituted && !self.to_unicode.is_empty() {
4026 if let Some(&unicode) = self.to_unicode.get(&cid) {
4028 *self.cmap.get(&unicode)?
4029 } else {
4030 cid
4034 }
4035 } else if self.substituted && !self.ordering.is_empty() && self.ordering != b"Identity" {
4036 let unicode = super::cid_unicode::cid_to_unicode(&self.ordering, cid)?;
4038 *self.cmap.get(&unicode)?
4039 } else if self.identity_cid_to_gid {
4040 cid
4042 } else if self.substituted && !self.cmap.is_empty() {
4043 if let Some(&g) = self.cmap.get(&(cid as u32)) {
4046 g
4047 } else {
4048 cid
4049 }
4050 } else if !self.cmap.is_empty() {
4051 *self.cmap.get(&(cid as u32))?
4053 } else {
4054 cid
4055 };
4056 let path = skrifa_glyph_path(&self.data, gid, self.units_per_em).or_else(|| {
4057 let glyf_data = get_glyf_data(&self.data, gid)?;
4060 let data_ref = &self.data;
4061 let p = parse_glyf_to_path(&glyf_data, &|cid| get_glyf_data(data_ref, cid));
4062 if p.is_empty() || p.segments.len() > 10_000 {
4065 None
4066 } else {
4067 Some(p)
4068 }
4069 });
4070 let path = path?;
4071 let scale = 1.0 / self.units_per_em;
4072 let m = if self.substituted {
4076 let pdf_w = self.cid_widths.get(&cid).copied();
4081 let font_w =
4082 hmtx_advance_width(&self.data, gid, self.units_per_em).unwrap_or(0.0) / 1000.0;
4083 if let Some(pw) = pdf_w {
4084 if font_w > 0.001 && pw > 0.001 {
4085 Matrix::new(scale * pw / font_w, 0.0, 0.0, scale, 0.0, 0.0)
4086 } else {
4087 Matrix::scale(scale, scale)
4088 }
4089 } else {
4090 Matrix::scale(scale, scale)
4091 }
4092 } else {
4093 Matrix::scale(scale, scale)
4094 };
4095 Some(path.transform(&m))
4096 }
4097
4098 fn has_glyph(&self, cid: u16) -> bool {
4102 if self.cid_widths.contains_key(&cid) {
4106 return true;
4107 }
4108 let gid = if let Some(ref map) = self.cid_to_gid_map {
4110 *map.get(cid as usize).unwrap_or(&0)
4111 } else if self.substituted && !self.to_unicode.is_empty() {
4112 if let Some(&unicode) = self.to_unicode.get(&cid) {
4114 if let Some(&g) = self.cmap.get(&unicode) {
4115 g
4116 } else {
4117 return false;
4118 }
4119 } else {
4120 return false;
4121 }
4122 } else if self.identity_cid_to_gid {
4123 cid
4124 } else {
4125 return true; };
4127 let num_glyphs = stet_fonts::truetype::get_num_glyphs(&self.data);
4129 (gid as u32) < num_glyphs
4130 }
4131
4132 fn glyph_width_cid(&self, cid: u16) -> f64 {
4133 let resolved = self.resolve_cid(cid);
4134 if let Some(&w) = self.cid_widths.get(&resolved) {
4135 return w;
4136 }
4137 if self.substituted && !self.to_unicode.is_empty() {
4142 if let Some(&unicode) = self.to_unicode.get(&cid) {
4143 if let Some(&gid) = self.cmap.get(&unicode) {
4144 if let Some(w) = hmtx_advance_width(&self.data, gid, self.units_per_em) {
4145 return w / 1000.0;
4146 }
4147 }
4148 }
4149 }
4150 self.default_width
4151 }
4152
4153 fn glyph_path_unicode(&self, unicode: u16) -> Option<PsPath> {
4156 let &gid = self.cmap.get(&(unicode as u32))?;
4157 let path = skrifa_glyph_path(&self.data, gid, self.units_per_em)?;
4158 let scale = 1.0 / self.units_per_em;
4159 let m = Matrix::scale(scale, scale);
4160 Some(path.transform(&m))
4161 }
4162
4163 fn glyph_width_unicode(&self, unicode: u16) -> f64 {
4166 if let Some(&gid) = self.cmap.get(&(unicode as u32)) {
4167 hmtx_advance_width(&self.data, gid, self.units_per_em)
4170 .map(|w| w / 1000.0)
4171 .unwrap_or(self.default_width)
4172 } else {
4173 self.default_width
4174 }
4175 }
4176}
4177
4178impl CidCffPdfFont {
4179 fn glyph_path_at_gid(&self, gid: usize) -> Option<PsPath> {
4181 if gid >= self.font.char_strings.len() {
4182 return None;
4183 }
4184 let (default_width_x, nominal_width_x, local_subrs, fd_font_matrix) = if self.font.is_cid
4185 && !self.font.fd_select.is_empty()
4186 && !self.font.fd_array.is_empty()
4187 {
4188 let fd_idx = *self.font.fd_select.get(gid).unwrap_or(&0) as usize;
4189 if let Some(fd) = self.font.fd_array.get(fd_idx) {
4190 (
4191 fd.default_width_x,
4192 fd.nominal_width_x,
4193 &fd.local_subrs,
4194 fd.font_matrix,
4195 )
4196 } else {
4197 (
4198 self.font.default_width_x,
4199 self.font.nominal_width_x,
4200 &self.font.local_subrs,
4201 None,
4202 )
4203 }
4204 } else {
4205 (
4206 self.font.default_width_x,
4207 self.font.nominal_width_x,
4208 &self.font.local_subrs,
4209 None,
4210 )
4211 };
4212 let result = execute_type2_charstring(
4213 &self.font.char_strings[gid],
4214 local_subrs,
4215 &self.font.global_subrs,
4216 default_width_x,
4217 nominal_width_x,
4218 false,
4219 )
4220 .ok()?;
4221 let effective_fm = if let Some(fd_fm) = fd_font_matrix {
4222 let fd = Matrix::new(fd_fm[0], fd_fm[1], fd_fm[2], fd_fm[3], fd_fm[4], fd_fm[5]);
4223 if fd.a.abs() < 0.01 || fd.d.abs() < 0.01 {
4224 fd
4225 } else {
4226 self.font_matrix.concat(&fd)
4227 }
4228 } else {
4229 self.font_matrix
4230 };
4231 Some(result.path.transform(&effective_fm))
4232 }
4233
4234 fn glyph_path_unicode(&self, unicode: u16) -> Option<PsPath> {
4236 let cmap = self.cmap.as_ref()?;
4237 let &gid = cmap.get(&(unicode as u32))?;
4238 self.glyph_path_at_gid(gid as usize)
4239 }
4240
4241 fn glyph_path_cid(&self, cid: u16) -> Option<PsPath> {
4242 if let Some(ref paths) = self.type1_paths {
4244 return paths.get(&cid).cloned();
4245 }
4246 let gid = if let Some(ref map) = self.pdf_cid_to_gid {
4250 *map.get(cid as usize).unwrap_or(&0) as usize
4252 } else if self.identity_cid_to_gid {
4253 cid as usize
4256 } else if let Some(ref cmap) = self.cmap {
4257 if !self.ordering.is_empty() && self.ordering != b"Identity" {
4262 let unicode = super::cid_unicode::cid_to_unicode(&self.ordering, cid)?;
4263 let gid_opt = cjk_fullwidth_alternative(unicode)
4268 .and_then(|alt| cmap.get(&alt))
4269 .or_else(|| cmap.get(&unicode));
4270 *gid_opt? as usize
4271 } else {
4272 *cmap.get(&(cid as u32))? as usize
4273 }
4274 } else if !self.font.cid_to_gid.is_empty() {
4275 let g = *self.font.cid_to_gid.get(cid as usize)?;
4276 if g == 0xFFFF {
4277 return None;
4278 }
4279 g as usize
4280 } else {
4281 cid as usize
4282 };
4283 self.glyph_path_at_gid(gid)
4284 }
4285
4286 fn glyph_width_cid(&self, cid: u16) -> f64 {
4287 self.cid_widths
4289 .get(&cid)
4290 .copied()
4291 .unwrap_or(self.default_width)
4292 }
4293}
4294
4295impl CffPdfFont {
4296 fn glyph_path(&self, char_code: u8) -> Option<PsPath> {
4297 let glyph_name = self.encoding[char_code as usize].as_deref()?;
4298 let gid = self
4305 .font
4306 .charset
4307 .iter()
4308 .position(|name| name == glyph_name)
4309 .or_else(|| {
4310 let cff_gid = self
4311 .font
4312 .encoding
4313 .get(char_code as usize)
4314 .copied()
4315 .unwrap_or(0) as usize;
4316 if cff_gid > 0 && cff_gid < self.font.char_strings.len() {
4317 Some(cff_gid)
4318 } else {
4319 None
4320 }
4321 });
4322 let gid = gid?;
4323 if gid >= self.font.char_strings.len() {
4324 return None;
4325 }
4326 let result = execute_type2_charstring(
4327 &self.font.char_strings[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 if let Some((adx, ady, bchar, achar)) = result.seac {
4338 return self.compose_seac(adx, ady, bchar, achar);
4339 }
4340
4341 Some(result.path)
4342 }
4343
4344 fn compose_seac(&self, adx: f64, ady: f64, bchar: u8, achar: u8) -> Option<PsPath> {
4347 use stet_fonts::encoding::STANDARD_ENCODING;
4348
4349 let base_name = STANDARD_ENCODING.get(bchar as usize).copied().unwrap_or("");
4350 let accent_name = STANDARD_ENCODING.get(achar as usize).copied().unwrap_or("");
4351
4352 let base_gid = self.font.charset.iter().position(|n| n == base_name)?;
4353 let accent_gid = self.font.charset.iter().position(|n| n == accent_name)?;
4354
4355 let base_result = execute_type2_charstring(
4356 &self.font.char_strings[base_gid],
4357 &self.font.local_subrs,
4358 &self.font.global_subrs,
4359 self.font.default_width_x,
4360 self.font.nominal_width_x,
4361 false,
4362 )
4363 .ok()?;
4364
4365 let accent_result = execute_type2_charstring(
4366 &self.font.char_strings[accent_gid],
4367 &self.font.local_subrs,
4368 &self.font.global_subrs,
4369 self.font.default_width_x,
4370 self.font.nominal_width_x,
4371 false,
4372 )
4373 .ok()?;
4374
4375 let mut combined = base_result.path;
4377 let offset = Matrix::translate(adx, ady);
4378 let shifted_accent = accent_result.path.transform(&offset);
4379 combined
4380 .segments
4381 .extend_from_slice(&shifted_accent.segments);
4382 Some(combined)
4383 }
4384}
4385
4386struct PsPathPen {
4388 path: PsPath,
4389 cur_x: f64,
4390 cur_y: f64,
4391}
4392
4393impl skrifa::outline::OutlinePen for PsPathPen {
4394 fn move_to(&mut self, x: f32, y: f32) {
4395 self.cur_x = x as f64;
4396 self.cur_y = y as f64;
4397 self.path
4398 .segments
4399 .push(PathSegment::MoveTo(self.cur_x, self.cur_y));
4400 }
4401 fn line_to(&mut self, x: f32, y: f32) {
4402 self.cur_x = x as f64;
4403 self.cur_y = y as f64;
4404 self.path
4405 .segments
4406 .push(PathSegment::LineTo(self.cur_x, self.cur_y));
4407 }
4408 fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
4409 let cx = cx as f64;
4410 let cy = cy as f64;
4411 let ex = x as f64;
4412 let ey = y as f64;
4413 let cp1x = self.cur_x + 2.0 / 3.0 * (cx - self.cur_x);
4415 let cp1y = self.cur_y + 2.0 / 3.0 * (cy - self.cur_y);
4416 let cp2x = ex + 2.0 / 3.0 * (cx - ex);
4417 let cp2y = ey + 2.0 / 3.0 * (cy - ey);
4418 self.cur_x = ex;
4419 self.cur_y = ey;
4420 self.path.segments.push(PathSegment::CurveTo {
4421 x1: cp1x,
4422 y1: cp1y,
4423 x2: cp2x,
4424 y2: cp2y,
4425 x3: ex,
4426 y3: ey,
4427 });
4428 }
4429 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
4430 self.cur_x = x as f64;
4431 self.cur_y = y as f64;
4432 self.path.segments.push(PathSegment::CurveTo {
4433 x1: cx0 as f64,
4434 y1: cy0 as f64,
4435 x2: cx1 as f64,
4436 y2: cy1 as f64,
4437 x3: self.cur_x,
4438 y3: self.cur_y,
4439 });
4440 }
4441 fn close(&mut self) {
4442 self.path.segments.push(PathSegment::ClosePath);
4443 }
4444}
4445
4446pub(crate) fn winansi_byte_to_unicode(byte: u8) -> u16 {
4455 match byte {
4456 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,
4484 }
4485}
4486
4487fn hmtx_advance_width(font_data: &[u8], gid: u16, units_per_em: f64) -> Option<f64> {
4490 use stet_fonts::truetype::{find_table, read_u16};
4491 let (hhea_off, _) = find_table(font_data, b"hhea")?;
4492 let (hmtx_off, _) = find_table(font_data, b"hmtx")?;
4493 if hhea_off + 36 > font_data.len() {
4494 return None;
4495 }
4496 let num_h_metrics = read_u16(font_data, hhea_off + 34) as usize;
4497 let gid = gid as usize;
4498 let advance = if gid < num_h_metrics {
4499 let offset = hmtx_off + gid * 4;
4500 if offset + 2 > font_data.len() {
4501 return None;
4502 }
4503 read_u16(font_data, offset)
4504 } else {
4505 if num_h_metrics == 0 {
4507 return None;
4508 }
4509 let offset = hmtx_off + (num_h_metrics - 1) * 4;
4510 if offset + 2 > font_data.len() {
4511 return None;
4512 }
4513 read_u16(font_data, offset)
4514 };
4515 Some(advance as f64 / units_per_em * 1000.0)
4517}
4518
4519fn skrifa_glyph_path(font_data: &[u8], gid: u16, units_per_em: f64) -> Option<PsPath> {
4520 let font_ref = skrifa::FontRef::from_index(font_data, 0).ok()?;
4522 let outlines = font_ref.outline_glyphs();
4523 let glyph = outlines.get(skrifa::GlyphId::new(gid as u32))?;
4524
4525 let hinting = skrifa::outline::HintingInstance::new(
4529 &outlines,
4530 skrifa::prelude::Size::new(units_per_em as f32),
4531 skrifa::instance::LocationRef::default(),
4532 skrifa::outline::HintingOptions {
4533 engine: skrifa::outline::Engine::Interpreter,
4534 target: skrifa::outline::Target::Mono,
4535 },
4536 )
4537 .ok();
4538
4539 let mut pen = PsPathPen {
4540 path: PsPath::new(),
4541 cur_x: 0.0,
4542 cur_y: 0.0,
4543 };
4544
4545 let result = if let Some(ref instance) = hinting {
4546 glyph.draw(instance, &mut pen)
4547 } else {
4548 glyph.draw(
4549 skrifa::outline::DrawSettings::unhinted(
4550 skrifa::prelude::Size::new(units_per_em as f32),
4551 skrifa::instance::LocationRef::default(),
4552 ),
4553 &mut pen,
4554 )
4555 };
4556
4557 result.ok()?;
4558 if pen.path.is_empty() {
4559 None
4560 } else {
4561 Some(pen.path)
4562 }
4563}