Files
wordcraft/crates/layout/src/math/structs.rs
T

337 lines
14 KiB
Rust

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<Bx>, over: Option<Bx>, 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<Bx>, sup: Option<Bx>, 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<Bx>, sup: Option<Bx>, 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<Arg>], col_jc: &[ColJc], st: St, d: usize) -> Bx {
let em = self.em(st);
let mut cells: Vec<Vec<Bx>> = 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::<f32>() + gap * ncols.saturating_sub(1) as f32;
out
}
}