1pub mod methods;
2
3use crate::commands::primitives::{PrimitiveIdentifier, PRIMITIVES};
4use crate::commands::TeXCommand;
5use crate::commands::{CharOrPrimitive, CommandScope, PrimitiveCommand, ResolvedToken};
6use crate::engine::filesystem::File;
7use crate::engine::filesystem::SourceReference;
8use crate::engine::gullet::Gullet;
9use crate::engine::mouth::Mouth;
10use crate::engine::state::{GroupType, State};
11use crate::engine::stomach::methods::{ParLine, ParLineSpec, SplitResult};
12use crate::engine::utils::outputs::Outputs;
13use crate::engine::{EngineAux, EngineReferences, EngineTypes};
14use crate::tex::catcodes::CommandCode;
15use crate::tex::nodes::boxes::{BoxInfo, BoxType, HBoxInfo, TeXBox, ToOrSpread};
16use crate::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
17use crate::tex::nodes::math::{
18 Delimiter, MathAtom, MathChar, MathClass, MathKernel, MathNode, MathNodeList, MathNodeListType,
19 MathNucleus, UnresolvedMathFontStyle,
20};
21use crate::tex::nodes::vertical::{VNode, VerticalNodeListType};
22use crate::tex::nodes::{BoxTarget, ListTarget, NodeList, WhatsitFunction, WhatsitNode};
23use crate::tex::numerics::{Skip, TeXDimen};
24use crate::tex::tokens::token_lists::TokenList;
25use crate::tex::tokens::{StandardToken, Token};
26use crate::utils::errors::{TeXError, TeXResult};
27use crate::utils::HMap;
28use either::Either;
29use std::fmt::Display;
30
31#[derive(Clone, Copy, Eq, PartialEq, Debug)]
33pub enum TeXMode {
34 Vertical,
36 InternalVertical,
38 Horizontal,
40 RestrictedHorizontal,
42 InlineMath,
44 DisplayMath,
46}
47impl Display for TeXMode {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 TeXMode::Vertical => write!(f, "vertical"),
51 TeXMode::InternalVertical => write!(f, "internal vertical"),
52 TeXMode::Horizontal => write!(f, "horizontal"),
53 TeXMode::RestrictedHorizontal => write!(f, "restricted horizontal"),
54 TeXMode::InlineMath => write!(f, "inline math"),
55 TeXMode::DisplayMath => write!(f, "display math"),
56 }
57 }
58}
59impl TeXMode {
60 pub fn is_vertical(&self) -> bool {
62 matches!(self, TeXMode::Vertical | TeXMode::InternalVertical)
63 }
64 pub fn is_horizontal(&self) -> bool {
66 matches!(self, TeXMode::Horizontal | TeXMode::RestrictedHorizontal)
67 }
68 pub fn is_math(&self) -> bool {
70 matches!(self, TeXMode::InlineMath | TeXMode::DisplayMath)
71 }
72 pub fn h_or_m(&self) -> bool {
74 matches!(
75 self,
76 TeXMode::Horizontal
77 | TeXMode::RestrictedHorizontal
78 | TeXMode::InlineMath
79 | TeXMode::DisplayMath
80 )
81 }
82}
83impl From<BoxType> for TeXMode {
84 fn from(bt: BoxType) -> Self {
85 match bt {
86 BoxType::Horizontal => TeXMode::RestrictedHorizontal,
87 BoxType::Vertical => TeXMode::InternalVertical,
88 }
89 }
90}
91
92pub trait Stomach<ET: EngineTypes > {
100 fn new(aux: &mut EngineAux<ET>, state: &mut ET::State) -> Self;
102 fn afterassignment(&mut self) -> &mut Option<ET::Token>;
104 fn data_mut(&mut self) -> &mut StomachData<ET>;
106 #[inline]
108 fn every_top(engine: &mut EngineReferences<ET>) {
109 engine.mouth.update_start_ref();
110 }
111 fn flush(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
113 use crate::engine::utils::outputs::Outputs;
114 let open_groups = std::mem::take(&mut engine.stomach.data_mut().open_lists);
115 if !open_groups.is_empty() {
116 engine.aux.outputs.message(format_args!(
117 "(\\end occurred inside a group at level {})",
118 engine.state.get_group_level()
119 ));
120 if let Some(g) = engine.state.get_group_type() {
121 engine.aux.outputs.message(format_args!("## {} group", g))
122 }
123 while engine.state.get_group_level() > 0 {
124 engine.state.pop(engine.aux, engine.mouth);
125 }
126 }
127 Self::add_node_v(engine, VNode::Penalty(-10000))?;
128 engine.stomach.data_mut().page.clear();
129 Ok(())
130 }
131 fn do_unexpandable(
133 engine: &mut EngineReferences<ET>,
134 name: PrimitiveIdentifier,
135 scope: CommandScope,
136 token: ET::Token,
137 apply: fn(&mut EngineReferences<ET>, ET::Token) -> TeXResult<(), ET>,
138 ) -> TeXResult<(), ET> {
139 if Self::maybe_switch_mode(engine, scope, token.clone(), name)? {
140 engine.trace_command(|engine| name.display(engine.state.get_escape_char()));
141 apply(engine, token)
142 } else {
143 Ok(())
144 }
145 }
146
147 fn do_assignment(
149 engine: &mut EngineReferences<ET>,
150 name: PrimitiveIdentifier,
151 token: ET::Token,
152 assign: fn(&mut EngineReferences<ET>, ET::Token, bool) -> TeXResult<(), ET>,
153 global: bool,
154 ) -> TeXResult<(), ET> {
155 engine.trace_command(|engine| name.display(engine.state.get_escape_char()));
156 assign(engine, token, global)?;
157 methods::insert_afterassignment(engine);
158 Ok(())
159 }
160
161 fn assign_font(
163 engine: &mut EngineReferences<ET>,
164 _token: ET::Token,
165 f: ET::Font,
166 global: bool,
167 ) -> TeXResult<(), ET> {
168 engine.state.set_current_font(engine.aux, f, global);
169 methods::insert_afterassignment(engine);
170 Ok(())
171 }
172 fn assign_int_register(
174 engine: &mut EngineReferences<ET>,
175 register: usize,
176 global: bool,
177 in_token: ET::Token,
178 ) -> TeXResult<(), ET> {
179 let val = engine.read_int(true, &in_token)?;
180 engine
181 .state
182 .set_int_register(engine.aux, register, val, global);
183 methods::insert_afterassignment(engine);
184 Ok(())
185 }
186 fn assign_dim_register(
188 engine: &mut EngineReferences<ET>,
189 register: usize,
190 global: bool,
191 in_token: ET::Token,
192 ) -> TeXResult<(), ET> {
193 let val = engine.read_dim(true, &in_token)?;
194 engine
195 .state
196 .set_dim_register(engine.aux, register, val, global);
197 methods::insert_afterassignment(engine);
198 Ok(())
199 }
200 fn assign_skip_register(
202 engine: &mut EngineReferences<ET>,
203 register: usize,
204 global: bool,
205 in_token: ET::Token,
206 ) -> TeXResult<(), ET> {
207 let val = engine.read_skip(true, &in_token)?;
208 engine
209 .state
210 .set_skip_register(engine.aux, register, val, global);
211 methods::insert_afterassignment(engine);
212 Ok(())
213 }
214 fn assign_muskip_register(
216 engine: &mut EngineReferences<ET>,
217 register: usize,
218 global: bool,
219 in_token: ET::Token,
220 ) -> TeXResult<(), ET> {
221 let val = engine.read_muskip(true, &in_token)?;
222 engine
223 .state
224 .set_muskip_register(engine.aux, register, val, global);
225 methods::insert_afterassignment(engine);
226 Ok(())
227 }
228 #[inline]
230 fn assign_toks_register(
231 engine: &mut EngineReferences<ET>,
232 token: ET::Token,
233 register: usize,
234 global: bool,
235 ) -> TeXResult<(), ET> {
236 methods::assign_toks_register(engine, token, register, global)
237 }
238 #[inline]
240 fn assign_primitive_toks(
241 engine: &mut EngineReferences<ET>,
242 token: ET::Token,
243 name: PrimitiveIdentifier,
244 global: bool,
245 ) -> TeXResult<(), ET> {
246 methods::assign_primitive_toks(engine, token, name, global)
247 }
248 fn assign_primitive_int(
250 engine: &mut EngineReferences<ET>,
251 name: PrimitiveIdentifier,
252 global: bool,
253 in_token: ET::Token,
254 ) -> TeXResult<(), ET> {
255 engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
256 let val = engine.read_int(true, &in_token)?;
257 engine
258 .state
259 .set_primitive_int(engine.aux, name, val, global);
260 methods::insert_afterassignment(engine);
261 Ok(())
262 }
263 fn assign_primitive_dim(
265 engine: &mut EngineReferences<ET>,
266 name: PrimitiveIdentifier,
267 global: bool,
268 in_token: ET::Token,
269 ) -> TeXResult<(), ET> {
270 engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
271 let val = engine.read_dim(true, &in_token)?;
272 engine
273 .state
274 .set_primitive_dim(engine.aux, name, val, global);
275 methods::insert_afterassignment(engine);
276 Ok(())
277 }
278 fn assign_primitive_skip(
280 engine: &mut EngineReferences<ET>,
281 name: PrimitiveIdentifier,
282 global: bool,
283 in_token: ET::Token,
284 ) -> TeXResult<(), ET> {
285 engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
286 let val = engine.read_skip(true, &in_token)?;
287 engine
288 .state
289 .set_primitive_skip(engine.aux, name, val, global);
290 methods::insert_afterassignment(engine);
291 Ok(())
292 }
293 fn assign_primitive_muskip(
295 engine: &mut EngineReferences<ET>,
296 name: PrimitiveIdentifier,
297 global: bool,
298 in_token: ET::Token,
299 ) -> TeXResult<(), ET> {
300 engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
301 let val = engine.read_muskip(true, &in_token)?;
302 engine
303 .state
304 .set_primitive_muskip(engine.aux, name, val, global);
305 methods::insert_afterassignment(engine);
306 Ok(())
307 }
308 fn do_whatsit(
310 engine: &mut EngineReferences<ET>,
311 name: PrimitiveIdentifier,
312 token: ET::Token,
313 read: fn(
314 &mut EngineReferences<ET>,
315 ET::Token,
316 ) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET>,
317 ) -> TeXResult<(), ET> {
318 if let Some(ret) = read(engine, token)? {
319 let wi = WhatsitNode::new(ret, name);
320 match engine.stomach.data_mut().mode() {
321 TeXMode::Vertical | TeXMode::InternalVertical => {
322 Self::add_node_v(engine, VNode::Whatsit(wi))?
323 }
324 TeXMode::Horizontal | TeXMode::RestrictedHorizontal => {
325 Self::add_node_h(engine, HNode::Whatsit(wi))
326 }
327 TeXMode::InlineMath | TeXMode::DisplayMath => {
328 Self::add_node_m(engine, MathNode::Whatsit(wi))
329 }
330 }
331 }
332 Ok(())
333 }
334 fn do_box(
336 engine: &mut EngineReferences<ET>,
337 _name: PrimitiveIdentifier,
338 token: ET::Token,
339 bx: fn(
340 &mut EngineReferences<ET>,
341 ET::Token,
342 ) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET>,
343 ) -> TeXResult<(), ET> {
344 match bx(engine, token)? {
345 either::Left(Some(bx)) => methods::add_box(engine, bx, BoxTarget::none()),
346 either::Left(None) => Ok(()),
347 either::Right(bi) => {
348 engine
349 .stomach
350 .data_mut()
351 .open_lists
352 .push(bi.open_list(engine.mouth.start_ref()));
353 Ok(())
354 }
355 }
356 }
357
358 fn do_char(
360 engine: &mut EngineReferences<ET>,
361 token: ET::Token,
362 char: ET::Char,
363 code: CommandCode,
364 ) -> TeXResult<(), ET> {
365 methods::do_char(engine, token, char, code)
366 }
367
368 fn do_defed_char(
370 engine: &mut EngineReferences<ET>,
371 token: ET::Token,
372 char: ET::Char,
373 ) -> TeXResult<(), ET> {
374 if engine.stomach.data_mut().mode().is_math() {
375 Self::do_char_in_math(engine, char)
376 } else {
377 methods::do_char(engine, token, char, CommandCode::Other)
378 }
379 }
380 fn do_char_in_math(engine: &mut EngineReferences<ET>, char: ET::Char) -> TeXResult<(), ET> {
381 ET::Stomach::add_node_m(
382 engine,
383 MathNode::Atom(MathAtom {
384 sup: None,
385 sub: None,
386 nucleus: MathNucleus::Simple {
387 cls: MathClass::Ord,
388 limits: None,
389 kernel: MathKernel::Char {
390 char,
391 style: UnresolvedMathFontStyle::of_fam(0),
392 },
393 },
394 }),
395 );
396 Ok(())
397 }
398 fn do_mathchar(engine: &mut EngineReferences<ET>, code: u32, token: Option<ET::Token>) {
400 let ret = match token.map(|t| t.to_enum()) {
401 Some(StandardToken::Character(char, _)) if code == 32768 => {
402 engine
403 .mouth
404 .requeue(ET::Token::from_char_cat(char, CommandCode::Active));
405 return;
406 }
407 Some(StandardToken::Character(char, _)) => {
408 MathChar::from_u32(code, engine.state, Some(char))
409 }
410 _ => MathChar::from_u32(code, engine.state, None),
411 };
412 ET::Stomach::add_node_m(engine, MathNode::Atom(ret.to_atom()));
413 }
414
415 fn close_box(engine: &mut EngineReferences<ET>, bt: BoxType) -> TeXResult<(), ET> {
418 methods::close_box(engine, bt)
419 }
420
421 fn maybe_switch_mode(
427 engine: &mut EngineReferences<ET>,
428 scope: CommandScope,
429 token: ET::Token,
430 name: PrimitiveIdentifier,
431 ) -> TeXResult<bool, ET> {
432 match (scope, engine.stomach.data_mut().mode()) {
433 (CommandScope::Any, _) => Ok(true),
434 (
435 CommandScope::SwitchesToHorizontal | CommandScope::SwitchesToHorizontalOrMath,
436 TeXMode::Horizontal | TeXMode::RestrictedHorizontal,
437 ) => Ok(true),
438 (CommandScope::SwitchesToVertical, TeXMode::Vertical | TeXMode::InternalVertical) => {
439 Ok(true)
440 }
441 (CommandScope::SwitchesToVertical, TeXMode::Horizontal) => {
442 engine.requeue(token)?;
443 Self::close_paragraph(engine)?;
444 Ok(false)
445 }
446 (
447 CommandScope::MathOnly | CommandScope::SwitchesToHorizontalOrMath,
448 TeXMode::InlineMath | TeXMode::DisplayMath,
449 ) => Ok(true),
450 (
451 CommandScope::SwitchesToHorizontal | CommandScope::SwitchesToHorizontalOrMath,
452 TeXMode::Vertical | TeXMode::InternalVertical,
453 ) => {
454 Self::open_paragraph(engine, token);
455 Ok(false)
456 }
457 (_, _) => {
458 TeXError::not_allowed_in_mode(
459 engine.aux,
460 engine.state,
461 engine.mouth,
462 name,
463 engine.stomach.data_mut().mode(),
464 )?;
465 Ok(false)
466 }
467 }
468 }
469
470 fn open_align(engine: &mut EngineReferences<ET>, _inner: BoxType, between: BoxType) {
472 engine
473 .state
474 .push(engine.aux, GroupType::Align, engine.mouth.line_number());
475 engine
476 .stomach
477 .data_mut()
478 .open_lists
479 .push(if between == BoxType::Vertical {
480 NodeList::Vertical {
481 tp: VerticalNodeListType::HAlign,
482 children: vec![],
483 }
484 } else {
485 NodeList::Horizontal {
486 tp: HorizontalNodeListType::VAlign,
487 children: vec![],
488 }
489 });
490 }
491
492 fn close_align(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
494 match engine.stomach.data_mut().open_lists.pop() {
495 Some(NodeList::Vertical {
496 children,
497 tp: VerticalNodeListType::HAlign,
498 }) => {
499 engine.state.pop(engine.aux, engine.mouth);
500 match engine.stomach.data_mut().open_lists.last_mut() {
501 Some(NodeList::Math { .. }) => {
502 Self::add_node_m(
503 engine,
504 MathNode::Atom(MathAtom {
505 nucleus: MathNucleus::VCenter {
506 children: children.into(),
507 start: engine.mouth.start_ref(),
508 end: engine.mouth.current_sourceref(),
509 scaled: ToOrSpread::None,
510 },
511 sup: None,
512 sub: None,
513 }),
514 );
515 }
516 _ => {
517 for c in children {
518 Self::add_node_v(engine, c)?;
519 }
520 }
521 }
522 }
523 Some(NodeList::Horizontal {
524 children,
525 tp: HorizontalNodeListType::VAlign,
526 }) => {
527 engine.state.pop(engine.aux, engine.mouth);
528 for c in children {
529 Self::add_node_h(engine, c);
530 }
531 }
532 _ => unreachable!("Stomach::close_align called outside of an align"),
533 };
534 Ok(())
535 }
536
537 fn add_node_m(
539 engine: &mut EngineReferences<ET>,
540 node: MathNode<ET, UnresolvedMathFontStyle<ET>>,
541 ) {
542 match engine.stomach.data_mut().open_lists.last_mut() {
543 Some(NodeList::Math { children, .. }) => {
544 children.push(node);
545 }
546 _ => unreachable!("Stomach::add_node_m called outside of math mode"),
547 }
548 }
549
550 fn add_node_h(engine: &mut EngineReferences<ET>, node: HNode<ET>) {
552 if let HNode::Penalty(i) = node {
553 engine.stomach.data_mut().lastpenalty = i;
554 }
555 match engine.stomach.data_mut().open_lists.last_mut() {
556 Some(NodeList::Horizontal { children, .. }) => {
557 children.push(node);
558 }
559 _ => unreachable!("Stomach::add_node_h called outside of horizontal mode"),
560 }
561 }
562
563 #[inline]
565 fn add_node_v(engine: &mut EngineReferences<ET>, node: VNode<ET>) -> TeXResult<(), ET> {
566 methods::add_node_v(engine, node)
567 }
568
569 fn maybe_do_output(
573 engine: &mut EngineReferences<ET>,
574 penalty: Option<i32>,
575 ) -> TeXResult<(), ET> {
576 let data = engine.stomach.data_mut();
577 if !data.in_output
578 && data.open_lists.is_empty()
579 && !data.page.is_empty()
580 && (data.pagetotal >= data.pagegoal || penalty.is_some())
581 {
582 Self::do_output(engine, penalty)
583 } else {
584 Ok(())
585 }
586 }
587
588 fn do_output(
590 engine: &mut EngineReferences<ET>,
591 caused_penalty: Option<i32>,
592 ) -> TeXResult<(), ET> {
593 methods::do_output(engine, caused_penalty)
594 }
595
596 fn split_vertical(
598 engine: &mut EngineReferences<ET>,
599 nodes: Vec<VNode<ET>>,
600 target: <ET as EngineTypes>::Dim,
601 ) -> SplitResult<ET> {
602 methods::vsplit_roughly(engine, nodes, target)
603 }
604
605 fn open_paragraph(engine: &mut EngineReferences<ET>, token: ET::Token) {
607 let sref = engine.mouth.start_ref();
608 let data = engine.stomach.data_mut();
609 data.prevgraf = 0;
610 data.open_lists.push(NodeList::Horizontal {
611 tp: HorizontalNodeListType::Paragraph(sref),
612 children: vec![],
613 });
614 match <ET as EngineTypes>::Gullet::char_or_primitive(engine.state, &token) {
615 Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.indent => {
616 Self::add_node_h(
617 engine,
618 HNode::Box(TeXBox::H {
619 children: vec![].into(),
620 info: HBoxInfo::ParIndent(
621 engine.state.get_primitive_dim(PRIMITIVES.parindent),
622 ),
623 start: sref,
624 end: sref,
625 preskip: None,
626 }),
627 )
628 }
629 Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.noindent => (),
630 _ => {
631 engine.mouth.requeue(token);
632 Self::add_node_h(
633 engine,
634 HNode::Box(TeXBox::H {
635 children: vec![].into(),
636 info: HBoxInfo::ParIndent(
637 engine.state.get_primitive_dim(PRIMITIVES.parindent),
638 ),
639 start: sref,
640 end: sref,
641 preskip: None,
642 }),
643 )
644 }
645 }
646 engine.push_every(PRIMITIVES.everypar)
647 }
648
649 fn close_paragraph(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
651 let ls = &mut engine.stomach.data_mut().open_lists;
652 match ls.pop() {
653 Some(NodeList::Horizontal {
654 tp: HorizontalNodeListType::Paragraph(sourceref),
655 children,
656 }) => {
657 if children.is_empty() {
658 let _ = engine.state.take_parshape();
659 engine.state.set_primitive_int(
660 engine.aux,
661 PRIMITIVES.hangafter,
662 ET::Int::default(),
663 false,
664 );
665 engine.state.set_primitive_dim(
666 engine.aux,
667 PRIMITIVES.hangindent,
668 ET::Dim::default(),
669 false,
670 );
671 return Ok(());
672 }
673 let spec = ParLineSpec::make(engine.state, engine.aux);
674 Self::split_paragraph(engine, spec, children, sourceref)?;
675 }
676 _ => unreachable!("Stomach::close_paragraph called outside of horizontal mode"),
677 }
678 Ok(())
679 }
680
681 fn split_paragraph(
683 engine: &mut EngineReferences<ET>,
684 specs: Vec<ParLineSpec<ET>>,
685 children: Vec<HNode<ET>>,
686 start_ref: SourceReference<<<ET as EngineTypes>::File as File>::SourceRefID>,
687 ) -> TeXResult<(), ET> {
688 if children.is_empty() {
689 return Ok(());
690 }
691 let parskip = engine.state.get_primitive_skip(PRIMITIVES.parskip);
692 if parskip != Skip::default() {
693 Self::add_node_v(engine, VNode::VSkip(parskip))?;
694 }
695 let ret = methods::split_paragraph_roughly(engine, specs, children, start_ref);
696 for line in ret {
697 match line {
698 ParLine::Adjust(n) => Self::add_node_v(engine, n)?,
699 ParLine::Line(bx) => Self::add_node_v(engine, VNode::Box(bx))?,
700 }
701 }
702 Ok(())
703 }
704}
705
706#[derive(Clone, Debug)]
711pub struct StomachData<ET: EngineTypes> {
712 pub page: Vec<VNode<ET>>,
713 pub open_lists: Vec<NodeList<ET>>,
714 pub pagegoal: ET::Dim,
715 pub pagetotal: ET::Dim,
716 pub pagestretch: ET::Dim,
717 pub pagefilstretch: ET::Dim,
718 pub pagefillstretch: ET::Dim,
719 pub pagefilllstretch: ET::Dim,
720 pub pageshrink: ET::Dim,
721 pub pagedepth: ET::Dim,
722 pub prevdepth: ET::Dim,
723 pub spacefactor: i32,
724 pub topmarks: HMap<usize, TokenList<ET::Token>>,
725 pub firstmarks: HMap<usize, TokenList<ET::Token>>,
726 pub botmarks: HMap<usize, TokenList<ET::Token>>,
727 pub splitfirstmarks: HMap<usize, TokenList<ET::Token>>,
728 pub splitbotmarks: HMap<usize, TokenList<ET::Token>>,
729 pub page_contains_boxes: bool,
730 pub lastpenalty: i32,
731 pub prevgraf: u16,
732 pub in_output: bool,
733 pub deadcycles: usize,
734 pub vadjusts: Vec<VNode<ET>>,
735 pub inserts: Vec<(usize, Box<[VNode<ET>]>)>,
736}
737impl<ET: EngineTypes> StomachData<ET> {
738 pub fn mode(&self) -> TeXMode {
740 match self.open_lists.last() {
741 Some(NodeList::Horizontal {
742 tp: HorizontalNodeListType::Paragraph(..),
743 ..
744 }) => TeXMode::Horizontal,
745 Some(NodeList::Horizontal { .. }) => TeXMode::RestrictedHorizontal,
746 Some(NodeList::Vertical { .. }) => TeXMode::InternalVertical,
747 Some(NodeList::Math { .. }) => {
748 for ls in self.open_lists.iter().rev() {
749 if let NodeList::Math {
750 tp: MathNodeListType::Top { display },
751 ..
752 } = ls
753 {
754 if *display {
755 return TeXMode::DisplayMath;
756 } else {
757 return TeXMode::InlineMath;
758 }
759 }
760 }
761 unreachable!()
762 }
763 None => TeXMode::Vertical,
764 }
765 }
766}
767
768impl<ET: EngineTypes> Default for StomachData<ET> {
769 fn default() -> Self {
770 StomachData {
771 page: vec![],
772 open_lists: vec![],
773 pagegoal: ET::Dim::from_sp(i32::MAX),
774 pagetotal: ET::Dim::default(),
775 pagestretch: ET::Dim::default(),
776 pagefilstretch: ET::Dim::default(),
777 pagefillstretch: ET::Dim::default(),
778 pagefilllstretch: ET::Dim::default(),
779 pageshrink: ET::Dim::default(),
780 pagedepth: ET::Dim::default(),
781 prevdepth: ET::Dim::from_sp(-65536000),
782 spacefactor: 1000,
783 topmarks: HMap::default(),
784 firstmarks: HMap::default(),
785 botmarks: HMap::default(),
786 splitfirstmarks: HMap::default(),
787 splitbotmarks: HMap::default(),
788 prevgraf: 0,
789 lastpenalty: 0,
790 page_contains_boxes: false,
791 in_output: false,
792 deadcycles: 0,
793 vadjusts: vec![],
794 inserts: vec![],
795 }
796 }
797}
798
799pub struct DefaultStomach<ET: EngineTypes > {
801 afterassignment: Option<ET::Token>,
802 data: StomachData<ET>,
803}
804impl<ET: EngineTypes > Stomach<ET> for DefaultStomach<ET> {
805 fn new(_aux: &mut EngineAux<ET>, _state: &mut ET::State) -> Self {
806 DefaultStomach {
807 afterassignment: None,
808 data: StomachData::default(),
809 }
810 }
811
812 fn afterassignment(&mut self) -> &mut Option<ET::Token> {
813 &mut self.afterassignment
814 }
815
816 fn data_mut(&mut self) -> &mut StomachData<ET> {
817 &mut self.data
818 }
819}
820
821impl<ET: EngineTypes> EngineReferences<'_, ET> {
822 pub fn read_box(
824 &mut self,
825 skip_eq: bool,
826 ) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
827 let mut read_eq = !skip_eq;
828 crate::expand_loop!(self,token,
829 ResolvedToken::Tk {char,code:CommandCode::Other} if !read_eq && matches!(char.try_into(),Ok(b'=')) => read_eq = true,
830 ResolvedToken::Tk { code:CommandCode::Space,..} => (),
831 ResolvedToken::Cmd(Some(TeXCommand::Primitive {cmd:PrimitiveCommand::Box(b),..})) =>
832 return b(self,token),
833 _ => break
834 );
835 self.general_error("A <box> was supposed to be here".to_string())?;
836 Ok(Either::Left(None))
837 }
838
839 pub fn read_char_or_math_group<
844 S,
845 F1: FnOnce(S, &mut Self, MathChar<ET>) -> TeXResult<(), ET>,
846 F2: FnOnce(S) -> ListTarget<ET, MathNode<ET, UnresolvedMathFontStyle<ET>>>,
847 >(
848 &mut self,
849 in_token: &ET::Token,
850 f: F1,
851 tp: F2,
852 s: S,
853 ) -> TeXResult<(), ET> {
854 crate::expand_loop!(self,token,
855 ResolvedToken::Tk {code:CommandCode::Space,..} => (),
856 ResolvedToken::Tk {code:CommandCode::BeginGroup,..} |
857 ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::BeginGroup,..})) => {
858 self.state.push(self.aux,GroupType::Math,self.mouth.line_number());
859 let list = NodeList::Math{children:MathNodeList::default(),start:self.mouth.start_ref(),
860 tp:MathNodeListType::Target(tp(s))};
861 self.stomach.data_mut().open_lists.push(list);
862 return Ok(())
863 },
864 ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Relax,..})) => (),
865 ResolvedToken::Tk {char,code:CommandCode::Other | CommandCode::Letter} => {
866 let code = self.state.get_mathcode(char);
867 if code == 32768 {
868 self.mouth.requeue(ET::Token::from_char_cat(char, CommandCode::Active));
869 continue
870 }
871 let mc = MathChar::from_u32(code,self.state, Some(char));
872 return f(s,self,mc)
873 },
874 ResolvedToken::Cmd(Some(TeXCommand::MathChar(u))) => {
875 let mc = MathChar::from_u32(*u, self.state,None);
876 return f(s,self,mc)
877 }
878 ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.delimiter => {
879 let int = self.read_int(true,&token)?;
880 match Delimiter::from_int(int,self.state) {
881 either::Left(d) => return f(s,self,d.small),
882 either::Right((d,i)) => {
883 self.general_error(format!("Bad delimiter code ({})",i))?;
884 return f(s,self,d.small)
885 }
886 }
887 }
888 _ => return Err(TeXError::General("Begingroup or math character expected\nTODO: Better error message".to_string()))
889 );
890 TeXError::file_end_while_use(self.aux, self.state, self.mouth, in_token)
891 }
892}