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