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_whitespace_normal_collapses_spaces() {
let html = r#"
<html><head><style>
p { white-space: normal; margin: 0; padding: 0; }
</style></head>
<body><p>Hello World</p></body></html>
"#;
let cache = run_layout(html);
assert!(
!cache.calculated_positions.is_empty(),
"Layout must produce positions"
);
}
#[test]
fn test_whitespace_normal_collapses_newlines_to_spaces() {
let html = r#"
<html><head><style>
p { white-space: normal; margin: 0; padding: 0; }
</style></head>
<body><p>Hello
World</p></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_normal_collapses_tabs() {
let html = "<html><head><style>\
p { white-space: normal; margin: 0; padding: 0; }\
</style></head>\
<body><p>Hello\tWorld</p></body></html>";
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_normal_preserves_inter_element_space() {
let html = r#"
<html><head><style>
span { white-space: normal; }
p { margin: 0; padding: 0; }
</style></head>
<body><p><span>Hello</span> <span>World</span></p></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_pre_preserves_spaces() {
let html = r#"
<html><head><style>
pre { white-space: pre; margin: 0; padding: 0; }
</style></head>
<body><pre>Hello World</pre></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_pre_honors_newlines() {
let html = "<html><head><style>\
pre { white-space: pre; margin: 0; padding: 0; }\
</style></head>\
<body><pre>Line1\nLine2\nLine3</pre></body></html>";
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_pre_preserves_tabs() {
let html = "<html><head><style>\
pre { white-space: pre; margin: 0; padding: 0; }\
</style></head>\
<body><pre>Col1\tCol2\tCol3</pre></body></html>";
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_pre_wrap_preserves_spaces_but_allows_wrapping() {
let html = r#"
<html><head><style>
p { white-space: pre-wrap; margin: 0; padding: 0; width: 200px; }
</style></head>
<body><p>Hello World This is a long text with preserved spaces</p></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_pre_wrap_honors_newlines() {
let html = "<html><head><style>\
p { white-space: pre-wrap; margin: 0; padding: 0; }\
</style></head>\
<body><p>Line1\nLine2</p></body></html>";
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_pre_line_collapses_spaces_but_honors_newlines() {
let html = "<html><head><style>\
p { white-space: pre-line; margin: 0; padding: 0; }\
</style></head>\
<body><p>Hello World\nNext Line</p></body></html>";
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_pre_line_multiple_newlines() {
let html = "<html><head><style>\
p { white-space: pre-line; margin: 0; padding: 0; }\
</style></head>\
<body><p>Line1\n\nLine3</p></body></html>";
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_nowrap_collapses_like_normal() {
let html = r#"
<html><head><style>
p { white-space: nowrap; margin: 0; padding: 0; width: 100px; }
</style></head>
<body><p>Hello World this is a very long line that should not wrap</p></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_inherited_from_parent() {
let html = r#"
<html><head><style>
div { white-space: pre; margin: 0; padding: 0; }
span { /* inherits pre from div */ }
</style></head>
<body><div><span>Hello World</span></div></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_override_in_child() {
let html = r#"
<html><head><style>
div { white-space: pre; margin: 0; padding: 0; }
p { white-space: normal; margin: 0; padding: 0; }
</style></head>
<body>
<div>
<p>Hello World</p>
</div>
</body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_empty_text_normal() {
let html = r#"
<html><head><style>
p { margin: 0; padding: 0; }
</style></head>
<body><p></p></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_only_spaces_normal() {
let html = r#"
<html><head><style>
p { margin: 0; padding: 0; }
</style></head>
<body><p> </p></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_only_newlines_normal() {
let html = "<html><head><style>\
p { margin: 0; padding: 0; }\
</style></head>\
<body><p>\n\n\n</p></body></html>";
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}
#[test]
fn test_whitespace_mixed_content_with_br() {
let html = r#"
<html><head><style>
p { white-space: normal; margin: 0; padding: 0; }
</style></head>
<body><p>Hello<br/>World</p></body></html>
"#;
let cache = run_layout(html);
assert!(!cache.calculated_positions.is_empty());
}