type Style = usize;
static GLYPH_W: &[(&str, [f64; 3])] = &[
("a", [0.1858, 0.1525, 0.1361]),
("b", [0.1508, 0.1236, 0.1113]),
("c", [0.1521, 0.1256, 0.1141]),
("d", [0.1829, 0.1463, 0.1300]),
("e", [0.1636, 0.1333, 0.1190]),
("f", [0.2099, 0.1646, 0.1410]),
("g", [0.1802, 0.1459, 0.1294]),
("h", [0.2025, 0.1645, 0.1454]),
("i", [0.1211, 0.0994, 0.0938]),
("j", [0.1649, 0.1305, 0.1137]),
("k", [0.1940, 0.1554, 0.1361]),
("l", [0.1118, 0.0903, 0.0844]),
("m", [0.3086, 0.2494, 0.2158]),
("n", [0.2110, 0.1737, 0.1548]),
("o", [0.1704, 0.1387, 0.1235]),
("p", [0.1768, 0.1449, 0.1300]),
("q", [0.1695, 0.1376, 0.1221]),
("r", [0.1683, 0.1381, 0.1241]),
("s", [0.1647, 0.1327, 0.1178]),
("t", [0.1269, 0.1062, 0.0991]),
("u", [0.2012, 0.1662, 0.1487]),
("v", [0.1830, 0.1494, 0.1332]),
("w", [0.2611, 0.2099, 0.1820]),
("x", [0.2009, 0.1594, 0.1391]),
("y", [0.1849, 0.1514, 0.1347]),
("z", [0.1789, 0.1444, 0.1275]),
("A", [0.2636, 0.2114, 0.1821]),
("B", [0.2842, 0.2243, 0.1904]),
("C", [0.2763, 0.2188, 0.1870]),
("D", [0.3007, 0.2374, 0.2011]),
("E", [0.2797, 0.2199, 0.1860]),
("F", [0.2748, 0.2161, 0.1829]),
("G", [0.2763, 0.2188, 0.1870]),
("H", [0.3207, 0.2490, 0.2082]),
("I", [0.1821, 0.1437, 0.1236]),
("J", [0.2287, 0.1787, 0.1525]),
("K", [0.3236, 0.2534, 0.2121]),
("L", [0.2392, 0.1928, 0.1679]),
("M", [0.3793, 0.2944, 0.2448]),
("N", [0.3207, 0.2490, 0.2082]),
("O", [0.2778, 0.2213, 0.1902]),
("P", [0.2745, 0.2168, 0.1843]),
("Q", [0.2778, 0.2213, 0.1902]),
("R", [0.2696, 0.2130, 0.1813]),
("S", [0.2358, 0.1860, 0.1594]),
("T", [0.2542, 0.2038, 0.1768]),
("U", [0.2783, 0.2178, 0.1850]),
("V", [0.2831, 0.2265, 0.1943]),
("W", [0.3807, 0.3022, 0.2553]),
("X", [0.3188, 0.2496, 0.2090]),
("Y", [0.2821, 0.2257, 0.1937]),
("Z", [0.2651, 0.2087, 0.1777]),
("0", [0.1757, 0.1401, 0.1196]),
("1", [0.1757, 0.1401, 0.1196]),
("2", [0.1757, 0.1401, 0.1196]),
("3", [0.1757, 0.1401, 0.1196]),
("4", [0.1757, 0.1401, 0.1196]),
("5", [0.1757, 0.1401, 0.1196]),
("6", [0.1757, 0.1401, 0.1196]),
("7", [0.1757, 0.1401, 0.1196]),
("8", [0.1757, 0.1401, 0.1196]),
("9", [0.1757, 0.1401, 0.1196]),
("alpha", [0.2261, 0.1827, 0.1605]),
("beta", [0.2173, 0.1720, 0.1480]),
("gamma", [0.2015, 0.1628, 0.1435]),
("delta", [0.1695, 0.1367, 0.1206]),
("epsilon", [0.1427, 0.1171, 0.1078]),
("varepsilon", [0.1639, 0.1328, 0.1186]),
("zeta", [0.1797, 0.1438, 0.1265]),
("eta", [0.1871, 0.1536, 0.1370]),
("theta", [0.1748, 0.1414, 0.1251]),
("vartheta", [0.2079, 0.1697, 0.1503]),
("mu", [0.2118, 0.1709, 0.1493]),
("nu", [0.1960, 0.1573, 0.1375]),
("xi", [0.1699, 0.1363, 0.1204]),
("pi", [0.2130, 0.1732, 0.1524]),
("phi", [0.2094, 0.1690, 0.1479]),
("varphi", [0.2299, 0.1849, 0.1613]),
("rho", [0.1817, 0.1458, 0.1276]),
("sigma", [0.2134, 0.1717, 0.1496]),
("tau", [0.1934, 0.1580, 0.1402]),
("Gamma", [0.2197, 0.1738, 0.1460]),
("Delta", [0.2929, 0.2309, 0.1928]),
("Pi", [0.2636, 0.2075, 0.1723]),
("wp", [0.2237, 0.1796, 0.1563]),
("hbar", [0.1899, 0.1557, 0.1391]),
("infty", [0.3515, 0.2802, 0.2392]),
("Ra", [0.1757, 0.1401, 0.1196]),
("Rb", [0.1953, 0.1552, 0.1318]),
("Rc", [0.1562, 0.1250, 0.1074]),
("Rd", [0.1953, 0.1552, 0.1318]),
("Re", [0.1562, 0.1250, 0.1074]),
("Rf", [0.1059, 0.0858, 0.0788]),
("Rg", [0.1763, 0.1406, 0.1200]),
("Rh", [0.1953, 0.1552, 0.1318]),
("Ri", [0.0976, 0.0796, 0.0708]),
("Rj", [0.1074, 0.0871, 0.0769]),
("Rk", [0.1855, 0.1477, 0.1257]),
("Rl", [0.0976, 0.0796, 0.0708]),
("Rm", [0.2929, 0.2309, 0.1928]),
("Rn", [0.1953, 0.1552, 0.1318]),
("Ro", [0.1843, 0.1467, 0.1249]),
("Rp", [0.1953, 0.1552, 0.1318]),
("Rq", [0.1855, 0.1477, 0.1257]),
("Rr", [0.1377, 0.1098, 0.0952]),
("Rs", [0.1386, 0.1113, 0.0964]),
("Rt", [0.1367, 0.1098, 0.0952]),
("Ru", [0.1953, 0.1552, 0.1318]),
("Rv", [0.1861, 0.1481, 0.1261]),
("Rw", [0.2544, 0.2011, 0.1688]),
("Rx", [0.1855, 0.1477, 0.1257]),
("Ry", [0.1861, 0.1481, 0.1261]),
("Rz", [0.1562, 0.1250, 0.1074]),
("R0", [0.1757, 0.1401, 0.1196]),
("R1", [0.1757, 0.1401, 0.1196]),
("R2", [0.1757, 0.1401, 0.1196]),
("R3", [0.1757, 0.1401, 0.1196]),
("R4", [0.1757, 0.1401, 0.1196]),
("R5", [0.1757, 0.1401, 0.1196]),
("R6", [0.1757, 0.1401, 0.1196]),
("R7", [0.1757, 0.1401, 0.1196]),
("R8", [0.1757, 0.1401, 0.1196]),
("R9", [0.1757, 0.1401, 0.1196]),
("sim", [0.2734, 0.2197, 0.1904]),
("to", [0.3515, 0.2802, 0.2392]),
("leq", [0.2734, 0.2197, 0.1904]),
("geq", [0.2734, 0.2197, 0.1904]),
("in", [0.2343, 0.1894, 0.1660]),
("notin", [0.2343, 0.1894, 0.1660]),
("approx", [0.2734, 0.2197, 0.1904]),
("equiv", [0.2734, 0.2197, 0.1904]),
("neq", [0.2734, 0.2158, 0.1806]),
("otimes", [0.2734, 0.2197, 0.1904]),
("times", [0.2734, 0.2197, 0.1904]),
("ast", [0.1757, 0.1440, 0.1294]),
("cdot", [0.0976, 0.0835, 0.0805]),
("langle", [0.1367, 0.1137, 0.1050]),
("rangle", [0.1367, 0.1137, 0.1050]),
("cdots", [0.4686, 0.2984, 0.2847]),
("vdots", [0.0976, 0.0976, 0.0976]),
("Vert", [0.1757, 0.1440, 0.1294]),
("eq", [0.2734, 0.2158, 0.1806]),
("lt", [0.2734, 0.2197, 0.1904]),
("gt", [0.2734, 0.2197, 0.1904]),
("plus", [0.2734, 0.2158, 0.1806]),
("minus", [0.2734, 0.2197, 0.1904]),
("pipe", [0.0976, 0.0835, 0.0805]),
("slash", [0.1757, 0.1440, 0.1294]),
("lbrace", [0.1757, 0.1440, 0.1294]),
("rbrace", [0.1757, 0.1440, 0.1294]),
("lpar", [0.1367, 0.1098, 0.0952]),
("rpar", [0.1367, 0.1098, 0.0952]),
("lbrk", [0.0976, 0.0796, 0.0708]),
("rbrk", [0.0976, 0.0796, 0.0708]),
("sum", [0.5077, 0.2953, 0.2109]),
("int", [0.3515, 0.1894, 0.1353]),
("oint", [0.3515, 0.1894, 0.1353]),
("prod", [0.4491, 0.2651, 0.1893]),
("max", [0.6541, 0.5186, 0.4381]),
("min", [0.5858, 0.4657, 0.3954]),
("sup", [0.5291, 0.4218, 0.3600]),
("inf", [0.4276, 0.3416, 0.2950]),
("lim", [0.4881, 0.3900, 0.3344]),
("liminf", [0.9743, 0.7796, 0.6725]),
("limsup", [1.0759, 0.8598, 0.7375]),
("Pr", [0.3768, 0.2987, 0.2533]),
("cos", [0.4706, 0.3764, 0.3234]),
("sin", [0.4315, 0.3461, 0.2990]),
("tan", [0.5077, 0.4052, 0.3466]),
("log", [0.4540, 0.3635, 0.3130]),
("exp", [0.5370, 0.4279, 0.3649]),
("ker", [0.4696, 0.3749, 0.3222]),
];
const SCRIPTSPACE: f64 = 0.0176;
const NULLDELIM2: f64 = 0.0843;
const THIN: f64 = 0.0586;
const MED: f64 = 0.0781;
const THICK: f64 = 0.0976;
const QUAD: f64 = 0.3515;
const PRIME: f64 = 0.0989;
const RAD_OVER: [f64; 3] = [0.2929, 0.2309, 0.1928];
const MAT_GAP: f64 = 0.3515;
fn matrix_brack(env: &str) -> f64 {
match env {
"bmatrix" | "Bmatrix" => 0.371,
"pmatrix" | "vmatrix" => 0.5174,
_ => 0.0,
}
}
const BOLD_FACTOR: f64 = 1.2;
const UNKNOWN_CMD: f64 = 0.22;
pub(crate) fn display_math_width_cm(tex: &str) -> f64 {
let mut tex = tex.to_string();
while let Some(i) = tex.find("\\tag{") {
let mut depth = 1usize;
let mut j = i + 5;
let bytes = tex.as_bytes();
while j < bytes.len() {
match bytes[j] {
b'{' => depth += 1,
b'}' => {
depth -= 1;
if depth == 0 {
j += 1;
break;
}
}
_ => {}
}
j += 1;
}
tex.replace_range(i..j.min(tex.len()), "");
}
split_display_rows(&tex)
.iter()
.map(|row| row_cm(row, 0, false))
.reduce(f64::max)
.unwrap_or(0.0)
}
fn split_display_rows(tex: &str) -> Vec<&str> {
let mut rows = Vec::new();
let mut depth = 0i32;
let mut start = 0usize;
let mut i = 0usize;
while i < tex.len() {
if let Some((len, is_begin, _)) = matrix_token_at(tex, i) {
depth = if is_begin { depth + 1 } else { (depth - 1).max(0) };
i += len;
continue;
}
if depth == 0 && tex[i..].starts_with("\\\\") {
rows.push(&tex[start..i]);
i += 2;
start = i;
continue;
}
i += 1;
}
rows.push(&tex[start..]);
rows
}
fn matrix_token_at(s: &str, i: usize) -> Option<(usize, bool, f64)> {
if !s[i..].starts_with('\\') {
return None;
}
let rest = &s[i + 1..];
let (head, is_begin) = if rest.starts_with("begin{") {
("begin{", true)
} else if rest.starts_with("end{") {
("end{", false)
} else {
return None;
};
let after = &rest[head.len()..];
let close = after.find('}')?;
let env = &after[..close];
let flavor = match env {
"matrix" | "bmatrix" | "Bmatrix" | "pmatrix" | "vmatrix" => env,
_ => return None,
};
Some((1 + head.len() + close + 1, is_begin, matrix_brack(flavor)))
}
const C_START: u8 = 0;
const C_ORD: u8 = 1;
const C_OP: u8 = 2;
const C_BIN: u8 = 3;
const C_REL: u8 = 4;
const C_OPEN: u8 = 5;
const C_CLOSE: u8 = 6;
const C_PUNCT: u8 = 7;
const C_INNER: u8 = 8;
static GLUE: [[u8; 9]; 9] = [
[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 2, 3, 1, 0, 0, 0], [0, 1, 1, 2, 3, 1, 1, 1, 1], [0, 2, 2, 0, 0, 2, 2, 2, 2], [0, 3, 3, 0, 0, 3, 3, 3, 3], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 2, 3, 1, 1, 1, 1], [0, 1, 1, 0, 1, 1, 1, 1, 1], [0, 0, 1, 2, 3, 1, 1, 1, 0], ];
fn glue_cm(units: u8, style: Style) -> f64 {
if style > 0 || units == 0 {
return 0.0;
}
match units {
1 => THIN,
2 => MED,
3 => THICK,
_ => 0.0,
}
}
fn inner_top(s: &str) -> bool {
s.contains("\\frac") || s.contains("\\sqrt") || s.contains("\\begin{")
}
fn row_cm(row: &str, style: Style, text_ctx: bool) -> f64 {
let mut style = style;
let mut bank = 0.0;
let mut s = row.to_string();
loop {
let Some((brack, mstart, inner, mend)) = find_matrix(&s) else { break };
let rows: Vec<Vec<&str>> = split_display_rows(&inner)
.iter()
.map(|r| r.split('&').collect())
.collect();
let ncols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
let mut total = 0.0;
for c in 0..ncols {
total += rows
.iter()
.filter_map(|r| r.get(c))
.map(|cell| row_cm(cell, style, text_ctx))
.fold(0.0f64, f64::max);
}
let widest = total + MAT_GAP * ncols.saturating_sub(1) as f64 + brack;
bank += widest;
s = format!("{}\u{E000}{}", &s[..mstart], &s[mend..]);
}
let mut w = bank;
let mut prev = C_START;
let mut cs = s.chars().peekable();
while let Some(c) = cs.next() {
if c == '\\' {
let mut name = String::new();
while let Some(&d) = cs.peek() {
if d.is_ascii_alphabetic() {
name.push(d);
cs.next();
} else {
break;
}
}
if name.is_empty() {
match cs.next() {
Some(',') => w += 0.1389,
Some(':') => w += 0.0308,
Some(';') => w += 0.0385,
Some('!') => w -= THIN,
Some('{') | Some('}') => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
w += 0.1757;
prev = C_ORD;
}
Some(' ') | Some('\\') | Some('%') | Some('$') | Some('&') => {}
Some(_) => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
w += UNKNOWN_CMD;
prev = C_ORD;
}
None => {}
}
continue;
}
if matches!(
name.as_str(),
"displaystyle" | "textstyle" | "scriptstyle" | "scriptscriptstyle"
) {
style = match name.as_str() {
"scriptstyle" => 1,
"scriptscriptstyle" => 2,
_ => 0,
};
continue;
}
if matches!(
name.as_str(),
"max" | "min" | "sup" | "inf" | "lim" | "liminf" | "limsup" | "Pr"
| "cos" | "sin" | "tan" | "log" | "exp" | "ker"
) {
w += glue_cm(GLUE[prev as usize][C_OP as usize], style);
let atom = glyph_cm(&name, style).unwrap_or(UNKNOWN_CMD);
let sc = take_scripts(&mut cs, style, text_ctx);
w += match sc {
Some(s) => atom.max(s),
None => atom,
};
prev = C_OP;
continue;
}
if matches!(
name.as_str(),
"sum" | "prod" | "coprod" | "bigcup" | "bigcap"
) {
w += glue_cm(GLUE[prev as usize][C_OP as usize], style);
let atom = glyph_cm(&name, style).unwrap_or(UNKNOWN_CMD);
let sc = take_scripts(&mut cs, style, text_ctx);
w += match (sc, style) {
(Some(s), 0) => atom.max(s),
(Some(s), _) => atom + s + SCRIPTSPACE,
(None, _) => atom,
};
prev = C_OP;
continue;
}
if matches!(name.as_str(), "int" | "iint" | "iiint" | "oint") {
w += glue_cm(GLUE[prev as usize][C_OP as usize], style);
let atom = glyph_cm(&name, style).unwrap_or(UNKNOWN_CMD);
let sc = take_scripts(&mut cs, style, text_ctx);
w += atom + sc.map(|s| s + SCRIPTSPACE).unwrap_or(0.0);
prev = C_OP;
continue;
}
match name.as_str() {
"left" => {
w += glue_cm(GLUE[prev as usize][C_OPEN as usize], style);
w += lr_delim(&mut cs);
prev = C_OPEN;
}
"right" => {
w += glue_cm(GLUE[prev as usize][C_CLOSE as usize], style);
w += lr_delim(&mut cs);
prev = C_CLOSE;
}
"frac" | "dfrac" | "tfrac" | "sqrt" => {
w += glue_cm(GLUE[prev as usize][C_INNER as usize], style);
if name == "sqrt" {
if cs.peek() == Some(&'[') {
take_bracket(&mut cs);
}
let a = take_group(&mut cs);
let thin_pad =
if inner_top(&a) { THIN } else { 0.0 };
w += row_cm(&a, style, text_ctx)
+ RAD_OVER[style.min(2)]
+ thin_pad;
} else {
let a = take_group(&mut cs);
let b = take_group(&mut cs);
let child = if style == 0 {
if text_ctx { 1 } else { 0 }
} else {
2
};
w += row_cm(&a, child, true).max(row_cm(&b, child, true))
+ NULLDELIM2;
}
prev = C_INNER;
}
"text" | "mathrm" | "textrm" | "operatorname" => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
let a = take_group(&mut cs);
w += a.chars()
.filter(|c| !c.is_whitespace())
.map(|c| roman_cm(c, style))
.sum::<f64>();
prev = C_ORD;
}
"mathcal" => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
let a = take_group(&mut cs);
w += a.chars()
.filter(|c| c.is_ascii_uppercase())
.map(|c| cal_cm(c, style))
.sum::<f64>();
prev = C_ORD;
}
"mathbb" => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
let a = take_group(&mut cs);
w += a.chars()
.filter(|c| c.is_ascii_uppercase())
.map(|c| bb_cm(c, style))
.sum::<f64>();
prev = C_ORD;
}
"mathbf" | "boldsymbol" | "bm" | "bold" => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
let a = take_group(&mut cs);
w += row_cm(&a, style, text_ctx) * BOLD_FACTOR;
prev = C_ORD;
}
"overset" | "underset" => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
let a = take_group(&mut cs);
let b = take_group(&mut cs);
let child = script_style(style);
w += row_cm(&a, child, text_ctx).max(row_cm(&b, style, text_ctx));
prev = C_ORD;
}
"hat" | "bar" | "vec" | "tilde" | "widehat" | "widetilde"
| "overline" | "underline" | "mathring" | "check" | "breve" => {
let a = take_group(&mut cs);
w += row_cm(&a, style, text_ctx);
}
"quad" => w += QUAD,
"qquad" => w += 2.0 * QUAD,
"langle" | "rangle" | "Vert" | "vert" | "cdots" | "vdots"
| "wp" | "hbar" | "infty" | "ldots" | "dots" => {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
w += glyph_cm(&name, style).unwrap_or(UNKNOWN_CMD);
prev = C_ORD;
}
_ => {
let bare_cls = if matches!(
name.as_str(),
"leq" | "geq" | "neq" | "approx" | "equiv" | "sim" | "simeq"
| "cong" | "to" | "gets" | "leftarrow" | "rightarrow"
| "leftrightarrow" | "Rightarrow" | "Longrightarrow"
| "implies" | "in" | "ni" | "notin" | "subset" | "supset"
| "subseteq" | "supseteq" | "propto" | "perp" | "parallel"
| "mid" | "ll" | "gg" | "prec" | "succ" | "subsetneq"
) {
C_REL
} else if matches!(
name.as_str(),
"pm" | "mp" | "times" | "div" | "cdot" | "circ" | "ast"
| "star" | "cap" | "cup" | "oplus" | "otimes" | "ominus"
| "wedge" | "vee" | "setminus" | "sqcap" | "sqcup"
) {
C_BIN
} else {
C_ORD
};
let mut best = 0.0f64;
let mut any = false;
while cs.peek() == Some(&'{') {
any = true;
let g = take_group(&mut cs);
best = best.max(row_cm(&g, style, text_ctx));
}
if any {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
w += best;
prev = C_ORD;
} else {
w += glue_cm(GLUE[prev as usize][bare_cls as usize], style);
w += glyph_cm(&name, style).unwrap_or(UNKNOWN_CMD);
prev = bare_cls;
}
}
}
} else if c == '^' || c == '_' {
let mut best = script_content(&mut cs, style, text_ctx);
while cs.peek() == Some(&'_') || cs.peek() == Some(&'^') {
cs.next();
best = best.max(script_content(&mut cs, style, text_ctx));
}
w += best + SCRIPTSPACE;
} else if c == '{' || c == '}' || c == '~' || c == ' ' || c == '\t' {
} else if c == '&' {
prev = C_START;
} else if c == '=' || c == '<' || c == '>' {
w += glue_cm(GLUE[prev as usize][C_REL as usize], style);
w += glyph_cm(rel_key(c), style).unwrap_or(UNKNOWN_CMD);
prev = C_REL;
} else if c == '+' || c == '-' {
w += glue_cm(GLUE[prev as usize][C_BIN as usize], style);
w += glyph_cm(if c == '+' { "plus" } else { "minus" }, style)
.unwrap_or(UNKNOWN_CMD);
prev = C_BIN;
} else if c == ',' || c == ';' || c == ':' {
w += glue_cm(GLUE[prev as usize][C_PUNCT as usize], style);
w += if c == ',' { 0.0976 } else { 0.08 };
prev = C_PUNCT;
} else if c == '!' {
w -= THIN;
} else if c == '\'' {
w += PRIME;
} else if c == '\u{E000}' {
w += glue_cm(GLUE[prev as usize][C_INNER as usize], style);
prev = C_INNER;
} else if c.is_ascii_alphanumeric() {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
w += glyph_cm(&c.to_string(), style).unwrap_or(UNKNOWN_CMD);
prev = C_ORD;
} else if "()[]|/".contains(c) {
let key = match c {
'(' => "lpar",
')' => "rpar",
'[' => "lbrk",
']' => "rbrk",
'|' => "pipe",
_ => "slash",
};
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
w += glyph_cm(key, style).unwrap_or(UNKNOWN_CMD);
prev = C_ORD;
} else {
w += glue_cm(GLUE[prev as usize][C_ORD as usize], style);
w += UNKNOWN_CMD;
prev = C_ORD;
}
}
w
}
fn script_style(style: Style) -> Style {
(style + 1).min(2)
}
fn rel_key(c: char) -> &'static str {
match c {
'=' => "eq",
'<' => "lt",
_ => "gt",
}
}
fn lr_delim(cs: &mut std::iter::Peekable<std::str::Chars>) -> f64 {
if cs.peek() == Some(&'\\') {
cs.next();
let mut name = String::new();
while let Some(&d) = cs.peek() {
if d.is_ascii_alphabetic() {
name.push(d);
cs.next();
} else {
break;
}
}
return match name.as_str() {
"langle" | "rangle" | "lceil" | "rceil" | "lfloor" | "rfloor" => 0.1367,
"Vert" | "vert" | "lVert" | "lvert" => 0.176,
"lbrace" | "rbrace" => 0.176,
"uparrow" | "downarrow" | "updownarrow" | "Uparrow" | "Downarrow"
| "Updownarrow" => 0.176,
"." => 0.0423,
_ => 0.1367,
};
}
match cs.next() {
Some('(') | Some(')') => 0.1367,
Some('[') | Some(']') => 0.0976,
Some('{') | Some('}') => 0.176,
Some('|') | Some('/') => 0.176,
_ => 0.0423, }
}
fn glyph_cm(key: &str, style: Style) -> Option<f64> {
GLYPH_W
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| v[style.min(2)])
}
fn roman_cm(c: char, style: Style) -> f64 {
if !c.is_ascii_alphanumeric() {
return 0.16;
}
let key = format!("R{}", c);
glyph_cm(&key, style).unwrap_or(0.16)
}
fn cal_cm(c: char, style: Style) -> f64 {
const CAL_W: &[(char, f64)] = &[
('A', 0.2806), ('D', 0.2809), ('F', 0.2875), ('L', 0.2424),
('R', 0.2979), ('S', 0.2392), ('T', 0.2807),
];
let d = CAL_W
.iter()
.find(|(k, _)| *k == c)
.map(|(_, v)| *v)
.unwrap_or(0.2684);
match style {
0 => d,
1 => d * 0.8,
_ => d * 0.72,
}
}
fn bb_cm(c: char, style: Style) -> f64 {
const BB_W: &[(char, f64)] = &[
('R', 0.2538), ('N', 0.2538), ('E', 0.2343), ('P', 0.2148),
];
let d = BB_W
.iter()
.find(|(k, _)| *k == c)
.map(|(_, v)| *v)
.unwrap_or(0.2391);
match style {
0 => d,
1 => d * 0.8,
_ => d * 0.72,
}
}
fn find_matrix(s: &str) -> Option<(f64, usize, String, usize)> {
let mut i = 0usize;
while i < s.len() {
let Some((len, true, brack)) = matrix_token_at(s, i) else {
match s[i..].as_bytes().iter().position(|&b| b == b'\\') {
Some(0) => i += 1,
Some(off) => i += off,
None => return None,
}
continue;
};
let inner_start = i + len;
let mut d = 1i32;
let mut k = inner_start;
let mut close_start = None;
while k < s.len() {
if let Some((l2, is_begin, _)) = matrix_token_at(s, k) {
if is_begin {
d += 1;
} else {
d -= 1;
if d == 0 {
close_start = Some(k);
}
}
k += l2;
if close_start.is_some() {
break;
}
continue;
}
k += 1;
}
let close_start = close_start?;
let inner = s[inner_start..close_start].to_string();
return Some((brack, i, inner, k));
}
None
}
fn take_group(cs: &mut std::iter::Peekable<std::str::Chars>) -> String {
let mut out = String::new();
if cs.peek() != Some(&'{') {
return out;
}
cs.next();
let mut depth = 1usize;
for c in cs.by_ref() {
match c {
'{' => {
depth += 1;
out.push(c);
}
'}' => {
depth -= 1;
if depth == 0 {
break;
}
out.push(c);
}
_ => out.push(c),
}
}
out
}
fn take_bracket(cs: &mut std::iter::Peekable<std::str::Chars>) {
if cs.peek() == Some(&'[') {
for c in cs.by_ref() {
if c == ']' {
break;
}
}
}
}
fn script_content(
cs: &mut std::iter::Peekable<std::str::Chars>,
style: Style,
text_ctx: bool,
) -> f64 {
let child = script_style(style);
if cs.peek() == Some(&'{') {
let inner = take_group(cs);
row_cm(&inner, child, text_ctx)
} else {
match cs.peek() {
Some('\\') => {
cs.next();
let mut nm = String::new();
while let Some(&d) = cs.peek() {
if d.is_ascii_alphabetic() {
nm.push(d);
cs.next();
} else {
break;
}
}
glyph_cm(&nm, child).unwrap_or(UNKNOWN_CMD)
}
Some(_) => {
let ch = cs.next().unwrap();
if ch.is_ascii_alphanumeric() {
glyph_cm(&ch.to_string(), child).unwrap_or(UNKNOWN_CMD)
} else {
UNKNOWN_CMD
}
}
None => 0.0,
}
}
}
fn take_scripts(
cs: &mut std::iter::Peekable<std::str::Chars>,
style: Style,
text_ctx: bool,
) -> Option<f64> {
let mut best: Option<f64> = None;
while cs.peek() == Some(&'_') || cs.peek() == Some(&'^') {
cs.next();
let g = script_content(cs, style, text_ctx);
best = Some(best.map_or(g, |b: f64| b.max(g)));
}
best
}
#[cfg(test)]
mod tests {
use super::display_math_width_cm as w;
fn eq(name: &str, tex: &str, truth_cm: f64) {
let got = w(tex);
assert!(
(got - truth_cm).abs() < 0.01,
"{name}: got {got:.4}, TeX says {truth_cm:.4}"
);
}
#[test]
fn calibrated_constructs() {
eq("frac-st", r"\frac{s}{t}", 0.249);
eq("frac-ab", r"\frac{a}{b}", 0.2706);
eq("sqrt-6g", r"\sqrt{gggggg}", 1.3741);
eq("sqrt-2g", r"\sqrt{gg}", 0.6531);
eq("x-sup-s", "x^{s}", 0.3512);
eq("x-sub-s", "x_{s}", 0.3512);
eq("x-subsup-s", "x_{s}^{s}", 0.3512);
eq("x-sup-g", "x^{g}", 0.3644);
eq("x-prime", "x'", 0.2995);
eq("x-eq-x", "x=x", 0.8703);
eq("x-leq-x", r"x\leq x", 0.8703);
eq("x-plus-x", "x+x", 0.8313);
eq("x-otimes-y", r"x\otimes y", 0.8154);
eq("script-eq", r"\scriptstyle x=x", 0.5345);
eq("script-plus", r"\scriptstyle x+x", 0.5345);
eq("max-lim", r"\max_{x\in A}", 0.6541);
eq("lim-lim", r"\lim_{x\in A}", 0.5601);
eq("sum-lim", r"\sum_{k=0}^{\beta}", 0.5112);
eq("int-lim", r"\int_{0}^{\zeta}", 0.5129);
eq("sum-frac", r"\sum\frac{a}{b}", 0.8364);
eq("x-frac", r"x\frac{a}{b}", 0.4710);
eq("frac-x", r"\frac{a}{b}x", 0.4710);
eq("frac-frac", r"\frac{a}{b}\frac{c}{d}", 0.5374);
eq("x-lr", r"x\left(x\right)", 0.7337);
eq("frac-lr", r"\frac{a}{b}\left(x\right)", 0.8029);
eq("close-x", r"\left(x\right)x", 0.7337);
eq("frac-nest", r"\frac{\frac{s}{t}}{u}", 0.302);
eq("overset-sim", r"\overset{\sim}{\vartheta}", 0.2197);
eq("x-comma", "x,", 0.2985);
eq("comma-x", ",x", 0.357);
eq("bmatrix", r"\begin{bmatrix} a & b \\ c & d \end{bmatrix}", 1.0906);
eq("pmatrix", r"\begin{pmatrix} a & b \\ c & d \end{pmatrix}", 1.2372);
eq("matrix", r"\begin{matrix} a & b \\ c & d \end{matrix}", 0.7198);
}
#[test]
fn calibrated_composites() {
eq("vfrac", r"\frac{\vartheta(\zeta)}{\Gamma}", 0.7453);
eq("vsumlim2", r"\sum_{k=0}^{\beta}\frac{\zeta}{k}", 0.8482);
eq("vlimfrac", r"\lim_{n\to\infty}\frac{\sigma}{n}", 1.0905);
eq("vmatmix", r"\begin{bmatrix} \vartheta & \zeta \\ \rho & \sigma \end{bmatrix}", 1.1438);
eq("vlrainbow", r"\frac{\alpha}{\beta}+\sqrt{\gamma}\cdot\mathcal{S}\leq\Vert\varphi\Vert", 2.7774);
eq("vunderset", r"\underset{\rho}{\text{max}}", 0.6541);
}
}