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    /// Insertion classes on the page, in increasing class order.
137    pub ins: Vec<PageIns>,
138    /// `\lastskip` etc. of the page (`last_glue`); `None` if the last item
139    /// was not glue.
140    pub last_glue: Option<Glue>,
141    /// `last_penalty`.
142    pub last_penalty: i32,
143    /// `last_kern`.
144    pub last_kern: i32,
145    /// `\lastnodetype` of the page.
146    pub last_node_type: i32,
147}
148
149impl PageState {
150    /// Allocates the list heads.
151    pub fn init(&mut self, nodes: &mut Nodes) {
152        self.contrib_head = nodes.new_temp();
153        self.page_head = nodes.new_temp();
154        self.page_tail = self.page_head;
155        self.last_node_type = -1;
156    }
157
158    /// The page stretch of glue order `o` (LuaTeX numbering).
159    pub fn stretch(&self, o: u8) -> i32 {
160        match o {
161            0 => self.so_far[2],
162            1 => self.fi_stretch,
163            2 => self.so_far[3],
164            3 => self.so_far[4],
165            _ => self.so_far[5],
166        }
167    }
168
169    /// Mutable access to the page stretch of order `o`.
170    pub fn stretch_mut(&mut self, o: u8) -> &mut i32 {
171        match o {
172            0 => &mut self.so_far[2],
173            1 => &mut self.fi_stretch,
174            2 => &mut self.so_far[3],
175            3 => &mut self.so_far[4],
176            _ => &mut self.so_far[5],
177        }
178    }
179}
180
181/// Mutable state of the typesetting routines.
182#[derive(Default)]
183pub struct TsState {
184    /// The nesting string of `show_box` (`cur_length` of TeX's string
185    /// pool).
186    pub show_prefix: Vec<u8>,
187    /// The font assumed at the start of `short_display` (−1 for none).
188    pub font_in_short_display: i64,
189    /// Whether the output routine is active.
190    pub output_active: bool,
191    /// Head of the adjustment list (`adjust_head`), allocated on first use.
192    pub adjust_head: NodeRef,
193    /// Head of the pre-adjustment list (LuaTeX's `\vadjust pre`).
194    pub pre_adjust_head: NodeRef,
195    /// Tail of the adjustment list collected by `hpack`, if active.
196    pub adjust_tail: Option<NodeRef>,
197    /// Tail of the pre-adjustment list collected by `hpack`, if active.
198    pub pre_adjust_tail: Option<NodeRef>,
199    /// Line where the current paragraph or alignment started
200    /// (`pack_begin_line`), negative for alignments.
201    pub pack_begin_line: i32,
202    /// Items removed by the page builder and `\vsplit` (`\pagediscards`,
203    /// `\splitdiscards`).
204    pub disc_ptr: [NodeRef; 4],
205    /// Cached current attribute list: (generation, list).
206    pub attr_cache: Option<(u64, Option<AttrList>)>,
207    /// Alignment state.
208    pub align: crate::align::AlignState,
209    /// Stretch and shrink totals of the most recent `hpack`/`vpack`.
210    pub last_totals: ([i32; 5], [i32; 5]),
211    /// The last line box made by `line_break` (`just_box`).
212    pub just_box: NodeRef,
213    /// The state of `mlist_to_hlist`.
214    pub math: crate::mlist::MathState,
215}
216
217impl std::fmt::Debug for TsState {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        f.debug_struct("TsState")
220            .field("output_active", &self.output_active)
221            .finish_non_exhaustive()
222    }
223}
224
225impl Engine {
226    /// Runs the node-list filter callback `cb` (LuaTeX's
227    /// `pre_linebreak_filter`, `hpack_filter`, …) on the list starting at
228    /// `head`; returns the new first node. A callback returning `false`
229    /// discards the list.
230    pub fn filter_list(&mut self, cb: &str, head: NodeRef, group: &str) -> NodeRef {
231        match self.run_list_filter(cb, head, group) {
232            crate::ListFilter::Keep(h) => {
233                if !h.is_null() {
234                    self.nodes.set_prev(h, NodeRef::NULL);
235                }
236                h
237            }
238            crate::ListFilter::Flush => {
239                self.nodes.flush_list(head);
240                NodeRef::NULL
241            }
242        }
243    }
244
245    /// Whether the current page and contributions are empty.
246    pub fn page_is_empty(&self) -> bool {
247        self.page.page_head == self.page.page_tail
248            && self.nodes.next(self.page.contrib_head).is_null()
249    }
250
251    /// Reports a command that is not implemented yet.
252    pub fn not_implemented(&mut self, what: &str) {
253        self.print_err("retex: not implemented yet: ");
254        self.printer.print_str(what);
255        self.help(&["This command is not supported by retex yet."]);
256        self.error();
257    }
258
259    /// The current attribute list (`current_attribute_list`).
260    pub fn cur_attr(&mut self) -> Option<AttrList> {
261        let generation = self.eqtb.attr_generation;
262        if let Some((g, a)) = &self.ts.attr_cache
263            && *g == generation
264        {
265            return a.clone();
266        }
267        let mut v: Vec<(u32, i32)> = Vec::new();
268        for (i, s) in self.eqtb.attr.iter().enumerate() {
269            if s.val != crate::eqtb::UNUSED_ATTRIBUTE {
270                v.push((i as u32, s.val));
271            }
272        }
273        let a: Option<AttrList> = if v.is_empty() {
274            None
275        } else {
276            Some(std::rc::Rc::from(v))
277        };
278        self.ts.attr_cache = Some((generation, a.clone()));
279        a
280    }
281
282    /// Allocates a node carrying the current attributes.
283    pub fn new_node(&mut self, id: u8, subtype: u16, data: crate::nodes::NodeData) -> NodeRef {
284        let p = self.nodes.alloc(id, subtype, data);
285        if self.eqtb.attrs_in_use {
286            let a = self.cur_attr();
287            self.nodes.set_attr(p, a);
288        }
289        p
290    }
291
292    /// `\spacefactor`, `\prevdepth` (`alter_aux`).
293    pub fn alter_aux(&mut self) {
294        let c = self.cur_chr;
295        if (c == SET_AUX_PREV_DEPTH)
296            != (self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical)
297        {
298            self.report_illegal_case();
299            return;
300        }
301        self.scan_optional_equals();
302        if c == SET_AUX_PREV_DEPTH {
303            self.scan_normal_dimen();
304            self.cur_list_mut().prev_depth = self.cur_val;
305        } else {
306            self.scan_int();
307            if self.cur_val <= 0 || self.cur_val > 32767 {
308                self.print_err("Bad space factor");
309                self.help(&["I allow only values in the range 1..32767 here."]);
310                self.int_error(self.cur_val);
311            } else {
312                self.cur_list_mut().space_factor = self.cur_val;
313            }
314        }
315    }
316
317    /// `\prevgraf` (`alter_prev_graf`).
318    pub fn alter_prev_graf(&mut self) {
319        self.scan_optional_equals();
320        self.scan_int();
321        let v = self.cur_val;
322        if v < 0 {
323            self.print_err("Bad ");
324            self.print_esc_str("prevgraf");
325            self.help(&["I allow only nonnegative values here."]);
326            self.int_error(v);
327        } else {
328            // TeX stores \prevgraf in the innermost non-math level.
329            let mut p = self.nest.len() - 1;
330            while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
331                p -= 1;
332            }
333            self.nest[p].prev_graf = v;
334        }
335    }
336
337    /// `\pagegoal` etc. (`alter_page_so_far`).
338    pub fn alter_page_so_far(&mut self) {
339        let c = self.cur_chr as usize;
340        self.scan_optional_equals();
341        self.scan_normal_dimen();
342        if c < self.page.so_far.len() {
343            self.page.so_far[c] = self.cur_val;
344        }
345    }
346
347    /// `\deadcycles`, `\insertpenalties` assignments (`alter_integer`) are
348    /// in the prefixed-command code; `\wd`, `\ht`, `\dp` assignments
349    /// (`alter_box_dimen`).
350    pub fn alter_box_dimen(&mut self) {
351        let c = self.cur_chr;
352        self.scan_register_num();
353        let b = self.eqtb.boxes[self.cur_val as usize].val;
354        self.scan_optional_equals();
355        self.scan_normal_dimen();
356        let v = self.cur_val;
357        if let Some(bd) = self.nodes.box_data_mut(b) {
358            match c {
359                2 => bd.width = v,
360                3 => bd.depth = v,
361                _ => bd.height = v,
362            }
363        }
364    }
365
366    /// `\spacefactor`/`\prevdepth` values for `\the`.
367    pub fn fetch_aux(&mut self, m: u32) -> Option<(i32, ValLevel)> {
368        let vertical = self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical;
369        if (m == SET_AUX_PREV_DEPTH) != vertical {
370            self.print_err("Improper ");
371            self.print_cmd_chr(crate::cmd::Cmd::SetAux, m);
372            self.help(&[
373                "You can refer to \\spacefactor only in horizontal mode;",
374                "you can refer to \\prevdepth only in vertical mode; and",
375                "neither of these is meaningful inside \\write. So",
376                "I'm forgetting what you said and using zero instead.",
377            ]);
378            self.error();
379            return Some((
380                0,
381                if m == SET_AUX_PREV_DEPTH {
382                    ValLevel::Dimen
383                } else {
384                    ValLevel::Int
385                },
386            ));
387        }
388        let cl = self.cur_list();
389        if m == SET_AUX_PREV_DEPTH {
390            Some((cl.prev_depth, ValLevel::Dimen))
391        } else {
392            Some((cl.space_factor, ValLevel::Int))
393        }
394    }
395
396    /// `\prevgraf` value.
397    pub fn fetch_prev_graf(&mut self) -> i32 {
398        if self.mode == crate::maincontrol::Mode::None {
399            return 0;
400        }
401        let mut p = self.nest.len() - 1;
402        while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
403            p -= 1;
404        }
405        self.nest[p].prev_graf
406    }
407
408    /// `\pagegoal` etc. values.
409    pub fn page_so_far_value(&self, m: u32) -> i32 {
410        if self.page.contents == PageContents::Empty && !self.ts.output_active && m == 0 {
411            return retex_arith::MAX_DIMEN;
412        }
413        self.page.so_far.get(m as usize).copied().unwrap_or(0)
414    }
415
416    /// `\lastpenalty`, `\lastkern`, `\lastskip`, `\lastnodetype` (§424).
417    pub fn fetch_last_node_item(&mut self, m: u32) -> (i32, crate::glue::Glue, ValLevel) {
418        use crate::nodes::NodeData;
419        use crate::scan::last_item as li;
420        let level = match m {
421            li::LAST_KERN => ValLevel::Dimen,
422            li::LAST_SKIP => ValLevel::Glue,
423            _ => ValLevel::Int,
424        };
425        let tail = self.cur_list().tail;
426        let head = self.cur_list().head;
427        if m == li::LAST_NODE_TYPE {
428            if tail == head {
429                if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
430                    return (self.page.last_node_type, Glue::ZERO, level);
431                }
432                return (-1, Glue::ZERO, level);
433            }
434            let v = etex_node_type(self.nodes.id(tail), self.nodes.subtype(tail));
435            return (v, Glue::ZERO, level);
436        }
437        if tail != head {
438            match &self.nodes.get(tail).data {
439                NodeData::Penalty { penalty } if m == li::LAST_PENALTY => {
440                    return (*penalty, Glue::ZERO, level);
441                }
442                NodeData::Kern { kern, .. } if m == li::LAST_KERN => {
443                    return (*kern, Glue::ZERO, level);
444                }
445                NodeData::Glue { spec, .. } if m == li::LAST_SKIP => {
446                    let l = if self.nodes.get(tail).subtype == crate::nodes::glue_sub::MU_GLUE {
447                        ValLevel::Mu
448                    } else {
449                        level
450                    };
451                    return (spec.width, *spec, l);
452                }
453                _ => {}
454            }
455        } else if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
456            match m {
457                li::LAST_PENALTY => return (self.page.last_penalty, Glue::ZERO, level),
458                li::LAST_KERN => return (self.page.last_kern, Glue::ZERO, level),
459                li::LAST_SKIP => {
460                    if let Some(g) = self.page.last_glue {
461                        return (g.width, g, level);
462                    }
463                }
464                _ => {}
465            }
466        }
467        (0, Glue::ZERO, level)
468    }
469
470    /// Whether box `p` is an hlist.
471    pub fn box_is_hlist(&self, p: NodeRef) -> bool {
472        self.nodes.id(p) == crate::nodes::id::HLIST
473    }
474
475    /// Current math style (`\mathstyle`), or −1 outside math.
476    pub fn cur_math_style(&self) -> i32 {
477        if self.mode.abs_kind() == crate::maincontrol::ModeKind::Math {
478            self.cur_list().math_style
479        } else {
480            -1
481        }
482    }
483
484    /// `\insertht`: the height of insertion class `n` on the current page.
485    pub fn insert_height(&self, n: u32) -> i32 {
486        self.page
487            .ins
488            .iter()
489            .find(|r| r.n == n)
490            .map_or(0, |r| r.height)
491    }
492}
493
494/// e-TeX's `\lastnodetype` value for a node of LuaTeX type `t`: −1 none,
495/// 0 glyph, 1 hlist, 2 vlist, 3 rule, 4 ins, 5 mark, 6 adjust,
496/// 7 ligature, 8 disc, 9 whatsit, 10 math, 11 glue, 12 kern,
497/// 13 penalty, 14 unset, 15 math mode nodes.
498pub fn etex_node_type(t: u8, subtype: u16) -> i32 {
499    use crate::nodes::id;
500    match t {
501        id::GLYPH => {
502            if subtype & crate::nodes::glyph_sub::LIGATURE != 0 {
503                7
504            } else {
505                0
506            }
507        }
508        id::HLIST => 1,
509        id::VLIST => 2,
510        id::RULE => 3,
511        id::INS => 4,
512        id::MARK => 5,
513        id::ADJUST => 6,
514        id::DISC => 8,
515        id::WHATSIT | id::LOCAL_PAR | id::DIR | id::BOUNDARY => 9,
516        id::MATH => 10,
517        id::GLUE => 11,
518        id::KERN | id::MARGIN_KERN => 12,
519        id::PENALTY => 13,
520        id::UNSET => 14,
521        _ => 15,
522    }
523}