Skip to main content

tex_engine/pdflatex/
commands.rs

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