Skip to main content

retex_engine/
linebreak.rs

1//! Breaking paragraphs into lines (*TeX: The Program* §813–§890, with
2//! e-TeX's `\lastlinefit` and penalty arrays, and LuaTeX's changes).
3//!
4//! LuaTeX differences reproduced here:
5//!
6//! * before breaking, the paragraph goes through the node processing
7//!   passes (hyphenation, ligatures, kerns; [`crate::nodeproc`]);
8//! * discretionaries carry their own penalty and hold their replacement
9//!   text, so the width of a line without a break at a discretionary
10//!   includes the replacement text;
11//! * at a chosen discretionary break, the pre-break text is placed before
12//!   the (now empty) discretionary node, which stays at the end of the
13//!   line; the post-break text starts the next line;
14//! * glue orders include `fi`;
15//! * `\tracingparagraphs` prints `@@n: line …` for new break nodes but a
16//!   single `@` when referring to them (`via @n`, `-> @n`).
17
18#![allow(clippy::needless_range_loop)]
19
20use std::rc::Rc;
21
22use crate::Engine;
23use crate::build::PackSpec;
24use crate::glue::Glue;
25use crate::nodes::{NodeData, NodeRef, glue_sub, id, kern_sub};
26use crate::primitives_gen::{
27    assign_dimen as dp, assign_glue as gp, assign_int as ip, set_etex_shape as es,
28};
29use crate::typeset::{AWFUL_BAD, EJECT_PENALTY, INF_BAD, INF_PENALTY};
30
31const VERY_LOOSE_FIT: usize = 0;
32const LOOSE_FIT: usize = 1;
33const DECENT_FIT: usize = 2;
34const TIGHT_FIT: usize = 3;
35
36/// Widths: natural width, stretch of orders normal, fi, fil, fill, filll,
37/// shrink, and the stretch and shrink of font expansion.
38type Widths = [i32; NW];
39
40/// The number of entries of [`Widths`].
41const NW: usize = 9;
42/// Index of the font expansion stretch in [`Widths`].
43const FONT_STRETCH: usize = 7;
44/// Index of the font expansion shrink in [`Widths`].
45const FONT_SHRINK: usize = 8;
46
47#[inline]
48fn add_glue(w: &mut Widths, g: &Glue) {
49    w[0] += g.width;
50    w[1 + (g.stretch_order as usize).min(4)] += g.stretch;
51    w[6] += g.shrink;
52}
53
54/// An entry of the active list.
55#[derive(Clone, Debug)]
56enum Entry {
57    /// The list head and terminator (`active` = `last_active`).
58    Sentinel,
59    /// An active breakpoint.
60    Active {
61        passive: Option<usize>,
62        line_number: i32,
63        fitness: usize,
64        hyphenated: bool,
65        total_demerits: i32,
66        short: i32,
67        glue: i32,
68    },
69    /// A delta node.
70    Delta(Widths),
71}
72
73struct Item {
74    next: usize,
75    entry: Entry,
76}
77
78/// A passive breakpoint record.
79struct Passive {
80    cur_break: NodeRef,
81    prev_break: Option<usize>,
82    serial: i32,
83    /// Left protrusion of the line after this break (`\protrudechars` > 1).
84    left_pw: i32,
85}
86
87/// The state of one `line_break` call.
88struct Breaker {
89    items: Vec<Item>,
90    free: Vec<usize>,
91    passive: Vec<Passive>,
92    active_width: Widths,
93    cur_active_width: Widths,
94    background: Widths,
95    break_width: Widths,
96    minimal_demerits: [i32; 4],
97    minimum_demerits: i32,
98    best_place: [Option<usize>; 4],
99    best_pl_line: [i32; 4],
100    best_pl_short: [i32; 4],
101    best_pl_glue: [i32; 4],
102    threshold: i32,
103    second_pass: bool,
104    final_pass: bool,
105    disc_width: i32,
106    /// Font stretch and shrink of the pre-break text being tried.
107    disc_fs: crate::expansion::FontStretch,
108    easy_line: i32,
109    last_special_line: i32,
110    first_width: i32,
111    second_width: i32,
112    first_indent: i32,
113    second_indent: i32,
114    par_shape: Option<std::rc::Rc<[i32]>>,
115    printed_node: NodeRef,
116    pass_number: i32,
117    do_last_line_fit: bool,
118    fill_width: [i32; 4],
119    last_line_fit: i32,
120    adj_demerits: i32,
121    line_penalty: i32,
122    double_hyphen_demerits: i32,
123    final_hyphen_demerits: i32,
124    tracing: bool,
125    no_shrink_error_yet: bool,
126    /// `\protrudechars` > 1: protrusion counts in line breaking.
127    protrude: bool,
128    /// Left protrusion of the first line.
129    first_left_pw: i32,
130    /// The list head.
131    head: NodeRef,
132    /// `\adjustspacing` > 1: font expansion counts in line breaking.
133    expand: bool,
134    /// Kerns take part in font expansion (`\adjustspacing` = 2).
135    expand_kerns: bool,
136}
137
138const ACTIVE: usize = 0;
139
140impl Breaker {
141    fn alloc(&mut self, e: Entry, next: usize) -> usize {
142        if let Some(i) = self.free.pop() {
143            self.items[i] = Item { next, entry: e };
144            i
145        } else {
146            self.items.push(Item { next, entry: e });
147            self.items.len() - 1
148        }
149    }
150
151    fn release(&mut self, i: usize) {
152        self.free.push(i);
153    }
154
155    fn is_delta(&self, i: usize) -> bool {
156        matches!(self.items[i].entry, Entry::Delta(_))
157    }
158
159    fn delta(&self, i: usize) -> Widths {
160        match self.items[i].entry {
161            Entry::Delta(d) => d,
162            _ => [0; NW],
163        }
164    }
165
166    fn line_number(&self, i: usize) -> i32 {
167        match self.items[i].entry {
168            Entry::Active { line_number, .. } => line_number,
169            Entry::Sentinel => i32::MAX,
170            Entry::Delta(_) => 0,
171        }
172    }
173
174    /// Frees all active and passive nodes.
175    fn clean_up(&mut self) {
176        self.items.truncate(1);
177        self.items[ACTIVE].next = ACTIVE;
178        self.free.clear();
179        self.passive.clear();
180    }
181}
182
183/// The parameters of line breaking: the current values of TeX's
184/// parameters for a paragraph, or those given to `tex.linebreak`.
185#[derive(Clone, Debug)]
186pub struct ParParams {
187    /// `\pretolerance`.
188    pub pretolerance: i32,
189    /// `\tolerance`.
190    pub tolerance: i32,
191    /// `\looseness`.
192    pub looseness: i32,
193    /// `\adjdemerits`.
194    pub adj_demerits: i32,
195    /// `\linepenalty`.
196    pub line_penalty: i32,
197    /// `\lastlinefit`.
198    pub last_line_fit: i32,
199    /// `\doublehyphendemerits`.
200    pub double_hyphen_demerits: i32,
201    /// `\finalhyphendemerits`.
202    pub final_hyphen_demerits: i32,
203    /// `\hangafter`.
204    pub hang_after: i32,
205    /// `\hangindent`.
206    pub hang_indent: i32,
207    /// `\hsize`.
208    pub hsize: i32,
209    /// `\emergencystretch`.
210    pub emergency_stretch: i32,
211    /// `\brokenpenalty`.
212    pub broken_penalty: i32,
213    /// `\protrudechars`.
214    pub protrude_chars: i32,
215    /// `\adjustspacing`.
216    pub adjust_spacing: i32,
217    /// `\tracingparagraphs`.
218    pub tracing_paragraphs: i32,
219    /// `\interlinepenalty`.
220    pub interline_penalty: i32,
221    /// `\clubpenalty`.
222    pub club_penalty: i32,
223    /// The penalty before the last line (`\widowpenalty` or
224    /// `\displaywidowpenalty`).
225    pub widow_penalty: i32,
226    /// `\interlinepenalties` (values, without their count).
227    pub interline_penalties: Option<Rc<[i32]>>,
228    /// `\clubpenalties`.
229    pub club_penalties: Option<Rc<[i32]>>,
230    /// `\widowpenalties` or `\displaywidowpenalties`.
231    pub widow_penalties: Option<Rc<[i32]>>,
232    /// `\leftskip`.
233    pub left_skip: Glue,
234    /// `\rightskip`.
235    pub right_skip: Glue,
236    /// `\parshape`: indentation and width of each line.
237    pub par_shape: Option<Rc<[i32]>>,
238    /// `\pardir`.
239    pub par_dir: i8,
240}
241
242/// What `tex.linebreak` reports about a paragraph it broke.
243#[derive(Clone, Copy, Debug)]
244pub struct LineBreakInfo {
245    /// The depth of the last line.
246    pub prev_depth: i32,
247    /// The number of lines.
248    pub prev_graf: i32,
249    /// The actual looseness.
250    pub looseness: i32,
251    /// The total demerits of the chosen breaks.
252    pub demerits: i32,
253}
254
255impl Engine {
256    /// Width of a node in a discretionary text.
257    fn disc_node_width(&self, s: NodeRef) -> i32 {
258        match self.nodes.data(s) {
259            NodeData::Glyph(_) => self.glyph_dimensions(s).0,
260            _ => self.nodes.width(s),
261        }
262    }
263
264    fn list_width(&self, mut s: NodeRef) -> i32 {
265        let mut w = 0;
266        while !s.is_null() {
267            w += self.disc_node_width(s);
268            s = self.nodes.next(s);
269        }
270        w
271    }
272
273    /// Recovers from infinite shrinkage in a paragraph (`finite_shrink`).
274    fn finite_shrink(&mut self, br: &mut Breaker, g: Glue) -> Glue {
275        if br.no_shrink_error_yet {
276            br.no_shrink_error_yet = false;
277            if br.tracing {
278                self.end_diagnostic(true);
279            }
280            self.print_err("Infinite glue shrinkage found in a paragraph");
281            self.help(&[
282                "The paragraph just ended includes some glue that has",
283                "infinite shrinkability, e.g., `\\hskip 0pt minus 1fil'.",
284                "Such glue doesn't belong there---it allows a paragraph",
285                "of any length to fit on one line. But it's safe to proceed,",
286                "since the offensive shrinkability has been made finite.",
287            ]);
288            self.error();
289            if br.tracing {
290                self.begin_diagnostic();
291            }
292        }
293        Glue {
294            shrink_order: 0,
295            ..g
296        }
297    }
298
299    /// Breaks the current paragraph into lines and appends them to the
300    /// enclosing vertical list (`line_break`). `widow_penalty` is the
301    /// penalty before the last line (`\widowpenalty` or
302    /// `\displaywidowpenalty`).
303    pub fn line_break(&mut self, final_widow_penalty: i32) {
304        self.work_begin(crate::profile::Work::LineBreak);
305        self.line_break_inner(final_widow_penalty);
306        self.work_end();
307    }
308
309    fn line_break_inner(&mut self, final_widow_penalty: i32) {
310        self.fonts.sync_dense();
311        // A display interrupts the paragraph: `$$` was just read. The
312        // callbacks then get the group code `math_shift` (observed).
313        let display = self.cur_cmd == crate::cmd::Cmd::MathShift;
314        let group = if display { "math_shift" } else { "" };
315        self.ts.pack_begin_line = self.cur_list().mode_line;
316        // Get ready to start: move the list to temp_head and append the
317        // \parfillskip glue.
318        let temp_head = self.nodes.new_temp();
319        let head = self.cur_list().head;
320        let first = self.nodes.next(head);
321        self.nodes.set_next(head, NodeRef::NULL);
322        // LuaTeX: the node processing passes (and their callbacks) see the
323        // paragraph as it is, before its final glue goes and \parfillskip
324        // comes (observed).
325        let first = self.node_process_list(first, crate::nodeproc::Context::Paragraph);
326        self.nodes.link(temp_head, first);
327        if first.is_null() {
328            self.nodes.set_next(temp_head, NodeRef::NULL);
329        }
330        let mut tail = if first.is_null() {
331            temp_head
332        } else {
333            self.nodes.tail_of(first)
334        };
335        if self.nodes.id(tail) == id::GLUE {
336            // Remove a final glue node.
337            let p = self.find_prev_in(temp_head, tail);
338            self.nodes.set_next(p, NodeRef::NULL);
339            self.nodes.flush_list(tail);
340            tail = p;
341        }
342        let pen = self.new_penalty_node(INF_PENALTY);
343        // Observed: subtype 2 (`linepenalty`).
344        self.nodes.set_subtype(pen, 2);
345        self.nodes.link(tail, pen);
346        let pfs = self.new_param_glue(glue_sub::PAR_FILL_SKIP);
347        self.nodes.link(pen, pfs);
348        let last_line_fill = pfs;
349        self.pop_nest();
350        // LuaTeX: the pre_linebreak_filter.
351        let first = self.nodes.next(temp_head);
352        let had_list = !first.is_null();
353        let first = self.filter_list("pre_linebreak_filter", first, group);
354        if had_list && first.is_null() {
355            // Observed: a callback that empties the paragraph (returning
356            // false or nil) removes its local_par node, and LuaTeX stops.
357            self.confusion("weird par dir");
358        }
359        self.nodes.link(temp_head, first);
360        if first.is_null() {
361            self.nodes.set_next(temp_head, NodeRef::NULL);
362        }
363        let old_tail = self.cur_list().tail;
364        let replaced = match self.lua.clone() {
365            Some(h) if h.has_callback("linebreak_filter") => {
366                self.figure_callback("linebreak_filter");
367                let first = self.nodes.next(temp_head);
368                h.linebreak_filter(self, first, display)
369            }
370            _ => None,
371        };
372        if let Some(list) = replaced {
373            // The callback's list goes to the vertical list as it is;
374            // `\prevdepth` and `\prevgraf` stay (observed).
375            let mut p = list;
376            while !p.is_null() && self.nodes.id(p) != id::HLIST {
377                p = self.nodes.next(p);
378            }
379            if p.is_null() {
380                self.print_err("Invalid linebreak_filter");
381                self.help(&[
382                    "A linebreaking routine should return a non-empty list of nodes",
383                    "and at least one of those has to be a \\hbox.",
384                    "Sorry, I cannot recover from this.",
385                ]);
386                self.succumb();
387            }
388            self.nodes.link(old_tail, list);
389            let t = self.nodes.tail_of(list);
390            self.cur_list_mut().tail = t;
391        } else {
392            let params = self.par_params(final_widow_penalty, display);
393            self.break_paragraph(temp_head, Some(last_line_fill), &params);
394        }
395        if self.has_callback("post_linebreak_filter") {
396            let first = self.nodes.next(old_tail);
397            self.nodes.set_next(old_tail, NodeRef::NULL);
398            let first = self.filter_list("post_linebreak_filter", first, group);
399            self.cur_list_mut().tail = old_tail;
400            if !first.is_null() {
401                self.nodes.link(old_tail, first);
402                let t = self.nodes.tail_of(first);
403                self.cur_list_mut().tail = t;
404            }
405        }
406        self.nodes.free(temp_head);
407        self.ts.pack_begin_line = 0;
408    }
409
410    /// The line breaking parameters of a paragraph: the current values,
411    /// with `widow_penalty` (`\widowpenalty` or `\displaywidowpenalty`)
412    /// before the last line.
413    pub fn par_params(&self, widow_penalty: i32, display: bool) -> ParParams {
414        let array = |k: u32| self.eqtb.etex_shape[k as usize].val.clone();
415        ParParams {
416            pretolerance: self.eqtb.int(ip::PRETOLERANCE),
417            tolerance: self.eqtb.int(ip::TOLERANCE),
418            looseness: self.eqtb.int(ip::LOOSENESS),
419            adj_demerits: self.eqtb.int(ip::ADJDEMERITS),
420            line_penalty: self.eqtb.int(ip::LINEPENALTY),
421            last_line_fit: self.eqtb.int(ip::LASTLINEFIT),
422            double_hyphen_demerits: self.eqtb.int(ip::DOUBLEHYPHENDEMERITS),
423            final_hyphen_demerits: self.eqtb.int(ip::FINALHYPHENDEMERITS),
424            hang_after: self.eqtb.int(ip::HANGAFTER),
425            hang_indent: self.eqtb.dimen(dp::HANGINDENT),
426            hsize: self.eqtb.dimen(dp::HSIZE),
427            emergency_stretch: self.eqtb.dimen(dp::EMERGENCYSTRETCH),
428            broken_penalty: self.eqtb.int(ip::BROKENPENALTY),
429            protrude_chars: self.eqtb.int(ip::PROTRUDECHARS),
430            adjust_spacing: self.eqtb.int(ip::ADJUSTSPACING),
431            tracing_paragraphs: self.eqtb.int(ip::TRACINGPARAGRAPHS),
432            interline_penalty: self.eqtb.int(ip::INTERLINEPENALTY),
433            club_penalty: self.eqtb.int(ip::CLUBPENALTY),
434            widow_penalty,
435            interline_penalties: array(es::INTERLINEPENALTIES),
436            club_penalties: array(es::CLUBPENALTIES),
437            widow_penalties: array(if display {
438                es::DISPLAYWIDOWPENALTIES
439            } else {
440                es::WIDOWPENALTIES
441            }),
442            left_skip: self.eqtb.glue(gp::LEFTSKIP),
443            right_skip: self.eqtb.glue(gp::RIGHTSKIP),
444            par_shape: self.eqtb.par_shape.val.clone(),
445            par_dir: self.dir_param(crate::primitives_gen::assign_dir::PARDIR) as i8,
446        }
447    }
448
449    /// `tex.linebreak`: breaks the list `head` (which should end with
450    /// the `\parfillskip` glue) into lines with parameters `p`, and
451    /// returns the vertical list of lines, with interline glue and
452    /// penalties, and what the breaking found. The list is neither
453    /// hyphenated nor given to callbacks; the current list's
454    /// `\prevdepth` starts the interline glue, and line numbers start
455    /// at 1 (observed).
456    pub fn lua_line_break(&mut self, head: NodeRef, p: &ParParams) -> (NodeRef, LineBreakInfo) {
457        if self.nodes.id(self.nodes.tail_of(head)) == id::GLYPH {
458            // Observed: LuaTeX's check of the list end.
459            self.luatex_fatal("linebreak", "invalid list tail, probably missing glue");
460        }
461        self.fonts.sync_dense();
462        let prev_depth = self.cur_list().prev_depth;
463        self.push_nest(crate::maincontrol::Mode::InternalVertical);
464        self.cur_list_mut().prev_depth = prev_depth;
465        let temp_head = self.nodes.new_temp();
466        self.nodes.link(temp_head, head);
467        let (looseness, demerits) = self.break_paragraph(temp_head, None, p);
468        self.nodes.free(temp_head);
469        let list_head = self.cur_list().head;
470        let lines = self.nodes.next(list_head);
471        self.nodes.set_next(list_head, NodeRef::NULL);
472        if !lines.is_null() {
473            self.nodes.set_prev(lines, NodeRef::NULL);
474        }
475        let info = LineBreakInfo {
476            prev_depth: self.cur_list().prev_depth,
477            prev_graf: self.cur_list().prev_graf,
478            looseness,
479            demerits,
480        };
481        self.pop_nest();
482        (lines, info)
483    }
484
485    /// Breaks the list after `temp_head` into lines with parameters `p`
486    /// and appends them to the current vertical list. `last_line_fill`
487    /// is the `\parfillskip` glue, for `\lastlinefit`. Returns the actual
488    /// looseness and the total demerits of the chosen breaks.
489    fn break_paragraph(
490        &mut self,
491        temp_head: NodeRef,
492        last_line_fill: Option<NodeRef>,
493        p: &ParParams,
494    ) -> (i32, i32) {
495        let mut br = Breaker {
496            items: vec![Item {
497                next: ACTIVE,
498                entry: Entry::Sentinel,
499            }],
500            free: Vec::new(),
501            passive: Vec::new(),
502            active_width: [0; NW],
503            cur_active_width: [0; NW],
504            background: [0; NW],
505            break_width: [0; NW],
506            minimal_demerits: [AWFUL_BAD; 4],
507            minimum_demerits: AWFUL_BAD,
508            best_place: [None; 4],
509            best_pl_line: [0; 4],
510            best_pl_short: [0; 4],
511            best_pl_glue: [0; 4],
512            threshold: 0,
513            second_pass: false,
514            final_pass: false,
515            disc_width: 0,
516            disc_fs: Default::default(),
517            easy_line: 0,
518            last_special_line: 0,
519            first_width: 0,
520            second_width: 0,
521            first_indent: 0,
522            second_indent: 0,
523            par_shape: p.par_shape.clone(),
524            printed_node: temp_head,
525            pass_number: 0,
526            do_last_line_fit: false,
527            fill_width: [0; 4],
528            last_line_fit: p.last_line_fit,
529            adj_demerits: p.adj_demerits,
530            line_penalty: p.line_penalty,
531            double_hyphen_demerits: p.double_hyphen_demerits,
532            final_hyphen_demerits: p.final_hyphen_demerits,
533            tracing: p.tracing_paragraphs > 0,
534            no_shrink_error_yet: true,
535            protrude: p.protrude_chars > 1,
536            first_left_pw: 0,
537            head: temp_head,
538            expand: p.adjust_spacing > 1,
539            expand_kerns: p.adjust_spacing == 2,
540        };
541        if br.expand && !br.protrude {
542            self.fix_back_links(temp_head);
543        }
544        if br.protrude {
545            self.fix_back_links(temp_head);
546            let first = self.nodes.next(temp_head);
547            br.first_left_pw = self.lb_left_protrusion(None, first);
548        }
549        let mut ls = p.left_skip;
550        let mut rs = p.right_skip;
551        if ls.shrink_order != 0 && ls.shrink != 0 {
552            ls = self.finite_shrink(&mut br, ls);
553        }
554        if rs.shrink_order != 0 && rs.shrink != 0 {
555            rs = self.finite_shrink(&mut br, rs);
556        }
557        br.background[0] = ls.width + rs.width;
558        br.background[1 + (ls.stretch_order as usize).min(4)] += ls.stretch;
559        br.background[1 + (rs.stretch_order as usize).min(4)] += rs.stretch;
560        br.background[6] = ls.shrink + rs.shrink;
561        // Check for special treatment of the last line (\lastlinefit).
562        if let Some(last_line_fill) = last_line_fill
563            && br.last_line_fit > 0
564        {
565            let q = self.nodes.glue_spec(last_line_fill);
566            if q.stretch > 0 && q.stretch_order > 0 && br.background[2..6].iter().all(|&v| v == 0) {
567                br.do_last_line_fit = true;
568                br.fill_width = [0; 4];
569                br.fill_width[(q.stretch_order as usize - 1).min(3)] = q.stretch;
570            }
571        }
572        // Line lengths.
573        let hsize = p.hsize;
574        let hang_indent = p.hang_indent;
575        let hang_after = p.hang_after;
576        match &br.par_shape {
577            None => {
578                if hang_indent == 0 {
579                    br.last_special_line = 0;
580                    br.second_width = hsize;
581                    br.second_indent = 0;
582                } else {
583                    br.last_special_line = hang_after.abs();
584                    if hang_after < 0 {
585                        br.first_width = hsize - hang_indent.abs();
586                        br.first_indent = hang_indent.max(0);
587                        br.second_width = hsize;
588                        br.second_indent = 0;
589                    } else {
590                        br.first_width = hsize;
591                        br.first_indent = 0;
592                        br.second_width = hsize - hang_indent.abs();
593                        br.second_indent = hang_indent.max(0);
594                    }
595                }
596            }
597            Some(s) => {
598                let n = (s.len() / 2) as i32;
599                br.last_special_line = n - 1;
600                br.second_indent = s[2 * (n as usize - 1)];
601                br.second_width = s[2 * (n as usize - 1) + 1];
602            }
603        }
604        br.easy_line = if p.looseness == 0 {
605            br.last_special_line
606        } else {
607            i32::MAX - 1
608        };
609        // Find optimal breakpoints.
610        br.threshold = p.pretolerance;
611        let emergency = p.emergency_stretch;
612        if br.threshold >= 0 {
613            if br.tracing {
614                self.begin_diagnostic();
615                self.printer.print_nl("@firstpass");
616            }
617            br.second_pass = false;
618            br.final_pass = false;
619        } else {
620            br.threshold = p.tolerance;
621            br.second_pass = true;
622            br.final_pass = emergency <= 0;
623            if br.tracing {
624                self.begin_diagnostic();
625            }
626        }
627        let prev_graf = self.cur_list().prev_graf;
628        let looseness = p.looseness;
629        let best_bet: usize;
630        let best_line: i32;
631        let mut actual_looseness = 0;
632        loop {
633            if br.threshold > INF_BAD {
634                br.threshold = INF_BAD;
635            }
636            // Create an active breakpoint for the beginning.
637            let q = br.alloc(
638                Entry::Active {
639                    passive: None,
640                    line_number: prev_graf + 1,
641                    fitness: DECENT_FIT,
642                    hyphenated: false,
643                    total_demerits: 0,
644                    short: 0,
645                    glue: 0,
646                },
647                ACTIVE,
648            );
649            br.items[ACTIVE].next = q;
650            br.active_width = br.background;
651            br.passive.clear();
652            br.printed_node = temp_head;
653            br.pass_number = 0;
654            self.ts.font_in_short_display = 0;
655            let mut cur_p = self.nodes.next(temp_head);
656            let mut auto_breaking = true;
657            let mut prev_p = cur_p;
658            while !cur_p.is_null() && br.items[ACTIVE].next != ACTIVE {
659                let t = self.nodes.id(cur_p);
660                match t {
661                    id::GLYPH => {
662                        // Advance past a string of characters.
663                        prev_p = cur_p;
664                        while !cur_p.is_null() && self.nodes.id(cur_p) == id::GLYPH {
665                            self.check_lost_char(cur_p);
666                            br.active_width[0] += self.glyph_dimensions(cur_p).0;
667                            if br.expand {
668                                let f = self.glyph_font_stretch(cur_p);
669                                br.active_width[FONT_STRETCH] += f.stretch;
670                                br.active_width[FONT_SHRINK] += f.shrink;
671                            }
672                            cur_p = self.nodes.next(cur_p);
673                        }
674                        continue;
675                    }
676                    id::HLIST | id::VLIST | id::RULE => {
677                        br.active_width[0] += self.nodes.width(cur_p);
678                    }
679                    id::GLUE => {
680                        if auto_breaking {
681                            let pt = self.nodes.id(prev_p);
682                            // Observed: not after the paragraph's local_par
683                            // node (`\noindent\hskip0pt` makes no empty first
684                            // line; soul's syllable analysis relies on it).
685                            let legal = pt == id::GLYPH
686                                || (crate::page::precedes_break(pt) && pt != id::LOCAL_PAR)
687                                || (pt == id::KERN
688                                    && !matches!(
689                                        self.nodes.subtype(prev_p),
690                                        kern_sub::USER | kern_sub::ITALIC
691                                    ));
692                            if legal {
693                                self.try_break(&mut br, cur_p, 0, false);
694                            }
695                        }
696                        let mut g = self.nodes.glue_spec(cur_p);
697                        if g.shrink_order != 0 && g.shrink != 0 {
698                            g = self.finite_shrink(&mut br, g);
699                            if let NodeData::Glue { spec, .. } = self.nodes.data_mut(cur_p) {
700                                *spec = g;
701                            }
702                        }
703                        add_glue(&mut br.active_width, &g);
704                    }
705                    id::KERN => {
706                        if matches!(self.nodes.subtype(cur_p), kern_sub::USER | kern_sub::ITALIC) {
707                            self.kern_break(&mut br, cur_p, auto_breaking);
708                        } else {
709                            br.active_width[0] += self.nodes.width(cur_p);
710                            if br.expand_kerns {
711                                let (pv, nx) = (self.nodes.prev(cur_p), self.nodes.next(cur_p));
712                                let f = self.kern_font_stretch(pv, cur_p, nx);
713                                br.active_width[FONT_STRETCH] += f.stretch;
714                                br.active_width[FONT_SHRINK] += f.shrink;
715                            }
716                        }
717                    }
718                    id::DISC => {
719                        let (pre, _post, rep) = self.nodes.disc_lists(cur_p);
720                        let pen = self.nodes.penalty(cur_p);
721                        // LuaTeX hyphenates before breaking lines; its first
722                        // pass ignores the discretionaries hyphenation made
723                        // (subtypes 3–5), like TeX82's first pass, which
724                        // runs before hyphenation.
725                        let from_hyphenation = self.nodes.subtype(cur_p) >= 3;
726                        if br.second_pass || !from_hyphenation {
727                            br.disc_width = self.list_width(pre);
728                            br.active_width[0] += br.disc_width;
729                            if br.expand {
730                                br.disc_fs = self.list_font_stretch(pre, br.expand_kerns);
731                                br.active_width[FONT_STRETCH] += br.disc_fs.stretch;
732                                br.active_width[FONT_SHRINK] += br.disc_fs.shrink;
733                            }
734                            self.try_break(&mut br, cur_p, pen, true);
735                            br.active_width[0] -= br.disc_width;
736                            if br.expand {
737                                br.active_width[FONT_STRETCH] -= br.disc_fs.stretch;
738                                br.active_width[FONT_SHRINK] -= br.disc_fs.shrink;
739                            }
740                        }
741                        br.active_width[0] += self.list_width(rep);
742                        if br.expand {
743                            let f = self.list_font_stretch(rep, br.expand_kerns);
744                            br.active_width[FONT_STRETCH] += f.stretch;
745                            br.active_width[FONT_SHRINK] += f.shrink;
746                        }
747                    }
748                    id::MATH => {
749                        auto_breaking = self.nodes.subtype(cur_p) == 1;
750                        self.kern_break(&mut br, cur_p, auto_breaking);
751                    }
752                    id::PENALTY => {
753                        let pen = self.nodes.penalty(cur_p);
754                        self.try_break(&mut br, cur_p, pen, false);
755                    }
756                    id::LOCAL_PAR
757                    | id::DIR
758                    | id::BOUNDARY
759                    | id::MARK
760                    | id::INS
761                    | id::ADJUST
762                    | id::WHATSIT => {}
763                    id::GLUE_SPEC => {
764                        self.luatex_warning("parbuilder", "found a glue_spec in a paragraph")
765                    }
766                    // Observed: other nodes (margin kerns, math noads,
767                    // unset nodes, ...), which Lua code can put in a
768                    // paragraph, stop LuaTeX.
769                    _ => self.luatex_fatal("parbuilder", &format!("weird node {t} in paragraph")),
770                }
771                prev_p = cur_p;
772                cur_p = self.nodes.next(cur_p);
773            }
774            if cur_p.is_null() {
775                // Try the final line break at the end of the paragraph.
776                self.try_break(&mut br, NodeRef::NULL, EJECT_PENALTY, true);
777                if br.items[ACTIVE].next != ACTIVE {
778                    // Find an active node with fewest demerits.
779                    let mut r = br.items[ACTIVE].next;
780                    let mut fewest = AWFUL_BAD;
781                    let mut bb = r;
782                    while r != ACTIVE {
783                        if let Entry::Active { total_demerits, .. } = br.items[r].entry
784                            && total_demerits < fewest
785                        {
786                            fewest = total_demerits;
787                            bb = r;
788                        }
789                        r = br.items[r].next;
790                    }
791                    let mut bl = br.line_number(bb);
792                    if looseness == 0 {
793                        best_bet = bb;
794                        best_line = bl;
795                        break;
796                    }
797                    // Find the best active node for the desired looseness.
798                    let mut r = br.items[ACTIVE].next;
799                    let mut actual = 0;
800                    let mut bb2 = bb;
801                    while r != ACTIVE {
802                        if let Entry::Active {
803                            line_number,
804                            total_demerits,
805                            ..
806                        } = br.items[r].entry
807                        {
808                            let line_diff = line_number - bl;
809                            if (line_diff < actual && looseness <= line_diff)
810                                || (line_diff > actual && looseness >= line_diff)
811                            {
812                                bb2 = r;
813                                actual = line_diff;
814                                fewest = total_demerits;
815                            } else if line_diff == actual && total_demerits < fewest {
816                                bb2 = r;
817                                fewest = total_demerits;
818                            }
819                        }
820                        r = br.items[r].next;
821                    }
822                    bl = br.line_number(bb2);
823                    if actual == looseness || br.final_pass {
824                        actual_looseness = actual;
825                        best_bet = bb2;
826                        best_line = bl;
827                        break;
828                    }
829                }
830            }
831            // Clean up and try again.
832            br.clean_up();
833            if !br.second_pass {
834                if br.tracing {
835                    self.printer.print_nl("@secondpass");
836                }
837                br.threshold = p.tolerance;
838                br.second_pass = true;
839                br.final_pass = emergency <= 0;
840            } else {
841                if br.tracing {
842                    self.printer.print_nl("@emergencypass");
843                }
844                br.background[1] += emergency;
845                br.final_pass = true;
846            }
847        }
848        if br.tracing {
849            self.end_diagnostic(true);
850            self.normalize_selector();
851        }
852        let demerits = match br.items[best_bet].entry {
853            Entry::Active { total_demerits, .. } => total_demerits,
854            _ => 0,
855        };
856        if let Some(last_line_fill) = last_line_fill
857            && br.do_last_line_fit
858        {
859            // Adjust the final line of the paragraph.
860            if let Entry::Active { short, glue, .. } = br.items[best_bet].entry {
861                if short == 0 {
862                    br.do_last_line_fit = false;
863                } else if let NodeData::Glue { spec, .. } = self.nodes.data_mut(last_line_fill) {
864                    spec.width += short - glue;
865                    spec.stretch = 0;
866                }
867            }
868        }
869        // Break the paragraph at the chosen breakpoints.
870        self.post_line_break(&mut br, best_bet, best_line, temp_head, p);
871        br.clean_up();
872        (actual_looseness, demerits)
873    }
874
875    fn find_prev_in(&self, head: NodeRef, q: NodeRef) -> NodeRef {
876        let mut p = head;
877        loop {
878            let n = self.nodes.next(p);
879            if n == q || n.is_null() {
880                return p;
881            }
882            p = n;
883        }
884    }
885
886    /// `kern_break`: a kern or math node may be a breakpoint if followed by
887    /// glue.
888    fn kern_break(&mut self, br: &mut Breaker, cur_p: NodeRef, auto_breaking: bool) {
889        let n = self.nodes.next(cur_p);
890        if !n.is_null() && auto_breaking && self.nodes.id(n) == id::GLUE {
891            self.try_break(br, cur_p, 0, false);
892        }
893        br.active_width[0] += self.nodes.width(cur_p);
894    }
895
896    /// Tests a breakpoint (`try_break`).
897    fn try_break(&mut self, br: &mut Breaker, cur_p: NodeRef, pi: i32, hyphenated: bool) {
898        let mut pi = pi;
899        if pi.abs() >= INF_PENALTY {
900            if pi > 0 {
901                return;
902            }
903            pi = EJECT_PENALTY;
904        }
905        let mut no_break_yet = true;
906        let mut prev_r = ACTIVE;
907        let mut prev_prev_r = ACTIVE;
908        let mut old_l = 0;
909        let mut line_width = 0;
910        let mut right_pw: Option<i32> = None;
911        br.cur_active_width = br.active_width;
912        loop {
913            let r = br.items[prev_r].next;
914            // If node r is a delta node, update cur_active_width.
915            if let Entry::Delta(d) = br.items[r].entry {
916                for k in 0..NW {
917                    br.cur_active_width[k] += d[k];
918                }
919                prev_prev_r = prev_r;
920                prev_r = r;
921                continue;
922            }
923            // If a line number class has ended, create new active nodes for
924            // the best feasible breaks in that class.
925            let l = br.line_number(r);
926            if l > old_l {
927                if br.minimum_demerits < AWFUL_BAD && (old_l != br.easy_line || r == ACTIVE) {
928                    if no_break_yet {
929                        no_break_yet = false;
930                        self.compute_break_width(br, cur_p, hyphenated);
931                    }
932                    // Insert a delta node to prepare for breaks at cur_p.
933                    if br.is_delta(prev_r) {
934                        let mut d = br.delta(prev_r);
935                        for k in 0..NW {
936                            d[k] = d[k] - br.cur_active_width[k] + br.break_width[k];
937                        }
938                        br.items[prev_r].entry = Entry::Delta(d);
939                    } else if prev_r == ACTIVE {
940                        br.active_width = br.break_width;
941                    } else {
942                        let mut d = [0; NW];
943                        for k in 0..NW {
944                            d[k] = br.break_width[k] - br.cur_active_width[k];
945                        }
946                        let q = br.alloc(Entry::Delta(d), r);
947                        br.items[prev_r].next = q;
948                        prev_prev_r = prev_r;
949                        prev_r = q;
950                    }
951                    if br.adj_demerits.abs() >= AWFUL_BAD - br.minimum_demerits {
952                        br.minimum_demerits = AWFUL_BAD - 1;
953                    } else {
954                        br.minimum_demerits += br.adj_demerits.abs();
955                    }
956                    for fit_class in VERY_LOOSE_FIT..=TIGHT_FIT {
957                        if br.minimal_demerits[fit_class] <= br.minimum_demerits {
958                            // Insert a new active node from best_place to cur_p.
959                            br.pass_number += 1;
960                            let left_pw = if br.protrude {
961                                self.lb_left_protrusion(Some(cur_p), NodeRef::NULL)
962                            } else {
963                                0
964                            };
965                            br.passive.push(Passive {
966                                cur_break: cur_p,
967                                prev_break: br.best_place[fit_class],
968                                serial: br.pass_number,
969                                left_pw,
970                            });
971                            let pidx = br.passive.len() - 1;
972                            let (short, glue) = if br.do_last_line_fit {
973                                (br.best_pl_short[fit_class], br.best_pl_glue[fit_class])
974                            } else {
975                                (0, 0)
976                            };
977                            let q = br.alloc(
978                                Entry::Active {
979                                    passive: Some(pidx),
980                                    line_number: br.best_pl_line[fit_class] + 1,
981                                    fitness: fit_class,
982                                    hyphenated,
983                                    total_demerits: br.minimal_demerits[fit_class],
984                                    short,
985                                    glue,
986                                },
987                                r,
988                            );
989                            br.items[prev_r].next = q;
990                            prev_r = q;
991                            if br.tracing {
992                                self.printer.print_nl("@@");
993                                self.printer.print_int(i64::from(br.pass_number));
994                                self.printer.print_str(": line ");
995                                self.printer
996                                    .print_int(i64::from(br.best_pl_line[fit_class]));
997                                self.printer.print_char('.' as u32);
998                                self.printer.print_int(fit_class as i64);
999                                if hyphenated {
1000                                    self.printer.print_char('-' as u32);
1001                                }
1002                                self.printer.print_str(" t=");
1003                                self.printer
1004                                    .print_int(i64::from(br.minimal_demerits[fit_class]));
1005                                if br.do_last_line_fit {
1006                                    self.printer.print_str(" s=");
1007                                    self.printer.print_scaled(short);
1008                                    self.printer.print_str(if cur_p.is_null() {
1009                                        " a="
1010                                    } else {
1011                                        " g="
1012                                    });
1013                                    self.printer.print_scaled(glue);
1014                                }
1015                                self.printer.print_str(" -> @");
1016                                match br.best_place[fit_class] {
1017                                    None => self.printer.print_char('0' as u32),
1018                                    Some(pp) => {
1019                                        self.printer.print_int(i64::from(br.passive[pp].serial))
1020                                    }
1021                                }
1022                            }
1023                        }
1024                        br.minimal_demerits[fit_class] = AWFUL_BAD;
1025                    }
1026                    br.minimum_demerits = AWFUL_BAD;
1027                    // Insert a delta node to prepare for the next active node.
1028                    if r != ACTIVE {
1029                        let mut d = [0; NW];
1030                        for k in 0..NW {
1031                            d[k] = br.cur_active_width[k] - br.break_width[k];
1032                        }
1033                        let q = br.alloc(Entry::Delta(d), r);
1034                        br.items[prev_r].next = q;
1035                        prev_prev_r = prev_r;
1036                        prev_r = q;
1037                    }
1038                }
1039                if r == ACTIVE {
1040                    return;
1041                }
1042                // Compute the new line width.
1043                if l > br.easy_line {
1044                    line_width = br.second_width;
1045                    old_l = i32::MAX - 1;
1046                } else {
1047                    old_l = l;
1048                    line_width = if l > br.last_special_line {
1049                        br.second_width
1050                    } else {
1051                        match &br.par_shape {
1052                            None => br.first_width,
1053                            Some(s) => s[2 * (l as usize - 1) + 1],
1054                        }
1055                    };
1056                }
1057            }
1058            // Consider the demerits for a line from r to cur_p.
1059            let (r_passive, r_fitness, r_hyph, r_total, r_short, r_glue) = match br.items[r].entry {
1060                Entry::Active {
1061                    passive,
1062                    fitness,
1063                    hyphenated,
1064                    total_demerits,
1065                    short,
1066                    glue,
1067                    ..
1068                } => (passive, fitness, hyphenated, total_demerits, short, glue),
1069                _ => (None, DECENT_FIT, false, 0, 0, 0),
1070            };
1071            let mut artificial_demerits = false;
1072            let mut shortfall = line_width - br.cur_active_width[0];
1073            if br.protrude {
1074                let rp = *right_pw.get_or_insert_with(|| self.lb_right_protrusion(cur_p, br.head));
1075                let lp = match r_passive {
1076                    Some(i) => br.passive[i].left_pw,
1077                    None => br.first_left_pw,
1078                };
1079                shortfall += lp + rp;
1080            }
1081            if br.expand {
1082                // Font expansion takes up the shortfall first.
1083                let caw = &br.cur_active_width;
1084                if shortfall > 0 && caw[FONT_STRETCH] > 0 {
1085                    shortfall = (shortfall - caw[FONT_STRETCH]).max(0);
1086                } else if shortfall < 0 && caw[FONT_SHRINK] > 0 {
1087                    shortfall = (shortfall + caw[FONT_SHRINK]).min(0);
1088                }
1089            }
1090            let mut g = 0i32;
1091            let b: i32;
1092            let fit_class: usize;
1093            let caw = br.cur_active_width;
1094            'found: {
1095                if shortfall > 0 {
1096                    if caw[2] != 0 || caw[3] != 0 || caw[4] != 0 || caw[5] != 0 {
1097                        if br.do_last_line_fit {
1098                            if cur_p.is_null() {
1099                                // Perform computations for the last line.
1100                                'not_found: {
1101                                    if r_short == 0 || r_glue <= 0 {
1102                                        break 'not_found;
1103                                    }
1104                                    if caw[2] != br.fill_width[0]
1105                                        || caw[3] != br.fill_width[1]
1106                                        || caw[4] != br.fill_width[2]
1107                                        || caw[5] != br.fill_width[3]
1108                                    {
1109                                        break 'not_found;
1110                                    }
1111                                    g = if r_short > 0 { caw[1] } else { caw[6] };
1112                                    if g <= 0 {
1113                                        break 'not_found;
1114                                    }
1115                                    let md = retex_arith::MAX_DIMEN;
1116                                    g = match retex_arith::fract(g, r_short, r_glue, md) {
1117                                        Ok(v) => v,
1118                                        Err(_) => {
1119                                            if r_short > 0 {
1120                                                md
1121                                            } else {
1122                                                -md
1123                                            }
1124                                        }
1125                                    };
1126                                    if br.last_line_fit < 1000 {
1127                                        g = retex_arith::fract(g, br.last_line_fit, 1000, md)
1128                                            .unwrap_or(if r_short > 0 { md } else { -md });
1129                                    }
1130                                    if g > 0 {
1131                                        if g > shortfall {
1132                                            g = shortfall;
1133                                        }
1134                                        if g > 7_230_584 && caw[1] < 1_663_497 {
1135                                            b = INF_BAD;
1136                                            fit_class = VERY_LOOSE_FIT;
1137                                            break 'found;
1138                                        }
1139                                        b = retex_arith::badness(g, caw[1]);
1140                                        fit_class = if b > 12 {
1141                                            if b > 99 { VERY_LOOSE_FIT } else { LOOSE_FIT }
1142                                        } else {
1143                                            DECENT_FIT
1144                                        };
1145                                        break 'found;
1146                                    } else if g < 0 {
1147                                        if -g > caw[6] {
1148                                            g = -caw[6];
1149                                        }
1150                                        b = retex_arith::badness(-g, caw[6]);
1151                                        fit_class = if b > 12 { TIGHT_FIT } else { DECENT_FIT };
1152                                        break 'found;
1153                                    }
1154                                }
1155                            }
1156                            shortfall = 0;
1157                        }
1158                        b = 0;
1159                        fit_class = DECENT_FIT;
1160                    } else if shortfall > 7_230_584 && caw[1] < 1_663_497 {
1161                        b = INF_BAD;
1162                        fit_class = VERY_LOOSE_FIT;
1163                    } else {
1164                        b = retex_arith::badness(shortfall, caw[1]);
1165                        fit_class = if b > 12 {
1166                            if b > 99 { VERY_LOOSE_FIT } else { LOOSE_FIT }
1167                        } else {
1168                            DECENT_FIT
1169                        };
1170                    }
1171                } else {
1172                    b = if -shortfall > caw[6] {
1173                        INF_BAD + 1
1174                    } else {
1175                        retex_arith::badness(-shortfall, caw[6])
1176                    };
1177                    fit_class = if b > 12 { TIGHT_FIT } else { DECENT_FIT };
1178                }
1179                if br.do_last_line_fit {
1180                    // Adjust the additional data for the last line.
1181                    if cur_p.is_null() {
1182                        shortfall = 0;
1183                    }
1184                    g = if shortfall > 0 {
1185                        caw[1]
1186                    } else if shortfall < 0 {
1187                        caw[6]
1188                    } else {
1189                        0
1190                    };
1191                }
1192            }
1193            let node_r_stays_active;
1194            let mut deactivate = false;
1195            if b > INF_BAD || pi == EJECT_PENALTY {
1196                if br.final_pass
1197                    && br.minimum_demerits == AWFUL_BAD
1198                    && br.items[r].next == ACTIVE
1199                    && prev_r == ACTIVE
1200                {
1201                    artificial_demerits = true;
1202                } else if b > br.threshold {
1203                    deactivate = true;
1204                }
1205                node_r_stays_active = false;
1206            } else {
1207                prev_r = r;
1208                if b > br.threshold {
1209                    continue;
1210                }
1211                node_r_stays_active = true;
1212            }
1213            if !deactivate {
1214                // Record a new feasible break.
1215                let mut d: i32;
1216                if artificial_demerits {
1217                    d = 0;
1218                } else {
1219                    d = br.line_penalty + b;
1220                    d = if d.abs() >= 10000 { 100_000_000 } else { d * d };
1221                    if pi != 0 {
1222                        if pi > 0 {
1223                            d += pi * pi;
1224                        } else if pi > EJECT_PENALTY {
1225                            d -= pi * pi;
1226                        }
1227                    }
1228                    if hyphenated && r_hyph {
1229                        if !cur_p.is_null() {
1230                            d += br.double_hyphen_demerits;
1231                        } else {
1232                            d += br.final_hyphen_demerits;
1233                        }
1234                    }
1235                    if (fit_class as i32 - r_fitness as i32).abs() > 1 {
1236                        d += br.adj_demerits;
1237                    }
1238                }
1239                if br.tracing {
1240                    self.print_feasible_break(br, cur_p, r_passive, b, pi, d, artificial_demerits);
1241                }
1242                d += r_total;
1243                if d <= br.minimal_demerits[fit_class] {
1244                    br.minimal_demerits[fit_class] = d;
1245                    br.best_place[fit_class] = r_passive;
1246                    br.best_pl_line[fit_class] = l;
1247                    if br.do_last_line_fit {
1248                        br.best_pl_short[fit_class] = shortfall;
1249                        br.best_pl_glue[fit_class] = g;
1250                    }
1251                    if d < br.minimum_demerits {
1252                        br.minimum_demerits = d;
1253                    }
1254                }
1255                if node_r_stays_active {
1256                    continue;
1257                }
1258            }
1259            // Deactivate node r.
1260            let rn = br.items[r].next;
1261            br.items[prev_r].next = rn;
1262            br.release(r);
1263            if prev_r == ACTIVE {
1264                // Update the active widths, since the first active node has
1265                // been deleted.
1266                let r = br.items[ACTIVE].next;
1267                if let Entry::Delta(d) = br.items[r].entry {
1268                    for k in 0..NW {
1269                        br.active_width[k] += d[k];
1270                    }
1271                    br.cur_active_width = br.active_width;
1272                    let rn = br.items[r].next;
1273                    br.items[ACTIVE].next = rn;
1274                    br.release(r);
1275                }
1276            } else if br.is_delta(prev_r) {
1277                let r = br.items[prev_r].next;
1278                if r == ACTIVE {
1279                    let d = br.delta(prev_r);
1280                    for k in 0..NW {
1281                        br.cur_active_width[k] -= d[k];
1282                    }
1283                    br.items[prev_prev_r].next = ACTIVE;
1284                    br.release(prev_r);
1285                    prev_r = prev_prev_r;
1286                } else if let Entry::Delta(dr) = br.items[r].entry {
1287                    let mut dp_ = br.delta(prev_r);
1288                    for k in 0..NW {
1289                        br.cur_active_width[k] += dr[k];
1290                        dp_[k] += dr[k];
1291                    }
1292                    br.items[prev_r].entry = Entry::Delta(dp_);
1293                    let rn = br.items[r].next;
1294                    br.items[prev_r].next = rn;
1295                    br.release(r);
1296                }
1297            }
1298        }
1299    }
1300
1301    /// Computes `break_width` for a break at `cur_p`.
1302    fn compute_break_width(&mut self, br: &mut Breaker, cur_p: NodeRef, hyphenated: bool) {
1303        br.break_width = br.background;
1304        let mut s = cur_p;
1305        if hyphenated && !cur_p.is_null() && self.nodes.id(cur_p) == id::DISC {
1306            let (_pre, post, rep) = self.nodes.disc_lists(cur_p);
1307            br.break_width[0] -= self.list_width(rep);
1308            br.break_width[0] += self.list_width(post);
1309            br.break_width[0] += br.disc_width;
1310            if br.expand {
1311                let r = self.list_font_stretch(rep, br.expand_kerns);
1312                let p = self.list_font_stretch(post, br.expand_kerns);
1313                br.break_width[FONT_STRETCH] += p.stretch - r.stretch + br.disc_fs.stretch;
1314                br.break_width[FONT_SHRINK] += p.shrink - r.shrink + br.disc_fs.shrink;
1315            }
1316            s = if post.is_null() {
1317                self.nodes.next(cur_p)
1318            } else {
1319                NodeRef::NULL
1320            };
1321        }
1322        while !s.is_null() {
1323            match self.nodes.data(s) {
1324                NodeData::Glue { spec, .. } => {
1325                    let g = *spec;
1326                    br.break_width[0] -= g.width;
1327                    br.break_width[1 + (g.stretch_order as usize).min(4)] -= g.stretch;
1328                    br.break_width[6] -= g.shrink;
1329                }
1330                NodeData::Penalty { .. } => {}
1331                NodeData::Math { .. } => br.break_width[0] -= self.nodes.width(s),
1332                NodeData::Kern { kern, .. } => {
1333                    if !matches!(self.nodes.subtype(s), kern_sub::USER | kern_sub::ITALIC) {
1334                        break;
1335                    }
1336                    br.break_width[0] -= *kern;
1337                }
1338                _ => break,
1339            }
1340            s = self.nodes.next(s);
1341        }
1342    }
1343
1344    /// Prints a feasible break for `\tracingparagraphs`.
1345    #[allow(clippy::too_many_arguments)]
1346    fn print_feasible_break(
1347        &mut self,
1348        br: &mut Breaker,
1349        cur_p: NodeRef,
1350        r_passive: Option<usize>,
1351        b: i32,
1352        pi: i32,
1353        d: i32,
1354        artificial: bool,
1355    ) {
1356        if br.printed_node != cur_p {
1357            // Print the list between printed_node and cur_p.
1358            self.printer.print_nl("");
1359            let start = self.nodes.next(br.printed_node);
1360            if cur_p.is_null() {
1361                self.short_display(start);
1362            } else {
1363                let save = self.nodes.next(cur_p);
1364                self.nodes.set_next(cur_p, NodeRef::NULL);
1365                self.printer.print_nl("");
1366                self.short_display(start);
1367                self.nodes.set_next(cur_p, save);
1368            }
1369            br.printed_node = cur_p;
1370        }
1371        self.printer.print_nl("@");
1372        if cur_p.is_null() {
1373            self.print_esc_str("par");
1374        } else {
1375            match self.nodes.id(cur_p) {
1376                id::GLUE => {}
1377                id::PENALTY => self.print_esc_str("penalty"),
1378                id::DISC => self.print_esc_str("discretionary"),
1379                id::KERN => self.print_esc_str("kern"),
1380                _ => self.print_esc_str("math"),
1381            }
1382        }
1383        self.printer.print_str(" via @");
1384        match r_passive {
1385            None => self.printer.print_char('0' as u32),
1386            Some(p) => self.printer.print_int(i64::from(br.passive[p].serial)),
1387        }
1388        self.printer.print_str(" b=");
1389        if b > INF_BAD {
1390            self.printer.print_char('*' as u32);
1391        } else {
1392            self.printer.print_int(i64::from(b));
1393        }
1394        self.printer.print_str(" p=");
1395        self.printer.print_int(i64::from(pi));
1396        self.printer.print_str(" d=");
1397        if artificial {
1398            self.printer.print_char('*' as u32);
1399        } else {
1400            self.printer.print_int(i64::from(d));
1401        }
1402    }
1403
1404    /// Justifies the lines and appends them to the vertical list
1405    /// (`post_line_break`).
1406    fn post_line_break(
1407        &mut self,
1408        br: &mut Breaker,
1409        best_bet: usize,
1410        best_line: i32,
1411        temp_head: NodeRef,
1412        p: &ParParams,
1413    ) {
1414        // Reverse the links of the relevant passive nodes.
1415        let mut breaks: Vec<NodeRef> = Vec::new();
1416        let mut q = match br.items[best_bet].entry {
1417            Entry::Active { passive, .. } => passive,
1418            _ => None,
1419        };
1420        while let Some(i) = q {
1421            breaks.push(br.passive[i].cur_break);
1422            q = br.passive[i].prev_break;
1423        }
1424        breaks.reverse();
1425        let prev_graf = self.cur_list().prev_graf;
1426        let mut cur_line = prev_graf + 1;
1427        let ls = p.left_skip;
1428        let ls_nonzero = ls.width != 0 || ls.stretch != 0 || ls.shrink != 0;
1429        // Observed: the lines get the direction of the paragraph's local
1430        // par node (its \pardir when it began), not the current one.
1431        let first = self.nodes.next(temp_head);
1432        let pardir = if first.is_null() {
1433            p.par_dir
1434        } else if let NodeData::LocalPar { dir, .. } = self.nodes.data(first) {
1435            *dir
1436        } else {
1437            p.par_dir
1438        };
1439        let protrude = p.protrude_chars > 0;
1440        let adjust_spacing = p.adjust_spacing > 0;
1441        // The local boxes of the paragraph (from the last local par node
1442        // seen).
1443        let mut local_left = NodeRef::NULL;
1444        let mut local_right = NodeRef::NULL;
1445        let mut post_disc_break = false;
1446        // Directions open at the last break (see `dirs`).
1447        let mut open_dirs: Vec<i8> = Vec::new();
1448        for (bi, &cb) in breaks.iter().enumerate() {
1449            // Reopen the directions of the previous line.
1450            let line_first = self.nodes.next(temp_head);
1451            if let Some((h, t)) = self.begin_dir_nodes(&open_dirs) {
1452                self.nodes.link(temp_head, h);
1453                self.nodes.link(t, line_first);
1454                if line_first.is_null() {
1455                    self.nodes.set_next(t, NodeRef::NULL);
1456                }
1457            }
1458            self.track_open_dirs(line_first, cb, &mut open_dirs);
1459            // Track local par nodes in this line.
1460            {
1461                let mut s = self.nodes.next(temp_head);
1462                while !s.is_null() && s != cb {
1463                    if let NodeData::LocalPar {
1464                        box_left,
1465                        box_right,
1466                        ..
1467                    } = self.nodes.data(s)
1468                    {
1469                        local_left = *box_left;
1470                        local_right = *box_right;
1471                    }
1472                    s = self.nodes.next(s);
1473                }
1474            }
1475            // Modify the end of the line to reflect the nature of the
1476            // break and to include \rightskip.
1477            let mut q = cb;
1478            let mut disc_break = false;
1479            post_disc_break = false;
1480            let mut rightskip_done = false;
1481            if !q.is_null() {
1482                match self.nodes.id(q) {
1483                    id::GLUE => {
1484                        let rs = p.right_skip;
1485                        if let NodeData::Glue { spec, leader } = self.nodes.data_mut(q) {
1486                            *spec = rs;
1487                            let l = std::mem::take(leader);
1488                            self.nodes.flush_list(l);
1489                        }
1490                        self.nodes.set_subtype(q, glue_sub::RIGHT_SKIP);
1491                        rightskip_done = true;
1492                    }
1493                    id::DISC => {
1494                        // Change the discretionary to compulsory: LuaTeX
1495                        // keeps the (emptied) node after the pre-break
1496                        // text.
1497                        let (pre, post, rep) = self.nodes.disc_lists(q);
1498                        self.nodes
1499                            .set_disc_lists(q, (NodeRef::NULL, NodeRef::NULL, NodeRef::NULL));
1500                        self.nodes.flush_list(rep);
1501                        let r = self.nodes.next(q);
1502                        if !post.is_null() {
1503                            let t = self.nodes.tail_of(post);
1504                            self.nodes.link(q, post);
1505                            self.nodes.link(t, r);
1506                            if r.is_null() {
1507                                self.nodes.set_next(t, NodeRef::NULL);
1508                            }
1509                            post_disc_break = true;
1510                        }
1511                        if !pre.is_null() {
1512                            let before = self.find_prev_in(temp_head, q);
1513                            let t = self.nodes.tail_of(pre);
1514                            self.nodes.link(before, pre);
1515                            self.nodes.link(t, q);
1516                        }
1517                        disc_break = true;
1518                    }
1519                    id::KERN | id::MATH => match self.nodes.data_mut(q) {
1520                        NodeData::Kern { kern, .. } => *kern = 0,
1521                        NodeData::Math { surround, glue } => {
1522                            *surround = 0;
1523                            *glue = Glue::ZERO;
1524                        }
1525                        _ => {}
1526                    },
1527                    _ => {}
1528                }
1529            } else {
1530                q = self.nodes.tail_of(temp_head);
1531            }
1532            let q0 = q;
1533            if !rightskip_done {
1534                let r = self.new_glue_node(p.right_skip, glue_sub::RIGHT_SKIP);
1535                // Observed: with the attributes of the node before it.
1536                if !q.is_null() && q != temp_head {
1537                    let a = self.nodes.attr(q).cloned();
1538                    self.nodes.set_attr(r, a);
1539                }
1540                let n = self.nodes.next(q);
1541                self.nodes.link(q, r);
1542                self.nodes.link(r, n);
1543                if n.is_null() {
1544                    self.nodes.set_next(r, NodeRef::NULL);
1545                }
1546                q = r;
1547            }
1548            if let Some((h, t)) = self.end_dir_nodes(&open_dirs) {
1549                // Close the open directions before \rightskip, or on the
1550                // last line before \parfillskip (after line breaking, so
1551                // that they make no breakpoint).
1552                let mut at = q;
1553                if bi + 1 == breaks.len() {
1554                    let p = self.find_prev_in(temp_head, q);
1555                    if self.nodes.id(p) == id::GLUE
1556                        && self.nodes.subtype(p) == glue_sub::PAR_FILL_SKIP
1557                    {
1558                        at = p;
1559                    }
1560                }
1561                let p = self.find_prev_in(temp_head, at);
1562                self.nodes.link(p, h);
1563                self.nodes.link(t, at);
1564            }
1565            if !local_right.is_null() {
1566                // The right local box goes before \rightskip (and before
1567                // \parfillskip on the last line).
1568                let mut target = q;
1569                if bi + 1 == breaks.len() {
1570                    let p = self.find_prev_in(temp_head, q);
1571                    if self.nodes.id(p) == id::GLUE
1572                        && self.nodes.subtype(p) == glue_sub::PAR_FILL_SKIP
1573                    {
1574                        target = p;
1575                    }
1576                }
1577                let b = self.nodes.copy_node(local_right);
1578                let p = self.find_prev_in(temp_head, target);
1579                self.nodes.link(p, b);
1580                self.nodes.link(b, target);
1581            }
1582            if protrude {
1583                self.insert_right_margin_kern(temp_head, q0);
1584            }
1585            // Put the \leftskip glue at the left and detach this line.
1586            let r = self.nodes.next(q);
1587            self.nodes.set_next(q, NodeRef::NULL);
1588            let mut line = self.nodes.next(temp_head);
1589            self.nodes.set_next(temp_head, r);
1590            if !r.is_null() {
1591                self.nodes.set_prev(r, temp_head);
1592            }
1593            if !local_left.is_null() {
1594                let b = self.nodes.copy_node(local_left);
1595                self.nodes.link(b, line);
1596                line = b;
1597            }
1598            if protrude {
1599                let k = self.left_margin_kern(line);
1600                if !k.is_null() {
1601                    self.nodes.link(k, line);
1602                    line = k;
1603                }
1604            }
1605            if ls_nonzero {
1606                let g = self.new_glue_node(ls, glue_sub::LEFT_SKIP);
1607                // Observed: with the attributes of the line's first node.
1608                if !line.is_null() {
1609                    let a = self.nodes.attr(line).cloned();
1610                    self.nodes.set_attr(g, a);
1611                }
1612                self.nodes.link(g, line);
1613                line = g;
1614            }
1615            if !line.is_null() {
1616                self.nodes.set_prev(line, NodeRef::NULL);
1617            }
1618            // Call the packaging subroutine.
1619            let (cur_width, cur_indent) = if cur_line > br.last_special_line {
1620                (br.second_width, br.second_indent)
1621            } else {
1622                match &br.par_shape {
1623                    None => (br.first_width, br.first_indent),
1624                    Some(s) => (
1625                        s[2 * (cur_line as usize - 1) + 1],
1626                        s[2 * (cur_line as usize - 1)],
1627                    ),
1628                }
1629            };
1630            self.start_adjust_lists();
1631            // A line whose expansion ratio rounds to 0 keeps its natural
1632            // glue (see `expand_line`).
1633            let natural = adjust_spacing && self.expand_line(line, cur_width);
1634            // The line has the paragraph's direction while it is packed
1635            // (observed in the box shown by an overfull line warning).
1636            self.pack_dir = pardir;
1637            let just_box = if natural {
1638                let b = self.hpack(line, PackSpec::Additional(0));
1639                if let Some(bd) = self.nodes.box_data_mut(b) {
1640                    bd.width = cur_width;
1641                }
1642                b
1643            } else {
1644                self.hpack(line, PackSpec::Exactly(cur_width))
1645            };
1646            self.ts.just_box = just_box;
1647            self.nodes.set_subtype(just_box, 1);
1648            if let Some(b) = self.nodes.box_data_mut(just_box) {
1649                b.shift = cur_indent;
1650                b.dir = pardir;
1651            }
1652            // Append the new box to the current vertical list, telling
1653            // contribute_filter (observed: "box", then "adjust" for
1654            // \vadjust material).
1655            let pre_h = self.ts.pre_adjust_head;
1656            if !pre_h.is_null() && !self.nodes.next(pre_h).is_null() {
1657                self.notify_callback("contribute_filter", "pre_adjust");
1658            }
1659            self.append_adjust_material(pre_h);
1660            self.notify_callback("contribute_filter", "box");
1661            self.append_to_vlist(just_box, "post_linebreak");
1662            let adj_h = self.ts.adjust_head;
1663            if !adj_h.is_null() && !self.nodes.next(adj_h).is_null() {
1664                self.notify_callback("contribute_filter", "adjust");
1665            }
1666            self.append_adjust_material(adj_h);
1667            self.ts.adjust_tail = None;
1668            self.ts.pre_adjust_tail = None;
1669            // Append a penalty node, if a nonzero penalty is appropriate.
1670            if cur_line + 1 != best_line {
1671                let mut pen = match &p.interline_penalties {
1672                    Some(a) if !a.is_empty() => {
1673                        let n = a.len() as i32;
1674                        a[(cur_line.min(n) - 1).max(0) as usize]
1675                    }
1676                    _ => p.interline_penalty,
1677                };
1678                match &p.club_penalties {
1679                    Some(a) if !a.is_empty() => {
1680                        let n = a.len() as i32;
1681                        let r = (cur_line - prev_graf).min(n);
1682                        pen += a[(r - 1).max(0) as usize];
1683                    }
1684                    _ => {
1685                        if cur_line == prev_graf + 1 {
1686                            pen += p.club_penalty;
1687                        }
1688                    }
1689                }
1690                match &p.widow_penalties {
1691                    Some(a) if !a.is_empty() => {
1692                        let n = a.len() as i32;
1693                        let r = (best_line - cur_line - 1).min(n);
1694                        pen += a[(r - 1).max(0) as usize];
1695                    }
1696                    _ => {
1697                        if cur_line + 2 == best_line {
1698                            pen += p.widow_penalty;
1699                        }
1700                    }
1701                }
1702                if disc_break {
1703                    pen += p.broken_penalty;
1704                }
1705                if pen != 0 {
1706                    let r = self.new_penalty_node(pen);
1707                    // Observed: subtype 1 (`linebreakpenalty`).
1708                    self.nodes.set_subtype(r, 1);
1709                    self.tail_append(r);
1710                }
1711            }
1712            cur_line += 1;
1713            // Prune unwanted nodes at the beginning of the next line.
1714            if bi + 1 < breaks.len() && !post_disc_break {
1715                let next_break = breaks[bi + 1];
1716                let mut r = temp_head;
1717                loop {
1718                    let q = self.nodes.next(r);
1719                    if q.is_null() || q == next_break {
1720                        break;
1721                    }
1722                    let t = self.nodes.id(q);
1723                    if t == id::GLYPH || crate::page::precedes_break(t) {
1724                        break;
1725                    }
1726                    if t == id::KERN
1727                        && !matches!(self.nodes.subtype(q), kern_sub::USER | kern_sub::ITALIC)
1728                    {
1729                        break;
1730                    }
1731                    if !matches!(t, id::GLUE | id::KERN | id::MATH | id::PENALTY) {
1732                        break;
1733                    }
1734                    r = q;
1735                }
1736                if r != temp_head {
1737                    let q = self.nodes.next(r);
1738                    self.nodes.set_next(r, NodeRef::NULL);
1739                    let first = self.nodes.next(temp_head);
1740                    self.nodes.flush_list(first);
1741                    self.nodes.link(temp_head, q);
1742                    if q.is_null() {
1743                        self.nodes.set_next(temp_head, NodeRef::NULL);
1744                    }
1745                }
1746            }
1747        }
1748        let _ = post_disc_break;
1749        if !self.nodes.next(temp_head).is_null() {
1750            // Should not happen; keep the material rather than abort.
1751            let rest = self.nodes.next(temp_head);
1752            self.nodes.set_next(temp_head, NodeRef::NULL);
1753            self.nodes.flush_list(rest);
1754        }
1755        self.cur_list_mut().prev_graf = best_line - 1;
1756    }
1757
1758    /// Appends the material collected in an adjustment list.
1759    fn append_adjust_material(&mut self, head: NodeRef) {
1760        if head.is_null() {
1761            return;
1762        }
1763        let first = self.nodes.next(head);
1764        if first.is_null() {
1765            return;
1766        }
1767        let tail = self.cur_list().tail;
1768        self.nodes.link(tail, first);
1769        let t = self.nodes.tail_of(first);
1770        self.cur_list_mut().tail = t;
1771        self.nodes.set_next(head, NodeRef::NULL);
1772    }
1773}