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