use azul_core::dom::{Dom, DomId};
use azul_core::geom::{LogicalPosition, LogicalRect, LogicalSize};
use azul_core::resources::RendererResources;
use azul_layout::font::loading::build_font_cache;
use azul_layout::font_traits::{FontManager, TextLayoutCache};
use azul_layout::paged::FragmentationContext;
use azul_layout::solver3::paged_layout::layout_document_paged_with_config;
use azul_layout::solver3::pagination::FakePageConfig;
use azul_layout::text3::default::PathLoader;
use azul_layout::xml::DomXmlExt;
use azul_layout::Solver3LayoutCache;
use std::collections::{BTreeMap, HashMap};
fn run_layout(html: &str) -> Solver3LayoutCache {
let styled_dom = Dom::from_xml_string(html);
let fc_cache = build_font_cache();
let mut font_manager = FontManager::new(fc_cache).expect("Failed to create FontManager");
let mut layout_cache = Solver3LayoutCache {
tree: None,
calculated_positions: Vec::new(),
viewport: None,
scroll_ids: HashMap::new(),
scroll_id_to_node_id: HashMap::new(),
counters: HashMap::new(),
float_cache: HashMap::new(),
cache_map: Default::default(),
previous_positions: Vec::new(),
cached_display_list: None,
prev_dom_ptr: 0,
prev_viewport: LogicalRect {
origin: LogicalPosition::zero(),
size: LogicalSize::zero(),
},
};
let mut text_cache = TextLayoutCache::new();
let content_size = LogicalSize::new(800.0, 600.0);
let fragmentation_context = FragmentationContext::new_paged(content_size);
let viewport = LogicalRect {
origin: LogicalPosition::zero(),
size: content_size,
};
let renderer_resources = RendererResources::default();
let mut debug_messages = Some(Vec::new());
let loader = PathLoader::new();
let font_loader = |bytes: std::sync::Arc<rust_fontconfig::FontBytes>, index: usize| {
loader.load_font_shared(bytes, index)
};
let page_config = FakePageConfig::new();
let _display_lists = layout_document_paged_with_config(
&mut layout_cache,
&mut text_cache,
fragmentation_context,
&styled_dom,
viewport,
&mut font_manager,
&BTreeMap::new(),
&mut debug_messages,
None,
&renderer_resources,
azul_core::resources::IdNamespace(0),
DomId::ROOT_ID,
font_loader,
page_config,
&azul_core::resources::ImageCache::default(), azul_core::task::GetSystemTimeCallback {
cb: azul_core::task::get_system_time_libstd,
},
false,
)
.expect("Layout should succeed");
layout_cache
}
#[test]
fn test_float_left_stays_within_container() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.float { float: left; width: 100px; height: 50px; background: red; }
</style></head>
<body><div class="container"><div class="float"></div></div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_right_stays_within_container() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.float { float: right; width: 100px; height: 50px; background: blue; }
</style></head>
<body><div class="container"><div class="float"></div></div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_successive_left_floats_stack_horizontally() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.f1 { float: left; width: 100px; height: 50px; background: red; }
.f2 { float: left; width: 100px; height: 50px; background: blue; }
</style></head>
<body><div class="container">
<div class="f1"></div>
<div class="f2"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_successive_right_floats_stack_horizontally() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.f1 { float: right; width: 100px; height: 50px; background: red; }
.f2 { float: right; width: 100px; height: 50px; background: blue; }
</style></head>
<body><div class="container">
<div class="f1"></div>
<div class="f2"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_left_and_right_floats_dont_overlap() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.fl { float: left; width: 100px; height: 50px; background: red; }
.fr { float: right; width: 100px; height: 50px; background: blue; }
</style></head>
<body><div class="container">
<div class="fl"></div>
<div class="fr"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_top_not_above_container() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; padding-top: 20px; }
.float { float: left; width: 100px; height: 50px; background: red; }
</style></head>
<body><div class="container"><div class="float"></div></div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_pushed_down_when_no_horizontal_space() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 250px; }
.f1 { float: left; width: 150px; height: 50px; background: red; }
.f2 { float: left; width: 150px; height: 50px; background: blue; }
</style></head>
<body><div class="container">
<div class="f1"></div>
<div class="f2"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_clear_left_pushes_past_left_float() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.float { float: left; width: 100px; height: 80px; background: red; }
.clear { clear: left; height: 30px; background: green; }
</style></head>
<body><div class="container">
<div class="float"></div>
<div class="clear"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_clear_right_pushes_past_right_float() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.float { float: right; width: 100px; height: 80px; background: red; }
.clear { clear: right; height: 30px; background: green; }
</style></head>
<body><div class="container">
<div class="float"></div>
<div class="clear"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_clear_both_pushes_past_all_floats() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.fl { float: left; width: 100px; height: 80px; background: red; }
.fr { float: right; width: 100px; height: 120px; background: blue; }
.clear { clear: both; height: 30px; background: green; }
</style></head>
<body><div class="container">
<div class="fl"></div>
<div class="fr"></div>
<div class="clear"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_clear_left_ignores_right_floats() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.fr { float: right; width: 100px; height: 120px; background: blue; }
.clear { clear: left; height: 30px; background: green; }
</style></head>
<body><div class="container">
<div class="fr"></div>
<div class="clear"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_overflow_hidden_creates_new_bfc_for_float_containment() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.float { float: left; width: 100px; height: 80px; background: red; }
.bfc { overflow: hidden; height: 100px; background: green; }
</style></head>
<body><div class="container">
<div class="float"></div>
<div class="bfc">Content in new BFC</div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_with_margin() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.float { float: left; width: 100px; height: 50px; margin: 10px; background: red; }
</style></head>
<body><div class="container"><div class="float"></div></div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_narrows_line_boxes() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.float { float: left; width: 100px; height: 100px; background: red; }
</style></head>
<body><div class="container">
<div class="float"></div>
<p>This text should flow around the float, wrapping to the right of it.
After the float ends, the text should return to full width.</p>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_none_is_not_floated() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.not-float { float: none; width: 100px; height: 50px; background: red; }
.next { height: 50px; background: blue; }
</style></head>
<body><div class="container">
<div class="not-float"></div>
<div class="next"></div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_multiple_floats_different_heights() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.f1 { float: left; width: 80px; height: 100px; background: red; }
.f2 { float: left; width: 80px; height: 60px; background: green; }
.f3 { float: left; width: 80px; height: 120px; background: blue; }
.f4 { float: right; width: 80px; height: 80px; background: yellow; }
</style></head>
<body><div class="container">
<div class="f1"></div>
<div class="f2"></div>
<div class="f3"></div>
<div class="f4"></div>
<p>Text flowing around multiple floats with different heights.</p>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_between_block_elements() {
let html = r#"
<html><head><style>
* { margin: 0; padding: 0; }
.container { width: 400px; }
.block { height: 30px; background: gray; }
.float { float: left; width: 100px; height: 80px; background: red; }
</style></head>
<body><div class="container">
<div class="block">Before float</div>
<div class="float"></div>
<div class="block">After float</div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_float_margin_never_collapses() {
let html = r#"
<html><head><style>
* { padding: 0; }
.container { width: 400px; margin: 0; }
.block { height: 30px; margin-bottom: 20px; background: gray; }
.float { float: left; width: 100px; height: 50px; margin-top: 20px; background: red; }
</style></head>
<body><div class="container">
<div class="block">Block with margin-bottom: 20px</div>
<div class="float">Float with margin-top: 20px</div>
</div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}