1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/layout/fragmentainer_iterator.h"
6 
7 #include "third_party/blink/renderer/core/layout/layout_multi_column_set.h"
8 
9 namespace blink {
10 
FragmentainerIterator(const LayoutFlowThread & flow_thread,const LayoutRect & physical_bounding_box_in_flow_thread,const LayoutRect & clip_rect_in_multicol_container)11 FragmentainerIterator::FragmentainerIterator(
12     const LayoutFlowThread& flow_thread,
13     const LayoutRect& physical_bounding_box_in_flow_thread,
14     const LayoutRect& clip_rect_in_multicol_container)
15     : flow_thread_(flow_thread),
16       clip_rect_in_multicol_container_(clip_rect_in_multicol_container),
17       current_fragmentainer_group_index_(0) {
18   // Put the bounds into flow thread-local coordinates by flipping it first.
19   // This is how rectangles typically are represented in layout, i.e. with the
20   // block direction coordinate flipped, if writing mode is vertical-rl.
21   LayoutRect bounds_in_flow_thread = physical_bounding_box_in_flow_thread;
22   flow_thread_.DeprecatedFlipForWritingMode(bounds_in_flow_thread);
23 
24   if (flow_thread_.IsHorizontalWritingMode()) {
25     logical_top_in_flow_thread_ = bounds_in_flow_thread.Y();
26     logical_bottom_in_flow_thread_ = bounds_in_flow_thread.MaxY();
27   } else {
28     logical_top_in_flow_thread_ = bounds_in_flow_thread.X();
29     logical_bottom_in_flow_thread_ = bounds_in_flow_thread.MaxX();
30   }
31   bounding_box_is_empty_ = bounds_in_flow_thread.IsEmpty();
32 
33   // Jump to the first interesting column set.
34   current_column_set_ = flow_thread.ColumnSetAtBlockOffset(
35       logical_top_in_flow_thread_, LayoutBox::kAssociateWithLatterPage);
36   if (!current_column_set_) {
37     SetAtEnd();
38     return;
39   }
40   // Then find the first interesting fragmentainer group.
41   current_fragmentainer_group_index_ =
42       current_column_set_->FragmentainerGroupIndexAtFlowThreadOffset(
43           logical_top_in_flow_thread_, LayoutBox::kAssociateWithLatterPage);
44 
45   // Now find the first and last fragmentainer we're interested in. We'll also
46   // clip against the clip rect here. In case the clip rect doesn't intersect
47   // with any of the fragmentainers, we have to move on to the next
48   // fragmentainer group, and see if we find something there.
49   if (!SetFragmentainersOfInterest()) {
50     MoveToNextFragmentainerGroup();
51     if (AtEnd())
52       return;
53   }
54 }
55 
Advance()56 void FragmentainerIterator::Advance() {
57   DCHECK(!AtEnd());
58 
59   if (current_fragmentainer_index_ < end_fragmentainer_index_) {
60     current_fragmentainer_index_++;
61   } else {
62     // That was the last fragmentainer to visit in this fragmentainer group.
63     // Advance to the next group.
64     MoveToNextFragmentainerGroup();
65     if (AtEnd())
66       return;
67   }
68 }
69 
PaginationOffset() const70 LayoutSize FragmentainerIterator::PaginationOffset() const {
71   return CurrentGroup().FlowThreadTranslationAtOffset(
72       FragmentainerLogicalTopInFlowThread(),
73       LayoutBox::kAssociateWithLatterPage, CoordinateSpaceConversion::kVisual);
74 }
75 
FragmentainerLogicalTopInFlowThread() const76 LayoutUnit FragmentainerIterator::FragmentainerLogicalTopInFlowThread() const {
77   DCHECK(!AtEnd());
78   const auto& group = CurrentGroup();
79   return group.LogicalTopInFlowThread() +
80          current_fragmentainer_index_ * group.ColumnLogicalHeight();
81 }
82 
ClipRectInFlowThread() const83 LayoutRect FragmentainerIterator::ClipRectInFlowThread() const {
84   DCHECK(!AtEnd());
85   LayoutRect clip_rect;
86   // An empty bounding box rect would typically be 0,0 0x0, so it would be
87   // placed in the first column always. However, the first column might not have
88   // a top edge clip (see FlowThreadPortionOverflowRectAt()). This might cause
89   // artifacts to paint outside of the column container. To avoid this
90   // situation, and since the logical bounding box is empty anyway, use the
91   // portion rect instead which is bounded on all sides. Note that we don't
92   // return an empty clip here, because an empty clip indicates that we have an
93   // empty column which may be treated differently by the calling code.
94   if (bounding_box_is_empty_) {
95     clip_rect =
96         CurrentGroup().FlowThreadPortionRectAt(current_fragmentainer_index_);
97   } else {
98     clip_rect = CurrentGroup().FlowThreadPortionOverflowRectAt(
99         current_fragmentainer_index_);
100   }
101   flow_thread_.DeprecatedFlipForWritingMode(clip_rect);
102   return clip_rect;
103 }
104 
CurrentGroup() const105 const MultiColumnFragmentainerGroup& FragmentainerIterator::CurrentGroup()
106     const {
107   DCHECK(!AtEnd());
108   return current_column_set_
109       ->FragmentainerGroups()[current_fragmentainer_group_index_];
110 }
111 
MoveToNextFragmentainerGroup()112 void FragmentainerIterator::MoveToNextFragmentainerGroup() {
113   do {
114     current_fragmentainer_group_index_++;
115     if (current_fragmentainer_group_index_ >=
116         current_column_set_->FragmentainerGroups().size()) {
117       // That was the last fragmentainer group in this set. Advance to the next.
118       current_column_set_ = current_column_set_->NextSiblingMultiColumnSet();
119       current_fragmentainer_group_index_ = 0;
120       if (!current_column_set_ ||
121           current_column_set_->LogicalTopInFlowThread() >=
122               logical_bottom_in_flow_thread_) {
123         SetAtEnd();
124         return;  // No more sets or next set out of range. We're done.
125       }
126     }
127     if (CurrentGroup().LogicalTopInFlowThread() >=
128         logical_bottom_in_flow_thread_) {
129       // This fragmentainer group doesn't intersect with the range we're
130       // interested in. We're done.
131       SetAtEnd();
132       return;
133     }
134   } while (!SetFragmentainersOfInterest());
135 }
136 
SetFragmentainersOfInterest()137 bool FragmentainerIterator::SetFragmentainersOfInterest() {
138   const MultiColumnFragmentainerGroup& group = CurrentGroup();
139 
140   // Figure out the start and end fragmentainers for the block range we're
141   // interested in. We might not have to walk the entire fragmentainer group.
142   group.ColumnIntervalForBlockRangeInFlowThread(
143       logical_top_in_flow_thread_, logical_bottom_in_flow_thread_,
144       current_fragmentainer_index_, end_fragmentainer_index_);
145 
146   if (HasClipRect()) {
147     // Now intersect with the fragmentainers that actually intersect with the
148     // visual clip rect, to narrow it down even further. The clip rect needs to
149     // be relative to the current fragmentainer group.
150     LayoutRect clip_rect = clip_rect_in_multicol_container_;
151     LayoutSize offset = group.FlowThreadTranslationAtOffset(
152         group.LogicalTopInFlowThread(), LayoutBox::kAssociateWithFormerPage,
153         CoordinateSpaceConversion::kVisual);
154     clip_rect.Move(-offset);
155     unsigned first_fragmentainer_in_clip_rect, last_fragmentainer_in_clip_rect;
156     group.ColumnIntervalForVisualRect(clip_rect,
157                                       first_fragmentainer_in_clip_rect,
158                                       last_fragmentainer_in_clip_rect);
159     // If the two fragmentainer intervals are disjoint, there's nothing of
160     // interest in this fragmentainer group.
161     if (first_fragmentainer_in_clip_rect > end_fragmentainer_index_ ||
162         last_fragmentainer_in_clip_rect < current_fragmentainer_index_)
163       return false;
164     if (current_fragmentainer_index_ < first_fragmentainer_in_clip_rect)
165       current_fragmentainer_index_ = first_fragmentainer_in_clip_rect;
166     if (end_fragmentainer_index_ > last_fragmentainer_in_clip_rect)
167       end_fragmentainer_index_ = last_fragmentainer_in_clip_rect;
168   }
169   DCHECK_GE(end_fragmentainer_index_, current_fragmentainer_index_);
170   return true;
171 }
172 
173 }  // namespace blink
174