mod common;
use common::*;
#[test]
fn pixel_correctness_solid_red() {
let scene = make_solid_red_scene(4.0);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize must succeed");
assert_eq!(img.width, 4);
assert_eq!(img.height, 4);
assert_eq!(pixel(&img.rgba, img.width, 2, 2), (255, 0, 0, 255));
assert_eq!(pixel(&img.rgba, img.width, 0, 0), (255, 0, 0, 255));
}
#[test]
fn determinism_identical_png_bytes() {
let scene = make_solid_red_scene(4.0);
let backend = TinySkiaBackend;
let provider = default_provider();
let png1 = backend
.rasterize(&scene, &provider, &no_assets())
.and_then(|img| backend.encode_png(&img))
.expect("first render");
let png2 = backend
.rasterize(&scene, &provider, &no_assets())
.and_then(|img| backend.encode_png(&img))
.expect("second render");
assert_eq!(
png1, png2,
"PNG output must be byte-identical for the same scene"
);
}
#[test]
fn png_magic_bytes() {
let scene = make_solid_red_scene(4.0);
let backend = TinySkiaBackend;
let provider = default_provider();
let png = backend
.rasterize(&scene, &provider, &no_assets())
.and_then(|img| backend.encode_png(&img))
.expect("render");
assert_eq!(
&png[..8],
&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
"output must start with PNG magic bytes"
);
}
#[test]
fn clip_clamps_fill_to_page() {
let mut scene = Scene::new(4.0, 4.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 4.0,
h: 4.0,
});
scene.commands.push(SceneCommand::FillRect {
x: 2.0,
y: 2.0,
w: 10.0,
h: 10.0,
paint: Paint::solid(red()),
});
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&scene, &provider, &no_assets())
.expect("must not panic or error");
assert_eq!(img.width, 4);
assert_eq!(img.height, 4);
assert_eq!(pixel(&img.rgba, img.width, 3, 3), (255, 0, 0, 255));
assert_eq!(pixel(&img.rgba, img.width, 0, 0), (0, 0, 0, 0));
}
#[test]
fn transparent_default_no_fill() {
let mut scene = Scene::new(4.0, 4.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 4.0,
h: 4.0,
});
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&scene, &provider, &no_assets())
.expect("must succeed");
for i in 0..(img.width * img.height) {
let base = (i * 4) as usize;
assert_eq!(
&img.rgba[base..base + 4],
&[0, 0, 0, 0],
"pixel {i} must be transparent"
);
}
}
#[test]
fn invalid_zero_size_returns_error() {
let scene = Scene::new(0.0, 0.0);
let backend = TinySkiaBackend;
let provider = default_provider();
assert!(
backend.rasterize(&scene, &provider, &no_assets()).is_err(),
"zero-size scene must return RenderError"
);
}
#[test]
fn fill_polygon_renders() {
let color = Color::srgb(0, 200, 0, 255);
let mut scene = Scene::new(100.0, 100.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 100.0,
h: 100.0,
});
scene.commands.push(SceneCommand::FillPolygon {
points: vec![50.0, 10.0, 90.0, 90.0, 10.0, 90.0],
paint: Paint::solid(color),
even_odd: false,
});
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img1 = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize 1");
let any_ink = (0..img1.height).any(|py| {
(0..img1.width).any(|px| {
let (_, g, _, a) = pixel(&img1.rgba, img1.width, px, py);
a > 0 && g > 0
})
});
assert!(
any_ink,
"FillPolygon must rasterize at least one green pixel"
);
let img2 = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize 2");
assert_eq!(
img1.rgba, img2.rgba,
"two rasterizes of FillPolygon must be byte-identical"
);
}
#[test]
fn stroke_polyline_renders() {
let color = Color::srgb(255, 0, 0, 255);
let mut scene = Scene::new(100.0, 100.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 100.0,
h: 100.0,
});
scene.commands.push(SceneCommand::StrokePolyline {
points: vec![10.0, 50.0, 50.0, 10.0, 90.0, 50.0],
color,
stroke_width: 4.0,
closed: false,
align: StrokeAlign::Center,
fill_even_odd: false,
});
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img1 = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize 1");
let any_ink = (0..img1.height).any(|py| {
(0..img1.width).any(|px| {
let (_, _, _, a) = pixel(&img1.rgba, img1.width, px, py);
a > 0
})
});
assert!(
any_ink,
"StrokePolyline must rasterize at least one ink pixel"
);
let img2 = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize 2");
assert_eq!(
img1.rgba, img2.rgba,
"two rasterizes of StrokePolyline must be byte-identical"
);
}
#[test]
fn stroke_ellipse_renders() {
let color = Color::srgb(255, 0, 0, 255);
let mut scene = Scene::new(100.0, 100.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 100.0,
h: 100.0,
});
scene.commands.push(SceneCommand::StrokeEllipse {
x: 20.0,
y: 30.0,
w: 60.0,
h: 40.0,
rx: None,
ry: None,
color,
stroke_width: 4.0,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img1 = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize 1");
let any_ink = (0..img1.height).any(|py| {
(0..img1.width).any(|px| {
let (_, _, _, a) = pixel(&img1.rgba, img1.width, px, py);
a > 0
})
});
assert!(
any_ink,
"StrokeEllipse must rasterize at least one ink pixel"
);
let img2 = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize 2");
assert_eq!(
img1.rgba, img2.rgba,
"two rasterizes of StrokeEllipse must be byte-identical"
);
}
#[test]
fn ellipse_partial_clip_truncates_not_reshapes() {
let mut scene = Scene::new(20.0, 20.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 20.0,
h: 20.0,
});
scene.commands.push(SceneCommand::PushClip {
x: 10.0,
y: 10.0,
w: 10.0,
h: 10.0,
});
scene.commands.push(SceneCommand::FillEllipse {
x: 0.0,
y: 0.0,
w: 20.0,
h: 20.0,
rx: None,
ry: None,
paint: Paint::solid(Color::srgb(255, 255, 255, 255)),
});
scene.commands.push(SceneCommand::PopClip);
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize must succeed");
let (_, _, _, a_outside) = pixel(&img.rgba, img.width, 18, 18);
assert_eq!(
a_outside, 0,
"pixel (18,18) is outside the true circle; must be transparent (truncate, not reshape)"
);
let (_, _, _, a_inside) = pixel(&img.rgba, img.width, 12, 12);
assert!(
a_inside > 0,
"pixel (12,12) is inside both the clip and the circle; must be filled"
);
}
#[test]
fn stroke_line_clipped_to_subpage_clip() {
let mut scene = Scene::new(20.0, 20.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 20.0,
h: 20.0,
});
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 5.0,
h: 5.0,
});
scene.commands.push(SceneCommand::StrokeLine {
x1: 0.0,
y1: 0.0,
x2: 20.0,
y2: 20.0,
color: Color::srgb(0, 0, 0, 255),
stroke_width: 4.0,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
scene.commands.push(SceneCommand::PopClip);
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize must succeed");
let (_, _, _, a_outside) = pixel(&img.rgba, img.width, 15, 15);
assert_eq!(
a_outside, 0,
"pixel (15,15) is outside the sub-page clip; the stroked line must be truncated there"
);
let (_, _, _, a_inside) = pixel(&img.rgba, img.width, 2, 2);
assert!(
a_inside > 0,
"pixel (2,2) is on the line inside the clip; must be inked"
);
}
#[test]
fn stroke_rect_draws_border_pixels() {
let mut scene = Scene::new(40.0, 40.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 40.0,
h: 40.0,
});
scene.commands.push(SceneCommand::StrokeRect {
x: 10.0,
y: 10.0,
w: 20.0,
h: 20.0,
color: Color::srgb(0, 0, 0, 255),
stroke_width: 4.0,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize must succeed");
let (_, _, _, a_border) = pixel(&img.rgba, img.width, 20, 10);
assert!(a_border > 0, "top border pixel (20,10) must be inked");
let (_, _, _, a_center) = pixel(&img.rgba, img.width, 20, 20);
assert_eq!(a_center, 0, "stroke-only interior (20,20) must be empty");
}
#[test]
fn fill_rounded_rect_cuts_corner_fills_center() {
let mut scene = Scene::new(40.0, 40.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 40.0,
h: 40.0,
});
scene.commands.push(SceneCommand::FillRoundedRect {
x: 0.0,
y: 0.0,
w: 40.0,
h: 40.0,
radius: 20.0,
radii: None,
paint: Paint::solid(Color::srgb(255, 255, 255, 255)),
});
scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&scene, &provider, &no_assets())
.expect("rasterize must succeed");
let (_, _, _, a_corner) = pixel(&img.rgba, img.width, 0, 0);
assert_eq!(
a_corner, 0,
"corner pixel (0,0) must be cut by the radius (transparent)"
);
let (_, _, _, a_center) = pixel(&img.rgba, img.width, 20, 20);
assert!(a_center > 0, "center pixel (20,20) must be filled");
}
#[test]
fn rounded_and_stroke_rects_deterministic_png() {
let mut scene = Scene::new(80.0, 80.0);
scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 80.0,
h: 80.0,
});
scene.commands.push(SceneCommand::StrokeRect {
x: 5.0,
y: 5.0,
w: 30.0,
h: 30.0,
color: Color::srgb(200, 0, 0, 255),
stroke_width: 3.0,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
scene.commands.push(SceneCommand::FillRoundedRect {
x: 40.0,
y: 5.0,
w: 30.0,
h: 30.0,
radius: 10.0,
radii: None,
paint: Paint::solid(Color::srgb(0, 200, 0, 255)),
});
scene.commands.push(SceneCommand::StrokeRoundedRect {
x: 20.0,
y: 40.0,
w: 40.0,
h: 30.0,
radius: 8.0,
radii: None,
color: Color::srgb(0, 0, 200, 255),
stroke_width: 3.0,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
scene.commands.push(SceneCommand::PopClip);
let provider = default_provider();
let png1 = render_png(&scene, &provider, &no_assets()).expect("first render");
let png2 = render_png(&scene, &provider, &no_assets()).expect("second render");
assert_eq!(
png1, png2,
"StrokeRect + FillRoundedRect + StrokeRoundedRect scene must render byte-identically"
);
}
#[test]
fn dashed_stroke_rect_renders_without_panic() {
let color = Color::srgb(255, 0, 0, 255);
let mut dashed_scene = Scene::new(60.0, 60.0);
dashed_scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 60.0,
h: 60.0,
});
dashed_scene.commands.push(SceneCommand::StrokeRect {
x: 5.0,
y: 5.0,
w: 50.0,
h: 50.0,
color,
stroke_width: 3.0,
stroke_dash: Some(8.0),
stroke_gap: Some(4.0),
stroke_linecap: Some(zenith_scene::ir::LineCap::Round),
});
dashed_scene.commands.push(SceneCommand::PopClip);
let backend = TinySkiaBackend;
let provider = default_provider();
let img = backend
.rasterize(&dashed_scene, &provider, &no_assets())
.expect("dashed StrokeRect must rasterize without panic");
let any_ink = (0..img.height).any(|py| {
(0..img.width).any(|px| {
let (_, _, _, a) = pixel(&img.rgba, img.width, px, py);
a > 0
})
});
assert!(any_ink, "dashed StrokeRect must ink at least one pixel");
let mut solid_scene = Scene::new(60.0, 60.0);
solid_scene.commands.push(SceneCommand::PushClip {
x: 0.0,
y: 0.0,
w: 60.0,
h: 60.0,
});
solid_scene.commands.push(SceneCommand::StrokeRect {
x: 5.0,
y: 5.0,
w: 50.0,
h: 50.0,
color,
stroke_width: 3.0,
stroke_dash: None,
stroke_gap: None,
stroke_linecap: None,
});
solid_scene.commands.push(SceneCommand::PopClip);
let solid_img = backend
.rasterize(&solid_scene, &provider, &no_assets())
.expect("solid StrokeRect must rasterize");
assert_ne!(
img.rgba, solid_img.rgba,
"dashed and solid strokes must produce different pixel output"
);
}