1use crate::Engine;
7use crate::build::PackSpec;
8use crate::nodes::{BoxData, NodeData, NodeRef, glue_sub, id};
9use crate::primitives_gen::{assign_dimen as dp, assign_int as ip};
10
11#[derive(Default, Clone, Copy)]
14struct Totals {
15 stretch: [i32; 5],
16 shrink: [i32; 5],
17}
18
19impl Totals {
20 #[inline]
21 fn add(&mut self, g: &crate::glue::Glue) {
22 let so = (g.stretch_order as usize).min(4);
23 let ho = (g.shrink_order as usize).min(4);
24 self.stretch[so] = self.stretch[so].wrapping_add(g.stretch);
25 self.shrink[ho] = self.shrink[ho].wrapping_add(g.shrink);
26 }
27
28 fn stretch_order(&self) -> u8 {
29 (1..5).rev().find(|&o| self.stretch[o] != 0).unwrap_or(0) as u8
30 }
31
32 fn shrink_order(&self) -> u8 {
33 (1..5).rev().find(|&o| self.shrink[o] != 0).unwrap_or(0) as u8
34 }
35}
36
37impl Engine {
38 #[inline]
44 pub fn check_lost_char(&mut self, p: NodeRef) {
45 if self.eqtb.int(ip::TRACINGLOSTCHARS) < 4 {
46 return;
47 }
48 let NodeData::Glyph(g) = self.nodes.data(p) else {
49 return;
50 };
51 let (f, c) = (g.font, g.chr);
52 if self.fonts.char_metrics(f, c).is_some() {
53 return;
54 }
55 self.printer.print_nl("Missing character: There is no ");
56 self.printer.print_char(c);
57 self.printer.print_str(&format!(" (U+{c:04X}) in font "));
58 let name = self.font(f).name.clone();
59 self.printer.print_bytes(&name);
60 self.printer.print_char('!' as u32);
61 self.printer.print_nl("");
62 }
63
64 pub fn shipout_lost_char(&mut self, f: u32, c: u32) {
69 let level = self.eqtb.int(ip::TRACINGLOSTCHARS);
70 if level <= 0 {
71 return;
72 }
73 if level == 1 {
74 self.begin_diagnostic();
75 }
76 self.printer.print_nl("Missing character: There is no ");
77 self.printer.print_char(c);
78 self.printer.print_str(&format!(" (U+{c:04X}) in font "));
79 let name = self.font(f).name.clone();
80 self.printer.print_bytes(&name);
81 self.printer.print_char('!' as u32);
82 if level == 1 {
83 self.end_diagnostic(false);
84 } else {
85 self.printer.print_nl("");
86 }
87 if level == 3 {
88 self.help(&[]);
89 self.error();
90 }
91 }
92
93 #[inline]
97 pub fn glyph_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
98 match self.nodes.data(p) {
99 NodeData::Glyph(g) => match self.fonts.char_metrics(g.font, g.chr) {
100 Some(m) => (
101 crate::expansion::expanded_width(m.width, g.expansion_factor),
102 m.height + g.yoffset,
103 m.depth - g.yoffset.max(0),
104 ),
105 None => (0, 0, 0),
106 },
107 _ => (0, 0, 0),
108 }
109 }
110
111 fn take_pack_dir(&mut self, r: NodeRef) {
113 let dir = std::mem::replace(&mut self.pack_dir, -1);
114 if dir >= 0
115 && let Some(b) = self.nodes.box_data_mut(r)
116 {
117 b.dir = dir;
118 }
119 }
120
121 pub fn hpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
123 self.fonts.sync_dense();
124 self.last_badness = 0;
125 let r = self.new_null_box_node(true);
126 self.take_pack_dir(r);
127 let (mut h, mut d, mut x) = (0i32, 0i32, 0i32);
128 let mut tot = Totals::default();
129 let head = self.nodes.new_temp();
132 self.nodes.link(head, p);
133 let mut q = head;
134 let mut p = p;
135 while !p.is_null() {
136 let next = self.nodes.next(p);
137 match self.nodes.data(p) {
138 NodeData::Glyph(_) => {
139 self.check_lost_char(p);
140 let (w, gh, gd) = self.glyph_dimensions(p);
141 x += w;
142 if gh > h {
143 h = gh;
144 }
145 if gd > d {
146 d = gd;
147 }
148 }
149 NodeData::Box(b) => {
150 x += b.width;
151 let s = b.shift;
152 if b.height - s > h {
153 h = b.height - s;
154 }
155 if b.depth + s > d {
156 d = b.depth + s;
157 }
158 }
159 NodeData::Unset { b, .. } => {
160 x += b.width;
161 if b.height > h {
162 h = b.height;
163 }
164 if b.depth > d {
165 d = b.depth;
166 }
167 }
168 NodeData::Rule {
169 width,
170 height,
171 depth,
172 ..
173 } => {
174 x += *width;
175 if *height > h {
176 h = *height;
177 }
178 if *depth > d {
179 d = *depth;
180 }
181 }
182 NodeData::Ins { .. } | NodeData::Mark { .. } | NodeData::Adjust { .. } => {
183 if let Some(at) = self.ts.adjust_tail {
184 self.nodes.set_next(q, next);
186 if !next.is_null() {
187 self.nodes.set_prev(next, q);
188 }
189 let is_pre = self.nodes.id(p) == id::ADJUST && self.nodes.subtype(p) == 1;
190 if self.nodes.id(p) == id::ADJUST {
191 let l = self.nodes.list_ptr(p);
192 self.nodes.set_list_ptr(p, NodeRef::NULL);
193 self.nodes.free(p);
194 if !l.is_null() {
195 let target = if is_pre {
196 self.ts.pre_adjust_tail.unwrap_or(at)
197 } else {
198 at
199 };
200 self.nodes.link(target, l);
201 let t = self.nodes.tail_of(l);
202 if is_pre && self.ts.pre_adjust_tail.is_some() {
203 self.ts.pre_adjust_tail = Some(t);
204 } else {
205 self.ts.adjust_tail = Some(t);
206 }
207 }
208 } else {
209 self.nodes.link(at, p);
210 self.nodes.set_next(p, NodeRef::NULL);
211 self.ts.adjust_tail = Some(p);
212 }
213 p = next;
214 continue;
215 }
216 }
217 NodeData::Glue { spec, leader } => {
218 x += spec.width;
219 tot.add(spec);
220 let (leader, sub) = (*leader, self.nodes.subtype(p));
221 if sub >= glue_sub::A_LEADERS && !leader.is_null() {
222 let (lh, ld) = (self.node_height(leader), self.node_depth(leader));
223 if lh > h {
224 h = lh;
225 }
226 if ld > d {
227 d = ld;
228 }
229 }
230 }
231 NodeData::Kern {
232 kern,
233 expansion_factor,
234 } => x += *kern + *expansion_factor,
235 NodeData::MarginKern { width, .. } => x += *width,
236 NodeData::Math { surround, glue } => {
237 if glue.width != 0 || glue.stretch != 0 || glue.shrink != 0 {
238 x += glue.width;
239 let g = *glue;
240 tot.add(&g);
241 } else {
242 x += *surround;
243 }
244 }
245 NodeData::Disc { replace, .. } => {
246 let mut s = *replace;
248 while !s.is_null() {
249 self.check_lost_char(s);
250 let (w, sh, sd) = self.simple_dimensions(s);
251 x += w;
252 if sh > h {
253 h = sh;
254 }
255 if sd > d {
256 d = sd;
257 }
258 s = self.nodes.next(s);
259 }
260 }
261 _ => {}
262 }
263 q = p;
264 p = next;
265 }
266 if let Some(at) = self.ts.adjust_tail {
267 self.nodes.set_next(at, NodeRef::NULL);
268 }
269 if let Some(at) = self.ts.pre_adjust_tail {
270 self.nodes.set_next(at, NodeRef::NULL);
271 }
272 let first = self.nodes.next(head);
273 self.nodes.free(head);
274 if !first.is_null() {
275 self.nodes.set_prev(first, NodeRef::NULL);
276 }
277 let w = match spec {
278 PackSpec::Exactly(w) => w,
279 PackSpec::Additional(w) => x + w,
280 };
281 {
282 let b = self.box_mut(r);
283 b.head = first;
284 b.height = h;
285 b.depth = d;
286 b.width = w;
287 }
288 self.ts.last_totals = (tot.stretch, tot.shrink);
289 let x = w - x;
290 let quality = self.has_callback("hpack_quality");
293 let mut incident: Option<(&str, i32)> = None;
294 let report: Option<()> = if x == 0 {
295 None
296 } else if x > 0 {
297 let o = tot.stretch_order();
298 let ts = tot.stretch[o as usize];
299 {
300 let b = self.box_mut(r);
301 b.glue_order = o;
302 b.glue_sign = 1;
303 if ts != 0 {
304 b.glue_set = crate::nodes::glue_ratio(x, ts);
305 } else {
306 b.glue_sign = 0;
307 b.glue_set = 0.0;
308 }
309 }
310 if o == 0 && !first.is_null() {
311 self.last_badness = retex_arith::badness(x, tot.stretch[0]);
312 if self.last_badness > self.eqtb.int(ip::HBADNESS) && quality {
313 let kind = if self.last_badness > 100 {
314 "underfull"
315 } else {
316 "loose"
317 };
318 incident = Some((kind, self.last_badness));
319 None
320 } else if self.last_badness > self.eqtb.int(ip::HBADNESS) {
321 self.printer.print_ln();
322 if self.last_badness > 100 {
323 self.diag_capture_start();
324 self.printer.print_nl("Underfull");
325 } else {
326 self.printer.print_nl("Loose");
327 }
328 self.printer.print_str(" \\hbox (badness ");
329 self.printer.print_int(i64::from(self.last_badness));
330 Some(())
331 } else {
332 None
333 }
334 } else {
335 None
336 }
337 } else {
338 let o = tot.shrink_order();
339 let ts = tot.shrink[o as usize];
340 {
341 let b = self.box_mut(r);
342 b.glue_order = o;
343 b.glue_sign = 2;
344 if ts != 0 {
345 b.glue_set = crate::nodes::glue_ratio(-x, ts);
346 } else {
347 b.glue_sign = 0;
348 b.glue_set = 0.0;
349 }
350 }
351 if ts < -x && o == 0 && !first.is_null() {
352 self.last_badness = 1_000_000;
353 self.box_mut(r).glue_set = 1.0;
354 let excess = -x - tot.shrink[0];
355 let hfuzz = self.eqtb.dimen(dp::HFUZZ);
356 if (excess > hfuzz || self.eqtb.int(ip::HBADNESS) < 100) && quality {
357 incident = Some(("overfull", excess));
358 None
359 } else if excess > hfuzz || self.eqtb.int(ip::HBADNESS) < 100 {
360 let ofr = self.eqtb.dimen(dp::OVERFULLRULE);
361 if ofr > 0 && excess > hfuzz {
362 let rule = self.nodes.new_rule(ofr, NULL_RULE, NULL_RULE);
363 if first.is_null() {
364 self.box_mut(r).head = rule;
365 } else {
366 let t = self.nodes.tail_of(first);
367 self.nodes.link(t, rule);
368 }
369 }
370 self.printer.print_ln();
371 self.diag_capture_start();
372 self.printer.print_nl("Overfull \\hbox (");
373 self.printer.print_scaled(excess);
374 self.printer.print_str("pt too wide");
375 Some(())
376 } else {
377 None
378 }
379 } else if o == 0 && !first.is_null() {
380 self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
381 if self.last_badness > self.eqtb.int(ip::HBADNESS) && quality {
382 incident = Some(("tight", self.last_badness));
383 None
384 } else if self.last_badness > self.eqtb.int(ip::HBADNESS) {
385 self.printer.print_ln();
386 self.diag_capture_start();
387 self.printer.print_nl("Tight \\hbox (badness ");
388 self.printer.print_int(i64::from(self.last_badness));
389 Some(())
390 } else {
391 None
392 }
393 } else {
394 None
395 }
396 };
397 if report.is_some() {
398 self.finish_hbox_diagnostic(r);
399 }
400 if let Some((kind, detail)) = incident {
401 let p = self.call_pack_quality("hpack_quality", kind, detail, r);
403 if !p.is_null() {
404 let head = self.nodes.list_ptr(r);
405 if head.is_null() {
406 self.box_mut(r).head = p;
407 } else {
408 let t = self.nodes.tail_of(head);
409 self.nodes.link(t, p);
410 }
411 }
412 }
413 r
414 }
415
416 fn call_pack_quality(&mut self, cb: &str, kind: &str, detail: i32, r: NodeRef) -> NodeRef {
419 let (first, last) = (self.ts.pack_begin_line, self.input.line());
420 match self.lua.clone() {
421 Some(h) => h.pack_quality(self, cb, kind, detail, r, first, last),
422 None => NodeRef::NULL,
423 }
424 }
425
426 pub fn natural_dimensions(&self, mut p: NodeRef, stop: NodeRef) -> (i32, i32, i32) {
430 let (mut w, mut h, mut d) = (0i32, 0i32, 0i32);
431 while !p.is_null() && p != stop {
432 let (pw, ph, pd) = match self.nodes.data(p) {
433 NodeData::Glyph(_) => self.glyph_dimensions(p),
434 NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
435 NodeData::Rule {
436 width,
437 height,
438 depth,
439 ..
440 } => (*width, *height, *depth),
441 NodeData::Disc { replace, .. } => {
442 let r = *replace;
443 self.natural_dimensions(r, NodeRef::NULL)
444 }
445 _ => (self.nodes.width(p), 0, 0),
446 };
447 w += pw;
448 h = h.max(ph);
449 d = d.max(pd);
450 p = self.nodes.next(p);
451 }
452 (w, h, d)
453 }
454
455 fn simple_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
457 match self.nodes.data(p) {
458 NodeData::Glyph(_) => self.glyph_dimensions(p),
459 NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
460 NodeData::Rule {
461 width,
462 height,
463 depth,
464 ..
465 } => (*width, *height, *depth),
466 NodeData::Kern {
467 kern,
468 expansion_factor,
469 } => (*kern + *expansion_factor, 0, 0),
470 _ => (0, 0, 0),
471 }
472 }
473
474 fn box_mut(&mut self, r: NodeRef) -> &mut BoxData {
475 match self.nodes.data_mut(r) {
476 NodeData::Box(b) => b,
477 _ => unreachable!("box_mut on a non-box node"),
478 }
479 }
480
481 fn finish_hbox_diagnostic(&mut self, r: NodeRef) {
483 if self.ts.output_active {
484 self.printer
485 .print_str(") has occurred while \\output is active");
486 } else {
487 let pbl = self.ts.pack_begin_line;
488 if pbl != 0 {
489 if pbl > 0 {
490 self.printer.print_str(") in paragraph at lines ");
491 } else {
492 self.printer.print_str(") in alignment at lines ");
493 }
494 self.printer.print_int(i64::from(pbl.abs()));
495 self.printer.print_str("--");
496 } else {
497 self.printer.print_str(") detected at line ");
498 }
499 self.printer.print_int(i64::from(self.input.line()));
500 }
501 let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
502 self.diag_capture_warning(at);
503 if self.diagnostics.is_some() {
504 let facts = self.hbox_facts(r);
505 if let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut()) {
506 d.facts = facts;
507 }
508 }
509 self.printer.print_ln();
510 self.ts.font_in_short_display = 0;
511 let l = self.nodes.list_ptr(r);
512 self.short_display(l);
513 self.printer.print_ln();
514 self.begin_diagnostic();
515 self.show_box(r);
516 self.end_diagnostic(true);
517 }
518
519 pub fn vpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
521 self.vpackage(p, spec, retex_arith::MAX_DIMEN)
522 }
523
524 pub fn vpackage(&mut self, p: NodeRef, spec: PackSpec, l: i32) -> NodeRef {
527 self.last_badness = 0;
528 let r = self.new_null_box_node(false);
529 self.take_pack_dir(r);
530 let (mut w, mut d, mut x) = (0i32, 0i32, 0i32);
531 let mut tot = Totals::default();
532 let mut q = p;
533 while !q.is_null() {
534 match self.nodes.data(q) {
535 NodeData::Box(b) => {
536 x += d + b.height;
537 d = b.depth;
538 if b.width + b.shift > w {
539 w = b.width + b.shift;
540 }
541 }
542 NodeData::Unset { b, .. } => {
543 x += d + b.height;
544 d = b.depth;
545 if b.width > w {
546 w = b.width;
547 }
548 }
549 NodeData::Rule {
550 width,
551 height,
552 depth,
553 ..
554 } => {
555 x += d + *height;
556 d = *depth;
557 if *width > w {
558 w = *width;
559 }
560 }
561 NodeData::Glue { spec, leader } => {
562 x += d;
563 d = 0;
564 x += spec.width;
565 tot.add(spec);
566 let leader = *leader;
567 if self.nodes.subtype(q) >= glue_sub::A_LEADERS && !leader.is_null() {
568 let lw = self.nodes.width(leader);
569 if lw > w {
570 w = lw;
571 }
572 }
573 }
574 NodeData::Kern { kern, .. } => {
575 x += d + *kern;
576 d = 0;
577 }
578 NodeData::Glyph(_) => self.confusion("vpack"),
581 _ => {}
582 }
583 q = self.nodes.next(q);
584 }
585 {
586 let b = self.box_mut(r);
587 b.width = w;
588 b.head = p;
589 }
590 if d > l {
591 x += d - l;
592 self.box_mut(r).depth = l;
593 } else {
594 self.box_mut(r).depth = d;
595 }
596 let h = match spec {
597 PackSpec::Exactly(h) => h,
598 PackSpec::Additional(h) => x + h,
599 };
600 self.box_mut(r).height = h;
601 self.ts.last_totals = (tot.stretch, tot.shrink);
602 let x = h - x;
603 let quality = self.has_callback("vpack_quality");
604 let mut incident: Option<(&str, i32)> = None;
605 let report = if x == 0 {
606 false
607 } else if x > 0 {
608 let o = tot.stretch_order();
609 let ts = tot.stretch[o as usize];
610 {
611 let b = self.box_mut(r);
612 b.glue_order = o;
613 b.glue_sign = 1;
614 if ts != 0 {
615 b.glue_set = crate::nodes::glue_ratio(x, ts);
616 } else {
617 b.glue_sign = 0;
618 b.glue_set = 0.0;
619 }
620 }
621 if o == 0 && !p.is_null() {
622 self.last_badness = retex_arith::badness(x, tot.stretch[0]);
623 if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
624 let kind = if self.last_badness > 100 {
625 "underfull"
626 } else {
627 "loose"
628 };
629 incident = Some((kind, self.last_badness));
630 false
631 } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
632 self.printer.print_ln();
633 if self.last_badness > 100 {
634 self.diag_capture_start();
635 self.printer.print_nl("Underfull");
636 } else {
637 self.printer.print_nl("Loose");
638 }
639 self.printer.print_str(" \\vbox (badness ");
640 self.printer.print_int(i64::from(self.last_badness));
641 true
642 } else {
643 false
644 }
645 } else {
646 false
647 }
648 } else {
649 let o = tot.shrink_order();
650 let ts = tot.shrink[o as usize];
651 {
652 let b = self.box_mut(r);
653 b.glue_order = o;
654 b.glue_sign = 2;
655 if ts != 0 {
656 b.glue_set = crate::nodes::glue_ratio(-x, ts);
657 } else {
658 b.glue_sign = 0;
659 b.glue_set = 0.0;
660 }
661 }
662 if ts < -x && o == 0 && !p.is_null() {
663 self.last_badness = 1_000_000;
664 self.box_mut(r).glue_set = 1.0;
665 let excess = -x - tot.shrink[0];
666 if (excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100)
667 && quality
668 {
669 incident = Some(("overfull", excess));
670 false
671 } else if excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100 {
672 self.printer.print_ln();
673 self.diag_capture_start();
674 self.printer.print_nl("Overfull \\vbox (");
675 self.printer.print_scaled(excess);
676 self.printer.print_str("pt too high");
677 true
678 } else {
679 false
680 }
681 } else if o == 0 && !p.is_null() {
682 self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
683 if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
684 incident = Some(("tight", self.last_badness));
685 false
686 } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
687 self.printer.print_ln();
688 self.diag_capture_start();
689 self.printer.print_nl("Tight \\vbox (badness ");
690 self.printer.print_int(i64::from(self.last_badness));
691 true
692 } else {
693 false
694 }
695 } else {
696 false
697 }
698 };
699 if let Some((kind, detail)) = incident {
700 self.call_pack_quality("vpack_quality", kind, detail, r);
702 }
703 if report {
704 if self.ts.output_active {
705 self.printer
706 .print_str(") has occurred while \\output is active");
707 } else {
708 let pbl = self.ts.pack_begin_line;
709 if pbl != 0 {
710 self.printer.print_str(") in alignment at lines ");
711 self.printer.print_int(i64::from(pbl.abs()));
712 self.printer.print_str("--");
713 } else {
714 self.printer.print_str(") detected at line ");
715 }
716 self.printer.print_int(i64::from(self.input.line()));
717 }
718 let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
719 self.diag_capture_warning(at);
720 if self.ts.output_active
722 && self.last_badness > 100
723 && self.last_badness < 1_000_000
724 && let Some((message, notes, help)) = self.underfull_page_note(self.last_badness)
725 && let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut())
726 && d.message.starts_with("Underfull \\vbox")
727 {
728 d.message = message;
729 d.notes = notes;
730 d.help = help;
731 }
732 if !self.ts.output_active {
733 self.printer.print_ln();
734 }
735 self.begin_diagnostic();
736 self.show_box(r);
737 self.end_diagnostic(true);
738 }
739 r
740 }
741}
742
743const NULL_RULE: i32 = crate::typeset::NULL_FLAG;