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 structure: Option<ActionTarget>,
250 target: ActionTarget,
252 new_window: Option<bool>,
254 },
255 Thread {
257 file: Option<Vec<u8>>,
259 target: ActionTarget,
261 },
262}
263
264#[derive(Clone, Copy, Debug, PartialEq, Eq)]
266pub enum DestPos {
267 Xyz {
269 x: i32,
271 y: i32,
273 zoom: i32,
275 },
276 Fit,
278 FitB,
280 FitH(i32),
282 FitBH(i32),
284 FitV(i32),
286 FitBV(i32),
288 FitR([i32; 4]),
290}
291
292#[derive(Clone, Debug)]
294pub struct LinkPiece {
295 pub objnum: i32,
297 pub rect: [i32; 4],
299 pub attr: Vec<u8>,
301 pub action: Vec<u8>,
303 pub user: bool,
305 pub source: PdfAction,
308}
309
310#[derive(Clone, Debug)]
312pub struct ActiveLink {
313 pub level: i32,
316 pub piece: Option<usize>,
318 pub first_open: bool,
321 pub margin: i32,
323 pub continued: bool,
326 pub attr: Vec<u8>,
328 pub action: PdfAction,
330}
331
332pub type AnnotBody = ([i32; 4], Vec<u8>);
334
335#[derive(Clone, Debug, Default)]
338pub struct PageItems {
339 pub annots: Vec<(i32, Option<AnnotBody>)>,
342 pub links: Vec<LinkPiece>,
344 pub dests: Vec<(i32, bool, DestPos, Option<i32>)>,
347 pub objects: Vec<PdfObject>,
349 pub attr: (Vec<u8>, Vec<u8>),
351 pub resources: (Vec<u8>, Vec<u8>),
353}
354
355#[derive(Clone, Debug, Default)]
357pub struct DocItems {
358 pub missing_dests: Vec<i32>,
360 pub missing_threads: Vec<(i32, Vec<u8>)>,
362 pub dest_names: Vec<(Vec<u8>, i32)>,
364 pub pages_attr: Vec<u8>,
366 pub trailer_id: Vec<u8>,
368}
369
370#[derive(Clone, Copy, Debug, Default)]
372pub struct DestEntry {
373 pub objnum: i32,
375 pub defined: bool,
377}
378
379#[derive(Clone, Debug, Default)]
381pub struct PdfState {
382 pub page_images: Vec<u32>,
385 pub objects: std::collections::BTreeMap<i32, PdfObject>,
387 pub catalog: Vec<u8>,
389 pub open_action: Option<i32>,
391 pub info: Vec<u8>,
393 pub names: Vec<u8>,
395 pub trailer: Vec<u8>,
397 pub outlines: Vec<Outline>,
399 pub glyph_to_unicode: Vec<(Vec<u8>, Vec<u8>)>,
401 pub colorstacks: Vec<ColorStack>,
403 pub last_obj: i32,
405 pub last_annot: i32,
407 pub last_link: i32,
409 pub counter: Rc<Cell<u32>>,
412 pub kinds: HashMap<i32, ObjKind>,
414 pub page_refs: HashMap<i32, i32>,
416 pub fonts: HashMap<u32, (u32, i32)>,
419 pub font_attrs: HashMap<u32, Vec<u8>>,
421 pub map_entries: Vec<Vec<u8>>,
424 pub lua: LuaPdfTexts,
426 pub retval: i32,
428 pub pos: (i32, i32),
431 pub late: Option<LateLuaContext>,
433 pub page_annots: Vec<i32>,
435 pub output_opened: bool,
438 pub dests: HashMap<DestId, DestEntry>,
440 pub dest_order: Vec<DestId>,
442 pub struct_dests: HashMap<DestId, DestEntry>,
444 pub threads: Vec<(DestId, i32)>,
446 pub page: PageItems,
448 pub pending: Vec<PdfObject>,
451 pub doc: DocItems,
453 pub fatal: bool,
455 pub objstm_stats: (u32, u32),
457 pub stream_written: bool,
461 pub obj_words: usize,
464 pub map_note_done: bool,
466 pub active_links: Vec<ActiveLink>,
468}
469
470impl PdfState {
471 pub fn max_obj(&self) -> i32 {
473 self.counter.get() as i32
474 }
475
476 pub fn named_dests(&self) -> usize {
478 self.dests
479 .keys()
480 .filter(|k| matches!(k, DestId::Name(_)))
481 .count()
482 }
483}
484
485impl Engine {
486 fn scan_pdf_string(&mut self) -> Vec<u8> {
488 let toks = self.scan_toks_expanded();
489 self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
490 }
491
492 pub fn pdf_alloc_obj(&mut self, kind: ObjKind) -> i32 {
494 let n = self.pdf.counter.get() + 1;
495 self.pdf.counter.set(n);
496 let n = n as i32;
497 self.pdf.kinds.insert(n, kind);
498 n
499 }
500
501 pub fn pdf_obj_kind(&self, n: i32) -> Option<ObjKind> {
503 self.pdf.kinds.get(&n).copied()
504 }
505
506 pub fn pdf_page_ref(&mut self, page: i32) -> i32 {
509 if let Some(&n) = self.pdf.page_refs.get(&page) {
510 return n;
511 }
512 let n = self.pdf_alloc_obj(ObjKind::Page);
513 self.pdf.page_refs.insert(page, n);
514 n
515 }
516
517 pub fn pdf_dest_obj(&mut self, id: &DestId) -> i32 {
519 if let Some(d) = self.pdf.dests.get(id) {
520 return d.objnum;
521 }
522 let n = self.pdf_alloc_obj(ObjKind::Other);
523 self.pdf.dests.insert(
524 id.clone(),
525 DestEntry {
526 objnum: n,
527 defined: false,
528 },
529 );
530 self.pdf.dest_order.push(id.clone());
531 n
532 }
533
534 pub fn pdf_struct_dest_obj(&mut self, id: &DestId) -> i32 {
537 if let Some(d) = self.pdf.struct_dests.get(id) {
538 return d.objnum;
539 }
540 let n = self.pdf_alloc_obj(ObjKind::Other);
541 self.pdf.struct_dests.insert(
542 id.clone(),
543 DestEntry {
544 objnum: n,
545 defined: false,
546 },
547 );
548 n
549 }
550
551 fn pdf_thread_obj(&mut self, id: DestId) -> i32 {
553 if let Some((_, n)) = self.pdf.threads.iter().find(|(t, _)| *t == id) {
554 return *n;
555 }
556 let n = self.pdf_alloc_obj(ObjKind::Other);
557 self.pdf.threads.push((id, n));
558 n
559 }
560
561 pub fn pdf_font(&mut self, f: u32) -> (u32, i32) {
568 if let Some(&r) = self.pdf.fonts.get(&f) {
569 return r;
570 }
571 let name = self.font(f).name.clone();
572 let shared = self
573 .pdf
574 .fonts
575 .iter()
576 .filter(|(g, _)| self.font_exists(**g as i32) && self.font(**g).name == name)
577 .map(|(_, r)| *r)
578 .min();
579 let r = match shared {
580 Some(r) => r,
581 None => (f, self.pdf_alloc_obj(ObjKind::Font)),
582 };
583 self.pdf.fonts.insert(f, r);
584 r
585 }
586
587 pub fn pdf_ensure_colorstacks(&mut self) {
589 if self.pdf.colorstacks.is_empty() {
590 self.pdf.colorstacks.push(ColorStack {
591 page: true,
592 mode: literal_mode::DIRECT,
593 init: b"0 g 0 G".to_vec(),
594 });
595 }
596 }
597
598 pub fn pdf_new_colorstack(&mut self, init: Vec<u8>, page: bool, mode: u8) -> i32 {
601 self.pdf_ensure_colorstacks();
602 self.pdf.colorstacks.push(ColorStack { page, mode, init });
603 (self.pdf.colorstacks.len() - 1) as i32
604 }
605
606 pub fn pdf_append_refobj(&mut self, n: i32) {
609 self.append_whatsit(whatsit_sub::PDF_REFOBJ, Whatsit::PdfRefobj { objnum: n });
610 }
611
612 pub fn pdf_define_object(&mut self, obj: PdfObject, immediate: bool) {
619 if !immediate {
620 self.pdf.obj_words += 5;
621 }
622 let n = obj.num;
623 self.pdf.last_obj = n;
624 self.pdf.stream_written |= immediate && obj.stream;
625 self.pdf.objects.insert(n, obj);
626 if immediate {
627 self.pdf_write_object(n);
628 }
629 }
630
631 fn append_whatsit(&mut self, subtype: u16, w: Whatsit) {
633 let p = self.new_node(id::WHATSIT, subtype, NodeData::Whatsit(Box::new(w)));
635 self.tail_append(p);
636 }
637
638 fn scan_pdf_rule_spec(&mut self) -> [i32; 3] {
641 let mut d = [RUNNING; 3];
642 loop {
643 let i = if self.scan_keyword("width") {
644 0
645 } else if self.scan_keyword("height") {
646 1
647 } else if self.scan_keyword("depth") {
648 2
649 } else {
650 return d;
651 };
652 self.scan_normal_dimen();
653 d[i] = self.cur_val;
654 }
655 }
656
657 pub fn pdf_fatal(&mut self, msg: &str) -> ! {
660 self.normalize_selector();
661 self.printer.print_nl("! error: (pdf backend): ");
662 self.printer.print_str(msg);
663 self.printer
664 .print_nl("! ==> Fatal error occurred, no output PDF file produced!");
665 self.printer.update_terminal();
666 self.pdf.fatal = true;
669 self.err.history = crate::error::History::FatalErrorStop;
670 let _ = std::fs::remove_file(self.output_path("pdf"));
671 self.printer.close_log();
672 std::panic::resume_unwind(Box::new(crate::error::JumpOut));
673 }
674
675 pub fn pdf_warning(&mut self, msg: &str) {
678 self.printer.print_nl("warning (pdf backend): ");
679 self.printer.print_str(msg);
680 self.printer.print_ln();
681 }
682
683 fn scan_pdf_action(&mut self) -> Option<PdfAction> {
685 if self.scan_keyword("user") {
686 return Some(PdfAction::User(self.scan_pdf_string()));
687 }
688 let goto = if self.scan_keyword("goto") {
689 true
690 } else if self.scan_keyword("thread") {
691 false
692 } else {
693 self.pdf_fatal("action type missing");
694 };
695 let file = if self.scan_keyword("file") {
696 Some(self.scan_pdf_string())
697 } else {
698 None
699 };
700 let structure = if goto && self.scan_keyword("struct") {
703 if self.scan_keyword("name") {
704 Some(ActionTarget::Name(self.scan_pdf_string()))
705 } else if self.scan_keyword("num") {
706 self.scan_int();
707 Some(ActionTarget::Num(self.cur_val))
708 } else {
709 self.pdf_fatal("identifier type missing");
710 }
711 } else {
712 None
713 };
714 let target = if goto && self.scan_keyword("page") {
715 self.scan_int();
716 let page = self.cur_val;
717 ActionTarget::Page(page, self.scan_pdf_string())
718 } else if self.scan_keyword("name") {
719 ActionTarget::Name(self.scan_pdf_string())
720 } else if self.scan_keyword("num") {
721 if goto && file.is_some() {
722 self.pdf_fatal("'goto' option cannot be used with both 'file' and 'num'");
723 }
724 self.scan_int();
725 ActionTarget::Num(self.cur_val)
726 } else {
727 self.pdf_fatal("identifier type missing");
728 };
729 let new_window = if self.scan_keyword("newwindow") {
730 Some(true)
731 } else if self.scan_keyword("nonewwindow") {
732 Some(false)
733 } else {
734 None
735 };
736 if new_window.is_some() && !(goto && file.is_some()) {
737 self.pdf_fatal(
738 "'newwindow' or 'nonewwindow' must be used with 'goto' and 'file' option",
739 );
740 }
741 Some(if goto {
742 PdfAction::Goto {
743 file,
744 structure,
745 target,
746 new_window,
747 }
748 } else {
749 PdfAction::Thread { file, target }
750 })
751 }
752
753 pub fn pdf_action_text(&mut self, action: &PdfAction) -> Vec<u8> {
757 fn string(out: &mut Vec<u8>, s: &[u8]) {
758 out.push(b'(');
759 out.extend_from_slice(s);
760 out.push(b')');
761 }
762 let mut d = b"<< ".to_vec();
763 match action {
764 PdfAction::User(u) => return u.clone(),
765 PdfAction::Goto {
766 file: None,
767 target,
768 structure,
769 ..
770 } => {
771 d.extend_from_slice(b"/S /GoTo /D ");
772 let sd = structure.as_ref().map(|s| {
774 let id = match s {
775 ActionTarget::Name(n) => DestId::Name(n.clone()),
776 ActionTarget::Num(n) => DestId::Num(*n),
777 ActionTarget::Page(..) => DestId::Num(0),
778 };
779 self.pdf_struct_dest_obj(&id)
780 });
781 match target {
782 ActionTarget::Name(n) => {
783 self.pdf_dest_obj(&DestId::Name(n.clone()));
784 string(&mut d, n);
785 }
786 ActionTarget::Num(n) => {
787 let o = self.pdf_dest_obj(&DestId::Num(*n));
788 d.extend_from_slice(format!("{o} 0 R").as_bytes());
789 }
790 ActionTarget::Page(p, spec) => {
791 let o = self.pdf_page_ref((*p).max(1));
792 d.extend_from_slice(format!("[ {o} 0 R ").as_bytes());
793 d.extend_from_slice(spec);
794 d.extend_from_slice(b" ]");
795 }
796 }
797 if let Some(o) = sd {
798 d.extend_from_slice(format!(" /SD {o} 0 R").as_bytes());
799 }
800 }
801 PdfAction::Goto {
802 file: Some(f),
803 target,
804 new_window,
805 ..
806 } => {
807 d.extend_from_slice(b"/F ");
808 string(&mut d, f);
809 d.push(b' ');
810 match new_window {
811 Some(true) => d.extend_from_slice(b" /NewWindow true"),
812 Some(false) => d.extend_from_slice(b" /NewWindow false"),
813 None => {}
814 }
815 match target {
816 ActionTarget::Name(n) => {
817 d.extend_from_slice(b" /S /GoToR /D ");
818 string(&mut d, n);
819 }
820 ActionTarget::Page(p, spec) => {
821 d.extend_from_slice(format!(" /S /GoTo /D [{} ", p - 1).as_bytes());
823 d.extend_from_slice(spec);
824 d.extend_from_slice(b" ]");
825 }
826 ActionTarget::Num(_) => {}
827 }
828 }
829 PdfAction::Thread { file, target } => {
830 if let Some(f) = file {
831 d.extend_from_slice(b"/F ");
833 string(&mut d, f);
834 d.extend_from_slice(b" /S /Thread");
835 } else {
836 d.extend_from_slice(b"/S /Thread /D ");
837 match target {
838 ActionTarget::Name(n) => {
839 self.pdf_thread_obj(DestId::Name(n.clone()));
840 string(&mut d, n);
841 }
842 ActionTarget::Num(n) => {
843 let o = self.pdf_thread_obj(DestId::Num(*n));
844 d.extend_from_slice(format!("{o} 0 R").as_bytes());
845 }
846 ActionTarget::Page(..) => {}
847 }
848 }
849 }
850 }
851 d.extend_from_slice(b" >>");
852 d
853 }
854
855 fn pdf_write_raw(&mut self, n: i32, body: Vec<u8>) {
858 let obj = PdfObject {
859 num: n,
860 data: body,
861 defined: true,
862 ..Default::default()
863 };
864 self.pdf_write_object_data(&obj);
865 }
866
867 fn pdf_write_action(&mut self, action: &PdfAction) -> i32 {
870 let n = self.pdf_alloc_obj(ObjKind::Other);
871 if let Some(mut be) = self.backend.take() {
872 be.begin_object(self);
873 self.backend = Some(be);
874 }
875 let body = match action {
876 PdfAction::User(u) => {
877 let mut b = b"\n".to_vec();
878 b.extend_from_slice(u);
879 b.push(b'\n');
880 b
881 }
882 _ => self.pdf_action_text(action),
883 };
884 self.pdf_write_raw(n, body);
885 n
886 }
887
888 pub fn pdf_extension(&mut self, immediate: bool) {
891 if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) <= 0 {
892 return;
895 }
896 if self.scan_keyword("literal") {
897 let late = self.scan_keyword("shipout");
898 let mode = if self.scan_keyword("direct") {
899 literal_mode::DIRECT
900 } else if self.scan_keyword("page") {
901 literal_mode::PAGE
902 } else if self.scan_keyword("raw") {
903 literal_mode::RAW
904 } else {
905 literal_mode::ORIGIN
906 };
907 let data = if late {
908 LateData::Tokens(self.scan_general_text())
909 } else {
910 LateData::Bytes(self.scan_pdf_string())
911 };
912 self.append_whatsit(
913 if late {
914 whatsit_sub::PDF_LATE_LITERAL
915 } else {
916 whatsit_sub::PDF_LITERAL
917 },
918 Whatsit::PdfLiteral { mode, data, late },
919 );
920 } else if self.scan_keyword("lateliteral") {
921 let data = LateData::Tokens(self.scan_general_text());
922 self.append_whatsit(
923 whatsit_sub::PDF_LATE_LITERAL,
924 Whatsit::PdfLiteral {
925 mode: literal_mode::ORIGIN,
926 data,
927 late: true,
928 },
929 );
930 } else if self.scan_keyword("save") {
931 self.append_whatsit(whatsit_sub::PDF_SAVE, Whatsit::PdfSave);
932 } else if self.scan_keyword("restore") {
933 self.append_whatsit(whatsit_sub::PDF_RESTORE, Whatsit::PdfRestore);
934 } else if self.scan_keyword("setmatrix") {
935 let data = self.scan_pdf_string();
936 self.append_whatsit(whatsit_sub::PDF_SETMATRIX, Whatsit::PdfSetmatrix { data });
937 } else if self.scan_keyword("colorstack") {
938 self.scan_int();
939 let stack = self.cur_val;
940 let (cmd, data) = if self.scan_keyword("set") {
941 (colorstack_cmd::SET, self.scan_pdf_string())
942 } else if self.scan_keyword("push") {
943 (colorstack_cmd::PUSH, self.scan_pdf_string())
944 } else if self.scan_keyword("pop") {
945 (colorstack_cmd::POP, Vec::new())
946 } else if self.scan_keyword("current") {
947 (colorstack_cmd::CURRENT, Vec::new())
948 } else {
949 self.print_err("Color stack action is missing");
950 self.help(&[
951 "The expected actions for \\pdfextension colorstack:",
952 " set, push, pop, current",
953 ]);
954 self.error();
955 return;
956 };
957 self.append_whatsit(
958 whatsit_sub::PDF_COLORSTACK,
959 Whatsit::PdfColorstack { stack, cmd, data },
960 );
961 } else if self.scan_keyword("obj") {
962 self.pdf_obj(immediate);
963 } else if self.scan_keyword("refobj") {
964 self.scan_int();
965 let n = self.cur_val;
966 self.pdf_append_refobj(n);
967 } else if self.scan_keyword("annot") {
968 let objnum = if self.scan_keyword("reserveobjnum") {
969 let n = self.pdf_alloc_obj(ObjKind::Annot);
970 self.pdf.last_annot = n;
971 self.scan_optional_space();
972 return;
973 } else if self.scan_keyword("useobjnum") {
974 self.scan_int();
975 self.cur_val
976 } else {
977 self.pdf_alloc_obj(ObjKind::Annot)
978 };
979 self.pdf.last_annot = objnum;
980 let dims = self.scan_pdf_rule_spec();
981 let data = self.scan_pdf_string();
982 self.append_whatsit(
983 whatsit_sub::PDF_ANNOT,
984 Whatsit::PdfAnnot { dims, objnum, data },
985 );
986 } else if self.scan_keyword("startlink") {
987 if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
988 self.pdf_fatal("startlink cannot be used in vertical mode");
989 }
990 let dims = self.scan_pdf_rule_spec();
991 let mut attr = Vec::new();
992 if self.scan_keyword("attr") {
993 attr = self.scan_pdf_string();
994 }
995 let Some(action) = self.scan_pdf_action() else {
996 return;
997 };
998 let objnum = self.pdf_alloc_obj(ObjKind::Link);
999 self.pdf.last_link = objnum;
1000 self.append_whatsit(
1001 whatsit_sub::PDF_START_LINK,
1002 Whatsit::PdfStartLink {
1003 dims,
1004 objnum,
1005 attr,
1006 action,
1007 },
1008 );
1009 } else if self.scan_keyword("endlink") {
1010 if self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical {
1011 self.pdf_fatal("endlink cannot be used in vertical mode");
1012 }
1013 self.append_whatsit(whatsit_sub::PDF_END_LINK, Whatsit::PdfEndLink);
1014 } else if self.scan_keyword("dest") {
1015 self.pdf_dest_cmd();
1016 } else if self.scan_keyword("outline") {
1017 self.pdf_outline_cmd();
1018 } else if self.scan_keyword("glyphtounicode") {
1019 let a = self.scan_pdf_string();
1020 let b = self.scan_pdf_string();
1021 self.pdf.glyph_to_unicode.push((a, b));
1022 } else if self.scan_keyword("catalog") {
1023 let s = self.scan_pdf_string();
1024 self.pdf.catalog.extend_from_slice(&s);
1025 if self.scan_keyword("openaction") {
1026 if self.pdf.open_action.is_some() {
1027 self.pdf_fatal("duplicate of openaction");
1028 }
1029 if let Some(a) = self.scan_pdf_action() {
1030 let n = self.pdf_write_action(&a);
1031 self.pdf.open_action = Some(n);
1032 }
1033 }
1034 } else if self.scan_keyword("info") {
1035 let s = self.scan_pdf_string();
1036 self.pdf.info.extend_from_slice(&s);
1037 } else if self.scan_keyword("names") {
1038 let s = self.scan_pdf_string();
1039 self.pdf.names.extend_from_slice(&s);
1040 } else if self.scan_keyword("trailer") {
1041 let s = self.scan_pdf_string();
1042 self.pdf.trailer.extend_from_slice(&s);
1043 } else if self.scan_keyword("fontattr") {
1044 self.scan_int();
1045 let _ = self.scan_pdf_string();
1046 } else if self.scan_keyword("mapfile") {
1047 let s = self.scan_pdf_string();
1048 if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
1053 let text = String::from_utf8_lossy(&s).trim().to_owned();
1054 let prefix = text.chars().next().filter(|c| matches!(c, '+' | '-' | '='));
1055 let name = text.trim_start_matches(['+', '-', '=']);
1056 if prefix != Some('-') {
1057 if prefix.is_some() && !self.pdf.map_note_done {
1058 self.print_map_file_note("pdftex.map");
1059 }
1060 self.print_map_file_note(name);
1061 self.pdf.map_note_done = true;
1062 }
1063 }
1064 self.pdf.map_entries.push(s);
1065 } else if self.scan_keyword("mapline") {
1066 let s = self.scan_pdf_string();
1067 self.pdf.map_entries.push(s);
1068 } else if self.scan_keyword("includechars") {
1069 let _ = self.scan_pdf_string();
1070 } else if self.scan_keyword("linkstate") {
1071 self.scan_int();
1072 let state = self.cur_val;
1073 self.append_whatsit(
1074 crate::nodes::whatsit_sub::PDF_LINK_STATE,
1075 Whatsit::PdfLinkState { state },
1076 );
1077 } else if self.scan_keyword("startthread") || self.scan_keyword("thread") {
1078 let _ = self.scan_pdf_rule_spec();
1080 if self.scan_keyword("attr") {
1081 let _ = self.scan_pdf_string();
1082 }
1083 if self.scan_keyword("num") {
1084 self.scan_int();
1085 } else if self.scan_keyword("name") {
1086 let _ = self.scan_pdf_string();
1087 }
1088 } else if self.scan_keyword("endthread") {
1089 } else {
1090 self.print_err("unexpected use of \\pdfextension");
1092 self.help(&[]);
1093 self.error();
1094 }
1095 }
1096
1097 fn pdf_dest_cmd(&mut self) {
1099 let structure = if self.scan_keyword("struct") {
1100 self.scan_int();
1101 Some(self.cur_val)
1102 } else {
1103 None
1104 };
1105 let id = if self.scan_keyword("num") {
1106 self.scan_int();
1107 DestId::Num(self.cur_val)
1108 } else if self.scan_keyword("name") {
1109 DestId::Name(self.scan_pdf_string())
1110 } else {
1111 self.pdf_fatal("identifier type missing");
1112 };
1113 let mut view = DestView {
1114 kind: DestKind::Xyz,
1115 zoom: 0,
1116 dims: [RUNNING; 3],
1117 };
1118 if self.scan_keyword("xyz") {
1119 if self.scan_keyword("zoom") {
1120 self.scan_int();
1121 view.zoom = self.cur_val;
1122 }
1123 } else if self.scan_keyword("fitbh") {
1124 view.kind = DestKind::FitBH;
1125 } else if self.scan_keyword("fitbv") {
1126 view.kind = DestKind::FitBV;
1127 } else if self.scan_keyword("fitb") {
1128 view.kind = DestKind::FitB;
1129 } else if self.scan_keyword("fith") {
1130 view.kind = DestKind::FitH;
1131 } else if self.scan_keyword("fitv") {
1132 view.kind = DestKind::FitV;
1133 } else if self.scan_keyword("fitr") {
1134 view.kind = DestKind::FitR;
1135 view.dims = self.scan_pdf_rule_spec();
1136 } else if self.scan_keyword("fit") {
1137 view.kind = DestKind::Fit;
1138 } else {
1139 self.pdf_fatal("destination type missing");
1140 }
1141 self.append_whatsit(
1142 whatsit_sub::PDF_DEST,
1143 Whatsit::PdfDest {
1144 id,
1145 view,
1146 structure,
1147 },
1148 );
1149 }
1150
1151 fn pdf_outline_cmd(&mut self) {
1153 let mut o = Outline::default();
1154 if self.scan_keyword("attr") {
1155 o.attr = self.scan_pdf_string();
1156 }
1157 if self.scan_keyword("useobjnum") {
1158 self.scan_int();
1159 o.action = self.cur_val;
1160 } else {
1161 let Some(a) = self.scan_pdf_action() else {
1162 return;
1163 };
1164 o.action = self.pdf_write_action(&a);
1165 }
1166 if self.scan_keyword("count") {
1167 self.scan_int();
1168 o.count = self.cur_val;
1169 }
1170 let title = self.scan_pdf_string();
1171 o.objnum = self.pdf_alloc_obj(ObjKind::Other);
1172 o.title = self.pdf_alloc_obj(ObjKind::Other);
1173 let mut body = b"(".to_vec();
1174 body.extend_from_slice(&title);
1175 body.extend_from_slice(b")\n");
1176 self.pdf_write_raw(o.title, body);
1177 self.pdf.outlines.push(o);
1178 }
1179
1180 fn pdf_obj(&mut self, immediate: bool) {
1182 if self.scan_keyword("reserveobjnum") {
1183 let n = self.pdf_alloc_obj(ObjKind::Obj);
1184 self.pdf.objects.insert(
1185 n,
1186 PdfObject {
1187 num: n,
1188 ..Default::default()
1189 },
1190 );
1191 self.pdf.last_obj = n;
1192 self.scan_optional_space();
1194 return;
1195 }
1196 let n = if self.scan_keyword("useobjnum") {
1197 self.scan_int();
1198 self.cur_val
1199 } else {
1200 self.pdf_alloc_obj(ObjKind::Obj)
1201 };
1202 let uncompressed = self.scan_keyword("uncompressed");
1203 let mut obj = PdfObject {
1204 num: n,
1205 uncompressed,
1206 defined: true,
1207 ..Default::default()
1208 };
1209 if self.scan_keyword("stream") {
1210 obj.stream = true;
1211 if self.scan_keyword("attr") {
1212 obj.attr = self.scan_pdf_string();
1213 }
1214 }
1215 if self.scan_keyword("file") {
1216 obj.file = true;
1217 }
1218 obj.data = self.scan_pdf_string();
1219 if immediate {
1220 self.pdf.obj_words += 5;
1221 }
1222 self.pdf_define_object(obj, immediate);
1223 }
1224
1225 pub fn pdf_write_object(&mut self, n: i32) {
1227 let Some(obj) = self.pdf.objects.get(&n).cloned() else {
1228 return;
1229 };
1230 if obj.written || !obj.defined {
1231 return;
1232 }
1233 if let Some(o) = self.pdf.objects.get_mut(&n) {
1234 o.written = true;
1235 }
1236 self.pdf_write_object_data(&obj);
1237 }
1238
1239 fn pdf_write_object_data(&mut self, obj: &PdfObject) {
1242 let Some(obj) = self.pdf_object_payload(obj) else {
1243 return;
1244 };
1245 match self.backend.take() {
1246 Some(mut be) => {
1247 be.write_object(self, &obj);
1248 self.backend = Some(be);
1249 }
1250 None => self.pdf.pending.push(obj),
1251 }
1252 }
1253
1254 pub(crate) fn pdf_take_page_object(&mut self, n: i32) -> Option<PdfObject> {
1257 let obj = self.pdf.objects.get_mut(&n)?;
1258 if obj.written || !obj.defined {
1259 return None;
1260 }
1261 obj.written = true;
1262 let obj = obj.clone();
1263 self.pdf_object_payload(&obj)
1264 }
1265
1266 fn pdf_object_payload(&mut self, obj: &PdfObject) -> Option<PdfObject> {
1269 let mut obj = obj.clone();
1270 if obj.file {
1271 let name = String::from_utf8_lossy(&obj.data).into_owned();
1272 let path = self
1273 .kpse
1274 .as_ref()
1275 .and_then(|k| {
1276 k.borrow_mut()
1277 .find_file(&name, retex_kpath::FileFormat::Tex)
1278 })
1279 .unwrap_or_else(|| std::path::PathBuf::from(&name));
1280 match std::fs::read(&path) {
1281 Ok(d) => {
1282 self.printer.print_str("<<");
1283 self.printer.print_bytes(name.as_bytes());
1284 self.printer.print_str(">>");
1285 obj.data = d;
1286 obj.file = false;
1287 }
1288 Err(_) => {
1289 self.print_err("pdf backend: cannot open file for embedding");
1290 self.help(&[]);
1291 self.error();
1292 return None;
1293 }
1294 }
1295 }
1296 Some(obj)
1297 }
1298
1299 pub fn whatsit_tokens(list: &TokenList) -> LateData {
1301 LateData::Tokens(list.clone())
1302 }
1303
1304 pub fn pdf_finish_document(&mut self) {
1308 let mut doc = DocItems::default();
1309 let mut warnings = Vec::new();
1313 let mut pages: Vec<(i32, i32)> = self
1314 .pdf
1315 .page_refs
1316 .iter()
1317 .filter(|(p, _)| **p > self.total_pages as i32)
1318 .map(|(p, n)| (*n, *p))
1319 .collect();
1320 pages.sort_unstable_by(|a, b| b.cmp(a));
1321 for (_, p) in pages {
1322 warnings.push(format!("page {p} has been referenced but does not exist"));
1323 }
1324 let order = self.pdf.dest_order.clone();
1325 for id in order.iter().rev() {
1326 if let Some(d) = self.pdf.dests.get(id).copied()
1327 && !d.defined
1328 {
1329 warnings.push(format!("unreferenced destination with {}", id.describe()));
1330 doc.missing_dests.push(d.objnum);
1331 }
1332 }
1333 for (id, n) in self.pdf.threads.clone() {
1334 warnings.push(format!("unknown thread destination {}", id.describe()));
1335 let title = match id {
1336 DestId::Name(n) => n,
1337 DestId::Num(k) => k.to_string().into_bytes(),
1338 };
1339 doc.missing_threads.push((n, title));
1340 }
1341 for (i, w) in warnings.iter().enumerate() {
1342 if i > 0 {
1343 self.printer.print_ln();
1344 }
1345 self.pdf_warning(w);
1346 }
1347 let mut names: Vec<(Vec<u8>, i32)> = self
1348 .pdf
1349 .dests
1350 .iter()
1351 .filter_map(|(k, d)| match k {
1352 DestId::Name(n) => Some((n.clone(), d.objnum)),
1353 DestId::Num(_) => None,
1354 })
1355 .collect();
1356 names.sort();
1357 doc.dest_names = names;
1358 let toks = |e: &mut Engine, i: u32| {
1359 let list = e.eqtb.toks_param(i).clone();
1360 e.print_to_string(|e| e.show_token_list(&list, 0, list.len(), i64::MAX))
1361 };
1362 doc.pages_attr = toks(self, crate::backend::PAGES_ATTR);
1363 doc.trailer_id = toks(self, crate::backend::TRAILER_ID);
1364 self.pdf.doc = doc;
1365 }
1366}