Skip to main content

rustex_lib/engine/
stomach.rs

1use crate::engine::nodes::{LineSkip, RusTeXNode};
2use crate::engine::state::RusTeXState;
3use crate::engine::{Font, Refs, Res, Types};
4use tex_engine::commands::primitives::PRIMITIVES;
5use tex_engine::commands::{PrimitiveCommand, TeXCommand};
6use tex_engine::engine::filesystem::{File, SourceReference};
7use tex_engine::engine::mouth::Mouth;
8use tex_engine::engine::state::{GroupType, State};
9use tex_engine::engine::stomach::methods::{
10    ParLine, ParLineSpec, SplitResult, insert_afterassignment, split_paragraph_roughly,
11};
12use tex_engine::engine::stomach::{Stomach, StomachData};
13use tex_engine::engine::{EngineAux, EngineReferences, EngineTypes};
14use tex_engine::prelude::*;
15use tex_engine::tex::nodes::NodeList;
16use tex_engine::tex::nodes::NodeTrait;
17use tex_engine::tex::nodes::boxes::{BoxType, ToOrSpread};
18use tex_engine::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
19use tex_engine::tex::nodes::math::{MathAtom, MathNode, MathNucleus};
20use tex_engine::tex::nodes::vertical::{VNode, VerticalNodeListType};
21use tex_engine::tex::numerics::Dim32;
22use tex_engine::tex::numerics::Skip;
23use tex_engine::tex::numerics::TeXDimen;
24use tex_engine::tex::tokens::CompactToken;
25
26pub struct RusTeXStomach {
27    afterassignment: Option<CompactToken>,
28    data: StomachData<Types>,
29    prevent_shipout: bool,
30    pub continuous: bool,
31}
32impl Stomach<Types> for RusTeXStomach {
33    fn new(_aux: &mut EngineAux<Types>, _state: &mut RusTeXState) -> Self {
34        Self {
35            afterassignment: None,
36            data: StomachData::default(),
37            prevent_shipout: false,
38            continuous: false,
39        }
40    }
41
42    fn afterassignment(&mut self) -> &mut Option<CompactToken> {
43        &mut self.afterassignment
44    }
45
46    fn data_mut(&mut self) -> &mut StomachData<Types> {
47        &mut self.data
48    }
49
50    fn split_vertical(engine: Refs, nodes: Vec<VNode<Types>>, target: Dim32) -> SplitResult<Types> {
51        vsplit(engine, nodes, target)
52    }
53
54    fn assign_font(engine: Refs, _token: CompactToken, f: Font, global: bool) -> Res<()> {
55        let g = engine.state.get_group_level() == 0
56            || global
57            || engine.aux.extension.change_markers.is_empty();
58        tex_engine::add_node!(Self;engine,
59                       VNode::Custom(RusTeXNode::FontChange(f.clone(),g)),
60                       HNode::Custom(RusTeXNode::FontChange(f.clone(),g)),
61                       MathNode::Custom(RusTeXNode::FontChange(f.clone(),g))
62        );
63        if !g {
64            *engine.aux.extension.change_markers.last_mut().unwrap() += 1;
65        }
66        engine.state.set_current_font(engine.aux, f, global);
67        insert_afterassignment(engine);
68        Ok(())
69    }
70
71    fn close_box(engine: &mut EngineReferences<Types>, bt: BoxType) -> Res<()> {
72        let markers = std::mem::take(engine.aux.extension.change_markers.last_mut().unwrap());
73        for _ in 0..markers {
74            tex_engine::add_node!(Self;engine,
75                            VNode::Custom(RusTeXNode::FontChangeEnd),
76                            HNode::Custom(RusTeXNode::FontChangeEnd),
77                           MathNode::Custom(RusTeXNode::FontChangeEnd)
78            )
79        }
80        tex_engine::engine::stomach::methods::close_box(engine, bt)
81    }
82
83    fn split_paragraph(
84        engine: Refs,
85        specs: Vec<ParLineSpec<Types>>,
86        children: Vec<HNode<Types>>,
87        sourceref: SourceReference<<<Types as EngineTypes>::File as File>::SourceRefID>,
88    ) -> Res<()> {
89        if children.is_empty() {
90            return Ok(());
91        }
92        let ret = split_paragraph_roughly(engine, specs.clone(), children, sourceref.clone());
93        engine.stomach.prevent_shipout = true;
94        Self::add_node_v(
95            engine,
96            VNode::Custom(RusTeXNode::ParagraphBegin {
97                specs,
98                start: sourceref,
99                end: engine.mouth.current_sourceref(),
100                lineskip: LineSkip::get(engine.state),
101                parskip: engine.state.get_primitive_skip(PRIMITIVES.parskip),
102            }),
103        )?;
104        let mut redo = vec![];
105        for line in ret {
106            match line {
107                ParLine::Adjust(n) => redo.push(n),
108                ParLine::Line(bx) => Self::add_node_v(engine, VNode::Box(bx))?,
109            }
110        }
111        engine.stomach.prevent_shipout = false;
112        Self::add_node_v(engine, VNode::Custom(RusTeXNode::ParagraphEnd))?;
113        for r in redo.into_iter() {
114            Self::add_node_v(engine, r)?;
115        }
116        Ok(())
117    }
118    /*
119    fn add_node_v(engine: &mut EngineReferences<Types>, node: VNode<Types>) {
120        if engine.stomach.data.in_output
121        match node {
122            VNode::Custom(RusTeXNode::PageBegin | RusTeXNode::PageEnd) => (),
123            _ => add_node_v(engine,node)
124        }
125    }
126     */
127
128    fn maybe_do_output(engine: &mut EngineReferences<Types>, penalty: Option<i32>) -> Res<()> {
129        if engine.stomach.prevent_shipout {
130            return Ok(());
131        }
132        let continuous = engine.stomach.continuous;
133        let data = engine.stomach.data_mut();
134        if !data.in_output && data.open_lists.is_empty() && !data.page.is_empty() {
135            if continuous {
136                //data.pagegoal = <Types as EngineTypes>::Dim::from_sp(180224000);
137                //engine.state.set_primitive_dim(engine.aux,PRIMITIVES.vsize,data.pagegoal,true);
138                if data.page_contains_boxes
139                    && data.pagetotal > <Types as EngineTypes>::Dim::from_sp(6553600 * 5)
140                {
141                    do_shipout(engine, penalty.or(Some(-10000)), |_| ())?;
142                    engine.stomach.data_mut().page_contains_boxes = true;
143                    Ok(())
144                } else if penalty.is_some() {
145                    do_shipout(engine, penalty, |data| {
146                        data.page
147                            .push(VNode::VSkip(Skip::new(Dim32(655360), None, None)))
148                    })
149                } else {
150                    Ok(())
151                }
152            } else if data.pagetotal >= data.pagegoal || penalty.is_some() {
153                RusTeXStomach::do_output(engine, penalty)
154            } else {
155                Ok(())
156            }
157        } else {
158            Ok(())
159        }
160    }
161
162    fn open_align(engine: Refs, _inner: BoxType, between: BoxType) {
163        engine
164            .state
165            .push(engine.aux, GroupType::Align, engine.mouth.line_number());
166        engine
167            .stomach
168            .data_mut()
169            .open_lists
170            .push(if between == BoxType::Vertical {
171                NodeList::Vertical {
172                    tp: VerticalNodeListType::HAlign,
173                    children: vec![],
174                }
175            } else {
176                NodeList::Horizontal {
177                    tp: HorizontalNodeListType::VAlign,
178                    children: vec![],
179                }
180            });
181        let lineskip = LineSkip::get(engine.state);
182        Self::add_node_v(engine, VNode::Custom(RusTeXNode::HAlignBegin { lineskip })).unwrap()
183    }
184    fn close_align(engine: &mut EngineReferences<Types>) -> Res<()> {
185        Self::add_node_v(engine, VNode::Custom(RusTeXNode::HAlignEnd))?;
186        match engine.state.get_group_type() {
187            Some(GroupType::Align) => (),
188            _ => todo!("throw error"),
189        }
190        match engine.stomach.data_mut().open_lists.pop() {
191            Some(NodeList::Vertical {
192                children,
193                tp: VerticalNodeListType::HAlign,
194            }) => {
195                engine.state.pop(engine.aux, &mut engine.mouth);
196                match engine.stomach.data_mut().open_lists.last_mut() {
197                    Some(NodeList::Math { .. }) => {
198                        Self::add_node_m(
199                            engine,
200                            MathNode::Atom(MathAtom {
201                                nucleus: MathNucleus::VCenter {
202                                    children: children.into(),
203                                    start: engine.mouth.start_ref(),
204                                    end: engine.mouth.current_sourceref(),
205                                    scaled: ToOrSpread::None,
206                                },
207                                sup: None,
208                                sub: None,
209                            }),
210                        );
211                    }
212                    _ => {
213                        for c in children {
214                            Self::add_node_v(engine, c)?;
215                        }
216                    }
217                }
218            }
219            Some(NodeList::Horizontal {
220                children,
221                tp: HorizontalNodeListType::VAlign,
222            }) => {
223                engine.state.pop(engine.aux, &mut engine.mouth);
224                for c in children {
225                    Self::add_node_h(engine, c);
226                }
227            }
228            _ => todo!("throw error"),
229        };
230        Ok(())
231    }
232}
233
234pub fn vsplit(engine: Refs, mut nodes: Vec<VNode<Types>>, mut target: Dim32) -> SplitResult<Types> {
235    let data = engine.stomach.data_mut();
236    data.topmarks.clear();
237    std::mem::swap(&mut data.botmarks, &mut data.topmarks);
238    data.firstmarks.clear();
239    data.splitfirstmarks.clear();
240    data.splitbotmarks.clear();
241    let mut in_par = None;
242    let mut split = nodes.len();
243    let iter = nodes.iter().enumerate();
244    for (i, n) in iter {
245        match n {
246            VNode::Custom(r @ RusTeXNode::ParagraphBegin { .. }) => {
247                in_par = Some(r.clone());
248            }
249            VNode::Custom(RusTeXNode::ParagraphEnd) => {
250                in_par = None;
251            }
252            VNode::Mark(i, v) => {
253                if !data.firstmarks.contains_key(&i) {
254                    data.firstmarks.insert(*i, v.clone());
255                }
256                data.botmarks.insert(*i, v.clone());
257            }
258            VNode::Insert(_, bx) => {
259                target = target - bx.iter().map(|c| c.height() + c.depth()).sum(); // - n.depth() ?
260                if target < Dim32(0) {
261                    split = i;
262                    break;
263                }
264            }
265            _ => {
266                target = target - (n.height() + n.depth()); // - n.depth() ?
267                if target < Dim32(0) {
268                    split = i;
269                    break;
270                }
271            }
272        }
273    }
274    let mut rest = nodes.split_off(split);
275    if let Some(b) = in_par {
276        rest.insert(0, VNode::Custom(b));
277        nodes.push(VNode::Custom(RusTeXNode::ParagraphEnd));
278    }
279    let split_penalty = match rest.first() {
280        Some(VNode::Penalty(p)) => {
281            let p = *p;
282            rest.drain(..1).next();
283            Some(p)
284        }
285        _ => None,
286    };
287
288    for n in &rest {
289        match n {
290            VNode::Mark(i, v) => {
291                if !data.splitfirstmarks.contains_key(&i) {
292                    data.splitfirstmarks.insert(*i, v.clone());
293                }
294                data.splitbotmarks.insert(*i, v.clone());
295            }
296            _ => (),
297        }
298    }
299    SplitResult {
300        first: nodes,
301        rest,
302        split_penalty,
303    }
304}
305
306fn do_shipout<F: FnOnce(&mut StomachData<Types>)>(
307    engine: &mut EngineReferences<Types>,
308    penalty: Option<i32>,
309    f: F,
310) -> Res<()> {
311    macro_rules! set_empty {
312        ($id:ident) => {
313            engine.state.set_command(
314                &engine.aux,
315                engine.aux.extension.$id,
316                Some(TeXCommand::Macro(engine.aux.extension.empty.clone())),
317                true,
318            )
319        };
320    }
321    set_empty!(oddhead);
322    set_empty!(oddfoot);
323    set_empty!(evenhead);
324    set_empty!(evenfoot);
325    engine.state.set_command(
326        &engine.aux,
327        engine.aux.extension.mkboth,
328        Some(TeXCommand::Macro(engine.aux.extension.gobbletwo.clone())),
329        true,
330    );
331
332    let iffalse = TeXCommand::Primitive {
333        cmd: PrimitiveCommand::Conditional(tex_engine::commands::tex::iffalse::<Types>),
334        name: PRIMITIVES.iffalse,
335    };
336    engine.state.set_command(
337        &engine.aux,
338        engine.aux.extension.specialpage,
339        Some(iffalse),
340        true,
341    );
342    let data = engine.stomach.data_mut();
343    data.page.insert(0, VNode::Custom(RusTeXNode::PageBegin));
344    f(data);
345    data.page.push(VNode::Custom(RusTeXNode::PageEnd));
346    RusTeXStomach::do_output(engine, penalty)
347}