1#![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
34type Widths = [i32; NW];
37
38const NW: usize = 9;
40const FONT_STRETCH: usize = 7;
42const 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#[derive(Clone, Debug)]
54enum Entry {
55 Sentinel,
57 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 Delta(Widths),
69}
70
71struct Item {
72 next: usize,
73 entry: Entry,
74}
75
76struct Passive {
78 cur_break: NodeRef,
79 prev_break: Option<usize>,
80 serial: i32,
81 left_pw: i32,
83}
84
85struct 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 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 protrude: bool,
126 first_left_pw: i32,
128 head: NodeRef,
130 expand: bool,
132 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 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 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 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 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 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 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 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 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 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 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 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 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 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 self.try_break(&mut br, NodeRef::NULL, EJECT_PENALTY, true);
531 if br.items[ACTIVE].next != ACTIVE {
532 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 '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 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 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 let rn = br.items[r].next;
1028 br.items[prev_r].next = rn;
1029 br.release(r);
1030 if prev_r == ACTIVE {
1031 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 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 #[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 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 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 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 let mut local_left = NodeRef::NULL;
1203 let mut local_right = NodeRef::NULL;
1204 let mut post_disc_break = false;
1205 let mut open_dirs: Vec<i8> = Vec::new();
1207 for (bi, &cb) in breaks.iter().enumerate() {
1208 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 {
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 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 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 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 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 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 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 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 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 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 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 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 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}