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