| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h" |
| |
| #include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.h" |
| #include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_placement.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h" |
| |
| namespace blink { |
| |
| NGGridLayoutAlgorithm::NGGridLayoutAlgorithm( |
| const NGLayoutAlgorithmParams& params) |
| : NGLayoutAlgorithm(params) { |
| DCHECK(params.space.IsNewFormattingContext()); |
| DCHECK(!params.break_token); |
| |
| border_box_size_ = container_builder_.InitialBorderBoxSize(); |
| |
| // At various stages of this algorithm we need to know if the grid |
| // available-size. If it is initially indefinite, we need to know the min/max |
| // sizes as well. Initialize all these to the same value. |
| grid_available_size_ = grid_min_available_size_ = grid_max_available_size_ = |
| ChildAvailableSize(); |
| |
| // Next if our inline-size is indefinite, compute the min/max inline-sizes. |
| if (grid_available_size_.inline_size == kIndefiniteSize) { |
| const LayoutUnit border_scrollbar_padding = |
| BorderScrollbarPadding().InlineSum(); |
| const MinMaxSizes sizes = ComputeMinMaxInlineSizes( |
| ConstraintSpace(), Style(), container_builder_.BorderPadding(), |
| [&border_scrollbar_padding](MinMaxSizesType) -> MinMaxSizesResult { |
| // If we've reached here we are inside the ComputeMinMaxSizes pass, |
| // and also have something like "min-width: min-content". This is |
| // cyclic. Just return the border/scrollbar/padding as our |
| // "intrinsic" size. |
| return MinMaxSizesResult( |
| {border_scrollbar_padding, border_scrollbar_padding}, |
| /* depends_on_percentage_block_size */ false); |
| }); |
| |
| grid_min_available_size_.inline_size = |
| (sizes.min_size - border_scrollbar_padding).ClampNegativeToZero(); |
| grid_max_available_size_.inline_size = |
| (sizes.max_size == LayoutUnit::Max()) |
| ? sizes.max_size |
| : (sizes.max_size - border_scrollbar_padding).ClampNegativeToZero(); |
| } |
| |
| // And similar for the min/max block-sizes. |
| if (grid_available_size_.block_size == kIndefiniteSize) { |
| const LayoutUnit border_scrollbar_padding = |
| BorderScrollbarPadding().BlockSum(); |
| const MinMaxSizes sizes = ComputeMinMaxBlockSizes( |
| ConstraintSpace(), Style(), container_builder_.BorderPadding(), |
| kIndefiniteSize); |
| |
| grid_min_available_size_.block_size = |
| (sizes.min_size - border_scrollbar_padding).ClampNegativeToZero(); |
| grid_max_available_size_.block_size = |
| (sizes.max_size == LayoutUnit::Max()) |
| ? sizes.max_size |
| : (sizes.max_size - border_scrollbar_padding).ClampNegativeToZero(); |
| } |
| } |
| |
| scoped_refptr<const NGLayoutResult> NGGridLayoutAlgorithm::Layout() { |
| // Measure items. |
| GridItems grid_items; |
| Vector<GridItemData> out_of_flow_items; |
| ConstructAndAppendGridItems(&grid_items, &out_of_flow_items); |
| |
| NGGridLayoutAlgorithmTrackCollection column_track_collection; |
| NGGridLayoutAlgorithmTrackCollection row_track_collection; |
| NGGridPlacement grid_placement(Style(), |
| ComputeAutomaticRepetitions(kForColumns), |
| ComputeAutomaticRepetitions(kForRows)); |
| |
| BuildAlgorithmTrackCollections(&grid_items, &column_track_collection, |
| &row_track_collection, &grid_placement); |
| |
| // Cache track span properties for grid items. |
| CacheGridItemsTrackSpanProperties(column_track_collection, &grid_items); |
| CacheGridItemsTrackSpanProperties(row_track_collection, &grid_items); |
| |
| // We perform the track sizing algorithm using two methods. First |
| // |InitializeTrackSizes|, which we need to get an initial column and row set |
| // geometry. Then |ComputeUsedTrackSizes|, to finalize the sizing algorithm |
| // for both dimensions. |
| GridGeometry grid_geometry = {InitializeTrackSizes(&column_track_collection), |
| InitializeTrackSizes(&row_track_collection)}; |
| |
| // Cache set indices for grid items. |
| for (auto& grid_item : grid_items.item_data) { |
| grid_item.SetIndices(column_track_collection); |
| grid_item.SetIndices(row_track_collection); |
| } |
| |
| // Resolve inline size. |
| ComputeUsedTrackSizes(SizingConstraint::kLayout, grid_geometry, |
| &column_track_collection, &grid_items); |
| |
| // Determine the final (used) column set geometry. |
| grid_geometry.column_geometry = ComputeSetGeometry( |
| column_track_collection, grid_available_size_.inline_size); |
| |
| // Resolve block size. |
| ComputeUsedTrackSizes(SizingConstraint::kLayout, grid_geometry, |
| &row_track_collection, &grid_items); |
| |
| // Determine the final (used) row set geometry. |
| grid_geometry.row_geometry = |
| ComputeSetGeometry(row_track_collection, grid_available_size_.block_size); |
| |
| // Intrinsic block size is based on the final row offset. Because gutters are |
| // included in row offsets, subtract out the final gutter (if there is one). |
| LayoutUnit final_gutter = (grid_geometry.row_geometry.sets.size() == 1) |
| ? LayoutUnit() |
| : grid_geometry.row_geometry.gutter_size; |
| LayoutUnit intrinsic_block_size = |
| grid_geometry.row_geometry.sets.back().offset - final_gutter + |
| BorderScrollbarPadding().block_end; |
| |
| intrinsic_block_size = |
| ClampIntrinsicBlockSize(ConstraintSpace(), Node(), |
| BorderScrollbarPadding(), intrinsic_block_size); |
| |
| LayoutUnit block_size = ComputeBlockSizeForFragment( |
| ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size, |
| border_box_size_.inline_size, Node().ShouldBeConsideredAsReplaced()); |
| |
| // If we had an indefinite available block-size, we now need to re-calculate |
| // our grid-gap, and alignment using our new block-size. |
| if (grid_available_size_.block_size == kIndefiniteSize) { |
| const LayoutUnit resolved_available_block_size = |
| (block_size - BorderScrollbarPadding().BlockSum()) |
| .ClampNegativeToZero(); |
| |
| grid_geometry.row_geometry = |
| ComputeSetGeometry(row_track_collection, resolved_available_block_size); |
| } |
| |
| PlaceGridItems(grid_items, grid_geometry, block_size); |
| |
| PlaceOutOfFlowDescendants(column_track_collection, row_track_collection, |
| grid_geometry, grid_placement, block_size); |
| |
| for (auto& out_of_flow_item : out_of_flow_items) { |
| out_of_flow_item.SetIndices(column_track_collection, &grid_placement); |
| out_of_flow_item.SetIndices(row_track_collection, &grid_placement); |
| } |
| |
| PlaceOutOfFlowItems(out_of_flow_items, grid_geometry, block_size); |
| |
| container_builder_.SetIntrinsicBlockSize(intrinsic_block_size); |
| container_builder_.SetFragmentsTotalBlockSize(block_size); |
| |
| NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), &container_builder_).Run(); |
| return container_builder_.ToBoxFragment(); |
| } |
| |
| namespace { |
| |
| LayoutUnit ComputeTotalTrackSize( |
| const NGGridLayoutAlgorithmTrackCollection& track_collection, |
| const LayoutUnit grid_gap) { |
| LayoutUnit total_track_size; |
| for (auto set_iterator = track_collection.GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| const auto& set = set_iterator.CurrentSet(); |
| total_track_size += set.BaseSize() + set.TrackCount() * grid_gap; |
| } |
| // Clamp to zero to avoid a negative |grid_gap| when there are no tracks. |
| total_track_size -= grid_gap; |
| return total_track_size.ClampNegativeToZero(); |
| } |
| |
| } // namespace |
| |
| MinMaxSizesResult NGGridLayoutAlgorithm::ComputeMinMaxSizes( |
| const MinMaxSizesInput& input) const { |
| // TODO(janewman): Handle the cases typically done via: |
| // CalculateMinMaxSizesIgnoringChildren. |
| |
| // Measure items. |
| GridItems grid_items; |
| ConstructAndAppendGridItems(&grid_items); |
| |
| NGGridLayoutAlgorithmTrackCollection column_track_collection_for_min_size; |
| NGGridLayoutAlgorithmTrackCollection row_track_collection; |
| NGGridPlacement grid_placement(Style(), |
| ComputeAutomaticRepetitions(kForColumns), |
| ComputeAutomaticRepetitions(kForRows)); |
| |
| BuildAlgorithmTrackCollections(&grid_items, |
| &column_track_collection_for_min_size, |
| &row_track_collection, &grid_placement); |
| |
| // Cache track span properties for grid items. |
| CacheGridItemsTrackSpanProperties(column_track_collection_for_min_size, |
| &grid_items); |
| |
| // Cache set indices for grid items. |
| for (auto& grid_item : grid_items) { |
| grid_item.SetIndices(column_track_collection_for_min_size); |
| grid_item.SetIndices(row_track_collection); |
| } |
| |
| GridGeometry grid_geometry = { |
| InitializeTrackSizes(&column_track_collection_for_min_size), |
| InitializeTrackSizes(&row_track_collection)}; |
| |
| // Before the track sizing algorithm, create a copy of the column collection; |
| // one will be used to compute the min size and the other for the max size. |
| NGGridLayoutAlgorithmTrackCollection column_track_collection_for_max_size = |
| column_track_collection_for_min_size; |
| |
| // Resolve inline size under a 'min-content' constraint. |
| ComputeUsedTrackSizes(SizingConstraint::kMinContent, grid_geometry, |
| &column_track_collection_for_min_size, &grid_items); |
| // Resolve inline size under a 'max-content' constraint. |
| ComputeUsedTrackSizes(SizingConstraint::kMaxContent, grid_geometry, |
| &column_track_collection_for_max_size, &grid_items); |
| |
| const LayoutUnit grid_gap = GridGap(kForColumns); |
| MinMaxSizes sizes{ |
| ComputeTotalTrackSize(column_track_collection_for_min_size, grid_gap), |
| ComputeTotalTrackSize(column_track_collection_for_max_size, grid_gap)}; |
| |
| // TODO(janewman): Confirm that |input.percentage_resolution_block_size| |
| // isn't used within grid layout. |
| sizes += BorderScrollbarPadding().InlineSum(); |
| return MinMaxSizesResult(sizes, /* depends_on_percentage_block_size */ false); |
| } |
| |
| NGGridLayoutAlgorithm::AutoPlacementType |
| NGGridLayoutAlgorithm::GridItemData::AutoPlacement( |
| GridTrackSizingDirection flow_direction) const { |
| bool is_major_indefinite = Span(flow_direction).IsIndefinite(); |
| bool is_minor_indefinite = |
| Span(flow_direction == kForColumns ? kForRows : kForColumns) |
| .IsIndefinite(); |
| |
| if (is_minor_indefinite && is_major_indefinite) |
| return AutoPlacementType::kBoth; |
| else if (is_minor_indefinite) |
| return AutoPlacementType::kMinor; |
| else if (is_major_indefinite) |
| return AutoPlacementType::kMajor; |
| |
| return AutoPlacementType::kNotNeeded; |
| } |
| |
| const GridSpan& NGGridLayoutAlgorithm::GridItemData::Span( |
| GridTrackSizingDirection track_direction) const { |
| return (track_direction == kForColumns) ? resolved_position.columns |
| : resolved_position.rows; |
| } |
| |
| void NGGridLayoutAlgorithm::GridItemData::SetSpan( |
| const GridSpan& span, |
| GridTrackSizingDirection track_direction) { |
| if (track_direction == kForColumns) |
| resolved_position.columns = span; |
| else |
| resolved_position.rows = span; |
| } |
| |
| wtf_size_t NGGridLayoutAlgorithm::GridItemData::StartLine( |
| GridTrackSizingDirection track_direction) const { |
| const GridSpan& span = (track_direction == kForColumns) |
| ? resolved_position.columns |
| : resolved_position.rows; |
| return span.StartLine(); |
| } |
| |
| wtf_size_t NGGridLayoutAlgorithm::GridItemData::EndLine( |
| GridTrackSizingDirection track_direction) const { |
| const GridSpan& span = (track_direction == kForColumns) |
| ? resolved_position.columns |
| : resolved_position.rows; |
| return span.EndLine(); |
| } |
| |
| wtf_size_t NGGridLayoutAlgorithm::GridItemData::SpanSize( |
| GridTrackSizingDirection track_direction) const { |
| const GridSpan& span = (track_direction == kForColumns) |
| ? resolved_position.columns |
| : resolved_position.rows; |
| return span.IntegerSpan(); |
| } |
| |
| const TrackSpanProperties& |
| NGGridLayoutAlgorithm::GridItemData::GetTrackSpanProperties( |
| GridTrackSizingDirection track_direction) const { |
| return track_direction == kForColumns ? column_span_properties |
| : row_span_properties; |
| } |
| |
| void NGGridLayoutAlgorithm::GridItemData::SetTrackSpanProperty( |
| TrackSpanProperties::PropertyId property, |
| GridTrackSizingDirection track_direction) { |
| if (track_direction == kForColumns) |
| column_span_properties.SetProperty(property); |
| else |
| row_span_properties.SetProperty(property); |
| } |
| |
| bool NGGridLayoutAlgorithm::GridItemData::IsSpanningFlexibleTrack( |
| GridTrackSizingDirection track_direction) const { |
| return GetTrackSpanProperties(track_direction) |
| .HasProperty(TrackSpanProperties::kHasFlexibleTrack); |
| } |
| |
| bool NGGridLayoutAlgorithm::GridItemData::IsSpanningIntrinsicTrack( |
| GridTrackSizingDirection track_direction) const { |
| return GetTrackSpanProperties(track_direction) |
| .HasProperty(TrackSpanProperties::kHasIntrinsicTrack); |
| } |
| |
| NGGridLayoutAlgorithm::ItemSetIndices |
| NGGridLayoutAlgorithm::GridItemData::SetIndices( |
| const NGGridLayoutAlgorithmTrackCollection& track_collection, |
| const NGGridPlacement* grid_placement) { |
| const GridTrackSizingDirection track_direction = track_collection.Direction(); |
| |
| // If the set indices are already computed, we can just return them. |
| base::Optional<ItemSetIndices>& cached_set_indices = |
| (track_direction == kForColumns) ? column_set_indices : row_set_indices; |
| if (cached_set_indices.has_value()) |
| return cached_set_indices.value(); |
| |
| // We only calculate the indexes if: |
| // 1. The item is in flow (it is a grid item) or |
| // 2. The item is out of flow, but the line was not defined as 'auto' and |
| // the line is within the bounds of the grid, since an out of flow item |
| // cannot create grid lines. |
| wtf_size_t start_line, end_line; |
| if (item_type == ItemType::kInGridFlow) { |
| start_line = StartLine(track_direction); |
| end_line = EndLine(track_direction); |
| |
| DCHECK_NE(start_line, kNotFound); |
| DCHECK_NE(end_line, kNotFound); |
| } else { |
| DCHECK(grid_placement); |
| grid_placement->ResolveOutOfFlowItemGridLines( |
| track_collection, node.Style(), &start_line, &end_line); |
| } |
| |
| // TODO(ansollano): An out of flow item can have an index that is in the |
| // middle of a range. Correctly handle this case. |
| ItemSetIndices set_indices; |
| if (start_line != kNotFound) { |
| DCHECK(track_collection.IsGridLineWithinImplicitGrid(start_line)); |
| // If a start line of an out of flow item is the last line of the grid, then |
| // the |set_indices.begin| is the number of sets in the collection. |
| if (track_collection.EndLineOfImplicitGrid() == start_line) { |
| DCHECK_EQ(item_type, ItemType::kOutOfFlow); |
| set_indices.begin = track_collection.SetCount(); |
| } else { |
| wtf_size_t first_spanned_range = |
| track_collection.RangeIndexFromTrackNumber(start_line); |
| set_indices.begin = |
| track_collection.RangeStartingSetIndex(first_spanned_range); |
| } |
| } |
| |
| if (end_line != kNotFound) { |
| DCHECK(track_collection.IsGridLineWithinImplicitGrid(end_line)); |
| // If an end line of an out of flow item is the first line of the grid, then |
| // the |set_indices.end| is 0. |
| if (!end_line) { |
| DCHECK_EQ(item_type, ItemType::kOutOfFlow); |
| set_indices.end = 0; |
| } else { |
| wtf_size_t last_spanned_range = |
| track_collection.RangeIndexFromTrackNumber(end_line - 1); |
| set_indices.end = |
| track_collection.RangeStartingSetIndex(last_spanned_range) + |
| track_collection.RangeSetCount(last_spanned_range); |
| } |
| } |
| |
| #if DCHECK_IS_ON() |
| if (set_indices.begin != kNotFound && set_indices.end != kNotFound) { |
| DCHECK_LE(set_indices.end, track_collection.SetCount()); |
| DCHECK_LT(set_indices.begin, set_indices.end); |
| } else if (set_indices.begin != kNotFound) { |
| DCHECK_LE(set_indices.begin, track_collection.SetCount()); |
| } else if (set_indices.end != kNotFound) { |
| DCHECK_LE(set_indices.end, track_collection.SetCount()); |
| } |
| #endif |
| |
| cached_set_indices = set_indices; |
| return set_indices; |
| } |
| |
| NGGridLayoutAlgorithm::GridItems::Iterator |
| NGGridLayoutAlgorithm::GridItems::begin() { |
| return Iterator(&item_data, reordered_item_indices.begin()); |
| } |
| |
| NGGridLayoutAlgorithm::GridItems::Iterator |
| NGGridLayoutAlgorithm::GridItems::end() { |
| return Iterator(&item_data, reordered_item_indices.end()); |
| } |
| |
| void NGGridLayoutAlgorithm::GridItems::Append( |
| const GridItemData& new_item_data) { |
| reordered_item_indices.push_back(item_data.size()); |
| item_data.emplace_back(new_item_data); |
| } |
| |
| bool NGGridLayoutAlgorithm::GridItems::IsEmpty() const { |
| return item_data.IsEmpty(); |
| } |
| |
| namespace { |
| |
| // TODO(ethavar): Consider doing this in-place, as it's not used more than once. |
| // Returns an iterator for every |NGGridSet| contained within an item's span in |
| // the relevant track collection. |
| NGGridLayoutAlgorithmTrackCollection::SetIterator GetSetIteratorForItem( |
| NGGridLayoutAlgorithm::GridItemData& item, |
| NGGridLayoutAlgorithmTrackCollection& track_collection) { |
| NGGridLayoutAlgorithm::ItemSetIndices set_indices = |
| item.SetIndices(track_collection); |
| return track_collection.GetSetIterator(set_indices.begin, set_indices.end); |
| } |
| |
| } // namespace |
| |
| // TODO(ethavar): Current implementation of this method simply returns the |
| // preferred size of the grid item in the relevant direction. We should follow |
| // the definitions from https://drafts.csswg.org/css-grid-2/#algo-spanning-items |
| // (i.e. compute minimum, min-content, and max-content contributions). |
| LayoutUnit NGGridLayoutAlgorithm::ContributionSizeForGridItem( |
| const GridGeometry& grid_geometry, |
| const GridItemData& grid_item, |
| GridTrackSizingDirection track_direction, |
| GridItemContributionType contribution_type) const { |
| const NGBlockNode& node = grid_item.node; |
| const ComputedStyle& item_style = node.Style(); |
| |
| // TODO(ikilpatrick): We'll need to record if any child used an indefinite |
| // size for its contribution, such that we can then do the 2nd pass on the |
| // track-sizing algorithm. |
| LogicalRect unused; |
| const NGConstraintSpace space = CreateConstraintSpace( |
| grid_geometry, grid_item, NGCacheSlot::kMeasure, &unused); |
| |
| bool is_parallel_with_track_direction = |
| (track_direction == kForColumns) == |
| IsParallelWritingMode(Style().GetWritingMode(), |
| item_style.GetWritingMode()); |
| |
| auto MinMaxContentSizes = [&]() -> MinMaxSizes { |
| DCHECK(is_parallel_with_track_direction); |
| // TODO(ikilpatrick): kIndefiniteSize is incorrect for the %-block-size. |
| // We'll want to determine this using the base or used track-sizes instead. |
| // This should match the %-resolution sizes we use for layout during |
| // measuring. |
| MinMaxSizesInput input(kIndefiniteSize, MinMaxSizesType::kContent); |
| return ComputeMinAndMaxContentContributionForSelf(node, input).sizes; |
| }; |
| |
| // This function will determine the correct block-size of a grid-item. |
| // TODO(ikilpatrick): This should try and skip layout when possible. Notes: |
| // - We'll need to do a full layout for tables. |
| // - We'll need special logic for replaced elements. |
| // - We'll need to respect the aspect-ratio when appropriate. |
| auto BlockSize = [&]() -> LayoutUnit { |
| DCHECK(!is_parallel_with_track_direction); |
| scoped_refptr<const NGLayoutResult> result = node.Layout(space); |
| // We want to return the block-size in the *child's* writing-mode. |
| return NGFragment(item_style.GetWritingDirection(), |
| result->PhysicalFragment()) |
| .BlockSize(); |
| }; |
| |
| LayoutUnit contribution; |
| switch (contribution_type) { |
| case GridItemContributionType::kForContentBasedMinimums: |
| case GridItemContributionType::kForIntrinsicMaximums: |
| if (is_parallel_with_track_direction) |
| contribution = MinMaxContentSizes().min_size; |
| else |
| contribution = BlockSize(); |
| break; |
| case GridItemContributionType::kForIntrinsicMinimums: { |
| // TODO(ikilpatrick): All of the below is incorrect for replaced elements. |
| |
| const Length& main_length = is_parallel_with_track_direction |
| ? item_style.LogicalWidth() |
| : item_style.LogicalHeight(); |
| |
| // We could be clever is and make this an if-stmt, but each type has |
| // subtle consequences. This forces us in the future when we add a new |
| // length type to consider what the best thing is for grid. |
| switch (main_length.GetType()) { |
| case Length::kAuto: |
| case Length::kFitContent: |
| case Length::kFillAvailable: |
| case Length::kPercent: |
| case Length::kCalculated: { |
| // All of the above lengths are considered auto if we are querying a |
| // minimum contribution. They all require definite track-sizes to |
| // determine their final size. |
| |
| // Scroll containers are "compressible", and we only consider their |
| // min-size when determining their contribution. |
| if (item_style.IsScrollContainer()) { |
| const NGBoxStrut border_padding = |
| ComputeBorders(space, node) + ComputePadding(space, item_style); |
| |
| // TODO(ikilpatrick): This block needs to respect the aspect-ratio, |
| // and apply the transferred min/max sizes when appropriate. We do |
| // this sometimes elsewhere so should unify and simplify this code. |
| if (is_parallel_with_track_direction) { |
| auto MinMaxSizesFunc = |
| [&](MinMaxSizesType type) -> MinMaxSizesResult { |
| // TODO(ikilpatrick): Again, kIndefiniteSize here is incorrect, |
| // and needs to use the base or resolved track sizes. |
| MinMaxSizesInput input(kIndefiniteSize, type); |
| return node.ComputeMinMaxSizes(item_style.GetWritingMode(), |
| input, &space); |
| }; |
| |
| contribution = ResolveMinInlineLength( |
| space, item_style, border_padding, MinMaxSizesFunc, |
| item_style.LogicalMinWidth()); |
| } else { |
| contribution = |
| ResolveMinBlockLength(space, item_style, border_padding, |
| item_style.LogicalMinHeight()); |
| } |
| break; |
| } |
| |
| if (is_parallel_with_track_direction) |
| contribution = MinMaxContentSizes().min_size; |
| else |
| contribution = BlockSize(); |
| break; |
| } |
| case Length::kMinContent: |
| case Length::kMaxContent: |
| case Length::kFixed: { |
| // All of the above lengths are "definite" (non-auto), and don't need |
| // the special min-size treatment above. (They will all end up being |
| // the specified size). |
| if (is_parallel_with_track_direction) { |
| // TODO(ikilpatrick): This is incorrect for replaced elements. |
| const NGBoxStrut border_padding = |
| ComputeBorders(space, node) + ComputePadding(space, item_style); |
| contribution = |
| ComputeInlineSizeForFragment(space, node, border_padding); |
| } else { |
| contribution = BlockSize(); |
| } |
| break; |
| } |
| case Length::kMinIntrinsic: |
| case Length::kDeviceWidth: |
| case Length::kDeviceHeight: |
| case Length::kExtendToZoom: |
| case Length::kNone: |
| NOTREACHED(); |
| break; |
| } |
| break; |
| } |
| case GridItemContributionType::kForMaxContentMinimums: |
| case GridItemContributionType::kForMaxContentMaximums: |
| if (is_parallel_with_track_direction) |
| contribution = MinMaxContentSizes().max_size; |
| else |
| contribution = BlockSize(); |
| break; |
| case GridItemContributionType::kForFreeSpace: |
| NOTREACHED() << "|kForFreeSpace| should only be used to distribute extra " |
| "space in maximize tracks and stretch auto tracks steps."; |
| break; |
| } |
| |
| DCHECK_NE(contribution, kIndefiniteSize); |
| return contribution + ((track_direction == kForColumns) |
| ? grid_item.margins.InlineSum() |
| : grid_item.margins.BlockSum()); |
| } |
| |
| void NGGridLayoutAlgorithm::ConstructAndAppendGridItems( |
| GridItems* grid_items, |
| Vector<GridItemData>* out_of_flow_items) const { |
| DCHECK(grid_items); |
| NGGridChildIterator iterator(Node()); |
| for (NGBlockNode child = iterator.NextChild(); child; |
| child = iterator.NextChild()) { |
| GridItemData grid_item(MeasureGridItem(child)); |
| // If |out_of_flow_items| is provided, store out-of-flow items separately, |
| // as they do not contribute to track sizing or auto-placement. |
| if (grid_item.item_type == ItemType::kInGridFlow) |
| grid_items->Append(grid_item); |
| else if (out_of_flow_items) |
| out_of_flow_items->emplace_back(grid_item); |
| } |
| } |
| |
| // https://drafts.csswg.org/css-grid-2/#auto-repeat |
| wtf_size_t NGGridLayoutAlgorithm::ComputeAutomaticRepetitions( |
| GridTrackSizingDirection track_direction) const { |
| const NGGridTrackList& track_list = |
| (track_direction == kForColumns) |
| ? Style().GridTemplateColumns().NGTrackList() |
| : Style().GridTemplateRows().NGTrackList(); |
| if (!track_list.HasAutoRepeater()) |
| return 0; |
| |
| LayoutUnit available_size = (track_direction == kForColumns) |
| ? grid_available_size_.inline_size |
| : grid_available_size_.block_size; |
| LayoutUnit max_available_size = available_size; |
| |
| if (available_size == kIndefiniteSize) { |
| max_available_size = (track_direction == kForColumns) |
| ? grid_max_available_size_.inline_size |
| : grid_max_available_size_.block_size; |
| available_size = (track_direction == kForColumns) |
| ? grid_min_available_size_.inline_size |
| : grid_min_available_size_.block_size; |
| } |
| |
| const LayoutUnit grid_gap = GridGap(track_direction, available_size); |
| |
| LayoutUnit auto_repeater_size; |
| LayoutUnit non_auto_specified_size; |
| for (wtf_size_t repeater_index = 0; |
| repeater_index < track_list.RepeaterCount(); ++repeater_index) { |
| const wtf_size_t repeater_track_count = |
| track_list.RepeatSize(repeater_index); |
| LayoutUnit repeater_size; |
| for (wtf_size_t track_index = 0; track_index < repeater_track_count; |
| ++track_index) { |
| const GridTrackSize& track_size = |
| track_list.RepeatTrackSize(repeater_index, track_index); |
| base::Optional<LayoutUnit> fixed_min_track_breadth; |
| base::Optional<LayoutUnit> fixed_max_track_breadth; |
| if (track_size.HasFixedMaxTrackBreadth()) { |
| fixed_max_track_breadth = MinimumValueForLength( |
| track_size.MaxTrackBreadth().length(), available_size); |
| } |
| if (track_size.HasFixedMinTrackBreadth()) { |
| fixed_min_track_breadth = MinimumValueForLength( |
| track_size.MinTrackBreadth().length(), available_size); |
| } |
| LayoutUnit track_contribution; |
| if (fixed_max_track_breadth && fixed_min_track_breadth) { |
| track_contribution = |
| std::max(*fixed_max_track_breadth, *fixed_min_track_breadth); |
| } else if (fixed_max_track_breadth) { |
| track_contribution = *fixed_max_track_breadth; |
| } else if (fixed_min_track_breadth) { |
| track_contribution = *fixed_min_track_breadth; |
| } |
| |
| // For the purpose of finding the number of auto-repeated tracks in a |
| // standalone axis, the UA must floor the track size to a UA-specified |
| // value to avoid division by zero. It is suggested that this floor be |
| // 1px. |
| if (track_list.RepeatType(repeater_index) != |
| NGGridTrackRepeater::kNoAutoRepeat) { |
| track_contribution = std::max(LayoutUnit(1), track_contribution); |
| } |
| |
| repeater_size += track_contribution + grid_gap; |
| } |
| if (track_list.RepeatType(repeater_index) == |
| NGGridTrackRepeater::kNoAutoRepeat) { |
| non_auto_specified_size += |
| repeater_size * track_list.RepeatCount(repeater_index, 0); |
| } else { |
| DCHECK_EQ(0, auto_repeater_size); |
| auto_repeater_size = repeater_size; |
| } |
| } |
| |
| DCHECK_GT(auto_repeater_size, 0); |
| |
| // We can compute the number of repetitions by satisfying the expression |
| // below. Notice that we subtract an extra |grid_gap| since it was included |
| // in the contribution for the last set in the collection. |
| // available_size = |
| // (repetitions * auto_repeater_size) + |
| // non_auto_specified_size - grid_gap |
| // |
| // Solving for repetitions we have: |
| // repetitions = |
| // available_size - (non_auto_specified_size - grid_gap) / |
| // auto_repeater_size |
| non_auto_specified_size -= grid_gap; |
| |
| // First we want to allow as many repetitions as possible, up to the max |
| // available-size. Only do this if we have a definite max-size. |
| // If a definite available-size was provided, |max_available_size| will be |
| // set to that value. |
| if (max_available_size != LayoutUnit::Max()) { |
| // Use floor to ensure that the auto repeater sizes goes under the max |
| // available-size. |
| const int count = FloorToInt( |
| (max_available_size - non_auto_specified_size) / auto_repeater_size); |
| return (count <= 0) ? 1u : count; |
| } |
| |
| // Next, consider the min available-size, which was already used to floor |
| // |available_size|. Use ceil to ensure that the auto repeater size goes |
| // above this min available-size. |
| const int count = CeilToInt((available_size - non_auto_specified_size) / |
| auto_repeater_size); |
| return (count <= 0) ? 1u : count; |
| } |
| |
| namespace { |
| |
| using AxisEdge = NGGridLayoutAlgorithm::AxisEdge; |
| |
| // Given an |item_position| determines the correct |AxisEdge| alignment. |
| // Additionally will determine if the grid-item should be stretched with the |
| // |is_stretched| out-parameter. |
| AxisEdge AxisEdgeFromItemPosition(const ComputedStyle& container_style, |
| const ComputedStyle& style, |
| const ItemPosition item_position, |
| bool is_inline_axis, |
| bool* is_stretched) { |
| DCHECK(is_stretched); |
| *is_stretched = false; |
| |
| // Auto-margins take precedence over any alignment properties. |
| if (style.MayHaveMargin()) { |
| bool start_auto = is_inline_axis |
| ? style.MarginStartUsing(container_style).IsAuto() |
| : style.MarginBeforeUsing(container_style).IsAuto(); |
| bool end_auto = is_inline_axis |
| ? style.MarginEndUsing(container_style).IsAuto() |
| : style.MarginAfterUsing(container_style).IsAuto(); |
| |
| if (start_auto && end_auto) |
| return AxisEdge::kCenter; |
| else if (start_auto) |
| return AxisEdge::kEnd; |
| else if (end_auto) |
| return AxisEdge::kStart; |
| } |
| |
| const auto container_writing_direction = |
| container_style.GetWritingDirection(); |
| |
| switch (item_position) { |
| case ItemPosition::kSelfStart: |
| case ItemPosition::kSelfEnd: { |
| // In order to determine the correct "self" axis-edge without a |
| // complicated set of if-branches we use two converters. |
| |
| // First use the grid-item's writing-direction to convert the logical |
| // edge into the physical coordinate space. |
| LogicalToPhysical<AxisEdge> physical(style.GetWritingDirection(), |
| AxisEdge::kStart, AxisEdge::kEnd, |
| AxisEdge::kStart, AxisEdge::kEnd); |
| |
| // Then use the container's writing-direction to convert the physical |
| // edges, into our logical coordinate space. |
| PhysicalToLogical<AxisEdge> logical(container_writing_direction, |
| physical.Top(), physical.Right(), |
| physical.Bottom(), physical.Left()); |
| |
| if (is_inline_axis) { |
| return item_position == ItemPosition::kSelfStart ? logical.InlineStart() |
| : logical.InlineEnd(); |
| } |
| return item_position == ItemPosition::kSelfStart ? logical.BlockStart() |
| : logical.BlockEnd(); |
| } |
| case ItemPosition::kCenter: |
| return AxisEdge::kCenter; |
| case ItemPosition::kFlexStart: |
| case ItemPosition::kStart: |
| return AxisEdge::kStart; |
| case ItemPosition::kFlexEnd: |
| case ItemPosition::kEnd: |
| return AxisEdge::kEnd; |
| case ItemPosition::kStretch: |
| *is_stretched = true; |
| return AxisEdge::kStart; |
| case ItemPosition::kBaseline: |
| case ItemPosition::kLastBaseline: |
| return AxisEdge::kBaseline; |
| case ItemPosition::kLeft: |
| DCHECK(is_inline_axis); |
| return container_writing_direction.IsLtr() ? AxisEdge::kStart |
| : AxisEdge::kEnd; |
| case ItemPosition::kRight: |
| DCHECK(is_inline_axis); |
| return container_writing_direction.IsRtl() ? AxisEdge::kStart |
| : AxisEdge::kEnd; |
| case ItemPosition::kLegacy: |
| case ItemPosition::kAuto: |
| case ItemPosition::kNormal: |
| NOTREACHED(); |
| break; |
| } |
| |
| NOTREACHED(); |
| return AxisEdge::kStart; |
| } |
| |
| } // namespace |
| |
| NGGridLayoutAlgorithm::GridItemData NGGridLayoutAlgorithm::MeasureGridItem( |
| const NGBlockNode node) const { |
| const auto& container_style = Style(); |
| |
| // Before we take track sizing into account for column width contributions, |
| // have all child inline and min/max sizes measured for content-based width |
| // resolution. |
| GridItemData grid_item(node); |
| const ComputedStyle& item_style = node.Style(); |
| |
| const ItemPosition normal_behaviour = |
| node.IsReplaced() ? ItemPosition::kStart : ItemPosition::kStretch; |
| |
| // Determine the alignment for the grid-item ahead of time (we may need to |
| // know if it stretches ahead of time to correctly determine any block-axis |
| // contribution). |
| grid_item.inline_axis_alignment = AxisEdgeFromItemPosition( |
| container_style, item_style, |
| item_style.ResolvedJustifySelf(normal_behaviour, &container_style) |
| .GetPosition(), |
| /* is_inline_axis */ true, &grid_item.is_inline_axis_stretched); |
| grid_item.block_axis_alignment = AxisEdgeFromItemPosition( |
| container_style, item_style, |
| item_style.ResolvedAlignSelf(normal_behaviour, &container_style) |
| .GetPosition(), |
| /* is_inline_axis */ false, &grid_item.is_block_axis_stretched); |
| |
| // TODO(ikilpatrick): This is likely incorrect for margins in the |
| // ComputeMinMaxSizes phase. |
| grid_item.margins = |
| ComputePhysicalMargins(item_style, ChildAvailableSize().inline_size) |
| .ConvertToLogical(ConstraintSpace().GetWritingDirection()); |
| |
| grid_item.item_type = node.IsOutOfFlowPositioned() ? ItemType::kOutOfFlow |
| : ItemType::kInGridFlow; |
| |
| return grid_item; |
| } |
| |
| void NGGridLayoutAlgorithm::BuildBlockTrackCollections( |
| GridItems* grid_items, |
| NGGridBlockTrackCollection* column_track_collection, |
| NGGridBlockTrackCollection* row_track_collection, |
| NGGridPlacement* grid_placement) const { |
| DCHECK(grid_items); |
| DCHECK(column_track_collection); |
| DCHECK(row_track_collection); |
| DCHECK(grid_placement); |
| const ComputedStyle& grid_style = Style(); |
| |
| auto BuildBlockTrackCollection = |
| [&](NGGridBlockTrackCollection* track_collection) { |
| const GridTrackSizingDirection track_direction = |
| track_collection->Direction(); |
| const wtf_size_t start_offset = |
| grid_placement->StartOffset(track_direction); |
| |
| const NGGridTrackList& template_track_list = |
| (track_direction == kForColumns) |
| ? grid_style.GridTemplateColumns().NGTrackList() |
| : grid_style.GridTemplateRows().NGTrackList(); |
| const NGGridTrackList& auto_track_list = |
| (track_direction == kForColumns) |
| ? grid_style.GridAutoColumns().NGTrackList() |
| : grid_style.GridAutoRows().NGTrackList(); |
| |
| track_collection->SetSpecifiedTracks( |
| &template_track_list, &auto_track_list, start_offset, |
| grid_placement->AutoRepetitions(track_collection->Direction())); |
| EnsureTrackCoverageForGridItems(*grid_items, track_collection); |
| track_collection->FinalizeRanges(start_offset); |
| }; |
| |
| grid_placement->RunAutoPlacementAlgorithm(grid_items); |
| BuildBlockTrackCollection(column_track_collection); |
| BuildBlockTrackCollection(row_track_collection); |
| } |
| |
| void NGGridLayoutAlgorithm::BuildAlgorithmTrackCollections( |
| GridItems* grid_items, |
| NGGridLayoutAlgorithmTrackCollection* column_track_collection, |
| NGGridLayoutAlgorithmTrackCollection* row_track_collection, |
| NGGridPlacement* grid_placement) const { |
| DCHECK(grid_items); |
| DCHECK(column_track_collection); |
| DCHECK(row_track_collection); |
| DCHECK(grid_placement); |
| |
| // Build block track collections. |
| NGGridBlockTrackCollection column_block_track_collection(kForColumns); |
| NGGridBlockTrackCollection row_block_track_collection(kForRows); |
| BuildBlockTrackCollections(grid_items, &column_block_track_collection, |
| &row_block_track_collection, grid_placement); |
| |
| // Build algorithm track collections from the block track collections. |
| *column_track_collection = NGGridLayoutAlgorithmTrackCollection( |
| column_block_track_collection, |
| grid_available_size_.inline_size == kIndefiniteSize); |
| |
| *row_track_collection = NGGridLayoutAlgorithmTrackCollection( |
| row_block_track_collection, |
| grid_available_size_.block_size == kIndefiniteSize); |
| } |
| |
| void NGGridLayoutAlgorithm::EnsureTrackCoverageForGridItems( |
| const GridItems& grid_items, |
| NGGridBlockTrackCollection* track_collection) const { |
| DCHECK(track_collection); |
| const GridTrackSizingDirection track_direction = |
| track_collection->Direction(); |
| for (const auto& grid_item : grid_items.item_data) { |
| track_collection->EnsureTrackCoverage(grid_item.StartLine(track_direction), |
| grid_item.SpanSize(track_direction)); |
| } |
| } |
| |
| void NGGridLayoutAlgorithm::CacheGridItemsTrackSpanProperties( |
| const NGGridLayoutAlgorithmTrackCollection& track_collection, |
| GridItems* grid_items) const { |
| DCHECK(grid_items); |
| const GridTrackSizingDirection track_direction = track_collection.Direction(); |
| |
| auto CompareGridItemsByStartLine = [grid_items, track_direction]( |
| wtf_size_t a, wtf_size_t b) -> bool { |
| return grid_items->item_data[a].StartLine(track_direction) < |
| grid_items->item_data[b].StartLine(track_direction); |
| }; |
| std::sort(grid_items->reordered_item_indices.begin(), |
| grid_items->reordered_item_indices.end(), |
| CompareGridItemsByStartLine); |
| |
| auto CacheTrackSpanPropertyForAllGridItems = |
| [&](TrackSpanProperties::PropertyId property) { |
| // At this point we have the grid items sorted by their start line in |
| // the respective direction; this is important since we'll process both, |
| // the ranges in the track collection and the grid items, incrementally. |
| auto range_iterator = track_collection.RangeIterator(); |
| |
| for (auto& grid_item : *grid_items) { |
| // We want to find the first range in the collection that: |
| // - Spans tracks located AFTER the start line of the current grid |
| // item; this can be done by checking that the last track number of |
| // the current range is NOT less than the current grid item's start |
| // line. Furthermore, since grid items are sorted by start line, if |
| // at any point a range is located BEFORE the current grid item's |
| // start line, the same range will also be located BEFORE any |
| // subsequent item's start line. |
| // - Contains a track that fulfills the specified property. |
| while (!range_iterator.IsAtEnd() && |
| (range_iterator.RangeTrackEnd() < |
| grid_item.StartLine(track_direction) || |
| !track_collection.RangeHasTrackSpanProperty( |
| range_iterator.RangeIndex(), property))) { |
| range_iterator.MoveToNextRange(); |
| } |
| |
| // Since we discarded every range in the track collection, any |
| // following grid item cannot fulfill the property. |
| if (range_iterator.IsAtEnd()) |
| break; |
| |
| // Notice that, from the way we build the ranges of a track collection |
| // (see |NGGridBlockTrackCollection::EnsureTrackCoverage|), any given |
| // range must either be completely contained or excluded from a grid |
| // item's span. Thus, if the current range's last track is also |
| // located BEFORE the item's end line, then this range, including a |
| // track that fulfills the specified property, is completely contained |
| // within this item's boundaries. Otherwise, this and every subsequent |
| // range are excluded from the grid item's span, meaning that such |
| // item cannot satisfy the property we are looking for. |
| if (range_iterator.RangeTrackEnd() < |
| grid_item.EndLine(track_direction)) { |
| grid_item.SetTrackSpanProperty(property, track_direction); |
| } |
| } |
| }; |
| |
| CacheTrackSpanPropertyForAllGridItems(TrackSpanProperties::kHasFlexibleTrack); |
| CacheTrackSpanPropertyForAllGridItems( |
| TrackSpanProperties::kHasIntrinsicTrack); |
| } |
| |
| // https://drafts.csswg.org/css-grid-2/#algo-init |
| NGGridLayoutAlgorithm::SetGeometry NGGridLayoutAlgorithm::InitializeTrackSizes( |
| NGGridLayoutAlgorithmTrackCollection* track_collection) const { |
| DCHECK(track_collection); |
| const GridTrackSizingDirection track_direction = |
| track_collection->Direction(); |
| LayoutUnit available_size = (track_direction == kForColumns) |
| ? grid_available_size_.inline_size |
| : grid_available_size_.block_size; |
| |
| LayoutUnit set_offset = (track_direction == kForColumns) |
| ? BorderScrollbarPadding().inline_start |
| : BorderScrollbarPadding().block_start; |
| wtf_size_t last_indefinite_index = kNotFound; |
| wtf_size_t index = 0u; |
| Vector<SetOffsetData> sets; |
| sets.ReserveInitialCapacity(track_collection->SetCount() + 1); |
| sets.emplace_back(set_offset, last_indefinite_index); |
| |
| const LayoutUnit grid_gap = GridGap(track_direction); |
| |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| auto& current_set = set_iterator.CurrentSet(); |
| const GridTrackSize& track_size = current_set.TrackSize(); |
| |
| if (track_size.IsFitContent()) { |
| // Indefinite lengths cannot occur, as they must be normalized to 'auto'. |
| DCHECK(!track_size.FitContentTrackBreadth().HasPercentage() || |
| available_size != kIndefiniteSize); |
| current_set.SetFitContentLimit(MinimumValueForLength( |
| track_size.FitContentTrackBreadth().length(), available_size)); |
| } |
| |
| if (track_size.HasFixedMinTrackBreadth()) { |
| DCHECK(!track_size.MinTrackBreadth().HasPercentage() || |
| available_size != kIndefiniteSize); |
| |
| // A fixed sizing function: Resolve to an absolute length and use that |
| // size as the track’s initial base size. |
| LayoutUnit fixed_min_breadth = MinimumValueForLength( |
| track_size.MinTrackBreadth().length(), available_size); |
| current_set.SetBaseSize(fixed_min_breadth * current_set.TrackCount()); |
| } else { |
| // An intrinsic sizing function: Use an initial base size of zero. |
| DCHECK(track_size.HasIntrinsicMinTrackBreadth()); |
| current_set.SetBaseSize(LayoutUnit()); |
| } |
| |
| // Note that, since |NGGridSet| initializes its growth limit as indefinite, |
| // an intrinsic or flexible sizing function needs no further resolution. |
| if (track_size.HasFixedMaxTrackBreadth()) { |
| DCHECK(!track_size.MaxTrackBreadth().HasPercentage() || |
| available_size != kIndefiniteSize); |
| |
| // A fixed sizing function: Resolve to an absolute length and use that |
| // size as the track’s initial growth limit; if the growth limit is less |
| // than the base size, increase the growth limit to match the base size. |
| LayoutUnit fixed_max_breadth = MinimumValueForLength( |
| track_size.MaxTrackBreadth().length(), available_size); |
| current_set.SetGrowthLimit( |
| std::max(current_set.BaseSize(), |
| fixed_max_breadth * current_set.TrackCount())); |
| } |
| |
| DCHECK_NE(track_size.GetType(), kLengthTrackSizing); |
| |
| // TODO(ikilpatrick): If all of are our row tracks are "inflexible" (they |
| // all have fixed min/max track breadths which are the same), we need to |
| // also apply 'align-content' upfront to ensure that orthogonal children |
| // have the correct available-size given. |
| |
| // For the purposes of our "base" row set geometry, we only use any fixed |
| // max-track breadth. We use this for sizing any orthogonal, (or |
| // %-block-size) children. |
| if (track_direction == kForRows && track_size.HasFixedMaxTrackBreadth()) { |
| set_offset += |
| current_set.GrowthLimit() + current_set.TrackCount() * grid_gap; |
| } else { |
| last_indefinite_index = index; |
| } |
| |
| sets.emplace_back(set_offset, last_indefinite_index); |
| ++index; |
| } |
| |
| return {sets, grid_gap}; |
| } |
| |
| // https://drafts.csswg.org/css-grid-2/#algo-track-sizing |
| void NGGridLayoutAlgorithm::ComputeUsedTrackSizes( |
| SizingConstraint sizing_constraint, |
| const GridGeometry& grid_geometry, |
| NGGridLayoutAlgorithmTrackCollection* track_collection, |
| GridItems* grid_items) const { |
| DCHECK(track_collection); |
| DCHECK(grid_items); |
| |
| // 2. Resolve intrinsic track sizing functions to absolute lengths. |
| ResolveIntrinsicTrackSizes(grid_geometry, track_collection, grid_items); |
| |
| // 3. If the free space is positive, distribute it equally to the base sizes |
| // of all tracks, freezing tracks as they reach their growth limits (and |
| // continuing to grow the unfrozen tracks as needed). |
| MaximizeTracks(sizing_constraint, track_collection); |
| |
| // TODO(janewman): 4. Expand Flexible Tracks |
| |
| // 5. Stretch 'auto' Tracks |
| StretchAutoTracks(sizing_constraint, track_collection); |
| } |
| |
| // Helpers for the track sizing algorithm. |
| namespace { |
| |
| using GridItemContributionType = |
| NGGridLayoutAlgorithm::GridItemContributionType; |
| using GridSetVector = Vector<NGGridSet*, 16>; |
| |
| LayoutUnit DefiniteGrowthLimit(const NGGridSet& set) { |
| LayoutUnit growth_limit = set.GrowthLimit(); |
| // For infinite growth limits, substitute the track’s base size. |
| return (growth_limit == kIndefiniteSize) ? set.BaseSize() : growth_limit; |
| } |
| |
| // Returns the corresponding size to be increased by accommodating a grid item's |
| // contribution; for intrinsic min track sizing functions, return the base size. |
| // For intrinsic max track sizing functions, return the growth limit. |
| LayoutUnit AffectedSizeForContribution( |
| const NGGridSet& set, |
| GridItemContributionType contribution_type) { |
| switch (contribution_type) { |
| case GridItemContributionType::kForIntrinsicMinimums: |
| case GridItemContributionType::kForContentBasedMinimums: |
| case GridItemContributionType::kForMaxContentMinimums: |
| return set.BaseSize(); |
| case GridItemContributionType::kForIntrinsicMaximums: |
| case GridItemContributionType::kForMaxContentMaximums: |
| return DefiniteGrowthLimit(set); |
| case GridItemContributionType::kForFreeSpace: |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| } |
| |
| void GrowAffectedSizeByPlannedIncrease( |
| NGGridSet& set, |
| GridItemContributionType contribution_type) { |
| bool did_growth_limit_become_finite = false; |
| switch (contribution_type) { |
| case GridItemContributionType::kForIntrinsicMinimums: |
| case GridItemContributionType::kForContentBasedMinimums: |
| case GridItemContributionType::kForMaxContentMinimums: |
| set.SetBaseSize(set.BaseSize() + set.PlannedIncrease()); |
| break; |
| case GridItemContributionType::kForIntrinsicMaximums: |
| case GridItemContributionType::kForMaxContentMaximums: |
| did_growth_limit_become_finite = (set.GrowthLimit() == kIndefiniteSize); |
| set.SetGrowthLimit(DefiniteGrowthLimit(set) + set.PlannedIncrease()); |
| break; |
| case GridItemContributionType::kForFreeSpace: |
| NOTREACHED(); |
| break; |
| } |
| |
| // Mark any tracks whose growth limit changed from infinite to finite in this |
| // step as infinitely growable for the next step. |
| if (contribution_type == GridItemContributionType::kForIntrinsicMaximums) |
| set.SetInfinitelyGrowable(did_growth_limit_become_finite); |
| else |
| set.SetInfinitelyGrowable(false); |
| } |
| |
| // Returns true if a set should increase its used size according to the steps in |
| // https://drafts.csswg.org/css-grid-2/#algo-spanning-items; false otherwise. |
| bool IsContributionAppliedToSet(const NGGridSet& set, |
| GridItemContributionType contribution_type) { |
| switch (contribution_type) { |
| case GridItemContributionType::kForIntrinsicMinimums: |
| return set.TrackSize().HasIntrinsicMinTrackBreadth(); |
| case GridItemContributionType::kForContentBasedMinimums: |
| return set.TrackSize().HasMinOrMaxContentMinTrackBreadth(); |
| case GridItemContributionType::kForMaxContentMinimums: |
| // TODO(ethavar): Check if the grid container is being sized under a |
| // 'max-content' constraint to consider 'auto' min track sizing functions, |
| // see https://drafts.csswg.org/css-grid-2/#track-size-max-content-min. |
| return set.TrackSize().HasMaxContentMinTrackBreadth(); |
| case GridItemContributionType::kForIntrinsicMaximums: |
| return set.TrackSize().HasIntrinsicMaxTrackBreadth(); |
| case GridItemContributionType::kForMaxContentMaximums: |
| return set.TrackSize().HasMaxContentOrAutoMaxTrackBreadth(); |
| case GridItemContributionType::kForFreeSpace: |
| return true; |
| } |
| } |
| |
| // https://drafts.csswg.org/css-grid-2/#extra-space |
| // Returns true if a set's used size should be consider to grow beyond its limit |
| // (see the "Distribute space beyond limits" section); otherwise, false. |
| // Note that we will deliberately return false in cases where we don't have a |
| // collection of tracks different than "all affected tracks". |
| bool ShouldUsedSizeGrowBeyondLimit(const NGGridSet& set, |
| GridItemContributionType contribution_type) { |
| switch (contribution_type) { |
| case GridItemContributionType::kForIntrinsicMinimums: |
| case GridItemContributionType::kForContentBasedMinimums: |
| return set.TrackSize().HasIntrinsicMaxTrackBreadth(); |
| case GridItemContributionType::kForMaxContentMinimums: |
| return set.TrackSize().HasMaxContentOrAutoMaxTrackBreadth(); |
| case GridItemContributionType::kForIntrinsicMaximums: |
| case GridItemContributionType::kForMaxContentMaximums: |
| case GridItemContributionType::kForFreeSpace: |
| return false; |
| } |
| } |
| |
| bool IsDistributionForGrowthLimits(GridItemContributionType contribution_type) { |
| switch (contribution_type) { |
| case GridItemContributionType::kForIntrinsicMinimums: |
| case GridItemContributionType::kForContentBasedMinimums: |
| case GridItemContributionType::kForMaxContentMinimums: |
| case GridItemContributionType::kForFreeSpace: |
| return false; |
| case GridItemContributionType::kForIntrinsicMaximums: |
| case GridItemContributionType::kForMaxContentMaximums: |
| return true; |
| } |
| } |
| |
| enum class InfinitelyGrowableBehavior { kEnforce, kIgnore }; |
| |
| // We define growth potential = limit - affected size; for base sizes, the limit |
| // is its growth limit. For growth limits, the limit is infinity if it is marked |
| // as "infinitely growable", and equal to the growth limit otherwise. |
| LayoutUnit GrowthPotentialForSet( |
| const NGGridSet& set, |
| GridItemContributionType contribution_type, |
| InfinitelyGrowableBehavior infinitely_growable_behavior = |
| InfinitelyGrowableBehavior::kEnforce) { |
| switch (contribution_type) { |
| case GridItemContributionType::kForIntrinsicMinimums: |
| case GridItemContributionType::kForContentBasedMinimums: |
| case GridItemContributionType::kForMaxContentMinimums: { |
| LayoutUnit growth_limit = set.GrowthLimit(); |
| return (growth_limit == kIndefiniteSize) ? kIndefiniteSize |
| : growth_limit - set.BaseSize(); |
| } |
| case GridItemContributionType::kForIntrinsicMaximums: |
| case GridItemContributionType::kForMaxContentMaximums: { |
| if (infinitely_growable_behavior == |
| InfinitelyGrowableBehavior::kEnforce && |
| !set.IsInfinitelyGrowable()) { |
| // If the affected size was a growth limit and the track is not marked |
| // infinitely growable, then the item-incurred increase will be zero. |
| return LayoutUnit(); |
| } |
| |
| LayoutUnit fit_content_limit = set.FitContentLimit(); |
| DCHECK(fit_content_limit >= 0 || fit_content_limit == kIndefiniteSize); |
| |
| // The max track sizing function of a 'fit-content' track is treated as |
| // 'max-content' until it reaches the limit specified as the 'fit-content' |
| // argument, after which it is treated as having a fixed sizing function |
| // of that argument (with a growth potential of zero). |
| if (fit_content_limit != kIndefiniteSize) { |
| LayoutUnit growth_potential = |
| fit_content_limit - DefiniteGrowthLimit(set); |
| return growth_potential.ClampNegativeToZero(); |
| } |
| // Otherwise, this set has infinite growth potential. |
| return kIndefiniteSize; |
| } |
| case GridItemContributionType::kForFreeSpace: { |
| LayoutUnit growth_limit = set.GrowthLimit(); |
| DCHECK_NE(growth_limit, kIndefiniteSize); |
| return growth_limit - set.BaseSize(); |
| } |
| } |
| } |
| |
| // Follow the definitions from https://drafts.csswg.org/css-grid-2/#extra-space; |
| // notice that this method replaces the notion of "tracks" with "sets". |
| void DistributeExtraSpaceToSets( |
| LayoutUnit extra_space, |
| bool is_equal_distribution, |
| GridItemContributionType contribution_type, |
| GridSetVector* sets_to_grow, |
| GridSetVector* sets_to_grow_beyond_limit = nullptr) { |
| DCHECK(extra_space && sets_to_grow); |
| |
| if (extra_space == kIndefiniteSize) { |
| // Infinite extra space should only happen when distributing free space at |
| // the maximize tracks step; in such case, we can simplify this method by |
| // "filling" every track base size up to their growth limit. |
| DCHECK_EQ(contribution_type, GridItemContributionType::kForFreeSpace); |
| for (NGGridSet* set : *sets_to_grow) { |
| set->SetItemIncurredIncrease( |
| GrowthPotentialForSet(*set, contribution_type)); |
| } |
| return; |
| } |
| |
| DCHECK_GT(extra_space, 0); |
| #if DCHECK_IS_ON() |
| if (IsDistributionForGrowthLimits(contribution_type)) |
| DCHECK_EQ(sets_to_grow, sets_to_grow_beyond_limit); |
| #endif |
| |
| auto ShareRatio = |
| [&is_equal_distribution](const NGGridSet& set) -> wtf_size_t { |
| if (is_equal_distribution) |
| return set.TrackCount(); |
| |
| // From https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items: |
| // If the sum of the flexible sizing functions of all flexible tracks |
| // spanned by the item is greater than zero, distributing space to such |
| // tracks according to the ratios of their flexible sizing functions |
| // rather than distributing space equally. |
| // |
| // Since we will use the flex factor to compute proportions out of a |
| // |LayoutUnit|, let the class round the floating point value. |
| DCHECK(set.TrackSize().HasFlexMaxTrackBreadth()); |
| LayoutUnit flex_factor = LayoutUnit::FromDoubleRound( |
| set.TrackSize().MaxTrackBreadth().Flex() * set.TrackCount()); |
| return flex_factor.RawValue(); |
| }; |
| |
| wtf_size_t share_ratio_sum = 0; |
| wtf_size_t growable_track_count = 0; |
| for (NGGridSet* set : *sets_to_grow) { |
| set->SetItemIncurredIncrease(LayoutUnit()); |
| share_ratio_sum += ShareRatio(*set); |
| |
| // From the first note in https://drafts.csswg.org/css-grid-2/#extra-space: |
| // If the affected size was a growth limit and the track is not marked |
| // "infinitely growable", then each item-incurred increase will be zero. |
| // |
| // When distributing space to growth limits, we need to increase each track |
| // up to its 'fit-content' limit. However, because of the note above, first |
| // we should only grow tracks marked as "infinitely growable" up to limits |
| // and then grow all affected tracks beyond limits. |
| // |
| // We can correctly resolve every scenario by doing a single sort of |
| // |sets_to_grow|, purposely ignoring the "infinitely growable" flag, then |
| // filtering out sets that won't take a share of the extra space at each |
| // step; for base sizes this is not required, but if there are no tracks |
| // with growth potential > 0, we can optimize by not sorting the sets. |
| if (GrowthPotentialForSet(*set, contribution_type)) |
| growable_track_count += set->TrackCount(); |
| } |
| |
| // If the sum of share ratios is zero, default to distribute equally. |
| if (!share_ratio_sum) |
| is_equal_distribution = true; |
| |
| if (is_equal_distribution) { |
| // Distribute space equally among tracks with growth potential > 0. |
| share_ratio_sum = growable_track_count; |
| } else { |
| // Otherwise, distribute space according to their flex factors; compute |
| // |share_ratio_sum| now filtering out sets with no growth potential. |
| share_ratio_sum = 0; |
| for (NGGridSet* set : *sets_to_grow) { |
| if (GrowthPotentialForSet(*set, contribution_type)) |
| share_ratio_sum += ShareRatio(*set); |
| } |
| } |
| |
| // We will sort the tracks by growth potential in non-decreasing order to |
| // distribute space up to limits; notice that if we start distributing space |
| // equally among all tracks we will eventually reach the limit of a track or |
| // run out of space to distribute. If the former scenario happens, it should |
| // be easy to see that the group of tracks that will reach its limit first |
| // will be that with the least growth potential. Otherwise, if tracks in such |
| // group does not reach their limit, every upcoming track with greater growth |
| // potential must be able to increase its size by the same amount. |
| if (growable_track_count || |
| IsDistributionForGrowthLimits(contribution_type)) { |
| auto CompareSetsByGrowthPotential = [contribution_type](NGGridSet* set_a, |
| NGGridSet* set_b) { |
| LayoutUnit growth_potential_a = GrowthPotentialForSet( |
| *set_a, contribution_type, InfinitelyGrowableBehavior::kIgnore); |
| LayoutUnit growth_potential_b = GrowthPotentialForSet( |
| *set_b, contribution_type, InfinitelyGrowableBehavior::kIgnore); |
| |
| if (growth_potential_a == kIndefiniteSize || |
| growth_potential_b == kIndefiniteSize) { |
| // At this point we know that there is at least one set with infinite |
| // growth potential; if |set_a| has a definite value, then |set_b| must |
| // have infinite growth potential, and thus, |set_a| < |set_b|. |
| return growth_potential_a != kIndefiniteSize; |
| } |
| // Straightforward comparison of definite growth potentials. |
| return growth_potential_a < growth_potential_b; |
| }; |
| std::sort(sets_to_grow->begin(), sets_to_grow->end(), |
| CompareSetsByGrowthPotential); |
| } |
| |
| auto ExtraSpaceShare = [&extra_space, &ShareRatio, &share_ratio_sum]( |
| const NGGridSet& set, |
| LayoutUnit growth_potential) -> LayoutUnit { |
| DCHECK(growth_potential >= 0 || growth_potential == kIndefiniteSize); |
| |
| // If this set won't take a share of the extra space, e.g. it has zero |
| // growth potential or the remaining share ratio sum is zero, exit early so |
| // that this set's share ratio is filtered out of |share_ratio_sum|. |
| if (!share_ratio_sum || !growth_potential) |
| return LayoutUnit(); |
| |
| wtf_size_t set_share_ratio = ShareRatio(set); |
| DCHECK_LE(set_share_ratio, share_ratio_sum); |
| |
| LayoutUnit extra_space_share = |
| (extra_space * set_share_ratio) / share_ratio_sum; |
| if (growth_potential != kIndefiniteSize) |
| extra_space_share = std::min(extra_space_share, growth_potential); |
| DCHECK_LE(extra_space_share, extra_space); |
| |
| share_ratio_sum -= set_share_ratio; |
| extra_space -= extra_space_share; |
| return extra_space_share; |
| }; |
| |
| // Distribute space up to limits: |
| // - For base sizes, grow the base size up to the growth limit. |
| // - For growth limits, the only case where a growth limit should grow at |
| // this step is when its set has already been marked "infinitely growable". |
| // Increase the growth limit up to the 'fit-content' argument (if any); note |
| // that these arguments could prevent this step to fulfill the entirety of |
| // the extra space and further distribution would be needed. |
| for (NGGridSet* set : *sets_to_grow) { |
| set->SetItemIncurredIncrease( |
| ExtraSpaceShare(*set, GrowthPotentialForSet(*set, contribution_type))); |
| } |
| |
| // Distribute space beyond limits: |
| // - For base sizes, every affected track can grow indefinitely. |
| // - For growth limits, grow tracks up to their 'fit-content' argument. |
| if (sets_to_grow_beyond_limit && extra_space) { |
| #if DCHECK_IS_ON() |
| // We expect |sets_to_grow_beyond_limit| to be ordered by growth potential |
| // for the following section of the algorithm to work. |
| // |
| // For base sizes, since going beyond limits should only happen after we |
| // grow every track up to their growth limits, it should be easy to see that |
| // every growth potential is now zero, so they're already ordered. |
| // |
| // Now let's consider growth limits: we forced the sets to be sorted by |
| // growth potential ignoring the "infinitely growable" flag, meaning that |
| // ultimately they will be sorted by remaining space to their 'fit-content' |
| // parameter (if it exists, infinite otherwise). If we ended up here, we |
| // must have filled the sets marked as "infinitely growable" up to their |
| // 'fit-content' parameter; therefore, if we only consider sets with |
| // remaining space to their 'fit-content' limit in the following |
| // distribution step, they should still be ordered. |
| LayoutUnit previous_growable_potential; |
| for (NGGridSet* set : *sets_to_grow_beyond_limit) { |
| LayoutUnit growth_potential = GrowthPotentialForSet( |
| *set, contribution_type, InfinitelyGrowableBehavior::kIgnore); |
| if (growth_potential) { |
| if (previous_growable_potential == kIndefiniteSize) { |
| DCHECK_EQ(growth_potential, kIndefiniteSize); |
| } else { |
| DCHECK(growth_potential >= previous_growable_potential || |
| growth_potential == kIndefiniteSize); |
| } |
| previous_growable_potential = growth_potential; |
| } |
| } |
| #endif |
| |
| auto BeyondLimitsGrowthPotential = |
| [contribution_type](const NGGridSet& set) -> LayoutUnit { |
| // For growth limits, ignore the "infinitely growable" flag and grow all |
| // affected tracks up to their 'fit-content' argument (note that |
| // |GrowthPotentialForSet| already accounts for it). |
| return !IsDistributionForGrowthLimits(contribution_type) |
| ? kIndefiniteSize |
| : GrowthPotentialForSet(set, contribution_type, |
| InfinitelyGrowableBehavior::kIgnore); |
| }; |
| |
| share_ratio_sum = 0; |
| for (NGGridSet* set : *sets_to_grow_beyond_limit) { |
| if (BeyondLimitsGrowthPotential(*set)) |
| share_ratio_sum += ShareRatio(*set); |
| } |
| for (NGGridSet* set : *sets_to_grow_beyond_limit) { |
| set->SetItemIncurredIncrease( |
| set->ItemIncurredIncrease() + |
| ExtraSpaceShare(*set, BeyondLimitsGrowthPotential(*set))); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| void NGGridLayoutAlgorithm::IncreaseTrackSizesToAccommodateGridItems( |
| const GridGeometry& grid_geometry, |
| GridItems::Iterator group_begin, |
| GridItems::Iterator group_end, |
| const bool is_group_spanning_flex_track, |
| GridItemContributionType contribution_type, |
| NGGridLayoutAlgorithmTrackCollection* track_collection) const { |
| DCHECK(track_collection); |
| const GridTrackSizingDirection track_direction = |
| track_collection->Direction(); |
| |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| set_iterator.CurrentSet().SetPlannedIncrease(LayoutUnit()); |
| } |
| |
| GridSetVector sets_to_grow; |
| GridSetVector sets_to_grow_beyond_limit; |
| for (auto grid_item = group_begin; grid_item != group_end; ++grid_item) { |
| // When the grid items of this group are not spanning a flexible track, we |
| // can skip the current item if it doesn't span an intrinsic track. |
| if (!grid_item->IsSpanningIntrinsicTrack(track_direction) && |
| !is_group_spanning_flex_track) { |
| continue; |
| } |
| |
| sets_to_grow.Shrink(0); |
| sets_to_grow_beyond_limit.Shrink(0); |
| |
| // TODO(ansollan): If the grid is auto-sized and has a calc or percent row |
| // gap, then the gap can't be calculated on the first pass as we wouldn't |
| // know our block size. |
| LayoutUnit spanned_tracks_size = |
| GridGap(track_direction) * (grid_item->SpanSize(track_direction) - 1); |
| |
| for (auto set_iterator = |
| GetSetIteratorForItem(*grid_item, *track_collection); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| NGGridSet& current_set = set_iterator.CurrentSet(); |
| spanned_tracks_size += |
| AffectedSizeForContribution(current_set, contribution_type); |
| |
| // From https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items: |
| // Distributing space only to flexible tracks (i.e. treating all other |
| // tracks as having a fixed sizing function). |
| if (is_group_spanning_flex_track && |
| !current_set.TrackSize().HasFlexMaxTrackBreadth()) { |
| continue; |
| } |
| |
| if (IsContributionAppliedToSet(current_set, contribution_type)) { |
| sets_to_grow.push_back(¤t_set); |
| if (ShouldUsedSizeGrowBeyondLimit(current_set, contribution_type)) |
| sets_to_grow_beyond_limit.push_back(¤t_set); |
| } |
| } |
| |
| if (sets_to_grow.IsEmpty()) |
| continue; |
| |
| // Subtract the corresponding size (base size or growth limit) of every |
| // spanned track from the grid item's size contribution to find the item's |
| // remaining size contribution. For infinite growth limits, substitute with |
| // the track's base size. This is the space to distribute, floor it at zero. |
| LayoutUnit extra_space = ContributionSizeForGridItem( |
| grid_geometry, *grid_item, track_direction, contribution_type); |
| extra_space -= spanned_tracks_size; |
| |
| if (extra_space <= 0) |
| continue; |
| |
| DistributeExtraSpaceToSets( |
| extra_space.ClampNegativeToZero(), |
| /* is_equal_distribution = */ !is_group_spanning_flex_track, |
| contribution_type, &sets_to_grow, |
| sets_to_grow_beyond_limit.IsEmpty() ? &sets_to_grow |
| : &sets_to_grow_beyond_limit); |
| |
| // For each affected track, if the track's item-incurred increase is larger |
| // than its planned increase, set the planned increase to that value. |
| for (NGGridSet* set : sets_to_grow) { |
| set->SetPlannedIncrease( |
| std::max(set->ItemIncurredIncrease(), set->PlannedIncrease())); |
| } |
| } |
| |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| GrowAffectedSizeByPlannedIncrease(set_iterator.CurrentSet(), |
| contribution_type); |
| } |
| } |
| |
| // https://drafts.csswg.org/css-grid-2/#algo-content |
| void NGGridLayoutAlgorithm::ResolveIntrinsicTrackSizes( |
| const GridGeometry& grid_geometry, |
| NGGridLayoutAlgorithmTrackCollection* track_collection, |
| GridItems* grid_items) const { |
| DCHECK(track_collection && grid_items); |
| const GridTrackSizingDirection track_direction = |
| track_collection->Direction(); |
| |
| // Reorder grid items to process them as follows: |
| // - First, consider items spanning a single non-flexible track. |
| // - Next, consider items with span size of 2 not spanning a flexible track. |
| // - Repeat incrementally for items with greater span sizes until all items |
| // not spanning a flexible track have been considered. |
| // - Finally, consider all items spanning a flexible track. |
| auto CompareGridItemsForIntrinsicTrackResolution = |
| [grid_items, track_direction](wtf_size_t a, wtf_size_t b) -> bool { |
| if (grid_items->item_data[a].IsSpanningFlexibleTrack(track_direction) || |
| grid_items->item_data[b].IsSpanningFlexibleTrack(track_direction)) { |
| // Ignore span sizes if one of the items spans a track with a flexible |
| // sizing function; items not spanning such tracks should come first. |
| return !grid_items->item_data[a].IsSpanningFlexibleTrack(track_direction); |
| } |
| return grid_items->item_data[a].SpanSize(track_direction) < |
| grid_items->item_data[b].SpanSize(track_direction); |
| }; |
| std::sort(grid_items->reordered_item_indices.begin(), |
| grid_items->reordered_item_indices.end(), |
| CompareGridItemsForIntrinsicTrackResolution); |
| |
| // First, process the items that don't span a flexible track. |
| auto current_group_begin = grid_items->begin(); |
| while (current_group_begin != grid_items->end() && |
| !current_group_begin->IsSpanningFlexibleTrack(track_direction)) { |
| // Each iteration considers all items with the same span size. |
| wtf_size_t current_group_span_size = |
| current_group_begin->SpanSize(track_direction); |
| |
| auto current_group_end = current_group_begin; |
| do { |
| DCHECK(!current_group_end->IsSpanningFlexibleTrack(track_direction)); |
| ++current_group_end; |
| } while (current_group_end != grid_items->end() && |
| !current_group_end->IsSpanningFlexibleTrack(track_direction) && |
| current_group_end->SpanSize(track_direction) == |
| current_group_span_size); |
| |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, current_group_end, |
| /* is_group_spanning_flex_track */ false, |
| GridItemContributionType::kForIntrinsicMinimums, track_collection); |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, current_group_end, |
| /* is_group_spanning_flex_track */ false, |
| GridItemContributionType::kForContentBasedMinimums, track_collection); |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, current_group_end, |
| /* is_group_spanning_flex_track */ false, |
| GridItemContributionType::kForMaxContentMinimums, track_collection); |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, current_group_end, |
| /* is_group_spanning_flex_track */ false, |
| GridItemContributionType::kForIntrinsicMaximums, track_collection); |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, current_group_end, |
| /* is_group_spanning_flex_track */ false, |
| GridItemContributionType::kForMaxContentMaximums, track_collection); |
| |
| // Move to the next group with greater span size. |
| current_group_begin = current_group_end; |
| } |
| |
| // From https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items: |
| // Increase sizes to accommodate spanning items crossing flexible tracks: |
| // Next, repeat the previous step instead considering (together, rather than |
| // grouped by span size) all items that do span a track with a flexible |
| // sizing function... |
| #if DCHECK_IS_ON() |
| // Every grid item of the remaining group should span a flexible track. |
| for (auto it = current_group_begin; it != grid_items->end(); ++it) |
| DCHECK(it->IsSpanningFlexibleTrack(track_direction)); |
| #endif |
| |
| // Now, process items spanning flexible tracks (if any). |
| if (current_group_begin != grid_items->end()) { |
| // We can safely skip contributions for maximums since a <flex> definition |
| // does not have an intrinsic max track sizing function. |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, grid_items->end(), |
| /* is_group_spanning_flex_track */ true, |
| GridItemContributionType::kForIntrinsicMinimums, track_collection); |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, grid_items->end(), |
| /* is_group_spanning_flex_track */ true, |
| GridItemContributionType::kForContentBasedMinimums, track_collection); |
| IncreaseTrackSizesToAccommodateGridItems( |
| grid_geometry, current_group_begin, grid_items->end(), |
| /* is_group_spanning_flex_track */ true, |
| GridItemContributionType::kForMaxContentMinimums, track_collection); |
| } |
| |
| // If any track still has an infinite growth limit (i.e. it had no items |
| // placed in it), set its growth limit to its base size. |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| auto& set = set_iterator.CurrentSet(); |
| if (set.GrowthLimit() == kIndefiniteSize) |
| set.SetGrowthLimit(set.BaseSize()); |
| } |
| } |
| |
| // https://drafts.csswg.org/css-grid-2/#algo-grow-tracks |
| void NGGridLayoutAlgorithm::MaximizeTracks( |
| SizingConstraint sizing_constraint, |
| NGGridLayoutAlgorithmTrackCollection* track_collection) const { |
| const LayoutUnit free_space = |
| DetermineFreeSpace(sizing_constraint, *track_collection); |
| if (!free_space) |
| return; |
| |
| GridSetVector sets_to_grow; |
| sets_to_grow.ReserveInitialCapacity(track_collection->SetCount()); |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| sets_to_grow.push_back(&set_iterator.CurrentSet()); |
| } |
| |
| DistributeExtraSpaceToSets(free_space, /* is_equal_distribution */ true, |
| GridItemContributionType::kForFreeSpace, |
| &sets_to_grow); |
| |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| auto& set = set_iterator.CurrentSet(); |
| set.SetBaseSize(set.BaseSize() + set.ItemIncurredIncrease()); |
| } |
| |
| // TODO(ethavar): If this would cause the grid to be larger than the grid |
| // container’s inner size as limited by its 'max-width/height', then redo this |
| // step, treating the available grid space as equal to the grid container’s |
| // inner size when it’s sized to its 'max-width/height'. |
| } |
| |
| // https://drafts.csswg.org/css-grid-2/#algo-stretch |
| void NGGridLayoutAlgorithm::StretchAutoTracks( |
| SizingConstraint sizing_constraint, |
| NGGridLayoutAlgorithmTrackCollection* track_collection) const { |
| const GridTrackSizingDirection track_direction = |
| track_collection->Direction(); |
| |
| // Stretching auto tracks should only occur if we have a "stretch" (or |
| // default) content distribution. |
| const auto& content_alignment = (track_direction == kForColumns) |
| ? Style().JustifyContent() |
| : Style().AlignContent(); |
| bool has_stretch_distribution = |
| content_alignment.Distribution() == ContentDistributionType::kStretch || |
| (content_alignment.GetPosition() == ContentPosition::kNormal && |
| content_alignment.Distribution() == ContentDistributionType::kDefault); |
| if (!has_stretch_distribution) |
| return; |
| |
| LayoutUnit free_space = |
| DetermineFreeSpace(sizing_constraint, *track_collection); |
| |
| // If the free space is indefinite, but the grid container has a definite |
| // min-width/height, use that size to calculate the free space for this step |
| // instead. |
| if (free_space == kIndefiniteSize) { |
| free_space = (track_direction == kForColumns) |
| ? grid_min_available_size_.inline_size |
| : grid_min_available_size_.block_size; |
| DCHECK_NE(free_space, kIndefiniteSize); |
| free_space -= ComputeTotalTrackSize(*track_collection, |
| GridGap(track_direction, free_space)); |
| } |
| |
| if (free_space <= 0) |
| return; |
| |
| // Expand tracks that have an 'auto' max track sizing function by dividing any |
| // remaining positive, definite free space equally amongst them. |
| GridSetVector sets_to_grow; |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| auto& set = set_iterator.CurrentSet(); |
| if (set.TrackSize().HasAutoMaxTrackBreadth()) |
| sets_to_grow.push_back(&set); |
| } |
| |
| if (sets_to_grow.IsEmpty()) |
| return; |
| |
| DistributeExtraSpaceToSets(free_space, /* is_equal_distribution */ true, |
| GridItemContributionType::kForFreeSpace, |
| &sets_to_grow, &sets_to_grow); |
| |
| for (auto set_iterator = track_collection->GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| auto& set = set_iterator.CurrentSet(); |
| set.SetBaseSize(set.BaseSize() + set.ItemIncurredIncrease()); |
| } |
| } |
| |
| namespace { |
| |
| // Contains the information about where the grid tracks start, and the |
| // gutter-size between them, taking into account the content alignment |
| // properties. |
| struct TrackAlignmentGeometry { |
| LayoutUnit start_offset; |
| LayoutUnit gutter_size; |
| }; |
| |
| TrackAlignmentGeometry ComputeTrackAlignmentGeometry( |
| const ComputedStyle& style, |
| const StyleContentAlignmentData& content_alignment, |
| const NGGridLayoutAlgorithmTrackCollection& track_collection, |
| LayoutUnit available_size, |
| LayoutUnit start_border_scrollbar_padding, |
| LayoutUnit grid_gap) { |
| // Determining the free-space is typically unnecessary, i.e. if there is |
| // default alignment. Only compute this on-demand. |
| auto FreeSpace = [&track_collection, &available_size, |
| &grid_gap]() -> LayoutUnit { |
| return available_size - ComputeTotalTrackSize(track_collection, grid_gap); |
| }; |
| |
| // The default alignment, perform adjustments on top of this. |
| TrackAlignmentGeometry geometry = {start_border_scrollbar_padding, grid_gap}; |
| |
| // If we have an indefinite |available_size| we can't perform any alignment, |
| // just return the default alignment. |
| if (available_size == kIndefiniteSize) |
| return geometry; |
| |
| // TODO(ikilpatrick): 'space-between', 'space-around', and 'space-evenly' all |
| // divide by the free-space, and may have a non-zero modulo. Investigate if |
| // this should be distributed between the tracks. |
| switch (content_alignment.Distribution()) { |
| case ContentDistributionType::kSpaceBetween: { |
| // Default behavior for 'space-between' is to start align content. |
| const wtf_size_t track_count = track_collection.EndLineOfImplicitGrid(); |
| const LayoutUnit free_space = FreeSpace(); |
| if (track_count < 2 || free_space < LayoutUnit()) |
| return geometry; |
| |
| geometry.gutter_size += free_space / (track_count - 1); |
| return geometry; |
| } |
| case ContentDistributionType::kSpaceAround: { |
| // Default behaviour for 'space-around' is to center content. |
| const wtf_size_t track_count = track_collection.EndLineOfImplicitGrid(); |
| const LayoutUnit free_space = FreeSpace(); |
| if (track_count < 1 || free_space < LayoutUnit()) { |
| geometry.start_offset += free_space / 2; |
| return geometry; |
| } |
| |
| LayoutUnit track_space = free_space / track_count; |
| geometry.start_offset += track_space / 2; |
| geometry.gutter_size += track_space; |
| return geometry; |
| } |
| case ContentDistributionType::kSpaceEvenly: { |
| // Default behaviour for 'space-evenly' is to center content. |
| const wtf_size_t track_count = track_collection.EndLineOfImplicitGrid(); |
| const LayoutUnit free_space = FreeSpace(); |
| if (free_space < LayoutUnit()) { |
| geometry.start_offset += free_space / 2; |
| return geometry; |
| } |
| |
| LayoutUnit track_space = free_space / (track_count + 1); |
| geometry.start_offset += track_space; |
| geometry.gutter_size += track_space; |
| return geometry; |
| } |
| case ContentDistributionType::kStretch: |
| case ContentDistributionType::kDefault: |
| break; |
| } |
| |
| switch (content_alignment.GetPosition()) { |
| case ContentPosition::kLeft: { |
| DCHECK(track_collection.IsForColumns()); |
| if (IsLtr(style.Direction())) |
| return geometry; |
| |
| geometry.start_offset += FreeSpace(); |
| return geometry; |
| } |
| case ContentPosition::kRight: { |
| DCHECK(track_collection.IsForColumns()); |
| if (IsRtl(style.Direction())) |
| return geometry; |
| |
| geometry.start_offset += FreeSpace(); |
| return geometry; |
| break; |
| } |
| case ContentPosition::kCenter: { |
| geometry.start_offset += FreeSpace() / 2; |
| return geometry; |
| } |
| case ContentPosition::kEnd: |
| case ContentPosition::kFlexEnd: { |
| geometry.start_offset += FreeSpace(); |
| return geometry; |
| } |
| case ContentPosition::kStart: |
| case ContentPosition::kFlexStart: |
| case ContentPosition::kNormal: |
| case ContentPosition::kBaseline: |
| case ContentPosition::kLastBaseline: |
| return geometry; |
| } |
| } |
| |
| } // namespace |
| |
| // Calculates the offsets for all sets. |
| NGGridLayoutAlgorithm::SetGeometry NGGridLayoutAlgorithm::ComputeSetGeometry( |
| const NGGridLayoutAlgorithmTrackCollection& track_collection, |
| const LayoutUnit available_size) const { |
| const TrackAlignmentGeometry track_alignment_geometry = |
| track_collection.IsForColumns() |
| ? ComputeTrackAlignmentGeometry(Style(), Style().JustifyContent(), |
| track_collection, available_size, |
| BorderScrollbarPadding().inline_start, |
| GridGap(kForColumns, available_size)) |
| : ComputeTrackAlignmentGeometry(Style(), Style().AlignContent(), |
| track_collection, available_size, |
| BorderScrollbarPadding().block_start, |
| GridGap(kForRows, available_size)); |
| |
| LayoutUnit set_offset = track_alignment_geometry.start_offset; |
| Vector<SetOffsetData> sets; |
| sets.ReserveInitialCapacity(track_collection.SetCount() + 1); |
| sets.emplace_back(set_offset, /* last_indefinite_index */ kNotFound); |
| |
| for (auto set_iterator = track_collection.GetSetIterator(); |
| !set_iterator.IsAtEnd(); set_iterator.MoveToNextSet()) { |
| const auto& set = set_iterator.CurrentSet(); |
| set_offset += set.BaseSize() + |
| set.TrackCount() * track_alignment_geometry.gutter_size; |
| sets.emplace_back(set_offset, /* last_indefinite_index */ kNotFound); |
| } |
| return {sets, track_alignment_geometry.gutter_size}; |
| } |
| |
| LayoutUnit NGGridLayoutAlgorithm::GridGap( |
| GridTrackSizingDirection track_direction, |
| LayoutUnit available_size) const { |
| const base::Optional<Length>& gap = |
| track_direction == kForColumns ? Style().ColumnGap() : Style().RowGap(); |
| |
| if (!gap) |
| return LayoutUnit(); |
| |
| // TODO(ansollan): Update behavior based on outcome of working group |
| // discussions. See https://github.com/w3c/csswg-drafts/issues/5566. |
| if (available_size == kIndefiniteSize) |
| available_size = LayoutUnit(); |
| |
| return MinimumValueForLength(*gap, available_size); |
| } |
| |
| // TODO(ikilpatrick): Determine if other uses of this method need to respect |
| // |grid_min_available_size_| similar to |StretchAutoTracks|. |
| LayoutUnit NGGridLayoutAlgorithm::DetermineFreeSpace( |
| SizingConstraint sizing_constraint, |
| const NGGridLayoutAlgorithmTrackCollection& track_collection) const { |
| switch (sizing_constraint) { |
| case SizingConstraint::kLayout: { |
| const GridTrackSizingDirection track_direction = |
| track_collection.Direction(); |
| LayoutUnit free_space = (track_direction == kForColumns) |
| ? grid_available_size_.inline_size |
| : grid_available_size_.block_size; |
| if (free_space != kIndefiniteSize) { |
| free_space -= ComputeTotalTrackSize( |
| track_collection, GridGap(track_direction, free_space)); |
| // If tracks consume more space than the grid container has available, |
| // clamp the free space to zero as there's no more room left to grow. |
| free_space = free_space.ClampNegativeToZero(); |
| } |
| return free_space; |
| } |
| case SizingConstraint::kMaxContent: |
| // If sizing under a max-content constraint, the free space is infinite. |
| return kIndefiniteSize; |
| case SizingConstraint::kMinContent: |
| // If sizing under a min-content constraint, the free space is zero. |
| return LayoutUnit(); |
| } |
| } |
| |
| namespace { |
| |
| // Returns the alignment offset for either the inline or block direction. |
| LayoutUnit AlignmentOffset(LayoutUnit container_size, |
| LayoutUnit size, |
| LayoutUnit margin_start, |
| LayoutUnit margin_end, |
| AxisEdge axis_edge) { |
| switch (axis_edge) { |
| case AxisEdge::kStart: |
| return margin_start; |
| case AxisEdge::kCenter: |
| return (container_size - size + margin_start - margin_end) / 2; |
| case AxisEdge::kEnd: |
| return container_size - margin_end - size; |
| case AxisEdge::kBaseline: |
| // TODO(kschmi): Implement baseline alignment. |
| return margin_start; |
| } |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| void AlignmentOffsetForOutOfFlow( |
| const AxisEdge inline_axis_edge, |
| const AxisEdge block_axis_edge, |
| const LogicalSize container_size, |
| NGLogicalStaticPosition::InlineEdge* inline_edge, |
| NGLogicalStaticPosition::BlockEdge* block_edge, |
| LogicalOffset* offset) { |
| using InlineEdge = NGLogicalStaticPosition::InlineEdge; |
| using BlockEdge = NGLogicalStaticPosition::BlockEdge; |
| |
| switch (inline_axis_edge) { |
| case AxisEdge::kStart: |
| *inline_edge = InlineEdge::kInlineStart; |
| break; |
| case AxisEdge::kCenter: |
| *inline_edge = InlineEdge::kInlineCenter; |
| offset->inline_offset += container_size.inline_size / 2; |
| break; |
| default: |
| *inline_edge = InlineEdge::kInlineEnd; |
| offset->inline_offset += container_size.inline_size; |
| break; |
| } |
| |
| switch (block_axis_edge) { |
| case AxisEdge::kStart: |
| *block_edge = BlockEdge::kBlockStart; |
| break; |
| case AxisEdge::kCenter: |
| *block_edge = BlockEdge::kBlockCenter; |
| offset->block_offset += container_size.block_size / 2; |
| break; |
| default: |
| *block_edge = BlockEdge::kBlockEnd; |
| offset->block_offset += container_size.block_size; |
| break; |
| } |
| } |
| |
| } // namespace |
| |
| const NGConstraintSpace NGGridLayoutAlgorithm::CreateConstraintSpace( |
| const GridGeometry& grid_geometry, |
| const GridItemData& grid_item, |
| NGCacheSlot cache_slot, |
| LogicalRect* rect) const { |
| DCHECK(rect); |
| |
| ComputeOffsetAndSize(grid_item, grid_geometry.column_geometry, kForColumns, |
| kIndefiniteSize, &rect->offset.inline_offset, |
| &rect->size.inline_size); |
| ComputeOffsetAndSize(grid_item, grid_geometry.row_geometry, kForRows, |
| kIndefiniteSize, &rect->offset.block_offset, |
| &rect->size.block_size); |
| |
| NGConstraintSpaceBuilder builder(ConstraintSpace(), |
| grid_item.node.Style().GetWritingDirection(), |
| /* is_new_fc */ true); |
| SetOrthogonalFallbackInlineSizeIfNeeded(Style(), grid_item.node, &builder); |
| builder.SetCacheSlot(cache_slot); |
| builder.SetIsPaintedAtomically(true); |
| builder.SetAvailableSize(rect->size); |
| builder.SetPercentageResolutionSize(rect->size); |
| builder.SetStretchInlineSizeIfAuto(grid_item.is_inline_axis_stretched && |
| rect->size.inline_size != kIndefiniteSize); |
| builder.SetStretchBlockSizeIfAuto(grid_item.is_block_axis_stretched && |
| rect->size.block_size != kIndefiniteSize); |
| return builder.ToConstraintSpace(); |
| } |
| |
| void NGGridLayoutAlgorithm::PlaceGridItems(const GridItems& grid_items, |
| const GridGeometry& grid_geometry, |
| LayoutUnit block_size) { |
| // |grid_items| is in DOM order to ensure proper painting order, but |
| // determining the grid's baseline is prioritized based on grid order. The |
| // baseline of the grid is determined by the first grid item with baseline |
| // alignment in the first row. If no items have baseline alignment, fall back |
| // to the first item in row-major order. |
| struct PositionAndBaseline { |
| PositionAndBaseline(const GridArea& resolved_position, LayoutUnit baseline) |
| : resolved_position(resolved_position), baseline(baseline) {} |
| GridArea resolved_position; |
| LayoutUnit baseline; |
| }; |
| base::Optional<PositionAndBaseline> alignment_baseline; |
| base::Optional<PositionAndBaseline> fallback_baseline; |
| |
| for (const auto& grid_item : grid_items.item_data) { |
| DCHECK(grid_item.column_set_indices.has_value()); |
| DCHECK(grid_item.row_set_indices.has_value()); |
| |
| LogicalRect containing_grid_area; |
| const NGConstraintSpace space = CreateConstraintSpace( |
| grid_geometry, grid_item, NGCacheSlot::kLayout, &containing_grid_area); |
| |
| scoped_refptr<const NGLayoutResult> result = grid_item.node.Layout(space); |
| const auto& physical_fragment = |
| To<NGPhysicalBoxFragment>(result->PhysicalFragment()); |
| |
| // Apply the grid-item's alignment (if any). |
| NGBoxFragment fragment(ConstraintSpace().GetWritingDirection(), |
| physical_fragment); |
| containing_grid_area.offset += LogicalOffset( |
| AlignmentOffset(containing_grid_area.size.inline_size, |
| fragment.InlineSize(), grid_item.margins.inline_start, |
| grid_item.margins.inline_end, |
| grid_item.inline_axis_alignment), |
| AlignmentOffset(containing_grid_area.size.block_size, |
| fragment.BlockSize(), grid_item.margins.block_start, |
| grid_item.margins.block_end, |
| grid_item.block_axis_alignment)); |
| |
| container_builder_.AddChild(physical_fragment, containing_grid_area.offset); |
| |
| // Compares GridArea objects in row-major grid order for baseline |
| // precedence. Returns 'true' if |a| < |b| and 'false' otherwise. |
| auto IsBeforeInGridOrder = [&](const GridArea& a, |
| const GridArea& b) -> bool { |
| return (a.rows < b.rows) || (a.rows == b.rows && (a.columns < b.columns)); |
| }; |
| |
| LayoutUnit baseline = fragment.BaselineOrSynthesize() + |
| containing_grid_area.offset.block_offset; |
| if (grid_item.block_axis_alignment == AxisEdge::kBaseline) { |
| if (!alignment_baseline || |
| IsBeforeInGridOrder(grid_item.resolved_position, |
| alignment_baseline->resolved_position)) { |
| alignment_baseline.emplace(grid_item.resolved_position, baseline); |
| } |
| } else if (!fallback_baseline || |
| IsBeforeInGridOrder(grid_item.resolved_position, |
| fallback_baseline->resolved_position)) { |
| fallback_baseline.emplace(grid_item.resolved_position, baseline); |
| } |
| } |
| |
| // Propagate the baseline from the appropriate child. |
| // TODO(kschmi): Synthesize baseline from alignment context if no grid items. |
| if (!grid_items.IsEmpty()) { |
| if (alignment_baseline && |
| (!fallback_baseline || alignment_baseline->resolved_position.rows <= |
| fallback_baseline->resolved_position.rows)) { |
| container_builder_.SetBaseline(alignment_baseline->baseline); |
| } else { |
| DCHECK(fallback_baseline); |
| container_builder_.SetBaseline(fallback_baseline->baseline); |
| } |
| } |
| } |
| |
| void NGGridLayoutAlgorithm::PlaceOutOfFlowItems( |
| const Vector<GridItemData>& out_of_flow_items, |
| const GridGeometry& grid_geometry, |
| LayoutUnit block_size) { |
| for (const GridItemData& out_of_flow_item : out_of_flow_items) { |
| DCHECK(out_of_flow_item.column_set_indices.has_value()); |
| DCHECK(out_of_flow_item.row_set_indices.has_value()); |
| |
| LogicalRect containing_block_rect = ComputeContainingGridAreaRect( |
| grid_geometry, out_of_flow_item, block_size); |
| NGLogicalStaticPosition::InlineEdge inline_edge; |
| NGLogicalStaticPosition::BlockEdge block_edge; |
| LogicalOffset child_offset = containing_block_rect.offset; |
| AlignmentOffsetForOutOfFlow(out_of_flow_item.inline_axis_alignment, |
| out_of_flow_item.block_axis_alignment, |
| containing_block_rect.size, &inline_edge, |
| &block_edge, &child_offset); |
| |
| container_builder_.AddOutOfFlowChildCandidate( |
| out_of_flow_item.node, child_offset, inline_edge, block_edge, |
| /* needs_block_offset_adjustment */ false, containing_block_rect); |
| } |
| } |
| |
| void NGGridLayoutAlgorithm::PlaceOutOfFlowDescendants( |
| const NGGridLayoutAlgorithmTrackCollection& column_track_collection, |
| const NGGridLayoutAlgorithmTrackCollection& row_track_collection, |
| const GridGeometry& grid_geometry, |
| const NGGridPlacement& grid_placement, |
| LayoutUnit block_size) { |
| // At this point, we'll have a list of OOF candidates from any inflow children |
| // of the grid (which have been propagated up). These might have an assigned |
| // 'grid-area', so we need to assign their correct 'containing block rect'. |
| Vector<NGLogicalOutOfFlowPositionedNode>* out_of_flow_descendants = |
| container_builder_.MutableOutOfFlowPositionedCandidates(); |
| DCHECK(out_of_flow_descendants); |
| |
| for (auto& out_of_flow_descendant : *out_of_flow_descendants) { |
| // TODO(ansollan): We don't need all parameters from |GridItemData| for out |
| // of flow items. Implement a reduced version in |MeasureGridItem| or only |
| // fill what is needed here. |
| GridItemData out_of_flow_item = |
| MeasureGridItem(out_of_flow_descendant.node); |
| |
| out_of_flow_item.SetIndices(column_track_collection, &grid_placement); |
| out_of_flow_item.SetIndices(row_track_collection, &grid_placement); |
| |
| out_of_flow_descendant.containing_block_rect = |
| ComputeContainingGridAreaRect(grid_geometry, out_of_flow_item, |
| block_size); |
| } |
| } |
| |
| LogicalRect NGGridLayoutAlgorithm::ComputeContainingGridAreaRect( |
| const GridGeometry& grid_geometry, |
| const GridItemData& item, |
| LayoutUnit block_size) { |
| LogicalRect rect; |
| ComputeOffsetAndSize(item, grid_geometry.column_geometry, kForColumns, |
| block_size, &rect.offset.inline_offset, |
| &rect.size.inline_size); |
| ComputeOffsetAndSize(item, grid_geometry.row_geometry, kForRows, block_size, |
| &rect.offset.block_offset, &rect.size.block_size); |
| return rect; |
| } |
| |
| void NGGridLayoutAlgorithm::ComputeOffsetAndSize( |
| const GridItemData& item, |
| const SetGeometry& set_geometry, |
| const GridTrackSizingDirection track_direction, |
| LayoutUnit block_size, |
| LayoutUnit* start_offset, |
| LayoutUnit* size) const { |
| wtf_size_t start_index, end_index; |
| LayoutUnit border; |
| // The default padding box value of the |size| will only be used in out of |
| // flow items in which both the start line and end line are defined as 'auto'. |
| if (track_direction == kForColumns) { |
| start_index = item.column_set_indices->begin; |
| end_index = item.column_set_indices->end; |
| border = container_builder_.Borders().inline_start; |
| *size = |
| border_box_size_.inline_size - container_builder_.Borders().InlineSum(); |
| } else { |
| start_index = item.row_set_indices->begin; |
| end_index = item.row_set_indices->end; |
| border = container_builder_.Borders().block_start; |
| *size = border_box_size_.block_size == kIndefiniteSize |
| ? block_size |
| : border_box_size_.block_size; |
| *size -= container_builder_.Borders().BlockSum(); |
| } |
| *start_offset = border; |
| LayoutUnit end_offset = border; |
| // If the start line is defined, the size is calculated by subtracting the |
| // offset at start index. Additionally, the start border is removed from the |
| // cumulated offset because it was already accounted for in the previous value |
| // of the size. |
| if (start_index != kNotFound) { |
| *start_offset = set_geometry.sets[start_index].offset; |
| *size -= (*start_offset - end_offset); |
| } |
| // If the end line is defined, the offset (which can be the offset at the |
| // start index or the start border) and the added grid gap after the spanned |
| // tracks are subtracted from the offset at the end index. |
| if (end_index != kNotFound) { |
| // If we are measuring a grid-item we might not yet have determined the |
| // final (used) sizes for all of out sets. |last_indefinite_index| is used |
| // to track what sets have indefinite/definite sizes. |
| // |
| // |last_indefinite_index| is the last set seen which was indefinite. If |
| // our |start_index| is greater than this, all the sets between this and |
| // our |end_index| are definite. |
| const wtf_size_t last_indefinite_index = |
| set_geometry.sets[end_index].last_indefinite_index; |
| end_offset = set_geometry.sets[end_index].offset; |
| if (last_indefinite_index == kNotFound || |
| start_index > last_indefinite_index) { |
| *size = end_offset - *start_offset - set_geometry.gutter_size; |
| } else { |
| *size = kIndefiniteSize; |
| } |
| } |
| |
| #if DCHECK_IS_ON() |
| if (start_index != kNotFound && end_index != kNotFound) { |
| DCHECK_LT(start_index, end_index); |
| DCHECK_LT(end_index, set_geometry.sets.size()); |
| DCHECK(*size >= 0 || *size == kIndefiniteSize); |
| } else { |
| // Only out of flow items can have an undefined ('auto') value for the start |
| // and/or end |set_indices|. |
| DCHECK_EQ(item.item_type, ItemType::kOutOfFlow); |
| } |
| #endif |
| } |
| } // namespace blink |