mod common;
use common::*;
use zenith_core::default_provider;
use zenith_scene::compile;
#[test]
fn runaround_none_is_byte_identical() {
let a = compile(&runaround_doc("", ""), &default_provider());
let b = compile(&runaround_doc("", ""), &default_provider());
assert_eq!(
a.scene.commands, b.scene.commands,
"a node without text-exclusion must be deterministic and unchanged"
);
let ys: std::collections::BTreeSet<u64> = glyph_run_positions(&a.scene.commands)
.iter()
.map(|(_, y)| y.to_bits())
.collect();
assert!(ys.len() > 1, "body must wrap onto multiple lines");
}
#[test]
fn runaround_left_exclusion_shifts_lines_right() {
let rect = r##"rect id="ex" x=(px)0 y=(px)0 w=(px)200 h=(px)120 fill="#000000""##;
let result = compile(
&runaround_doc(rect, r#"text-exclusion="ex""#),
&default_provider(),
);
let pos = glyph_run_positions(&result.scene.commands);
assert!(!pos.is_empty(), "text must render");
let mut saw_shifted = false;
let mut saw_returned = false;
for (x, y) in &pos {
if *y <= 120.0 {
assert!(
*x >= 200.0 - 0.5,
"a line in the exclusion band must start at/after the rect right edge (200); got x={x} y={y}"
);
saw_shifted = true;
} else if *x < 1.0 {
saw_returned = true;
}
}
assert!(saw_shifted, "at least one line must be shifted right");
assert!(
saw_returned,
"lines below the exclusion must return to text_x (0)"
);
}
#[test]
fn runaround_right_exclusion_narrows_lines() {
let rect = r##"rect id="ex" x=(px)250 y=(px)0 w=(px)150 h=(px)120 fill="#000000""##;
let narrowed = compile(
&runaround_doc(rect, r#"text-exclusion="ex""#),
&default_provider(),
);
let uniform = compile(&runaround_doc("", ""), &default_provider());
let mut min_x_by_y: std::collections::BTreeMap<u64, f64> = std::collections::BTreeMap::new();
for (x, y) in glyph_run_positions(&narrowed.scene.commands) {
let e = min_x_by_y.entry(y.to_bits()).or_insert(f64::INFINITY);
*e = e.min(x);
}
for (y_bits, min_x) in &min_x_by_y {
let y = f64::from_bits(*y_bits);
if y <= 120.0 {
assert!(
*min_x < 1.0,
"a right-exclusion line must start at text_x (0); got min_x={min_x} y={y}"
);
}
}
let n_narrow = glyph_run_positions(&narrowed.scene.commands).len();
let n_uniform = glyph_run_positions(&uniform.scene.commands).len();
assert!(
n_narrow >= n_uniform,
"narrowing the band must not reduce the line/run count ({n_narrow} vs {n_uniform})"
);
}
#[test]
fn runaround_full_width_exclusion_skips_lines() {
let rect = r##"rect id="ex" x=(px)0 y=(px)100 w=(px)400 h=(px)120 fill="#000000""##;
let result = compile(
&runaround_doc(rect, r#"text-exclusion="ex""#),
&default_provider(),
);
let pos = glyph_run_positions(&result.scene.commands);
for (_, y) in &pos {
assert!(
!(*y > 100.0 && *y < 220.0),
"no glyph run may land inside a full-width exclusion band (100..220); got y={y}"
);
}
assert!(
pos.iter().any(|(_, y)| *y <= 100.0),
"text must flow above the band"
);
assert!(
pos.iter().any(|(_, y)| *y >= 220.0),
"text must resume below the band"
);
}
#[test]
fn runaround_unresolved_ref_emits_advisory_and_renders_uniform() {
let bad = compile(
&runaround_doc("", r#"text-exclusion="nope""#),
&default_provider(),
);
let uniform = compile(&runaround_doc("", ""), &default_provider());
let advisories: Vec<_> = bad
.diagnostics
.iter()
.filter(|d| d.code == "text-exclusion.unresolved_ref")
.collect();
assert_eq!(
advisories.len(),
1,
"exactly one unresolved-ref advisory; got {:?}",
bad.diagnostics
);
assert_eq!(
bad.scene.commands, uniform.scene.commands,
"an unresolved exclusion must render the uniform stream (byte-identical)"
);
}