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        match self.run_list_filter(cb, head, group) {
250            crate::ListFilter::Keep(h) => {
251                if !h.is_null() {
252                    self.nodes.set_prev(h, NodeRef::NULL);
253                }
254                h
255            }
256            crate::ListFilter::Flush => {
257                self.nodes.flush_list(head);
258                NodeRef::NULL
259            }
260        }
261    }
262
263    /// Whether the current page and contributions are empty.
264    pub fn page_is_empty(&self) -> bool {
265        self.page.page_head == self.page.page_tail
266            && self.nodes.next(self.page.contrib_head).is_null()
267    }
268
269    /// Reports a command that is not implemented yet.
270    pub fn not_implemented(&mut self, what: &str) {
271        self.print_err("retex: not implemented yet: ");
272        self.printer.print_str(what);
273        self.help(&["This command is not supported by retex yet."]);
274        self.error();
275    }
276
277    /// The current attribute list (`current_attribute_list`).
278    pub fn cur_attr(&mut self) -> Option<AttrList> {
279        let generation = self.eqtb.attr_generation;
280        if let Some((g, a)) = &self.ts.attr_cache
281            && *g == generation
282        {
283            return a.clone();
284        }
285        let mut v: Vec<(u32, i32)> = Vec::new();
286        for (i, s) in self.eqtb.attr.iter().enumerate() {
287            if s.val != crate::eqtb::UNUSED_ATTRIBUTE {
288                v.push((i as u32, s.val));
289            }
290        }
291        let a: Option<AttrList> = if v.is_empty() {
292            None
293        } else {
294            Some(std::rc::Rc::from(v))
295        };
296        self.ts.attr_cache = Some((generation, a.clone()));
297        a
298    }
299
300    /// Allocates a node carrying the current attributes.
301    pub fn new_node(&mut self, id: u8, subtype: u16, data: crate::nodes::NodeData) -> NodeRef {
302        let p = self.nodes.alloc(id, subtype, data);
303        self.give_new_attr(p);
304        p
305    }
306
307    /// Gives new node `p` the current attributes, or those of the noad
308    /// `mlist_to_hlist` is building nodes for.
309    #[inline]
310    pub fn give_new_attr(&mut self, p: NodeRef) {
311        if let Some(a) = &self.ts.attr_override {
312            let a = a.clone();
313            self.nodes.set_attr(p, a);
314        } else if self.eqtb.attrs_in_use {
315            let a = self.cur_attr();
316            self.nodes.set_attr(p, a);
317        }
318    }
319
320    /// `\spacefactor`, `\prevdepth` (`alter_aux`).
321    pub fn alter_aux(&mut self) {
322        let c = self.cur_chr;
323        if (c == SET_AUX_PREV_DEPTH)
324            != (self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical)
325        {
326            self.report_illegal_case();
327            return;
328        }
329        self.scan_optional_equals();
330        if c == SET_AUX_PREV_DEPTH {
331            self.scan_normal_dimen();
332            self.cur_list_mut().prev_depth = self.cur_val;
333        } else {
334            self.scan_int();
335            if self.cur_val <= 0 || self.cur_val > 32767 {
336                self.print_err("Bad space factor");
337                self.help(&["I allow only values in the range 1..32767 here."]);
338                self.int_error(self.cur_val);
339            } else {
340                self.cur_list_mut().space_factor = self.cur_val;
341            }
342        }
343    }
344
345    /// `\prevgraf` (`alter_prev_graf`).
346    pub fn alter_prev_graf(&mut self) {
347        self.scan_optional_equals();
348        self.scan_int();
349        let v = self.cur_val;
350        if v < 0 {
351            self.print_err("Bad ");
352            self.print_esc_str("prevgraf");
353            self.help(&["I allow only nonnegative values here."]);
354            self.int_error(v);
355        } else {
356            // TeX stores \prevgraf in the innermost non-math level.
357            let mut p = self.nest.len() - 1;
358            while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
359                p -= 1;
360            }
361            self.nest[p].prev_graf = v;
362        }
363    }
364
365    /// `\pagegoal` etc. (`alter_page_so_far`).
366    pub fn alter_page_so_far(&mut self) {
367        let c = self.cur_chr as usize;
368        self.scan_optional_equals();
369        self.scan_normal_dimen();
370        if c < self.page.so_far.len() {
371            self.page.so_far[c] = self.cur_val;
372        }
373    }
374
375    /// `\deadcycles`, `\insertpenalties` assignments (`alter_integer`) are
376    /// in the prefixed-command code; `\wd`, `\ht`, `\dp` assignments
377    /// (`alter_box_dimen`).
378    pub fn alter_box_dimen(&mut self) {
379        let c = self.cur_chr;
380        self.scan_register_num();
381        let b = self.eqtb.boxes[self.cur_val as usize].val;
382        self.scan_optional_equals();
383        self.scan_normal_dimen();
384        let v = self.cur_val;
385        if let Some(bd) = self.nodes.box_data_mut(b) {
386            match c {
387                2 => bd.width = v,
388                3 => bd.depth = v,
389                _ => bd.height = v,
390            }
391        }
392    }
393
394    /// `\spacefactor`/`\prevdepth` values for `\the`.
395    pub fn fetch_aux(&mut self, m: u32) -> Option<(i32, ValLevel)> {
396        let vertical = self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical;
397        if (m == SET_AUX_PREV_DEPTH) != vertical {
398            self.print_err("Improper ");
399            self.print_cmd_chr(crate::cmd::Cmd::SetAux, m);
400            self.help(&[
401                "You can refer to \\spacefactor only in horizontal mode;",
402                "you can refer to \\prevdepth only in vertical mode; and",
403                "neither of these is meaningful inside \\write. So",
404                "I'm forgetting what you said and using zero instead.",
405            ]);
406            self.error();
407            return Some((
408                0,
409                if m == SET_AUX_PREV_DEPTH {
410                    ValLevel::Dimen
411                } else {
412                    ValLevel::Int
413                },
414            ));
415        }
416        let cl = self.cur_list();
417        if m == SET_AUX_PREV_DEPTH {
418            Some((cl.prev_depth, ValLevel::Dimen))
419        } else {
420            Some((cl.space_factor, ValLevel::Int))
421        }
422    }
423
424    /// `\prevgraf` value.
425    pub fn fetch_prev_graf(&mut self) -> i32 {
426        if self.mode == crate::maincontrol::Mode::None {
427            return 0;
428        }
429        let mut p = self.nest.len() - 1;
430        while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
431            p -= 1;
432        }
433        self.nest[p].prev_graf
434    }
435
436    /// `\pagegoal` etc. values.
437    pub fn page_so_far_value(&self, m: u32) -> i32 {
438        // tex.web §421: on an empty page, outside the output routine,
439        // \pagegoal is \maxdimen and the other values are zero.
440        if self.page.contents == PageContents::Empty && !self.ts.output_active {
441            return if m == 0 { retex_arith::MAX_DIMEN } else { 0 };
442        }
443        self.page.so_far.get(m as usize).copied().unwrap_or(0)
444    }
445
446    /// `\lastpenalty`, `\lastkern`, `\lastskip`, `\lastnodetype` (§424).
447    pub fn fetch_last_node_item(&mut self, m: u32) -> (i32, crate::glue::Glue, ValLevel) {
448        use crate::nodes::NodeData;
449        use crate::scan::last_item as li;
450        let level = match m {
451            li::LAST_KERN => ValLevel::Dimen,
452            li::LAST_SKIP => ValLevel::Glue,
453            _ => ValLevel::Int,
454        };
455        let tail = self.cur_list().tail;
456        let head = self.cur_list().head;
457        if m == li::LAST_NODE_TYPE {
458            if tail == head {
459                if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
460                    return (self.page.last_node_type, Glue::ZERO, level);
461                }
462                return (-1, Glue::ZERO, level);
463            }
464            let v = etex_node_type(self.nodes.id(tail), self.nodes.subtype(tail));
465            return (v, Glue::ZERO, level);
466        }
467        if tail != head {
468            match &self.nodes.get(tail).data {
469                NodeData::Penalty { penalty } if m == li::LAST_PENALTY => {
470                    return (*penalty, Glue::ZERO, level);
471                }
472                NodeData::Kern { kern, .. } if m == li::LAST_KERN => {
473                    return (*kern, Glue::ZERO, level);
474                }
475                NodeData::Glue { spec, .. } if m == li::LAST_SKIP => {
476                    let l = if self.nodes.get(tail).subtype == crate::nodes::glue_sub::MU_GLUE {
477                        ValLevel::Mu
478                    } else {
479                        level
480                    };
481                    return (spec.width, *spec, l);
482                }
483                _ => {}
484            }
485        } else if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
486            match m {
487                li::LAST_PENALTY => return (self.page.last_penalty, Glue::ZERO, level),
488                li::LAST_KERN => return (self.page.last_kern, Glue::ZERO, level),
489                li::LAST_SKIP => {
490                    if let Some(g) = self.page.last_glue {
491                        return (g.width, g, level);
492                    }
493                }
494                _ => {}
495            }
496        }
497        (0, Glue::ZERO, level)
498    }
499
500    /// Whether box `p` is an hlist.
501    pub fn box_is_hlist(&self, p: NodeRef) -> bool {
502        self.nodes.id(p) == crate::nodes::id::HLIST
503    }
504
505    /// Current math style (`\mathstyle`), or −1 outside math.
506    pub fn cur_math_style(&self) -> i32 {
507        if self.mode.abs_kind() == crate::maincontrol::ModeKind::Math {
508            self.cur_list().math_style
509        } else {
510            -1
511        }
512    }
513
514    /// `\insertht`: the height of insertion class `n` on the current page.
515    pub fn insert_height(&self, n: u32) -> i32 {
516        self.page
517            .ins
518            .iter()
519            .find(|r| r.n == n)
520            .map_or(0, |r| r.height)
521    }
522}
523
524/// e-TeX's `\lastnodetype` value for a node of LuaTeX type `t`: −1 none,
525/// 0 glyph, 1 hlist, 2 vlist, 3 rule, 4 ins, 5 mark, 6 adjust,
526/// 7 ligature, 8 disc, 9 whatsit, 10 math, 11 glue, 12 kern,
527/// 13 penalty, 14 unset, 15 math mode nodes.
528pub fn etex_node_type(t: u8, subtype: u16) -> i32 {
529    use crate::nodes::id;
530    match t {
531        id::GLYPH => {
532            if subtype & crate::nodes::glyph_sub::LIGATURE != 0 {
533                7
534            } else {
535                0
536            }
537        }
538        id::HLIST => 1,
539        id::VLIST => 2,
540        id::RULE => 3,
541        id::INS => 4,
542        id::MARK => 5,
543        id::ADJUST => 6,
544        id::DISC => 8,
545        // Observed: a local_par node (at the start of a paragraph, after
546        // a display, or for a change of \localinterlinepenalty) counts as
547        // none.
548        id::LOCAL_PAR => -1,
549        id::WHATSIT | id::DIR | id::BOUNDARY => 9,
550        id::MATH => 10,
551        id::GLUE => 11,
552        id::KERN | id::MARGIN_KERN => 12,
553        id::PENALTY => 13,
554        id::UNSET => 14,
555        _ => 15,
556    }
557}