// Copyright 2016 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/fragmentainer_iterator.h" #include "third_party/blink/renderer/core/layout/layout_multi_column_set.h" namespace blink { FragmentainerIterator::FragmentainerIterator( const LayoutFlowThread& flow_thread, const LayoutRect& physical_bounding_box_in_flow_thread, const LayoutRect& clip_rect_in_multicol_container) : flow_thread_(flow_thread), clip_rect_in_multicol_container_(clip_rect_in_multicol_container), current_fragmentainer_group_index_(0) { // Put the bounds into flow thread-local coordinates by flipping it first. // This is how rectangles typically are represented in layout, i.e. with the // block direction coordinate flipped, if writing mode is vertical-rl. LayoutRect bounds_in_flow_thread = physical_bounding_box_in_flow_thread; flow_thread_.DeprecatedFlipForWritingMode(bounds_in_flow_thread); if (flow_thread_.IsHorizontalWritingMode()) { logical_top_in_flow_thread_ = bounds_in_flow_thread.Y(); logical_bottom_in_flow_thread_ = bounds_in_flow_thread.MaxY(); } else { logical_top_in_flow_thread_ = bounds_in_flow_thread.X(); logical_bottom_in_flow_thread_ = bounds_in_flow_thread.MaxX(); } bounding_box_is_empty_ = bounds_in_flow_thread.IsEmpty(); // Jump to the first interesting column set. current_column_set_ = flow_thread.ColumnSetAtBlockOffset( logical_top_in_flow_thread_, LayoutBox::kAssociateWithLatterPage); if (!current_column_set_) { SetAtEnd(); return; } // Then find the first interesting fragmentainer group. current_fragmentainer_group_index_ = current_column_set_->FragmentainerGroupIndexAtFlowThreadOffset( logical_top_in_flow_thread_, LayoutBox::kAssociateWithLatterPage); // Now find the first and last fragmentainer we're interested in. We'll also // clip against the clip rect here. In case the clip rect doesn't intersect // with any of the fragmentainers, we have to move on to the next // fragmentainer group, and see if we find something there. if (!SetFragmentainersOfInterest()) { MoveToNextFragmentainerGroup(); if (AtEnd()) return; } } void FragmentainerIterator::Advance() { DCHECK(!AtEnd()); if (current_fragmentainer_index_ < end_fragmentainer_index_) { current_fragmentainer_index_++; } else { // That was the last fragmentainer to visit in this fragmentainer group. // Advance to the next group. MoveToNextFragmentainerGroup(); if (AtEnd()) return; } } LayoutSize FragmentainerIterator::PaginationOffset() const { return CurrentGroup().FlowThreadTranslationAtOffset( FragmentainerLogicalTopInFlowThread(), LayoutBox::kAssociateWithLatterPage, CoordinateSpaceConversion::kVisual); } LayoutUnit FragmentainerIterator::FragmentainerLogicalTopInFlowThread() const { DCHECK(!AtEnd()); const auto& group = CurrentGroup(); return group.LogicalTopInFlowThread() + current_fragmentainer_index_ * group.ColumnLogicalHeight(); } LayoutRect FragmentainerIterator::ClipRectInFlowThread() const { DCHECK(!AtEnd()); LayoutRect clip_rect; // An empty bounding box rect would typically be 0,0 0x0, so it would be // placed in the first column always. However, the first column might not have // a top edge clip (see FlowThreadPortionOverflowRectAt()). This might cause // artifacts to paint outside of the column container. To avoid this // situation, and since the logical bounding box is empty anyway, use the // portion rect instead which is bounded on all sides. Note that we don't // return an empty clip here, because an empty clip indicates that we have an // empty column which may be treated differently by the calling code. if (bounding_box_is_empty_) { clip_rect = CurrentGroup().FlowThreadPortionRectAt(current_fragmentainer_index_); } else { clip_rect = CurrentGroup().FlowThreadPortionOverflowRectAt( current_fragmentainer_index_); } flow_thread_.DeprecatedFlipForWritingMode(clip_rect); return clip_rect; } const MultiColumnFragmentainerGroup& FragmentainerIterator::CurrentGroup() const { DCHECK(!AtEnd()); return current_column_set_ ->FragmentainerGroups()[current_fragmentainer_group_index_]; } void FragmentainerIterator::MoveToNextFragmentainerGroup() { do { current_fragmentainer_group_index_++; if (current_fragmentainer_group_index_ >= current_column_set_->FragmentainerGroups().size()) { // That was the last fragmentainer group in this set. Advance to the next. current_column_set_ = current_column_set_->NextSiblingMultiColumnSet(); current_fragmentainer_group_index_ = 0; if (!current_column_set_ || current_column_set_->LogicalTopInFlowThread() >= logical_bottom_in_flow_thread_) { SetAtEnd(); return; // No more sets or next set out of range. We're done. } } if (CurrentGroup().LogicalTopInFlowThread() >= logical_bottom_in_flow_thread_) { // This fragmentainer group doesn't intersect with the range we're // interested in. We're done. SetAtEnd(); return; } } while (!SetFragmentainersOfInterest()); } bool FragmentainerIterator::SetFragmentainersOfInterest() { const MultiColumnFragmentainerGroup& group = CurrentGroup(); // Figure out the start and end fragmentainers for the block range we're // interested in. We might not have to walk the entire fragmentainer group. group.ColumnIntervalForBlockRangeInFlowThread( logical_top_in_flow_thread_, logical_bottom_in_flow_thread_, current_fragmentainer_index_, end_fragmentainer_index_); if (HasClipRect()) { // Now intersect with the fragmentainers that actually intersect with the // visual clip rect, to narrow it down even further. The clip rect needs to // be relative to the current fragmentainer group. LayoutRect clip_rect = clip_rect_in_multicol_container_; LayoutSize offset = group.FlowThreadTranslationAtOffset( group.LogicalTopInFlowThread(), LayoutBox::kAssociateWithFormerPage, CoordinateSpaceConversion::kVisual); clip_rect.Move(-offset); unsigned first_fragmentainer_in_clip_rect, last_fragmentainer_in_clip_rect; group.ColumnIntervalForVisualRect(clip_rect, first_fragmentainer_in_clip_rect, last_fragmentainer_in_clip_rect); // If the two fragmentainer intervals are disjoint, there's nothing of // interest in this fragmentainer group. if (first_fragmentainer_in_clip_rect > end_fragmentainer_index_ || last_fragmentainer_in_clip_rect < current_fragmentainer_index_) return false; if (current_fragmentainer_index_ < first_fragmentainer_in_clip_rect) current_fragmentainer_index_ = first_fragmentainer_in_clip_rect; if (end_fragmentainer_index_ > last_fragmentainer_in_clip_rect) end_fragmentainer_index_ = last_fragmentainer_in_clip_rect; } DCHECK_GE(end_fragmentainer_index_, current_fragmentainer_index_); return true; } } // namespace blink