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