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