OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 "config.h" |
| 6 |
| 7 #include "core/layout/ColumnBalancer.h" |
| 8 |
| 9 #include "core/layout/LayoutMultiColumnSet.h" |
| 10 |
| 11 namespace blink { |
| 12 |
| 13 ColumnBalancer::ColumnBalancer(const MultiColumnFragmentainerGroup& group) |
| 14 : m_group(group) |
| 15 { |
| 16 } |
| 17 |
| 18 void ColumnBalancer::traverse() |
| 19 { |
| 20 traverseSubtree(*m_group.columnSet().flowThread()); |
| 21 ASSERT(!flowThreadOffset()); |
| 22 } |
| 23 |
| 24 void ColumnBalancer::traverseSubtree(const LayoutBox& box) |
| 25 { |
| 26 if (box.childrenInline() && box.isLayoutBlockFlow()) { |
| 27 // Look for breaks between lines. |
| 28 for (const RootInlineBox* line = toLayoutBlockFlow(box).firstRootBox();
line; line = line->nextRootBox()) { |
| 29 LayoutUnit lineTopInFlowThread = m_flowThreadOffset + line->lineTopW
ithLeading(); |
| 30 if (lineTopInFlowThread < group().logicalTopInFlowThread()) |
| 31 continue; |
| 32 if (lineTopInFlowThread >= group().logicalBottomInFlowThread()) |
| 33 break; |
| 34 examineLine(*line); |
| 35 } |
| 36 return; |
| 37 } |
| 38 |
| 39 const LayoutFlowThread* flowThread = group().columnSet().flowThread(); |
| 40 bool isHorizontalWritingMode = flowThread->isHorizontalWritingMode(); |
| 41 |
| 42 // Look for breaks between and inside children. |
| 43 for (const LayoutObject* child = box.slowFirstChild(); child; child = child-
>nextSibling()) { |
| 44 if (!child->isBox()) |
| 45 continue; |
| 46 const LayoutBox& childBox = toLayoutBox(*child); |
| 47 LayoutRect overflowRect = childBox.layoutOverflowRect(); |
| 48 LayoutUnit childLogicalBottomWithOverflow = childBox.logicalTop() + (isH
orizontalWritingMode ? overflowRect.maxY() : overflowRect.maxX()); |
| 49 if (m_flowThreadOffset + childLogicalBottomWithOverflow <= group().logic
alTopInFlowThread()) { |
| 50 // This child is fully above the fragmentainer group we're examining
. |
| 51 continue; |
| 52 } |
| 53 LayoutUnit childLogicalTopWithOverflow = childBox.logicalTop() + (isHori
zontalWritingMode ? overflowRect.y() : overflowRect.x()); |
| 54 if (m_flowThreadOffset + childLogicalTopWithOverflow >= group().logicalB
ottomInFlowThread()) { |
| 55 // This child is fully below the fragmentainer group we're examining
. We cannot just |
| 56 // stop here, though, thanks to negative margins. So keep looking. |
| 57 continue; |
| 58 } |
| 59 if (childBox.isOutOfFlowPositioned() || childBox.isColumnSpanAll()) |
| 60 continue; |
| 61 |
| 62 // Tables are wicked. Both table rows and table cells are relative to th
eir table section. |
| 63 LayoutUnit offsetForThisChild = childBox.isTableRow() ? LayoutUnit() : c
hildBox.logicalTop(); |
| 64 m_flowThreadOffset += offsetForThisChild; |
| 65 |
| 66 examineBoxAfterEntering(childBox); |
| 67 // Unless the child is unsplittable, or if the child establishes an inne
r multicol |
| 68 // container, we descend into its subtree for further examination. |
| 69 if (!childBox.isUnsplittableForPagination() |
| 70 && (!childBox.isLayoutBlockFlow() || !toLayoutBlockFlow(childBox).mu
ltiColumnFlowThread())) |
| 71 traverseSubtree(childBox); |
| 72 examineBoxBeforeLeaving(childBox); |
| 73 |
| 74 m_flowThreadOffset -= offsetForThisChild; |
| 75 } |
| 76 } |
| 77 |
| 78 InitialColumnHeightFinder::InitialColumnHeightFinder(const MultiColumnFragmentai
nerGroup& group) |
| 79 : ColumnBalancer(group) |
| 80 { |
| 81 traverse(); |
| 82 // We have now found each explicit / forced break, and their location. Now w
e need to figure out |
| 83 // how many additional implicit / soft breaks we need and guess where they w
ill occur, in order |
| 84 // to provide an initial column height. |
| 85 distributeImplicitBreaks(); |
| 86 } |
| 87 |
| 88 LayoutUnit InitialColumnHeightFinder::initialMinimalBalancedHeight() const |
| 89 { |
| 90 unsigned index = contentRunIndexWithTallestColumns(); |
| 91 LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset()
: group().logicalTopInFlowThread(); |
| 92 return m_contentRuns[index].columnLogicalHeight(startOffset); |
| 93 } |
| 94 |
| 95 void InitialColumnHeightFinder::examineBoxAfterEntering(const LayoutBox& box) |
| 96 { |
| 97 ASSERT(isFirstAfterBreak(flowThreadOffset()) || !box.paginationStrut()); |
| 98 if (box.hasForcedBreakBefore()) |
| 99 addContentRun(flowThreadOffset()); |
| 100 if (box.hasForcedBreakAfter()) |
| 101 addContentRun(flowThreadOffset() + box.logicalHeight()); |
| 102 } |
| 103 |
| 104 void InitialColumnHeightFinder::examineBoxBeforeLeaving(const LayoutBox& box) |
| 105 { |
| 106 } |
| 107 |
| 108 void InitialColumnHeightFinder::examineLine(const RootInlineBox& line) |
| 109 { |
| 110 } |
| 111 |
| 112 void InitialColumnHeightFinder::addContentRun(LayoutUnit endOffsetInFlowThread) |
| 113 { |
| 114 if (!m_contentRuns.isEmpty() && endOffsetInFlowThread <= m_contentRuns.last(
).breakOffset()) |
| 115 return; |
| 116 // Append another item as long as we haven't exceeded used column count. Wha
t ends up in the |
| 117 // overflow area shouldn't affect column balancing. |
| 118 if (m_contentRuns.size() < group().columnSet().usedColumnCount()) |
| 119 m_contentRuns.append(ContentRun(endOffsetInFlowThread)); |
| 120 } |
| 121 |
| 122 unsigned InitialColumnHeightFinder::contentRunIndexWithTallestColumns() const |
| 123 { |
| 124 unsigned indexWithLargestHeight = 0; |
| 125 LayoutUnit largestHeight; |
| 126 LayoutUnit previousOffset = group().logicalTopInFlowThread(); |
| 127 size_t runCount = m_contentRuns.size(); |
| 128 ASSERT(runCount); |
| 129 for (size_t i = 0; i < runCount; i++) { |
| 130 const ContentRun& run = m_contentRuns[i]; |
| 131 LayoutUnit height = run.columnLogicalHeight(previousOffset); |
| 132 if (largestHeight < height) { |
| 133 largestHeight = height; |
| 134 indexWithLargestHeight = i; |
| 135 } |
| 136 previousOffset = run.breakOffset(); |
| 137 } |
| 138 return indexWithLargestHeight; |
| 139 } |
| 140 |
| 141 void InitialColumnHeightFinder::distributeImplicitBreaks() |
| 142 { |
| 143 // Insert a final content run to encompass all content. This will include ov
erflow if this is |
| 144 // the last group in the multicol container. |
| 145 addContentRun(group().logicalBottomInFlowThread()); |
| 146 unsigned columnCount = m_contentRuns.size(); |
| 147 |
| 148 // If there is room for more breaks (to reach the used value of column-count
), imagine that we |
| 149 // insert implicit breaks at suitable locations. At any given time, the cont
ent run with the |
| 150 // currently tallest columns will get another implicit break "inserted", whi
ch will increase its |
| 151 // column count by one and shrink its columns' height. Repeat until we have
the desired total |
| 152 // number of breaks. The largest column height among the runs will then be t
he initial column |
| 153 // height for the balancer to use. |
| 154 while (columnCount < group().columnSet().usedColumnCount()) { |
| 155 unsigned index = contentRunIndexWithTallestColumns(); |
| 156 m_contentRuns[index].assumeAnotherImplicitBreak(); |
| 157 columnCount++; |
| 158 } |
| 159 } |
| 160 |
| 161 MinimumSpaceShortageFinder::MinimumSpaceShortageFinder(const MultiColumnFragment
ainerGroup& group) |
| 162 : ColumnBalancer(group) |
| 163 , m_minimumSpaceShortage(LayoutUnit::max()) |
| 164 , m_pendingStrut(LayoutUnit::min()) |
| 165 , m_forcedBreaksCount(0) |
| 166 { |
| 167 traverse(); |
| 168 } |
| 169 |
| 170 void MinimumSpaceShortageFinder::examineBoxAfterEntering(const LayoutBox& box) |
| 171 { |
| 172 if (box.hasForcedBreakBefore()) |
| 173 m_forcedBreaksCount++; |
| 174 if (box.hasForcedBreakAfter()) |
| 175 m_forcedBreaksCount++; |
| 176 |
| 177 // Look for breaks before the child box. |
| 178 bool isFirstAfterBreak = this->isFirstAfterBreak(flowThreadOffset()); |
| 179 ASSERT(isFirstAfterBreak || !box.paginationStrut()); |
| 180 if (isFirstAfterBreak && !box.hasForcedBreakBefore()) { |
| 181 // This box is first after a soft break. |
| 182 LayoutUnit strut = box.paginationStrut(); |
| 183 if (box.isUnsplittableForPagination()) { |
| 184 // Since we cannot break inside the box, just figure out how much mo
re space we would |
| 185 // need to prevent it from being pushed to the next column. |
| 186 recordSpaceShortage(box.logicalHeight() - strut); |
| 187 } else if (m_pendingStrut == LayoutUnit::min()) { |
| 188 // We now want to look for the first piece of unbreakable content (e
.g. a line or a |
| 189 // block-displayed image) inside this block. That ought to be a good
candidate for |
| 190 // minimum space shortage; a much better one than reporting space sh
ortage for the |
| 191 // entire block (which we'll also do (further down), in case we coul
dn't find anything |
| 192 // more suitable). |
| 193 m_pendingStrut = strut; |
| 194 } |
| 195 } |
| 196 |
| 197 if (!box.isUnsplittableForPagination()) { |
| 198 // See if this breakable box crosses column boundaries. |
| 199 LayoutUnit bottomInFlowThread = flowThreadOffset() + box.logicalHeight()
; |
| 200 if (isFirstAfterBreak || group().columnLogicalTopForOffset(flowThreadOff
set()) != group().columnLogicalTopForOffset(bottomInFlowThread)) { |
| 201 // If the child crosses a column boundary, record space shortage, in
case nothing |
| 202 // inside it has already done so. The column balancer needs to know
by how much it |
| 203 // has to stretch the columns to make more content fit. If no breaks
are reported |
| 204 // (but do occur), the balancer will have no clue. Only measure the
space after the |
| 205 // last column boundary, in case it crosses more than one. |
| 206 LayoutUnit spaceUsedInLastColumn = bottomInFlowThread - group().colu
mnLogicalTopForOffset(bottomInFlowThread); |
| 207 recordSpaceShortage(spaceUsedInLastColumn); |
| 208 } |
| 209 } |
| 210 } |
| 211 |
| 212 void MinimumSpaceShortageFinder::examineBoxBeforeLeaving(const LayoutBox& box) |
| 213 { |
| 214 if (m_pendingStrut == LayoutUnit::min() || !box.isUnsplittableForPagination(
)) |
| 215 return; |
| 216 |
| 217 // The previous break was before a breakable block. Here's the first piece o
f unbreakable |
| 218 // content after / inside that block. We want to record the distance from th
e top of the column |
| 219 // to the bottom of this box as space shortage. |
| 220 LayoutUnit logicalOffsetFromCurrentColumn = flowThreadOffset() - group().col
umnLogicalTopForOffset(flowThreadOffset()); |
| 221 recordSpaceShortage(logicalOffsetFromCurrentColumn + box.logicalHeight() - m
_pendingStrut); |
| 222 m_pendingStrut = LayoutUnit::min(); |
| 223 } |
| 224 |
| 225 void MinimumSpaceShortageFinder::examineLine(const RootInlineBox& line) |
| 226 { |
| 227 LayoutUnit lineTop = line.lineTopWithLeading(); |
| 228 LayoutUnit lineTopInFlowThread = flowThreadOffset() + lineTop; |
| 229 LayoutUnit lineHeight = line.lineBottomWithLeading() - lineTop; |
| 230 if (m_pendingStrut != LayoutUnit::min()) { |
| 231 // The previous break was before a breakable block. Here's the first lin
e after / inside |
| 232 // that block. We want to record the distance from the top of the column
to the bottom of |
| 233 // this box as space shortage. |
| 234 LayoutUnit logicalOffsetFromCurrentColumn = lineTopInFlowThread - group(
).columnLogicalTopForOffset(lineTopInFlowThread); |
| 235 recordSpaceShortage(logicalOffsetFromCurrentColumn + lineHeight - m_pend
ingStrut); |
| 236 m_pendingStrut = LayoutUnit::min(); |
| 237 return; |
| 238 } |
| 239 ASSERT(isFirstAfterBreak(lineTopInFlowThread) || !line.paginationStrut()); |
| 240 if (isFirstAfterBreak(lineTopInFlowThread)) |
| 241 recordSpaceShortage(lineHeight - line.paginationStrut()); |
| 242 } |
| 243 |
| 244 } // namespace blink |
OLD | NEW |