use azul_core::{
dom::{Dom, DomId, IdOrClass, NodeType},
geom::LogicalSize,
resources::RendererResources,
styled_dom::StyledDom,
};
use azul_css::css::Css;
use azul_layout::{
callbacks::ExternalSystemCallbacks,
cpurender::{self, AzulPixmap, RenderOptions},
glyph_cache::GlyphCache,
window::LayoutWindow,
window_state::FullWindowState,
};
use rust_fontconfig::FcFontCache;
struct RenderHarness {
font_cache: FcFontCache,
glyph_cache: GlyphCache,
previous_pixmap: Option<AzulPixmap>,
}
impl RenderHarness {
fn new() -> Self {
Self {
font_cache: FcFontCache::build(),
glyph_cache: GlyphCache::new(),
previous_pixmap: None,
}
}
fn render_frame(&mut self, dom: Dom, css_str: &str, w: f32, h: f32) -> AzulPixmap {
let css = if css_str.is_empty() {
Css::empty()
} else {
Css::from_string(css_str.into())
};
let mut dom = dom;
let styled_dom = StyledDom::create(&mut dom, css);
let mut lw = LayoutWindow::new(self.font_cache.clone()).unwrap();
let mut ws = FullWindowState::default();
ws.size.dimensions = LogicalSize::new(w, h);
let rr = RendererResources::default();
let sc = ExternalSystemCallbacks::rust_internal();
let mut dbg = Some(Vec::new());
lw.layout_and_generate_display_list(styled_dom, &ws, &rr, &sc, &mut dbg)
.unwrap();
let dom_id = DomId { inner: 0 };
let dl = &lw.layout_results.get(&dom_id).unwrap().display_list;
let opts = RenderOptions { width: w, height: h, dpi_factor: 1.0 };
let pixmap = cpurender::render_with_font_manager(
dl, &rr, &lw.font_manager, opts, &mut self.glyph_cache,
).unwrap();
self.previous_pixmap = Some(pixmap.clone_pixmap());
pixmap
}
}
fn cls(name: &str) -> Vec<IdOrClass> {
vec![IdOrClass::Class(name.into())]
}
fn pixel_diff_count(a: &AzulPixmap, b: &AzulPixmap, threshold: u8) -> usize {
assert_eq!(a.width(), b.width());
assert_eq!(a.height(), b.height());
let ad = a.data();
let bd = b.data();
let mut count = 0;
for i in (0..ad.len()).step_by(4) {
let dr = (ad[i] as i16 - bd[i] as i16).unsigned_abs() as u8;
let dg = (ad[i+1] as i16 - bd[i+1] as i16).unsigned_abs() as u8;
let db = (ad[i+2] as i16 - bd[i+2] as i16).unsigned_abs() as u8;
if dr > threshold || dg > threshold || db > threshold {
count += 1;
}
}
count
}
#[test]
fn identical_frames_produce_identical_pixmaps() {
let mut h = RenderHarness::new();
let css = r#"
* { margin: 0; padding: 0; }
body { width: 200px; height: 200px; }
.box { width: 100px; height: 100px; background-color: #ff0000; }
"#;
let dom1 = Dom::create_body().with_child(
Dom::create_div().with_ids_and_classes(cls("box").into()),
);
let dom2 = Dom::create_body().with_child(
Dom::create_div().with_ids_and_classes(cls("box").into()),
);
let frame1 = h.render_frame(dom1, css, 200.0, 200.0);
let frame2 = h.render_frame(dom2, css, 200.0, 200.0);
let diff = pixel_diff_count(&frame1, &frame2, 0);
assert_eq!(
diff, 0,
"Identical DOMs should produce identical pixmaps, but {} pixels differ",
diff
);
}
#[test]
fn text_change_only_affects_text_region() {
let mut h = RenderHarness::new();
let css = r#"
* { margin: 0; padding: 0; }
body { width: 200px; height: 200px; background: #ffffff; font-family: sans-serif; font-size: 14px; }
.box { width: 200px; height: 200px; }
"#;
let dom1 = Dom::create_body().with_child(
Dom::create_div()
.with_ids_and_classes(cls("box").into())
.with_child(Dom::create_text("Hello")),
);
let dom2 = Dom::create_body().with_child(
Dom::create_div()
.with_ids_and_classes(cls("box").into())
.with_child(Dom::create_text("World")),
);
let frame1 = h.render_frame(dom1, css, 200.0, 200.0);
let frame2 = h.render_frame(dom2, css, 200.0, 200.0);
let total_pixels = (frame1.width() * frame1.height()) as usize;
let diff = pixel_diff_count(&frame1, &frame2, 0);
assert!(
diff > 0,
"Text changed, some pixels should differ"
);
assert!(
diff < total_pixels / 5,
"Only text region should differ (<20% of pixels), but {}% changed",
diff * 100 / total_pixels
);
}
#[test]
fn different_text_content_produces_different_output() {
let mut h = RenderHarness::new();
let css = r#"
* { margin: 0; padding: 0; }
body { width: 200px; height: 100px; font-family: sans-serif; font-size: 16px; }
"#;
let dom1 = Dom::create_body().with_child(Dom::create_text("AAAA"));
let dom2 = Dom::create_body().with_child(Dom::create_text("ZZZZ"));
let frame1 = h.render_frame(dom1, css, 200.0, 100.0);
let frame2 = h.render_frame(dom2, css, 200.0, 100.0);
let diff = pixel_diff_count(&frame1, &frame2, 0);
assert!(
diff > 0,
"Different text content should produce different pixels"
);
}
#[test]
fn resize_preserves_top_left_content() {
let mut h = RenderHarness::new();
let css = r#"
* { margin: 0; padding: 0; }
body { width: 200px; height: 200px; background: #ff0000; }
"#;
let dom = Dom::create_body();
let frame_small = h.render_frame(dom, css, 200.0, 200.0);
let css_large = r#"
* { margin: 0; padding: 0; }
body { width: 300px; height: 300px; background: #ff0000; }
"#;
let dom_large = Dom::create_body();
let frame_large = h.render_frame(dom_large, css_large, 300.0, 300.0);
let overlap_diff = cpurender::compare_region(&frame_small, &frame_large, 0, 0, 200, 200, 2);
assert!(
overlap_diff < 100,
"Top-left 200x200 should be nearly identical (both solid red), but {} pixels differ",
overlap_diff
);
}