orinium_browser/engine/layouter/builder/
layout_fix.rs1use crate::engine::layouter::shift::shift_box;
2use crate::engine::layouter::text_layouter::TextFlowLayouter;
3use crate::engine::layouter::types::{InfoNode, NodeKind, TextAlign, WhiteSpace};
4use ui_layout::{
5 AutoSizeBehavior, Display, GridTrack, InnerDisplay, LayoutChild, LayoutNode, Length,
6 LengthOrAuto, OuterDisplay,
7};
8
9pub fn is_collapsible_whitespace_info(info: &InfoNode, white_space: WhiteSpace) -> bool {
20 if !matches!(white_space, WhiteSpace::Normal | WhiteSpace::Nowrap) {
22 return false;
23 }
24 matches!(&info.kind, NodeKind::Text { text, .. } if text.trim().is_empty())
25}
26
27pub fn is_block_layout_child(child: &LayoutChild) -> bool {
31 child.node().is_some_and(|node| {
32 node.style.display.outer() == Some(OuterDisplay::Block)
33 || (node.style.display.inner() == Some(InnerDisplay::FlowRoot)
34 && node.style.size.auto_behavior == AutoSizeBehavior::ShrinkToFit)
35 })
36}
37
38pub fn constrain_auto_grid_track_items(node: &mut LayoutNode) -> bool {
51 let mut changed = false;
52 for child in &mut node.children {
53 if let LayoutChild::Node(child) = child {
54 changed |= constrain_auto_grid_track_items(child);
55 }
56 }
57
58 if node.style.display.inner() != Some(InnerDisplay::Grid)
59 || node.style.grid_template_columns.is_empty()
60 {
61 return changed;
62 }
63
64 let mut item_index = 0usize;
65 for child in &mut node.children {
66 let LayoutChild::Node(child) = child else {
67 continue;
68 };
69 if child.style.display == Display::None || child.style.position.kind.is_out_of_flow() {
70 continue;
71 }
72
73 let auto_track = node
74 .style
75 .grid_template_columns
76 .get(item_index)
77 .is_some_and(|track| matches!(track, GridTrack::Breadth(LengthOrAuto::Auto)));
78 item_index += 1;
79 let self_aligned = child.style.spacing.margin_left == LengthOrAuto::Auto
80 || child.style.spacing.margin_right == LengthOrAuto::Auto;
81 if (!auto_track && !self_aligned) || child.style.size.width != LengthOrAuto::Auto {
82 continue;
83 }
84
85 let Some(model) = child.layout_box.iter().next() else {
86 continue;
87 };
88 let intrinsic_width = model.children_box.width.max(0.0);
89 if intrinsic_width > 0.0 && intrinsic_width + 0.5 < model.content_box.width {
90 child.style.size.width = LengthOrAuto::Length(Length::Px(intrinsic_width));
91 changed = true;
92 }
93 }
94
95 changed
96}
97
98pub fn correct_atomic_inline_spacing(node: &mut LayoutNode) {
104 correct_atomic_inline_spacing_impl(node, None);
105}
106
107pub fn correct_atomic_inline_spacing_with_info(node: &mut LayoutNode, info: &InfoNode) {
110 correct_atomic_inline_spacing_impl(node, Some(info));
111}
112
113fn correct_atomic_inline_spacing_impl(node: &mut LayoutNode, info: Option<&InfoNode>) {
117 let containing_rect = node.layout_box.iter().next().map(|model| model.content_box);
118 let containing_width = containing_rect.map(|rect| rect.width);
119 let text_align = info
120 .and_then(|info| match &info.kind {
121 NodeKind::Container { style, .. } => Some(style.text_align),
122 _ => None,
123 })
124 .unwrap_or_default();
125 let wraps_inline_content = matches!(
126 node.style.display.inner(),
127 Some(InnerDisplay::Flow | InnerDisplay::FlowRoot)
128 );
129 let mut previous: Option<(f32, f32)> = None;
130 let mut line_y: Option<f32> = None;
131 let mut line_start_x = 0.0;
132 let mut line_bottom = 0.0;
133 let mut line_margin_bottom = 0.0;
134 let mut preceding_block_bottom: Option<(f32, f32)> = None;
135
136 for (child_index, child) in node.children.iter_mut().enumerate() {
137 let LayoutChild::Node(child) = child else {
138 continue;
139 };
140
141 let child_info = info.and_then(|info| info.children.get(child_index));
142 correct_atomic_inline_spacing_impl(child, child_info);
143
144 let is_atomic_inline = child.style.display.outer() == Some(OuterDisplay::Inline)
145 && child.style.display.inner() != Some(InnerDisplay::Flow);
146
147 if is_atomic_inline && let Some(model) = child.layout_box.iter().next() {
148 let rect = model.border_box;
149 let margin_left = fixed_nonnegative_px(&child.style.spacing.margin_left);
150 let margin_right = fixed_nonnegative_px(&child.style.spacing.margin_right);
151 let margin_top = fixed_nonnegative_px(&child.style.spacing.margin_top);
152 let margin_bottom = fixed_nonnegative_px(&child.style.spacing.margin_bottom);
153
154 if line_y.is_none_or(|y| (y - rect.y).abs() >= 0.5) {
155 previous = None;
156 line_y = Some(rect.y);
157 line_start_x = rect.x;
158 line_bottom = rect.bottom();
159 line_margin_bottom = margin_bottom;
160 }
161
162 let mut desired_x = match previous {
163 Some((right, previous_margin_right)) => right + previous_margin_right + margin_left,
164 None => {
165 let margin_width = margin_left + rect.width + margin_right;
166 let aligned_x = containing_rect.map_or(rect.x, |containing| {
167 let free_space = (containing.width - margin_width).max(0.0);
168 match text_align {
170 TextAlign::Left => 0.0,
171 TextAlign::Center => free_space / 2.0,
172 TextAlign::Right => free_space,
173 }
174 });
175 aligned_x + margin_left
176 }
177 };
178 let mut desired_y = rect.y + margin_top;
184 if previous.is_none()
185 && let Some((block_bottom, block_margin_bottom)) = preceding_block_bottom
186 {
187 desired_y = desired_y.max(block_bottom + block_margin_bottom + margin_top);
188 }
189 let exceeds_line = previous.is_some()
190 && wraps_inline_content
191 && containing_width
192 .is_some_and(|width| desired_x + rect.width + margin_right > width + 0.5);
193 if exceeds_line {
194 desired_x = line_start_x + margin_left;
195 desired_y = line_bottom + line_margin_bottom + margin_top;
196 line_y = Some(desired_y);
197 line_bottom = desired_y + rect.height;
198 line_margin_bottom = margin_bottom;
199 }
200
201 let shift_x = desired_x - rect.x;
202 if shift_x.abs() >= 0.01 {
203 shift_box(&mut child.layout_box, shift_x, 0.0);
204 }
205 let shift_y = desired_y - rect.y;
206 if shift_y.abs() >= 0.01 {
207 shift_box(&mut child.layout_box, 0.0, shift_y);
208 }
209
210 line_bottom = line_bottom.max(desired_y + rect.height);
211 line_margin_bottom = line_margin_bottom.max(margin_bottom);
212 previous = Some((desired_x + rect.width, margin_right));
213 } else if matches!(child.layout_box, ui_layout::LayoutBox::BlockBox(_)) {
214 previous = None;
215 line_y = None;
216 if !child.style.position.kind.is_out_of_flow()
217 && let Some(model) = child.layout_box.iter().next()
218 {
219 preceding_block_bottom = Some((
220 model.border_box.bottom(),
221 fixed_nonnegative_px(&child.style.spacing.margin_bottom),
222 ));
223 }
224 }
225 }
226
227 expand_auto_inline_width_to_children(node);
228 correct_single_row_grid_inline_alignment(node);
229}
230
231fn expand_auto_inline_width_to_children(node: &mut LayoutNode) {
237 if node.style.display.outer() != Some(OuterDisplay::Inline)
238 || node.style.size.width != LengthOrAuto::Auto
239 {
240 return;
241 }
242 let required_width = node
244 .children
245 .iter()
246 .filter_map(LayoutChild::node)
247 .filter_map(|child| {
248 child.layout_box.iter().next().map(|model| {
249 model.border_box.right() + fixed_nonnegative_px(&child.style.spacing.margin_right)
250 })
251 })
252 .fold(0.0, f32::max);
253 if required_width <= 0.0 {
254 return;
255 }
256 if let Some(model) = node.layout_box.iter().next() {
257 let extra = required_width - model.content_box.width;
258 if extra > 0.0 {
259 match &mut node.layout_box {
260 ui_layout::LayoutBox::BlockBox(model) => {
261 model.content_box.width += extra;
262 model.padding_box.width += extra;
263 model.border_box.width += extra;
264 model.children_box.width = model.children_box.width.max(required_width);
265 }
266 ui_layout::LayoutBox::InlineBox(inline) => {
267 inline.box_model.content_box.width += extra;
268 inline.box_model.padding_box.width += extra;
269 inline.box_model.border_box.width += extra;
270 inline.box_model.children_box.width =
271 inline.box_model.children_box.width.max(required_width);
272 if let Some(last) = inline.line_spans.last_mut() {
273 last.x_range.end += extra;
274 }
275 }
276 ui_layout::LayoutBox::None => {}
277 }
278 }
279 }
280}
281
282pub fn refresh_missing_text_layout_results(
293 layout: &mut LayoutNode,
294 info: &InfoNode,
295 viewport: (f32, f32),
296) {
297 let containing = layout
298 .layout_box
299 .iter()
300 .next()
301 .map(|model| (model.content_box.width, model.content_box.height))
302 .unwrap_or(viewport);
303
304 for (layout_child, info_child) in layout.children.iter_mut().zip(&info.children) {
305 match (layout_child, &info_child.kind) {
306 (LayoutChild::Node(child), _) => {
307 refresh_missing_text_layout_results(child, info_child, viewport);
308 }
309 (LayoutChild::Custom(custom), NodeKind::Text { text_id, .. })
310 if TextFlowLayouter::get_result(*text_id).is_none() =>
311 {
312 let Some(box_model) = custom.result().map(|result| result.box_model.clone()) else {
313 continue;
314 };
315 let line_height = custom
316 .style()
317 .line_height
318 .resolve_with(Some(containing.0), viewport.0, viewport.1)
319 .unwrap_or(box_model.border_box.height);
320 let _ = custom.layouter_mut().layout(&ui_layout::LayoutContext {
321 containing_block_width: Some(containing.0),
322 containing_block_height: Some(containing.1),
323 start_pos: (box_model.border_box.x, box_model.border_box.y),
324 available_inline_size: box_model.border_box.width.max(1.0),
325 float_space: None,
326 line_height,
327 viewport_width: viewport.0,
328 viewport_height: viewport.1,
329 });
330 }
331 _ => {}
332 }
333 }
334}
335
336fn correct_single_row_grid_inline_alignment(node: &mut LayoutNode) {
342 if node.style.display.inner() != Some(InnerDisplay::Grid) {
343 return;
344 }
345 let Some(content_box) = node.layout_box.iter().next().map(|model| model.content_box) else {
346 return;
347 };
348 let column_gap = fixed_nonnegative_px(&node.style.column_gap);
349 let item_indices: Vec<usize> = node
350 .children
351 .iter()
352 .enumerate()
353 .filter_map(|(index, child)| {
354 child.node().and_then(|child| {
355 (child.style.display != Display::None
356 && !child.style.position.kind.is_out_of_flow())
357 .then_some(index)
358 })
359 })
360 .collect();
361 if item_indices.len() > node.style.grid_template_columns.len() {
362 return;
363 }
364
365 for (position, index) in item_indices.iter().copied().enumerate() {
366 let next_x = item_indices
367 .get(position + 1)
368 .and_then(|next| node.children[*next].node())
369 .and_then(|next| next.layout_box.iter().next())
370 .map(|model| model.border_box.x - column_gap)
371 .unwrap_or(content_box.width);
372 let child = node.children[index].node_mut().expect("grid item");
373 let left_auto = child.style.spacing.margin_left == LengthOrAuto::Auto;
374 let right_auto = child.style.spacing.margin_right == LengthOrAuto::Auto;
375 if !left_auto && !right_auto {
376 continue;
377 }
378 let Some(model) = child.layout_box.iter().next() else {
379 continue;
380 };
381 let track_start = model.border_box.x;
382 let free_space = (next_x - track_start - model.border_box.width).max(0.0);
383 let offset = match (left_auto, right_auto) {
384 (true, true) => free_space / 2.0,
385 (true, false) => free_space,
386 _ => 0.0,
387 };
388 shift_box(&mut child.layout_box, offset, 0.0);
389 }
390}
391
392fn fixed_nonnegative_px(value: &LengthOrAuto) -> f32 {
399 match value {
400 LengthOrAuto::Length(Length::Px(value)) => value.max(0.0),
401 _ => 0.0,
402 }
403}