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 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 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(); if target < Dim32(0) {
261 split = i;
262 break;
263 }
264 }
265 _ => {
266 target = target - (n.height() + n.depth()); 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}