Skip to main content

retex_engine/
typeset.rs

1//! Typesetting: shared state, hooks and small helpers.
2//!
3//! The typesetting routines are spread over several modules:
4//!
5//! * [`crate::build`] — list building in main control (§1055–§1135);
6//! * [`crate::hmode`] — characters, spaces and discretionaries;
7//! * [`crate::pack`] — `hpack` and `vpack` (§644–§679);
8//! * [`crate::nodeproc`] — LuaTeX's hyphenation, ligaturing and kerning
9//!   passes;
10//! * [`crate::linebreak`] — breaking paragraphs into lines (§813–§890);
11//! * [`crate::page`] — `\vsplit`, the page builder, the output routine and
12//!   `\shipout` (§967–§1028);
13//! * [`crate::align`] — alignments (§768–§812);
14//! * [`crate::showbox`] — displaying boxes.
15
16use std::collections::HashMap;
17
18use crate::Engine;
19use crate::glue::Glue;
20use crate::nodes::{AttrList, NodeRef, Nodes};
21use crate::scan::{SET_AUX_PREV_DEPTH, ValLevel};
22use crate::token::TokenList;
23
24/// The "running" dimension of rules (`null_flag`).
25pub const NULL_FLAG: i32 = -0o10000000000;
26
27/// The penalty that forces a break (`eject_penalty`).
28pub const EJECT_PENALTY: i32 = -10000;
29
30/// The infinite penalty (`inf_penalty`).
31pub const INF_PENALTY: i32 = 10000;
32
33/// Infinite badness (`inf_bad`).
34pub const INF_BAD: i32 = 10000;
35
36/// Awful badness (`awful_bad`).
37pub const AWFUL_BAD: i32 = 0o7777777777;
38
39/// Deplorable badness (`deplorable`).
40pub const DEPLORABLE: i32 = 100000;
41
42/// Mark texts: `\topmark` … `\splitbotmark` for each marks class.
43#[derive(Debug, Default, Clone)]
44pub struct Marks {
45    /// For each class: top, first, bot, split first, split bot.
46    pub classes: HashMap<u32, [Option<TokenList>; 5]>,
47}
48
49impl Marks {
50    /// The mark text of `class` and kind `code` (0–4).
51    pub fn get(&self, class: u32, code: u32) -> Option<TokenList> {
52        self.classes
53            .get(&class)
54            .and_then(|m| m.get(code as usize).cloned().flatten())
55    }
56
57    /// Sets the mark text of `class` and kind `code`.
58    pub fn set(&mut self, class: u32, code: usize, t: Option<TokenList>) {
59        let e = self.classes.entry(class).or_default();
60        e[code] = t;
61    }
62}
63
64/// Mark kinds as indices into [`Marks`] entries.
65pub mod mark_code {
66    /// `\topmark`.
67    pub const TOP: usize = 0;
68    /// `\firstmark`.
69    pub const FIRST: usize = 1;
70    /// `\botmark`.
71    pub const BOT: usize = 2;
72    /// `\splitfirstmark`.
73    pub const SPLIT_FIRST: usize = 3;
74    /// `\splitbotmark`.
75    pub const SPLIT_BOT: usize = 4;
76}
77
78/// What the current page contains (`page_contents`).
79#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
80pub enum PageContents {
81    /// Nothing yet.
82    #[default]
83    Empty,
84    /// Only insertions.
85    InsertsOnly,
86    /// A box or rule has been seen.
87    BoxThere,
88}
89
90/// An insertion class record on the current page (TeX's page_ins nodes,
91/// §981).
92#[derive(Debug, Clone, Copy)]
93pub struct PageIns {
94    /// The insertion class.
95    pub n: u32,
96    /// Whether the class has been split (`split_up`), as opposed to
97    /// `inserting`.
98    pub split_up: bool,
99    /// Natural height plus depth accumulated so far.
100    pub height: i32,
101    /// For split classes: where the split occurs (`broken_ptr`).
102    pub broken_ptr: NodeRef,
103    /// The insertion node that is split (`broken_ins`).
104    pub broken_ins: NodeRef,
105    /// The most recent insertion of this class (`last_ins_ptr`).
106    pub last_ins_ptr: NodeRef,
107    /// The last insertion of this class on the best page
108    /// (`best_ins_ptr`).
109    pub best_ins_ptr: NodeRef,
110}
111
112/// The state of the page builder (§980–§982).
113#[derive(Debug, Default, Clone)]
114pub struct PageState {
115    /// Head of the contribution list.
116    pub contrib_head: NodeRef,
117    /// Head of the current page.
118    pub page_head: NodeRef,
119    /// Tail of the current page.
120    pub page_tail: NodeRef,
121    /// `page_so_far`: goal, total, stretch (normal, fil, fill, filll),
122    /// shrink, depth. The `fi` stretch is in [`PageState::fi_stretch`].
123    pub so_far: [i32; 8],
124    /// Page stretch of order `fi` (LuaTeX).
125    pub fi_stretch: i32,
126    /// `page_max_depth`.
127    pub max_depth: i32,
128    /// What the page contains.
129    pub contents: PageContents,
130    /// The best break found so far (`best_page_break`).
131    pub best_break: NodeRef,
132    /// Its cost (`least_page_cost`).
133    pub least_cost: i32,
134    /// The page goal at the best break (`best_size`).
135    pub best_size: i32,
136    /// `so_far` at the best break (for [`crate::pagenote`]).
137    pub best_so_far: [i32; 8],
138    /// Whether the page had infinite stretch at the best break.
139    pub best_infinite: bool,
140    /// What the last page break left out (default output mode).
141    pub last_break: Option<crate::pagenote::PageBreakInfo>,
142    /// Insertion classes on the page, in increasing class order.
143    pub ins: Vec<PageIns>,
144    /// `\lastskip` etc. of the page (`last_glue`); `None` if the last item
145    /// was not glue.
146    pub last_glue: Option<Glue>,
147    /// `last_penalty`.
148    pub last_penalty: i32,
149    /// `last_kern`.
150    pub last_kern: i32,
151    /// `\lastnodetype` of the page.
152    pub last_node_type: i32,
153}
154
155impl PageState {
156    /// Allocates the list heads.
157    pub fn init(&mut self, nodes: &mut Nodes) {
158        self.contrib_head = nodes.new_temp();
159        self.page_head = nodes.new_temp();
160        self.page_tail = self.page_head;
161        self.last_node_type = -1;
162    }
163
164    /// The page stretch of glue order `o` (LuaTeX numbering).
165    pub fn stretch(&self, o: u8) -> i32 {
166        match o {
167            0 => self.so_far[2],
168            1 => self.fi_stretch,
169            2 => self.so_far[3],
170            3 => self.so_far[4],
171            _ => self.so_far[5],
172        }
173    }
174
175    /// Mutable access to the page stretch of order `o`.
176    pub fn stretch_mut(&mut self, o: u8) -> &mut i32 {
177        match o {
178            0 => &mut self.so_far[2],
179            1 => &mut self.fi_stretch,
180            2 => &mut self.so_far[3],
181            3 => &mut self.so_far[4],
182            _ => &mut self.so_far[5],
183        }
184    }
185}
186
187/// Mutable state of the typesetting routines.
188#[derive(Default)]
189pub struct TsState {
190    /// The nesting string of `show_box` (`cur_length` of TeX's string
191    /// pool).
192    pub show_prefix: Vec<u8>,
193    /// The font assumed at the start of `short_display` (−1 for none).
194    pub font_in_short_display: i64,
195    /// Whether the output routine is active.
196    pub output_active: bool,
197    /// Head of the adjustment list (`adjust_head`), allocated on first use.
198    pub adjust_head: NodeRef,
199    /// Head of the pre-adjustment list (LuaTeX's `\vadjust pre`).
200    pub pre_adjust_head: NodeRef,
201    /// Tail of the adjustment list collected by `hpack`, if active.
202    pub adjust_tail: Option<NodeRef>,
203    /// Tail of the pre-adjustment list collected by `hpack`, if active.
204    pub pre_adjust_tail: Option<NodeRef>,
205    /// Line where the current paragraph or alignment started
206    /// (`pack_begin_line`), negative for alignments.
207    pub pack_begin_line: i32,
208    /// Items removed by the page builder and `\vsplit` (`\pagediscards`,
209    /// `\splitdiscards`).
210    pub disc_ptr: [NodeRef; 4],
211    /// Cached current attribute list: (generation, list).
212    pub attr_cache: Option<(u64, Option<AttrList>)>,
213    /// The attributes new nodes get instead of the current ones, while
214    /// `mlist_to_hlist` builds nodes for a noad (LuaTeX gives them the
215    /// attributes of the noad or kernel they come from).
216    pub attr_override: Option<Option<AttrList>>,
217    /// The attributes of the boxes being built (indexed from the save
218    /// stack).
219    pub box_attrs: Vec<Option<AttrList>>,
220    /// Alignment state.
221    pub align: crate::align::AlignState,
222    /// Stretch and shrink totals of the most recent `hpack`/`vpack`.
223    pub last_totals: ([i32; 5], [i32; 5]),
224    /// The last line box made by `line_break` (`just_box`).
225    pub just_box: NodeRef,
226    /// The state of `mlist_to_hlist`.
227    pub math: crate::mlist::MathState,
228}
229
230impl std::fmt::Debug for TsState {
231    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232        f.debug_struct("TsState")
233            .field("output_active", &self.output_active)
234            .finish_non_exhaustive()
235    }
236}
237
238impl Engine {
239    /// Runs the node-list filter callback `cb` (LuaTeX's
240    /// `pre_linebreak_filter`, `hpack_filter`, …) on the list starting at
241    /// `head`; returns the new first node. A callback returning `false`
242    /// discards the list.
243    pub fn filter_list(&mut self, cb: &str, head: NodeRef, group: &str) -> NodeRef {
244        // Observed: LuaTeX does not call the list callbacks for an empty
245        // list (`\hbox{}`, `\vbox{}`), so they never get nil.
246        if head.is_null() {
247            return head;
248        }
249        // Observed: the list the callback gets has no node before its head
250        // (babel's bidi code walks back from it).
251        self.nodes.set_prev(head, NodeRef::NULL);
252        match self.run_list_filter(cb, head, group) {
253            crate::ListFilter::Keep(h) => {
254                if !h.is_null() {
255                    self.nodes.set_prev(h, NodeRef::NULL);
256                }
257                h
258            }
259            crate::ListFilter::Flush => {
260                self.nodes.flush_list(head);
261                NodeRef::NULL
262            }
263        }
264    }
265
266    /// Whether the current page and contributions are empty.
267    pub fn page_is_empty(&self) -> bool {
268        self.page.page_head == self.page.page_tail
269            && self.nodes.next(self.page.contrib_head).is_null()
270    }
271
272    /// Reports a command that is not implemented yet.
273    pub fn not_implemented(&mut self, what: &str) {
274        self.print_err("retex: not implemented yet: ");
275        self.printer.print_str(what);
276        self.help(&["This command is not supported by retex yet."]);
277        self.error();
278    }
279
280    /// The current attribute list (`current_attribute_list`).
281    pub fn cur_attr(&mut self) -> Option<AttrList> {
282        let generation = self.eqtb.attr_generation;
283        if let Some((g, a)) = &self.ts.attr_cache
284            && *g == generation
285        {
286            return a.clone();
287        }
288        let mut v: Vec<(u32, i32)> = Vec::new();
289        for (i, s) in self.eqtb.attr.iter().enumerate() {
290            if s.val != crate::eqtb::UNUSED_ATTRIBUTE {
291                v.push((i as u32, s.val));
292            }
293        }
294        let a: Option<AttrList> = if v.is_empty() {
295            None
296        } else {
297            Some(std::rc::Rc::from(v))
298        };
299        self.ts.attr_cache = Some((generation, a.clone()));
300        a
301    }
302
303    /// Allocates a node carrying the current attributes.
304    pub fn new_node(&mut self, id: u8, subtype: u16, data: crate::nodes::NodeData) -> NodeRef {
305        let p = self.nodes.alloc(id, subtype, data);
306        self.give_new_attr(p);
307        p
308    }
309
310    /// Gives new node `p` the current attributes, or those of the noad
311    /// `mlist_to_hlist` is building nodes for.
312    #[inline]
313    pub fn give_new_attr(&mut self, p: NodeRef) {
314        if let Some(a) = &self.ts.attr_override {
315            let a = a.clone();
316            self.nodes.set_attr(p, a);
317        } else if self.eqtb.attrs_in_use {
318            let a = self.cur_attr();
319            self.nodes.set_attr(p, a);
320        }
321    }
322
323    /// `\spacefactor`, `\prevdepth` (`alter_aux`).
324    pub fn alter_aux(&mut self) {
325        let c = self.cur_chr;
326        if (c == SET_AUX_PREV_DEPTH)
327            != (self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical)
328        {
329            self.report_illegal_case();
330            return;
331        }
332        self.scan_optional_equals();
333        if c == SET_AUX_PREV_DEPTH {
334            self.scan_normal_dimen();
335            self.cur_list_mut().prev_depth = self.cur_val;
336        } else {
337            self.scan_int();
338            if self.cur_val <= 0 || self.cur_val > 32767 {
339                self.print_err("Bad space factor");
340                self.help(&["I allow only values in the range 1..32767 here."]);
341                self.int_error(self.cur_val);
342            } else {
343                self.cur_list_mut().space_factor = self.cur_val;
344            }
345        }
346    }
347
348    /// `\prevgraf` (`alter_prev_graf`).
349    pub fn alter_prev_graf(&mut self) {
350        self.scan_optional_equals();
351        self.scan_int();
352        let v = self.cur_val;
353        if v < 0 {
354            self.print_err("Bad ");
355            self.print_esc_str("prevgraf");
356            self.help(&["I allow only nonnegative values here."]);
357            self.int_error(v);
358        } else {
359            // TeX stores \prevgraf in the innermost non-math level.
360            let mut p = self.nest.len() - 1;
361            while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
362                p -= 1;
363            }
364            self.nest[p].prev_graf = v;
365        }
366    }
367
368    /// `\pagegoal` etc. (`alter_page_so_far`).
369    pub fn alter_page_so_far(&mut self) {
370        let c = self.cur_chr as usize;
371        self.scan_optional_equals();
372        self.scan_normal_dimen();
373        if c < self.page.so_far.len() {
374            self.page.so_far[c] = self.cur_val;
375        }
376    }
377
378    /// `\deadcycles`, `\insertpenalties` assignments (`alter_integer`) are
379    /// in the prefixed-command code; `\wd`, `\ht`, `\dp` assignments
380    /// (`alter_box_dimen`).
381    pub fn alter_box_dimen(&mut self) {
382        let c = self.cur_chr;
383        self.scan_register_num();
384        let b = self.eqtb.boxes[self.cur_val as usize].val;
385        self.scan_optional_equals();
386        self.scan_normal_dimen();
387        let v = self.cur_val;
388        if let Some(bd) = self.nodes.box_data_mut(b) {
389            match c {
390                2 => bd.width = v,
391                3 => bd.depth = v,
392                _ => bd.height = v,
393            }
394        }
395    }
396
397    /// `\spacefactor`/`\prevdepth` values for `\the`.
398    pub fn fetch_aux(&mut self, m: u32) -> Option<(i32, ValLevel)> {
399        let vertical = self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical;
400        if (m == SET_AUX_PREV_DEPTH) != vertical {
401            self.print_err("Improper ");
402            self.print_cmd_chr(crate::cmd::Cmd::SetAux, m);
403            self.help(&[
404                "You can refer to \\spacefactor only in horizontal mode;",
405                "you can refer to \\prevdepth only in vertical mode; and",
406                "neither of these is meaningful inside \\write. So",
407                "I'm forgetting what you said and using zero instead.",
408            ]);
409            self.error();
410            return Some((
411                0,
412                if m == SET_AUX_PREV_DEPTH {
413                    ValLevel::Dimen
414                } else {
415                    ValLevel::Int
416                },
417            ));
418        }
419        let cl = self.cur_list();
420        if m == SET_AUX_PREV_DEPTH {
421            Some((cl.prev_depth, ValLevel::Dimen))
422        } else {
423            Some((cl.space_factor, ValLevel::Int))
424        }
425    }
426
427    /// `\prevgraf` value.
428    pub fn fetch_prev_graf(&mut self) -> i32 {
429        if self.mode == crate::maincontrol::Mode::None {
430            return 0;
431        }
432        let mut p = self.nest.len() - 1;
433        while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
434            p -= 1;
435        }
436        self.nest[p].prev_graf
437    }
438
439    /// `\pagegoal` etc. values.
440    pub fn page_so_far_value(&self, m: u32) -> i32 {
441        // tex.web §421: on an empty page, outside the output routine,
442        // \pagegoal is \maxdimen and the other values are zero.
443        if self.page.contents == PageContents::Empty && !self.ts.output_active {
444            return if m == 0 { retex_arith::MAX_DIMEN } else { 0 };
445        }
446        self.page.so_far.get(m as usize).copied().unwrap_or(0)
447    }
448
449    /// `\lastpenalty`, `\lastkern`, `\lastskip`, `\lastnodetype` (§424).
450    pub fn fetch_last_node_item(&mut self, m: u32) -> (i32, crate::glue::Glue, ValLevel) {
451        use crate::nodes::NodeData;
452        use crate::scan::last_item as li;
453        let level = match m {
454            li::LAST_KERN => ValLevel::Dimen,
455            li::LAST_SKIP => ValLevel::Glue,
456            _ => ValLevel::Int,
457        };
458        let tail = self.cur_list().tail;
459        let head = self.cur_list().head;
460        if m == li::LAST_NODE_TYPE {
461            if tail == head {
462                if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
463                    return (self.page.last_node_type, Glue::ZERO, level);
464                }
465                return (-1, Glue::ZERO, level);
466            }
467            let v = etex_node_type(self.nodes.id(tail), self.nodes.subtype(tail));
468            return (v, Glue::ZERO, level);
469        }
470        if tail != head {
471            match &self.nodes.get(tail).data {
472                NodeData::Penalty { penalty } if m == li::LAST_PENALTY => {
473                    return (*penalty, Glue::ZERO, level);
474                }
475                NodeData::Kern { kern, .. } if m == li::LAST_KERN => {
476                    return (*kern, Glue::ZERO, level);
477                }
478                NodeData::Glue { spec, .. } if m == li::LAST_SKIP => {
479                    let l = if self.nodes.get(tail).subtype == crate::nodes::glue_sub::MU_GLUE {
480                        ValLevel::Mu
481                    } else {
482                        level
483                    };
484                    return (spec.width, *spec, l);
485                }
486                _ => {}
487            }
488        } else if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
489            match m {
490                li::LAST_PENALTY => return (self.page.last_penalty, Glue::ZERO, level),
491                li::LAST_KERN => return (self.page.last_kern, Glue::ZERO, level),
492                li::LAST_SKIP => {
493                    if let Some(g) = self.page.last_glue {
494                        return (g.width, g, level);
495                    }
496                }
497                _ => {}
498            }
499        }
500        (0, Glue::ZERO, level)
501    }
502
503    /// Whether box `p` is an hlist.
504    pub fn box_is_hlist(&self, p: NodeRef) -> bool {
505        self.nodes.id(p) == crate::nodes::id::HLIST
506    }
507
508    /// Current math style (`\mathstyle`), or −1 outside math.
509    pub fn cur_math_style(&self) -> i32 {
510        if self.mode.abs_kind() == crate::maincontrol::ModeKind::Math {
511            self.cur_list().math_style
512        } else {
513            -1
514        }
515    }
516
517    /// `\insertht`: the height of insertion class `n` on the current page.
518    pub fn insert_height(&self, n: u32) -> i32 {
519        self.page
520            .ins
521            .iter()
522            .find(|r| r.n == n)
523            .map_or(0, |r| r.height)
524    }
525}
526
527/// e-TeX's `\lastnodetype` value for a node of LuaTeX type `t`: −1 none,
528/// 0 glyph, 1 hlist, 2 vlist, 3 rule, 4 ins, 5 mark, 6 adjust,
529/// 7 ligature, 8 disc, 9 whatsit, 10 math, 11 glue, 12 kern,
530/// 13 penalty, 14 unset, 15 math mode nodes.
531pub fn etex_node_type(t: u8, subtype: u16) -> i32 {
532    use crate::nodes::id;
533    match t {
534        id::GLYPH => {
535            if subtype & crate::nodes::glyph_sub::LIGATURE != 0 {
536                7
537            } else {
538                0
539            }
540        }
541        id::HLIST => 1,
542        id::VLIST => 2,
543        id::RULE => 3,
544        id::INS => 4,
545        id::MARK => 5,
546        id::ADJUST => 6,
547        id::DISC => 8,
548        // Observed: a local_par node (at the start of a paragraph, after
549        // a display, or for a change of \localinterlinepenalty) counts as
550        // none.
551        id::LOCAL_PAR => -1,
552        id::WHATSIT | id::DIR | id::BOUNDARY => 9,
553        id::MATH => 10,
554        id::GLUE => 11,
555        id::KERN | id::MARGIN_KERN => 12,
556        id::PENALTY => 13,
557        id::UNSET => 14,
558        _ => 15,
559    }
560}