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