Skip to main content

tex_engine/engine/stomach/
methods.rs

1use crate::commands::primitives::{PrimitiveIdentifier, PRIMITIVES};
2use crate::commands::{PrimitiveCommand, ResolvedToken, TeXCommand};
3use crate::engine::filesystem::{File, SourceReference};
4use crate::engine::fontsystem::Font;
5use crate::engine::gullet::Gullet;
6use crate::engine::mouth::Mouth;
7use crate::engine::state::{GroupType, State};
8use crate::engine::stomach::{Stomach, TeXMode};
9use crate::engine::{EngineAux, EngineReferences, EngineTypes};
10use crate::prelude::{Character, CommandCode, TokenList};
11use crate::tex::nodes::boxes::{BoxType, HBoxInfo, TeXBox, ToOrSpread, VBoxInfo};
12use crate::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
13use crate::tex::nodes::math::{
14    MathAtom, MathGroup, MathKernel, MathNode, MathNodeList, MathNodeListType, MathNucleus,
15    UnresolvedMathFontStyle,
16};
17use crate::tex::nodes::vertical::{VNode, VerticalNodeListType};
18use crate::tex::nodes::{BoxTarget, ListTarget, NodeList, NodeTrait};
19use crate::tex::numerics::Skip;
20use crate::tex::numerics::TeXDimen;
21use crate::tex::tokens::Token;
22use crate::utils::errors::{TeXError, TeXResult};
23
24#[doc(hidden)]
25#[macro_export]
26macro_rules! add_node {
27    ($S:ty;$engine:expr,$v:expr,$h:expr,$m:expr) => {
28        match $engine.stomach.data_mut().mode() {
29            TeXMode::Vertical | TeXMode::InternalVertical => <$S>::add_node_v($engine, $v)?,
30            TeXMode::Horizontal | TeXMode::RestrictedHorizontal => <$S>::add_node_h($engine, $h),
31            _ => <$S>::add_node_m($engine, $m),
32        }
33    };
34}
35
36/// inserts the `\afterassignment` [`Token`]
37pub fn insert_afterassignment<ET: EngineTypes>(engine: &mut EngineReferences<ET>) {
38    if let Some(t) = std::mem::take(engine.stomach.afterassignment()) {
39        engine.requeue(t).unwrap()
40    }
41}
42
43fn read_tokens<ET: EngineTypes, F: FnOnce(&mut EngineReferences<ET>, TokenList<ET::Token>)>(
44    engine: &mut EngineReferences<ET>,
45    token: ET::Token,
46    f: F,
47) -> TeXResult<(), ET> {
48    let mut tks = shared_vector::Vector::new();
49    engine.read_until_endgroup(&token, |_, _, t| {
50        tks.push(t);
51        Ok(())
52    })?;
53    f(engine, TokenList::from(tks));
54    Ok(())
55}
56
57/// Default implementation for [`Stomach::assign_toks_register`].
58pub fn assign_toks_register<ET: EngineTypes>(
59    engine: &mut EngineReferences<ET>,
60    token: ET::Token,
61    register: usize,
62    global: bool,
63) -> TeXResult<(), ET> {
64    let mut had_eq = false;
65    let cont = |engine: &mut EngineReferences<ET>, ls| {
66        engine
67            .state
68            .set_toks_register(engine.aux, register, ls, global);
69        insert_afterassignment(engine);
70    };
71    crate::expand_loop!(ET; engine,tk,
72        ResolvedToken::Tk{char,code} => match (char.try_into(),code) {
73            (_,CommandCode::Space) => (),
74            (Ok(b'='),CommandCode::Other) if !had_eq => {
75                if had_eq {
76                    TeXError::missing_begingroup(engine.aux,engine.state,engine.mouth)?;
77                    if let Some(a) = engine.gullet.get_align_data() {a.ingroups += 1}
78                    return read_tokens(engine,token,cont);
79                }
80                had_eq = true;
81            }
82            (_,CommandCode::BeginGroup) => {
83                return read_tokens(engine,token,cont);
84            }
85            _ => {
86                TeXError::missing_begingroup(engine.aux,engine.state,engine.mouth)?;
87                if let Some(a) = engine.gullet.get_align_data() {a.ingroups += 1}
88                return read_tokens(engine,token,cont);
89            }
90        }
91        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveToks})) => {
92            cont(engine,engine.state.get_primitive_tokens(*name).clone());
93            return Ok(())
94        }
95        ResolvedToken::Cmd(Some(TeXCommand::ToksRegister(u))) => {
96            cont(engine,engine.state.get_toks_register(*u).clone());
97            return Ok(())
98        }
99        _ => {
100            TeXError::missing_begingroup(engine.aux,engine.state,engine.mouth)?;
101            if let Some(a) = engine.gullet.get_align_data() {a.ingroups += 1}
102            return read_tokens(engine,token,cont);
103        }
104    );
105    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &token)?;
106    Ok(())
107}
108
109/// Default implementation for [`Stomach::assign_primitive_toks`].
110pub fn assign_primitive_toks<ET: EngineTypes>(
111    engine: &mut EngineReferences<ET>,
112    token: ET::Token,
113    name: PrimitiveIdentifier,
114    global: bool,
115) -> TeXResult<(), ET> {
116    let mut had_eq = false;
117    macro_rules! read {
118        () => {{
119            let mut tks = shared_vector::Vector::new();
120            if name == PRIMITIVES.output {
121                tks.push(ET::Token::from_char_cat(
122                    b'{'.into(),
123                    CommandCode::BeginGroup,
124                ));
125                engine.read_until_endgroup(&token, |_, _, t| {
126                    tks.push(t);
127                    Ok(())
128                })?;
129                tks.push(ET::Token::from_char_cat(b'}'.into(), CommandCode::EndGroup));
130            } else {
131                engine.read_until_endgroup(&token, |_, _, t| {
132                    tks.push(t);
133                    Ok(())
134                })?;
135            }
136            engine
137                .state
138                .set_primitive_tokens(engine.aux, name, TokenList::from(tks), global);
139            insert_afterassignment(engine);
140            return Ok(());
141        }};
142    }
143    crate::expand_loop!(ET; engine,tk,
144        ResolvedToken::Tk{char,code} => match (char.try_into(),code) {
145            (_,CommandCode::Space) => (),
146            (Ok(b'='),CommandCode::Other) if !had_eq => {
147                if had_eq {
148                    TeXError::missing_begingroup(engine.aux,engine.state,engine.mouth)?;
149                    if let Some(a) = engine.gullet.get_align_data() {a.ingroups += 1}
150                    read!();
151                }
152                had_eq = true;
153            }
154            (_,CommandCode::BeginGroup) => {
155                read!()
156            }
157            _ => {
158                TeXError::missing_begingroup(engine.aux,engine.state,engine.mouth)?;
159                if let Some(a) = engine.gullet.get_align_data() {a.ingroups += 1}
160                read!();
161            }
162        }
163        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveToks})) => {
164            let tks = engine.state.get_primitive_tokens(*name);
165            let tks = if *name == PRIMITIVES.output {
166                let mut ntk = shared_vector::Vector::new();
167                ntk.push(ET::Token::from_char_cat(b'{'.into(),CommandCode::BeginGroup));
168                ntk.extend_from_slice(tks.0.as_slice());
169                ntk.push(ET::Token::from_char_cat(b'}'.into(),CommandCode::EndGroup));
170                ntk.into()
171            } else {
172                tks.clone()
173            };
174            engine.state.set_primitive_tokens(engine.aux,*name,tks,global);
175            insert_afterassignment(engine);
176            return Ok(())
177        }
178        ResolvedToken::Cmd(Some(TeXCommand::ToksRegister(u))) => {
179            let tks = engine.state.get_toks_register(*u);
180            let tks = if name == PRIMITIVES.output {
181                let mut ntk = shared_vector::Vector::new();
182                ntk.push(ET::Token::from_char_cat(b'{'.into(),CommandCode::BeginGroup));
183                ntk.extend_from_slice(tks.0.as_slice());
184                ntk.push(ET::Token::from_char_cat(b'}'.into(),CommandCode::EndGroup));
185                ntk.into()
186            } else {
187                tks.clone()
188            };
189            engine.state.set_primitive_tokens(engine.aux,name,tks,global);
190            insert_afterassignment(engine);
191            return Ok(())
192        }
193        _ => {
194            TeXError::missing_begingroup(engine.aux,engine.state,engine.mouth)?;
195            if let Some(a) = engine.gullet.get_align_data() {a.ingroups += 1}
196            read!();
197        }
198    );
199    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &token)?;
200    Ok(())
201}
202
203pub(crate) fn add_box<ET: EngineTypes>(
204    engine: &mut EngineReferences<ET>,
205    bx: TeXBox<ET>,
206    target: BoxTarget<ET>,
207) -> TeXResult<(), ET> {
208    if target.is_some() {
209        target.call(engine, bx)?
210    } else {
211        match engine.stomach.data_mut().mode() {
212            TeXMode::Horizontal | TeXMode::RestrictedHorizontal => {
213                ET::Stomach::add_node_h(engine, HNode::Box(bx))
214            }
215            TeXMode::Vertical | TeXMode::InternalVertical => {
216                ET::Stomach::add_node_v(engine, VNode::Box(bx))?
217            }
218            TeXMode::InlineMath | TeXMode::DisplayMath => {
219                ET::Stomach::add_node_m(engine, bx.to_math())
220            }
221        }
222    }
223    Ok(())
224}
225
226/// Default implementation for [`Stomach::do_char`].
227pub fn do_char<ET: EngineTypes>(
228    engine: &mut EngineReferences<ET>,
229    token: ET::Token,
230    char: ET::Char,
231    code: CommandCode,
232) -> TeXResult<(), ET> {
233    match code {
234        CommandCode::EOF => (),
235        CommandCode::Space if engine.stomach.data_mut().mode().is_horizontal() => {
236            ET::Stomach::add_node_h(engine, HNode::Space)
237        }
238        CommandCode::Space => (),
239        CommandCode::BeginGroup if engine.stomach.data_mut().mode().is_math() => {
240            engine
241                .state
242                .push(engine.aux, GroupType::Math, engine.mouth.line_number());
243            engine
244                .stomach
245                .data_mut()
246                .open_lists
247                .push(NodeList::new_math(engine.mouth.start_ref()));
248        }
249        CommandCode::EndGroup if engine.stomach.data_mut().mode().is_math() => {
250            close_group_in_m(engine)?
251        }
252        CommandCode::BeginGroup => {
253            engine
254                .state
255                .push(engine.aux, GroupType::Simple, engine.mouth.line_number())
256        }
257        CommandCode::EndGroup => match engine.state.get_group_type() {
258            Some(GroupType::Simple) => engine.state.pop(engine.aux, engine.mouth),
259            Some(
260                GroupType::HBox | GroupType::Math | GroupType::MathChoice | GroupType::LeftRight,
261            ) => ET::Stomach::close_box(engine, BoxType::Horizontal)?,
262            Some(
263                GroupType::VBox
264                | GroupType::VCenter
265                | GroupType::VTop
266                | GroupType::Insert
267                | GroupType::VAdjust
268                | GroupType::Noalign,
269            ) => ET::Stomach::close_box(engine, BoxType::Vertical)?,
270            _ => engine.general_error("Extra }, or forgotten \\endgroup".to_string())?,
271        },
272        CommandCode::Other | CommandCode::Letter
273            if engine.stomach.data_mut().mode().is_horizontal() =>
274        {
275            do_word(engine, char)?
276        }
277        CommandCode::Other | CommandCode::Letter | CommandCode::MathShift
278            if engine.stomach.data_mut().mode().is_vertical() =>
279        {
280            ET::Stomach::open_paragraph(engine, token)
281        }
282        CommandCode::MathShift if engine.stomach.data_mut().mode().is_math() => close_math(engine)?,
283        CommandCode::MathShift => open_math(engine)?,
284        CommandCode::Other | CommandCode::Letter /*if engine.stomach.data_mut().mode().is_math()*/ => {
285            let code = engine.state.get_mathcode(char);
286            if code == 32768 {
287                engine.mouth.requeue(ET::Token::from_char_cat(char, CommandCode::Active));
288                return Ok(())
289            }
290            ET::Stomach::do_mathchar(engine, code, Some(token))
291        }
292        CommandCode::Superscript if engine.stomach.data_mut().mode().is_math() => {
293            do_superscript(engine, &token)?
294        }
295        CommandCode::Subscript if engine.stomach.data_mut().mode().is_math() => {
296            do_subscript(engine, &token)?
297        }
298        CommandCode::Escape
299        | CommandCode::Primitive
300        | CommandCode::Active
301        | CommandCode::Argument => unreachable!(),
302        CommandCode::AlignmentTab => engine.general_error(format!(
303            "Misplaced alignment tab character {}",
304            char.display()
305        ))?,
306        CommandCode::Parameter => {
307            let mode = engine.stomach.data_mut().mode();
308            engine.general_error(format!(
309                "You can't use `macro parameter character {}` in {} mode",
310                char.display(),
311                mode
312            ))?
313        }
314        CommandCode::Superscript | CommandCode::Subscript => {
315            TeXError::missing_dollar_inserted(engine.aux, engine.state, engine.mouth)?;
316            engine.mouth.requeue(token);
317            engine.mouth.requeue(ET::Token::from_char_cat(
318                b'$'.into(),
319                CommandCode::MathShift,
320            ));
321        }
322    }
323    Ok(())
324}
325
326#[allow(clippy::no_effect)]
327fn do_word<ET: EngineTypes>(
328    engine: &mut EngineReferences<ET>,
329    char: ET::Char,
330) -> TeXResult<(), ET> {
331    // TODO trace
332    let mut current = char;
333    macro_rules! char {
334        ($c:expr) => {{
335            let font = engine.state.get_current_font().clone();
336            match font.ligature(current, $c) {
337                Some(c) => {
338                    current = c;
339                }
340                None => {
341                    add_char::<ET>(engine.stomach, engine.state, current, font);
342                    current = $c;
343                }
344            }
345        }};
346    }
347
348    macro_rules! end {
349        ($e:expr) => {{
350            let font = engine.state.get_current_font().clone();
351            add_char::<ET>(engine.stomach, engine.state, current, font);
352            $e;
353            engine.stomach.data_mut().spacefactor = 1000;
354            return Ok(());
355        }};
356    }
357    crate::expand_loop!(ET;token => {
358        if token.is_primitive() == Some(PRIMITIVES.noexpand) { engine.get_next(false)?; continue}
359    };engine,
360        ResolvedToken::Tk { char, code:CommandCode::Letter|CommandCode::Other } =>
361            char!(char),
362        ResolvedToken::Cmd(Some(TeXCommand::Char {char,code:CommandCode::Letter|CommandCode::Other})) =>
363            char!(*char),
364        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,..})) if *name == PRIMITIVES.char => {
365            let char = engine.read_charcode(false,&token)?;
366            char!(char)
367        }
368        ResolvedToken::Tk { code:CommandCode::Space, .. } |
369        ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::Space,..})) =>
370            end!(ET::Stomach::add_node_h(engine,HNode::Space)),
371        ResolvedToken::Tk { char, code } =>
372            end!(ET::Stomach::do_char(engine,token,char,code)?),
373        ResolvedToken::Cmd(Some(TeXCommand::Char {char, code})) =>
374            end!(ET::Stomach::do_char(engine,token,*char,*code)?),
375        ResolvedToken::Cmd(None) => {
376            TeXError::undefined(engine.aux,engine.state,engine.mouth,&token)?;
377            end!(())
378        }
379        ResolvedToken::Cmd(Some(cmd)) => {
380            end!(crate::do_cmd!(ET;engine,token,cmd))
381        }
382    );
383    end!(())
384}
385
386fn add_char<ET: EngineTypes>(
387    slf: &mut ET::Stomach,
388    state: &ET::State,
389    char: ET::Char,
390    font: ET::Font,
391) {
392    let sf = state.get_sfcode(char);
393    let data = slf.data_mut();
394    if sf > 1000 && data.spacefactor < 1000 {
395        data.spacefactor = 1000;
396    } else {
397        data.spacefactor = sf as i32;
398    }
399    match slf.data_mut().open_lists.last_mut() {
400        Some(NodeList::Horizontal { children, .. }) => {
401            children.push(HNode::Char { char, font });
402        }
403        _ => unreachable!(),
404    }
405}
406
407fn open_math<ET: EngineTypes>(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
408    let (display, every) = match engine.stomach.data_mut().mode() {
409        TeXMode::Horizontal => {
410            match engine.get_next(false)? {
411                Some(tk) => {
412                    if tk.command_code() == CommandCode::MathShift {
413                        engine.stomach.data_mut().prevgraf = 3; // heuristic
414                        (true, PRIMITIVES.everydisplay)
415                    } else {
416                        engine.requeue(tk)?;
417                        (false, PRIMITIVES.everymath)
418                    }
419                }
420                None => return Err(TeXError::EmergencyStop),
421            }
422        }
423        _ => (false, PRIMITIVES.everymath),
424    };
425    engine.stomach.data_mut().open_lists.push(NodeList::Math {
426        children: MathNodeList::default(),
427        start: engine.mouth.start_ref(),
428        tp: MathNodeListType::Top { display },
429    });
430    engine.state.push(
431        engine.aux,
432        GroupType::MathShift { display },
433        engine.mouth.line_number(),
434    );
435    engine
436        .state
437        .set_primitive_int(engine.aux, PRIMITIVES.fam, (-1).into(), false);
438    engine.push_every(every);
439    Ok(())
440}
441
442fn close_math<ET: EngineTypes>(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
443    match engine.stomach.data_mut().open_lists.pop() {
444        Some(NodeList::Math {
445            children,
446            start,
447            tp: MathNodeListType::Top { display },
448        }) => {
449            if display {
450                engine.stomach.data_mut().prevgraf += 3;
451                match engine.get_next(false)? {
452                    Some(tk) if tk.command_code() == CommandCode::MathShift => (),
453                    _ => engine.general_error("Display math should end with $$".to_string())?,
454                }
455            }
456            let (children, eqno) = children.close(start, engine.mouth.current_sourceref());
457            let group = MathGroup::close(
458                engine.state,
459                if display {
460                    Some((
461                        engine.state.get_primitive_skip(PRIMITIVES.abovedisplayskip),
462                        engine.state.get_primitive_skip(PRIMITIVES.belowdisplayskip),
463                    ))
464                } else {
465                    None
466                },
467                start,
468                engine.mouth.current_sourceref(),
469                children,
470                eqno,
471            );
472            engine.state.pop(engine.aux, engine.mouth);
473            ET::Stomach::add_node_h(engine, HNode::MathGroup(group));
474        }
475        _ => engine.general_error("Unexpected end of math mode".to_string())?,
476    }
477    Ok(())
478}
479
480fn close_group_in_m<ET: EngineTypes>(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
481    let ls = engine.stomach.data_mut().open_lists.pop();
482    if !engine.stomach.data_mut().mode().is_math() {
483        return Err(TeXError::TooManyCloseBraces);
484    }
485    match ls {
486        Some(NodeList::Math {
487            children,
488            start,
489            tp: MathNodeListType::Target(t),
490        }) if t.is_some() => {
491            engine.state.pop(engine.aux, engine.mouth);
492            let (children, None) = children.close(start, engine.mouth.current_sourceref()) else {
493                unreachable!()
494            };
495            t.call(engine, children, start)
496        }
497        Some(NodeList::Math {
498            children,
499            start,
500            tp: MathNodeListType::Target(target),
501        }) => {
502            match children {
503                MathNodeList::Simple(v) => ET::Stomach::add_node_m(
504                    engine,
505                    MathNode::Atom(MathAtom {
506                        nucleus: MathNucleus::Inner(MathKernel::List {
507                            children: v.into(),
508                            start,
509                            end: engine.mouth.current_sourceref(),
510                        }),
511                        sub: None,
512                        sup: None,
513                    }),
514                ),
515                MathNodeList::Over {
516                    top,
517                    sep,
518                    bottom,
519                    left,
520                    right,
521                } => ET::Stomach::add_node_m(
522                    engine,
523                    MathNode::Over {
524                        start,
525                        end: engine.mouth.current_sourceref(),
526                        top: top.into(),
527                        bottom: bottom.into(),
528                        sep,
529                        left,
530                        right,
531                    },
532                ),
533                MathNodeList::EqNo { .. } => {
534                    engine.general_error("Extra }, or forgotten $".to_string())?;
535                    engine.stomach.data_mut().open_lists.push(NodeList::Math {
536                        children,
537                        tp: MathNodeListType::Target(target),
538                        start,
539                    });
540                }
541            }
542            engine.state.pop(engine.aux, engine.mouth);
543            Ok(())
544        }
545        _ => unreachable!(),
546    }
547}
548
549fn do_superscript<ET: EngineTypes>(
550    engine: &mut EngineReferences<ET>,
551    in_token: &ET::Token,
552) -> TeXResult<(), ET> {
553    do_xscript(engine, Script::Super, in_token)
554}
555
556fn do_subscript<ET: EngineTypes>(
557    engine: &mut EngineReferences<ET>,
558    in_token: &ET::Token,
559) -> TeXResult<(), ET> {
560    do_xscript(engine, Script::Sub, in_token)
561}
562
563pub(crate) enum Script {
564    Super,
565    Sub,
566}
567impl Script {
568    pub fn invalid<ET: EngineTypes>(&self, a: &MathAtom<ET, UnresolvedMathFontStyle<ET>>) -> bool {
569        match self {
570            Script::Super => a.sup.is_some(),
571            Script::Sub => a.sub.is_some(),
572        }
573    }
574    pub fn merge<ET: EngineTypes>(
575        self,
576        n: MathNode<ET, UnresolvedMathFontStyle<ET>>,
577        a: &mut MathAtom<ET, UnresolvedMathFontStyle<ET>>,
578    ) {
579        match self {
580            Script::Sub => a.sub = Some(vec![n].into()),
581            Script::Super => a.sup = Some(vec![n].into()),
582        }
583    }
584    pub fn tp<ET: EngineTypes>(&self) -> ListTarget<ET, MathNode<ET, UnresolvedMathFontStyle<ET>>> {
585        match self {
586            Script::Super => ListTarget::<ET, _>::new(|engine, children, _| {
587                if let Some(NodeList::Math { children: ch, .. }) =
588                    engine.stomach.data_mut().open_lists.last_mut()
589                {
590                    if let Some(MathNode::Atom(a)) = ch.list_mut().last_mut() {
591                        a.sup = Some(children.into());
592                        Ok(())
593                    } else {
594                        unreachable!()
595                    }
596                } else {
597                    unreachable!()
598                }
599            }),
600            _ => ListTarget::<ET, _>::new(|engine, children, _| {
601                if let Some(NodeList::Math { children: ch, .. }) =
602                    engine.stomach.data_mut().open_lists.last_mut()
603                {
604                    if let Some(MathNode::Atom(a)) = ch.list_mut().last_mut() {
605                        a.sub = Some(children.into());
606                        Ok(())
607                    } else {
608                        unreachable!()
609                    }
610                } else {
611                    unreachable!()
612                }
613            }),
614        }
615    }
616}
617
618fn do_xscript<ET: EngineTypes>(
619    engine: &mut EngineReferences<ET>,
620    script: Script,
621    in_token: &ET::Token,
622) -> TeXResult<(), ET> {
623    match engine.stomach.data_mut().open_lists.last_mut() {
624        Some(NodeList::Math { children, .. }) => match children.list_mut().last() {
625            Some(MathNode::Atom(a)) if script.invalid(a) => {
626                engine.general_error(format!(
627                    "Double {}script",
628                    match script {
629                        Script::Super => "super",
630                        Script::Sub => "sub",
631                    }
632                ))?;
633                return Ok(());
634            }
635            Some(MathNode::Atom(_)) => (),
636            _ => children.push(MathNode::Atom(MathAtom::empty())),
637        },
638        _ => unreachable!(),
639    }
640    engine.read_char_or_math_group(
641        in_token,
642        |script, engine, c| {
643            match engine.stomach.data_mut().open_lists.last_mut() {
644                Some(NodeList::Math { children, .. }) => match children.list_mut().last_mut() {
645                    Some(MathNode::Atom(a)) => script.merge(MathNode::Atom(c.to_atom()), a),
646                    _ => unreachable!(),
647                },
648                _ => unreachable!(),
649            }
650            Ok(())
651        },
652        |script| script.tp(),
653        script,
654    )
655}
656
657/// Default implementation for [`Stomach::close_box`].
658pub fn close_box<ET: EngineTypes>(
659    engine: &mut EngineReferences<ET>,
660    bt: BoxType,
661) -> TeXResult<(), ET> {
662    if let Some(NodeList::Horizontal {
663        tp: HorizontalNodeListType::Paragraph(_),
664        ..
665    }) = engine.stomach.data_mut().open_lists.last()
666    {
667        ET::Stomach::close_paragraph(engine)?
668    }
669    match engine.stomach.data_mut().open_lists.pop() {
670        Some(NodeList::Vertical {
671            children,
672            tp: VerticalNodeListType::VAdjust,
673        }) if bt == BoxType::Vertical => {
674            engine.state.pop(engine.aux, engine.mouth);
675            engine.stomach.data_mut().vadjusts.extend(children);
676        }
677        Some(NodeList::Vertical {
678            children,
679            tp: VerticalNodeListType::Insert(n),
680        }) if bt == BoxType::Vertical => {
681            engine.state.pop(engine.aux, engine.mouth);
682            match engine.stomach.data_mut().mode() {
683                TeXMode::Vertical => {
684                    ET::Stomach::add_node_v(engine, VNode::Insert(n, children.into()))?
685                }
686                TeXMode::Horizontal if engine.stomach.data_mut().open_lists.len() == 1 => {
687                    ET::Stomach::add_node_h(engine, HNode::Insert(n, children.into()))
688                }
689                _ => engine.stomach.data_mut().inserts.push((n, children.into())),
690            }
691        }
692        Some(NodeList::Vertical {
693            children,
694            tp: VerticalNodeListType::Box(info, start, target),
695        }) if bt == BoxType::Vertical => {
696            engine.state.pop(engine.aux, engine.mouth);
697            let bx = TeXBox::V {
698                children: children.into(),
699                info,
700                start,
701                end: engine.mouth.current_sourceref(),
702            };
703            bx.width();
704            bx.height();
705            bx.depth();
706            add_box(engine, bx, target)?
707        }
708        Some(NodeList::Vertical {
709            children,
710            tp: VerticalNodeListType::VCenter(start, scaled),
711        }) if bt == BoxType::Vertical => {
712            engine.state.pop(engine.aux, engine.mouth);
713            ET::Stomach::add_node_m(
714                engine,
715                MathNode::Atom(MathAtom {
716                    nucleus: MathNucleus::VCenter {
717                        children: children.into(),
718                        start,
719                        end: engine.mouth.current_sourceref(),
720                        scaled,
721                    },
722                    sub: None,
723                    sup: None,
724                }),
725            );
726        }
727        Some(NodeList::Horizontal {
728            children,
729            tp: HorizontalNodeListType::Box(info, start, target),
730        }) if bt == BoxType::Horizontal => {
731            engine.state.pop(engine.aux, engine.mouth);
732            let bx = TeXBox::H {
733                children: children.into(),
734                info,
735                start,
736                end: engine.mouth.current_sourceref(),
737                preskip: None,
738            };
739            bx.width();
740            bx.height();
741            bx.depth();
742            add_box(engine, bx, target)?
743        }
744        _ => unreachable!(),
745    }
746    match engine.stomach.data_mut().mode() {
747        TeXMode::Vertical => {
748            let data = engine.stomach.data_mut();
749            let inserts = std::mem::take(&mut data.inserts);
750            data.page
751                .extend(inserts.into_iter().map(|(a, b)| VNode::Insert(a, b)));
752            let adjusts = std::mem::take(&mut data.vadjusts);
753            data.page.extend(adjusts);
754        }
755        TeXMode::Horizontal => {
756            let adjusts = std::mem::take(&mut engine.stomach.data_mut().vadjusts);
757            ET::Stomach::add_node_h(engine, HNode::VAdjust(adjusts.into()))
758        }
759        _ => (),
760    }
761    Ok(())
762}
763
764/// Default implementation for [`Stomach::add_node_v`].
765pub fn add_node_v<ET: EngineTypes>(
766    engine: &mut EngineReferences<ET>,
767    mut node: VNode<ET>,
768) -> TeXResult<(), ET> {
769    let data = engine.stomach.data_mut();
770    let prevdepth = data.prevdepth;
771
772    if let VNode::HRule { .. } = node {
773        data.prevdepth = ET::Dim::from_sp(-65536000);
774    } else {
775        data.prevdepth = node.depth();
776    }
777
778    let ht = node.height();
779
780    let pre = match node {
781        VNode::Box(TeXBox::H {
782            ref mut preskip, ..
783        }) => {
784            if prevdepth > ET::Dim::from_sp(-65536000) {
785                let baselineskip = engine.state.get_primitive_skip(PRIMITIVES.baselineskip);
786                let lineskiplimit = engine.state.get_primitive_dim(PRIMITIVES.lineskiplimit);
787                let b = Skip::new(
788                    baselineskip.base - prevdepth - ht,
789                    baselineskip.stretch,
790                    baselineskip.shrink,
791                );
792                let sk = if b.base >= lineskiplimit {
793                    b
794                } else {
795                    engine.state.get_primitive_skip(PRIMITIVES.lineskip)
796                };
797                if sk != Skip::default() {
798                    *preskip = Some(sk);
799                    Some(sk)
800                } else {
801                    None
802                }
803            } else {
804                None
805            }
806        }
807        _ => None,
808    };
809
810    match data.open_lists.last_mut() {
811        Some(NodeList::Vertical { children, .. }) => {
812            children.push(node);
813            return Ok(());
814        }
815        Some(_) => unreachable!("add_node_v in non-vertical mode"),
816        _ => (),
817    }
818    if !data.page_contains_boxes
819    /*data.pagegoal == <<Self::ET as EngineTypes>::Dim as TeXDimen>::from_sp(i32::MAX)*/
820    {
821        match &node {
822            VNode::Box(_) | VNode::Insert(..) | VNode::HRule { .. } => {
823                //crate::debug_log!(debug => "Here: {} \n\n {}",node.readable(),engine.mouth.display_position());
824                data.page_contains_boxes = true;
825                data.pagegoal = engine.state.get_primitive_dim(PRIMITIVES.vsize);
826            }
827            n if n.discardable() => return Ok(()),
828            _ => (),
829        }
830    }
831
832    if let Some(pre) = pre {
833        data.pagetotal = data.pagetotal + pre.base;
834    }
835    data.pagetotal = data.pagetotal + ht + node.depth(); // ?
836    if let VNode::Penalty(i) = node {
837        data.lastpenalty = i;
838        if i <= -10000 {
839            if data.page_contains_boxes {
840                return ET::Stomach::maybe_do_output(engine, Some(i));
841            } else {
842                return Ok(());
843            }
844        }
845    }
846    let disc = node.discardable();
847    data.page.push(node);
848    if disc && data.lastpenalty < 1000 {
849        ET::Stomach::maybe_do_output(engine, None)
850    } else {
851        Ok(())
852    }
853}
854
855/// Default implementation for [`Stomach::do_output`].
856pub fn do_output<ET: EngineTypes>(
857    engine: &mut EngineReferences<ET>,
858    caused_penalty: Option<i32>,
859) -> TeXResult<(), ET> {
860    let data = engine.stomach.data_mut();
861    let page = std::mem::take(&mut data.page);
862    let goal = data.pagegoal;
863    data.pagetotal = <ET as EngineTypes>::Dim::default();
864    data.page_contains_boxes = false;
865    // TODO more precisely
866    engine
867        .state
868        .set_primitive_int(engine.aux, PRIMITIVES.badness, (10).into(), true);
869
870    let SplitResult {
871        mut first,
872        rest,
873        split_penalty,
874    } = match caused_penalty {
875        Some(p) => SplitResult {
876            first: page,
877            rest: vec![],
878            split_penalty: Some(p),
879        },
880        _ => ET::Stomach::split_vertical(engine, page, goal),
881    };
882
883    let split_penalty = split_penalty.unwrap_or(0);
884
885    engine
886        .state
887        .push(engine.aux, GroupType::Output, engine.mouth.line_number());
888
889    let data = engine.stomach.data_mut();
890
891    data.open_lists.push(NodeList::Vertical {
892        tp: VerticalNodeListType::Page,
893        children: vec![],
894    });
895
896    data.in_output = true;
897    data.deadcycles += 1;
898    /* INSERTS:
899        1. read vbox => \box n = b
900
901        g = \pagegoal
902        t = \pagetotal
903        q = \insertpenalties (accumulated for the current page)
904        d = \pagedepth (<= \maxdepth)
905        z = \pageshrink
906        x = b.height() + b.depth()
907        f = \count n / 1000
908
909        if (\box n is empty) {
910            g -= h*f + w
911        } else {}
912    */
913
914    let mut deletes = vec![];
915    for (i, b) in first.iter_mut().enumerate() {
916        if let VNode::Insert(n, v) = b {
917            let children: Box<[VNode<ET>]> = match engine.state.take_box_register(*n) {
918                Some(TeXBox::V { children, .. }) => {
919                    let mut c = children.into_vec();
920                    c.extend(std::mem::replace(v, vec![].into()).into_vec().into_iter());
921                    c.into()
922                }
923                _ => std::mem::replace(v, vec![].into()),
924            };
925            engine.state.set_box_register(
926                engine.aux,
927                *n,
928                Some(TeXBox::V {
929                    info: VBoxInfo::new_box(ToOrSpread::None),
930                    children,
931                    start: engine.mouth.current_sourceref(),
932                    end: engine.mouth.current_sourceref(),
933                }),
934                true,
935            );
936            deletes.push(i)
937        }
938    }
939    for (j, i) in deletes.into_iter().enumerate() {
940        first.remove(i - j);
941    }
942
943    engine.state.set_primitive_int(
944        engine.aux,
945        PRIMITIVES.outputpenalty,
946        split_penalty.into(),
947        true,
948    );
949
950    let bx = TeXBox::V {
951        children: first.into(),
952        info: VBoxInfo::new_box(ToOrSpread::None),
953        start: engine.mouth.current_sourceref(),
954        end: engine.mouth.current_sourceref(),
955    };
956
957    //crate::debug_log!(debug => "Here: {} \n\n {}",bx.readable(),engine.mouth.display_position());
958
959    engine
960        .state
961        .set_box_register(engine.aux, 255, Some(bx), false);
962
963    engine.push_every(PRIMITIVES.output);
964    engine.get_next(false).unwrap(); // '{':BeginGroup
965
966    //crate::debug_log!(debug => "Here: {} at {}",engine.mouth.display_position(),engine.preview());
967
968    let depth = engine.state.get_group_level();
969    loop {
970        let next = match engine.get_next(false)? {
971            Some(t) => t,
972            None => unreachable!(),
973        };
974        //println!("HERE: {}",engine.preview());
975        if engine.state.get_group_level() == depth && next.command_code() == CommandCode::EndGroup {
976            engine.state.pop(engine.aux, engine.mouth);
977            match engine.stomach.data_mut().open_lists.pop() {
978                Some(NodeList::Vertical {
979                    children,
980                    tp: VerticalNodeListType::Page,
981                }) => {
982                    engine.stomach.data_mut().pagegoal =
983                        <ET as EngineTypes>::Dim::from_sp(i32::MAX);
984                    for c in children {
985                        ET::Stomach::add_node_v(engine, c)?;
986                    }
987                    for r in rest.into_iter() {
988                        ET::Stomach::add_node_v(engine, r)?
989                    }
990                }
991                _ => unreachable!(),
992            }
993            engine.stomach.data_mut().in_output = false;
994            return Ok(());
995        }
996        if next.is_primitive() == Some(PRIMITIVES.noexpand) {
997            engine.get_next(false).unwrap();
998            continue;
999        }
1000        crate::expand!(ET;engine,next;
1001            ResolvedToken::Tk { char, code } => do_char(engine, next, char, code)?,
1002            ResolvedToken::Cmd(Some(TeXCommand::Char {char, code})) => do_char(engine, next, *char, *code)?,
1003            ResolvedToken::Cmd(None) => TeXError::undefined(engine.aux,engine.state,engine.mouth,&next)?,
1004            ResolvedToken::Cmd(Some(cmd)) => crate::do_cmd!(ET;engine,next,cmd)
1005        );
1006    }
1007}
1008
1009/// The result of splitting a vertical list
1010pub struct SplitResult<ET: EngineTypes> {
1011    /// The nodes before the split
1012    pub first: Vec<VNode<ET>>,
1013    /// The remaining nodes after the split
1014    pub rest: Vec<VNode<ET>>,
1015    /// The penalty at the split (currently not properly implemented)
1016    pub split_penalty: Option<i32>,
1017}
1018
1019/// Default implementation for [`Stomach::split_vertical`]. Not TeX accurate,
1020/// but should be good enough for most purposes, if we don't insinst on precise pagination
1021pub fn vsplit_roughly<ET: EngineTypes>(
1022    engine: &mut EngineReferences<ET>,
1023    mut nodes: Vec<VNode<ET>>,
1024    mut target: ET::Dim,
1025) -> SplitResult<ET> {
1026    let data = engine.stomach.data_mut();
1027    data.topmarks.clear();
1028    std::mem::swap(&mut data.botmarks, &mut data.topmarks);
1029    data.firstmarks.clear();
1030    data.splitfirstmarks.clear();
1031    data.splitbotmarks.clear();
1032    let mut split = nodes.len();
1033    let iter = nodes.iter().enumerate();
1034    for (i, n) in iter {
1035        match n {
1036            VNode::Mark(i, v) => {
1037                if !data.firstmarks.contains_key(i) {
1038                    data.firstmarks.insert(*i, v.clone());
1039                }
1040                data.botmarks.insert(*i, v.clone());
1041            }
1042            VNode::Insert(_, bx) => {
1043                target = target - bx.iter().map(|c| c.height() + c.depth()).sum(); // - n.depth() ?
1044                if target < ET::Dim::default() {
1045                    split = i;
1046                    break;
1047                }
1048            }
1049            _ => {
1050                target = target - (n.height() + n.depth()); // - n.depth() ?
1051                if target < ET::Dim::default() {
1052                    split = i;
1053                    break;
1054                }
1055            }
1056        }
1057    }
1058    let mut rest = nodes.split_off(split);
1059    let split_penalty = match rest.first() {
1060        Some(VNode::Penalty(p)) => {
1061            let p = *p;
1062            rest.drain(..1).next();
1063            Some(p)
1064        }
1065        _ => None,
1066    };
1067
1068    for n in &rest {
1069        if let VNode::Mark(i, v) = n {
1070            if !data.splitfirstmarks.contains_key(i) {
1071                data.splitfirstmarks.insert(*i, v.clone());
1072            }
1073            data.splitbotmarks.insert(*i, v.clone());
1074        }
1075    }
1076    SplitResult {
1077        first: nodes,
1078        rest,
1079        split_penalty,
1080    }
1081}
1082
1083/// Specification of a (target)line in a paragraph
1084#[derive(Debug, Clone, Eq, PartialEq)]
1085pub struct ParLineSpec<ET: EngineTypes> {
1086    /// `\leftskip`
1087    pub leftskip: Skip<ET::Dim>,
1088    /// `\rightskip`
1089    pub rightskip: Skip<ET::Dim>,
1090    /// The target width of the line
1091    pub target: ET::Dim,
1092}
1093impl<ET: EngineTypes> ParLineSpec<ET> {
1094    /// Creates the relevant [`ParLineSpec`]s from the current state, by looking
1095    /// at the current `\parshape`, `\hangindent`, `\hangafter`, `\hsize`,
1096    /// `\leftskip` and `\rightskip`.
1097    pub fn make(state: &mut ET::State, aux: &mut EngineAux<ET>) -> Vec<ParLineSpec<ET>> {
1098        let parshape = state.take_parshape(); // (left-indent,width)*
1099        let hangindent = state.get_primitive_dim(PRIMITIVES.hangindent); // left-indent
1100        let hangafter = state.get_primitive_int(PRIMITIVES.hangafter).into();
1101        state.set_primitive_int(aux, PRIMITIVES.hangafter, ET::Int::default(), false);
1102        state.set_primitive_dim(aux, PRIMITIVES.hangindent, ET::Dim::default(), false);
1103        let leftskip = state.get_primitive_skip(PRIMITIVES.leftskip);
1104        let rightskip = state.get_primitive_skip(PRIMITIVES.rightskip);
1105        let hsize = state.get_primitive_dim(PRIMITIVES.hsize);
1106        if parshape.is_empty() {
1107            if hangindent == ET::Dim::default() || hangafter == 0 {
1108                vec![ParLineSpec {
1109                    target: hsize + -(leftskip.base + rightskip.base),
1110                    leftskip,
1111                    rightskip,
1112                }]
1113            } else if hangafter < 0 {
1114                let mut r: Vec<ParLineSpec<ET>> = (0..-hangafter)
1115                    .map(|_| ParLineSpec {
1116                        target: hsize - (leftskip.base + rightskip.base + hangindent),
1117                        leftskip: leftskip + hangindent,
1118                        rightskip,
1119                    })
1120                    .collect();
1121                r.push(ParLineSpec {
1122                    target: hsize - (leftskip.base + rightskip.base),
1123                    leftskip,
1124                    rightskip,
1125                });
1126                r
1127            } else {
1128                let mut r: Vec<ParLineSpec<ET>> = (0..hangafter)
1129                    .map(|_| ParLineSpec {
1130                        target: hsize - (leftskip.base + rightskip.base),
1131                        leftskip,
1132                        rightskip,
1133                    })
1134                    .collect();
1135                r.push(ParLineSpec {
1136                    target: hsize - (leftskip.base + rightskip.base + hangindent),
1137                    leftskip: leftskip + hangindent,
1138                    rightskip,
1139                });
1140                r
1141            }
1142        } else {
1143            parshape
1144                .into_iter()
1145                .map(|(i, l)| {
1146                    let left = leftskip + i;
1147                    let target = l - (leftskip.base + rightskip.base);
1148                    let right = rightskip + (hsize - (l + i));
1149                    ParLineSpec {
1150                        leftskip: left,
1151                        rightskip: right,
1152                        target,
1153                    }
1154                })
1155                .collect()
1156        }
1157    }
1158}
1159
1160/// The result of breaking a paragraph into lines - either an actual line (horizontal box)
1161/// or vertical material inserted via `\vadjust`.
1162pub enum ParLine<ET: EngineTypes> {
1163    Line(TeXBox<ET>), //{contents:Vec<TeXNode<ET>>, broken_early:bool },
1164    Adjust(VNode<ET>),
1165}
1166
1167/// Rough implementation of paragraph breaking
1168pub fn split_paragraph_roughly<ET: EngineTypes>(
1169    engine: &mut EngineReferences<ET>,
1170    specs: Vec<ParLineSpec<ET>>,
1171    children: Vec<HNode<ET>>,
1172    start: SourceReference<<ET::File as File>::SourceRefID>,
1173) -> Vec<ParLine<ET>> {
1174    let mut ret: Vec<ParLine<ET>> = Vec::new();
1175    let mut hgoals = specs.into_iter();
1176    let mut nodes = children.into_iter();
1177    let mut line_spec = hgoals.next().unwrap();
1178    let mut target = line_spec.target;
1179    let mut currstart = start;
1180    let mut currend = currstart;
1181    let mut curr_height = ET::Dim::default();
1182    let mut curr_depth = ET::Dim::default();
1183    'A: loop {
1184        let mut line = vec![];
1185        let mut reinserts = vec![];
1186
1187        macro_rules! next_line {
1188            ($b:literal) => {
1189                if !line.is_empty() {
1190                    let start = std::mem::replace(&mut currstart, currend.clone());
1191                    ret.push(ParLine::Line(TeXBox::H {
1192                        children: line.into(),
1193                        start,
1194                        end: engine.mouth.current_sourceref(),
1195                        info: HBoxInfo::ParLine {
1196                            spec: line_spec.clone(),
1197                            ends_with_line_break: $b,
1198                            inner_height: std::mem::take(&mut curr_height),
1199                            inner_depth: std::mem::take(&mut curr_depth),
1200                        },
1201                        preskip: None,
1202                    }));
1203                }
1204                for c in reinserts {
1205                    ret.push(ParLine::Adjust(c));
1206                }
1207                match hgoals.next() {
1208                    None => (),
1209                    Some(e) => line_spec = e,
1210                }
1211                target = line_spec.target;
1212            };
1213        }
1214
1215        loop {
1216            match nodes.next() {
1217                None => {
1218                    if !line.is_empty() {
1219                        let start = std::mem::replace(&mut currstart, currend);
1220                        ret.push(ParLine::Line(TeXBox::H {
1221                            children: line.into(),
1222                            start,
1223                            end: currend,
1224                            info: HBoxInfo::ParLine {
1225                                spec: line_spec.clone(),
1226                                ends_with_line_break: false,
1227                                inner_height: std::mem::take(&mut curr_height),
1228                                inner_depth: std::mem::take(&mut curr_depth),
1229                            },
1230                            preskip: None,
1231                        }));
1232                    }
1233                    for c in reinserts {
1234                        ret.push(ParLine::Adjust(c));
1235                    }
1236                    break 'A;
1237                }
1238                Some(HNode::Mark(i, m)) => reinserts.push(VNode::Mark(i, m)),
1239                Some(HNode::Insert(n, ch)) => reinserts.push(VNode::Insert(n, ch)),
1240                Some(HNode::VAdjust(ls)) => reinserts.extend(ls.into_vec()),
1241                Some(HNode::MathGroup(
1242                    g @ MathGroup {
1243                        display: Some(_), ..
1244                    },
1245                )) => {
1246                    let ht = g.height();
1247                    let dp = g.depth();
1248                    let (a, b) = g.display.unwrap();
1249                    next_line!(false);
1250                    ret.push(ParLine::Line(TeXBox::H {
1251                        start: g.start,
1252                        end: g.end,
1253                        children: vec![HNode::MathGroup(g)].into(),
1254                        info: HBoxInfo::ParLine {
1255                            spec: line_spec.clone(),
1256                            ends_with_line_break: false,
1257                            inner_height: ht + dp + a.base + b.base,
1258                            inner_depth: ET::Dim::default(),
1259                        },
1260                        preskip: None,
1261                    }));
1262                    continue 'A;
1263                }
1264                Some(HNode::Penalty(i)) if i <= -10000 => {
1265                    next_line!(true); // TODO mark somehow
1266                    continue 'A;
1267                }
1268                Some(HNode::Penalty(_)) => (),
1269                Some(
1270                    n @ HNode::Space
1271                    | n @ HNode::Hss
1272                    | n @ HNode::HSkip(_)
1273                    | n @ HNode::HFil
1274                    | n @ HNode::HFill,
1275                ) if target <= ET::Dim::default() => {
1276                    line.push(n);
1277                    break;
1278                }
1279                Some(node) => {
1280                    if let Some((_, b)) = node.sourceref() {
1281                        currend = *b
1282                    }
1283                    target = target + (-node.width());
1284                    curr_height = curr_height.max(node.height());
1285                    curr_depth = curr_depth.max(node.depth());
1286                    line.push(node);
1287                }
1288            }
1289        }
1290        next_line!(false);
1291    }
1292    ret
1293}