use crate::Engine;
use crate::build::PackSpec;
use crate::nodes::{BoxData, NodeData, NodeRef, glue_sub, id};
use crate::primitives_gen::{assign_dimen as dp, assign_int as ip};
#[derive(Default, Clone, Copy)]
struct Totals {
stretch: [i32; 5],
shrink: [i32; 5],
}
impl Totals {
#[inline]
fn add(&mut self, g: &crate::glue::Glue) {
let so = (g.stretch_order as usize).min(4);
let ho = (g.shrink_order as usize).min(4);
self.stretch[so] = self.stretch[so].wrapping_add(g.stretch);
self.shrink[ho] = self.shrink[ho].wrapping_add(g.shrink);
}
fn stretch_order(&self) -> u8 {
(1..5).rev().find(|&o| self.stretch[o] != 0).unwrap_or(0) as u8
}
fn shrink_order(&self) -> u8 {
(1..5).rev().find(|&o| self.shrink[o] != 0).unwrap_or(0) as u8
}
}
impl Engine {
#[inline]
pub fn check_lost_char(&mut self, p: NodeRef) {
if self.eqtb.int(ip::TRACINGLOSTCHARS) < 4 {
return;
}
let NodeData::Glyph(g) = self.nodes.data(p) else {
return;
};
let (f, c) = (g.font, g.chr);
if self.fonts.char_metrics(f, c).is_some() {
return;
}
self.printer.print_nl("Missing character: There is no ");
self.printer.print_char(c);
self.printer.print_str(&format!(" (U+{c:04X}) in font "));
let name = self.font(f).name.clone();
self.printer.print_bytes(&name);
self.printer.print_char('!' as u32);
self.printer.print_nl("");
}
#[inline]
pub fn glyph_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
match self.nodes.data(p) {
NodeData::Glyph(g) => match self.fonts.char_metrics(g.font, g.chr) {
Some(m) => (
crate::expansion::expanded_width(m.width, g.expansion_factor),
m.height + g.yoffset,
m.depth - g.yoffset,
),
None => (0, 0, 0),
},
_ => (0, 0, 0),
}
}
fn take_pack_dir(&mut self, r: NodeRef) {
let dir = std::mem::replace(&mut self.pack_dir, -1);
if dir >= 0
&& let Some(b) = self.nodes.box_data_mut(r)
{
b.dir = dir;
}
}
pub fn hpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
self.fonts.sync_dense();
self.last_badness = 0;
let r = self.new_null_box_node(true);
self.take_pack_dir(r);
let (mut h, mut d, mut x) = (0i32, 0i32, 0i32);
let mut tot = Totals::default();
let head = self.nodes.new_temp();
self.nodes.link(head, p);
let mut q = head;
let mut p = p;
while !p.is_null() {
let next = self.nodes.next(p);
match self.nodes.data(p) {
NodeData::Glyph(_) => {
self.check_lost_char(p);
let (w, gh, gd) = self.glyph_dimensions(p);
x += w;
if gh > h {
h = gh;
}
if gd > d {
d = gd;
}
}
NodeData::Box(b) => {
x += b.width;
let s = b.shift;
if b.height - s > h {
h = b.height - s;
}
if b.depth + s > d {
d = b.depth + s;
}
}
NodeData::Unset { b, .. } => {
x += b.width;
if b.height > h {
h = b.height;
}
if b.depth > d {
d = b.depth;
}
}
NodeData::Rule {
width,
height,
depth,
..
} => {
x += *width;
if *height > h {
h = *height;
}
if *depth > d {
d = *depth;
}
}
NodeData::Ins { .. } | NodeData::Mark { .. } | NodeData::Adjust { .. } => {
if let Some(at) = self.ts.adjust_tail {
self.nodes.set_next(q, next);
if !next.is_null() {
self.nodes.set_prev(next, q);
}
let is_pre = self.nodes.id(p) == id::ADJUST && self.nodes.subtype(p) == 1;
if self.nodes.id(p) == id::ADJUST {
let l = self.nodes.list_ptr(p);
self.nodes.set_list_ptr(p, NodeRef::NULL);
self.nodes.free(p);
if !l.is_null() {
let target = if is_pre {
self.ts.pre_adjust_tail.unwrap_or(at)
} else {
at
};
self.nodes.link(target, l);
let t = self.nodes.tail_of(l);
if is_pre && self.ts.pre_adjust_tail.is_some() {
self.ts.pre_adjust_tail = Some(t);
} else {
self.ts.adjust_tail = Some(t);
}
}
} else {
self.nodes.link(at, p);
self.nodes.set_next(p, NodeRef::NULL);
self.ts.adjust_tail = Some(p);
}
p = next;
continue;
}
}
NodeData::Glue { spec, leader } => {
x += spec.width;
tot.add(spec);
let (leader, sub) = (*leader, self.nodes.subtype(p));
if sub >= glue_sub::A_LEADERS && !leader.is_null() {
let (lh, ld) = (self.node_height(leader), self.node_depth(leader));
if lh > h {
h = lh;
}
if ld > d {
d = ld;
}
}
}
NodeData::Kern {
kern,
expansion_factor,
} => x += *kern + *expansion_factor,
NodeData::MarginKern { width, .. } => x += *width,
NodeData::Math { surround, glue } => {
if glue.width != 0 || glue.stretch != 0 || glue.shrink != 0 {
x += glue.width;
let g = *glue;
tot.add(&g);
} else {
x += *surround;
}
}
NodeData::Disc { replace, .. } => {
let mut s = *replace;
while !s.is_null() {
self.check_lost_char(s);
let (w, sh, sd) = self.simple_dimensions(s);
x += w;
if sh > h {
h = sh;
}
if sd > d {
d = sd;
}
s = self.nodes.next(s);
}
}
_ => {}
}
q = p;
p = next;
}
if let Some(at) = self.ts.adjust_tail {
self.nodes.set_next(at, NodeRef::NULL);
}
if let Some(at) = self.ts.pre_adjust_tail {
self.nodes.set_next(at, NodeRef::NULL);
}
let first = self.nodes.next(head);
self.nodes.free(head);
if !first.is_null() {
self.nodes.set_prev(first, NodeRef::NULL);
}
let w = match spec {
PackSpec::Exactly(w) => w,
PackSpec::Additional(w) => x + w,
};
{
let b = self.box_mut(r);
b.head = first;
b.height = h;
b.depth = d;
b.width = w;
}
self.ts.last_totals = (tot.stretch, tot.shrink);
let x = w - x;
let report: Option<()> = if x == 0 {
None
} else if x > 0 {
let o = tot.stretch_order();
let ts = tot.stretch[o as usize];
{
let b = self.box_mut(r);
b.glue_order = o;
b.glue_sign = 1;
if ts != 0 {
b.glue_set = crate::nodes::glue_ratio(x, ts);
} else {
b.glue_sign = 0;
b.glue_set = 0.0;
}
}
if o == 0 && !first.is_null() {
self.last_badness = retex_arith::badness(x, tot.stretch[0]);
if self.last_badness > self.eqtb.int(ip::HBADNESS) {
self.printer.print_ln();
if self.last_badness > 100 {
self.diag_capture_start();
self.printer.print_nl("Underfull");
} else {
self.printer.print_nl("Loose");
}
self.printer.print_str(" \\hbox (badness ");
self.printer.print_int(i64::from(self.last_badness));
Some(())
} else {
None
}
} else {
None
}
} else {
let o = tot.shrink_order();
let ts = tot.shrink[o as usize];
{
let b = self.box_mut(r);
b.glue_order = o;
b.glue_sign = 2;
if ts != 0 {
b.glue_set = crate::nodes::glue_ratio(-x, ts);
} else {
b.glue_sign = 0;
b.glue_set = 0.0;
}
}
if ts < -x && o == 0 && !first.is_null() {
self.last_badness = 1_000_000;
self.box_mut(r).glue_set = 1.0;
let excess = -x - tot.shrink[0];
let hfuzz = self.eqtb.dimen(dp::HFUZZ);
if excess > hfuzz || self.eqtb.int(ip::HBADNESS) < 100 {
let ofr = self.eqtb.dimen(dp::OVERFULLRULE);
if ofr > 0 && excess > hfuzz {
let rule = self.nodes.new_rule(ofr, NULL_RULE, NULL_RULE);
if first.is_null() {
self.box_mut(r).head = rule;
} else {
let t = self.nodes.tail_of(first);
self.nodes.link(t, rule);
}
}
self.printer.print_ln();
self.diag_capture_start();
self.printer.print_nl("Overfull \\hbox (");
self.printer.print_scaled(excess);
self.printer.print_str("pt too wide");
Some(())
} else {
None
}
} else if o == 0 && !first.is_null() {
self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
if self.last_badness > self.eqtb.int(ip::HBADNESS) {
self.printer.print_ln();
self.diag_capture_start();
self.printer.print_nl("Tight \\hbox (badness ");
self.printer.print_int(i64::from(self.last_badness));
Some(())
} else {
None
}
} else {
None
}
};
if report.is_some() {
self.finish_hbox_diagnostic(r);
}
r
}
pub fn natural_dimensions(&self, mut p: NodeRef, stop: NodeRef) -> (i32, i32, i32) {
let (mut w, mut h, mut d) = (0i32, 0i32, 0i32);
while !p.is_null() && p != stop {
let (pw, ph, pd) = match self.nodes.data(p) {
NodeData::Glyph(_) => self.glyph_dimensions(p),
NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
NodeData::Rule {
width,
height,
depth,
..
} => (*width, *height, *depth),
NodeData::Disc { replace, .. } => {
let r = *replace;
self.natural_dimensions(r, NodeRef::NULL)
}
_ => (self.nodes.width(p), 0, 0),
};
w += pw;
h = h.max(ph);
d = d.max(pd);
p = self.nodes.next(p);
}
(w, h, d)
}
fn simple_dimensions(&self, p: NodeRef) -> (i32, i32, i32) {
match self.nodes.data(p) {
NodeData::Glyph(_) => self.glyph_dimensions(p),
NodeData::Box(b) => (b.width, b.height - b.shift, b.depth + b.shift),
NodeData::Rule {
width,
height,
depth,
..
} => (*width, *height, *depth),
NodeData::Kern {
kern,
expansion_factor,
} => (*kern + *expansion_factor, 0, 0),
_ => (0, 0, 0),
}
}
fn box_mut(&mut self, r: NodeRef) -> &mut BoxData {
match self.nodes.data_mut(r) {
NodeData::Box(b) => b,
_ => unreachable!("box_mut on a non-box node"),
}
}
fn finish_hbox_diagnostic(&mut self, r: NodeRef) {
if self.ts.output_active {
self.printer
.print_str(") has occurred while \\output is active");
} else {
let pbl = self.ts.pack_begin_line;
if pbl != 0 {
if pbl > 0 {
self.printer.print_str(") in paragraph at lines ");
} else {
self.printer.print_str(") in alignment at lines ");
}
self.printer.print_int(i64::from(pbl.abs()));
self.printer.print_str("--");
} else {
self.printer.print_str(") detected at line ");
}
self.printer.print_int(i64::from(self.input.line()));
}
let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
self.diag_capture_warning(at);
if self.diagnostics.is_some() {
let facts = self.hbox_facts(r);
if let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut()) {
d.facts = facts;
}
}
self.printer.print_ln();
self.ts.font_in_short_display = 0;
let l = self.nodes.list_ptr(r);
self.short_display(l);
self.printer.print_ln();
self.begin_diagnostic();
self.show_box(r);
self.end_diagnostic(true);
}
pub fn vpack(&mut self, p: NodeRef, spec: PackSpec) -> NodeRef {
self.vpackage(p, spec, retex_arith::MAX_DIMEN)
}
pub fn vpackage(&mut self, p: NodeRef, spec: PackSpec, l: i32) -> NodeRef {
self.last_badness = 0;
let r = self.new_null_box_node(false);
self.take_pack_dir(r);
let (mut w, mut d, mut x) = (0i32, 0i32, 0i32);
let mut tot = Totals::default();
let mut q = p;
while !q.is_null() {
match self.nodes.data(q) {
NodeData::Box(b) => {
x += d + b.height;
d = b.depth;
if b.width + b.shift > w {
w = b.width + b.shift;
}
}
NodeData::Unset { b, .. } => {
x += d + b.height;
d = b.depth;
if b.width > w {
w = b.width;
}
}
NodeData::Rule {
width,
height,
depth,
..
} => {
x += d + *height;
d = *depth;
if *width > w {
w = *width;
}
}
NodeData::Glue { spec, leader } => {
x += d;
d = 0;
x += spec.width;
tot.add(spec);
let leader = *leader;
if self.nodes.subtype(q) >= glue_sub::A_LEADERS && !leader.is_null() {
let lw = self.nodes.width(leader);
if lw > w {
w = lw;
}
}
}
NodeData::Kern { kern, .. } => {
x += d + *kern;
d = 0;
}
_ => {}
}
q = self.nodes.next(q);
}
{
let b = self.box_mut(r);
b.width = w;
b.head = p;
}
if d > l {
x += d - l;
self.box_mut(r).depth = l;
} else {
self.box_mut(r).depth = d;
}
let h = match spec {
PackSpec::Exactly(h) => h,
PackSpec::Additional(h) => x + h,
};
self.box_mut(r).height = h;
self.ts.last_totals = (tot.stretch, tot.shrink);
let x = h - x;
let report = if x == 0 {
false
} else if x > 0 {
let o = tot.stretch_order();
let ts = tot.stretch[o as usize];
{
let b = self.box_mut(r);
b.glue_order = o;
b.glue_sign = 1;
if ts != 0 {
b.glue_set = crate::nodes::glue_ratio(x, ts);
} else {
b.glue_sign = 0;
b.glue_set = 0.0;
}
}
if o == 0 && !p.is_null() {
self.last_badness = retex_arith::badness(x, tot.stretch[0]);
if self.last_badness > self.eqtb.int(ip::VBADNESS) {
self.printer.print_ln();
if self.last_badness > 100 {
self.diag_capture_start();
self.printer.print_nl("Underfull");
} else {
self.printer.print_nl("Loose");
}
self.printer.print_str(" \\vbox (badness ");
self.printer.print_int(i64::from(self.last_badness));
true
} else {
false
}
} else {
false
}
} else {
let o = tot.shrink_order();
let ts = tot.shrink[o as usize];
{
let b = self.box_mut(r);
b.glue_order = o;
b.glue_sign = 2;
if ts != 0 {
b.glue_set = crate::nodes::glue_ratio(-x, ts);
} else {
b.glue_sign = 0;
b.glue_set = 0.0;
}
}
if ts < -x && o == 0 && !p.is_null() {
self.last_badness = 1_000_000;
self.box_mut(r).glue_set = 1.0;
let excess = -x - tot.shrink[0];
if excess > self.eqtb.dimen(dp::VFUZZ) || self.eqtb.int(ip::VBADNESS) < 100 {
self.printer.print_ln();
self.diag_capture_start();
self.printer.print_nl("Overfull \\vbox (");
self.printer.print_scaled(excess);
self.printer.print_str("pt too high");
true
} else {
false
}
} else if o == 0 && !p.is_null() {
self.last_badness = retex_arith::badness(-x, tot.shrink[0]);
if self.last_badness > self.eqtb.int(ip::VBADNESS) {
self.printer.print_ln();
self.diag_capture_start();
self.printer.print_nl("Tight \\vbox (badness ");
self.printer.print_int(i64::from(self.last_badness));
true
} else {
false
}
} else {
false
}
};
if report {
if self.ts.output_active {
self.printer
.print_str(") has occurred while \\output is active");
} else {
let pbl = self.ts.pack_begin_line;
if pbl != 0 {
self.printer.print_str(") in alignment at lines ");
self.printer.print_int(i64::from(pbl.abs()));
self.printer.print_str("--");
} else {
self.printer.print_str(") detected at line ");
}
self.printer.print_int(i64::from(self.input.line()));
}
let at = (self.ts.pack_begin_line != 0).then(|| self.ts.pack_begin_line.unsigned_abs());
self.diag_capture_warning(at);
if self.ts.output_active
&& self.last_badness > 100
&& self.last_badness < 1_000_000
&& let Some((message, notes, help)) = self.underfull_page_note(self.last_badness)
&& let Some(d) = self.diagnostics.as_mut().and_then(|l| l.last_mut())
&& d.message.starts_with("Underfull \\vbox")
{
d.message = message;
d.notes = notes;
d.help = help;
}
if !self.ts.output_active {
self.printer.print_ln();
}
self.begin_diagnostic();
self.show_box(r);
self.end_diagnostic(true);
}
r
}
}
const NULL_RULE: i32 = crate::typeset::NULL_FLAG;