use crate::{
graphics::block_style,
html::css_parse::{BoxSizing, OverflowWrap, WhiteSpace, WordBreak},
layout::{
BoxConstraint, Cursor, InlineRun, LayoutBox, LayoutContext, PaintStyle, Rect,
inner_formatting_context::flow::{constrained_block_width, dispatch},
layoutbox::{
generation::{GeneratedBoxKind, generate_node},
text_node_run, visible_children,
},
},
tdom::node::{NodeArena, NodeId, NodeType, effective_font_size_cells},
};
use crate::html::css_parse::Position;
use crate::html::css_parse::resolve_line_height_cells;
use super::items::layout_flex_items;
use super::prepare::{prepare_anonymous_text_item, prepare_flex_item};
pub(super) const FLEX_ITEM_MIN_CROSS: usize = 1;
pub(super) enum FlexItemSource {
Node(NodeId),
AnonymousText(AnonymousTextItem),
}
pub(super) struct AnonymousTextItem {
pub(super) runs: Vec<InlineRun>,
pub(super) font_size_cells: usize,
pub(super) line_height_cells: usize,
pub(super) white_space: WhiteSpace,
pub(super) word_break: WordBreak,
pub(super) overflow_wrap: OverflowWrap,
}
pub(super) struct FlexItemPrep {
pub(super) source: FlexItemSource,
pub(super) basis: usize,
pub(super) min_main: usize,
pub(super) css_min_main: Option<usize>,
pub(super) css_max_main: Option<usize>,
pub(super) css_cross: Option<usize>,
pub(super) grow: f32,
pub(super) shrink: f32,
pub(super) align_self: crate::html::css_parse::AlignItems,
pub(super) margin_main_before: usize,
pub(super) margin_main_after: usize,
pub(super) margin_cross_before: usize,
pub(super) margin_cross_after: usize,
pub(super) margin_auto_main: (bool, bool),
pub(super) baseline: usize,
pub(super) order: i32,
pub(super) natural_width: usize,
pub(super) natural_height: usize,
}
pub(super) fn layout_flex_container(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<Rect>,
) -> LayoutBox {
let node = &arena[node_id];
let own_font_size_cells =
effective_font_size_cells(arena, node_id, context.font, context.viewport);
let child_context = context.child(own_font_size_cells);
let edges =
node.css_attr
.get_box_edges(containing.width, child_context.font, child_context.viewport);
let line_height_cells = resolve_line_height_cells(
node.css_attr.get_line_height(),
own_font_size_cells,
child_context.font,
child_context.viewport,
);
let (outer_x, outer_y_top, block_width, preallocated_height) = match pre_allocated_rect {
Some(rect) => (rect.x, rect.y, rect.width.max(1), Some(rect.height.max(1))),
None => {
cursor.collapse_and_skip(edges.margin.top);
let outer_y_top = cursor.y;
let available =
(containing.width as i64 - edges.margin.horizontal() as i64).max(0) as usize;
let block_width = constrained_block_width(
&node.css_attr,
available,
edges,
child_context.font,
child_context.viewport,
);
let mut edges = edges;
let margin_auto = node.css_attr.get_margin_auto();
if margin_auto.left || margin_auto.right {
let free = available.saturating_sub(block_width);
match (margin_auto.left, margin_auto.right) {
(true, true) => edges.margin.left = (free / 2) as isize,
(true, false) => edges.margin.left = free as isize,
_ => {}
}
}
(
(containing.x as i64 + edges.margin.left as i64).max(0) as usize,
outer_y_top,
block_width,
None,
)
}
};
let content_x = outer_x + edges.border.left.thickness() + edges.padding.left;
let content_width = block_width.saturating_sub(edges.inner_horizontal()).max(1);
let content_y = outer_y_top + edges.border.top.thickness() + edges.padding.top;
let content_constraint = BoxConstraint {
x: content_x,
width: content_width,
};
let direction = node.css_attr.get_flex_direction();
let wrap = node.css_attr.get_flex_wrap();
let justify = node.css_attr.get_justify_content();
let align_items = node.css_attr.get_align_items();
let align_content = node.css_attr.get_align_content();
let gap = node
.css_attr
.get_gap(child_context.font, child_context.viewport);
let container_css_height = preallocated_height
.map(|height| height.saturating_sub(edges.inner_vertical()).max(1))
.or_else(|| {
node.css_attr
.get_height(None, child_context.font, child_context.viewport)
});
let has_explicit_height = container_css_height.is_some();
let block_fg = node.css_attr.get_color();
let block_bg = node.css_attr.get_bg_color();
let block_font_style = block_style(&node.css_attr);
let transform = node.css_attr.get_text_transform();
let white_space = node.css_attr.get_white_space(&node.node_type);
let word_break = node.css_attr.get_word_break();
let overflow_wrap = node.css_attr.get_overflow_wrap();
let mut positioned_boxes: Vec<LayoutBox> = Vec::new();
let mut item_preps: Vec<FlexItemPrep> = Vec::new();
let mut text_runs: Vec<InlineRun> = Vec::new();
for &child_id in &visible_children(arena, node_id) {
let child = &arena[child_id];
if child.node_type == NodeType::Text {
if let Some(run) = text_node_run(
arena,
child_id,
block_fg,
block_bg,
block_font_style,
transform,
white_space,
) {
text_runs.push(run);
}
continue;
}
if !text_runs.is_empty() {
item_preps.push(prepare_anonymous_text_item(
AnonymousTextItem {
runs: std::mem::take(&mut text_runs),
font_size_cells: own_font_size_cells,
line_height_cells,
white_space,
word_break,
overflow_wrap,
},
direction,
align_items,
));
}
let Some(generated) = generate_node(arena, child_id) else {
continue;
};
if !matches!(
generated.kind,
GeneratedBoxKind::Normal | GeneratedBoxKind::Replaced
) {
continue;
}
let positioning = arena[child_id].css_attr.get_positioning(
content_width,
child_context.font,
child_context.viewport,
);
match positioning.position {
Position::Absolute | Position::Fixed => {
let mut local_cursor = Cursor {
y: 0,
prev_bottom_margin: 0,
};
if let Some(b) = dispatch(
arena,
child_id,
content_constraint,
&mut local_cursor,
child_context,
None,
) {
positioned_boxes.push(b);
}
}
_ => item_preps.push(prepare_flex_item(
arena,
child_id,
direction,
content_width,
if direction.is_column() {
container_css_height
} else {
None
},
child_context,
align_items,
)),
}
}
if !text_runs.is_empty() {
item_preps.push(prepare_anonymous_text_item(
AnonymousTextItem {
runs: std::mem::take(&mut text_runs),
font_size_cells: own_font_size_cells,
line_height_cells,
white_space,
word_break,
overflow_wrap,
},
direction,
align_items,
));
}
let (mut child_boxes, lines_total_height) = if item_preps.is_empty() {
(Vec::new(), 0usize)
} else {
layout_flex_items(
arena,
node_id,
item_preps,
direction,
wrap,
justify,
align_items,
align_content,
gap,
content_constraint,
content_y,
child_context,
has_explicit_height,
container_css_height.unwrap_or(0),
)
};
child_boxes.extend(positioned_boxes);
let mut outer_height = match preallocated_height {
Some(height) => {
let inner_v = edges.padding.top
+ edges.padding.bottom
+ edges.border.top.thickness()
+ edges.border.bottom.thickness();
height.max(lines_total_height.saturating_add(inner_v))
}
None if has_explicit_height => container_css_height.unwrap_or(0).max(lines_total_height),
None => lines_total_height,
};
if preallocated_height.is_none() {
let inner_v = edges.padding.top
+ edges.padding.bottom
+ edges.border.top.thickness()
+ edges.border.bottom.thickness();
let sizing = node.css_attr.get_box_sizing();
let to_content = |h: usize| match sizing {
BoxSizing::BorderBox => h.saturating_sub(inner_v),
BoxSizing::ContentBox => h,
};
if let Some(m) = node
.css_attr
.get_min_height(None, child_context.font, child_context.viewport)
.map(to_content)
{
outer_height = outer_height.max(m);
}
if let Some(m) = node
.css_attr
.get_max_height(None, child_context.font, child_context.viewport)
.map(to_content)
{
outer_height = outer_height.min(m).max(1);
}
}
if preallocated_height.is_none() {
let total_box_height = outer_height
.saturating_add(edges.border.top.thickness())
.saturating_add(edges.padding.top)
.saturating_add(edges.border.bottom.thickness())
.saturating_add(edges.padding.bottom);
cursor.y = outer_y_top
.saturating_add(total_box_height)
.saturating_add(edges.margin.bottom);
cursor.leave_bottom_margin(edges.margin.bottom);
}
let total_box_height = if preallocated_height.is_some() {
outer_height
} else {
outer_height
.saturating_add(edges.border.top.thickness())
.saturating_add(edges.padding.top)
.saturating_add(edges.border.bottom.thickness())
.saturating_add(edges.padding.bottom)
};
let fill_bg = block_bg.is_some();
let rect = Rect::new(outer_x, outer_y_top, block_width, total_box_height);
let paint_style = PaintStyle {
box_edges: edges,
runs: None,
column_layout: None,
font_size_cells: own_font_size_cells,
line_height_cells,
border_radius: node.css_attr.get_border_radius(
content_width,
child_context.font,
child_context.viewport,
),
fill_bg,
spacing_after: 0,
};
LayoutBox::new_flex_container(
node_id,
rect,
paint_style,
arena[node_id]
.css_attr
.outer_display(&arena[node_id].node_type),
child_boxes,
)
}