| // Copyright 2015 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/multi_column_fragmentainer_group.h" |
| |
| #include "third_party/blink/renderer/core/layout/column_balancer.h" |
| #include "third_party/blink/renderer/core/layout/fragmentation_context.h" |
| #include "third_party/blink/renderer/core/layout/layout_multi_column_set.h" |
| |
| namespace blink { |
| |
| // Limit the maximum column count, to prevent potential performance problems. |
| static const unsigned kColumnCountClampMax = 10000; |
| |
| // Clamp "infinite" clips to a number of pixels that can be losslessly |
| // converted to and from floating point, to avoid loss of precision. |
| // Note that tables have something similar, see |
| // TableLayoutAlgorithm::kTableMaxWidth. |
| static const int kMulticolMaxClipPixels = 1000000; |
| |
| MultiColumnFragmentainerGroup::MultiColumnFragmentainerGroup( |
| const LayoutMultiColumnSet& column_set) |
| : column_set_(&column_set) {} |
| |
| bool MultiColumnFragmentainerGroup::IsFirstGroup() const { |
| return &column_set_->FirstFragmentainerGroup() == this; |
| } |
| |
| bool MultiColumnFragmentainerGroup::IsLastGroup() const { |
| return &column_set_->LastFragmentainerGroup() == this; |
| } |
| |
| LayoutSize MultiColumnFragmentainerGroup::OffsetFromColumnSet() const { |
| LayoutSize offset(LayoutUnit(), LogicalTop()); |
| if (!column_set_->FlowThread()->IsHorizontalWritingMode()) |
| return offset.TransposedSize(); |
| return offset; |
| } |
| |
| LayoutUnit |
| MultiColumnFragmentainerGroup::BlockOffsetInEnclosingFragmentationContext() |
| const { |
| return LogicalTop() + column_set_->LogicalTopFromMulticolContentEdge() + |
| column_set_->MultiColumnFlowThread() |
| ->BlockOffsetInEnclosingFragmentationContext(); |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::LogicalHeightInFlowThreadAt( |
| unsigned column_index) const { |
| DCHECK(IsLogicalHeightKnown()); |
| LayoutUnit column_height = ColumnLogicalHeight(); |
| LayoutUnit logical_top = LogicalTopInFlowThreadAt(column_index); |
| LayoutUnit logical_bottom = logical_top + column_height; |
| unsigned actual_count = ActualColumnCount(); |
| if (column_index + 1 >= actual_count) { |
| // The last column may contain overflow content, if the actual column count |
| // was clamped, so using the column height won't do. This is also a way to |
| // stay within the bounds of the flow thread, if the last column happens to |
| // contain LESS than the other columns. We also need this clamping if we're |
| // given a column index *after* the last column. Height should obviously be |
| // 0 then. We may be called with a column index that's one entry past the |
| // end if we're dealing with zero-height content at the very end of the flow |
| // thread, and this location is at a column boundary. |
| if (column_index + 1 == actual_count) |
| logical_bottom = LogicalBottomInFlowThread(); |
| else |
| logical_bottom = logical_top; |
| } |
| return (logical_bottom - logical_top).ClampNegativeToZero(); |
| } |
| |
| void MultiColumnFragmentainerGroup::ResetColumnHeight() { |
| max_logical_height_ = CalculateMaxColumnHeight(); |
| |
| LayoutMultiColumnFlowThread* flow_thread = |
| column_set_->MultiColumnFlowThread(); |
| if (column_set_->HeightIsAuto()) { |
| FragmentationContext* enclosing_fragmentation_context = |
| flow_thread->EnclosingFragmentationContext(); |
| if (enclosing_fragmentation_context && |
| enclosing_fragmentation_context->IsFragmentainerLogicalHeightKnown()) { |
| // Set an initial height, based on the fragmentainer height in the outer |
| // fragmentation context, in order to tell how much content this |
| // MultiColumnFragmentainerGroup can hold, and when we need to append a |
| // new one. |
| is_logical_height_known_ = true; |
| logical_height_ = max_logical_height_; |
| return; |
| } |
| } |
| // If the multicol container has a definite height, use it as the column |
| // height. This even applies when we are to balance the columns. We'll still |
| // use the definite height as an initial height, and lay out once at that |
| // column height. If it turns out that the content needs less than this |
| // height, we have to balance and shrink the height and lay out the columns |
| // over again. |
| if (LayoutUnit logical_height = flow_thread->ColumnHeightAvailable()) { |
| is_logical_height_known_ = true; |
| SetAndConstrainColumnHeight(HeightAdjustedForRowOffset(logical_height)); |
| } else { |
| is_logical_height_known_ = false; |
| logical_height_ = LayoutUnit(); |
| } |
| } |
| |
| bool MultiColumnFragmentainerGroup::RecalculateColumnHeight( |
| LayoutMultiColumnSet& column_set) { |
| LayoutUnit old_column_height = logical_height_; |
| |
| max_logical_height_ = CalculateMaxColumnHeight(); |
| |
| // Only the last row may have auto height, and thus be balanced. There are no |
| // good reasons to balance the preceding rows, and that could potentially lead |
| // to an insane number of layout passes as well. |
| if (IsLastGroup() && column_set.HeightIsAuto()) { |
| LayoutUnit new_column_height; |
| if (!column_set.IsInitialHeightCalculated()) { |
| // Initial balancing: Start with the lowest imaginable column height. Also |
| // calculate the height of the tallest piece of unbreakable content. |
| // Columns should never get any shorter than that (unless constrained by |
| // max-height). Propagate this to our containing column set, in case there |
| // is an outer multicol container that also needs to balance. After having |
| // calculated the initial column height, the multicol container needs |
| // another layout pass with the column height that we just calculated. |
| InitialColumnHeightFinder initial_height_finder( |
| column_set, LogicalTopInFlowThread(), LogicalBottomInFlowThread()); |
| column_set.PropagateTallestUnbreakableLogicalHeight( |
| initial_height_finder.TallestUnbreakableLogicalHeight()); |
| new_column_height = initial_height_finder.InitialMinimalBalancedHeight(); |
| } else { |
| // Rebalancing: After having laid out again, we'll need to rebalance if |
| // the height wasn't enough and we're allowed to stretch it, and then |
| // re-lay out. There are further details on the column balancing |
| // machinery in ColumnBalancer and its derivates. |
| new_column_height = RebalanceColumnHeightIfNeeded(); |
| } |
| SetAndConstrainColumnHeight(new_column_height); |
| } else { |
| // The position of the column set may have changed, in which case height |
| // available for columns may have changed as well. |
| SetAndConstrainColumnHeight(logical_height_); |
| } |
| |
| // We may not have found our final height yet, but at least we've found a |
| // height. |
| is_logical_height_known_ = true; |
| |
| if (logical_height_ == old_column_height) |
| return false; // No change. We're done. |
| |
| return true; // Need another pass. |
| } |
| |
| LayoutSize MultiColumnFragmentainerGroup::FlowThreadTranslationAtOffset( |
| LayoutUnit offset_in_flow_thread, |
| LayoutBox::PageBoundaryRule rule, |
| CoordinateSpaceConversion mode) const { |
| LayoutMultiColumnFlowThread* flow_thread = |
| column_set_->MultiColumnFlowThread(); |
| |
| // A column out of range doesn't have a flow thread portion, so we need to |
| // clamp to make sure that we stay within the actual columns. This means that |
| // content in the overflow area will be mapped to the last actual column, |
| // instead of being mapped to an imaginary column further ahead. |
| unsigned column_index = |
| offset_in_flow_thread >= LogicalBottomInFlowThread() |
| ? ActualColumnCount() - 1 |
| : ColumnIndexAtOffset(offset_in_flow_thread, rule); |
| |
| LayoutRect portion_rect(FlowThreadPortionRectAt(column_index)); |
| flow_thread->DeprecatedFlipForWritingMode(portion_rect); |
| portion_rect.MoveBy(flow_thread->PhysicalLocation().ToLayoutPoint()); |
| |
| LayoutRect column_rect(ColumnRectAt(column_index)); |
| column_rect.Move(OffsetFromColumnSet()); |
| column_set_->DeprecatedFlipForWritingMode(column_rect); |
| column_rect.MoveBy(column_set_->PhysicalLocation().ToLayoutPoint()); |
| |
| LayoutSize translation_relative_to_flow_thread = |
| column_rect.Location() - portion_rect.Location(); |
| if (mode == CoordinateSpaceConversion::kContaining) |
| return translation_relative_to_flow_thread; |
| |
| LayoutSize enclosing_translation; |
| if (LayoutMultiColumnFlowThread* enclosing_flow_thread = |
| flow_thread->EnclosingFlowThread()) { |
| const MultiColumnFragmentainerGroup& first_row = |
| flow_thread->FirstMultiColumnSet()->FirstFragmentainerGroup(); |
| // Translation that would map points in the coordinate space of the |
| // outermost flow thread to visual points in the first column in the first |
| // fragmentainer group (row) in our multicol container. |
| LayoutSize enclosing_translation_origin = |
| enclosing_flow_thread->FlowThreadTranslationAtOffset( |
| first_row.BlockOffsetInEnclosingFragmentationContext(), |
| LayoutBox::kAssociateWithLatterPage, mode); |
| |
| // Translation that would map points in the coordinate space of the |
| // outermost flow thread to visual points in the first column in this |
| // fragmentainer group. |
| enclosing_translation = |
| enclosing_flow_thread->FlowThreadTranslationAtOffset( |
| BlockOffsetInEnclosingFragmentationContext(), |
| LayoutBox::kAssociateWithLatterPage, mode); |
| |
| // What we ultimately return from this method is a translation that maps |
| // points in the coordinate space of our flow thread to a visual point in a |
| // certain column in this fragmentainer group. We had to go all the way up |
| // to the outermost flow thread, since this fragmentainer group may be in a |
| // different outer column than the first outer column that this multicol |
| // container lives in. It's the visual distance between the first |
| // fragmentainer group and this fragmentainer group that we need to add to |
| // the translation. |
| enclosing_translation -= enclosing_translation_origin; |
| } |
| |
| return enclosing_translation + translation_relative_to_flow_thread; |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::ColumnLogicalTopForOffset( |
| LayoutUnit offset_in_flow_thread) const { |
| unsigned column_index = ColumnIndexAtOffset( |
| offset_in_flow_thread, LayoutBox::kAssociateWithLatterPage); |
| return LogicalTopInFlowThreadAt(column_index); |
| } |
| |
| LayoutPoint MultiColumnFragmentainerGroup::VisualPointToFlowThreadPoint( |
| const LayoutPoint& visual_point, |
| SnapToColumnPolicy snap) const { |
| unsigned column_index = ColumnIndexAtVisualPoint(visual_point); |
| LayoutRect column_rect = ColumnRectAt(column_index); |
| LayoutPoint local_point(visual_point); |
| local_point.MoveBy(-column_rect.Location()); |
| if (!column_set_->IsHorizontalWritingMode()) { |
| if (snap == kSnapToColumn) { |
| LayoutUnit column_start = column_set_->StyleRef().IsLeftToRightDirection() |
| ? LayoutUnit() |
| : column_rect.Height(); |
| if (local_point.X() < 0) |
| local_point = LayoutPoint(LayoutUnit(), column_start); |
| else if (local_point.X() > ColumnLogicalHeight()) |
| local_point = LayoutPoint(ColumnLogicalHeight(), column_start); |
| } |
| return LayoutPoint(local_point.X() + LogicalTopInFlowThreadAt(column_index), |
| local_point.Y()); |
| } |
| if (snap == kSnapToColumn) { |
| LayoutUnit column_start = column_set_->StyleRef().IsLeftToRightDirection() |
| ? LayoutUnit() |
| : column_rect.Width(); |
| if (local_point.Y() < 0) |
| local_point = LayoutPoint(column_start, LayoutUnit()); |
| else if (local_point.Y() > ColumnLogicalHeight()) |
| local_point = LayoutPoint(column_start, ColumnLogicalHeight()); |
| } |
| return LayoutPoint(local_point.X(), |
| local_point.Y() + LogicalTopInFlowThreadAt(column_index)); |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::FragmentsBoundingBox( |
| const LayoutRect& bounding_box_in_flow_thread) const { |
| // Find the start and end column intersected by the bounding box. |
| LayoutRect flipped_bounding_box_in_flow_thread(bounding_box_in_flow_thread); |
| LayoutFlowThread* flow_thread = column_set_->FlowThread(); |
| flow_thread->DeprecatedFlipForWritingMode( |
| flipped_bounding_box_in_flow_thread); |
| bool is_horizontal_writing_mode = column_set_->IsHorizontalWritingMode(); |
| LayoutUnit bounding_box_logical_top = |
| is_horizontal_writing_mode ? flipped_bounding_box_in_flow_thread.Y() |
| : flipped_bounding_box_in_flow_thread.X(); |
| LayoutUnit bounding_box_logical_bottom = |
| is_horizontal_writing_mode ? flipped_bounding_box_in_flow_thread.MaxY() |
| : flipped_bounding_box_in_flow_thread.MaxX(); |
| if (bounding_box_logical_bottom <= LogicalTopInFlowThread() || |
| bounding_box_logical_top >= LogicalBottomInFlowThread()) { |
| // The bounding box doesn't intersect this fragmentainer group. |
| return LayoutRect(); |
| } |
| unsigned start_column; |
| unsigned end_column; |
| ColumnIntervalForBlockRangeInFlowThread(bounding_box_logical_top, |
| bounding_box_logical_bottom, |
| start_column, end_column); |
| |
| LayoutRect start_column_flow_thread_overflow_portion = |
| FlowThreadPortionOverflowRectAt(start_column); |
| flow_thread->DeprecatedFlipForWritingMode( |
| start_column_flow_thread_overflow_portion); |
| LayoutRect start_column_rect(bounding_box_in_flow_thread); |
| start_column_rect.Intersect(start_column_flow_thread_overflow_portion); |
| start_column_rect.Move( |
| FlowThreadTranslationAtOffset(LogicalTopInFlowThreadAt(start_column), |
| LayoutBox::kAssociateWithLatterPage, |
| CoordinateSpaceConversion::kContaining)); |
| if (start_column == end_column) |
| return start_column_rect; // It all takes place in one column. We're done. |
| |
| LayoutRect end_column_flow_thread_overflow_portion = |
| FlowThreadPortionOverflowRectAt(end_column); |
| flow_thread->DeprecatedFlipForWritingMode( |
| end_column_flow_thread_overflow_portion); |
| LayoutRect end_column_rect(bounding_box_in_flow_thread); |
| end_column_rect.Intersect(end_column_flow_thread_overflow_portion); |
| end_column_rect.Move(FlowThreadTranslationAtOffset( |
| LogicalTopInFlowThreadAt(end_column), LayoutBox::kAssociateWithLatterPage, |
| CoordinateSpaceConversion::kContaining)); |
| return UnionRect(start_column_rect, end_column_rect); |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::CalculateOverflow() const { |
| // Note that we just return the bounding rectangle of the column boxes here. |
| // We currently don't examine overflow caused by the actual content that ends |
| // up in each column. |
| LayoutRect overflow_rect; |
| if (unsigned column_count = ActualColumnCount()) { |
| overflow_rect = ColumnRectAt(0); |
| if (column_count > 1) |
| overflow_rect.UniteEvenIfEmpty(ColumnRectAt(column_count - 1)); |
| } |
| return overflow_rect; |
| } |
| |
| unsigned MultiColumnFragmentainerGroup::ActualColumnCount() const { |
| unsigned count = UnclampedActualColumnCount(); |
| count = std::min(count, kColumnCountClampMax); |
| DCHECK_GE(count, 1u); |
| return count; |
| } |
| |
| void MultiColumnFragmentainerGroup::UpdateFromNG(LayoutUnit logical_height) { |
| logical_height_ = logical_height; |
| is_logical_height_known_ = true; |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::HeightAdjustedForRowOffset( |
| LayoutUnit height) const { |
| LayoutUnit adjusted_height = |
| height - LogicalTop() - column_set_->LogicalTopFromMulticolContentEdge(); |
| return adjusted_height.ClampNegativeToZero(); |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::CalculateMaxColumnHeight() const { |
| LayoutMultiColumnFlowThread* flow_thread = |
| column_set_->MultiColumnFlowThread(); |
| LayoutUnit max_column_height = flow_thread->MaxColumnLogicalHeight(); |
| LayoutUnit max_height = HeightAdjustedForRowOffset(max_column_height); |
| if (FragmentationContext* enclosing_fragmentation_context = |
| flow_thread->EnclosingFragmentationContext()) { |
| if (enclosing_fragmentation_context->IsFragmentainerLogicalHeightKnown()) { |
| // We're nested inside another fragmentation context whose fragmentainer |
| // heights are known. This constrains the max height. |
| LayoutUnit remaining_outer_logical_height = |
| enclosing_fragmentation_context->RemainingLogicalHeightAt( |
| BlockOffsetInEnclosingFragmentationContext()); |
| if (max_height > remaining_outer_logical_height) |
| max_height = remaining_outer_logical_height; |
| } |
| } |
| return max_height; |
| } |
| |
| void MultiColumnFragmentainerGroup::SetAndConstrainColumnHeight( |
| LayoutUnit new_height) { |
| logical_height_ = new_height; |
| if (logical_height_ > max_logical_height_) |
| logical_height_ = max_logical_height_; |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::RebalanceColumnHeightIfNeeded() |
| const { |
| if (ActualColumnCount() <= column_set_->UsedColumnCount()) { |
| // With the current column height, the content fits without creating |
| // overflowing columns. We're done. |
| return logical_height_; |
| } |
| |
| if (logical_height_ >= max_logical_height_) { |
| // We cannot stretch any further. We'll just have to live with the |
| // overflowing columns. This typically happens if the max column height is |
| // less than the height of the tallest piece of unbreakable content (e.g. |
| // lines). |
| return logical_height_; |
| } |
| |
| MinimumSpaceShortageFinder shortage_finder( |
| ColumnSet(), LogicalTopInFlowThread(), LogicalBottomInFlowThread()); |
| |
| if (shortage_finder.ForcedBreaksCount() + 1 >= |
| column_set_->UsedColumnCount()) { |
| // Too many forced breaks to allow any implicit breaks. Initial balancing |
| // should already have set a good height. There's nothing more we should do. |
| return logical_height_; |
| } |
| |
| // If the initial guessed column height wasn't enough, stretch it now. Stretch |
| // by the lowest amount of space. |
| LayoutUnit min_space_shortage = shortage_finder.MinimumSpaceShortage(); |
| |
| DCHECK_GT(min_space_shortage, 0); // We should never _shrink_ the height! |
| |
| if (min_space_shortage == LayoutUnit::Max()) { |
| // We failed to find an amount to stretch the columns by. This is a bug; see |
| // e.g. crbug.com/510340 . If this happens, though, we need bail out rather |
| // than looping infinitely. |
| return logical_height_; |
| } |
| |
| return logical_height_ + min_space_shortage; |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::ColumnRectAt( |
| unsigned column_index) const { |
| LayoutUnit column_logical_width = column_set_->PageLogicalWidth(); |
| LayoutUnit column_logical_height = LogicalHeightInFlowThreadAt(column_index); |
| LayoutUnit column_logical_top; |
| LayoutUnit column_logical_left; |
| LayoutUnit column_gap = column_set_->ColumnGap(); |
| |
| if (column_set_->StyleRef().IsLeftToRightDirection()) { |
| column_logical_left += column_index * (column_logical_width + column_gap); |
| } else { |
| column_logical_left += column_set_->ContentLogicalWidth() - |
| column_logical_width - |
| column_index * (column_logical_width + column_gap); |
| } |
| |
| LayoutRect column_rect(column_logical_left, column_logical_top, |
| column_logical_width, column_logical_height); |
| if (!column_set_->IsHorizontalWritingMode()) |
| return column_rect.TransposedRect(); |
| return column_rect; |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::FlowThreadPortionRectAt( |
| unsigned column_index) const { |
| LayoutUnit logical_top = LogicalTopInFlowThreadAt(column_index); |
| LayoutUnit portion_logical_height = LogicalHeightInFlowThreadAt(column_index); |
| if (column_set_->IsHorizontalWritingMode()) { |
| return LayoutRect(LayoutUnit(), logical_top, |
| column_set_->PageLogicalWidth(), portion_logical_height); |
| } |
| return LayoutRect(logical_top, LayoutUnit(), portion_logical_height, |
| column_set_->PageLogicalWidth()); |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::FlowThreadPortionOverflowRectAt( |
| unsigned column_index) const { |
| // This function determines the portion of the flow thread that paints for the |
| // column. |
| // |
| // In the block direction, we will not clip overflow out of the top of the |
| // first column, or out of the bottom of the last column. This applies only to |
| // the true first column and last column across all column sets. |
| // |
| // FIXME: Eventually we will know overflow on a per-column basis, but we can't |
| // do this until we have a painting mode that understands not to paint |
| // contents from a previous column in the overflow area of a following column. |
| bool is_first_column_in_row = !column_index; |
| bool is_last_column_in_row = column_index == ActualColumnCount() - 1; |
| |
| LayoutRect portion_rect = FlowThreadPortionRectAt(column_index); |
| bool is_first_column_in_multicol_container = |
| is_first_column_in_row && |
| this == &column_set_->FirstFragmentainerGroup() && |
| !column_set_->PreviousSiblingMultiColumnSet(); |
| bool is_last_column_in_multicol_container = |
| is_last_column_in_row && this == &column_set_->LastFragmentainerGroup() && |
| !column_set_->NextSiblingMultiColumnSet(); |
| // Calculate the overflow rectangle. It will be clipped at the logical top |
| // and bottom of the column box, unless it's the first or last column in the |
| // multicol container, in which case it should allow overflow. It will also |
| // be clipped in the middle of adjacent column gaps. Care is taken here to |
| // avoid rounding errors. |
| LayoutRect overflow_rect( |
| IntRect(-kMulticolMaxClipPixels, -kMulticolMaxClipPixels, |
| 2 * kMulticolMaxClipPixels, 2 * kMulticolMaxClipPixels)); |
| if (column_set_->IsHorizontalWritingMode()) { |
| if (!is_first_column_in_multicol_container) |
| overflow_rect.ShiftYEdgeTo(portion_rect.Y()); |
| if (!is_last_column_in_multicol_container) |
| overflow_rect.ShiftMaxYEdgeTo(portion_rect.MaxY()); |
| } else { |
| if (!is_first_column_in_multicol_container) |
| overflow_rect.ShiftXEdgeTo(portion_rect.X()); |
| if (!is_last_column_in_multicol_container) |
| overflow_rect.ShiftMaxXEdgeTo(portion_rect.MaxX()); |
| } |
| return overflow_rect; |
| } |
| |
| unsigned MultiColumnFragmentainerGroup::ColumnIndexAtOffset( |
| LayoutUnit offset_in_flow_thread, |
| LayoutBox::PageBoundaryRule page_boundary_rule) const { |
| // Handle the offset being out of range. |
| if (offset_in_flow_thread < logical_top_in_flow_thread_) |
| return 0; |
| |
| if (!IsLogicalHeightKnown()) |
| return 0; |
| LayoutUnit column_height = ColumnLogicalHeight(); |
| unsigned column_index = |
| ((offset_in_flow_thread - logical_top_in_flow_thread_) / column_height) |
| .Floor(); |
| if (page_boundary_rule == LayoutBox::kAssociateWithFormerPage && |
| column_index > 0 && |
| LogicalTopInFlowThreadAt(column_index) == offset_in_flow_thread) { |
| // We are exactly at a column boundary, and we've been told to associate |
| // offsets at column boundaries with the former column, not the latter. |
| column_index--; |
| } |
| return column_index; |
| } |
| |
| unsigned MultiColumnFragmentainerGroup::ConstrainedColumnIndexAtOffset( |
| LayoutUnit offset_in_flow_thread, |
| LayoutBox::PageBoundaryRule page_boundary_rule) const { |
| unsigned index = |
| ColumnIndexAtOffset(offset_in_flow_thread, page_boundary_rule); |
| return std::min(index, ActualColumnCount() - 1); |
| } |
| |
| unsigned MultiColumnFragmentainerGroup::ColumnIndexAtVisualPoint( |
| const LayoutPoint& visual_point) const { |
| LayoutUnit column_length = column_set_->PageLogicalWidth(); |
| LayoutUnit offset_in_column_progression_direction = |
| column_set_->IsHorizontalWritingMode() ? visual_point.X() |
| : visual_point.Y(); |
| if (!column_set_->StyleRef().IsLeftToRightDirection()) { |
| offset_in_column_progression_direction = |
| column_set_->LogicalWidth() - offset_in_column_progression_direction; |
| } |
| LayoutUnit column_gap = column_set_->ColumnGap(); |
| if (column_length + column_gap <= 0) |
| return 0; |
| // Column boundaries are in the middle of the column gap. |
| int index = ((offset_in_column_progression_direction + column_gap / 2) / |
| (column_length + column_gap)) |
| .ToInt(); |
| if (index < 0) |
| return 0; |
| return std::min(unsigned(index), ActualColumnCount() - 1); |
| } |
| |
| void MultiColumnFragmentainerGroup::ColumnIntervalForBlockRangeInFlowThread( |
| LayoutUnit logical_top_in_flow_thread, |
| LayoutUnit logical_bottom_in_flow_thread, |
| unsigned& first_column, |
| unsigned& last_column) const { |
| logical_top_in_flow_thread = |
| std::max(logical_top_in_flow_thread, LogicalTopInFlowThread()); |
| logical_bottom_in_flow_thread = |
| std::min(logical_bottom_in_flow_thread, LogicalBottomInFlowThread()); |
| first_column = ConstrainedColumnIndexAtOffset( |
| logical_top_in_flow_thread, LayoutBox::kAssociateWithLatterPage); |
| if (logical_bottom_in_flow_thread <= logical_top_in_flow_thread) { |
| // Zero-height block range. There'll be one column in the interval. Set it |
| // right away. This is important if we're at a column boundary, since |
| // calling ConstrainedColumnIndexAtOffset() with the end-exclusive bottom |
| // offset would actually give us the *previous* column. |
| last_column = first_column; |
| } else { |
| last_column = ConstrainedColumnIndexAtOffset( |
| logical_bottom_in_flow_thread, LayoutBox::kAssociateWithFormerPage); |
| } |
| } |
| |
| void MultiColumnFragmentainerGroup::ColumnIntervalForVisualRect( |
| const LayoutRect& rect, |
| unsigned& first_column, |
| unsigned& last_column) const { |
| bool is_column_ltr = column_set_->StyleRef().IsLeftToRightDirection(); |
| if (column_set_->IsHorizontalWritingMode()) { |
| if (is_column_ltr) { |
| first_column = ColumnIndexAtVisualPoint(rect.MinXMinYCorner()); |
| last_column = ColumnIndexAtVisualPoint(rect.MaxXMinYCorner()); |
| } else { |
| first_column = ColumnIndexAtVisualPoint(rect.MaxXMinYCorner()); |
| last_column = ColumnIndexAtVisualPoint(rect.MinXMinYCorner()); |
| } |
| } else { |
| if (is_column_ltr) { |
| first_column = ColumnIndexAtVisualPoint(rect.MinXMinYCorner()); |
| last_column = ColumnIndexAtVisualPoint(rect.MinXMaxYCorner()); |
| } else { |
| first_column = ColumnIndexAtVisualPoint(rect.MinXMaxYCorner()); |
| last_column = ColumnIndexAtVisualPoint(rect.MinXMinYCorner()); |
| } |
| } |
| DCHECK_LE(first_column, last_column); |
| } |
| |
| unsigned MultiColumnFragmentainerGroup::UnclampedActualColumnCount() const { |
| // We must always return a value of 1 or greater. Column count = 0 is a |
| // meaningless situation, and will confuse and cause problems in other parts |
| // of the code. |
| if (!IsLogicalHeightKnown()) |
| return 1; |
| // Our flow thread portion determines our column count. We have as many |
| // columns as needed to fit all the content. |
| LayoutUnit flow_thread_portion_height = LogicalHeightInFlowThread(); |
| if (!flow_thread_portion_height) |
| return 1; |
| |
| LayoutUnit column_height = ColumnLogicalHeight(); |
| unsigned count = (flow_thread_portion_height / column_height).Floor(); |
| // flowThreadPortionHeight may be saturated, so detect the remainder manually. |
| if (count * column_height < flow_thread_portion_height) |
| count++; |
| |
| DCHECK_GE(count, 1u); |
| return count; |
| } |
| |
| MultiColumnFragmentainerGroupList::MultiColumnFragmentainerGroupList( |
| LayoutMultiColumnSet& column_set) |
| : column_set_(column_set) { |
| Append(MultiColumnFragmentainerGroup(column_set_)); |
| } |
| |
| // An explicit empty destructor of MultiColumnFragmentainerGroupList should be |
| // in MultiColumnFragmentainerGroup.cpp, because if an implicit destructor is |
| // used, msvc 2015 tries to generate its destructor (because the class is |
| // dll-exported class) and causes a compile error because of lack of |
| // MultiColumnFragmentainerGroup::operator=. Since |
| // MultiColumnFragmentainerGroup is non-copyable, we cannot define the |
| // operator=. |
| MultiColumnFragmentainerGroupList::~MultiColumnFragmentainerGroupList() = |
| default; |
| |
| MultiColumnFragmentainerGroup& |
| MultiColumnFragmentainerGroupList::AddExtraGroup() { |
| Append(MultiColumnFragmentainerGroup(column_set_)); |
| return Last(); |
| } |
| |
| void MultiColumnFragmentainerGroupList::DeleteExtraGroups() { |
| Shrink(1); |
| } |
| |
| } // namespace blink |