1#![allow(clippy::missing_errors_doc, clippy::needless_pass_by_value)]
2
3use md5::Digest;
4
5use super::nodes::{
6 ColorStackAction, NumOrName, PDFAnnot, PDFBoxSpec, PDFCatalog, PDFColor, PDFDest, PDFExtension,
7 PDFImage, PDFLiteral, PDFLiteralOption, PDFNode, PDFObj, PDFOutline, PDFStartLink, PDFXForm,
8 PDFXImage,
9};
10use crate::commands::primitives::*;
11use crate::commands::CommandScope;
12use crate::engine::filesystem::{File, FileSystem};
13use crate::engine::fontsystem::Font;
14use crate::engine::gullet::methods::CSOrActiveChar;
15use crate::engine::gullet::Gullet;
16use crate::engine::state::State;
17use crate::engine::stomach::Stomach;
18use crate::engine::stomach::TeXMode;
19use crate::engine::{EngineReferences, EngineTypes, TeXEngine};
20use crate::pdflatex::{FileWithMD5, FontWithLpRp};
21use crate::prelude::CSHandler;
22use crate::prelude::Character;
23use crate::tex::catcodes::CommandCode;
24use crate::tex::nodes::horizontal::HNode;
25use crate::tex::nodes::math::MathNode;
26use crate::tex::nodes::vertical::VNode;
27use crate::tex::nodes::WhatsitFunction;
28use crate::tex::numerics::NumSet;
29use crate::tex::tokens::token_lists::Otherize;
30use crate::tex::tokens::{StandardToken, Token};
31use crate::utils::errors::{TeXError, TeXResult};
32use std::fmt::Write;
33
34pub fn pdftexversion<ET: EngineTypes>(
35 _engine: &mut EngineReferences<ET>,
36 _tk: ET::Token,
37) -> TeXResult<ET::Int, ET> {
38 Ok(<ET::Num as NumSet>::Int::from(140))
39}
40
41pub fn pdfmajorversion<ET: EngineTypes>(
42 _engine: &mut EngineReferences<ET>,
43 _tk: ET::Token,
44) -> TeXResult<ET::Int, ET> {
45 Ok(<ET::Num as NumSet>::Int::from(1))
46}
47
48pub fn pdftexrevision<ET: EngineTypes>(
49 _engine: &mut EngineReferences<ET>,
50 exp: &mut Vec<ET::Token>,
51 _tk: ET::Token,
52) -> TeXResult<(), ET> {
53 exp.push(ET::Token::from_char_cat(b'2'.into(), CommandCode::Other));
54 exp.push(ET::Token::from_char_cat(b'5'.into(), CommandCode::Other));
55 Ok(())
56}
57
58pub fn pdfcatalog<ET: EngineTypes>(
59 engine: &mut EngineReferences<ET>,
60 tk: ET::Token,
61) -> TeXResult<(), ET>
62where
63 ET::Extension: PDFExtension<ET>,
64 ET::CustomNode: From<PDFNode<ET>>,
65{
66 let mut literal = String::new();
67 engine.read_braced_string(true, true, &tk, &mut literal)?;
68 let action = if engine.read_keyword(b"openaction")? {
69 Some(super::nodes::action_spec(engine, &tk)?)
70 } else {
71 None
72 };
73 let node = PDFNode::PDFCatalog(PDFCatalog { literal, action });
74 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
75 Ok(())
76}
77
78pub fn pdfcolorstack<ET: EngineTypes>(
79 engine: &mut EngineReferences<ET>,
80 tk: ET::Token,
81) -> TeXResult<(), ET>
82where
83 ET::Extension: PDFExtension<ET>,
84 ET::CustomNode: From<PDFNode<ET>>,
85{
86 let index = engine.read_int(false, &tk)?.into();
87 if index < 0 || index >= (engine.aux.extension.colorstacks().len() as i64) {
88 engine.general_error(format!("Unknown color stack number {}", index))?;
89 }
90 let index = index as usize;
91 let kw = engine.read_keywords(&[b"push", b"pop", b"set", b"current"])?;
92 match kw {
93 Some(b"current") => crate::add_node!(ET::Stomach;engine,
94 VNode::Custom(PDFNode::Color(ColorStackAction::Current(index)).into()),
95 HNode::Custom(PDFNode::Color(ColorStackAction::Current(index)).into()),
96 MathNode::Custom(PDFNode::Color(ColorStackAction::Current(index)).into())
97 ),
98 Some(b"pop") => {
99 crate::add_node!(ET::Stomach;engine,
100 VNode::Custom(PDFNode::Color(ColorStackAction::Pop(index)).into()),
101 HNode::Custom(PDFNode::Color(ColorStackAction::Pop(index)).into()),
102 MathNode::Custom(PDFNode::Color(ColorStackAction::Pop(index)).into())
103 )
104 }
105 Some(b"set") => {
106 let mut color = String::new();
107 engine.read_braced_string(true, true, &tk, &mut color)?;
108 let color = PDFColor::parse(color);
109 crate::add_node!(ET::Stomach;engine,
110 VNode::Custom(PDFNode::Color(ColorStackAction::Set(index,color)).into()),
111 HNode::Custom(PDFNode::Color(ColorStackAction::Set(index,color)).into()),
112 MathNode::Custom(PDFNode::Color(ColorStackAction::Set(index,color)).into())
113 )
114 }
115 Some(b"push") => {
116 let mut color = String::new();
117 engine.read_braced_string(true, true, &tk, &mut color)?;
118 let color = PDFColor::parse(color);
119 crate::add_node!(ET::Stomach;engine,
120 VNode::Custom(PDFNode::Color(ColorStackAction::Push(index,color)).into()),
121 HNode::Custom(PDFNode::Color(ColorStackAction::Push(index,color)).into()),
122 MathNode::Custom(PDFNode::Color(ColorStackAction::Push(index,color)).into())
123 )
124 }
125 _ => TeXError::missing_keyword(
126 engine.aux,
127 engine.state,
128 engine.mouth,
129 &["current", "pop", "set", "push"],
130 )?,
131 }
132 Ok(())
133}
134
135pub fn pdfcolorstackinit<ET: EngineTypes>(
136 engine: &mut EngineReferences<ET>,
137 tk: ET::Token,
138) -> TeXResult<ET::Int, ET>
139where
140 ET::Extension: PDFExtension<ET>,
141{
142 engine.read_keyword(b"page")?;
143 engine.read_keyword(b"direct")?;
144 let mut color = String::new();
145 engine.read_braced_string(false, true, &tk, &mut color)?;
146 let color = PDFColor::parse(color);
147 let idx = engine.aux.extension.colorstacks().len() as i32;
148 engine.aux.extension.colorstacks().push(vec![color]);
149 Ok(ET::Int::from(idx))
150}
151
152pub fn pdfdest<ET: EngineTypes>(
153 engine: &mut EngineReferences<ET>,
154 tk: ET::Token,
155) -> TeXResult<(), ET>
156where
157 ET::Extension: PDFExtension<ET>,
158 ET::CustomNode: From<PDFNode<ET>>,
159{
160 let structnum = if engine.read_keyword(b"struct")? {
161 Some(engine.read_int(false, &tk)?.into())
162 } else {
163 None
164 };
165 let id = match super::nodes::num_or_name(engine, &tk)? {
166 Some(n) => n,
167 _ => {
168 TeXError::missing_keyword(engine.aux, engine.state, engine.mouth, &["name", "num"])?;
169 NumOrName::Num(0)
170 }
171 };
172 let dest = super::nodes::pdfdest_type(engine, &tk)?;
173 let node = PDFNode::PDFDest(PDFDest {
174 structnum,
175 id,
176 dest,
177 });
178 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
179 Ok(())
180}
181
182pub fn pdfstartlink<ET: EngineTypes>(
183 engine: &mut EngineReferences<ET>,
184 tk: ET::Token,
185) -> TeXResult<(), ET>
186where
187 ET::Extension: PDFExtension<ET>,
188 ET::CustomNode: From<PDFNode<ET>>,
189{
190 let mut width = None;
191 let mut height = None;
192 let mut depth = None;
193 loop {
194 match engine.read_keywords(&[b"width", b"height", b"depth"])? {
195 Some(b"width") => width = Some(engine.read_dim(false, &tk)?),
196 Some(b"height") => height = Some(engine.read_dim(false, &tk)?),
197 Some(b"depth") => depth = Some(engine.read_dim(false, &tk)?),
198 _ => break,
199 }
200 }
201 let attr = if engine.read_keyword(b"attr")? {
202 let mut attr = String::new();
203 engine.read_braced_string(true, true, &tk, &mut attr)?;
204 Some(attr)
205 } else {
206 None
207 };
208 let action = super::nodes::action_spec(engine, &tk)?;
209 let node = PDFNode::PDFStartLink(PDFStartLink {
210 width,
211 height,
212 depth,
213 attr,
214 action,
215 });
216 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
217 Ok(())
218}
219
220pub fn pdfendlink<ET: EngineTypes>(
221 engine: &mut EngineReferences<ET>,
222 _tk: ET::Token,
223) -> TeXResult<(), ET>
224where
225 ET::Extension: PDFExtension<ET>,
226 ET::CustomNode: From<PDFNode<ET>>,
227{
228 crate::add_node!(ET::Stomach;engine,
229 VNode::Custom(PDFNode::PDFEndLink.into()),
230 HNode::Custom(PDFNode::PDFEndLink.into()),
231 MathNode::Custom(PDFNode::PDFEndLink.into())
232 );
233 Ok(())
234}
235
236pub fn pdfsave<ET: EngineTypes>(
237 engine: &mut EngineReferences<ET>,
238 _tk: ET::Token,
239) -> TeXResult<(), ET>
240where
241 ET::Extension: PDFExtension<ET>,
242 ET::CustomNode: From<PDFNode<ET>>,
243{
244 crate::add_node!(ET::Stomach;engine,
245 VNode::Custom(PDFNode::PDFSave.into()),
246 HNode::Custom(PDFNode::PDFSave.into()),
247 MathNode::Custom(PDFNode::PDFSave.into())
248 );
249 Ok(())
250}
251pub fn pdfrestore<ET: EngineTypes>(
252 engine: &mut EngineReferences<ET>,
253 _tk: ET::Token,
254) -> TeXResult<(), ET>
255where
256 ET::Extension: PDFExtension<ET>,
257 ET::CustomNode: From<PDFNode<ET>>,
258{
259 crate::add_node!(ET::Stomach;engine,
260 VNode::Custom(PDFNode::PDFRestore.into()),
261 HNode::Custom(PDFNode::PDFRestore.into()),
262 MathNode::Custom(PDFNode::PDFRestore.into())
263 );
264 Ok(())
265}
266
267pub fn pdfsetmatrix<ET: EngineTypes>(
268 engine: &mut EngineReferences<ET>,
269 tk: ET::Token,
270) -> TeXResult<(), ET>
271where
272 ET::Extension: PDFExtension<ET>,
273 ET::CustomNode: From<PDFNode<ET>>,
274{
275 let mut str = engine.aux.memory.get_string();
276 engine.read_braced_string(true, true, &tk, &mut str)?;
277 let mut scale = 0f32;
278 let mut rotate = 0f32;
279 let mut skewx = 0f32;
280 let mut skewy = 0f32;
281 for (i, s) in str.split(|c: char| c.is_ascii_whitespace()).enumerate() {
282 let f = match s.parse::<f32>() {
283 Ok(f) => f,
284 _ => {
285 engine.general_error(
286 "pdfTeX error (\\pdfsetmatrix): Unrecognized format".to_string(),
287 )?;
288 1.0
289 }
290 };
291 match i {
292 0 => scale = f,
293 1 => rotate = f,
294 2 => skewx = f,
295 3 => skewy = f,
296 _ => engine
297 .general_error("pdfTeX error (\\pdfsetmatrix): Unrecognized format".to_string())?,
298 }
299 }
300 engine.aux.memory.return_string(str);
301 crate::add_node!(ET::Stomach;engine,
302 VNode::Custom(PDFNode::PDFMatrix{scale,rotate,skewx,skewy}.into()),
303 HNode::Custom(PDFNode::PDFMatrix{scale,rotate,skewx,skewy}.into()),
304 MathNode::Custom(PDFNode::PDFMatrix{scale,rotate,skewx,skewy}.into())
305 );
306 Ok(())
307}
308
309pub fn pdfinfo<ET: EngineTypes>(
310 engine: &mut EngineReferences<ET>,
311 tk: ET::Token,
312) -> TeXResult<(), ET> {
313 engine.skip_argument(&tk)
314}
315
316pub fn ifincsname<ET: EngineTypes>(
317 engine: &mut EngineReferences<ET>,
318 _tk: ET::Token,
319) -> TeXResult<bool, ET> {
320 Ok(*engine.gullet.csnames() > 0)
321}
322pub fn ifpdfabsnum<ET: EngineTypes>(
323 engine: &mut EngineReferences<ET>,
324 tk: ET::Token,
325) -> TeXResult<bool, ET> {
326 let first = engine.read_int(false, &tk)?;
327 let rel = match engine.read_chars(b"=<>")? {
328 either::Left(b) => b,
329 _ => {
330 TeXError::missing_keyword(engine.aux, engine.state, engine.mouth, &["=", "<", ">"])?;
331 b'='
332 }
333 };
334 let second = engine.read_int(false, &tk)?;
335
336 let first = if first < <<ET as EngineTypes>::Num as NumSet>::Int::default() {
337 -first
338 } else {
339 first
340 };
341 let second = if second < <<ET as EngineTypes>::Num as NumSet>::Int::default() {
342 -second
343 } else {
344 second
345 };
346 Ok(match rel {
347 b'=' => first == second,
348 b'<' => first < second,
349 b'>' => first > second,
350 _ => unreachable!(),
351 })
352}
353pub fn ifpdfabsdim<ET: EngineTypes>(
354 engine: &mut EngineReferences<ET>,
355 tk: ET::Token,
356) -> TeXResult<bool, ET> {
357 let first = engine.read_dim(false, &tk)?;
358 let rel = match engine.read_chars(b"=<>")? {
359 either::Left(b) => b,
360 _ => {
361 TeXError::missing_keyword(engine.aux, engine.state, engine.mouth, &["=", "<", ">"])?;
362 b'='
363 }
364 };
365 let second = engine.read_dim(false, &tk)?;
366 let first = if first < <<ET as EngineTypes>::Num as NumSet>::Dim::default() {
367 -first
368 } else {
369 first
370 };
371 let second = if second < <<ET as EngineTypes>::Num as NumSet>::Dim::default() {
372 -second
373 } else {
374 second
375 };
376 Ok(match rel {
377 b'=' => first == second,
378 b'<' => first < second,
379 b'>' => first > second,
380 _ => unreachable!(),
381 })
382}
383pub fn ifpdfprimitive<ET: EngineTypes>(
384 engine: &mut EngineReferences<ET>,
385 _tk: ET::Token,
386) -> TeXResult<bool, ET> {
387 use crate::engine::mouth::Mouth;
388 engine.general_error(format!(
389 "Not yet implemented: \\ifpdfprimitive at {}",
390 engine.mouth.current_sourceref().display(engine.filesystem)
391 ))?;
392 Ok(false)
393}
394
395pub fn lpcode_get<ET: EngineTypes>(
396 engine: &mut EngineReferences<ET>,
397 tk: ET::Token,
398) -> TeXResult<ET::Int, ET>
399where
400 ET::Font: FontWithLpRp,
401{
402 let fnt = engine.read_font(false, &tk)?;
403 let char = engine.read_charcode(false, &tk)?;
404 Ok(fnt.get_lp(char))
405}
406pub fn lpcode_set<ET: EngineTypes>(
407 engine: &mut EngineReferences<ET>,
408 tk: ET::Token,
409 _globally: bool,
410) -> TeXResult<(), ET>
411where
412 ET::Font: FontWithLpRp,
413{
414 let mut fnt = engine.read_font(false, &tk)?;
415 let char = engine.read_charcode(false, &tk)?;
416 let code = engine.read_int(true, &tk)?;
417 fnt.set_lp(char, code);
418 Ok(())
419}
420pub fn rpcode_get<ET: EngineTypes>(
421 engine: &mut EngineReferences<ET>,
422 tk: ET::Token,
423) -> TeXResult<ET::Int, ET>
424where
425 ET::Font: FontWithLpRp,
426{
427 let fnt = engine.read_font(false, &tk)?;
428 let char = engine.read_charcode(false, &tk)?;
429 Ok(fnt.get_rp(char))
430}
431pub fn rpcode_set<ET: EngineTypes>(
432 engine: &mut EngineReferences<ET>,
433 tk: ET::Token,
434 _globally: bool,
435) -> TeXResult<(), ET>
436where
437 ET::Font: FontWithLpRp,
438{
439 let mut fnt = engine.read_font(false, &tk)?;
440 let char = engine.read_charcode(false, &tk)?;
441 let code = engine.read_int(true, &tk)?;
442 fnt.set_rp(char, code);
443 Ok(())
444}
445
446pub fn leftmarginkern<ET: EngineTypes>(
447 engine: &mut EngineReferences<ET>,
448 exp: &mut Vec<ET::Token>,
449 tk: ET::Token,
450) -> TeXResult<(), ET> {
451 let _ = engine.read_int(false, &tk)?;
453 Otherize::new(&mut |t| exp.push(t)).write_str("0pt")?;
454 Ok(())
455}
456pub fn rightmarginkern<ET: EngineTypes>(
457 engine: &mut EngineReferences<ET>,
458 exp: &mut Vec<ET::Token>,
459 tk: ET::Token,
460) -> TeXResult<(), ET> {
461 let _ = engine.read_int(false, &tk)?;
463 Otherize::new(&mut |t| exp.push(t)).write_str("0pt")?;
464 Ok(())
465}
466
467pub fn pdfcreationdate<ET: EngineTypes>(
468 engine: &mut EngineReferences<ET>,
469 exp: &mut Vec<ET::Token>,
470 _tk: ET::Token,
471) -> TeXResult<(), ET> {
472 use chrono::{Datelike, Timelike};
473 let dt = engine.aux.start_time;
474 let mut f = |t| exp.push(t);
475 let mut tk = Otherize::new(&mut f);
476 write!(
477 tk,
478 "D:{}{:02}{:02}{:02}{:02}{:02}{}'",
479 dt.year(),
480 dt.month(),
481 dt.day(),
482 dt.hour(),
483 dt.minute(),
484 dt.second(),
485 dt.offset().to_string().replace(':', "'")
486 )?;
487 Ok(())
488}
489
490pub fn pdffilemoddate<ET: EngineTypes>(
491 engine: &mut EngineReferences<ET>,
492 exp: &mut Vec<ET::Token>,
493 tk: ET::Token,
494) -> TeXResult<(), ET> {
495 use chrono::{Datelike, Timelike};
496 let mut filename = engine.aux.memory.get_string();
497 engine.read_braced_string(true, false, &tk, &mut filename)?;
498 let f = engine.filesystem.get(&filename);
499 engine.aux.memory.return_string(filename);
500 let path = f.path();
501 if let Ok(Ok(st)) = std::fs::metadata(path).map(|md| md.modified()) {
502 let dt: chrono::DateTime<chrono::Local> = chrono::DateTime::from(st);
503 let mut f = |t| exp.push(t);
504 let mut tk = Otherize::new(&mut f);
505 write!(
506 tk,
507 "D:{}{:02}{:02}{:02}{:02}{:02}{}'",
508 dt.year(),
509 dt.month(),
510 dt.day(),
511 dt.hour(),
512 dt.minute(),
513 dt.second(),
514 dt.offset().to_string().replace(':', "'")
515 )?;
516 }
517 Ok(())
518}
519
520pub fn pdfescapestring<ET: EngineTypes>(
521 engine: &mut EngineReferences<ET>,
522 exp: &mut Vec<ET::Token>,
523 tk: ET::Token,
524) -> TeXResult<(), ET> {
525 use crate::tex::characters::Character;
526 engine.expand_until_bgroup(false, &tk)?;
527 engine.expand_until_endgroup(true, false, &tk, |_, _, t| {
528 match t.to_enum() {
529 StandardToken::Character(_, CommandCode::Space) => ET::Char::string_to_iter("\\040")
530 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other))),
531 StandardToken::Character(c, _) if c == ET::Char::from(b' ') => {
532 ET::Char::string_to_iter("\\040")
533 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
534 }
535 StandardToken::Character(c, _) if c == ET::Char::from(b'(') => {
536 ET::Char::string_to_iter("\\(")
537 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
538 }
539 StandardToken::Character(c, _) if c == ET::Char::from(b')') => {
540 ET::Char::string_to_iter("\\)")
541 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
542 }
543 StandardToken::Character(c, _) if c == ET::Char::from(b'\\') => {
544 ET::Char::string_to_iter("\\\\")
545 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
546 }
547 StandardToken::Character(c, _) if c == ET::Char::from(b'#') => {
548 ET::Char::string_to_iter("\\#")
549 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
550 }
551 StandardToken::Character(c, _) => {
552 exp.push(ET::Token::from_char_cat(c, CommandCode::Other))
553 }
554 StandardToken::Primitive(_) => (),
555 StandardToken::ControlSequence(_) => (),
556 };
557 Ok(())
558 })?;
559 Ok(())
560}
561
562pub fn pdfescapename<ET: EngineTypes>(
563 engine: &mut EngineReferences<ET>,
564 exp: &mut Vec<ET::Token>,
565 tk: ET::Token,
566) -> TeXResult<(), ET> {
567 use crate::tex::characters::Character;
568 engine.expand_until_bgroup(false, &tk)?;
569 engine.expand_until_endgroup(true, false, &tk, |_, _, t| {
570 match t.to_enum() {
571 StandardToken::Character(_, CommandCode::Space) => ET::Char::string_to_iter("#20")
572 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other))),
573 StandardToken::Character(c, _) if c == ET::Char::from(b' ') => {
574 ET::Char::string_to_iter("#20")
575 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
576 }
577 StandardToken::Character(c, _) if c == ET::Char::from(b'(') => {
578 ET::Char::string_to_iter("#28")
579 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
580 }
581 StandardToken::Character(c, _) if c == ET::Char::from(b')') => {
582 ET::Char::string_to_iter("#29")
583 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
584 }
585 StandardToken::Character(c, _) if c == ET::Char::from(b'\\') => {
586 ET::Char::string_to_iter("#5C")
587 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
588 }
589 StandardToken::Character(c, _) if c == ET::Char::from(b'#') => {
590 ET::Char::string_to_iter("#23")
591 .for_each(|c| exp.push(ET::Token::from_char_cat(c, CommandCode::Other)))
592 }
593 StandardToken::Character(c, _) => {
594 exp.push(ET::Token::from_char_cat(c, CommandCode::Other))
595 }
596 StandardToken::Primitive(_) => (),
597 StandardToken::ControlSequence(_) => (),
598 };
599 Ok(())
600 })?;
601 Ok(())
602}
603
604pub fn pdfescapehex<ET: EngineTypes>(
605 engine: &mut EngineReferences<ET>,
606 exp: &mut Vec<ET::Token>,
607 tk: ET::Token,
608) -> TeXResult<(), ET> {
609 use crate::tex::characters::Character;
610 engine.expand_until_bgroup(false, &tk)?;
611 engine.expand_until_endgroup(true, false, &tk, |_, _, t| {
612 match t.to_enum() {
613 StandardToken::Character(c, _) => {
614 let num = c.into();
615 for c in ET::Char::string_to_iter(&format!("{:02X}", num)) {
616 exp.push(ET::Token::from_char_cat(c, CommandCode::Other))
617 }
618 }
619 StandardToken::Primitive(_) => (),
620 StandardToken::ControlSequence(_) => (),
621 };
622 Ok(())
623 })?;
624 Ok(())
625}
626pub fn pdfunescapehex<ET: EngineTypes>(
627 engine: &mut EngineReferences<ET>,
628 exp: &mut Vec<ET::Token>,
629 tk: ET::Token,
630) -> TeXResult<(), ET> {
631 engine.expand_until_bgroup(false, &tk)?;
632 let mut s = String::new();
633 engine.expand_until_endgroup(true, false, &tk, |_, _, t| {
634 match t.to_enum() {
635 StandardToken::Character(c, _) if s.is_empty() => {
636 let num = c.into();
637 if (48..=57).contains(&num) || (65..=70).contains(&num) || (97..=102).contains(&num)
638 {
639 s.push((num as u8).into());
640 } else {
641 }
643 }
644 StandardToken::Character(c, _) => {
645 let num = c.into();
646 if (48..=57).contains(&num) || (65..=70).contains(&num) || (97..=102).contains(&num)
647 {
648 s.push((num as u8).into());
649 } else {
650 }
652 let c = u8::from_str_radix(&s, 16).unwrap();
653 s.clear();
654 exp.push(ET::Token::from_char_cat(c.into(), CommandCode::Other));
655 }
656 StandardToken::Primitive(_) => (),
657 StandardToken::ControlSequence(_) => (),
658 };
659 Ok(())
660 })?;
661 Ok(())
662}
663
664pub fn pdffilesize<ET: EngineTypes>(
665 engine: &mut EngineReferences<ET>,
666 exp: &mut Vec<ET::Token>,
667 tk: ET::Token,
668) -> TeXResult<(), ET> {
669 let mut filename = engine.aux.memory.get_string();
670 engine.read_braced_string(false, true, &tk, &mut filename)?;
671 let file = engine.filesystem.get(&filename);
672 engine.aux.memory.return_string(filename);
673 if file.exists() {
674 let size = file.size();
675 for u in size.to_string().bytes() {
676 exp.push(ET::Token::from_char_cat(u.into(), CommandCode::Other));
677 }
678 }
679 Ok(())
680}
681
682pub fn pdfglyphtounicode<ET: EngineTypes>(
683 engine: &mut EngineReferences<ET>,
684 tk: ET::Token,
685) -> TeXResult<(), ET> {
686 engine.skip_argument(&tk)?;
688 engine.skip_argument(&tk)
689}
690
691pub fn pdfmatch<ET: EngineTypes>(
692 engine: &mut EngineReferences<ET>,
693 exp: &mut Vec<ET::Token>,
694 tk: ET::Token,
695) -> TeXResult<(), ET>
696where
697 ET::Extension: PDFExtension<ET>,
698{
699 let icase = engine.read_keyword(b"icase")?;
700 let _subcount = if engine.read_keyword(b"subcount")? {
701 engine.read_int(false, &tk)?.into()
702 } else {
703 -1
704 }; let mut pattern_string = String::new();
706 let mut target_string = String::new();
707 if icase {
708 pattern_string.push_str("(?i)");
709 }
710 engine.read_braced_string(false, true, &tk, &mut pattern_string)?;
711 engine.read_braced_string(false, true, &tk, &mut target_string)?;
712 let pdfmatches = engine.aux.extension.pdfmatches();
713 pdfmatches.clear();
714
715 match regex::Regex::new(&pattern_string) {
716 Err(_) => {
717 exp.push(ET::Token::from_char_cat(b'-'.into(), CommandCode::Other));
718 exp.push(ET::Token::from_char_cat(b'1'.into(), CommandCode::Other));
719 }
720 Ok(reg) => match reg.captures_iter(&target_string).next() {
721 None => exp.push(ET::Token::from_char_cat(b'0'.into(), CommandCode::Other)),
722 Some(capture) => {
723 let cap = capture.get(0).unwrap();
724 pdfmatches.push(format!("{}->{}", cap.start(), cap.as_str()));
725 for cap in capture.iter().skip(1) {
726 match cap {
727 None => pdfmatches.push("-1".to_string()),
728 Some(cap) => {
729 pdfmatches.push(format!("{}->{}", cap.start(), cap.as_str()));
730 }
731 }
732 }
733 exp.push(ET::Token::from_char_cat(b'1'.into(), CommandCode::Other));
734 }
735 },
736 }
737 Ok(())
738}
739
740pub fn pdflastmatch<ET: EngineTypes>(
741 engine: &mut EngineReferences<ET>,
742 exp: &mut Vec<ET::Token>,
743 tk: ET::Token,
744) -> TeXResult<(), ET>
745where
746 ET::Extension: PDFExtension<ET>,
747{
748 let i = engine.read_int(false, &tk)?.into();
749 let i = if i < 0 { 0usize } else { i as usize };
750 match engine.aux.extension.pdfmatches().get(i) {
751 None => {
752 exp.push(ET::Token::from_char_cat(b'-'.into(), CommandCode::Other));
753 exp.push(ET::Token::from_char_cat(b'1'.into(), CommandCode::Other));
754 }
755 Some(s) => {
756 let mut f = |t| exp.push(t);
757 let mut t = Otherize::new(&mut f);
758 write!(t, "{}", s)?;
759 }
760 }
761 Ok(())
762}
763
764pub fn pdfmdfivesum<ET: EngineTypes>(
765 engine: &mut EngineReferences<ET>,
766 exp: &mut Vec<ET::Token>,
767 tk: ET::Token,
768) -> TeXResult<(), ET>
769where
770 ET::File: FileWithMD5,
771{
772 let mut f = |t| exp.push(t);
773 if engine.read_keyword(b"file")? {
774 let mut filename = String::new();
775 engine.read_braced_string(true, true, &tk, &mut filename)?;
776 let file = engine.filesystem.get(&filename);
777 let mut t = Otherize::new(&mut f);
778 for i in file.md5() {
779 write!(t, "{i:02X}")?;
780 }
781 } else {
782 let mut str = String::new();
783 engine.read_braced_string(false, true, &tk, &mut str)?;
784 let mut t = Otherize::new(&mut f);
785 let mut hasher = md5::Md5::default();
786 hasher.update(str.as_bytes());
787 let r: [u8; 16] = hasher.finalize().into();
788 for i in r {
789 write!(t, "{i:02X}")?;
790 }
791 }
792 Ok(())
793}
794
795pub fn pdfannot<ET: EngineTypes>(
796 engine: &mut EngineReferences<ET>,
797 tk: ET::Token,
798) -> TeXResult<(), ET>
799where
800 ET::Extension: PDFExtension<ET>,
801 ET::CustomNode: From<PDFNode<ET>>,
802{
803 let num = match engine.read_keywords(&[b"reserveobjnum", b"useobjnum"])? {
804 Some(b"reserveobjnum") => {
805 engine.aux.extension.pdfannots().push(PDFAnnot {
806 width: None,
807 height: None,
808 depth: None,
809 content: String::new(),
810 });
811 return Ok(());
812 }
813 Some(b"useobjnum") => {
814 let num = engine.read_int(false, &tk)?.into();
815 if num < 0 {
816 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
817 }
818 let num = num as usize;
819 if num >= engine.aux.extension.pdfannots().len() {
820 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
821 }
822 Some(num)
823 }
824 _ => None,
825 };
826 let mut width = None;
827 let mut height = None;
828 let mut depth = None;
829 loop {
830 match engine.read_keywords(&[b"width", b"height", b"depth"])? {
831 Some(b"width") => width = Some(engine.read_dim(false, &tk)?),
832 Some(b"height") => height = Some(engine.read_dim(false, &tk)?),
833 Some(b"depth") => depth = Some(engine.read_dim(false, &tk)?),
834 _ => break,
835 }
836 }
837 let mut content = String::new();
838 engine.read_braced_string(true, true, &tk, &mut content)?;
839 let annot = PDFAnnot {
840 width,
841 height,
842 depth,
843 content,
844 };
845 match num {
846 None => engine.aux.extension.pdfannots().push(annot.clone()),
847 Some(num) => engine.aux.extension.pdfannots()[num] = annot.clone(),
848 }
849 crate::add_node!(ET::Stomach;engine,
850 VNode::Custom(PDFNode::PDFAnnot(annot).into()),
851 HNode::Custom(PDFNode::PDFAnnot(annot).into()),
852 MathNode::Custom(PDFNode::PDFAnnot(annot).into())
853 );
854 Ok(())
855}
856
857pub fn pdflastannot<ET: EngineTypes>(
858 engine: &mut EngineReferences<ET>,
859 _tk: ET::Token,
860) -> TeXResult<<ET::Num as NumSet>::Int, ET>
861where
862 ET::Extension: PDFExtension<ET>,
863{
864 Ok(<ET::Num as NumSet>::Int::from(
865 (engine.aux.extension.pdfannots().len() as i32) - 1,
866 ))
867}
868
869pub fn parse_pdfobj<ET: EngineTypes>(
870 engine: &mut EngineReferences<ET>,
871 tk: &ET::Token,
872) -> TeXResult<usize, ET>
873where
874 ET::Extension: PDFExtension<ET>,
875{
876 match engine.read_keywords(&[b"reserveobjnum", b"useobjnum", b"stream"])? {
877 Some(b"reserveobjnum") => {
878 engine.aux.extension.pdfobjs().push(PDFObj(String::new()));
879 Ok(engine.aux.extension.pdfobjs().len() - 1)
880 }
881 Some(b"useobjnum") => {
882 let num = engine.read_int(false, tk)?.into();
883 if num < 0 {
884 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
885 }
886 let num = num as usize;
887 if num >= engine.aux.extension.pdfobjs().len() {
888 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
889 }
890 let mut str = String::new();
891 engine.read_braced_string(false, true, tk, &mut str)?;
892 engine.aux.extension.pdfobjs()[num] = PDFObj(str);
893 Ok(num)
894 }
895 Some(b"stream") => {
896 if engine.read_keyword(b"attr")? {
897 }
899 let mut str = String::new();
900 engine.read_braced_string(false, true, tk, &mut str)?;
901 engine.aux.extension.pdfobjs().push(PDFObj(str));
902 Ok(engine.aux.extension.pdfobjs().len() - 1)
903 }
904 _ => {
905 TeXError::missing_keyword(
906 engine.aux,
907 engine.state,
908 engine.mouth,
909 &["reserveobjnum", "useobjnum", "stream"],
910 )?;
911 Ok(0)
912 }
913 }
914}
915
916pub fn pdfobj<ET: EngineTypes>(
917 engine: &mut EngineReferences<ET>,
918 tk: ET::Token,
919) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET>
920where
921 ET::Extension: PDFExtension<ET>,
922{
923 parse_pdfobj(engine, &tk)?;
924 Ok(None)
925}
926pub fn pdfobj_immediate<ET: EngineTypes>(
927 engine: &mut EngineReferences<ET>,
928 tk: ET::Token,
929) -> TeXResult<(), ET>
930where
931 ET::Extension: PDFExtension<ET>,
932 ET::CustomNode: From<PDFNode<ET>>,
933{
934 let num = parse_pdfobj(engine, &tk)?;
935 let node = PDFNode::Obj(engine.aux.extension.pdfobjs()[num].clone());
936 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
937 Ok(())
938}
939
940pub fn pdfrefobj<ET: EngineTypes>(
941 engine: &mut EngineReferences<ET>,
942 tk: ET::Token,
943) -> TeXResult<(), ET>
944where
945 ET::Extension: PDFExtension<ET>,
946 ET::CustomNode: From<PDFNode<ET>>,
947{
948 let num = engine.read_int(false, &tk)?.into();
949 if num < 0 {
950 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
951 }
952 match engine.aux.extension.pdfobjs().get(num as usize) {
953 None => {
954 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
955 }
956 Some(o) => {
957 let node = PDFNode::Obj(o.clone());
958 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()))
959 }
960 }
961 Ok(())
962}
963
964pub fn pdflastobj<ET: EngineTypes>(
965 engine: &mut EngineReferences<ET>,
966 _tk: ET::Token,
967) -> TeXResult<ET::Int, ET>
968where
969 ET::Extension: PDFExtension<ET>,
970{
971 Ok(<ET::Num as NumSet>::Int::from(
972 (engine.aux.extension.pdfobjs().len() as i32) - 1,
973 ))
974}
975
976pub fn pdfoutline<ET: EngineTypes>(
977 engine: &mut EngineReferences<ET>,
978 tk: ET::Token,
979) -> TeXResult<(), ET>
980where
981 ET::Extension: PDFExtension<ET>,
982 ET::CustomNode: From<PDFNode<ET>>,
983{
984 let mut attr = String::new();
985 if engine.read_keyword(b"attr")? {
986 engine.read_braced_string(true, true, &tk, &mut attr)?;
987 }
988 let action = super::nodes::action_spec(engine, &tk)?;
989 let count = if engine.read_keyword(b"count")? {
990 Some(engine.read_int(false, &tk)?.into())
991 } else {
992 None
993 };
994 let mut content = String::new();
995 engine.read_braced_string(true, true, &tk, &mut content)?;
996 let node = PDFNode::PDFOutline(PDFOutline {
997 attr,
998 action,
999 count,
1000 content,
1001 });
1002 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
1003 Ok(())
1004}
1005
1006pub fn parse_pdfxform<ET: EngineTypes>(
1007 engine: &mut EngineReferences<ET>,
1008 tk: &ET::Token,
1009) -> TeXResult<usize, ET>
1010where
1011 ET::Extension: PDFExtension<ET>,
1012{
1013 let mut attr = String::new();
1014 if engine.read_keyword(b"attr")? {
1015 engine.read_braced_string(true, true, tk, &mut attr)?;
1016 }
1017 let mut resources = String::new();
1018 if engine.read_keyword(b"resources")? {
1019 engine.read_braced_string(true, true, tk, &mut resources)?;
1020 }
1021 let idx = engine.read_register_index(false, tk)?;
1022 let bx = engine.state.take_box_register(idx);
1023 engine.aux.extension.pdfxforms().push(PDFXForm {
1024 attr,
1025 resources,
1026 bx,
1027 });
1028 Ok(engine.aux.extension.pdfxforms().len() - 1)
1029}
1030pub fn pdfxform<ET: EngineTypes>(
1031 engine: &mut EngineReferences<ET>,
1032 tk: ET::Token,
1033) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET>
1034where
1035 ET::Extension: PDFExtension<ET>,
1036{
1037 parse_pdfxform(engine, &tk)?;
1038 Ok(None)
1039}
1040pub fn pdfxform_immediate<ET: EngineTypes>(
1041 engine: &mut EngineReferences<ET>,
1042 tk: ET::Token,
1043) -> TeXResult<(), ET>
1044where
1045 ET::Extension: PDFExtension<ET>,
1046 ET::CustomNode: From<PDFNode<ET>>,
1047{
1048 let num = parse_pdfxform(engine, &tk)?;
1049 let form = engine.aux.extension.pdfxforms()[num].clone();
1050 let node = PDFNode::XForm(form);
1051 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
1052 Ok(())
1053}
1054
1055pub fn pdfrefxform<ET: EngineTypes>(
1056 engine: &mut EngineReferences<ET>,
1057 tk: ET::Token,
1058) -> TeXResult<(), ET>
1059where
1060 ET::Extension: PDFExtension<ET>,
1061 ET::CustomNode: From<PDFNode<ET>>,
1062{
1063 let num = engine.read_int(false, &tk)?.into();
1064 if num < 0 {
1065 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
1066 }
1067 match engine.aux.extension.pdfxforms().get(num as usize) {
1068 None => {
1069 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
1070 }
1071 Some(n) => {
1072 let node = PDFNode::XForm(n.clone());
1073 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()))
1074 }
1075 }
1076 Ok(())
1077}
1078
1079pub fn pdflastxform<ET: EngineTypes>(
1080 engine: &mut EngineReferences<ET>,
1081 _tk: ET::Token,
1082) -> TeXResult<ET::Int, ET>
1083where
1084 ET::Extension: PDFExtension<ET>,
1085{
1086 Ok(<ET::Num as NumSet>::Int::from(
1087 (engine.aux.extension.pdfxforms().len() as i32) - 1,
1088 ))
1089}
1090
1091pub fn pdfximage<ET: EngineTypes>(
1092 engine: &mut EngineReferences<ET>,
1093 tk: ET::Token,
1094) -> TeXResult<(), ET>
1095where
1096 ET::Extension: PDFExtension<ET>,
1097{
1098 let mut width: Option<ET::Dim> = None;
1099 let mut height: Option<ET::Dim> = None;
1100 let mut depth: Option<ET::Dim> = None;
1101 loop {
1102 match engine.read_keywords(&[b"width", b"height", b"depth"])? {
1103 Some(b"width") => width = Some(engine.read_dim(false, &tk)?),
1104 Some(b"height") => height = Some(engine.read_dim(false, &tk)?),
1105 Some(b"depth") => depth = Some(engine.read_dim(false, &tk)?),
1106 _ => break,
1107 }
1108 }
1109 let mut attr = String::new();
1110 if engine.read_keyword(b"attr")? {
1111 engine.read_braced_string(true, true, &tk, &mut attr)?
1112 }
1113 let page = if engine.read_keyword(b"page")? {
1114 Some(engine.read_int(false, &tk)?.into())
1115 } else {
1116 None
1117 };
1118 let colorspace = if engine.read_keyword(b"colorspace")? {
1119 Some(engine.read_int(false, &tk)?.into())
1120 } else {
1121 None
1122 };
1123 let boxspec = match engine.read_keywords(&[
1124 b"mediabox",
1125 b"cropbox",
1126 b"bleedbox",
1127 b"trimbox",
1128 b"artbox",
1129 ])? {
1130 None => None,
1131 Some(b"mediabox") => Some(PDFBoxSpec::MediaBox),
1132 Some(b"cropbox") => Some(PDFBoxSpec::CropBox),
1133 Some(b"bleedbox") => Some(PDFBoxSpec::BleedBox),
1134 Some(b"trimbox") => Some(PDFBoxSpec::TrimBox),
1135 Some(b"artbox") => Some(PDFBoxSpec::ArtBox),
1136 _ => unreachable!(),
1137 };
1138 let mut filename = String::new();
1139 engine.read_braced_string(true, true, &tk, &mut filename)?;
1140 let file = engine.filesystem.get(&filename);
1141
1142 let img = if file.path().extension().is_some_and(|ext| ext == "pdf") {
1143 super::nodes::pdf_as_image(file.path(), &mut engine.aux.extension)
1144 } else {
1145 let Ok(img) = image::ImageReader::open(file.path()) else {
1146 engine.general_error("Unknown type of image".into())?;
1147 return Ok(());
1148 };
1149 let Ok(Ok(img)) = img.with_guessed_format().map(image::ImageReader::decode) else {
1150 engine.general_error("Unknown type of image".into())?;
1151 return Ok(());
1152 };
1153 PDFImage::Img(img)
1154 };
1155 let img = PDFXImage {
1156 width,
1157 height,
1158 depth,
1159 attr,
1160 page,
1161 colorspace,
1162 boxspec,
1163 img,
1164 filepath: file.path().to_path_buf(),
1165 };
1166 engine.aux.extension.pdfximages().push(img);
1167 Ok(())
1168}
1169
1170pub fn pdfrefximage<ET: EngineTypes>(
1171 engine: &mut EngineReferences<ET>,
1172 tk: ET::Token,
1173) -> TeXResult<(), ET>
1174where
1175 ET::Extension: PDFExtension<ET>,
1176 ET::CustomNode: From<PDFNode<ET>>,
1177{
1178 let num = engine.read_int(false, &tk)?.into();
1179 if num < 0 {
1180 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
1181 }
1182 match engine.aux.extension.pdfximages().get(num as usize) {
1183 None => {
1184 engine.general_error("pdfTeX error (ext1): invalid object number.".to_string())?;
1185 }
1186 Some(n) => {
1187 let node = PDFNode::XImage(n.clone());
1188 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()))
1189 }
1190 }
1191 Ok(())
1192}
1193
1194pub fn pdfpageattr<ET: EngineTypes>(
1195 engine: &mut EngineReferences<ET>,
1196 tk: ET::Token,
1197) -> TeXResult<(), ET>
1198where
1199 ET::Extension: PDFExtension<ET>,
1200 ET::CustomNode: From<PDFNode<ET>>,
1201{
1202 engine.expand_until_bgroup(false, &tk)?;
1203 let mut v = Vec::new();
1204 engine.read_until_endgroup(&tk, |_, _, t| {
1205 v.push(t);
1206 Ok(())
1207 })?;
1208 let node = PDFNode::PDFPageAttr(v.into());
1209 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
1210 Ok(())
1211}
1212
1213pub fn pdfpagesattr<ET: EngineTypes>(
1214 engine: &mut EngineReferences<ET>,
1215 tk: ET::Token,
1216) -> TeXResult<(), ET>
1217where
1218 ET::Extension: PDFExtension<ET>,
1219 ET::CustomNode: From<PDFNode<ET>>,
1220{
1221 engine.expand_until_bgroup(false, &tk)?;
1222 let mut v = Vec::new();
1223 engine.read_until_endgroup(&tk, |_, _, t| {
1224 v.push(t);
1225 Ok(())
1226 })?;
1227 let node = PDFNode::PDFPagesAttr(v.into());
1228 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
1229 Ok(())
1230}
1231
1232pub fn pdfprimitive<ET: EngineTypes>(
1233 engine: &mut EngineReferences<ET>,
1234 _tk: ET::Token,
1235) -> TeXResult<(), ET> {
1236 let name = engine.read_csname()?;
1237 let s = engine
1238 .aux
1239 .memory
1240 .cs_interner_mut()
1241 .resolve(&name)
1242 .to_string();
1243 match engine.state.primitives().get_name(&s) {
1244 None => (),
1245 Some(s) => engine.requeue(ET::Token::primitive(s))?,
1246 }
1247 Ok(())
1248}
1249
1250pub fn pdflastximage<ET: EngineTypes>(
1251 engine: &mut EngineReferences<ET>,
1252 _tk: ET::Token,
1253) -> TeXResult<ET::Int, ET>
1254where
1255 ET::Extension: PDFExtension<ET>,
1256{
1257 Ok((engine.aux.extension.pdfximages().len() as i32 - 1).into())
1258}
1259
1260pub fn pdfliteral<ET: EngineTypes>(
1261 engine: &mut EngineReferences<ET>,
1262 tk: ET::Token,
1263) -> TeXResult<(), ET>
1264where
1265 ET::Extension: PDFExtension<ET>,
1266 ET::CustomNode: From<PDFNode<ET>>,
1267{
1268 let _ = engine.read_keyword(b"shipout")?; let option = match engine.read_keywords(&[b"direct", b"page"])? {
1270 Some(b"direct") => PDFLiteralOption::Direct,
1271 Some(b"page") => PDFLiteralOption::Page,
1272 _ => PDFLiteralOption::None,
1273 };
1274 let mut literal = String::new();
1275 engine.read_braced_string(true, true, &tk, &mut literal)?;
1276 let node = PDFNode::PDFLiteral(PDFLiteral { literal, option });
1277 crate::add_node!(ET::Stomach;engine,VNode::Custom(node.into()),HNode::Custom(node.into()),MathNode::Custom(node.into()));
1278 Ok(())
1279}
1280
1281pub fn pdfshellescape<ET: EngineTypes>(
1282 _engine: &mut EngineReferences<ET>,
1283 _tk: ET::Token,
1284) -> TeXResult<ET::Int, ET> {
1285 Ok(<ET::Num as NumSet>::Int::from(2))
1286}
1287
1288pub fn pdfstrcmp<ET: EngineTypes>(
1289 engine: &mut EngineReferences<ET>,
1290 exp: &mut Vec<ET::Token>,
1291 tk: ET::Token,
1292) -> TeXResult<(), ET> {
1293 let mut first = String::new();
1294 let mut second = String::new();
1295 engine.read_braced_string(false, true, &tk, &mut first)?;
1296 engine.read_braced_string(false, true, &tk, &mut second)?;
1297 match first.cmp(&second) {
1298 std::cmp::Ordering::Less => {
1299 exp.push(ET::Token::from_char_cat(b'-'.into(), CommandCode::Other));
1300 exp.push(ET::Token::from_char_cat(b'1'.into(), CommandCode::Other));
1301 }
1302 std::cmp::Ordering::Equal => {
1303 exp.push(ET::Token::from_char_cat(b'0'.into(), CommandCode::Other));
1304 }
1305 std::cmp::Ordering::Greater => {
1306 exp.push(ET::Token::from_char_cat(b'1'.into(), CommandCode::Other));
1307 }
1308 }
1309 Ok(())
1310}
1311
1312pub fn pdffontsize<ET: EngineTypes>(
1313 engine: &mut EngineReferences<ET>,
1314 exp: &mut Vec<ET::Token>,
1315 tk: ET::Token,
1316) -> TeXResult<(), ET> {
1317 let dim = engine.read_font(false, &tk)?.get_at();
1318 let mut f = |t| exp.push(t);
1319 let mut t = Otherize::new(&mut f);
1320 write!(t, "{}", dim)?;
1321 Ok(())
1322}
1323
1324pub fn showstream<ET: EngineTypes>(
1326 engine: &mut EngineReferences<ET>,
1327 tk: ET::Token,
1328) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET>
1329where
1330 ET::Extension: PDFExtension<ET>,
1331 ET::CustomNode: From<PDFNode<ET>>,
1332{
1333 showstream_immediate::<ET>(engine, tk)?;
1334 Ok(None)
1335}
1336pub fn showstream_immediate<ET: EngineTypes>(
1337 engine: &mut EngineReferences<ET>,
1338 tk: ET::Token,
1339) -> TeXResult<(), ET>
1340where
1341 ET::Extension: PDFExtension<ET>,
1342 ET::CustomNode: From<PDFNode<ET>>,
1343{
1344 if engine
1345 .need_next(false, &tk)?
1346 .char_value()
1347 .map(|v| v.to_char())
1348 == Some('=')
1349 {
1350 let _ = engine.need_next(false, &tk)?;
1351 }
1352 Ok(())
1353}
1354
1355pub fn pdffontexpand<ET: EngineTypes>(
1356 engine: &mut EngineReferences<ET>,
1357 tk: ET::Token,
1358) -> TeXResult<(), ET> {
1359 let _ = engine.read_font(false, &tk)?;
1361 let _ = engine.read_int(false, &tk)?;
1362 let _ = engine.read_int(false, &tk)?;
1363 let _ = engine.read_int(false, &tk)?;
1364 engine.read_keyword(b"autoexpand")?;
1365 Ok(())
1366}
1367
1368pub fn partokenname<ET: EngineTypes>(
1369 engine: &mut EngineReferences<ET>,
1370 tk: ET::Token,
1371) -> TeXResult<(), ET>
1372where
1373 ET::Extension: PDFExtension<ET>,
1374 ET::CustomNode: From<PDFNode<ET>>,
1375{
1376 let CSOrActiveChar::Name(par) = engine.read_control_sequence(&tk)? else {
1377 return TeXResult::Err(TeXError::General(
1378 "expected control sequence after \\partokenname".to_string(),
1379 ));
1380 };
1381 engine.state.set_par_token(par);
1382 Ok(())
1383}
1384
1385const PRIMITIVE_INTS: &[&str] = &[
1386 "pdfadjustspacing",
1387 "pdfcompresslevel",
1388 "pdfdecimaldigits",
1389 "pdfdraftmode",
1390 "pdfgentounicode",
1391 "pdfminorversion",
1392 "pdfobjcompresslevel",
1393 "pdfoutput",
1394 "pdfpkresolution",
1395 "pdfprotrudechars",
1396 "pdfprependkern",
1397 "pdfappendkern",
1398 "tracingstacklevels",
1399 "partokencontext",
1400];
1401
1402const PRIMITIVE_DIMS: &[&str] = &[
1403 "pdfhorigin",
1404 "pdflinkmargin",
1405 "pdfpageheight",
1406 "pdfpagewidth",
1407 "pdfvorigin",
1408];
1409
1410const PRIMITIVE_TOKS: &[&str] = &["pdfpageresources"];
1411
1412pub fn register_pdftex_primitives<E: TeXEngine>(engine: &mut E)
1413where
1414 <E::Types as EngineTypes>::Extension: PDFExtension<E::Types>,
1415 <E::Types as EngineTypes>::CustomNode: From<PDFNode<E::Types>>,
1416 <E::Types as EngineTypes>::File: FileWithMD5,
1417 <E::Types as EngineTypes>::Font: FontWithLpRp,
1418{
1419 register_expandable(engine, "leftmarginkern", leftmarginkern);
1420 register_expandable(engine, "rightmarginkern", rightmarginkern);
1421 register_expandable(engine, "pdfcreationdate", pdfcreationdate);
1422 register_expandable(engine, "pdffilemoddate", pdffilemoddate);
1423 register_expandable(engine, "pdfescapestring", pdfescapestring);
1424 register_expandable(engine, "pdfescapename", pdfescapename);
1425 register_expandable(engine, "pdfescapehex", pdfescapehex);
1426 register_expandable(engine, "pdfunescapehex", pdfunescapehex);
1427 register_expandable(engine, "pdffilesize", pdffilesize);
1428 register_expandable(engine, "pdfmatch", pdfmatch);
1429 register_expandable(engine, "pdflastmatch", pdflastmatch);
1430 register_expandable(engine, "pdfstrcmp", pdfstrcmp);
1431 register_expandable(engine, "pdftexrevision", pdftexrevision);
1432 register_expandable(engine, "pdfmdfivesum", pdfmdfivesum);
1433 register_expandable(engine, "pdffontsize", pdffontsize);
1434
1435 register_int(engine, "pdftexversion", pdftexversion, None);
1436 register_int(engine, "pdfmajorversion", pdfmajorversion, None);
1437 register_int(engine, "pdfshellescape", pdfshellescape, None);
1438 register_int(engine, "pdfcolorstackinit", pdfcolorstackinit, None);
1439 register_int(engine, "lpcode", lpcode_get, Some(lpcode_set));
1440 register_int(engine, "rpcode", rpcode_get, Some(rpcode_set));
1441
1442 register_conditional(engine, "ifincsname", ifincsname);
1443 register_conditional(engine, "ifpdfabsdim", ifpdfabsdim);
1444 register_conditional(engine, "ifpdfabsnum", ifpdfabsnum);
1445 register_conditional(engine, "ifpdfprimitive", ifpdfprimitive);
1446
1447 register_unexpandable(engine, "pdfcatalog", CommandScope::Any, pdfcatalog);
1448 register_unexpandable(
1449 engine,
1450 "pdfglyphtounicode",
1451 CommandScope::Any,
1452 pdfglyphtounicode,
1453 );
1454 register_unexpandable(engine, "pdfcolorstack", CommandScope::Any, pdfcolorstack);
1455 register_unexpandable(engine, "pdfdest", CommandScope::Any, pdfdest);
1456 register_unexpandable(engine, "pdfinfo", CommandScope::Any, pdfinfo);
1457 register_unexpandable(engine, "pdfliteral", CommandScope::Any, pdfliteral);
1458 register_unexpandable(engine, "pdffontexpand", CommandScope::Any, pdffontexpand);
1459 register_unexpandable(engine, "pdfoutline", CommandScope::Any, pdfoutline);
1460 register_unexpandable(engine, "pdfstartlink", CommandScope::Any, pdfstartlink);
1461 register_unexpandable(engine, "pdfendlink", CommandScope::Any, pdfendlink);
1462 register_unexpandable(engine, "pdfsave", CommandScope::Any, pdfsave);
1463 register_unexpandable(engine, "pdfrestore", CommandScope::Any, pdfrestore);
1464 register_unexpandable(engine, "pdfsetmatrix", CommandScope::Any, pdfsetmatrix);
1465 register_unexpandable(engine, "pdfannot", CommandScope::Any, pdfannot);
1466
1467 register_whatsit(engine, "showstream", showstream, showstream_immediate, None);
1468 register_whatsit(engine, "pdfobj", pdfobj, pdfobj_immediate, None);
1469 register_whatsit(engine, "pdfxform", pdfxform, pdfxform_immediate, None);
1470 register_whatsit(
1471 engine,
1472 "pdfpageattr",
1473 |e, t| {
1474 pdfpageattr(e, t)?;
1475 Ok(None)
1476 },
1477 pdfpageattr,
1478 Some(|_, _| Ok(Vec::new())),
1479 );
1480 register_whatsit(
1481 engine,
1482 "pdfpagesattr",
1483 |e, t| {
1484 pdfpagesattr(e, t)?;
1485 Ok(None)
1486 },
1487 pdfpagesattr,
1488 Some(|_, _| Ok(Vec::new())),
1489 );
1490 register_unexpandable(engine, "partokenname", CommandScope::Any, partokenname);
1491 register_unexpandable(engine, "pdfrefobj", CommandScope::Any, pdfrefobj);
1492 register_int(engine, "pdflastobj", pdflastobj, None);
1493 register_unexpandable(engine, "pdfrefxform", CommandScope::Any, pdfrefxform);
1494 register_int(engine, "pdflastxform", pdflastxform, None);
1495 register_unexpandable(engine, "pdfximage", CommandScope::Any, pdfximage);
1496 register_unexpandable(engine, "pdfrefximage", CommandScope::Any, pdfrefximage);
1497 register_simple_expandable(engine, "pdfprimitive", pdfprimitive);
1498 register_int(engine, "pdflastximage", pdflastximage, None);
1499 register_int(engine, "pdflastannot", pdflastannot, None);
1500 register_simple_expandable(engine, "pdfsavepos", |_, _| Ok(()));
1501 register_int(
1502 engine,
1503 "pdflastxpos",
1504 |_, _| Ok(<E::Types as EngineTypes>::Int::default()),
1505 None,
1506 );
1507 register_int(
1508 engine,
1509 "pdflastypos",
1510 |_, _| Ok(<E::Types as EngineTypes>::Int::default()),
1511 None,
1512 );
1513
1514 register_primitive_int(engine, PRIMITIVE_INTS);
1515 register_primitive_dim(engine, PRIMITIVE_DIMS);
1516 register_primitive_toks(engine, PRIMITIVE_TOKS);
1517
1518 cmtodos!(engine, pdfelapsedtime, pdfresettimer);
1519
1520 cmtodo!(engine, efcode);
1521 cmtodo!(engine, knaccode);
1522 cmtodo!(engine, knbccode);
1523 cmtodo!(engine, knbscode);
1524 cmtodo!(engine, pdfadjustinterwordglue);
1525 cmtodo!(engine, pdfforcepagebox);
1526 cmtodo!(engine, pdfgamma);
1527 cmtodo!(engine, pdfimageapplygamma);
1528 cmtodo!(engine, pdfimagegamma);
1529 cmtodo!(engine, pdfimagehicolor);
1530 cmtodo!(engine, pdfimageresolution);
1531 cmtodo!(engine, pdfinclusioncopyfonts);
1532 cmtodo!(engine, pdfinclusionerrorlevel);
1533 cmtodo!(engine, pdfinfoomitdate);
1534 cmtodo!(engine, pdfomitcharset);
1535 cmtodo!(engine, pdfomitinfodict);
1536 cmtodo!(engine, pdfomitprocset);
1537 cmtodo!(engine, pdfpagebox);
1538 cmtodo!(engine, pdfsuppressptexinfo);
1539 cmtodo!(engine, pdfsuppresswarningdupdest);
1540 cmtodo!(engine, pdfsuppresswarningdupmap);
1541 cmtodo!(engine, pdfsuppresswarningpagegroup);
1542 cmtodo!(engine, pdftracingfonts);
1543 cmtodo!(engine, pdfuniqueresname);
1544 cmtodo!(engine, shbscode);
1545 cmtodo!(engine, stbscode);
1546 cmtodo!(engine, tagcode);
1547 cmtodo!(engine, pdflastlink);
1548 cmtodo!(engine, pdflastximagecolordepth);
1549 cmtodo!(engine, pdflastximagepages);
1550 cmtodo!(engine, pdfrandomseed);
1551 cmtodo!(engine, pdfretval);
1552 cmtodo!(engine, pdfdestmargin);
1553 cmtodo!(engine, pdfeachlinedepth);
1554 cmtodo!(engine, pdfeachlineheight);
1555 cmtodo!(engine, pdffirstlineheight);
1556 cmtodo!(engine, pdfignoreddimen);
1557 cmtodo!(engine, pdflastlinedepth);
1558 cmtodo!(engine, pdfpxdimen);
1559 cmtodo!(engine, pdfthreadmargin);
1560 cmtodo!(engine, pdfpkmode);
1561 cmtodo!(engine, pdffiledump);
1562 cmtodo!(engine, pdffontname);
1563 cmtodo!(engine, pdffontobjnum);
1564 cmtodo!(engine, pdfincludechars);
1565 cmtodo!(engine, pdfinsertht);
1566 cmtodo!(engine, pdfnormaldeviate);
1567 cmtodo!(engine, pdfpageref);
1568 cmtodo!(engine, pdftexbanner);
1569 cmtodo!(engine, pdfuniformdeviate);
1570 cmtodo!(engine, pdfxformname);
1571 cmtodo!(engine, pdfximagebbox);
1572
1573 cmtodo!(engine, letterspacefont);
1574 cmtodo!(engine, pdfcopyfont);
1575 cmtodo!(engine, pdfendthread);
1576 cmtodo!(engine, pdffakespace);
1577 cmtodo!(engine, pdffontattr);
1578 cmtodo!(engine, pdfinterwordspaceoff);
1579 cmtodo!(engine, pdfinterwordspaceon);
1580 cmtodo!(engine, pdfmapfile);
1581 cmtodo!(engine, pdfmapline);
1582 cmtodo!(engine, pdfnames);
1583 cmtodo!(engine, pdfnobuiltintounicode);
1584 cmtodo!(engine, pdfnoligatures);
1585 cmtodo!(engine, pdfrunninglinkoff);
1586 cmtodo!(engine, pdfrunninglinkon);
1587 cmtodo!(engine, pdfsetrandomseed);
1588 cmtodo!(engine, pdfspacefont);
1589 cmtodo!(engine, pdfthread);
1590 cmtodo!(engine, pdftrailer);
1591 cmtodo!(engine, pdftrailerid);
1592 cmtodo!(engine, pdfstartthread);
1593 cmtodo!(engine, quitvmode);
1594
1595 }