Index: third_party/WebKit/Source/core/layout/ColumnBalancer.cpp |
diff --git a/third_party/WebKit/Source/core/layout/ColumnBalancer.cpp b/third_party/WebKit/Source/core/layout/ColumnBalancer.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..524d8c054e26a6efdf186efa713cf896241fc1b5 |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/layout/ColumnBalancer.cpp |
@@ -0,0 +1,244 @@ |
+// 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 "config.h" |
+ |
+#include "core/layout/ColumnBalancer.h" |
+ |
+#include "core/layout/LayoutMultiColumnSet.h" |
+ |
+namespace blink { |
+ |
+ColumnBalancer::ColumnBalancer(const MultiColumnFragmentainerGroup& group) |
+ : m_group(group) |
+{ |
+} |
+ |
+void ColumnBalancer::traverse() |
+{ |
+ traverseSubtree(*m_group.columnSet().flowThread()); |
+ ASSERT(!flowThreadOffset()); |
+} |
+ |
+void ColumnBalancer::traverseSubtree(const LayoutBox& box) |
+{ |
+ if (box.childrenInline() && box.isLayoutBlockFlow()) { |
+ // Look for breaks between lines. |
+ for (const RootInlineBox* line = toLayoutBlockFlow(box).firstRootBox(); line; line = line->nextRootBox()) { |
+ LayoutUnit lineTopInFlowThread = m_flowThreadOffset + line->lineTopWithLeading(); |
+ if (lineTopInFlowThread < group().logicalTopInFlowThread()) |
+ continue; |
+ if (lineTopInFlowThread >= group().logicalBottomInFlowThread()) |
+ break; |
+ examineLine(*line); |
+ } |
+ return; |
+ } |
+ |
+ const LayoutFlowThread* flowThread = group().columnSet().flowThread(); |
+ bool isHorizontalWritingMode = flowThread->isHorizontalWritingMode(); |
+ |
+ // Look for breaks between and inside children. |
+ for (const LayoutObject* child = box.slowFirstChild(); child; child = child->nextSibling()) { |
+ if (!child->isBox()) |
+ continue; |
+ const LayoutBox& childBox = toLayoutBox(*child); |
+ LayoutRect overflowRect = childBox.layoutOverflowRect(); |
+ LayoutUnit childLogicalBottomWithOverflow = childBox.logicalTop() + (isHorizontalWritingMode ? overflowRect.maxY() : overflowRect.maxX()); |
+ if (m_flowThreadOffset + childLogicalBottomWithOverflow <= group().logicalTopInFlowThread()) { |
+ // This child is fully above the fragmentainer group we're examining. |
+ continue; |
+ } |
+ LayoutUnit childLogicalTopWithOverflow = childBox.logicalTop() + (isHorizontalWritingMode ? overflowRect.y() : overflowRect.x()); |
+ if (m_flowThreadOffset + childLogicalTopWithOverflow >= group().logicalBottomInFlowThread()) { |
+ // This child is fully below the fragmentainer group we're examining. We cannot just |
+ // stop here, though, thanks to negative margins. So keep looking. |
+ continue; |
+ } |
+ if (childBox.isOutOfFlowPositioned() || childBox.isColumnSpanAll()) |
+ continue; |
+ |
+ // Tables are wicked. Both table rows and table cells are relative to their table section. |
+ LayoutUnit offsetForThisChild = childBox.isTableRow() ? LayoutUnit() : childBox.logicalTop(); |
+ m_flowThreadOffset += offsetForThisChild; |
+ |
+ examineBoxAfterEntering(childBox); |
+ // Unless the child is unsplittable, or if the child establishes an inner multicol |
+ // container, we descend into its subtree for further examination. |
+ if (!childBox.isUnsplittableForPagination() |
+ && (!childBox.isLayoutBlockFlow() || !toLayoutBlockFlow(childBox).multiColumnFlowThread())) |
+ traverseSubtree(childBox); |
+ examineBoxBeforeLeaving(childBox); |
+ |
+ m_flowThreadOffset -= offsetForThisChild; |
+ } |
+} |
+ |
+InitialColumnHeightFinder::InitialColumnHeightFinder(const MultiColumnFragmentainerGroup& group) |
+ : ColumnBalancer(group) |
+{ |
+ traverse(); |
+ // We have now found each explicit / forced break, and their location. Now we need to figure out |
+ // how many additional implicit / soft breaks we need and guess where they will occur, in order |
+ // to provide an initial column height. |
+ distributeImplicitBreaks(); |
+} |
+ |
+LayoutUnit InitialColumnHeightFinder::initialMinimalBalancedHeight() const |
+{ |
+ unsigned index = contentRunIndexWithTallestColumns(); |
+ LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset() : group().logicalTopInFlowThread(); |
+ return m_contentRuns[index].columnLogicalHeight(startOffset); |
+} |
+ |
+void InitialColumnHeightFinder::examineBoxAfterEntering(const LayoutBox& box) |
+{ |
+ ASSERT(isFirstAfterBreak(flowThreadOffset()) || !box.paginationStrut()); |
+ if (box.hasForcedBreakBefore()) |
+ addContentRun(flowThreadOffset()); |
+ if (box.hasForcedBreakAfter()) |
+ addContentRun(flowThreadOffset() + box.logicalHeight()); |
+} |
+ |
+void InitialColumnHeightFinder::examineBoxBeforeLeaving(const LayoutBox& box) |
+{ |
+} |
+ |
+void InitialColumnHeightFinder::examineLine(const RootInlineBox& line) |
+{ |
+} |
+ |
+void InitialColumnHeightFinder::addContentRun(LayoutUnit endOffsetInFlowThread) |
+{ |
+ if (!m_contentRuns.isEmpty() && endOffsetInFlowThread <= m_contentRuns.last().breakOffset()) |
+ return; |
+ // Append another item as long as we haven't exceeded used column count. What ends up in the |
+ // overflow area shouldn't affect column balancing. |
+ if (m_contentRuns.size() < group().columnSet().usedColumnCount()) |
+ m_contentRuns.append(ContentRun(endOffsetInFlowThread)); |
+} |
+ |
+unsigned InitialColumnHeightFinder::contentRunIndexWithTallestColumns() const |
+{ |
+ unsigned indexWithLargestHeight = 0; |
+ LayoutUnit largestHeight; |
+ LayoutUnit previousOffset = group().logicalTopInFlowThread(); |
+ size_t runCount = m_contentRuns.size(); |
+ ASSERT(runCount); |
+ for (size_t i = 0; i < runCount; i++) { |
+ const ContentRun& run = m_contentRuns[i]; |
+ LayoutUnit height = run.columnLogicalHeight(previousOffset); |
+ if (largestHeight < height) { |
+ largestHeight = height; |
+ indexWithLargestHeight = i; |
+ } |
+ previousOffset = run.breakOffset(); |
+ } |
+ return indexWithLargestHeight; |
+} |
+ |
+void InitialColumnHeightFinder::distributeImplicitBreaks() |
+{ |
+ // Insert a final content run to encompass all content. This will include overflow if this is |
+ // the last group in the multicol container. |
+ addContentRun(group().logicalBottomInFlowThread()); |
+ unsigned columnCount = m_contentRuns.size(); |
+ |
+ // If there is room for more breaks (to reach the used value of column-count), imagine that we |
+ // insert implicit breaks at suitable locations. At any given time, the content run with the |
+ // currently tallest columns will get another implicit break "inserted", which will increase its |
+ // column count by one and shrink its columns' height. Repeat until we have the desired total |
+ // number of breaks. The largest column height among the runs will then be the initial column |
+ // height for the balancer to use. |
+ while (columnCount < group().columnSet().usedColumnCount()) { |
+ unsigned index = contentRunIndexWithTallestColumns(); |
+ m_contentRuns[index].assumeAnotherImplicitBreak(); |
+ columnCount++; |
+ } |
+} |
+ |
+MinimumSpaceShortageFinder::MinimumSpaceShortageFinder(const MultiColumnFragmentainerGroup& group) |
+ : ColumnBalancer(group) |
+ , m_minimumSpaceShortage(LayoutUnit::max()) |
+ , m_pendingStrut(LayoutUnit::min()) |
+ , m_forcedBreaksCount(0) |
+{ |
+ traverse(); |
+} |
+ |
+void MinimumSpaceShortageFinder::examineBoxAfterEntering(const LayoutBox& box) |
+{ |
+ if (box.hasForcedBreakBefore()) |
+ m_forcedBreaksCount++; |
+ if (box.hasForcedBreakAfter()) |
+ m_forcedBreaksCount++; |
+ |
+ // Look for breaks before the child box. |
+ bool isFirstAfterBreak = this->isFirstAfterBreak(flowThreadOffset()); |
+ ASSERT(isFirstAfterBreak || !box.paginationStrut()); |
+ if (isFirstAfterBreak && !box.hasForcedBreakBefore()) { |
+ // This box is first after a soft break. |
+ LayoutUnit strut = box.paginationStrut(); |
+ if (box.isUnsplittableForPagination()) { |
+ // Since we cannot break inside the box, just figure out how much more space we would |
+ // need to prevent it from being pushed to the next column. |
+ recordSpaceShortage(box.logicalHeight() - strut); |
+ } else if (m_pendingStrut == LayoutUnit::min()) { |
+ // We now want to look for the first piece of unbreakable content (e.g. a line or a |
+ // block-displayed image) inside this block. That ought to be a good candidate for |
+ // minimum space shortage; a much better one than reporting space shortage for the |
+ // entire block (which we'll also do (further down), in case we couldn't find anything |
+ // more suitable). |
+ m_pendingStrut = strut; |
+ } |
+ } |
+ |
+ if (!box.isUnsplittableForPagination()) { |
+ // See if this breakable box crosses column boundaries. |
+ LayoutUnit bottomInFlowThread = flowThreadOffset() + box.logicalHeight(); |
+ if (isFirstAfterBreak || group().columnLogicalTopForOffset(flowThreadOffset()) != group().columnLogicalTopForOffset(bottomInFlowThread)) { |
+ // If the child crosses a column boundary, record space shortage, in case nothing |
+ // inside it has already done so. The column balancer needs to know by how much it |
+ // has to stretch the columns to make more content fit. If no breaks are reported |
+ // (but do occur), the balancer will have no clue. Only measure the space after the |
+ // last column boundary, in case it crosses more than one. |
+ LayoutUnit spaceUsedInLastColumn = bottomInFlowThread - group().columnLogicalTopForOffset(bottomInFlowThread); |
+ recordSpaceShortage(spaceUsedInLastColumn); |
+ } |
+ } |
+} |
+ |
+void MinimumSpaceShortageFinder::examineBoxBeforeLeaving(const LayoutBox& box) |
+{ |
+ if (m_pendingStrut == LayoutUnit::min() || !box.isUnsplittableForPagination()) |
+ return; |
+ |
+ // The previous break was before a breakable block. Here's the first piece of unbreakable |
+ // content after / inside that block. We want to record the distance from the top of the column |
+ // to the bottom of this box as space shortage. |
+ LayoutUnit logicalOffsetFromCurrentColumn = flowThreadOffset() - group().columnLogicalTopForOffset(flowThreadOffset()); |
+ recordSpaceShortage(logicalOffsetFromCurrentColumn + box.logicalHeight() - m_pendingStrut); |
+ m_pendingStrut = LayoutUnit::min(); |
+} |
+ |
+void MinimumSpaceShortageFinder::examineLine(const RootInlineBox& line) |
+{ |
+ LayoutUnit lineTop = line.lineTopWithLeading(); |
+ LayoutUnit lineTopInFlowThread = flowThreadOffset() + lineTop; |
+ LayoutUnit lineHeight = line.lineBottomWithLeading() - lineTop; |
+ if (m_pendingStrut != LayoutUnit::min()) { |
+ // The previous break was before a breakable block. Here's the first line after / inside |
+ // that block. We want to record the distance from the top of the column to the bottom of |
+ // this box as space shortage. |
+ LayoutUnit logicalOffsetFromCurrentColumn = lineTopInFlowThread - group().columnLogicalTopForOffset(lineTopInFlowThread); |
+ recordSpaceShortage(logicalOffsetFromCurrentColumn + lineHeight - m_pendingStrut); |
+ m_pendingStrut = LayoutUnit::min(); |
+ return; |
+ } |
+ ASSERT(isFirstAfterBreak(lineTopInFlowThread) || !line.paginationStrut()); |
+ if (isFirstAfterBreak(lineTopInFlowThread)) |
+ recordSpaceShortage(lineHeight - line.paginationStrut()); |
+} |
+ |
+} // namespace blink |