blob: 4dd77f6c4465320ccdd3134108c19a6927856856 [file] [log] [blame]
// 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 "core/layout/LayoutMultiColumnSet.h"
namespace blink {
// A column balancer traverses a portion of the subtree of a flow thread that belongs to one or
// more fragmentainer groups within one column set, in order to collect certain data to be used for
// column balancing. This is an abstract class that just walks the subtree and leaves it to
// subclasses to actually collect data.
class ColumnBalancer {
protected:
ColumnBalancer(const LayoutMultiColumnSet&,
LayoutUnit logicalTopInFlowThread,
LayoutUnit logicalBottomInFlowThread);
const LayoutMultiColumnSet& columnSet() const { return m_columnSet; }
// The flow thread portion we're examining. It may be that of the entire column set, or just of
// a fragmentainer group.
const LayoutUnit logicalTopInFlowThread() const {
return m_logicalTopInFlowThread;
}
const LayoutUnit logicalBottomInFlowThread() const {
return m_logicalBottomInFlowThread;
}
const MultiColumnFragmentainerGroup& groupAtOffset(
LayoutUnit offsetInFlowThread) const {
return m_columnSet.fragmentainerGroupAtFlowThreadOffset(
offsetInFlowThread, LayoutBox::AssociateWithLatterPage);
}
LayoutUnit offsetFromColumnLogicalTop(LayoutUnit offsetInFlowThread) const {
return offsetInFlowThread -
groupAtOffset(offsetInFlowThread)
.columnLogicalTopForOffset(offsetInFlowThread);
}
// Flow thread offset for the layout object that we're currently examining.
LayoutUnit flowThreadOffset() const { return m_flowThreadOffset; }
// Return true if the specified offset is at the top of a column, as long as it's not the first
// column in the flow thread portion.
bool isFirstAfterBreak(LayoutUnit flowThreadOffset) const {
if (flowThreadOffset <= m_logicalTopInFlowThread) {
// The first column is either not after any break at all, or after a break in a
// previous fragmentainer group.
return false;
}
return flowThreadOffset ==
groupAtOffset(flowThreadOffset)
.columnLogicalTopForOffset(flowThreadOffset);
}
bool isLogicalTopWithinBounds(LayoutUnit logicalTopInFlowThread) const {
return logicalTopInFlowThread >= m_logicalTopInFlowThread &&
logicalTopInFlowThread < m_logicalBottomInFlowThread;
}
bool isLogicalBottomWithinBounds(LayoutUnit logicalBottomInFlowThread) const {
return logicalBottomInFlowThread > m_logicalTopInFlowThread &&
logicalBottomInFlowThread <= m_logicalBottomInFlowThread;
}
// Examine and collect column balancing data from a layout box that has been found to intersect
// with the flow thread portion we're examining. Does not recurse into
// children. flowThreadOffset() will return the offset from |box| to the flow thread. Two hooks
// are provided here. The first one is called right after entering and before traversing the
// subtree of the box, and the second one right after having traversed the subtree.
virtual void examineBoxAfterEntering(const LayoutBox&,
EBreak previousBreakAfterValue) = 0;
virtual void examineBoxBeforeLeaving(const LayoutBox&) = 0;
// Examine and collect column balancing data from a line that has been found to intersect with
// the flow thread portion. Does not recurse into layout objects on that line.
virtual void examineLine(const RootInlineBox&) = 0;
// Examine and collect column balancing data for everything in the flow thread portion. Will
// trigger calls to examineBoxAfterEntering(), examineBoxBeforeLeaving() and examineLine() for
// interesting boxes and lines.
void traverse();
private:
void traverseSubtree(const LayoutBox&);
const LayoutMultiColumnSet& m_columnSet;
const LayoutUnit m_logicalTopInFlowThread;
const LayoutUnit m_logicalBottomInFlowThread;
LayoutUnit m_flowThreadOffset;
};
// After an initial layout pass, we know the height of the contents of a flow thread. Based on
// this, we can estimate an initial minimal column height. This class will collect the necessary
// information from the layout objects to make this estimate. This estimate may be used to perform
// another layout iteration. If we after such a layout iteration cannot fit the contents with the
// given column height without creating overflowing columns, we will have to stretch the columns by
// some amount and lay out again. We may need to do this several times (but typically not more
// times than the number of columns that we have). The amount to stretch is provided by the sister
// of this class, named MinimumSpaceShortageFinder.
class InitialColumnHeightFinder final : public ColumnBalancer {
public:
InitialColumnHeightFinder(const LayoutMultiColumnSet&,
LayoutUnit logicalTopInFlowThread,
LayoutUnit logicalBottomInFlowThread);
LayoutUnit initialMinimalBalancedHeight() const;
// Height of the tallest piece of unbreakable content. This is the minimum column logical height
// required to avoid fragmentation where it shouldn't occur (inside unbreakable content, between
// orphans and widows, etc.). This will be used as a hint to the column balancer to help set a
// good initial column height.
LayoutUnit tallestUnbreakableLogicalHeight() const {
return m_tallestUnbreakableLogicalHeight;
}
private:
void examineBoxAfterEntering(const LayoutBox&,
EBreak previousBreakAfterValue);
void examineBoxBeforeLeaving(const LayoutBox&);
void examineLine(const RootInlineBox&);
// Record that there's a pagination strut that ends at the specified |offsetInFlowThread|, which
// is an offset exactly at the top of some column.
void recordStrutBeforeOffset(LayoutUnit offsetInFlowThread, LayoutUnit strut);
// Return the accumulated space used by struts at all column boundaries preceding the specified
// flowthread offset.
LayoutUnit spaceUsedByStrutsAt(LayoutUnit offsetInFlowThread) const;
// Add a content run, specified by its end position. A content run is appended at every
// forced/explicit break and at the end of the column set. The content runs are used to
// determine where implicit/soft breaks will occur, in order to calculate an initial column
// height.
void addContentRun(LayoutUnit endOffsetInFlowThread);
// Return the index of the content run with the currently tallest columns, taking all implicit
// breaks assumed so far into account.
unsigned contentRunIndexWithTallestColumns() const;
// Given the current list of content runs, make assumptions about where we need to insert
// implicit breaks (if there's room for any at all; depending on the number of explicit breaks),
// and store the results. This is needed in order to balance the columns.
void distributeImplicitBreaks();
// A run of content without explicit (forced) breaks; i.e. a flow thread portion between two
// explicit breaks, between flow thread start and an explicit break, between an explicit break
// and flow thread end, or, in cases when there are no explicit breaks at all: between flow
// thread portion start and flow thread portion end. We need to know where the explicit breaks
// are, in order to figure out where the implicit breaks will end up, so that we get the columns
// properly balanced. A content run starts out as representing one single column, and will
// represent one additional column for each implicit break "inserted" there.
class ContentRun {
public:
ContentRun(LayoutUnit breakOffset)
: m_breakOffset(breakOffset), m_assumedImplicitBreaks(0) {}
unsigned assumedImplicitBreaks() const { return m_assumedImplicitBreaks; }
void assumeAnotherImplicitBreak() { m_assumedImplicitBreaks++; }
LayoutUnit breakOffset() const { return m_breakOffset; }
// Return the column height that this content run would require, considering the implicit
// breaks assumed so far.
LayoutUnit columnLogicalHeight(LayoutUnit startOffset) const {
return LayoutUnit::fromFloatCeil(float(m_breakOffset - startOffset) /
float(m_assumedImplicitBreaks + 1));
}
private:
LayoutUnit m_breakOffset; // Flow thread offset where this run ends.
unsigned
m_assumedImplicitBreaks; // Number of implicit breaks in this run assumed so far.
};
Vector<ContentRun, 32> m_contentRuns;
// Shortest strut found at each column boundary (index 0 being the boundary between the first
// and the second column, index 1 being the one between the second and the third boundary, and
// so on). There may be several objects that cross the same column boundary, and we're only
// interested in the shortest one. For example, when having a float beside regular in-flow
// content, we end up with two parallel fragmentation flows [1]. The shortest strut found at a
// column boundary is the amount of space that we wasted at said column boundary, and it needs
// to be deducted when estimating the initial balanced column height, or we risk making the
// column row too tall. An entry set to LayoutUnit::max() means that we didn't detect any object
// crossing that boundary.
//
// [1] http://www.w3.org/TR/css3-break/#parallel-flows
Vector<LayoutUnit, 32> m_shortestStruts;
LayoutUnit m_tallestUnbreakableLogicalHeight;
};
// If we have previously used InitialColumnHeightFinder to estimate an initial column height, and
// that didn't result in tall enough columns, we need subsequent layout passes where we increase
// the column height by the minimum space shortage at column breaks. This class finds the minimum
// space shortage after having laid out with the current column height.
class MinimumSpaceShortageFinder final : public ColumnBalancer {
public:
MinimumSpaceShortageFinder(const LayoutMultiColumnSet&,
LayoutUnit logicalTopInFlowThread,
LayoutUnit logicalBottomInFlowThread);
LayoutUnit minimumSpaceShortage() const { return m_minimumSpaceShortage; }
unsigned forcedBreaksCount() const { return m_forcedBreaksCount; }
private:
void examineBoxAfterEntering(const LayoutBox&,
EBreak previousBreakAfterValue);
void examineBoxBeforeLeaving(const LayoutBox&);
void examineLine(const RootInlineBox&);
void recordSpaceShortage(LayoutUnit shortage) {
// Only positive values are interesting (and allowed) here. Zero space shortage may
// be reported when we're at the top of a column and the element has zero
// height.
if (shortage > 0)
m_minimumSpaceShortage = std::min(m_minimumSpaceShortage, shortage);
}
// The smallest amout of space shortage that caused a column break.
LayoutUnit m_minimumSpaceShortage;
// Set when breaking before a block, and we're looking for the first unbreakable descendant, in
// order to report correct space shortage for that one.
LayoutUnit m_pendingStrut;
unsigned m_forcedBreaksCount;
};
} // namespace blink