orinium_browser/engine/layouter/builder/
layout_fix.rs1use crate::engine::layouter::text_layouter::TextFlowLayouter;
2use crate::engine::layouter::types::{InfoNode, NodeKind, TextAlign};
3use ui_layout::{
4 AutoSizeBehavior, Display, GridTrack, InnerDisplay, LayoutChild, LayoutNode, Length,
5 LengthOrAuto, OuterDisplay,
6};
7
8pub fn maximum_fixed_descendant_width(children: &[LayoutChild]) -> Option<f32> {
15 children
16 .iter()
17 .filter_map(|child| match child {
18 LayoutChild::Node(node) => {
19 let own = match node.style.size.width {
20 LengthOrAuto::Length(Length::Px(width))
21 if width.is_finite() && width >= 0.0 =>
22 {
23 Some(width)
24 }
25 _ => None,
26 };
27 own.into_iter()
28 .chain(maximum_fixed_descendant_width(&node.children))
29 .max_by(f32::total_cmp)
30 }
31 _ => None,
32 })
33 .max_by(f32::total_cmp)
34}
35
36pub fn is_collapsible_whitespace_info(info: &InfoNode) -> bool {
39 matches!(&info.kind, NodeKind::Text { text, .. } if text.trim().is_empty())
40}
41
42pub fn is_block_layout_child(child: &LayoutChild) -> bool {
46 child.node().is_some_and(|node| {
47 node.style.display.outer() == Some(OuterDisplay::Block)
48 || (node.style.display.inner() == Some(InnerDisplay::FlowRoot)
49 && node.style.size.auto_behavior == AutoSizeBehavior::ShrinkToFit)
50 })
51}
52
53pub fn constrain_auto_grid_track_items(node: &mut LayoutNode) -> bool {
66 let mut changed = false;
67 for child in &mut node.children {
68 if let LayoutChild::Node(child) = child {
69 changed |= constrain_auto_grid_track_items(child);
70 }
71 }
72
73 if node.style.display.inner() != Some(InnerDisplay::Grid)
74 || node.style.grid_template_columns.is_empty()
75 {
76 return changed;
77 }
78
79 let mut item_index = 0usize;
80 for child in &mut node.children {
81 let LayoutChild::Node(child) = child else {
82 continue;
83 };
84 if child.style.display == Display::None || child.style.position.kind.is_out_of_flow() {
85 continue;
86 }
87
88 let auto_track = node
89 .style
90 .grid_template_columns
91 .get(item_index)
92 .is_some_and(|track| matches!(track, GridTrack::Breadth(LengthOrAuto::Auto)));
93 item_index += 1;
94 let self_aligned = child.style.spacing.margin_left == LengthOrAuto::Auto
95 || child.style.spacing.margin_right == LengthOrAuto::Auto;
96 if (!auto_track && !self_aligned) || child.style.size.width != LengthOrAuto::Auto {
97 continue;
98 }
99
100 let Some(model) = child.layout_box.iter().next() else {
101 continue;
102 };
103 let intrinsic_width = model.children_box.width.max(0.0);
104 if intrinsic_width > 0.0 && intrinsic_width + 0.5 < model.content_box.width {
105 child.style.size.width = LengthOrAuto::Length(Length::Px(intrinsic_width));
106 changed = true;
107 }
108 }
109
110 changed
111}
112
113pub fn correct_atomic_inline_spacing(node: &mut LayoutNode) {
119 correct_atomic_inline_spacing_impl(node, None);
120}
121
122pub fn correct_atomic_inline_spacing_with_info(node: &mut LayoutNode, info: &InfoNode) {
125 correct_atomic_inline_spacing_impl(node, Some(info));
126}
127
128fn correct_atomic_inline_spacing_impl(node: &mut LayoutNode, info: Option<&InfoNode>) {
132 let containing_rect = node.layout_box.iter().next().map(|model| model.content_box);
133 let containing_width = containing_rect.map(|rect| rect.width);
134 let text_align = info
135 .and_then(|info| match &info.kind {
136 NodeKind::Container { style, .. } => Some(style.text_align),
137 _ => None,
138 })
139 .unwrap_or_default();
140 let wraps_inline_content = matches!(
141 node.style.display.inner(),
142 Some(InnerDisplay::Flow | InnerDisplay::FlowRoot)
143 );
144 let mut previous: Option<(f32, f32)> = None;
145 let mut line_y: Option<f32> = None;
146 let mut line_start_x = 0.0;
147 let mut line_bottom = 0.0;
148 let mut line_margin_bottom = 0.0;
149 let mut preceding_block_bottom: Option<(f32, f32)> = None;
150
151 for (child_index, child) in node.children.iter_mut().enumerate() {
152 let LayoutChild::Node(child) = child else {
153 continue;
154 };
155
156 let child_info = info.and_then(|info| info.children.get(child_index));
157 correct_atomic_inline_spacing_impl(child, child_info);
158
159 let is_atomic_inline = child.style.display.outer() == Some(OuterDisplay::Inline)
160 && child.style.display.inner() != Some(InnerDisplay::Flow);
161
162 if is_atomic_inline && let Some(model) = child.layout_box.iter().next() {
163 let rect = model.border_box;
164 let margin_left = fixed_nonnegative_px(&child.style.spacing.margin_left);
165 let margin_right = fixed_nonnegative_px(&child.style.spacing.margin_right);
166 let margin_top = fixed_nonnegative_px(&child.style.spacing.margin_top);
167 let margin_bottom = fixed_nonnegative_px(&child.style.spacing.margin_bottom);
168
169 if line_y.is_none_or(|y| (y - rect.y).abs() >= 0.5) {
170 previous = None;
171 line_y = Some(rect.y);
172 line_start_x = rect.x;
173 line_bottom = rect.bottom();
174 line_margin_bottom = margin_bottom;
175 }
176
177 let mut desired_x = match previous {
178 Some((right, previous_margin_right)) => right + previous_margin_right + margin_left,
179 None => {
180 let margin_width = margin_left + rect.width + margin_right;
181 let aligned_x = containing_rect.map_or(rect.x, |containing| {
182 let free_space = (containing.width - margin_width).max(0.0);
183 containing.x
184 + match text_align {
185 TextAlign::Left => 0.0,
186 TextAlign::Center => free_space / 2.0,
187 TextAlign::Right => free_space,
188 }
189 });
190 aligned_x + margin_left
191 }
192 };
193 let mut desired_y = rect.y + margin_top;
199 if previous.is_none()
200 && let Some((block_bottom, block_margin_bottom)) = preceding_block_bottom
201 {
202 desired_y = desired_y.max(block_bottom + block_margin_bottom + margin_top);
203 }
204 let exceeds_line = previous.is_some()
205 && wraps_inline_content
206 && containing_width
207 .is_some_and(|width| desired_x + rect.width + margin_right > width + 0.5);
208 if exceeds_line {
209 desired_x = line_start_x + margin_left;
210 desired_y = line_bottom + line_margin_bottom + margin_top;
211 line_y = Some(desired_y);
212 line_bottom = desired_y + rect.height;
213 line_margin_bottom = margin_bottom;
214 }
215
216 let shift_x = desired_x - rect.x;
217 if shift_x.abs() >= 0.01 {
218 shift_layout_box_x(&mut child.layout_box, shift_x);
219 }
220 let shift_y = desired_y - rect.y;
221 if shift_y.abs() >= 0.01 {
222 shift_layout_box_y(&mut child.layout_box, shift_y);
223 }
224
225 line_bottom = line_bottom.max(desired_y + rect.height);
226 line_margin_bottom = line_margin_bottom.max(margin_bottom);
227 previous = Some((desired_x + rect.width, margin_right));
228 } else if matches!(child.layout_box, ui_layout::LayoutBox::BlockBox(_)) {
229 previous = None;
230 line_y = None;
231 if !child.style.position.kind.is_out_of_flow()
232 && let Some(model) = child.layout_box.iter().next()
233 {
234 preceding_block_bottom = Some((
235 model.border_box.bottom(),
236 fixed_nonnegative_px(&child.style.spacing.margin_bottom),
237 ));
238 }
239 }
240 }
241
242 expand_auto_inline_width_to_children(node);
243 correct_single_row_grid_inline_alignment(node);
244}
245
246fn expand_auto_inline_width_to_children(node: &mut LayoutNode) {
252 if node.style.display.outer() != Some(OuterDisplay::Inline)
253 || node.style.size.width != LengthOrAuto::Auto
254 {
255 return;
256 }
257 let required_width = node
259 .children
260 .iter()
261 .filter_map(LayoutChild::node)
262 .filter_map(|child| {
263 child.layout_box.iter().next().map(|model| {
264 model.border_box.right() + fixed_nonnegative_px(&child.style.spacing.margin_right)
265 })
266 })
267 .fold(0.0, f32::max);
268 if required_width <= 0.0 {
269 return;
270 }
271 if let Some(model) = node.layout_box.iter().next() {
272 let extra = required_width - model.content_box.width;
273 if extra > 0.0 {
274 match &mut node.layout_box {
275 ui_layout::LayoutBox::BlockBox(model) => {
276 model.content_box.width += extra;
277 model.padding_box.width += extra;
278 model.border_box.width += extra;
279 model.children_box.width = model.children_box.width.max(required_width);
280 }
281 ui_layout::LayoutBox::InlineBox(inline) => {
282 inline.box_model.content_box.width += extra;
283 inline.box_model.padding_box.width += extra;
284 inline.box_model.border_box.width += extra;
285 inline.box_model.children_box.width =
286 inline.box_model.children_box.width.max(required_width);
287 if let Some(last) = inline.line_spans.last_mut() {
288 last.x_range.end += extra;
289 }
290 }
291 ui_layout::LayoutBox::None => {}
292 }
293 }
294 }
295}
296
297pub fn refresh_missing_text_layout_results(
308 layout: &mut LayoutNode,
309 info: &InfoNode,
310 viewport: (f32, f32),
311) {
312 let containing = layout
313 .layout_box
314 .iter()
315 .next()
316 .map(|model| (model.content_box.width, model.content_box.height))
317 .unwrap_or(viewport);
318
319 for (layout_child, info_child) in layout.children.iter_mut().zip(&info.children) {
320 match (layout_child, &info_child.kind) {
321 (LayoutChild::Node(child), _) => {
322 refresh_missing_text_layout_results(child, info_child, viewport);
323 }
324 (LayoutChild::Custom(custom), NodeKind::Text { text_id, .. })
325 if TextFlowLayouter::get_result(*text_id).is_none() =>
326 {
327 let Some(box_model) = custom.result().map(|result| result.box_model.clone()) else {
328 continue;
329 };
330 let line_height = custom
331 .style()
332 .line_height
333 .resolve_with(Some(containing.0), viewport.0, viewport.1)
334 .unwrap_or(box_model.border_box.height);
335 let _ = custom.layouter_mut().layout(&ui_layout::LayoutContext {
336 containing_block_width: Some(containing.0),
337 containing_block_height: Some(containing.1),
338 start_pos: (box_model.border_box.x, box_model.border_box.y),
339 available_inline_size: box_model.border_box.width.max(1.0),
340 line_height,
341 viewport_width: viewport.0,
342 viewport_height: viewport.1,
343 });
344 }
345 _ => {}
346 }
347 }
348}
349
350fn correct_single_row_grid_inline_alignment(node: &mut LayoutNode) {
356 if node.style.display.inner() != Some(InnerDisplay::Grid) {
357 return;
358 }
359 let Some(content_box) = node.layout_box.iter().next().map(|model| model.content_box) else {
360 return;
361 };
362 let column_gap = fixed_nonnegative_px(&node.style.column_gap);
363 let item_indices: Vec<usize> = node
364 .children
365 .iter()
366 .enumerate()
367 .filter_map(|(index, child)| {
368 child.node().and_then(|child| {
369 (child.style.display != Display::None
370 && !child.style.position.kind.is_out_of_flow())
371 .then_some(index)
372 })
373 })
374 .collect();
375 if item_indices.len() > node.style.grid_template_columns.len() {
376 return;
377 }
378
379 for (position, index) in item_indices.iter().copied().enumerate() {
380 let next_x = item_indices
381 .get(position + 1)
382 .and_then(|next| node.children[*next].node())
383 .and_then(|next| next.layout_box.iter().next())
384 .map(|model| model.border_box.x - column_gap)
385 .unwrap_or(content_box.width);
386 let child = node.children[index].node_mut().expect("grid item");
387 let left_auto = child.style.spacing.margin_left == LengthOrAuto::Auto;
388 let right_auto = child.style.spacing.margin_right == LengthOrAuto::Auto;
389 if !left_auto && !right_auto {
390 continue;
391 }
392 let Some(model) = child.layout_box.iter().next() else {
393 continue;
394 };
395 let track_start = model.border_box.x;
396 let free_space = (next_x - track_start - model.border_box.width).max(0.0);
397 let offset = match (left_auto, right_auto) {
398 (true, true) => free_space / 2.0,
399 (true, false) => free_space,
400 _ => 0.0,
401 };
402 shift_layout_box_x(&mut child.layout_box, offset);
403 }
404}
405
406fn shift_layout_box_x(layout_box: &mut ui_layout::LayoutBox, shift_x: f32) {
413 let shift_model = |model: &mut ui_layout::BoxModel| {
414 model.border_box.x += shift_x;
415 model.padding_box.x += shift_x;
416 model.content_box.x += shift_x;
417 model.children_box.x += shift_x;
418 };
419 match layout_box {
420 ui_layout::LayoutBox::None => {}
421 ui_layout::LayoutBox::BlockBox(model) => shift_model(model),
422 ui_layout::LayoutBox::InlineBox(inline) => {
423 shift_model(&mut inline.box_model);
424 for span in &mut inline.line_spans {
425 span.line_pos.0 += shift_x;
426 }
427 }
428 }
429}
430
431fn shift_layout_box_y(layout_box: &mut ui_layout::LayoutBox, shift_y: f32) {
434 let shift_model = |model: &mut ui_layout::BoxModel| {
435 model.border_box.y += shift_y;
436 model.padding_box.y += shift_y;
437 model.content_box.y += shift_y;
438 model.children_box.y += shift_y;
439 };
440 match layout_box {
441 ui_layout::LayoutBox::None => {}
442 ui_layout::LayoutBox::BlockBox(model) => shift_model(model),
443 ui_layout::LayoutBox::InlineBox(inline) => {
444 shift_model(&mut inline.box_model);
445 for span in &mut inline.line_spans {
446 span.line_pos.1 += shift_y;
447 }
448 }
449 }
450}
451
452fn fixed_nonnegative_px(value: &LengthOrAuto) -> f32 {
455 match value {
456 LengthOrAuto::Length(Length::Px(value)) => value.max(0.0),
457 _ => 0.0,
458 }
459}