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