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