use super::bx::{Bx, Cls, St, hcat}; use super::state::wide_accent; use super::{Arg, C, ColJc, Ctx, FracKind, MItem, MNode, glyph_bounds}; impl Ctx { /// Limits under and over `base` (centred), spaced as for big operators (`big`) or as /// accents-like limits. pub(super) fn limits(&self, base: Bx, under: Option, over: Option, ic: f32, st: St, big: bool) -> Bx { let w = base.w.max(under.as_ref().map_or(0.0, |b| b.w)).max(over.as_ref().map_or(0.0, |b| b.w)); let mut out = Bx { w, ..Default::default() }; let (ba, bd) = (base.a, base.d); let bw = base.w; out.put(base, (w - bw) / 2.0, 0.0); if let Some(o) = over { let rise = if big { self.k(C::UpperLimitBaselineRiseMin, st).max(self.k(C::UpperLimitGapMin, st) + o.d) } else { self.k(C::UpperLimitGapMin, st) + o.d }; let x = (w - o.w) / 2.0 + ic / 2.0; out.put(o, x, ba + rise); } if let Some(u) = under { let drop = if big { self.k(C::LowerLimitBaselineDropMin, st).max(self.k(C::LowerLimitGapMin, st) + u.a) } else { self.k(C::LowerLimitGapMin, st) + u.a }; let x = (w - u.w) / 2.0 - ic / 2.0; out.put(u, x, -bd - drop); } out } /// Limits beside a big operator: the superscript at its advance, the subscript tucked left /// under the slant by the italic correction. pub(super) fn attach_op(&self, base: Bx, sub: Option, sup: Option, st: St) -> Bx { let ic = base.ic; let mut b = base; b.ic = 0.0; let (bw, bd) = (b.w, b.d); let mut out = self.attach(b, None, sup, st, false); if let Some(s) = sub { let v = self.k(C::SubscriptShiftDown, st).max(s.a - self.k(C::SubscriptTopMax, st)).max(bd + self.k(C::SubscriptBaselineDropMin, st)); let x = (bw - ic).max(0.0); let sw = s.w; out.put(s, x, -v); out.w = out.w.max(x + sw + self.k(C::SpaceAfterScript, st)); } out } /// Sub/superscripts after `base`. `char_base`: the base is a single character (no baseline drop). pub(super) fn attach(&self, base: Bx, sub: Option, sup: Option, st: St, char_base: bool) -> Bx { let (bw, ba, bd, ic) = (base.w, base.a, base.d, base.ic); let (first, last) = (base.first, base.last); let mut out = Bx { w: bw, ..Default::default() }; out.put(base, 0.0, 0.0); let mut u = 0.0f32; let mut v = 0.0f32; if let Some(p) = &sup { let shift = if st.cramped { self.k(C::SuperscriptShiftUpCramped, st) } else { self.k(C::SuperscriptShiftUp, st) }; u = shift.max(p.d + self.k(C::SuperscriptBottomMin, st)); if !char_base { u = u.max(ba - self.k(C::SuperscriptBaselineDropMax, st)); } } if let Some(b) = &sub { v = self.k(C::SubscriptShiftDown, st).max(b.a - self.k(C::SubscriptTopMax, st)); if !char_base { v = v.max(bd + self.k(C::SubscriptBaselineDropMin, st)); } } if let (Some(p), Some(b)) = (&sup, &sub) { let gap = (u - p.d) - (b.a - v); let min = self.k(C::SubSuperscriptGapMin, st); if gap < min { v += min - gap; let lift = self.k(C::SuperscriptBottomMaxWithSubscript, st) - (u - p.d); if lift > 0.0 { u += lift; v -= lift; } } } let space = self.k(C::SpaceAfterScript, st); let mut w = bw; if let Some(p) = sup { let x = bw + ic; w = w.max(x + p.w); out.put(p, x, u); } if let Some(b) = sub { let x = bw; w = w.max(x + b.w); out.put(b, x, -v); } out.w = w + if w > bw { space } else { 0.0 }; out.first = first; out.last = last; out } pub(super) fn frac(&mut self, kind: FracKind, num: &[MNode], den: &[MNode], st: St, d: usize) -> Bx { let em = self.em(st); match kind { FracKind::Linear => { let n = self.sub(0, num, st, d); let dn = self.sub(1, den, st, d); let slash = self.glyphs(self.face, "/", self.size(st), st.level, self.color, (false, false)).cls(Cls::Ord); hcat(vec![n, slash, dn]) } FracKind::Skewed => { let n = self.sub(0, num, st.script(), d); let dn = self.sub(1, den, st.script().cramp(), d); let size = self.size(st); let slash = self.glyphs(self.face, "\u{2044}", size, 0, self.color, (false, false)); let gap = self.k(C::SkewedFractionHorizontalGap, st) / 2.0; let (nw, sw) = (n.w, slash.w); let mut out = Bx { w: n.w + gap + slash.w + gap + dn.w, ..Default::default() }; let up = self.axis(st) + self.k(C::SkewedFractionVerticalGap, st) / 2.0; out.put(n, 0.0, up.max(0.0)); out.put(slash, nw + gap, 0.0); out.put(dn, nw + gap + sw + gap, -self.k(C::SkewedFractionVerticalGap, st) / 2.0); out } FracKind::Bar | FracKind::NoBar => { let cs = st.frac(); let n = self.sub(0, num, St { cramped: st.cramped, ..cs }, d); let dn = self.sub(1, den, cs.cramp(), d); let axis = self.axis(st); let (u, v) = if kind == FracKind::Bar { let t = self.k(C::FractionRuleThickness, st); let (su, sd, gn, gd) = if st.display { ( self.k(C::FractionNumeratorDisplayStyleShiftUp, st), self.k(C::FractionDenominatorDisplayStyleShiftDown, st), self.k(C::FractionNumDisplayStyleGapMin, st), self.k(C::FractionDenomDisplayStyleGapMin, st), ) } else { ( self.k(C::FractionNumeratorShiftUp, st), self.k(C::FractionDenominatorShiftDown, st), self.k(C::FractionNumeratorGapMin, st), self.k(C::FractionDenominatorGapMin, st), ) }; (su.max(gn + t / 2.0 + axis + n.d), sd.max(gd + t / 2.0 - axis + dn.a)) } else { let (mut su, mut sd, gap) = if st.display { ( self.k(C::StackTopDisplayStyleShiftUp, st), self.k(C::StackBottomDisplayStyleShiftDown, st), self.k(C::StackDisplayStyleGapMin, st), ) } else { (self.k(C::StackTopShiftUp, st), self.k(C::StackBottomShiftDown, st), self.k(C::StackGapMin, st)) }; let g = (su - n.d) - (dn.a - sd); if g < gap { su += (gap - g) / 2.0; sd += (gap - g) / 2.0; } (su, sd) }; let pad = em * 0.08; let w = n.w.max(dn.w) + 2.0 * pad; let (nw, dw) = (n.w, dn.w); let mut out = Bx { w, ..Default::default() }; out.put(n, (w - nw) / 2.0, u); out.put(dn, (w - dw) / 2.0, -v); if kind == FracKind::Bar { let t = self.k(C::FractionRuleThickness, st).max(0.4); out.rect(pad * 0.5, axis - t / 2.0, w - pad, t, self.color); } out } } } pub(super) fn radical(&mut self, deg: &[MNode], deg_hide: bool, e: &[MNode], st: St, d: usize) -> Bx { let b = self.sub(1, e, st.cramp(), d); let size = self.size(st); let t = self.k(C::RadicalRuleThickness, st).max(0.4); let mut gap = if st.display { self.k(C::RadicalDisplayStyleVerticalGap, st) } else { self.k(C::RadicalVerticalGap, st) }; let target = b.a + b.d + gap + t; let gid = self.face.glyph_for('√'); let g = self.stretch_v(gid, target, size).unwrap_or_else(|| { let mut g = self.glyphs(self.face, "√", size, 0, self.color, (false, false)); g.glyph = None; g }); let extra = (g.a + g.d) - target; if extra > 0.0 { gap += extra / 2.0; } // The sign's top meets the rule over the base. let top = b.a + gap + t; let dy = top - g.a; let (ga, gd, gw) = (g.a, g.d, g.w); let mut sign = Bx { w: gw, ..Default::default() }; sign.put(g, 0.0, dy); // Degree. let mut x0 = 0.0f32; let mut out = Bx::default(); if !deg_hide { let db = self.sub(0, deg, St { level: (st.level + 2).min(2), display: false, cramped: true, upright: st.upright }, d); let kb = self.k(C::RadicalKernBeforeDegree, st); let ka = self.k(C::RadicalKernAfterDegree, st); let raise = self.t.as_ref().map_or(60.0, |t| t.radical_degree_bottom_raise_percent) / 100.0 * (ga + gd); let y = (dy - gd) + raise; let dw = db.w; out.put(db, kb, y); x0 = (kb + dw + ka).max(0.0); } let bw = b.w; out.put(sign, x0, 0.0); out.rect(x0 + gw, top - t, bw, t, self.color); out.put(b, x0 + gw, 0.0); out.w = x0 + gw + bw; out.a = out.a.max(top + self.k(C::RadicalExtraAscender, st)); out } pub(super) fn accent(&mut self, chr: char, e: &[MNode], st: St, d: usize) -> Bx { let b = self.sub(0, e, st.cramp(), d); let size = self.size(st); let color = self.color; // Overline-style accents are rules. if matches!(chr, '\u{305}' | '\u{AF}' | '\u{304}' | '\u{203E}') { let (w, a) = (b.w, b.a); let mut out = Bx { w, ..Default::default() }; let (f, l) = (b.first, b.last); out.put(b, 0.0, 0.0); let t = self.k(C::OverbarRuleThickness, st).max(0.4); let y = a + self.k(C::OverbarVerticalGap, st); out.rect(0.0, y, w, t, color); out.first = f; out.last = l; return out; } let gid = self.face.glyph_for(chr); // Combining marks have no advance: compare the base with the accent's ink. let ink = glyph_bounds(&self.face, gid).map(|(x0, _, x1, _)| (x1 - x0) * size / self.upem).unwrap_or(0.0); let narrow = gid != 0 && b.w <= ink * 1.2; let mut acc = if wide_accent(chr) && gid != 0 && !narrow { self.stretch_h(gid, b.w, size).unwrap_or_else(|| self.glyph_id(gid, size, color)) } else if gid != 0 { self.glyph_id(gid, size, color) } else { self.char_glyphs(chr, size, 0) }; acc.glyph = None; // Horizontal: the base's attachment point under the accent's. let base_x = b.glyph.map(|(_, _, ax)| ax).unwrap_or(b.w / 2.0); let acc_x = { let single = acc.items.first().and_then(|it| match it { MItem::Glyphs { glyphs, .. } if glyphs.len() == 1 => glyphs.first().map(|g| g.0), _ => None, }); let k = size / self.upem; match single { Some(g) => match self.t.as_ref().and_then(|t| t.top_accent.get(&g).copied()) { Some(v) => v * k, None => glyph_bounds(&self.face, g).map(|(x0, _, x1, _)| (x0 + x1) / 2.0 * k).unwrap_or(acc.w / 2.0), }, None => acc.w / 2.0, } }; // Vertical: accents sit on an x-height base; taller bases push them up. let abh = self.k(C::AccentBaseHeight, st); let raise = (b.a - abh).max(0.0); let (f, l, w, ic) = (b.first, b.last, b.w, b.ic); let glyph = b.glyph; let mut out = Bx { w, ..Default::default() }; out.put(b, 0.0, 0.0); let mut x = base_x - acc_x; if x < 0.0 && acc.w > w { x = (w - acc.w) / 2.0; } out.put(acc, x, raise); out.first = f; out.last = l; out.ic = ic; out.glyph = glyph.filter(|_| false); out } pub(super) fn matrix(&mut self, rows: &[Vec], col_jc: &[ColJc], st: St, d: usize) -> Bx { let em = self.em(st); let mut cells: Vec> = Vec::new(); let mut flat = 0usize; for r in rows.iter().take(1000) { let mut row = Vec::new(); for (ci, c) in r.iter().enumerate() { if ci < 64 { row.push(self.sub(flat, c, st, d)); } flat += 1; } cells.push(row); } let ncols = cells.iter().map(Vec::len).max().unwrap_or(0); let mut col_w = vec![0.0f32; ncols]; for r in &cells { for (i, b) in r.iter().enumerate() { if let Some(c) = col_w.get_mut(i) { *c = c.max(b.w); } } } let gap = em * 0.8; let mut placed = Vec::new(); for r in cells { let mut row = Bx::default(); let mut x = 0.0f32; for (i, b) in r.into_iter().enumerate() { let cw = col_w.get(i).copied().unwrap_or(0.0); let bw = b.w; let dx = match col_jc.get(i).copied().unwrap_or_default() { ColJc::Center => (cw - bw) / 2.0, ColJc::Left => 0.0, ColJc::Right => cw - bw, }; row.put(b, x + dx, 0.0); x += cw + gap; } row.w = (x - gap).max(0.0); placed.push((row, 0.0)); } let (mut out, _) = self.stack_rows(placed, st, em * 0.25, em * 1.2); out.w = col_w.iter().sum::() + gap * ncols.saturating_sub(1) as f32; out } }