Skip to main content

tex_engine/pdflatex/
commands.rs

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    // todo
452    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    // todo
462    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                    // TODO
642                }
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                    // TODO
651                }
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    // TODO
687    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    }; // TODO use subcount
705    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                // TODO
898            }
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")?; // TODO
1269    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
1324// dummy
1325pub 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    // TODO
1360    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    /*
1596    register_conditional!(ifincsname,engine,(e,cmd) =>ifincsname::<ET>(e,&cmd));
1597    register_conditional!(ifpdfabsdim,engine,(e,cmd) =>ifpdfabsdim::<ET>(e,&cmd));
1598    register_conditional!(ifpdfabsnum,engine,(e,cmd) =>ifpdfabsnum::<ET>(e,&cmd));
1599    register_value_assign_int!(lpcode,engine);
1600    register_unexpandable!(pdfcatalog,engine,None,(e,cmd) =>pdfcatalog::<ET>(e,&cmd));
1601    register_unexpandable!(pdfcolorstack,engine,None,(e,cmd) =>pdfcolorstack::<ET>(e,&cmd));
1602    register_int!(pdfcolorstackinit,engine,(e,c) => pdfcolorstackinit::<ET>(e,&c));
1603    register_expandable!(pdfcreationdate,engine,(e,cmd,f) =>pdfcreationdate::<ET>(e,&cmd,f));
1604    register_unexpandable!(pdfdest,engine,None,(e,cmd) =>pdfdest::<ET>(e,&cmd));
1605    register_int!(pdfelapsedtime,engine,(e,c) => pdfelapsedtime::<ET>(e,&c));
1606    register_unexpandable!(pdfendlink,engine,None,(e,cmd) =>pdfendlink::<ET>(e,&cmd));
1607    register_expandable!(pdfescapestring,engine,(e,cmd,f) =>pdfescapestring::<ET>(e,&cmd,f));
1608    register_expandable!(pdffilesize,engine,(e,cmd,f) =>pdffilesize::<ET>(e,&cmd,f));
1609    register_unexpandable!(pdffontexpand,engine,None,(e,cmd) =>pdffontexpand::<ET>(e,&cmd));
1610    register_expandable!(pdffontsize,engine,(e,cmd,f) =>pdffontsize::<ET>(e,&cmd,f));
1611    register_unexpandable!(pdfglyphtounicode,engine,None,(e,cmd) =>pdfglyphtounicode::<ET>(e,&cmd));
1612    register_int!(pdflastobj,engine,(e,c) => pdflastobj::<ET>(e,&c));
1613    register_int!(pdflastxform,engine,(e,c) => pdflastxform::<ET>(e,&c));
1614    register_int!(pdflastximage,engine,(e,c) => pdflastximage::<ET>(e,&c));
1615    register_unexpandable!(pdfliteral,engine,None,(e,cmd) =>pdfliteral::<ET>(e,&cmd));
1616    register_int!(pdfmajorversion,engine,(_,c) => pdfmajorversion::<ET>(&c));
1617    register_expandable!(pdfmatch,engine,(e,cmd,f) =>pdfmatch::<ET>(e,&cmd,f));
1618    register_expandable!(pdfmdfivesum,engine,(e,cmd,f) =>pdfmdfivesum::<ET>(e,&cmd,f));
1619    register_unexpandable!(pdfobj,engine,None,(e,cmd) =>pdfobj::<ET>(e,&cmd));
1620    register_unexpandable!(pdfoutline,engine,None,(e,cmd) =>pdfoutline::<ET>(e,&cmd));
1621    register_whatsit!(pdfrefxform,engine,(e,cmd) =>pdfrefxform::<ET>(e,&cmd));
1622    register_unexpandable!(pdfrefximage,engine,None,(e,cmd) =>pdfrefximage::<ET>(e,&cmd));
1623    register_unexpandable!(pdfresettimer,engine,None,(e,cmd) =>pdfresettimer::<ET>(e,&cmd));
1624    register_unexpandable!(pdfrestore,engine,None,(e,cmd) => pdfrestore::<ET>(e,&cmd));
1625    register_unexpandable!(pdfsave,engine,None,(e,cmd) => pdfsave::<ET>(e,&cmd));
1626    register_unexpandable!(pdfsetmatrix,engine,None,(e,cmd) =>pdfsetmatrix::<ET>(e,&cmd));
1627    register_int!(pdfshellescape,engine,(_,c) => pdfshellescape::<ET>(&c));
1628    register_unexpandable!(pdfstartlink,engine,None,(e,cmd) =>pdfstartlink::<ET>(e,&cmd));
1629    register_expandable!(pdfstrcmp,engine,(e,cmd,f) =>pdfstrcmp::<ET>(e,&cmd,f));
1630    register_expandable!(pdftexrevision,engine,(e,c,f) =>pdftexrevision::<ET>(e,&c,f));
1631    register_int!(pdftexversion,engine,(_,c) => pdftexversion::<ET>(&c));
1632    register_unexpandable!(pdfxform,engine,None,(e,cmd) =>pdfxform::<ET>(e,&cmd));
1633    register_unexpandable!(pdfximage,engine,None,(e,cmd) =>pdfximage::<ET>(e,&cmd));
1634    register_value_assign_int!(rpcode,engine);
1635     */
1636}