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
36pub 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
57pub 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
109pub 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
226pub 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 => {
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 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; (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
657pub 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
764pub 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 {
821 match &node {
822 VNode::Box(_) | VNode::Insert(..) | VNode::HRule { .. } => {
823 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(); 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
855pub 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 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 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 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(); 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 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
1009pub struct SplitResult<ET: EngineTypes> {
1011 pub first: Vec<VNode<ET>>,
1013 pub rest: Vec<VNode<ET>>,
1015 pub split_penalty: Option<i32>,
1017}
1018
1019pub 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(); if target < ET::Dim::default() {
1045 split = i;
1046 break;
1047 }
1048 }
1049 _ => {
1050 target = target - (n.height() + n.depth()); 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#[derive(Debug, Clone, Eq, PartialEq)]
1085pub struct ParLineSpec<ET: EngineTypes> {
1086 pub leftskip: Skip<ET::Dim>,
1088 pub rightskip: Skip<ET::Dim>,
1090 pub target: ET::Dim,
1092}
1093impl<ET: EngineTypes> ParLineSpec<ET> {
1094 pub fn make(state: &mut ET::State, aux: &mut EngineAux<ET>) -> Vec<ParLineSpec<ET>> {
1098 let parshape = state.take_parshape(); let hangindent = state.get_primitive_dim(PRIMITIVES.hangindent); 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
1160pub enum ParLine<ET: EngineTypes> {
1163 Line(TeXBox<ET>), Adjust(VNode<ET>),
1165}
1166
1167pub 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); 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}