mod common;
use common::{SceneCommand, compile, default_provider, parse};
fn auto_cell_src(cell_attrs: &str, text: &str) -> String {
format!(
r##"zenith version=1 {{
project id="proj.ac" name="AC"
tokens format="zenith-token-v1" {{
token id="color.ink" type="color" value="#000000"
}}
styles {{}}
document id="doc.ac" title="AC" {{
page id="page.ac" w=(px)640 h=(px)400 {{
table id="t.ac" x=(px)40 y=(px)40 w=(px)400 h=(px)200 cell-padding=(px)0 gap=(px)0 {{
column width=(px)400
row {{
cell {cell_attrs} {{ text id="cx" fill=(token)"color.ink" {{ span "{text}" }} }}
}}
}}
}}
}}
}}
"##
)
}
fn glyph_runs(result: &zenith_scene::CompileResult) -> Vec<(f64, f64)> {
result
.scene
.commands
.iter()
.filter_map(|c| match c {
SceneCommand::DrawGlyphRun { x, y, .. } => Some((*x, *y)),
_ => None,
})
.collect()
}
#[test]
fn cell_text_without_geometry_compiles_into_content_box() {
let result = compile(&parse(&auto_cell_src("", "Hello")), &default_provider());
let runs = glyph_runs(&result);
assert!(!runs.is_empty(), "cell text without w/h must still render");
assert!(
runs[0].0 >= 40.0 - 0.01,
"glyph run x must be inside cell content box; got {runs:?}"
);
}
#[test]
fn cell_h_align_shifts_text_horizontally() {
let start = compile(&parse(&auto_cell_src("", "Hi")), &default_provider());
let center = compile(
&parse(&auto_cell_src("h-align=\"center\"", "Hi")),
&default_provider(),
);
let end = compile(
&parse(&auto_cell_src("h-align=\"end\"", "Hi")),
&default_provider(),
);
let sx = glyph_runs(&start)[0].0;
let cx = glyph_runs(¢er)[0].0;
let ex = glyph_runs(&end)[0].0;
assert!(cx > sx, "center start ({cx}) must be right of start ({sx})");
assert!(ex > cx, "end start ({ex}) must be right of center ({cx})");
}
fn v_align_src(cell_attrs: &str) -> String {
format!(
r##"zenith version=1 {{
project id="proj.va" name="VA"
tokens format="zenith-token-v1" {{
token id="color.ink" type="color" value="#000000"
}}
styles {{}}
document id="doc.va" title="VA" {{
page id="page.va" w=(px)640 h=(px)400 {{
table id="t.va" x=(px)40 y=(px)40 w=(px)400 h=(px)200 cell-padding=(px)0 gap=(px)0 {{
column width=(px)120
column width=(px)120
row {{
cell {cell_attrs} {{ text id="short" fill=(token)"color.ink" {{ span "Hi" }} }}
cell {{ text id="tall" fill=(token)"color.ink" {{ span "L1\nL2\nL3\nL4" }} }}
}}
}}
}}
}}
}}
"##
)
}
#[test]
fn cell_v_align_shifts_text_vertically() {
let top = compile(&parse(&v_align_src("")), &default_provider());
let middle = compile(
&parse(&v_align_src("v-align=\"middle\"")),
&default_provider(),
);
let bottom = compile(
&parse(&v_align_src("v-align=\"bottom\"")),
&default_provider(),
);
let ty = glyph_runs(&top)[0].1;
let my = glyph_runs(&middle)[0].1;
let by = glyph_runs(&bottom)[0].1;
assert!(my > ty, "middle baseline ({my}) must be below top ({ty})");
assert!(
by > my,
"bottom baseline ({by}) must be below middle ({my})"
);
}
#[test]
fn cell_text_wraps_to_narrow_column() {
let src = r##"zenith version=1 {
project id="proj.wr" name="WR"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#000000"
}
styles {}
document id="doc.wr" title="WR" {
page id="page.wr" w=(px)640 h=(px)400 {
table id="t.wr" x=(px)40 y=(px)40 w=(px)80 h=(px)300 cell-padding=(px)0 gap=(px)0 {
column width=(px)80
row {
cell { text id="cw" fill=(token)"color.ink" { span "one two three four five six seven eight" } }
}
}
}
}
}
"##;
let result = compile(&parse(src), &default_provider());
let runs = glyph_runs(&result);
assert!(
runs.len() >= 2,
"long text in a narrow column must wrap to multiple lines; got {} run(s)",
runs.len()
);
assert!(
runs.windows(2).all(|w| w[1].1 >= w[0].1 - 0.01),
"wrapped lines must descend; got {runs:?}"
);
}
#[test]
fn cell_text_with_explicit_geometry_unchanged() {
let build = |cell_attrs: &str| {
format!(
r##"zenith version=1 {{
project id="proj.ex" name="EX"
tokens format="zenith-token-v1" {{ token id="color.ink" type="color" value="#000000" }}
styles {{}}
document id="doc.ex" title="EX" {{
page id="page.ex" w=(px)640 h=(px)400 {{
table id="t.ex" x=(px)40 y=(px)40 w=(px)400 h=(px)200 cell-padding=(px)0 gap=(px)0 {{
column width=(px)400
row {{
cell {cell_attrs} {{ text id="ce" x=(px)0 y=(px)0 w=(px)400 align="start" fill=(token)"color.ink" {{ span "Fixed" }} }}
}}
}}
}}
}}
}}
"##
)
};
let start = compile(&parse(&build("")), &default_provider());
let centered = compile(&parse(&build("h-align=\"center\"")), &default_provider());
assert_eq!(
glyph_runs(&start),
glyph_runs(¢ered),
"explicit-geometry cell text must ignore cell h-align (author override wins)"
);
}
fn offset_cell_src(text_y_px: u32) -> String {
format!(
r##"zenith version=1 {{
project id="proj.off" name="OFF"
tokens format="zenith-token-v1" {{
token id="color.ink" type="color" value="#000000"
}}
styles {{}}
document id="doc.off" title="OFF" {{
page id="page.off" w=(px)640 h=(px)400 {{
table id="t.off" x=(px)40 y=(px)40 w=(px)400 h=(px)200 cell-padding=(px)0 gap=(px)0 {{
column width=(px)400
row {{
cell {{ text id="cx" x=(px)0 y=(px){text_y_px} fill=(token)"color.ink" {{ span "Offset" }} }}
}}
}}
}}
}}
}}
"##
)
}
#[test]
fn cell_text_y_offset_grows_row_so_clip_contains_text() {
let y_off = 40.0_f64;
let result = compile(&parse(&offset_cell_src(40)), &default_provider());
let max_clip_h = result
.scene
.commands
.iter()
.filter_map(|c| match c {
SceneCommand::PushClip { h, .. } => Some(*h),
_ => None,
})
.fold(0.0_f64, f64::max);
assert!(
max_clip_h >= y_off,
"cell content clip height ({max_clip_h}) must cover the text y offset \
({y_off}); a smaller clip means the offset text is cut off"
);
}