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 pub fn set_dimensions(
461 &self,
462 mut p: NodeRef,
463 stop: NodeRef,
464 glue_set: f64,
465 sign: i32,
466 order: i32,
467 ) -> (i32, i32, i32) {
468 let (mut w, mut h, mut d) = (0i64, 0i32, 0i32);
469 let (mut cur_glue, mut cur_g) = (0f64, 0i64);
470 while !p.is_null() && p != stop {
471 if let NodeData::Glue { spec, .. } = self.nodes.data(p) {
472 let mut wd = i64::from(spec.width) - cur_g;
473 let set = match sign {
474 1 if i32::from(spec.stretch_order) == order => {
475 cur_glue += f64::from(spec.stretch);
476 true
477 }
478 2 if i32::from(spec.shrink_order) == order => {
479 cur_glue -= f64::from(spec.shrink);
480 true
481 }
482 _ => false,
483 };
484 if set {
485 cur_g = (glue_set * cur_glue).clamp(-1e9, 1e9).round() as i64;
486 }
487 wd += cur_g;
488 w += wd;
489 } else {
490 let (pw, ph, pd) = self.natural_dimensions(p, self.nodes.next(p));
491 w += i64::from(pw);
492 h = h.max(ph);
493 d = d.max(pd);
494 }
495 p = self.nodes.next(p);
496 }
497 (w as i32, h, d)
498 }
499
500 fn simple_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
502 match self.nodes.data(p) {
503 NodeData::Glyph(_) => self.glyph_dimensions(p),
504 NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
505 NodeData::Rule {
506 width,
507 height,
508 depth,
509 ..
510 } => (*width, *height, *depth),
511 NodeData::Kern {
512 kern,
513 expansion_factor,
514 } => (*kern + *expansion_factor, 0, 0),
515 _ => (0, 0, 0),
516 }
517 }
518
519 fn box_mut(&mut self, r: NodeRef) -> &mut BoxData {
520 match self.nodes.data_mut(r) {
521 NodeData::Box(b) => b,
522 _ => unreachable!("box_mut on a non-box node"),
523 }
524 }
525
526 fn finish_hbox_diagnostic(&mut self, r: NodeRef) {
528 if self.ts.output_active {
529 self.printer
530 .print_str(") has occurred while \\output is active");
531 } else {
532 let pbl = self.ts.pack_begin_line;
533 if pbl != 0 {
534 if pbl > 0 {
535 self.printer.print_str(") in paragraph at lines ");
536 } else {
537 self.printer.print_str(") in alignment at lines ");
538 }
539 self.printer.print_int(i64::from(pbl.abs()));
540 self.printer.print_str("--");
541 } else {
542 self.printer.print_str(") detected at line ");
543 }
544 self.printer.print_int(i64::from(self.input.line()));
545 }
546 let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
547 self.diag_capture_warning(at);
548 if self.diagnostics.is_some() {
549 let facts = self.hbox_facts(r);
550 if let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut()) {
551 d.facts = facts;
552 }
553 }
554 self.printer.print_ln();
555 self.ts.font_in_short_display = 0;
556 let l = self.nodes.list_ptr(r);
557 self.short_display(l);
558 self.printer.print_ln();
559 self.begin_diagnostic();
560 self.show_box(r);
561 self.end_diagnostic(true);
562 }
563
564 pub fn vpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
566 self.vpackage(p, spec, retex_arith::MAX_DIMEN)
567 }
568
569 pub fn vpackage(&mut self, p: NodeRef, spec: PackSpec, l: i32) -> NodeRef {
572 self.last_badness = 0;
573 let r = self.new_null_box_node(false);
574 self.take_pack_dir(r);
575 let (mut w, mut d, mut x) = (0i32, 0i32, 0i32);
576 let mut tot = Totals::default();
577 let mut q = p;
578 while !q.is_null() {
579 match self.nodes.data(q) {
580 NodeData::Box(b) => {
581 x += d + b.height;
582 d = b.depth;
583 if b.width + b.shift > w {
584 w = b.width + b.shift;
585 }
586 }
587 NodeData::Unset { b, .. } => {
588 x += d + b.height;
589 d = b.depth;
590 if b.width > w {
591 w = b.width;
592 }
593 }
594 NodeData::Rule {
595 width,
596 height,
597 depth,
598 ..
599 } => {
600 x += d + *height;
601 d = *depth;
602 if *width > w {
603 w = *width;
604 }
605 }
606 NodeData::Glue { spec, leader } => {
607 x += d;
608 d = 0;
609 x += spec.width;
610 tot.add(spec);
611 let leader = *leader;
612 if self.nodes.subtype(q) >= glue_sub::A_LEADERS && !leader.is_null() {
613 let lw = self.nodes.width(leader);
614 if lw > w {
615 w = lw;
616 }
617 }
618 }
619 NodeData::Kern { kern, .. } => {
620 x += d + *kern;
621 d = 0;
622 }
623 NodeData::Glyph(_) => self.confusion("vpack"),
626 _ => {}
627 }
628 q = self.nodes.next(q);
629 }
630 {
631 let b = self.box_mut(r);
632 b.width = w;
633 b.head = p;
634 }
635 if d > l {
636 x += d - l;
637 self.box_mut(r).depth = l;
638 } else {
639 self.box_mut(r).depth = d;
640 }
641 let h = match spec {
642 PackSpec::Exactly(h) => h,
643 PackSpec::Additional(h) => x + h,
644 };
645 self.box_mut(r).height = h;
646 self.ts.last_totals = (tot.stretch, tot.shrink);
647 let x = h - x;
648 let quality = self.has_callback("vpack_quality");
649 let mut incident: Option<(&str, i32)> = None;
650 let report = if x == 0 {
651 false
652 } else if x > 0 {
653 let o = tot.stretch_order();
654 let ts = tot.stretch[o as usize];
655 {
656 let b = self.box_mut(r);
657 b.glue_order = o;
658 b.glue_sign = 1;
659 if ts != 0 {
660 b.glue_set = crate::nodes::glue_ratio(x, ts);
661 } else {
662 b.glue_sign = 0;
663 b.glue_set = 0.0;
664 }
665 }
666 if o == 0 && !p.is_null() {
667 self.last_badness = retex_arith::badness(x, tot.stretch[0]);
668 if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
669 let kind = if self.last_badness > 100 {
670 "underfull"
671 } else {
672 "loose"
673 };
674 incident = Some((kind, self.last_badness));
675 false
676 } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
677 self.printer.print_ln();
678 if self.last_badness > 100 {
679 self.diag_capture_start();
680 self.printer.print_nl("Underfull");
681 } else {
682 self.printer.print_nl("Loose");
683 }
684 self.printer.print_str(" \\vbox (badness ");
685 self.printer.print_int(i64::from(self.last_badness));
686 true
687 } else {
688 false
689 }
690 } else {
691 false
692 }
693 } else {
694 let o = tot.shrink_order();
695 let ts = tot.shrink[o as usize];
696 {
697 let b = self.box_mut(r);
698 b.glue_order = o;
699 b.glue_sign = 2;
700 if ts != 0 {
701 b.glue_set = crate::nodes::glue_ratio(-x, ts);
702 } else {
703 b.glue_sign = 0;
704 b.glue_set = 0.0;
705 }
706 }
707 if ts < -x && o == 0 && !p.is_null() {
708 self.last_badness = 1_000_000;
709 self.box_mut(r).glue_set = 1.0;
710 let excess = -x - tot.shrink[0];
711 if (excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100)
712 && quality
713 {
714 incident = Some(("overfull", excess));
715 false
716 } else if excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100 {
717 self.printer.print_ln();
718 self.diag_capture_start();
719 self.printer.print_nl("Overfull \\vbox (");
720 self.printer.print_scaled(excess);
721 self.printer.print_str("pt too high");
722 true
723 } else {
724 false
725 }
726 } else if o == 0 && !p.is_null() {
727 self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
728 if self.last_badness > self.eqtb.int(ip::VBADNESS) && quality {
729 incident = Some(("tight", self.last_badness));
730 false
731 } else if self.last_badness > self.eqtb.int(ip::VBADNESS) {
732 self.printer.print_ln();
733 self.diag_capture_start();
734 self.printer.print_nl("Tight \\vbox (badness ");
735 self.printer.print_int(i64::from(self.last_badness));
736 true
737 } else {
738 false
739 }
740 } else {
741 false
742 }
743 };
744 if let Some((kind, detail)) = incident {
745 self.call_pack_quality("vpack_quality", kind, detail, r);
747 }
748 if report {
749 if self.ts.output_active {
750 self.printer
751 .print_str(") has occurred while \\output is active");
752 } else {
753 let pbl = self.ts.pack_begin_line;
754 if pbl != 0 {
755 self.printer.print_str(") in alignment at lines ");
756 self.printer.print_int(i64::from(pbl.abs()));
757 self.printer.print_str("--");
758 } else {
759 self.printer.print_str(") detected at line ");
760 }
761 self.printer.print_int(i64::from(self.input.line()));
762 }
763 let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
764 self.diag_capture_warning(at);
765 if self.ts.output_active
767 && self.last_badness > 100
768 && self.last_badness < 1_000_000
769 && let Some((message, notes, help)) = self.underfull_page_note(self.last_badness)
770 && let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut())
771 && d.message.starts_with("Underfull \\vbox")
772 {
773 d.message = message;
774 d.notes = notes;
775 d.help = help;
776 }
777 if !self.ts.output_active {
778 self.printer.print_ln();
779 }
780 self.begin_diagnostic();
781 self.show_box(r);
782 self.end_diagnostic(true);
783 }
784 r
785 }
786}
787
788const NULL_RULE: i32 = crate::typeset::NULL_FLAG;