use rustyfi_backend::{FontKey, FontMetrics, HorzBox, Length, PureHorzBox};
use rustyfi_lang::value::Value;
use rustyfi_lang::{elaborate, eval, primitives, typecheck, CompileError};
struct Mono;
impl FontMetrics for Mono {
fn advance(&self, _f: FontKey, _c: char, size: Length) -> Option<Length> {
Some(size * 0.5)
}
fn ascender(&self, _f: FontKey, size: Length) -> Length {
size * 0.75
}
fn descender(&self, _f: FontKey, size: Length) -> Length {
size * 0.25
}
}
const FS: f64 = 12.0;
const SCRIPT_FS: f64 = FS * 0.7;
const GLYPH: f64 = FS * 0.5;
const SCRIPT_GLYPH: f64 = SCRIPT_FS * 0.5;
#[track_caller]
fn assert_pt(actual: Length, expected_pt: f64, what: &str) {
assert!(
(actual.0 - expected_pt).abs() < 1e-9,
"{what}: expected {expected_pt} pt, got {} pt",
actual.0
);
}
fn width_of(body: &str) -> Length {
let src = format!(
"let-inline ctx \\dummy m = inline-nil\n\
in\n\
let ctx = get-initial-context 200pt (command \\dummy) in\n\
{body}"
);
let v = run(&src).unwrap_or_else(|e| panic!("{body} should compile and evaluate: {e}"));
match v {
Value::InlineBoxes(boxes) => {
assert_eq!(boxes.len(), 1, "expected exactly one box, got {boxes:?}");
match boxes.into_iter().next().unwrap() {
HorzBox::Pure(PureHorzBox::Math { width, .. }) => width,
other => panic!("expected a PureHorzBox::Math, got {other:?}"),
}
}
other => panic!("expected inline-boxes, got {other:?}"),
}
}
fn run(src: &str) -> Result<Value, CompileError> {
let file = rustyfi_syntax::parse_file(src)?;
let env = primitives::base_env();
let store = rustyfi_lang::symbol::SymbolStore::new();
let scope = elaborate::Scope::new(&store, env.names());
let program = elaborate::elaborate_program(&file, &scope)?;
typecheck::typecheck(&program)?;
let mut interp = eval::Interp::new(&Mono);
Ok(interp.eval(&env, &rustyfi_lang::ast::debrand(&program.body, &store))?)
}
#[test]
fn punct_spaces_after_itself_and_not_before() {
assert_pt(
width_of("embed-math ctx ${a,b}"),
3.0 * GLYPH + FS * 0.125,
"3 glyphs + ONE punct space, after the comma only",
);
}
#[test]
fn operator_takes_op_space_on_both_sides() {
assert_pt(
width_of(
"embed-math ctx (math-concat (math-char MathOrd `a`) \
(math-concat (math-char MathOp `f`) (math-char MathOrd `b`)))",
),
3.0 * GLYPH + 2.0 * FS * 0.125,
"3 glyphs + 2 op spaces",
);
}
#[test]
fn inner_takes_inner_space_on_both_sides() {
assert_pt(
width_of(
"embed-math ctx (math-concat (math-char MathOrd `a`) \
(math-concat (math-group MathInner MathInner (math-char MathOrd `i`)) \
(math-char MathOrd `b`)))",
),
3.0 * GLYPH + 2.0 * FS * 0.125,
"3 glyphs + 2 inner spaces",
);
}
#[test]
fn close_takes_no_space_even_after_a_relation() {
assert_pt(
width_of(
"embed-math ctx (math-concat (math-char MathRel `=`) \
(math-group MathClose MathClose (math-char MathOrd `x`)))",
),
2.0 * GLYPH,
"a closing delimiter is reached before the relation rows: no space",
);
}
#[test]
fn punct_outranks_close_because_its_arm_comes_first() {
assert_pt(
width_of(
"embed-math ctx (math-concat (math-char MathPunct `,`) \
(math-group MathClose MathClose (math-char MathOrd `x`)))",
),
2.0 * GLYPH + FS * 0.125,
"2 glyphs + one punct space, NOT the `(_, Close)` arm's nothing",
);
}
#[test]
fn open_after_a_relation_still_takes_relation_space() {
assert_pt(
width_of(
"embed-math ctx (math-concat (math-char MathRel `=`) \
(math-group MathOpen MathOpen (math-char MathOrd `x`)))",
),
2.0 * GLYPH + FS * 0.375,
"2 glyphs + one relation space",
);
}
#[test]
fn script_level_suppresses_binary_spacing() {
assert_pt(
width_of("embed-math ctx ${a^{b+c}}"),
GLYPH + 3.0 * SCRIPT_GLYPH,
"a script's own atoms set flush: no binary space inside a script",
);
}
#[test]
fn script_level_keeps_op_spacing_at_the_script_size() {
assert_pt(
width_of(
"embed-math ctx (math-sup (math-char MathOrd `a`) \
(math-concat (math-char MathOrd `b`) \
(math-concat (math-char MathOp `f`) (math-char MathOrd `c`))))",
),
GLYPH + 3.0 * SCRIPT_GLYPH + 2.0 * SCRIPT_FS * 0.125,
"the base + 3 script glyphs + 2 op spaces AT THE SCRIPT SIZE",
);
}
#[test]
fn the_same_list_at_base_level_scales_its_op_spacing_by_the_base_size() {
assert_pt(
width_of(
"embed-math ctx (math-concat (math-char MathOrd `b`) \
(math-concat (math-char MathOp `f`) (math-char MathOrd `c`)))",
),
3.0 * GLYPH + 2.0 * FS * 0.125,
"3 glyphs + 2 op spaces, sized by the BASE font",
);
}