1use std::cell::Cell;
29use std::collections::HashMap;
30use std::rc::Rc;
31
32use crate::Engine;
33use crate::nodes::{LateData, NodeData, RUNNING, Whatsit, id, whatsit_sub};
34use crate::token::TokenList;
35
36#[allow(missing_docs)]
38pub mod literal_mode {
39 pub const ORIGIN: u8 = 0;
40 pub const PAGE: u8 = 1;
41 pub const DIRECT: u8 = 2;
42 pub const RAW: u8 = 3;
43}
44
45#[allow(missing_docs)]
47pub mod colorstack_cmd {
48 pub const SET: u8 = 0;
49 pub const PUSH: u8 = 1;
50 pub const POP: u8 = 2;
51 pub const CURRENT: u8 = 3;
52}
53
54#[derive(Clone, Debug, Default)]
56pub struct PdfObject {
57 pub num: i32,
59 pub data: Vec<u8>,
61 pub stream: bool,
63 pub attr: Vec<u8>,
65 pub file: bool,
67 pub uncompressed: bool,
69 pub defined: bool,
71 pub written: bool,
73}
74
75#[derive(Clone, Debug, Default)]
77pub struct Outline {
78 pub attr: Vec<u8>,
80 pub objnum: i32,
82 pub title: i32,
84 pub action: i32,
86 pub count: i32,
88}
89
90#[derive(Clone, Debug)]
92pub struct ColorStack {
93 pub page: bool,
96 pub mode: u8,
100 pub init: Vec<u8>,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum ObjKind {
107 Obj,
109 Annot,
111 Link,
113 Page,
115 Font,
117 Ximage,
119 Xform,
121 Other,
123}
124
125impl ObjKind {
126 pub fn name(self) -> &'static str {
128 match self {
129 ObjKind::Obj => "obj",
130 ObjKind::Annot => "annot",
131 ObjKind::Link | ObjKind::Other => "others",
132 ObjKind::Page => "page",
133 ObjKind::Font => "font",
134 ObjKind::Ximage => "ximage",
135 ObjKind::Xform => "xform",
136 }
137 }
138}
139
140#[derive(Clone, Debug, Default)]
144pub struct LuaPdfTexts {
145 pub catalog: Option<Vec<u8>>,
147 pub info: Option<Vec<u8>>,
149 pub names: Option<Vec<u8>>,
151 pub trailer: Option<Vec<u8>>,
153 pub trailer_id: Option<Vec<u8>>,
155 pub page_attributes: Option<Vec<u8>>,
157 pub page_resources: Option<Vec<u8>>,
159 pub pages_attributes: Option<Vec<u8>>,
161 pub xform_attributes: Option<Vec<u8>>,
163 pub xform_resources: Option<Vec<u8>>,
165}
166
167#[derive(Clone, Debug, Default)]
169pub struct LateLuaContext {
170 pub prints: Vec<(u8, Vec<u8>)>,
172 pub annots: Vec<i32>,
174}
175
176#[derive(Clone, Debug, PartialEq, Eq, Hash)]
178pub enum DestId {
179 Name(Vec<u8>),
181 Num(i32),
183}
184
185impl DestId {
186 fn describe(&self) -> String {
188 match self {
189 DestId::Name(n) => format!("name '{}'", String::from_utf8_lossy(n)),
190 DestId::Num(n) => format!("num '{n}'"),
191 }
192 }
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub enum DestKind {
198 Xyz,
200 Fit,
202 FitH,
204 FitV,
206 FitB,
208 FitBH,
210 FitBV,
212 FitR,
214}
215
216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub struct DestView {
219 pub kind: DestKind,
221 pub zoom: i32,
223 pub dims: [i32; 3],
225}
226
227#[derive(Clone, Debug, PartialEq, Eq)]
229pub enum ActionTarget {
230 Name(Vec<u8>),
232 Num(i32),
234 Page(i32, Vec<u8>),
236}
237
238#[derive(Clone, Debug, PartialEq, Eq)]
240pub enum PdfAction {
241 User(Vec<u8>),
243 Goto {
245 file: Option<Vec<u8>>,
247 target: ActionTarget,
249 new_window: Option<bool>,
251 },
252 Thread {
254 file: Option<Vec<u8>>,
256 target: ActionTarget,
258 },
259}
260
261#[derive(Clone, Copy, Debug, PartialEq, Eq)]
263pub enum DestPos {
264 Xyz {
266 x: i32,
268 y: i32,
270 zoom: i32,
272 },
273 Fit,
275 FitB,
277 FitH(i32),
279 FitBH(i32),
281 FitV(i32),
283 FitBV(i32),
285 FitR([i32; 4]),
287}
288
289#[derive(Clone, Debug)]
291pub struct LinkPiece {
292 pub objnum: i32,
294 pub rect: [i32; 4],
296 pub attr: Vec<u8>,
298 pub action: Vec<u8>,
300 pub user: bool,
302 pub source: PdfAction,
305}
306
307#[derive(Clone, Debug)]
309pub struct ActiveLink {
310 pub level: i32,
313 pub piece: Option<usize>,
315 pub first_open: bool,
318 pub margin: i32,
320 pub continued: bool,
323 pub attr: Vec<u8>,
325 pub action: PdfAction,
327}
328
329pub type AnnotBody = ([i32; 4], Vec<u8>);
331
332#[derive(Clone, Debug, Default)]
335pub struct PageItems {
336 pub annots: Vec<(i32, Option<AnnotBody>)>,
339 pub links: Vec<LinkPiece>,
341 pub dests: Vec<(i32, bool, DestPos)>,
343 pub objects: Vec<PdfObject>,
345 pub attr: (Vec<u8>, Vec<u8>),
347 pub resources: (Vec<u8>, Vec<u8>),
349}
350
351#[derive(Clone, Debug, Default)]
353pub struct DocItems {
354 pub missing_dests: Vec<i32>,
356 pub missing_threads: Vec<(i32, Vec<u8>)>,
358 pub dest_names: Vec<(Vec<u8>, i32)>,
360 pub pages_attr: Vec<u8>,
362 pub trailer_id: Vec<u8>,
364}
365
366#[derive(Clone, Copy, Debug, Default)]
368pub struct DestEntry {
369 pub objnum: i32,
371 pub defined: bool,
373}
374
375#[derive(Clone, Debug, Default)]
377pub struct PdfState {
378 pub page_images: Vec<u32>,
381 pub objects: std::collections::BTreeMap<i32, PdfObject>,
383 pub catalog: Vec<u8>,
385 pub open_action: Option<i32>,
387 pub info: Vec<u8>,
389 pub names: Vec<u8>,
391 pub trailer: Vec<u8>,
393 pub outlines: Vec<Outline>,
395 pub glyph_to_unicode: Vec<(Vec<u8>, Vec<u8>)>,
397 pub colorstacks: Vec<ColorStack>,
399 pub last_obj: i32,
401 pub last_annot: i32,
403 pub last_link: i32,
405 pub counter: Rc<Cell<u32>>,
408 pub kinds: HashMap<i32, ObjKind>,
410 pub page_refs: HashMap<i32, i32>,
412 pub fonts: HashMap<u32, (u32, i32)>,
415 pub font_attrs: HashMap<u32, Vec<u8>>,
417 pub map_entries: Vec<Vec<u8>>,
420 pub lua: LuaPdfTexts,
422 pub retval: i32,
424 pub pos: (i32, i32),
427 pub late: Option<LateLuaContext>,
429 pub page_annots: Vec<i32>,
431 pub output_opened: bool,
434 pub dests: HashMap<DestId, DestEntry>,
436 pub dest_order: Vec<DestId>,
438 pub threads: Vec<(DestId, i32)>,
440 pub page: PageItems,
442 pub pending: Vec<PdfObject>,
445 pub doc: DocItems,
447 pub fatal: bool,
449 pub objstm_stats: (u32, u32),
451 pub stream_written: bool,
455 pub obj_words: usize,
458 pub map_note_done: bool,
460 pub active_links: Vec<ActiveLink>,
462}
463
464impl PdfState {
465 pub fn max_obj(&self) -> i32 {
467 self.counter.get() as i32
468 }
469
470 pub fn named_dests(&self) -> usize {
472 self.dests
473 .keys()
474 .filter(|k| matches!(k, DestId::Name(_)))
475 .count()
476 }
477}
478
479impl Engine {
480 fn scan_pdf_string(&mut self) -> Vec<u8> {
482 let toks = self.scan_toks_expanded();
483 self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
484 }
485
486 pub fn pdf_alloc_obj(&mut self, kind: ObjKind) -> i32 {
488 let n = self.pdf.counter.get() + 1;
489 self.pdf.counter.set(n);
490 let n = n as i32;
491 self.pdf.kinds.insert(n, kind);
492 n
493 }
494
495 pub fn pdf_obj_kind(&self, n: i32) -> Option<ObjKind> {
497 self.pdf.kinds.get(&n).copied()
498 }
499
500 pub fn pdf_page_ref(&mut self, page: i32) -> i32 {
503 if let Some(&n) = self.pdf.page_refs.get(&page) {
504 return n;
505 }
506 let n = self.pdf_alloc_obj(ObjKind::Page);
507 self.pdf.page_refs.insert(page, n);
508 n
509 }
510
511 pub fn pdf_dest_obj(&mut self, id: &DestId) -> i32 {
513 if let Some(d) = self.pdf.dests.get(id) {
514 return d.objnum;
515 }
516 let n = self.pdf_alloc_obj(ObjKind::Other);
517 self.pdf.dests.insert(
518 id.clone(),
519 DestEntry {
520 objnum: n,
521 defined: false,
522 },
523 );
524 self.pdf.dest_order.push(id.clone());
525 n
526 }
527
528 fn pdf_thread_obj(&mut self, id: DestId) -> i32 {
530 if let Some((_, n)) = self.pdf.threads.iter().find(|(t, _)| *t == id) {
531 return *n;
532 }
533 let n = self.pdf_alloc_obj(ObjKind::Other);
534 self.pdf.threads.push((id, n));
535 n
536 }
537
538 pub fn pdf_font(&mut self, f: u32) -> (u32, i32) {
545 if let Some(&r) = self.pdf.fonts.get(&f) {
546 return r;
547 }
548 let name = self.font(f).name.clone();
549 let shared = self
550 .pdf
551 .fonts
552 .iter()
553 .filter(|(g, _)| self.font_exists(**g as i32) && self.font(**g).name == name)
554 .map(|(_, r)| *r)
555 .min();
556 let r = match shared {
557 Some(r) => r,
558 None => (f, self.pdf_alloc_obj(ObjKind::Font)),
559 };
560 self.pdf.fonts.insert(f, r);
561 r
562 }
563
564 pub fn pdf_ensure_colorstacks(&mut self) {
566 if self.pdf.colorstacks.is_empty() {
567 self.pdf.colorstacks.push(ColorStack {
568 page: true,
569 mode: literal_mode::DIRECT,
570 init: b"0 g 0 G".to_vec(),
571 });
572 }
573 }
574
575 pub fn pdf_new_colorstack(&mut self, init: Vec<u8>, page: bool, mode: u8) -> i32 {
578 self.pdf_ensure_colorstacks();
579 self.pdf.colorstacks.push(ColorStack { page, mode, init });
580 (self.pdf.colorstacks.len() - 1) as i32
581 }
582
583 pub fn pdf_append_refobj(&mut self, n: i32) {
586 self.append_whatsit(whatsit_sub::PDF_REFOBJ, Whatsit::PdfRefobj { objnum: n });
587 }
588
589 pub fn pdf_define_object(&mut self, obj: PdfObject, immediate: bool) {
596 if !immediate {
597 self.pdf.obj_words += 5;
598 }
599 let n = obj.num;
600 self.pdf.last_obj = n;
601 self.pdf.stream_written |= immediate && obj.stream;
602 self.pdf.objects.insert(n, obj);
603 if immediate {
604 self.pdf_write_object(n);
605 }
606 }
607
608 fn append_whatsit(&mut self, subtype: u16, w: Whatsit) {
610 let p = self
611 .nodes
612 .alloc(id::WHATSIT, subtype, NodeData::Whatsit(Box::new(w)));
613 self.tail_append(p);
614 }
615
616 fn scan_pdf_rule_spec(&mut self) -> [i32; 3] {
619 let mut d = [RUNNING; 3];
620 loop {
621 let i = if self.scan_keyword("width") {
622 0
623 } else if self.scan_keyword("height") {
624 1
625 } else if self.scan_keyword("depth") {
626 2
627 } else {
628 return d;
629 };
630 self.scan_normal_dimen();
631 d[i] = self.cur_val;
632 }
633 }
634
635 pub fn pdf_fatal(&mut self, msg: &str) -> ! {
638 self.normalize_selector();
639 self.printer.print_nl("! error: (pdf backend): ");
640 self.printer.print_str(msg);
641 self.printer
642 .print_nl("! ==> Fatal error occurred, no output PDF file produced!");
643 self.printer.update_terminal();
644 self.pdf.fatal = true;
647 self.err.history = crate::error::History::FatalErrorStop;
648 let _ = std::fs::remove_file(self.output_path("pdf"));
649 self.printer.close_log();
650 std::panic::resume_unwind(Box::new(crate::error::JumpOut));
651 }
652
653 pub fn pdf_warning(&mut self, msg: &str) {
656 self.printer.print_nl("warning (pdf backend): ");
657 self.printer.print_str(msg);
658 self.printer.print_ln();
659 }
660
661 fn scan_pdf_action(&mut self) -> Option<PdfAction> {
663 if self.scan_keyword("user") {
664 return Some(PdfAction::User(self.scan_pdf_string()));
665 }
666 let goto = if self.scan_keyword("goto") {
667 true
668 } else if self.scan_keyword("thread") {
669 false
670 } else {
671 self.print_err("Action type missing");
672 self.help(&[]);
673 self.error();
674 return None;
675 };
676 let file = if self.scan_keyword("file") {
677 Some(self.scan_pdf_string())
678 } else {
679 None
680 };
681 let target = if goto && self.scan_keyword("page") {
682 self.scan_int();
683 let page = self.cur_val;
684 ActionTarget::Page(page, self.scan_pdf_string())
685 } else if self.scan_keyword("name") {
686 ActionTarget::Name(self.scan_pdf_string())
687 } else if self.scan_keyword("num") {
688 if goto && file.is_some() {
689 self.pdf_fatal("'goto' option cannot be used with both 'file' and 'num'");
690 }
691 self.scan_int();
692 ActionTarget::Num(self.cur_val)
693 } else {
694 self.print_err("identifier type missing");
695 self.help(&[]);
696 self.error();
697 return None;
698 };
699 let new_window = if self.scan_keyword("newwindow") {
700 Some(true)
701 } else if self.scan_keyword("nonewwindow") {
702 Some(false)
703 } else {
704 None
705 };
706 if new_window.is_some() && !(goto && file.is_some()) {
707 self.pdf_fatal(
708 "'newwindow' or 'nonewwindow' must be used with 'goto' and 'file' option",
709 );
710 }
711 Some(if goto {
712 PdfAction::Goto {
713 file,
714 target,
715 new_window,
716 }
717 } else {
718 PdfAction::Thread { file, target }
719 })
720 }
721
722 pub fn pdf_action_text(&mut self, action: &PdfAction) -> Vec<u8> {
726 fn string(out: &mut Vec<u8>, s: &[u8]) {
727 out.push(b'(');
728 out.extend_from_slice(s);
729 out.push(b')');
730 }
731 let mut d = b"<< ".to_vec();
732 match action {
733 PdfAction::User(u) => return u.clone(),
734 PdfAction::Goto {
735 file: None, target, ..
736 } => {
737 d.extend_from_slice(b"/S /GoTo /D ");
738 match target {
739 ActionTarget::Name(n) => {
740 self.pdf_dest_obj(&DestId::Name(n.clone()));
741 string(&mut d, n);
742 }
743 ActionTarget::Num(n) => {
744 let o = self.pdf_dest_obj(&DestId::Num(*n));
745 d.extend_from_slice(format!("{o} 0 R").as_bytes());
746 }
747 ActionTarget::Page(p, spec) => {
748 let o = self.pdf_page_ref((*p).max(1));
749 d.extend_from_slice(format!("[ {o} 0 R ").as_bytes());
750 d.extend_from_slice(spec);
751 d.extend_from_slice(b" ]");
752 }
753 }
754 }
755 PdfAction::Goto {
756 file: Some(f),
757 target,
758 new_window,
759 } => {
760 d.extend_from_slice(b"/F ");
761 string(&mut d, f);
762 d.push(b' ');
763 match new_window {
764 Some(true) => d.extend_from_slice(b" /NewWindow true"),
765 Some(false) => d.extend_from_slice(b" /NewWindow false"),
766 None => {}
767 }
768 match target {
769 ActionTarget::Name(n) => {
770 d.extend_from_slice(b" /S /GoToR /D ");
771 string(&mut d, n);
772 }
773 ActionTarget::Page(p, spec) => {
774 d.extend_from_slice(format!(" /S /GoTo /D [{} ", p - 1).as_bytes());
776 d.extend_from_slice(spec);
777 d.extend_from_slice(b" ]");
778 }
779 ActionTarget::Num(_) => {}
780 }
781 }
782 PdfAction::Thread { file, target } => {
783 if let Some(f) = file {
784 d.extend_from_slice(b"/F ");
786 string(&mut d, f);
787 d.extend_from_slice(b" /S /Thread");
788 } else {
789 d.extend_from_slice(b"/S /Thread /D ");
790 match target {
791 ActionTarget::Name(n) => {
792 self.pdf_thread_obj(DestId::Name(n.clone()));
793 string(&mut d, n);
794 }
795 ActionTarget::Num(n) => {
796 let o = self.pdf_thread_obj(DestId::Num(*n));
797 d.extend_from_slice(format!("{o} 0 R").as_bytes());
798 }
799 ActionTarget::Page(..) => {}
800 }
801 }
802 }
803 }
804 d.extend_from_slice(b" >>");
805 d
806 }
807
808 fn pdf_write_raw(&mut self, n: i32, body: Vec<u8>) {
811 let obj = PdfObject {
812 num: n,
813 data: body,
814 defined: true,
815 ..Default::default()
816 };
817 self.pdf_write_object_data(&obj);
818 }
819
820 fn pdf_write_action(&mut self, action: &PdfAction) -> i32 {
823 let n = self.pdf_alloc_obj(ObjKind::Other);
824 if let Some(mut be) = self.backend.take() {
825 be.begin_object(self);
826 self.backend = Some(be);
827 }
828 let body = match action {
829 PdfAction::User(u) => {
830 let mut b = b"\n".to_vec();
831 b.extend_from_slice(u);
832 b.push(b'\n');
833 b
834 }
835 _ => self.pdf_action_text(action),
836 };
837 self.pdf_write_raw(n, body);
838 n
839 }
840
841 pub fn pdf_extension(&mut self, immediate: bool) {
844 if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) <= 0 {
845 return;
848 }
849 if self.scan_keyword("literal") {
850 let late = self.scan_keyword("shipout");
851 let mode = if self.scan_keyword("direct") {
852 literal_mode::DIRECT
853 } else if self.scan_keyword("page") {
854 literal_mode::PAGE
855 } else if self.scan_keyword("raw") {
856 literal_mode::RAW
857 } else {
858 literal_mode::ORIGIN
859 };
860 let data = if late {
861 LateData::Tokens(self.scan_general_text())
862 } else {
863 LateData::Bytes(self.scan_pdf_string())
864 };
865 self.append_whatsit(
866 if late {
867 whatsit_sub::PDF_LATE_LITERAL
868 } else {
869 whatsit_sub::PDF_LITERAL
870 },
871 Whatsit::PdfLiteral { mode, data, late },
872 );
873 } else if self.scan_keyword("lateliteral") {
874 let data = LateData::Tokens(self.scan_general_text());
875 self.append_whatsit(
876 whatsit_sub::PDF_LATE_LITERAL,
877 Whatsit::PdfLiteral {
878 mode: literal_mode::ORIGIN,
879 data,
880 late: true,
881 },
882 );
883 } else if self.scan_keyword("save") {
884 self.append_whatsit(whatsit_sub::PDF_SAVE, Whatsit::PdfSave);
885 } else if self.scan_keyword("restore") {
886 self.append_whatsit(whatsit_sub::PDF_RESTORE, Whatsit::PdfRestore);
887 } else if self.scan_keyword("setmatrix") {
888 let data = self.scan_pdf_string();
889 self.append_whatsit(whatsit_sub::PDF_SETMATRIX, Whatsit::PdfSetmatrix { data });
890 } else if self.scan_keyword("colorstack") {
891 self.scan_int();
892 let stack = self.cur_val;
893 let (cmd, data) = if self.scan_keyword("set") {
894 (colorstack_cmd::SET, self.scan_pdf_string())
895 } else if self.scan_keyword("push") {
896 (colorstack_cmd::PUSH, self.scan_pdf_string())
897 } else if self.scan_keyword("pop") {
898 (colorstack_cmd::POP, Vec::new())
899 } else if self.scan_keyword("current") {
900 (colorstack_cmd::CURRENT, Vec::new())
901 } else {
902 self.print_err("Color stack action is missing");
903 self.help(&[
904 "The expected actions for \\pdfextension colorstack:",
905 " set, push, pop, current",
906 ]);
907 self.error();
908 return;
909 };
910 self.append_whatsit(
911 whatsit_sub::PDF_COLORSTACK,
912 Whatsit::PdfColorstack { stack, cmd, data },
913 );
914 } else if self.scan_keyword("obj") {
915 self.pdf_obj(immediate);
916 } else if self.scan_keyword("refobj") {
917 self.scan_int();
918 let n = self.cur_val;
919 self.pdf_append_refobj(n);
920 } else if self.scan_keyword("annot") {
921 let objnum = if self.scan_keyword("reserveobjnum") {
922 let n = self.pdf_alloc_obj(ObjKind::Annot);
923 self.pdf.last_annot = n;
924 self.scan_optional_space();
925 return;
926 } else if self.scan_keyword("useobjnum") {
927 self.scan_int();
928 self.cur_val
929 } else {
930 self.pdf_alloc_obj(ObjKind::Annot)
931 };
932 self.pdf.last_annot = objnum;
933 let dims = self.scan_pdf_rule_spec();
934 let data = self.scan_pdf_string();
935 self.append_whatsit(
936 whatsit_sub::PDF_ANNOT,
937 Whatsit::PdfAnnot { dims, objnum, data },
938 );
939 } else if self.scan_keyword("startlink") {
940 if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
941 self.pdf_fatal("startlink cannot be used in vertical mode");
942 }
943 let dims = self.scan_pdf_rule_spec();
944 let mut attr = Vec::new();
945 if self.scan_keyword("attr") {
946 attr = self.scan_pdf_string();
947 }
948 let Some(action) = self.scan_pdf_action() else {
949 return;
950 };
951 let objnum = self.pdf_alloc_obj(ObjKind::Link);
952 self.pdf.last_link = objnum;
953 self.append_whatsit(
954 whatsit_sub::PDF_START_LINK,
955 Whatsit::PdfStartLink {
956 dims,
957 objnum,
958 attr,
959 action,
960 },
961 );
962 } else if self.scan_keyword("endlink") {
963 if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
964 self.pdf_fatal("endlink cannot be used in vertical mode");
965 }
966 self.append_whatsit(whatsit_sub::PDF_END_LINK, Whatsit::PdfEndLink);
967 } else if self.scan_keyword("dest") {
968 self.pdf_dest_cmd();
969 } else if self.scan_keyword("outline") {
970 self.pdf_outline_cmd();
971 } else if self.scan_keyword("glyphtounicode") {
972 let a = self.scan_pdf_string();
973 let b = self.scan_pdf_string();
974 self.pdf.glyph_to_unicode.push((a, b));
975 } else if self.scan_keyword("catalog") {
976 let s = self.scan_pdf_string();
977 self.pdf.catalog.extend_from_slice(&s);
978 if self.scan_keyword("openaction") {
979 if self.pdf.open_action.is_some() {
980 self.pdf_fatal("duplicate of openaction");
981 }
982 if let Some(a) = self.scan_pdf_action() {
983 let n = self.pdf_write_action(&a);
984 self.pdf.open_action = Some(n);
985 }
986 }
987 } else if self.scan_keyword("info") {
988 let s = self.scan_pdf_string();
989 self.pdf.info.extend_from_slice(&s);
990 } else if self.scan_keyword("names") {
991 let s = self.scan_pdf_string();
992 self.pdf.names.extend_from_slice(&s);
993 } else if self.scan_keyword("trailer") {
994 let s = self.scan_pdf_string();
995 self.pdf.trailer.extend_from_slice(&s);
996 } else if self.scan_keyword("fontattr") {
997 self.scan_int();
998 let _ = self.scan_pdf_string();
999 } else if self.scan_keyword("mapfile") {
1000 let s = self.scan_pdf_string();
1001 if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
1006 let text = String::from_utf8_lossy(&s).trim().to_owned();
1007 let prefix = text.chars().next().filter(|c| matches!(c, '+' | '-' | '='));
1008 let name = text.trim_start_matches(['+', '-', '=']);
1009 if prefix != Some('-') {
1010 if prefix.is_some() && !self.pdf.map_note_done {
1011 self.print_map_file_note("pdftex.map");
1012 }
1013 self.print_map_file_note(name);
1014 self.pdf.map_note_done = true;
1015 }
1016 }
1017 self.pdf.map_entries.push(s);
1018 } else if self.scan_keyword("mapline") {
1019 let s = self.scan_pdf_string();
1020 self.pdf.map_entries.push(s);
1021 } else if self.scan_keyword("includechars") {
1022 let _ = self.scan_pdf_string();
1023 } else if self.scan_keyword("linkstate") {
1024 self.scan_int();
1025 } else if self.scan_keyword("startthread") || self.scan_keyword("thread") {
1026 let _ = self.scan_pdf_rule_spec();
1028 if self.scan_keyword("attr") {
1029 let _ = self.scan_pdf_string();
1030 }
1031 if self.scan_keyword("num") {
1032 self.scan_int();
1033 } else if self.scan_keyword("name") {
1034 let _ = self.scan_pdf_string();
1035 }
1036 } else if self.scan_keyword("endthread") {
1037 } else {
1038 self.print_err("unexpected use of \\pdfextension");
1040 self.help(&[]);
1041 self.error();
1042 }
1043 }
1044
1045 fn pdf_dest_cmd(&mut self) {
1047 if self.scan_keyword("struct") {
1048 self.scan_int();
1049 }
1050 let id = if self.scan_keyword("num") {
1051 self.scan_int();
1052 DestId::Num(self.cur_val)
1053 } else if self.scan_keyword("name") {
1054 DestId::Name(self.scan_pdf_string())
1055 } else {
1056 self.print_err("identifier type missing");
1057 self.help(&[]);
1058 self.error();
1059 return;
1060 };
1061 let mut view = DestView {
1062 kind: DestKind::Xyz,
1063 zoom: 0,
1064 dims: [RUNNING; 3],
1065 };
1066 if self.scan_keyword("xyz") {
1067 if self.scan_keyword("zoom") {
1068 self.scan_int();
1069 view.zoom = self.cur_val;
1070 }
1071 } else if self.scan_keyword("fitbh") {
1072 view.kind = DestKind::FitBH;
1073 } else if self.scan_keyword("fitbv") {
1074 view.kind = DestKind::FitBV;
1075 } else if self.scan_keyword("fitb") {
1076 view.kind = DestKind::FitB;
1077 } else if self.scan_keyword("fith") {
1078 view.kind = DestKind::FitH;
1079 } else if self.scan_keyword("fitv") {
1080 view.kind = DestKind::FitV;
1081 } else if self.scan_keyword("fitr") {
1082 view.kind = DestKind::FitR;
1083 view.dims = self.scan_pdf_rule_spec();
1084 } else if self.scan_keyword("fit") {
1085 view.kind = DestKind::Fit;
1086 } else {
1087 self.print_err("destination type missing");
1088 self.help(&[]);
1089 self.error();
1090 }
1091 self.append_whatsit(whatsit_sub::PDF_DEST, Whatsit::PdfDest { id, view });
1092 }
1093
1094 fn pdf_outline_cmd(&mut self) {
1096 let mut o = Outline::default();
1097 if self.scan_keyword("attr") {
1098 o.attr = self.scan_pdf_string();
1099 }
1100 if self.scan_keyword("useobjnum") {
1101 self.scan_int();
1102 o.action = self.cur_val;
1103 } else {
1104 let Some(a) = self.scan_pdf_action() else {
1105 return;
1106 };
1107 o.action = self.pdf_write_action(&a);
1108 }
1109 if self.scan_keyword("count") {
1110 self.scan_int();
1111 o.count = self.cur_val;
1112 }
1113 let title = self.scan_pdf_string();
1114 o.objnum = self.pdf_alloc_obj(ObjKind::Other);
1115 o.title = self.pdf_alloc_obj(ObjKind::Other);
1116 let mut body = b"(".to_vec();
1117 body.extend_from_slice(&title);
1118 body.extend_from_slice(b")\n");
1119 self.pdf_write_raw(o.title, body);
1120 self.pdf.outlines.push(o);
1121 }
1122
1123 fn pdf_obj(&mut self, immediate: bool) {
1125 if self.scan_keyword("reserveobjnum") {
1126 let n = self.pdf_alloc_obj(ObjKind::Obj);
1127 self.pdf.objects.insert(
1128 n,
1129 PdfObject {
1130 num: n,
1131 ..Default::default()
1132 },
1133 );
1134 self.pdf.last_obj = n;
1135 return;
1136 }
1137 let n = if self.scan_keyword("useobjnum") {
1138 self.scan_int();
1139 self.cur_val
1140 } else {
1141 self.pdf_alloc_obj(ObjKind::Obj)
1142 };
1143 let uncompressed = self.scan_keyword("uncompressed");
1144 let mut obj = PdfObject {
1145 num: n,
1146 uncompressed,
1147 defined: true,
1148 ..Default::default()
1149 };
1150 if self.scan_keyword("stream") {
1151 obj.stream = true;
1152 if self.scan_keyword("attr") {
1153 obj.attr = self.scan_pdf_string();
1154 }
1155 }
1156 if self.scan_keyword("file") {
1157 obj.file = true;
1158 }
1159 obj.data = self.scan_pdf_string();
1160 if immediate {
1161 self.pdf.obj_words += 5;
1162 }
1163 self.pdf_define_object(obj, immediate);
1164 }
1165
1166 pub fn pdf_write_object(&mut self, n: i32) {
1168 let Some(obj) = self.pdf.objects.get(&n).cloned() else {
1169 return;
1170 };
1171 if obj.written || !obj.defined {
1172 return;
1173 }
1174 if let Some(o) = self.pdf.objects.get_mut(&n) {
1175 o.written = true;
1176 }
1177 self.pdf_write_object_data(&obj);
1178 }
1179
1180 fn pdf_write_object_data(&mut self, obj: &PdfObject) {
1183 let Some(obj) = self.pdf_object_payload(obj) else {
1184 return;
1185 };
1186 match self.backend.take() {
1187 Some(mut be) => {
1188 be.write_object(self, &obj);
1189 self.backend = Some(be);
1190 }
1191 None => self.pdf.pending.push(obj),
1192 }
1193 }
1194
1195 pub(crate) fn pdf_take_page_object(&mut self, n: i32) -> Option<PdfObject> {
1198 let obj = self.pdf.objects.get_mut(&n)?;
1199 if obj.written || !obj.defined {
1200 return None;
1201 }
1202 obj.written = true;
1203 let obj = obj.clone();
1204 self.pdf_object_payload(&obj)
1205 }
1206
1207 fn pdf_object_payload(&mut self, obj: &PdfObject) -> Option<PdfObject> {
1210 let mut obj = obj.clone();
1211 if obj.file {
1212 let name = String::from_utf8_lossy(&obj.data).into_owned();
1213 let path = self
1214 .kpse
1215 .as_ref()
1216 .and_then(|k| {
1217 k.borrow_mut()
1218 .find_file(&name, retex_kpath::FileFormat::Tex)
1219 })
1220 .unwrap_or_else(|| std::path::PathBuf::from(&name));
1221 match std::fs::read(&path) {
1222 Ok(d) => {
1223 self.printer.print_str("<<");
1224 self.printer.print_bytes(name.as_bytes());
1225 self.printer.print_str(">>");
1226 obj.data = d;
1227 obj.file = false;
1228 }
1229 Err(_) => {
1230 self.print_err("pdf backend: cannot open file for embedding");
1231 self.help(&[]);
1232 self.error();
1233 return None;
1234 }
1235 }
1236 }
1237 Some(obj)
1238 }
1239
1240 pub fn whatsit_tokens(list: &TokenList) -> LateData {
1242 LateData::Tokens(list.clone())
1243 }
1244
1245 pub fn pdf_finish_document(&mut self) {
1249 let mut doc = DocItems::default();
1250 let mut warnings = Vec::new();
1254 let mut pages: Vec<(i32, i32)> = self
1255 .pdf
1256 .page_refs
1257 .iter()
1258 .filter(|(p, _)| **p > self.total_pages as i32)
1259 .map(|(p, n)| (*n, *p))
1260 .collect();
1261 pages.sort_unstable_by(|a, b| b.cmp(a));
1262 for (_, p) in pages {
1263 warnings.push(format!("page {p} has been referenced but does not exist"));
1264 }
1265 let order = self.pdf.dest_order.clone();
1266 for id in order.iter().rev() {
1267 if let Some(d) = self.pdf.dests.get(id).copied()
1268 && !d.defined
1269 {
1270 warnings.push(format!("unreferenced destination with {}", id.describe()));
1271 doc.missing_dests.push(d.objnum);
1272 }
1273 }
1274 for (id, n) in self.pdf.threads.clone() {
1275 warnings.push(format!("unknown thread destination {}", id.describe()));
1276 let title = match id {
1277 DestId::Name(n) => n,
1278 DestId::Num(k) => k.to_string().into_bytes(),
1279 };
1280 doc.missing_threads.push((n, title));
1281 }
1282 for (i, w) in warnings.iter().enumerate() {
1283 if i > 0 {
1284 self.printer.print_ln();
1285 }
1286 self.pdf_warning(w);
1287 }
1288 let mut names: Vec<(Vec<u8>, i32)> = self
1289 .pdf
1290 .dests
1291 .iter()
1292 .filter_map(|(k, d)| match k {
1293 DestId::Name(n) => Some((n.clone(), d.objnum)),
1294 DestId::Num(_) => None,
1295 })
1296 .collect();
1297 names.sort();
1298 doc.dest_names = names;
1299 let toks = |e: &mut Engine, i: u32| {
1300 let list = e.eqtb.toks_param(i).clone();
1301 e.print_to_string(|e| e.show_token_list(&list, 0, list.len(), i64::MAX))
1302 };
1303 doc.pages_attr = toks(self, crate::backend::PAGES_ATTR);
1304 doc.trailer_id = toks(self, crate::backend::TRAILER_ID);
1305 self.pdf.doc = doc;
1306 }
1307}