Skip to main content

rustex_lib/engine/
commands.rs

1use crate::engine::extension::CSS;
2use crate::engine::nodes::RusTeXNode;
3use crate::engine::stomach::RusTeXStomach;
4use crate::engine::{Refs, Res, Types, register_command};
5use crate::utils::{VecMap, VecSet};
6use tex_engine::add_node;
7use tex_engine::commands::primitives::{register_simple_expandable, register_unexpandable};
8use tex_engine::commands::{CommandScope, PrimitiveCommand};
9use tex_engine::engine::DefaultEngine;
10use tex_engine::engine::mouth::Mouth;
11use tex_engine::engine::state::State;
12use tex_engine::engine::stomach::Stomach;
13use tex_engine::prelude::*;
14use tex_engine::tex::nodes::horizontal::HNode;
15use tex_engine::tex::nodes::math::{
16    MathAtom, MathClass, MathKernel, MathNode, MathNucleus, UnresolvedMarkers,
17};
18use tex_engine::tex::nodes::vertical::VNode;
19use tex_engine::tex::nodes::{ListTarget, NodeList};
20use tex_engine::tex::tokens::CompactToken;
21use tex_engine::utils::errors::TeXError;
22
23pub fn register_primitives_preinit(engine: &mut DefaultEngine<Types>) {
24    register_simple_expandable(engine, CLOSE_FONT, close_font);
25    engine.state.register_primitive(
26        &mut engine.aux,
27        "rustexBREAK",
28        PrimitiveCommand::Unexpandable {
29            scope: CommandScope::Any,
30            apply: |_, _| {
31                println!("HERE!");
32                Ok(())
33            },
34        },
35    );
36}
37
38pub fn register_primitives_postinit(engine: &mut DefaultEngine<Types>) {
39    /*register_command(engine, true, "LaTeX", "",
40                     "L\\kern-.3em\\raise.5ex\\hbox{\\check@mathfonts\\fontsize\\sf@size\\z@\\math@fontsfalse\\selectfont A}\\kern-.15em\\TeX",
41                     true, false
42    );*/
43    crate::engine::pgf::register_pgf(engine);
44    register_unexpandable(
45        engine,
46        "rustex@annotateHTML",
47        CommandScope::Any,
48        annot_begin,
49    );
50    register_unexpandable(engine, "rustex@HTMLNode", CommandScope::Any, node_begin);
51    register_unexpandable(
52        engine,
53        "rustex@annotateHTMLEnd",
54        CommandScope::Any,
55        annot_end,
56    );
57    engine.state.register_primitive(
58        &mut engine.aux,
59        "if@rustex",
60        PrimitiveCommand::Conditional(tex_engine::commands::tex::iftrue),
61    );
62    register_unexpandable(
63        engine,
64        "rustex@addNamespaceAbbrev",
65        CommandScope::Any,
66        namespace,
67    );
68    register_unexpandable(engine, "rustex@addMeta", CommandScope::Any, meta);
69    register_unexpandable(engine, "rustex@annotateTop", CommandScope::Any, annot_top);
70    register_unexpandable(engine, "rustex@cssLink", CommandScope::Any, css_link);
71    register_unexpandable(engine, "rustex@cssLiteral", CommandScope::Any, css_literal);
72    register_unexpandable(
73        engine,
74        "rustex@HTMLLiteral",
75        CommandScope::Any,
76        html_literal,
77    );
78    register_unexpandable(
79        engine,
80        "rustex@annotateInvisible",
81        CommandScope::Any,
82        invisible_begin,
83    );
84    register_unexpandable(
85        engine,
86        "rustex@annotateInvisibleEnd",
87        CommandScope::Any,
88        invisible_end,
89    );
90    register_unexpandable(
91        engine,
92        "rustex@@underbrace",
93        CommandScope::MathOnly,
94        underbrace,
95    );
96    register_unexpandable(
97        engine,
98        "rustex@@overbrace",
99        CommandScope::MathOnly,
100        overbrace,
101    );
102    // if@rustex
103    // rustex@directHTML
104}
105
106fn html_literal(engine: Refs, token: CompactToken) -> Res<()> {
107    let mut lit = String::new();
108    engine.read_braced_string(true, true, &token, &mut lit)?;
109    let node = RusTeXNode::Literal(lit);
110    add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
111    Ok(())
112}
113fn css_link(engine: Refs, token: CompactToken) -> Res<()> {
114    let mut file = String::new();
115    engine.read_braced_string(true, true, &token, &mut file)?;
116    engine.aux.extension.css.insert(CSS::File(file));
117    Ok(())
118}
119fn css_literal(engine: Refs, token: CompactToken) -> Res<()> {
120    let mut literal = String::new();
121    engine.read_braced_string(true, true, &token, &mut literal)?;
122    engine.aux.extension.css.insert(CSS::Literal(literal));
123    Ok(())
124}
125
126fn namespace(engine: Refs, token: CompactToken) -> Res<()> {
127    let mut key = String::new();
128    engine.read_braced_string(true, true, &token, &mut key)?;
129    let mut value = String::new();
130    engine.read_braced_string(true, true, &token, &mut value)?;
131    engine.aux.extension.namespaces.insert(key, value);
132    Ok(())
133}
134
135fn meta(engine: Refs, token: CompactToken) -> Res<()> {
136    let mut attrs = VecMap::default();
137    let mut str = String::new();
138    engine.read_braced_string(true, true, &token, &mut str)?;
139    let mut s = str[..].trim();
140    while !s.is_empty() {
141        let key = if let Some(i) = s.find('=') {
142            let (n, v) = s.split_at(i);
143            s = v[1..].trim_start();
144            n.trim()
145        } else {
146            todo!()
147        };
148        let delim = if s.starts_with('"') {
149            s = &s[1..];
150            '"'
151        } else if s.starts_with('\'') {
152            s = &s[1..];
153            '\''
154        } else {
155            todo!()
156        };
157        let val = if let Some(i) = s.find(delim) {
158            let (v, r) = s.split_at(i);
159            s = r[1..].trim_start();
160            v.trim()
161        } else {
162            todo!()
163        };
164        attrs.insert(key.to_string(), val.to_string());
165    }
166    engine.aux.extension.metas.push(attrs);
167    Ok(())
168}
169
170fn annot_top(engine: Refs, token: CompactToken) -> Res<()> {
171    let mut str = String::new();
172    engine.read_braced_string(true, true, &token, &mut str)?;
173    let mut s = str[..].trim();
174    let top = &mut engine.aux.extension.top;
175    while !s.is_empty() {
176        let key = if let Some(i) = s.find('=') {
177            let (n, v) = s.split_at(i);
178            s = v[1..].trim_start();
179            n.trim()
180        } else {
181            todo!()
182        };
183        let delim = if s.starts_with('"') {
184            s = &s[1..];
185            '"'
186        } else if s.starts_with('\'') {
187            s = &s[1..];
188            '\''
189        } else {
190            todo!()
191        };
192        let val = if let Some(i) = s.find(delim) {
193            let (v, r) = s.split_at(i);
194            s = r[1..].trim_start();
195            v.trim()
196        } else {
197            todo!()
198        };
199        top.insert(key.to_string(), val.to_string());
200    }
201    Ok(())
202}
203
204fn parse_annotations(
205    orig: &str,
206) -> Res<(
207    VecMap<String, String>,
208    VecMap<String, String>,
209    VecSet<String>,
210)> {
211    let mut attrs = VecMap::default();
212    let mut styles = VecMap::default();
213    let mut classes = VecSet::default();
214    let mut s = orig.trim();
215    while !s.is_empty() {
216        let style = if s.starts_with("style:") {
217            s = &s[6..];
218            Some(true)
219        } else if s.starts_with("class:") {
220            s = &s[6..];
221            Some(false)
222        } else {
223            None
224        };
225        let key = if let Some(i) = s.find('=') {
226            let (n, v) = s.split_at(i);
227            s = v[1..].trim_start();
228            n.trim()
229        } else {
230            return Err(TeXError::General(format!("Invalid annotation: {orig}")));
231        };
232        let delim = if s.starts_with('"') {
233            s = &s[1..];
234            '"'
235        } else if s.starts_with('\'') {
236            s = &s[1..];
237            '\''
238        } else {
239            return Err(TeXError::General(format!("Invalid annotation: {orig}")));
240        };
241        let val = if let Some(i) = s.find(delim) {
242            let (v, r) = s.split_at(i);
243            s = r[1..].trim_start();
244            v.trim()
245        } else {
246            return Err(TeXError::General(format!("Invalid annotation: {orig}")));
247        };
248        if style == Some(true) {
249            styles.insert(key.to_string(), val.to_string());
250        } else if style == Some(false) {
251            if !val.is_empty() {
252                return Err(TeXError::General("Invalid class annotation".to_string()));
253            }
254            classes.insert(key.to_string());
255        } else {
256            attrs.insert(key.to_string(), val.to_string());
257        }
258    }
259    Ok((attrs, styles, classes))
260}
261
262fn node_begin(engine: Refs, token: CompactToken) -> Res<()> {
263    let start = engine.mouth.start_ref();
264    let mut tag = String::new();
265    engine.read_braced_string(true, true, &token, &mut tag)?;
266    let mut str = String::new();
267    engine.read_braced_string(true, true, &token, &mut str)?;
268    let (attrs, styles, classes) = parse_annotations(&str)?;
269    let node = RusTeXNode::AnnotBegin {
270        attrs,
271        styles,
272        start,
273        classes,
274        tag: Some(tag),
275    };
276    add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
277    Ok(())
278}
279fn invisible_begin(engine: Refs, _token: CompactToken) -> Res<()> {
280    let node = RusTeXNode::InvisibleBegin;
281    add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
282    Ok(())
283}
284fn invisible_end(engine: Refs, _token: CompactToken) -> Res<()> {
285    let node = RusTeXNode::InvisibleEnd;
286    add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
287    Ok(())
288}
289fn annot_begin(engine: Refs, token: CompactToken) -> Res<()> {
290    let start = engine.mouth.start_ref();
291    let mut str = String::new();
292    engine.read_braced_string(true, true, &token, &mut str)?;
293    let (attrs, styles, classes) = parse_annotations(&str)?;
294    let node = RusTeXNode::AnnotBegin {
295        attrs,
296        styles,
297        start,
298        classes,
299        tag: None,
300    };
301    add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
302    Ok(())
303}
304fn annot_end(engine: Refs, _token: CompactToken) -> Res<()> {
305    let node = RusTeXNode::AnnotEnd(engine.mouth.current_sourceref());
306    add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
307    Ok(())
308}
309
310pub const CLOSE_FONT: &str = "!\"$%&/(closefont)\\&%$\"!";
311pub fn close_font(engine: Refs, _token: CompactToken) -> Res<()> {
312    match engine.stomach.data_mut().open_lists.last_mut() {
313        Some(NodeList::Vertical { children, .. }) => match children.last() {
314            Some(VNode::Custom(RusTeXNode::FontChange(_, _))) => {
315                children.pop();
316            }
317            _ => children.push(VNode::Custom(RusTeXNode::FontChangeEnd)),
318        },
319        Some(NodeList::Horizontal { children, .. }) => match children.last() {
320            Some(HNode::Custom(RusTeXNode::FontChange(_, _))) => {
321                children.pop();
322            }
323            _ => children.push(HNode::Custom(RusTeXNode::FontChangeEnd)),
324        },
325        Some(NodeList::Math { children, .. }) => match children.list_mut().last() {
326            Some(MathNode::Custom(RusTeXNode::FontChange(_, _))) => {
327                children.list_mut().pop();
328            }
329            _ => children.push(MathNode::Custom(RusTeXNode::FontChangeEnd)),
330        },
331        _ => engine
332            .stomach
333            .data_mut()
334            .page
335            .push(VNode::Custom(RusTeXNode::FontChangeEnd)),
336    }
337    Ok(())
338}
339
340// ------------------------------------------------------------------
341
342fn underbrace(engine: Refs, token: CompactToken) -> Res<()> {
343    const LITERAL: &str = "<mo stretchy=\"true\">⏟</mo>";
344    //engine.state.push(engine.aux, GroupType::Math, engine.mouth.line_number());
345    engine.read_char_or_math_group(
346        &token,
347        |(), engine, char| {
348            let node = MathNode::Atom(MathAtom {
349                sup: None,
350                sub: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
351                    LITERAL.to_string(),
352                ))])),
353                nucleus: MathNucleus::Simple {
354                    cls: char.cls,
355                    kernel: MathKernel::Char {
356                        char: char.char,
357                        style: char.style,
358                    },
359                    limits: Some(true),
360                },
361            });
362            let node = MathNode::Atom(MathAtom {
363                sup: None,
364                sub: None,
365                nucleus: MathNucleus::Simple {
366                    cls: MathClass::Ord,
367                    limits: Some(true),
368                    kernel: MathKernel::List {
369                        start: engine.mouth.current_sourceref(),
370                        end: engine.mouth.current_sourceref(),
371                        children: Box::new([node]),
372                    },
373                },
374            });
375            //engine.state.pop(engine.aux, engine.mouth);
376            RusTeXStomach::add_node_m(engine, node);
377            Ok(())
378        },
379        |()| {
380            ListTarget::<Types, _>::new(|engine, mut children, rf| {
381                children.insert(0, MathNode::Marker(UnresolvedMarkers::Display));
382                let node = MathNode::Atom(MathAtom {
383                    sup: None,
384                    sub: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
385                        LITERAL.to_string(),
386                    ))])),
387                    nucleus: MathNucleus::Simple {
388                        cls: MathClass::Ord,
389                        limits: Some(true),
390                        kernel: MathKernel::List {
391                            start: rf,
392                            children: children.into(),
393                            end: engine.mouth.current_sourceref(),
394                        },
395                    },
396                });
397                let node = MathNode::Atom(MathAtom {
398                    sup: None,
399                    sub: None,
400                    nucleus: MathNucleus::Simple {
401                        cls: MathClass::Ord,
402                        limits: Some(true),
403                        kernel: MathKernel::List {
404                            start: engine.mouth.current_sourceref(),
405                            end: engine.mouth.current_sourceref(),
406                            children: Box::new([node]),
407                        },
408                    },
409                });
410                RusTeXStomach::add_node_m(engine, node);
411                Ok(())
412            })
413        },
414        (),
415    )
416}
417fn overbrace(engine: Refs, token: CompactToken) -> Res<()> {
418    const LITERAL: &str = "<mo stretchy=\"true\">⏞</mo>";
419    RusTeXStomach::add_node_m(engine, MathNode::Marker(UnresolvedMarkers::Display));
420    engine.read_char_or_math_group(
421        &token,
422        |(), engine, char| {
423            let node = MathNode::Atom(MathAtom {
424                sub: None,
425                sup: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
426                    LITERAL.to_string(),
427                ))])),
428                nucleus: MathNucleus::Simple {
429                    cls: char.cls,
430                    kernel: MathKernel::Char {
431                        char: char.char,
432                        style: char.style,
433                    },
434                    limits: Some(true),
435                },
436            });
437            let node = MathNode::Atom(MathAtom {
438                sup: None,
439                sub: None,
440                nucleus: MathNucleus::Simple {
441                    cls: MathClass::Ord,
442                    limits: Some(true),
443                    kernel: MathKernel::List {
444                        start: engine.mouth.current_sourceref(),
445                        end: engine.mouth.current_sourceref(),
446                        children: Box::new([node]),
447                    },
448                },
449            });
450            RusTeXStomach::add_node_m(engine, node);
451            Ok(())
452        },
453        |()| {
454            ListTarget::<Types, _>::new(|engine, mut children, rf| {
455                children.insert(0, MathNode::Marker(UnresolvedMarkers::Display));
456                let node = MathNode::Atom(MathAtom {
457                    sub: None,
458                    sup: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
459                        LITERAL.to_string(),
460                    ))])),
461                    nucleus: MathNucleus::Simple {
462                        cls: MathClass::Ord,
463                        limits: Some(true),
464                        kernel: MathKernel::List {
465                            start: rf,
466                            children: children.into(),
467                            end: engine.mouth.current_sourceref(),
468                        },
469                    },
470                });
471                let node = MathNode::Atom(MathAtom {
472                    sup: None,
473                    sub: None,
474                    nucleus: MathNucleus::Simple {
475                        cls: MathClass::Ord,
476                        limits: Some(true),
477                        kernel: MathKernel::List {
478                            start: engine.mouth.current_sourceref(),
479                            end: engine.mouth.current_sourceref(),
480                            children: Box::new([node]),
481                        },
482                    },
483                });
484                RusTeXStomach::add_node_m(engine, node);
485                Ok(())
486            })
487        },
488        (),
489    )
490}