1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
//! CSS Grid Layout Algorithm Implementation
//!
//! This module implements the CSS Grid Layout Module Level 1 specification.
//! Reference: <https://www.w3.org/TR/css-grid-1/>
//!
//! ## Current Implementation Steps
//!
//! 1. STEP 1: Compute available grid space (§11.1)
//! 2. STEP 2: Resolve gutters/gap (§10.1)
//! 3. STEP 3: Resolve explicit grid (§7.1)
//! 4. STEP 4: Place grid items (§8.5)
//! 5. STEP 5: Initial track sizing with iterative re-resolution (§11.3-11.4)
//! 6. STEP 6: Compute item sizes using resolved track sizes
//! 7. STEP 7: Finalize tracks (§11.5 Intrinsic + §11.6 Maximize + §11.7 Flex + §11.8 Stretch)
//! 8. STEP 8: Content alignment (§10.5)
//! 9. STEP 9: Item positioning with self-alignment (§10.3-10.4)
//!
//! ## Optimization
//!
//! This implementation separates occupancy tracking from item storage:
//! - Occupancy bitmap: 1 bit per cell, laid out along auto-flow direction
//! for cache-friendly dense scanning
//! - Items Vec: O(N) items stored separately
//!
//! This achieves:
//! - **Time**: O(1) bit-level lookup for occupied cells
//! - **Space**: 1 bit per cell
//! - **Cache**: Sequential bit access along auto-flow direction
//!
//! ## Related Specifications
//! - CSS Grid Layout Module Level 1: <https://www.w3.org/TR/css-grid-1/>
//! - CSS Box Alignment Module Level 3: <https://www.w3.org/TR/css-align-3/>
use alloc::vec::Vec;
use euclid::Rect;
use float_pigment_css::length_num::LengthNum;
use float_pigment_css::num_traits::Zero;
use float_pigment_css::typing::{AlignSelf, JustifySelf};
mod alignment;
mod grid_item;
mod matrix;
mod placement;
mod template;
mod track;
mod track_size;
mod track_sizing;
use crate::{
algo::grid::{
alignment::{
calculate_align_content_offset, calculate_alignment_offset,
calculate_justify_content_offset, calculate_justify_offset,
calculate_justify_offset_rtl, resolve_grid_align_self, resolve_grid_justify_self,
},
grid_item::GridLayoutItem,
matrix::{GridLayoutMatrix, GridMatrix},
placement::place_grid_items,
template::initialize_track_list,
track_size::apply_track_size,
track_sizing::{classify_track_at_index, compute_track_sizes},
},
compute_special_position_children, is_display_none, is_independent_positioning, AxisInfo,
AxisReverse, CollapsedBlockMargin, ComputeRequest, ComputeRequestKind, ComputeResult,
DefLength, Edge, EdgeOption, LayoutStyle, LayoutTreeNode, LayoutTreeVisitor, LayoutUnit,
Normalized, OptionNum, OptionSize, Point, Size, SizingMode, Vector,
};
/// The resolved auto-flow direction for the grid container.
///
/// CSS Grid §7.7: grid-auto-flow
/// <https://www.w3.org/TR/css-grid-1/#grid-auto-flow-property>
///
/// This is the simplified flow direction (without dense/sparse),
/// used internally to determine the inline vs block axis mapping.
#[derive(Clone, PartialEq)]
pub(crate) enum GridFlow {
/// Row flow: inline axis = column, block axis = row.
Row,
/// Column flow: inline axis = row, block axis = column.
Column,
}
/// The trait for computing CSS Grid layout on a `LayoutUnit`.
///
/// Implements the Grid Sizing Algorithm from CSS Grid Layout Module Level 1.
/// <https://www.w3.org/TR/css-grid-1/#algo-grid-sizing>
pub(crate) trait GridContainer<T: LayoutTreeNode> {
/// Compute the grid layout for the given node and return the result.
fn compute(
&mut self,
env: &mut T::Env,
node: &T,
request: ComputeRequest<T::Length>,
margin: EdgeOption<T::Length>,
border: Edge<T::Length>,
padding_border: Edge<T::Length>,
) -> ComputeResult<T::Length>;
}
impl<T: LayoutTreeNode> GridContainer<T> for LayoutUnit<T> {
/// Main Grid layout computation function.
///
/// Implements the Grid Sizing Algorithm from CSS Grid Layout Module Level 1.
/// Reference: <https://www.w3.org/TR/css-grid-1/#algo-grid-sizing>
///
/// ## W3C §11.1 Grid Sizing Algorithm Steps
///
/// The specification defines these steps:
/// 1. Use track sizing algorithm for columns
/// 2. Use track sizing algorithm for rows
/// 3. Re-resolve columns if min-content contribution changed (§11.1 Step 3)
/// 4. Re-resolve rows if min-content contribution changed (§11.1 Step 4)
/// 5. Align tracks via align-content/justify-content
///
/// ## Implementation Steps
///
/// 1. **Available Space**: Calculate container content box
/// 2. **Gutters** (§10.1): Calculate row-gap and column-gap
/// 3. **Explicit Grid** (§7.1): Resolve grid-template-rows/columns
/// 4. **Placement** (§8.5): Place items using auto-placement
/// 5. **Track Sizing** (§11.3-11.4): Size columns, then rows, with iterative re-resolution
/// 6. **Item Sizing**: Compute each item's size using resolved track sizes
/// 7. **Finalize Tracks** (§11.5-11.8): Intrinsic sizing, maximize, flex, stretch
/// 8. **Content Alignment** (§10.5): Apply align/justify-content
/// 9. **Item Positioning** (§10.3-10.4): Apply align/justify-self
fn compute(
&mut self,
env: &mut T::Env,
node: &T,
request: ComputeRequest<T::Length>,
margin: EdgeOption<T::Length>,
border: Edge<T::Length>,
padding_border: Edge<T::Length>,
) -> ComputeResult<T::Length> {
// ═══════════════════════════════════════════════════════════════════════
// STEP 1: Compute Available Grid Space
// CSS Grid §11.1: https://www.w3.org/TR/css-grid-1/#algo-grid-sizing
//
// The available grid space is the space in which the grid tracks are
// sized, determined by the grid container's content box.
// ═══════════════════════════════════════════════════════════════════════
let style = node.style();
let axis_info =
AxisInfo::from_writing_mode_and_direction(style.writing_mode(), style.direction());
let collapsed_margin = CollapsedBlockMargin::from_margin(
margin
.or_zero()
.main_axis_start(axis_info.dir, axis_info.main_dir_rev),
margin
.or_zero()
.main_axis_end(axis_info.dir, axis_info.main_dir_rev),
);
// Short-circuit if the requested size is determined and the ComputeRequestKind is not Position.
if let Some(size) = self.is_requested_size_fixed(&request, Some(collapsed_margin)) {
return size;
}
let requested_size = request.size;
let requested_inner_size = Normalized(OptionSize::new(
requested_size.width - padding_border.horizontal(),
requested_size.height - padding_border.vertical(),
));
let mut available_grid_space = OptionSize::new(
requested_size.width.or(request.max_content.width) - padding_border.horizontal(),
requested_size.height.or(request.max_content.height) - padding_border.vertical(),
);
// ═══════════════════════════════════════════════════════════════════════
// STEP 2: Resolve Gutters (Gap)
// CSS Grid §10.1: https://www.w3.org/TR/css-grid-1/#gutters
//
// The row-gap and column-gap properties define the size of the gutters
// between grid rows and columns respectively.
// ═══════════════════════════════════════════════════════════════════════
let column_gap = style
.column_gap()
.resolve(available_grid_space.width, node)
.or_zero();
let row_gap = style
.row_gap()
.resolve(available_grid_space.height, node)
.or_zero();
// ═══════════════════════════════════════════════════════════════════════
// STEP 3: Resolve the Explicit Grid
// CSS Grid §7.1: https://www.w3.org/TR/css-grid-1/#explicit-grids
//
// The grid-template-columns and grid-template-rows properties define
// the line names and track sizing functions of the explicit grid.
// ═══════════════════════════════════════════════════════════════════════
let grid_template_rows = style.grid_template_rows();
let grid_template_columns = style.grid_template_columns();
// CSS Grid §7.6: Implicit Track Sizing
// https://www.w3.org/TR/css-grid-1/#auto-tracks
// grid-auto-rows/columns specify sizes for implicitly-created tracks
let grid_auto_rows = style.grid_auto_rows();
let grid_auto_columns = style.grid_auto_columns();
let row_track_list = initialize_track_list::<T>(&grid_template_rows);
let column_track_list = initialize_track_list::<T>(&grid_template_columns);
// ═══════════════════════════════════════════════════════════════════════
// STEP 4: Grid Item Placement (Single-Pass with Dynamic Expansion)
// CSS Grid §8.5: https://www.w3.org/TR/css-grid-1/#auto-placement-algo
//
// Grid items are placed according to the auto-placement algorithm.
// The matrix automatically expands when items are placed beyond
// the explicit grid boundaries.
// ═══════════════════════════════════════════════════════════════════════
// Filter out absolutely positioned and display:none children
// CSS Grid §9: Absolutely positioned items are not grid items for placement
// https://www.w3.org/TR/css-grid-1/#abspos
let children = node
.tree_visitor()
.children_iter()
.filter(|child| {
!is_independent_positioning(*child) && !is_display_none::<T>(child.style())
})
.collect::<Vec<_>>();
let children_count = children.len();
let mut grid_matrix = GridMatrix::new(
row_track_list.len(), // Explicit row count
column_track_list.len(), // Explicit column count
style.grid_auto_flow(),
children_count,
);
// Single-pass placement with automatic grid expansion
place_grid_items(&mut grid_matrix, children.into_iter());
// After placement, get the actual grid dimensions (may include implicit tracks)
let actual_row_count = grid_matrix.row_count();
let actual_column_count = grid_matrix.column_count();
// Calculate total gap space: (n-1) gaps for n tracks
// CSS Grid §10.1: Gutters are only placed between tracks, not at edges
let total_column_gaps = if actual_column_count > 1 {
column_gap.mul_i32(actual_column_count as i32 - 1)
} else {
T::Length::zero()
};
let total_row_gaps = if actual_row_count > 1 {
row_gap.mul_i32(actual_row_count as i32 - 1)
} else {
T::Length::zero()
};
// Subtract gaps from available space before track sizing
available_grid_space.width = available_grid_space.width - total_column_gaps;
available_grid_space.height = available_grid_space.height - total_row_gaps;
// ═══════════════════════════════════════════════════════════════════════
// STEP 5: Grid Sizing Algorithm (with Iterative Re-resolution)
// CSS Grid §11.1 https://www.w3.org/TR/css-grid-1/#algo-grid-sizing
// ═══════════════════════════════════════════════════════════════════════
// Save original available space for potential re-iteration
let original_available_width = available_grid_space.width;
let original_available_height = available_grid_space.height;
// First, the track sizing algorithm is used to resolve the sizes of the grid columns.
apply_track_size(
column_track_list.as_slice(),
GridFlow::Column,
&mut grid_matrix,
node,
requested_inner_size.width,
&mut available_grid_space.width,
);
// Next, the track sizing algorithm resolves the sizes of the grid rows.
apply_track_size(
row_track_list.as_slice(),
GridFlow::Row,
&mut grid_matrix,
node,
requested_inner_size.height,
&mut available_grid_space.height,
);
// CSS Grid §11.1 Step 3-4: Re-resolution (once only)
// https://www.w3.org/TR/css-grid-1/#algo-grid-sizing
// If any grid item's min-content contribution changed after row
// sizing (step 2), re-resolve columns, then re-resolve rows.
let has_intrinsic_columns = column_track_list.iter().any(|item| item.is_intrinsic())
|| actual_column_count > column_track_list.len();
let has_intrinsic_rows = row_track_list.iter().any(|item| item.is_intrinsic())
|| actual_row_count > row_track_list.len();
if has_intrinsic_columns && has_intrinsic_rows {
let initial_column_sizes: Vec<_> = grid_matrix
.items()
.map(|item| item.fixed_track_inline_size().cloned())
.collect();
// Re-resolve column sizes (once only, per spec)
available_grid_space.width = original_available_width;
apply_track_size(
column_track_list.as_slice(),
GridFlow::Column,
&mut grid_matrix,
node,
requested_inner_size.width,
&mut available_grid_space.width,
);
// Only re-resolve rows if column sizes actually changed
let column_sizes_changed = grid_matrix
.items()
.zip(initial_column_sizes.iter())
.any(|(item, initial)| item.fixed_track_inline_size() != initial.as_ref());
if column_sizes_changed {
available_grid_space.height = original_available_height;
apply_track_size(
row_track_list.as_slice(),
GridFlow::Row,
&mut grid_matrix,
node,
requested_inner_size.height,
&mut available_grid_space.height,
);
}
}
// ═══════════════════════════════════════════════════════════════════════
// STEP 6: Compute Item Sizes
//
// Compute each grid item's size using the resolved track sizes from STEP 5.
// This includes:
// - min-content size: used for intrinsic track sizing in STEP 7 (§11.5)
// - computed size: the item's actual layout size
//
// The min-content/max-content contribution of a grid item is its
// outer size (including margins).
// Reference: CSS Sizing §5 - Intrinsic Size Determination
// https://www.w3.org/TR/css-sizing-3/#intrinsic-sizes
// ═══════════════════════════════════════════════════════════════════════
let mut grid_layout_matrix = GridLayoutMatrix::new(
grid_matrix.row_count(),
grid_matrix.column_count(),
children_count,
);
// Pre-compute which tracks need intrinsic layout passes.
//
// min-content pass needed for: auto / fr / min-content / max-content tracks.
// max-content pass needed for: auto / max-content tracks (§11.5 Step 4).
// Fixed tracks (length/percentage) don't need either.
let column_track_types: Vec<_> = (0..actual_column_count)
.map(|i| classify_track_at_index(i, &column_track_list, &grid_auto_columns))
.collect();
let row_track_types: Vec<_> = (0..actual_row_count)
.map(|i| classify_track_at_index(i, &row_track_list, &grid_auto_rows))
.collect();
for grid_item in grid_matrix.items() {
let row = grid_item.row();
let column = grid_item.column();
let child_node = grid_item.node;
let mut child_layout_node = child_node.layout_node().unit();
let fixed_track_inline_size = grid_item.fixed_track_inline_size().unwrap().clone();
let fixed_track_block_size = grid_item.fixed_track_block_size().unwrap().clone();
let track_size = Size::new(fixed_track_inline_size, fixed_track_block_size);
let (child_margin, child_border, child_padding_border) =
child_layout_node.margin_border_padding(child_node, track_size);
let css_size = child_layout_node.css_border_box_size(
child_node,
track_size,
child_border,
child_padding_border,
);
let min_max_limit_css_size = child_layout_node
.normalized_min_max_limit(
child_node,
track_size,
child_border,
child_padding_border,
)
.normalized_size(css_size);
let size = Normalized(Size::new(
min_max_limit_css_size.0.width.or(track_size.width),
min_max_limit_css_size.0.height.or(track_size.height),
));
let has_definite_css_width = css_size.width.is_some();
let has_definite_css_height = css_size.height.is_some();
let item_tracks_all_fixed = !column_track_types[column].needs_min_content()
&& !row_track_types[row].needs_min_content();
let unconstrained = Size::new(OptionNum::none(), OptionNum::none());
// §11.5 Step 2: min-content contribution for base_size
let min_content_size =
if (has_definite_css_width && has_definite_css_height) || item_tracks_all_fixed {
None
} else {
Some(
child_layout_node
.compute_internal(
env,
child_node,
ComputeRequest {
size: Normalized(unconstrained),
parent_inner_size: Normalized(unconstrained),
max_content: Normalized(unconstrained),
kind: ComputeRequestKind::AllSize,
parent_is_block: false,
sizing_mode: SizingMode::MinContent,
},
)
.size
.0,
)
};
// §11.5 Step 4: max-content contribution for growth_limit
// Must use unconstrained (infinite) available space, not track_size.
let needs_max_content = column_track_types[column].needs_max_content()
|| row_track_types[row].needs_max_content();
let max_content_size =
if (has_definite_css_width && has_definite_css_height) || !needs_max_content {
None
} else {
Some(
child_layout_node
.compute_internal(
env,
child_node,
ComputeRequest {
size: Normalized(unconstrained),
parent_inner_size: Normalized(unconstrained),
max_content: Normalized(unconstrained),
kind: ComputeRequestKind::AllSize,
parent_is_block: false,
sizing_mode: SizingMode::MaxContent,
},
)
.size
.0,
)
};
let res = child_layout_node.compute_internal(
env,
child_node,
ComputeRequest {
size,
parent_inner_size: Normalized(track_size),
max_content: Normalized(track_size),
kind: ComputeRequestKind::AllSize,
parent_is_block: false,
sizing_mode: request.sizing_mode,
},
);
let mut grid_layout_item =
GridLayoutItem::new(row, column, child_node, child_margin, css_size, track_size);
grid_layout_item.set_min_content_size(min_content_size);
grid_layout_item.set_max_content_size(max_content_size);
grid_layout_item.set_computed_size(res.size.0);
grid_layout_matrix.add_item(grid_layout_item);
}
drop(grid_matrix);
// ═══════════════════════════════════════════════════════════════════════
// STEP 7: Finalize Track Sizes
// CSS Grid §11.5 (Resolve Intrinsic Track Sizes):
// https://www.w3.org/TR/css-grid-1/#algo-content
// CSS Grid §11.7 (Expand Flexible Tracks):
// https://www.w3.org/TR/css-grid-1/#algo-flex-tracks
//
// Determine final track sizes based on item content:
// - For fixed tracks: use the specified size
// - For auto tracks: size to fit content (using outer/margin-box size)
// - For fr tracks: distribute remaining space via iterative algorithm
// ═══════════════════════════════════════════════════════════════════════
let (mut column_tracks, mut row_tracks) = compute_track_sizes(
&grid_layout_matrix,
&column_track_list,
&row_track_list,
available_grid_space,
&grid_auto_columns,
&grid_auto_rows,
);
let has_definite_width = !matches!(style.width(), DefLength::Auto);
let has_definite_height = !matches!(style.height(), DefLength::Auto);
// ═══════════════════════════════════════════════════════════════════════
// STEP 7b: Maximize Tracks (§11.6)
// CSS Grid §11.6: https://www.w3.org/TR/css-grid-1/#algo-grow-tracks
//
// Distribute free space equally to the base sizes of all non-flex tracks,
// freezing tracks as they reach their growth limits.
// Tracks with infinite growth limits (auto tracks) never freeze.
// ═══════════════════════════════════════════════════════════════════════
if has_definite_width {
if let Some(container_width) = requested_inner_size.0.width.val() {
let total_column_size = column_tracks.total_base_size();
let free_space = container_width - total_column_size - total_column_gaps;
column_tracks.maximize(free_space);
}
}
if has_definite_height {
if let Some(container_height) = requested_inner_size.0.height.val() {
let total_row_size = row_tracks.total_base_size();
let free_space = container_height - total_row_size - total_row_gaps;
row_tracks.maximize(free_space);
}
}
// ═══════════════════════════════════════════════════════════════════════
// STEP 7c: Stretch auto Tracks (§11.8)
// CSS Grid §11.8: https://www.w3.org/TR/css-grid-1/#algo-stretch
//
// When justify-content is `normal`/`stretch`, or align-content is `normal`/`stretch`,
// auto tracks are stretched to fill the container.
// Per CSS Box Alignment §5.1.4, `normal` behaves as `stretch` in grid containers.
// Reference: https://drafts.csswg.org/css-align-3/#distribution-grid
// ═══════════════════════════════════════════════════════════════════════
use float_pigment_css::typing::{AlignContent, JustifyContent};
let should_stretch_columns = matches!(
style.justify_content(),
JustifyContent::Normal | JustifyContent::Stretch
);
let should_stretch_rows = matches!(
style.align_content(),
AlignContent::Normal | AlignContent::Stretch
);
if should_stretch_columns && has_definite_width {
if let Some(container_width) = requested_inner_size.0.width.val() {
let total_column_size = column_tracks.total_base_size();
let free_space = container_width - total_column_size - total_column_gaps;
column_tracks.stretch_auto_tracks(free_space);
}
}
if should_stretch_rows && has_definite_height {
if let Some(container_height) = requested_inner_size.0.height.val() {
let total_row_size = row_tracks.total_base_size();
let free_space = container_height - total_row_size - total_row_gaps;
row_tracks.stretch_auto_tracks(free_space);
}
}
// Get final track sizes
let each_inline_size = column_tracks.resolved_sizes();
let each_block_size = row_tracks.resolved_sizes();
// Update items with maximized track sizes
for item in grid_layout_matrix.items_mut() {
let row = item.row();
let column = item.column();
item.track_size.width = OptionNum::some(each_inline_size[column]);
item.track_size.height = OptionNum::some(each_block_size[row]);
}
let total_inline_size: T::Length = each_inline_size
.iter()
.fold(T::Length::zero(), |acc, cur| acc + *cur)
+ total_column_gaps;
let total_block_size: T::Length = each_block_size
.iter()
.fold(T::Length::zero(), |acc, cur| acc + *cur)
+ total_row_gaps;
// ═══════════════════════════════════════════════════════════════════════
// STEP 8: Content Alignment (align-content / justify-content)
// CSS Grid §10.5: https://www.w3.org/TR/css-grid-1/#grid-align
//
// Distribute remaining space among tracks within the container:
// - align-content: Aligns tracks in the block axis (vertical)
// - justify-content: Aligns tracks in the inline axis (horizontal)
//
// Values like space-between, space-around, space-evenly distribute
// extra space between and around the tracks.
// ═══════════════════════════════════════════════════════════════════════
let container_content_width = requested_size.width.unwrap_or(total_inline_size);
let container_content_height = requested_size.height.unwrap_or(total_block_size);
let (block_content_offset, block_gap_addition) = calculate_align_content_offset(
style.align_content(),
total_block_size,
container_content_height,
grid_layout_matrix.row_count(),
);
let (inline_content_offset, inline_gap_addition) = calculate_justify_content_offset(
style.justify_content(),
total_inline_size,
container_content_width,
grid_layout_matrix.column_count(),
);
// Precompute row and column offsets for O(1) lookup during positioning
// This includes content alignment gaps
let row_gap_with_alignment = row_gap + block_gap_addition;
let column_gap_with_alignment = column_gap + inline_gap_addition;
grid_layout_matrix.set_row_sizes(&each_block_size, row_gap_with_alignment);
grid_layout_matrix.set_column_sizes(&each_inline_size, column_gap_with_alignment);
// ═══════════════════════════════════════════════════════════════════════
// STEP 9: Item Positioning and Self-Alignment
// CSS Grid §10.3-10.4: https://www.w3.org/TR/css-grid-1/#grid-align
//
// Position each grid item within its grid area:
// - align-self (§10.4): Aligns item in the block axis within its cell
// - justify-self (§10.3): Aligns item in the inline axis within its cell
//
// Items are positioned at: base_offset + alignment_offset + margin
// ═══════════════════════════════════════════════════════════════════════
// Check if inline axis is reversed (RTL)
let is_inline_reversed = matches!(axis_info.cross_dir_rev, AxisReverse::Reversed);
let container_inline_size = match axis_info.dir {
crate::AxisDirection::Vertical => container_content_width,
crate::AxisDirection::Horizontal => container_content_height,
};
let axis_info_for_origin = if is_inline_reversed {
AxisInfo {
cross_dir_rev: AxisReverse::NotReversed,
..axis_info
}
} else {
axis_info
};
for grid_layout_item in grid_layout_matrix.items() {
let row = grid_layout_item.row();
let column = grid_layout_item.column();
let block_offset = block_content_offset + grid_layout_matrix.get_row_offset(row);
let track_width = grid_layout_item
.track_size
.width
.val()
.unwrap_or(T::Length::zero());
let inline_offset = if is_inline_reversed {
container_inline_size
- inline_content_offset
- grid_layout_matrix.get_column_offset(column)
- track_width
} else {
// LTR: position from left edge
inline_content_offset + grid_layout_matrix.get_column_offset(column)
};
let mut layout_node = grid_layout_item.node.layout_node().unit();
// CSS Box Alignment §6.1: Resolve align-self/justify-self for stretch
// https://www.w3.org/TR/css-align-3/#self-alignment
let child_style = grid_layout_item.node.style();
let align_self = resolve_grid_align_self::<T>(child_style, style);
let justify_self = resolve_grid_justify_self::<T>(child_style, style);
let has_auto_horizontal_margin = grid_layout_item.margin.is_left_right_either_none();
let has_auto_vertical_margin = grid_layout_item.margin.is_top_bottom_either_none();
let stretch_width = if justify_self == JustifySelf::Stretch
&& grid_layout_item.css_size.width.is_none()
&& !has_auto_horizontal_margin
{
grid_layout_item.track_size.width - grid_layout_item.margin.horizontal()
} else {
grid_layout_item
.css_size
.width
.or(grid_layout_item.track_size.width)
};
let stretch_height = if align_self == AlignSelf::Stretch
&& grid_layout_item.css_size.height.is_none()
&& !has_auto_vertical_margin
{
grid_layout_item.track_size.height - grid_layout_item.margin.vertical()
} else {
grid_layout_item
.css_size
.height
.or(grid_layout_item.track_size.height)
};
let size = Size::new(stretch_width, stretch_height);
let compute_result = layout_node.compute_internal(
env,
grid_layout_item.node,
ComputeRequest {
size: Normalized(size),
parent_inner_size: Normalized(grid_layout_item.track_size),
max_content: Normalized(grid_layout_item.track_size),
kind: request.kind,
parent_is_block: false,
sizing_mode: request.sizing_mode,
},
);
let track_height = grid_layout_item
.track_size
.height
.val()
.unwrap_or(T::Length::zero());
let track_size = Size::new(track_width, track_height);
// Get the actual item size (computed size)
let item_size = compute_result.size.0;
// Calculate alignment offset in block axis (vertical)
let align_offset =
calculate_alignment_offset(align_self, item_size.height, track_size.height);
// Calculate justify offset in inline axis (horizontal)
// For RTL, reverse the justify alignment direction
let justify_offset = if is_inline_reversed {
calculate_justify_offset_rtl(justify_self, item_size.width, track_size.width)
} else {
calculate_justify_offset(justify_self, item_size.width, track_size.width)
};
layout_node.gen_origin(
axis_info_for_origin,
track_size,
block_offset
+ align_offset
+ grid_layout_item
.margin
.main_axis_start(axis_info.dir, axis_info.main_dir_rev)
.or_zero(),
inline_offset
+ justify_offset
+ grid_layout_item
.margin
.cross_axis_start(axis_info.dir, axis_info.cross_dir_rev)
.or_zero(),
);
}
let size = Size::new(
requested_size
.width
.unwrap_or(total_inline_size + padding_border.horizontal()),
requested_size
.height
.unwrap_or(total_block_size + padding_border.vertical()),
);
let ret = ComputeResult {
size: Normalized(size),
first_baseline_ascent: Vector::zero(),
last_baseline_ascent: Vector::zero(),
collapsed_margin: CollapsedBlockMargin::zero(),
};
if request.kind != ComputeRequestKind::Position {
self.cache.write_all_size(node, &request, ret);
} else {
compute_special_position_children(
env,
node,
&ret,
border,
padding_border,
AxisInfo {
dir: axis_info.dir,
main_dir_rev: axis_info.main_dir_rev,
cross_dir_rev: axis_info.cross_dir_rev,
},
true,
);
self.result = Rect::new(Point::zero(), ret.size.0);
self.cache.write_position(node, &request, ret);
}
ret
}
}