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