katex_parser/anvil/common.rs
1//! Shared, backend-agnostic helpers for the rendering backends (mathml,
2//! typst, unicode). These encode KaTeX facts that each backend would otherwise
3//! re-implement with drift risk: symbol resolution, null delimiters, control
4//! sequence names, font sizes, unit conversions, and style selection.
5
6use crate::ast::{Measurement, ParseNode, StyleLevel};
7use crate::symbol_registry::unicode_symbol;
8
9/// Resolves a TeX command name or raw Unicode string to its Unicode rendering
10/// via the parser's symbol registry, falling back to the input itself.
11pub fn resolve_symbol(text: &str) -> String {
12 unicode_symbol(text).unwrap_or_else(|| text.to_string())
13}
14
15/// True for the null delimiter `.` used by `\left.` / `\right.`, which
16/// renders as nothing in every backend.
17pub fn is_null_delimiter(text: &str) -> bool {
18 text == "."
19}
20
21/// Strips the leading backslash from a control-sequence label (`\sin` ->
22/// `sin`). Labels without a backslash are returned unchanged.
23pub fn command_name(label: &str) -> String {
24 label.strip_prefix('\\').unwrap_or(label).to_string()
25}
26
27/// The KaTeX font-size multiplier table (`\tiny` .. `\Huge`, including
28/// `\sixptsize`), indexed by the 1-based `Sizing.size` field. Sizes outside
29/// the table fall back to 1.0 (`\normalsize`).
30pub fn katex_size_multiplier(size: usize) -> f64 {
31 const SIZES: [f64; 11] = [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.44, 1.728, 2.074, 2.488];
32 size.checked_sub(1)
33 .and_then(|index| SIZES.get(index))
34 .copied()
35 .unwrap_or(1.0)
36}
37
38/// Converts a measurement to an em length: `em` passes through, `mu` uses
39/// the 18 mu per em ratio, and `ex` the standard 0.5 x-height ratio. Returns
40/// `None` for units with no fixed em relation (`fill`, `pt`, ...).
41pub fn em_value(measurement: &Measurement) -> Option<f64> {
42 match measurement.unit.as_str() {
43 "em" => Some(measurement.number),
44 "mu" => Some(measurement.number / 18.0),
45 "ex" => Some(measurement.number * 0.5),
46 _ => None,
47 }
48}
49
50/// Selects the body of a `\mathchoice` node for the current style level.
51pub fn math_choice_variant(
52 display: &[ParseNode],
53 text: &[ParseNode],
54 script: &[ParseNode],
55 scriptscript: &[ParseNode],
56 style: StyleLevel,
57) -> Vec<ParseNode> {
58 match style {
59 StyleLevel::DisplayStyle => display.to_vec(),
60 StyleLevel::TextStyle => text.to_vec(),
61 StyleLevel::ScriptStyle => script.to_vec(),
62 StyleLevel::ScriptScriptStyle => scriptscript.to_vec(),
63 }
64}