mod common;
use common::*;
use zenith_core::default_provider;
use zenith_scene::ir::SceneCommand;
fn glyph_rows(cmds: &[SceneCommand]) -> Vec<(f64, f32, usize)> {
cmds.iter()
.filter_map(|c| match c {
SceneCommand::DrawGlyphRun {
y,
font_size,
glyphs,
..
} => Some((*y, *font_size, glyphs.len())),
_ => None,
})
.collect()
}
#[test]
fn chained_markdown_flows_blocks_heading_then_paragraphs() {
let src = r##"zenith version=1 {
project id="proj.cmd" name="CMD"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.h1" type="dimension" value=(px)48
}
styles {}
document id="doc.cmd" title="CMD" {
page id="page.cmd" w=(px)800 h=(px)1600 {
text id="mbox1" x=(px)20 y=(px)0 w=(px)400 h=(px)160 chain="art" format="markdown" fill=(token)"color.ink" font-family=(token)"font.body" font-size=(px)16 {
block role="h1" font-size=(token)"size.h1"
span "# Big Heading\n\nFirst paragraph alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november.\n\nSecond paragraph oscar papa quebec romeo sierra tango uniform victor whiskey xray yankee zulu one two three.\n\nThird paragraph four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen."
}
text id="mbox2" x=(px)20 y=(px)1000 w=(px)400 h=(px)560 chain="art" format="markdown" fill=(token)"color.ink" font-family=(token)"font.body" font-size=(px)16 {
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
let cmds = &result.scene.commands;
assert!(
result
.diagnostics
.iter()
.all(|d| d.severity != zenith_core::Severity::Error),
"expected no errors; got: {:?}",
result.diagnostics
);
let box1 = glyph_rows(cmds)
.into_iter()
.filter(|(y, _, _)| *y >= 0.0 && *y < 500.0)
.collect::<Vec<_>>();
let box2 = glyph_rows(cmds)
.into_iter()
.filter(|(y, _, _)| *y >= 900.0 && *y < 1600.0)
.collect::<Vec<_>>();
assert!(
!box1.is_empty(),
"box1 must draw the heading + opening text"
);
assert!(!box2.is_empty(), "box2 must receive the continuation");
let first_font_size = box1[0].1;
assert!(
(first_font_size - 48.0).abs() < 0.5,
"the heading must render at the h1 font-size 48; got {first_font_size}"
);
let has_body = box1
.iter()
.chain(box2.iter())
.any(|(_, fs, _)| (*fs - 16.0).abs() < 0.5);
assert!(
has_body,
"body paragraphs must render at the 16px body size"
);
let heading_runs = box1
.iter()
.filter(|(_, fs, _)| (*fs - 48.0).abs() < 0.5)
.count();
assert!(
heading_runs >= 1,
"expected at least one heading-sized run; got {heading_runs}"
);
let result2 = compile(&doc, &default_provider());
assert_eq!(
result.scene.commands, result2.scene.commands,
"chained markdown compile must be deterministic"
);
}
#[test]
fn non_markdown_chain_byte_identical() {
let src = r##"zenith version=1 {
project id="proj.ncm" name="NCM"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)24
}
styles {}
document id="doc.ncm" title="NCM" {
page id="page.ncm" w=(px)600 h=(px)1400 {
text id="nbox1" x=(px)10 y=(px)0 w=(px)300 h=(px)80 chain="plain" fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" {
span "Alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa quebec romeo sierra tango uniform victor whiskey"
}
text id="nbox2" x=(px)10 y=(px)1000 w=(px)300 h=(px)380 chain="plain" fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" {
}
}
}
}"##;
let doc = parse(src);
let r1 = compile(&doc, &default_provider());
let runs = glyph_rows(&r1.scene.commands);
assert!(!runs.is_empty(), "the plain chain must draw text");
assert!(
runs.iter().all(|(_, fs, _)| (*fs - 24.0).abs() < 0.5),
"a non-markdown chain must keep the uniform 24px size; got {:?}",
runs.iter().map(|r| r.1).collect::<Vec<_>>()
);
let box1 = glyph_runs_in_y(&r1.scene.commands, 0.0, 500.0);
let box2 = glyph_runs_in_y(&r1.scene.commands, 1000.0, 1400.0);
assert!(box1 > 0, "box1 must draw; got {box1}");
assert!(box2 > box1, "box2 must carry the continuation; got {box2}");
let r2 = compile(&doc, &default_provider());
assert_eq!(
r1.scene.commands, r2.scene.commands,
"non-markdown chain must compile deterministically + unchanged"
);
}
fn min_glyph_x(cmds: &[SceneCommand], lo: f64, hi: f64) -> Option<f64> {
cmds.iter()
.filter_map(|c| match c {
SceneCommand::DrawGlyphRun { x, y, .. } if *y >= lo && *y < hi => Some(*x),
_ => None,
})
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
}
fn has_fill_rgb(cmds: &[SceneCommand], lo: f64, hi: f64, rgb: (u8, u8, u8)) -> bool {
cmds.iter().any(|c| match c {
SceneCommand::FillRect {
y,
paint: zenith_scene::ir::Paint::Solid { color },
..
} => *y >= lo && *y < hi && (color.r, color.g, color.b) == rgb,
_ => false,
})
}
#[test]
fn chained_markdown_indent_codebg_and_hr_flow() {
let src = r##"zenith version=1 {
project id="proj.cmi" name="CMI"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
}
styles {}
document id="doc.cmi" title="CMI" {
page id="page.cmi" w=(px)800 h=(px)2000 {
text id="cibox1" x=(px)20 y=(px)0 w=(px)400 h=(px)120 chain="ci" format="markdown" fill=(token)"color.ink" font-family=(token)"font.body" font-size=(px)16 {
span "Plain paragraph alpha bravo charlie delta echo foxtrot golf hotel.\n\n> Quoted blockquote line india juliet kilo lima mike november oscar.\n\n- List item papa quebec romeo sierra tango uniform victor whiskey.\n\n```\ncode line one\ncode line two\n```\n\n---\n\nClosing paragraph after the rule xray yankee zulu."
}
text id="cibox2" x=(px)20 y=(px)1000 w=(px)400 h=(px)800 chain="ci" format="markdown" fill=(token)"color.ink" font-family=(token)"font.body" font-size=(px)16 {
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
let cmds = &result.scene.commands;
assert!(
result
.diagnostics
.iter()
.all(|d| d.severity != zenith_core::Severity::Error),
"expected no errors; got: {:?}",
result.diagnostics
);
let all_min = min_glyph_x(cmds, 0.0, 2000.0).expect("flow must draw glyphs");
let indented_exists = cmds.iter().any(|c| match c {
SceneCommand::DrawGlyphRun { x, .. } => *x >= all_min + 20.0,
_ => false,
});
assert!(
indented_exists,
"blockquote/list lines must be shifted right by the indent; min x = {all_min}"
);
assert!(
has_fill_rgb(cmds, 0.0, 2000.0, (245, 245, 245)),
"a code-block background FillRect (#F5F5F5) must be drawn in the chain flow"
);
assert!(
has_fill_rgb(cmds, 0.0, 2000.0, (204, 204, 204)),
"an hr rule FillRect (#CCCCCC) must be drawn in the chain flow"
);
let result2 = compile(&doc, &default_provider());
assert_eq!(
result.scene.commands, result2.scene.commands,
"chained markdown with indent/code/hr must be deterministic"
);
}