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