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