1use std::{
6 collections::{BTreeMap, BTreeSet},
7 sync::Arc,
8};
9
10use azul_core::{
11 dom::{FormattingContext, NodeId, NodeType},
12 geom::LogicalSize,
13 resources::RendererResources,
14 styled_dom::{StyledDom, StyledNodeState},
15};
16use azul_css::{
17 css::CssPropertyValue,
18 props::{
19 basic::PixelValue,
20 layout::{LayoutDisplay, LayoutHeight, LayoutPosition, LayoutWidth, LayoutWritingMode},
21 property::{CssProperty, CssPropertyType},
22 },
23 LayoutDebugMessage,
24};
25use rust_fontconfig::FcFontCache;
26
27#[cfg(feature = "text_layout")]
28use crate::text3;
29use crate::{
30 font::parsed::ParsedFont,
31 font_traits::{
32 AvailableSpace, FontLoaderTrait, FontManager, ImageSource, InlineContent, InlineImage,
33 InlineShape, LayoutCache, LayoutFragment, ObjectFit, ParsedFontTrait, ShapeDefinition,
34 StyleProperties, StyledRun, UnifiedConstraints,
35 },
36 solver3::{
37 fc::split_text_for_whitespace,
38 geometry::{BoxProps, BoxSizing, IntrinsicSizes},
39 getters::{
40 get_css_box_sizing, get_css_height, get_css_width, get_display_property,
41 get_style_properties, get_writing_mode, MultiValue,
42 },
43 layout_tree::{AnonymousBoxType, LayoutNode, LayoutTree},
44 positioning::get_position_type,
45 LayoutContext, LayoutError, Result,
46 },
47};
48
49pub fn resolve_percentage_with_box_model(
102 containing_block_dimension: f32,
103 percentage: f32,
104 _margins: (f32, f32),
105 _borders: (f32, f32),
106 _paddings: (f32, f32),
107) -> f32 {
108 (containing_block_dimension * percentage).max(0.0)
111}
112
113pub fn calculate_intrinsic_sizes<T: ParsedFontTrait>(
115 ctx: &mut LayoutContext<'_, T>,
116 tree: &mut LayoutTree,
117 dirty_nodes: &BTreeSet<usize>,
118) -> Result<()> {
119 if dirty_nodes.is_empty() {
120 return Ok(());
121 }
122
123 ctx.debug_log("Starting intrinsic size calculation");
124 let mut calculator = IntrinsicSizeCalculator::new(ctx);
125 calculator.calculate_intrinsic_recursive(tree, tree.root)?;
126 ctx.debug_log("Finished intrinsic size calculation");
127 Ok(())
128}
129
130struct IntrinsicSizeCalculator<'a, 'b, T: ParsedFontTrait> {
131 ctx: &'a mut LayoutContext<'b, T>,
132 text_cache: LayoutCache,
133}
134
135impl<'a, 'b, T: ParsedFontTrait> IntrinsicSizeCalculator<'a, 'b, T> {
136 fn new(ctx: &'a mut LayoutContext<'b, T>) -> Self {
137 Self {
138 ctx,
139 text_cache: LayoutCache::new(),
140 }
141 }
142
143 fn calculate_intrinsic_recursive(
144 &mut self,
145 tree: &mut LayoutTree,
146 node_index: usize,
147 ) -> Result<IntrinsicSizes> {
148 static COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
149 let count = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
150 if count % 50 == 0 {}
151
152 let node = tree
153 .get(node_index)
154 .cloned()
155 .ok_or(LayoutError::InvalidTree)?;
156
157 let position = get_position_type(self.ctx.styled_dom, node.dom_node_id);
159 if position == LayoutPosition::Absolute || position == LayoutPosition::Fixed {
160 if let Some(n) = tree.get_mut(node_index) {
161 n.intrinsic_sizes = Some(IntrinsicSizes::default());
162 }
163 return Ok(IntrinsicSizes::default());
164 }
165
166 let mut child_intrinsics = BTreeMap::new();
168 for &child_index in &node.children {
169 let child_intrinsic = self.calculate_intrinsic_recursive(tree, child_index)?;
170 child_intrinsics.insert(child_index, child_intrinsic);
171 }
172
173 let intrinsic = self.calculate_node_intrinsic_sizes(tree, node_index, &child_intrinsics)?;
175
176 if let Some(n) = tree.get_mut(node_index) {
177 n.intrinsic_sizes = Some(intrinsic);
178 }
179
180 Ok(intrinsic)
181 }
182
183 fn calculate_node_intrinsic_sizes(
184 &mut self,
185 tree: &LayoutTree,
186 node_index: usize,
187 child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
188 ) -> Result<IntrinsicSizes> {
189 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
190
191 if let Some(dom_id) = node.dom_node_id {
194 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
195 if node_data.is_iframe_node() {
196 return Ok(IntrinsicSizes {
197 min_content_width: 300.0,
198 max_content_width: 300.0,
199 preferred_width: None, min_content_height: 150.0,
201 max_content_height: 150.0,
202 preferred_height: None, });
204 }
205
206 if let NodeType::Image(image_ref) = node_data.get_node_type() {
208 let size = image_ref.get_size();
209 let width = if size.width > 0.0 { size.width } else { 100.0 };
210 let height = if size.height > 0.0 { size.height } else { 100.0 };
211 return Ok(IntrinsicSizes {
212 min_content_width: width,
213 max_content_width: width,
214 preferred_width: Some(width),
215 min_content_height: height,
216 max_content_height: height,
217 preferred_height: Some(height),
218 });
219 }
220 }
221
222 match node.formatting_context {
223 FormattingContext::Block { .. } => {
224 self.calculate_block_intrinsic_sizes(tree, node_index, child_intrinsics)
225 }
226 FormattingContext::Inline => self.calculate_inline_intrinsic_sizes(tree, node_index),
227 FormattingContext::Table => {
228 self.calculate_table_intrinsic_sizes(tree, node_index, child_intrinsics)
229 }
230 _ => self.calculate_block_intrinsic_sizes(tree, node_index, child_intrinsics),
231 }
232 }
233
234 fn calculate_block_intrinsic_sizes(
235 &mut self,
236 tree: &LayoutTree,
237 node_index: usize,
238 child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
239 ) -> Result<IntrinsicSizes> {
240 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
241 let writing_mode = if let Some(dom_id) = node.dom_node_id {
242 let node_state =
243 &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
244 get_writing_mode(self.ctx.styled_dom, dom_id, node_state).unwrap_or_default()
245 } else {
246 LayoutWritingMode::default()
247 };
248
249 if child_intrinsics.is_empty() && node.dom_node_id.is_some() {
252 let dom_id = node.dom_node_id.unwrap();
253 let node_hierarchy = &self.ctx.styled_dom.node_hierarchy.as_container();
254
255 let has_text = dom_id.az_children(node_hierarchy).any(|child_id| {
257 let child_node_data = &self.ctx.styled_dom.node_data.as_container()[child_id];
258 matches!(child_node_data.get_node_type(), NodeType::Text(_))
259 });
260
261 if has_text {
262 self.ctx.debug_log(&format!(
263 "Block node {} has no layout children but has text DOM children - calculating \
264 as inline content",
265 node_index
266 ));
267 return self.calculate_inline_intrinsic_sizes(tree, node_index);
270 }
271 }
272
273 let mut max_child_min_cross = 0.0f32;
274 let mut max_child_max_cross = 0.0f32;
275 let mut total_main_size = 0.0;
276
277 for &child_index in &node.children {
278 if let Some(child_intrinsic) = child_intrinsics.get(&child_index) {
279 let (child_min_cross, child_max_cross, child_main_size) = match writing_mode {
280 LayoutWritingMode::HorizontalTb => (
281 child_intrinsic.min_content_width,
282 child_intrinsic.max_content_width,
283 child_intrinsic.max_content_height,
284 ),
285 _ => (
286 child_intrinsic.min_content_height,
287 child_intrinsic.max_content_height,
288 child_intrinsic.max_content_width,
289 ),
290 };
291
292 max_child_min_cross = max_child_min_cross.max(child_min_cross);
293 max_child_max_cross = max_child_max_cross.max(child_max_cross);
294 total_main_size += child_main_size;
295 }
296 }
297
298 let (min_width, max_width, min_height, max_height) = match writing_mode {
299 LayoutWritingMode::HorizontalTb => (
300 max_child_min_cross,
301 max_child_max_cross,
302 total_main_size,
303 total_main_size,
304 ),
305 _ => (
306 total_main_size,
307 total_main_size,
308 max_child_min_cross,
309 max_child_max_cross,
310 ),
311 };
312
313 Ok(IntrinsicSizes {
314 min_content_width: min_width,
315 max_content_width: max_width,
316 preferred_width: None,
317 min_content_height: min_height,
318 max_content_height: max_height,
319 preferred_height: None,
320 })
321 }
322
323 fn calculate_inline_intrinsic_sizes(
324 &mut self,
325 tree: &LayoutTree,
326 node_index: usize,
327 ) -> Result<IntrinsicSizes> {
328 self.ctx.debug_log(&format!(
329 "Calculating inline intrinsic sizes for node {}",
330 node_index
331 ));
332
333 let inline_content = collect_inline_content(&mut self.ctx, tree, node_index)?;
335
336 if inline_content.is_empty() {
337 self.ctx
338 .debug_log("No inline content found, returning default sizes");
339 return Ok(IntrinsicSizes::default());
340 }
341
342 self.ctx.debug_log(&format!(
343 "Found {} inline content items",
344 inline_content.len()
345 ));
346
347 let min_fragments = vec![LayoutFragment {
350 id: "min".to_string(),
351 constraints: UnifiedConstraints {
352 available_width: AvailableSpace::MinContent,
353 ..Default::default()
354 },
355 }];
356
357 let loaded_fonts = self.ctx.font_manager.get_loaded_fonts();
359
360 let min_layout = match self.text_cache.layout_flow(
361 &inline_content,
362 &[],
363 &min_fragments,
364 &self.ctx.font_manager.font_chain_cache,
365 &self.ctx.font_manager.fc_cache,
366 &loaded_fonts,
367 self.ctx.debug_messages,
368 ) {
369 Ok(layout) => layout,
370 Err(e) => {
371 self.ctx.debug_log(&format!(
372 "Warning: Sizing failed during min-content layout: {:?}",
373 e
374 ));
375 self.ctx
376 .debug_log("Using fallback: returning default intrinsic sizes");
377 return Ok(IntrinsicSizes {
379 min_content_width: 100.0, max_content_width: 300.0,
381 preferred_width: None,
382 min_content_height: 20.0, max_content_height: 20.0,
384 preferred_height: None,
385 });
386 }
387 };
388
389 let max_fragments = vec![LayoutFragment {
392 id: "max".to_string(),
393 constraints: UnifiedConstraints {
394 available_width: AvailableSpace::MaxContent,
395 ..Default::default()
396 },
397 }];
398
399 let max_layout = match self.text_cache.layout_flow(
400 &inline_content,
401 &[],
402 &max_fragments,
403 &self.ctx.font_manager.font_chain_cache,
404 &self.ctx.font_manager.fc_cache,
405 &loaded_fonts,
406 self.ctx.debug_messages,
407 ) {
408 Ok(layout) => layout,
409 Err(e) => {
410 self.ctx.debug_log(&format!(
411 "Warning: Sizing failed during max-content layout: {:?}",
412 e
413 ));
414 self.ctx.debug_log("Using fallback from min-content layout");
415 min_layout.clone()
417 }
418 };
419
420 let min_width = min_layout
421 .fragment_layouts
422 .get("min")
423 .map(|l| l.bounds().width)
424 .unwrap_or(0.0);
425
426 let max_width = max_layout
427 .fragment_layouts
428 .get("max")
429 .map(|l| l.bounds().width)
430 .unwrap_or(0.0);
431
432 let height = max_layout
434 .fragment_layouts
435 .get("max")
436 .map(|l| l.bounds().height)
437 .unwrap_or(0.0);
438
439 Ok(IntrinsicSizes {
440 min_content_width: min_width,
441 max_content_width: max_width,
442 preferred_width: None, min_content_height: height, max_content_height: height,
445 preferred_height: None,
446 })
447 }
448
449 fn calculate_table_intrinsic_sizes(
450 &self,
451 _tree: &LayoutTree,
452 _node_index: usize,
453 _child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
454 ) -> Result<IntrinsicSizes> {
455 Ok(IntrinsicSizes::default())
456 }
457}
458
459fn collect_inline_content_for_sizing<T: ParsedFontTrait>(
475 ctx: &mut LayoutContext<'_, T>,
476 tree: &LayoutTree,
477 ifc_root_index: usize,
478) -> Result<Vec<InlineContent>> {
479 ctx.debug_log(&format!(
480 "Collecting inline content from node {} for intrinsic sizing",
481 ifc_root_index
482 ));
483
484 let mut content = Vec::new();
485
486 collect_inline_content_recursive(ctx, tree, ifc_root_index, &mut content)?;
488
489 ctx.debug_log(&format!(
490 "Collected {} inline content items from node {}",
491 content.len(),
492 ifc_root_index
493 ));
494
495 Ok(content)
496}
497
498fn collect_inline_content_recursive<T: ParsedFontTrait>(
509 ctx: &mut LayoutContext<'_, T>,
510 tree: &LayoutTree,
511 node_index: usize,
512 content: &mut Vec<InlineContent>,
513) -> Result<()> {
514 let node = tree.get(node_index).ok_or(LayoutError::InvalidTree)?;
515
516 let Some(dom_id) = node.dom_node_id else {
519 return process_layout_children(ctx, tree, node, content);
521 };
522
523 if let Some(text) = extract_text_from_node(ctx.styled_dom, dom_id) {
525 let style_props = Arc::new(get_style_properties(ctx.styled_dom, dom_id));
526 ctx.debug_log(&format!("Found text in node {}: '{}'", node_index, text));
527 let text_items = split_text_for_whitespace(
529 ctx.styled_dom,
530 dom_id,
531 &text,
532 style_props,
533 );
534 content.extend(text_items);
535 }
536
537 let node_hierarchy = &ctx.styled_dom.node_hierarchy.as_container();
540 for child_id in dom_id.az_children(node_hierarchy) {
541 let child_dom_node = &ctx.styled_dom.node_data.as_container()[child_id];
543 if let NodeType::Text(text_data) = child_dom_node.get_node_type() {
544 let text = text_data.as_str().to_string();
545 let style_props = Arc::new(get_style_properties(ctx.styled_dom, child_id));
546 ctx.debug_log(&format!(
547 "Found text in DOM child of node {}: '{}'",
548 node_index, text
549 ));
550 let text_items = split_text_for_whitespace(
552 ctx.styled_dom,
553 child_id,
554 &text,
555 style_props,
556 );
557 content.extend(text_items);
558 }
559 }
560
561 process_layout_children(ctx, tree, node, content)
562}
563
564fn process_layout_children<T: ParsedFontTrait>(
566 ctx: &mut LayoutContext<'_, T>,
567 tree: &LayoutTree,
568 node: &LayoutNode,
569 content: &mut Vec<InlineContent>,
570) -> Result<()> {
571 use azul_css::props::basic::SizeMetric;
572 use azul_css::props::layout::{LayoutHeight, LayoutWidth};
573
574 for &child_index in &node.children {
576 let child_node = tree.get(child_index).ok_or(LayoutError::InvalidTree)?;
577 let Some(child_dom_id) = child_node.dom_node_id else {
578 continue;
579 };
580
581 let display = get_display_property(ctx.styled_dom, Some(child_dom_id));
582
583 if display.unwrap_or_default() == LayoutDisplay::Inline {
585 ctx.debug_log(&format!(
588 "Recursing into inline child at node {}",
589 child_index
590 ));
591 collect_inline_content_recursive(ctx, tree, child_index, content)?;
592 } else {
593 let intrinsic_sizes = child_node.intrinsic_sizes.unwrap_or_default();
597
598 let node_state =
601 &ctx.styled_dom.styled_nodes.as_container()[child_dom_id].styled_node_state;
602 let css_width = get_css_width(ctx.styled_dom, child_dom_id, node_state);
603 let css_height = get_css_height(ctx.styled_dom, child_dom_id, node_state);
604
605 let used_width = match css_width {
607 MultiValue::Exact(LayoutWidth::Px(px)) => {
608 use azul_css::props::basic::pixel::{DEFAULT_FONT_SIZE, PT_TO_PX};
610 match px.metric {
611 SizeMetric::Px => px.number.get(),
612 SizeMetric::Pt => px.number.get() * PT_TO_PX,
613 SizeMetric::In => px.number.get() * 96.0,
614 SizeMetric::Cm => px.number.get() * 96.0 / 2.54,
615 SizeMetric::Mm => px.number.get() * 96.0 / 25.4,
616 SizeMetric::Em | SizeMetric::Rem => px.number.get() * DEFAULT_FONT_SIZE,
617 _ => intrinsic_sizes.max_content_width,
619 }
620 }
621 MultiValue::Exact(LayoutWidth::MinContent) => intrinsic_sizes.min_content_width,
622 MultiValue::Exact(LayoutWidth::MaxContent) => intrinsic_sizes.max_content_width,
623 _ => intrinsic_sizes.max_content_width,
625 };
626
627 let used_height = match css_height {
629 MultiValue::Exact(LayoutHeight::Px(px)) => {
630 use azul_css::props::basic::pixel::{DEFAULT_FONT_SIZE, PT_TO_PX};
631 match px.metric {
632 SizeMetric::Px => px.number.get(),
633 SizeMetric::Pt => px.number.get() * PT_TO_PX,
634 SizeMetric::In => px.number.get() * 96.0,
635 SizeMetric::Cm => px.number.get() * 96.0 / 2.54,
636 SizeMetric::Mm => px.number.get() * 96.0 / 25.4,
637 SizeMetric::Em | SizeMetric::Rem => px.number.get() * DEFAULT_FONT_SIZE,
638 _ => intrinsic_sizes.max_content_height,
639 }
640 }
641 MultiValue::Exact(LayoutHeight::MinContent) => intrinsic_sizes.min_content_height,
642 MultiValue::Exact(LayoutHeight::MaxContent) => intrinsic_sizes.max_content_height,
643 _ => intrinsic_sizes.max_content_height,
644 };
645
646 ctx.debug_log(&format!(
647 "Found atomic inline child at node {}: display={:?}, intrinsic_width={}, used_width={}, css_width={:?}",
648 child_index, display, intrinsic_sizes.max_content_width, used_width, css_width
649 ));
650
651 content.push(InlineContent::Shape(InlineShape {
653 shape_def: ShapeDefinition::Rectangle {
654 size: crate::text3::cache::Size {
655 width: used_width,
656 height: used_height,
657 },
658 corner_radius: None,
659 },
660 fill: None,
661 stroke: None,
662 baseline_offset: used_height,
663 source_node_id: Some(child_dom_id),
664 }));
665 }
666 }
667
668 Ok(())
669}
670
671pub fn collect_inline_content<T: ParsedFontTrait>(
673 ctx: &mut LayoutContext<'_, T>,
674 tree: &LayoutTree,
675 ifc_root_index: usize,
676) -> Result<Vec<InlineContent>> {
677 collect_inline_content_for_sizing(ctx, tree, ifc_root_index)
678}
679
680fn calculate_intrinsic_recursive<T: ParsedFontTrait>(
681 ctx: &mut LayoutContext<'_, T>,
682 tree: &mut LayoutTree,
683 node_index: usize,
684) -> Result<IntrinsicSizes> {
685 let node = tree
686 .get(node_index)
687 .cloned()
688 .ok_or(LayoutError::InvalidTree)?;
689
690 let position = get_position_type(ctx.styled_dom, node.dom_node_id);
692 if position == LayoutPosition::Absolute || position == LayoutPosition::Fixed {
693 if let Some(n) = tree.get_mut(node_index) {
694 n.intrinsic_sizes = Some(IntrinsicSizes::default());
695 }
696 return Ok(IntrinsicSizes::default());
697 }
698
699 let mut child_intrinsics = BTreeMap::new();
701 for &child_index in &node.children {
702 let child_intrinsic = calculate_intrinsic_recursive(ctx, tree, child_index)?;
703 child_intrinsics.insert(child_index, child_intrinsic);
704 }
705
706 let intrinsic = calculate_node_intrinsic_sizes_stub(ctx, &node, &child_intrinsics);
708
709 if let Some(n) = tree.get_mut(node_index) {
710 n.intrinsic_sizes = Some(intrinsic.clone());
711 }
712
713 Ok(intrinsic)
714}
715
716fn calculate_node_intrinsic_sizes_stub<T: ParsedFontTrait>(
719 _ctx: &LayoutContext<'_, T>,
720 _node: &LayoutNode,
721 child_intrinsics: &BTreeMap<usize, IntrinsicSizes>,
722) -> IntrinsicSizes {
723 let mut max_width: f32 = 0.0;
725 let mut max_height: f32 = 0.0;
726 let mut total_width: f32 = 0.0;
727 let mut total_height: f32 = 0.0;
728
729 for intrinsic in child_intrinsics.values() {
730 max_width = max_width.max(intrinsic.max_content_width);
731 max_height = max_height.max(intrinsic.max_content_height);
732 total_width += intrinsic.max_content_width;
733 total_height += intrinsic.max_content_height;
734 }
735
736 IntrinsicSizes {
737 min_content_width: total_width.min(max_width),
738 min_content_height: total_height.min(max_height),
739 max_content_width: max_width.max(total_width),
740 max_content_height: max_height.max(total_height),
741 preferred_width: None,
742 preferred_height: None,
743 }
744}
745
746pub fn calculate_used_size_for_node(
758 styled_dom: &StyledDom,
759 dom_id: Option<NodeId>,
760 containing_block_size: LogicalSize,
761 intrinsic: IntrinsicSizes,
762 _box_props: &BoxProps,
763) -> Result<LogicalSize> {
764 let Some(id) = dom_id else {
765 return Ok(LogicalSize::new(
770 containing_block_size.width,
771 if intrinsic.max_content_height > 0.0 {
772 intrinsic.max_content_height
773 } else {
774 0.0
776 },
777 ));
778 };
779
780 let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
781 let css_width = get_css_width(styled_dom, id, node_state);
782 let css_height = get_css_height(styled_dom, id, node_state);
783 let writing_mode = get_writing_mode(styled_dom, id, node_state);
784 let display = get_display_property(styled_dom, Some(id));
785
786 let resolved_width = match css_width.unwrap_or_default() {
789 LayoutWidth::Auto => {
790 match display.unwrap_or_default() {
792 LayoutDisplay::Block
793 | LayoutDisplay::FlowRoot
794 | LayoutDisplay::ListItem
795 | LayoutDisplay::Flex
796 | LayoutDisplay::Grid => {
797 let available_width = containing_block_size.width
806 - _box_props.margin.left
807 - _box_props.margin.right
808 - _box_props.border.left
809 - _box_props.border.right
810 - _box_props.padding.left
811 - _box_props.padding.right;
812
813 available_width.max(0.0)
814 }
815 LayoutDisplay::Inline | LayoutDisplay::InlineBlock => {
816 intrinsic.max_content_width
819 }
820 _ => intrinsic.max_content_width,
822 }
823 }
824 LayoutWidth::Px(px) => {
825 use azul_css::props::basic::{
827 pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
828 SizeMetric,
829 };
830 let pixels_opt = match px.metric {
831 SizeMetric::Px => Some(px.number.get()),
832 SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
833 SizeMetric::In => Some(px.number.get() * 96.0),
834 SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
835 SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
836 SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
837 SizeMetric::Percent => None,
838 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => None,
839 };
840
841 match pixels_opt {
842 Some(pixels) => pixels,
843 None => match px.to_percent() {
844 Some(p) => {
845 let result = resolve_percentage_with_box_model(
846 containing_block_size.width,
847 p.get(),
848 (_box_props.margin.left, _box_props.margin.right),
849 (_box_props.border.left, _box_props.border.right),
850 (_box_props.padding.left, _box_props.padding.right),
851 );
852
853 result
854 }
855 None => intrinsic.max_content_width,
856 },
857 }
858 }
859 LayoutWidth::MinContent => intrinsic.min_content_width,
860 LayoutWidth::MaxContent => intrinsic.max_content_width,
861 };
862
863 let resolved_height = match css_height.unwrap_or_default() {
866 LayoutHeight::Auto => {
867 intrinsic.max_content_height
871 }
872 LayoutHeight::Px(px) => {
873 use azul_css::props::basic::{
875 pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
876 SizeMetric,
877 };
878 let pixels_opt = match px.metric {
879 SizeMetric::Px => Some(px.number.get()),
880 SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
881 SizeMetric::In => Some(px.number.get() * 96.0),
882 SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
883 SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
884 SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
885 SizeMetric::Percent => None,
886 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => None,
887 };
888
889 match pixels_opt {
890 Some(pixels) => pixels,
891 None => match px.to_percent() {
892 Some(p) => resolve_percentage_with_box_model(
893 containing_block_size.height,
894 p.get(),
895 (_box_props.margin.top, _box_props.margin.bottom),
896 (_box_props.border.top, _box_props.border.bottom),
897 (_box_props.padding.top, _box_props.padding.bottom),
898 ),
899 None => intrinsic.max_content_height,
900 },
901 }
902 }
903 LayoutHeight::MinContent => intrinsic.min_content_height,
904 LayoutHeight::MaxContent => intrinsic.max_content_height,
905 };
906
907 let constrained_width = apply_width_constraints(
915 styled_dom,
916 id,
917 node_state,
918 resolved_width,
919 containing_block_size.width,
920 _box_props,
921 );
922
923 let constrained_height = apply_height_constraints(
924 styled_dom,
925 id,
926 node_state,
927 resolved_height,
928 containing_block_size.height,
929 _box_props,
930 );
931
932 let box_sizing = match get_css_box_sizing(styled_dom, id, node_state) {
937 MultiValue::Exact(bs) => bs,
938 MultiValue::Auto | MultiValue::Initial | MultiValue::Inherit => {
939 azul_css::props::layout::LayoutBoxSizing::ContentBox
940 }
941 };
942
943 let (border_box_width, border_box_height) = match box_sizing {
944 azul_css::props::layout::LayoutBoxSizing::BorderBox => {
945 (constrained_width, constrained_height)
949 }
950 azul_css::props::layout::LayoutBoxSizing::ContentBox => {
951 let border_box_width = constrained_width
956 + _box_props.padding.left
957 + _box_props.padding.right
958 + _box_props.border.left
959 + _box_props.border.right;
960 let border_box_height = constrained_height
961 + _box_props.padding.top
962 + _box_props.padding.bottom
963 + _box_props.border.top
964 + _box_props.border.bottom;
965 (border_box_width, border_box_height)
966 }
967 };
968
969 let cross_size = border_box_width;
973 let main_size = border_box_height;
974
975 let result =
977 LogicalSize::from_main_cross(main_size, cross_size, writing_mode.unwrap_or_default());
978
979 Ok(result)
980}
981
982fn apply_width_constraints(
985 styled_dom: &StyledDom,
986 id: NodeId,
987 node_state: &StyledNodeState,
988 tentative_width: f32,
989 containing_block_width: f32,
990 box_props: &BoxProps,
991) -> f32 {
992 use azul_css::props::basic::{
993 pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
994 SizeMetric,
995 };
996
997 use crate::solver3::getters::{get_css_max_width, get_css_min_width, MultiValue};
998
999 let min_width = match get_css_min_width(styled_dom, id, node_state) {
1001 MultiValue::Exact(mw) => {
1002 let px = &mw.inner;
1003 let pixels_opt = match px.metric {
1004 SizeMetric::Px => Some(px.number.get()),
1005 SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
1006 SizeMetric::In => Some(px.number.get() * 96.0),
1007 SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
1008 SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
1009 SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
1010 SizeMetric::Percent => None,
1011 _ => None,
1012 };
1013
1014 match pixels_opt {
1015 Some(pixels) => pixels,
1016 None => px
1017 .to_percent()
1018 .map(|p| {
1019 resolve_percentage_with_box_model(
1020 containing_block_width,
1021 p.get(),
1022 (box_props.margin.left, box_props.margin.right),
1023 (box_props.border.left, box_props.border.right),
1024 (box_props.padding.left, box_props.padding.right),
1025 )
1026 })
1027 .unwrap_or(0.0),
1028 }
1029 }
1030 _ => 0.0,
1031 };
1032
1033 let max_width = match get_css_max_width(styled_dom, id, node_state) {
1035 MultiValue::Exact(mw) => {
1036 let px = &mw.inner;
1037 if px.number.get() >= core::f32::MAX - 1.0 {
1039 None
1040 } else {
1041 let pixels_opt = match px.metric {
1042 SizeMetric::Px => Some(px.number.get()),
1043 SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
1044 SizeMetric::In => Some(px.number.get() * 96.0),
1045 SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
1046 SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
1047 SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
1048 SizeMetric::Percent => None,
1049 _ => None,
1050 };
1051
1052 match pixels_opt {
1053 Some(pixels) => Some(pixels),
1054 None => px.to_percent().map(|p| {
1055 resolve_percentage_with_box_model(
1056 containing_block_width,
1057 p.get(),
1058 (box_props.margin.left, box_props.margin.right),
1059 (box_props.border.left, box_props.border.right),
1060 (box_props.padding.left, box_props.padding.right),
1061 )
1062 }),
1063 }
1064 }
1065 }
1066 _ => None,
1067 };
1068
1069 let mut result = tentative_width;
1072
1073 if let Some(max) = max_width {
1074 result = result.min(max);
1075 }
1076
1077 result = result.max(min_width);
1078
1079 result
1080}
1081
1082fn apply_height_constraints(
1085 styled_dom: &StyledDom,
1086 id: NodeId,
1087 node_state: &StyledNodeState,
1088 tentative_height: f32,
1089 containing_block_height: f32,
1090 box_props: &BoxProps,
1091) -> f32 {
1092 use azul_css::props::basic::{
1093 pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
1094 SizeMetric,
1095 };
1096
1097 use crate::solver3::getters::{get_css_max_height, get_css_min_height, MultiValue};
1098
1099 let min_height = match get_css_min_height(styled_dom, id, node_state) {
1101 MultiValue::Exact(mh) => {
1102 let px = &mh.inner;
1103 let pixels_opt = match px.metric {
1104 SizeMetric::Px => Some(px.number.get()),
1105 SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
1106 SizeMetric::In => Some(px.number.get() * 96.0),
1107 SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
1108 SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
1109 SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
1110 SizeMetric::Percent => None,
1111 _ => None,
1112 };
1113
1114 match pixels_opt {
1115 Some(pixels) => pixels,
1116 None => px
1117 .to_percent()
1118 .map(|p| {
1119 resolve_percentage_with_box_model(
1120 containing_block_height,
1121 p.get(),
1122 (box_props.margin.top, box_props.margin.bottom),
1123 (box_props.border.top, box_props.border.bottom),
1124 (box_props.padding.top, box_props.padding.bottom),
1125 )
1126 })
1127 .unwrap_or(0.0),
1128 }
1129 }
1130 _ => 0.0,
1131 };
1132
1133 let max_height = match get_css_max_height(styled_dom, id, node_state) {
1135 MultiValue::Exact(mh) => {
1136 let px = &mh.inner;
1137 if px.number.get() >= core::f32::MAX - 1.0 {
1139 None
1140 } else {
1141 let pixels_opt = match px.metric {
1142 SizeMetric::Px => Some(px.number.get()),
1143 SizeMetric::Pt => Some(px.number.get() * PT_TO_PX),
1144 SizeMetric::In => Some(px.number.get() * 96.0),
1145 SizeMetric::Cm => Some(px.number.get() * 96.0 / 2.54),
1146 SizeMetric::Mm => Some(px.number.get() * 96.0 / 25.4),
1147 SizeMetric::Em | SizeMetric::Rem => Some(px.number.get() * DEFAULT_FONT_SIZE),
1148 SizeMetric::Percent => None,
1149 _ => None,
1150 };
1151
1152 match pixels_opt {
1153 Some(pixels) => Some(pixels),
1154 None => px.to_percent().map(|p| {
1155 resolve_percentage_with_box_model(
1156 containing_block_height,
1157 p.get(),
1158 (box_props.margin.top, box_props.margin.bottom),
1159 (box_props.border.top, box_props.border.bottom),
1160 (box_props.padding.top, box_props.padding.bottom),
1161 )
1162 }),
1163 }
1164 }
1165 }
1166 _ => None,
1167 };
1168
1169 let mut result = tentative_height;
1172
1173 if let Some(max) = max_height {
1174 result = result.min(max);
1175 }
1176
1177 result = result.max(min_height);
1178
1179 result
1180}
1181
1182fn collect_text_recursive(
1183 tree: &LayoutTree,
1184 node_index: usize,
1185 styled_dom: &StyledDom,
1186 content: &mut Vec<InlineContent>,
1187) {
1188 let node = match tree.get(node_index) {
1189 Some(n) => n,
1190 None => return,
1191 };
1192
1193 if let Some(dom_id) = node.dom_node_id {
1195 if let Some(text) = extract_text_from_node(styled_dom, dom_id) {
1196 content.push(InlineContent::Text(StyledRun {
1197 text,
1198 style: std::sync::Arc::new(StyleProperties::default()),
1199 logical_start_byte: 0,
1200 source_node_id: Some(dom_id),
1201 }));
1202 }
1203 }
1204
1205 for &child_index in &node.children {
1207 collect_text_recursive(tree, child_index, styled_dom, content);
1208 }
1209}
1210
1211pub fn extract_text_from_node(styled_dom: &StyledDom, node_id: NodeId) -> Option<String> {
1212 match &styled_dom.node_data.as_container()[node_id].get_node_type() {
1213 NodeType::Text(text_data) => Some(text_data.as_str().to_string()),
1214 _ => None,
1215 }
1216}
1217
1218fn debug_log(debug_messages: &mut Option<Vec<LayoutDebugMessage>>, message: &str) {
1219 if let Some(messages) = debug_messages {
1220 messages.push(LayoutDebugMessage::info(message));
1221 }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226 use super::*;
1227
1228 #[test]
1229 fn test_resolve_percentage_with_box_model_basic() {
1230 let result = resolve_percentage_with_box_model(
1232 595.0,
1233 1.0, (0.0, 0.0),
1235 (0.0, 0.0),
1236 (0.0, 0.0),
1237 );
1238 assert_eq!(result, 595.0);
1239 }
1240
1241 #[test]
1242 fn test_resolve_percentage_with_box_model_with_margins() {
1243 let result = resolve_percentage_with_box_model(
1248 595.0,
1249 1.0, (20.0, 20.0),
1251 (0.0, 0.0),
1252 (0.0, 0.0),
1253 );
1254 assert_eq!(result, 595.0);
1255 }
1256
1257 #[test]
1258 fn test_resolve_percentage_with_box_model_with_all_box_properties() {
1259 let result = resolve_percentage_with_box_model(
1264 500.0,
1265 1.0, (10.0, 10.0),
1267 (5.0, 5.0),
1268 (8.0, 8.0),
1269 );
1270 assert_eq!(result, 500.0);
1271 }
1272
1273 #[test]
1274 fn test_resolve_percentage_with_box_model_50_percent() {
1275 let result = resolve_percentage_with_box_model(
1279 600.0,
1280 0.5, (20.0, 20.0),
1282 (0.0, 0.0),
1283 (0.0, 0.0),
1284 );
1285 assert_eq!(result, 300.0);
1286 }
1287
1288 #[test]
1289 fn test_resolve_percentage_with_box_model_asymmetric() {
1290 let result = resolve_percentage_with_box_model(
1295 1000.0,
1296 1.0,
1297 (100.0, 50.0),
1298 (10.0, 20.0),
1299 (5.0, 15.0),
1300 );
1301 assert_eq!(result, 1000.0);
1302 }
1303
1304 #[test]
1305 fn test_resolve_percentage_with_box_model_negative_clamping() {
1306 let result = resolve_percentage_with_box_model(
1310 100.0,
1311 1.0,
1312 (60.0, 60.0), (0.0, 0.0),
1314 (0.0, 0.0),
1315 );
1316 assert_eq!(result, 100.0);
1317 }
1318
1319 #[test]
1320 fn test_resolve_percentage_with_box_model_zero_percent() {
1321 let result = resolve_percentage_with_box_model(
1323 1000.0,
1324 0.0, (100.0, 100.0),
1326 (10.0, 10.0),
1327 (5.0, 5.0),
1328 );
1329 assert_eq!(result, 0.0);
1330 }
1331}