blob: ddc5cd2b29b0e6185ae87085e3e785bca94a046a [file] [log] [blame]
/*
* Copyright (C) 2012 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/layout/LayoutMultiColumnSet.h"
#include "core/editing/PositionWithAffinity.h"
#include "core/layout/LayoutMultiColumnFlowThread.h"
#include "core/layout/MultiColumnFragmentainerGroup.h"
#include "core/paint/MultiColumnSetPainter.h"
#include "platform/RuntimeEnabledFeatures.h"
namespace blink {
LayoutMultiColumnSet::LayoutMultiColumnSet(LayoutFlowThread* flowThread)
: LayoutBlockFlow(nullptr),
m_fragmentainerGroups(*this),
m_flowThread(flowThread),
m_initialHeightCalculated(false) {}
LayoutMultiColumnSet* LayoutMultiColumnSet::createAnonymous(
LayoutFlowThread& flowThread,
const ComputedStyle& parentStyle) {
Document& document = flowThread.document();
LayoutMultiColumnSet* layoutObject = new LayoutMultiColumnSet(&flowThread);
layoutObject->setDocumentForAnonymous(&document);
layoutObject->setStyle(ComputedStyle::createAnonymousStyleWithDisplay(
parentStyle, EDisplay::Block));
return layoutObject;
}
unsigned LayoutMultiColumnSet::fragmentainerGroupIndexAtFlowThreadOffset(
LayoutUnit flowThreadOffset,
PageBoundaryRule rule) const {
ASSERT(m_fragmentainerGroups.size() > 0);
if (flowThreadOffset <= 0)
return 0;
// TODO(mstensho): Introduce an interval tree or similar to speed up this.
for (unsigned index = 0; index < m_fragmentainerGroups.size(); index++) {
const auto& row = m_fragmentainerGroups[index];
if (rule == AssociateWithLatterPage) {
if (row.logicalTopInFlowThread() <= flowThreadOffset &&
row.logicalBottomInFlowThread() > flowThreadOffset)
return index;
} else if (row.logicalTopInFlowThread() < flowThreadOffset &&
row.logicalBottomInFlowThread() >= flowThreadOffset) {
return index;
}
}
return m_fragmentainerGroups.size() - 1;
}
const MultiColumnFragmentainerGroup&
LayoutMultiColumnSet::fragmentainerGroupAtVisualPoint(
const LayoutPoint& visualPoint) const {
ASSERT(m_fragmentainerGroups.size() > 0);
LayoutUnit blockOffset =
isHorizontalWritingMode() ? visualPoint.y() : visualPoint.x();
for (unsigned index = 0; index < m_fragmentainerGroups.size(); index++) {
const auto& row = m_fragmentainerGroups[index];
if (row.logicalTop() + row.logicalHeight() > blockOffset)
return row;
}
return m_fragmentainerGroups.last();
}
LayoutUnit LayoutMultiColumnSet::pageLogicalHeightForOffset(
LayoutUnit offsetInFlowThread) const {
const MultiColumnFragmentainerGroup& lastRow = lastFragmentainerGroup();
if (!lastRow.logicalHeight()) {
// In the first layout pass of an auto-height multicol container, height
// isn't set. No need to perform the series of complicated dance steps below
// to figure out that we should simply return 0. Bail now.
ASSERT(m_fragmentainerGroups.size() == 1);
return LayoutUnit();
}
if (offsetInFlowThread >=
lastRow.logicalTopInFlowThread() + fragmentainerGroupCapacity(lastRow)) {
// The offset is outside the bounds of the fragmentainer groups that we have
// established at this point. If we're nested inside another fragmentation
// context, we need to calculate the height on our own.
const LayoutMultiColumnFlowThread* flowThread = multiColumnFlowThread();
if (FragmentationContext* enclosingFragmentationContext =
flowThread->enclosingFragmentationContext()) {
// We'd ideally like to translate |offsetInFlowThread| to an offset in the
// coordinate space of the enclosing fragmentation context here, but
// that's hard, since the offset is out of bounds. So just use the bottom
// we have found so far.
LayoutUnit enclosingContextBottom =
lastRow.blockOffsetInEnclosingFragmentationContext() +
lastRow.logicalHeight();
LayoutUnit enclosingFragmentainerHeight =
enclosingFragmentationContext->fragmentainerLogicalHeightAt(
enclosingContextBottom);
// Constrain against specified height / max-height.
LayoutUnit currentMulticolHeight = logicalTopFromMulticolContentEdge() +
lastRow.logicalTop() +
lastRow.logicalHeight();
LayoutUnit multicolHeightWithExtraRow =
currentMulticolHeight + enclosingFragmentainerHeight;
multicolHeightWithExtraRow = std::min(
multicolHeightWithExtraRow, flowThread->maxColumnLogicalHeight());
return std::max(LayoutUnit(1),
multicolHeightWithExtraRow - currentMulticolHeight);
}
}
return fragmentainerGroupAtFlowThreadOffset(offsetInFlowThread,
AssociateWithLatterPage)
.logicalHeight();
}
LayoutUnit LayoutMultiColumnSet::pageRemainingLogicalHeightForOffset(
LayoutUnit offsetInFlowThread,
PageBoundaryRule pageBoundaryRule) const {
const MultiColumnFragmentainerGroup& row =
fragmentainerGroupAtFlowThreadOffset(offsetInFlowThread,
pageBoundaryRule);
LayoutUnit pageLogicalHeight = row.logicalHeight();
// It's not allowed to call this method if the height is unknown.
DCHECK(pageLogicalHeight);
LayoutUnit pageLogicalBottom =
row.columnLogicalTopForOffset(offsetInFlowThread) + pageLogicalHeight;
LayoutUnit remainingLogicalHeight = pageLogicalBottom - offsetInFlowThread;
if (pageBoundaryRule == AssociateWithFormerPage) {
// An offset exactly at a column boundary will act as being part of the
// former column in question (i.e. no remaining space), rather than being
// part of the latter (i.e. one whole column length of remaining space).
remainingLogicalHeight = intMod(remainingLogicalHeight, pageLogicalHeight);
} else if (!remainingLogicalHeight) {
// When pageBoundaryRule is AssociateWithLatterPage, we should never return
// 0, because if there's no space left, it means that we should be at a
// column boundary, in which case we should return the amount of space
// remaining in the *next* column. But this is not true if the offset is
// "infinite" (saturated), so allow this to happen in that case.
ASSERT(offsetInFlowThread.mightBeSaturated());
remainingLogicalHeight = pageLogicalHeight;
}
return remainingLogicalHeight;
}
bool LayoutMultiColumnSet::isPageLogicalHeightKnown() const {
return firstFragmentainerGroup().logicalHeight();
}
bool LayoutMultiColumnSet::newFragmentainerGroupsAllowed() const {
if (!isPageLogicalHeightKnown()) {
// If we have no clue about the height of the multicol container, bail. This
// situation occurs initially when an auto-height multicol container is
// nested inside another auto-height multicol container. We need at least an
// estimated height of the outer multicol container before we can check what
// an inner fragmentainer group has room for.
// Its height is indefinite for now.
return false;
}
if (isInitialHeightCalculated()) {
// We only insert additional fragmentainer groups in the initial layout
// pass. We only want to balance columns in the last fragmentainer group (if
// we need to balance at all), so we want that last fragmentainer group to
// be the same one in all layout passes that follow.
return false;
}
return true;
}
LayoutUnit LayoutMultiColumnSet::nextLogicalTopForUnbreakableContent(
LayoutUnit flowThreadOffset,
LayoutUnit contentLogicalHeight) const {
ASSERT(flowThreadOffset.mightBeSaturated() ||
pageLogicalTopForOffset(flowThreadOffset) == flowThreadOffset);
FragmentationContext* enclosingFragmentationContext =
multiColumnFlowThread()->enclosingFragmentationContext();
if (!enclosingFragmentationContext) {
// If there's no enclosing fragmentation context, there'll ever be only one
// row, and all columns there will have the same height.
return flowThreadOffset;
}
// Assert the problematic situation. If we have no problem with the column
// height, why are we even here?
ASSERT(pageLogicalHeightForOffset(flowThreadOffset) < contentLogicalHeight);
// There's a likelihood for subsequent rows to be taller than the first one.
// TODO(mstensho): if we're doubly nested (e.g. multicol in multicol in
// multicol), we need to look beyond the first row here.
const MultiColumnFragmentainerGroup& firstRow = firstFragmentainerGroup();
LayoutUnit firstRowLogicalBottomInFlowThread =
firstRow.logicalTopInFlowThread() + fragmentainerGroupCapacity(firstRow);
if (flowThreadOffset >= firstRowLogicalBottomInFlowThread)
return flowThreadOffset; // We're not in the first row. Give up.
LayoutUnit newLogicalHeight =
enclosingFragmentationContext->fragmentainerLogicalHeightAt(
firstRow.blockOffsetInEnclosingFragmentationContext() +
firstRow.logicalHeight());
if (contentLogicalHeight > newLogicalHeight) {
// The next outer column or page doesn't have enough space either. Give up
// and stay where we are.
return flowThreadOffset;
}
return firstRowLogicalBottomInFlowThread;
}
LayoutMultiColumnSet* LayoutMultiColumnSet::nextSiblingMultiColumnSet() const {
for (LayoutObject* sibling = nextSibling(); sibling;
sibling = sibling->nextSibling()) {
if (sibling->isLayoutMultiColumnSet())
return toLayoutMultiColumnSet(sibling);
}
return nullptr;
}
LayoutMultiColumnSet* LayoutMultiColumnSet::previousSiblingMultiColumnSet()
const {
for (LayoutObject* sibling = previousSibling(); sibling;
sibling = sibling->previousSibling()) {
if (sibling->isLayoutMultiColumnSet())
return toLayoutMultiColumnSet(sibling);
}
return nullptr;
}
bool LayoutMultiColumnSet::hasFragmentainerGroupForColumnAt(
LayoutUnit offsetInFlowThread,
PageBoundaryRule pageBoundaryRule) const {
const MultiColumnFragmentainerGroup& lastRow = lastFragmentainerGroup();
LayoutUnit maxLogicalBottomInFlowThread =
lastRow.logicalTopInFlowThread() + fragmentainerGroupCapacity(lastRow);
if (pageBoundaryRule == AssociateWithFormerPage)
return offsetInFlowThread <= maxLogicalBottomInFlowThread;
return offsetInFlowThread < maxLogicalBottomInFlowThread;
}
MultiColumnFragmentainerGroup&
LayoutMultiColumnSet::appendNewFragmentainerGroup() {
MultiColumnFragmentainerGroup newGroup(*this);
{ // Extra scope here for previousGroup; it's potentially invalid once we
// modify the m_fragmentainerGroups Vector.
MultiColumnFragmentainerGroup& previousGroup = m_fragmentainerGroups.last();
// This is the flow thread block offset where |previousGroup| ends and
// |newGroup| takes over.
LayoutUnit blockOffsetInFlowThread =
previousGroup.logicalTopInFlowThread() +
fragmentainerGroupCapacity(previousGroup);
previousGroup.setLogicalBottomInFlowThread(blockOffsetInFlowThread);
newGroup.setLogicalTopInFlowThread(blockOffsetInFlowThread);
newGroup.setLogicalTop(previousGroup.logicalTop() +
previousGroup.logicalHeight());
newGroup.resetColumnHeight();
}
m_fragmentainerGroups.append(newGroup);
return m_fragmentainerGroups.last();
}
LayoutUnit LayoutMultiColumnSet::logicalTopFromMulticolContentEdge() const {
// We subtract the position of the first column set or spanner placeholder,
// rather than the "before" border+padding of the multicol container. This
// distinction doesn't matter after layout, but during layout it does:
// The flow thread (i.e. the multicol contents) is laid out before the column
// sets and spanner placeholders, which means that compesating for a top
// border+padding that hasn't yet been baked into the offset will produce the
// wrong results in the first layout pass, and we'd end up performing a wasted
// layout pass in many cases.
const LayoutBox& firstColumnBox =
*multiColumnFlowThread()->firstMultiColumnBox();
// The top margin edge of the first column set or spanner placeholder is flush
// with the top content edge of the multicol container. The margin here never
// collapses with other margins, so we can just subtract it. Column sets never
// have margins, but spanner placeholders may.
LayoutUnit firstColumnBoxMarginEdge =
firstColumnBox.logicalTop() -
multiColumnBlockFlow()->marginBeforeForChild(firstColumnBox);
return logicalTop() - firstColumnBoxMarginEdge;
}
LayoutUnit LayoutMultiColumnSet::logicalTopInFlowThread() const {
return firstFragmentainerGroup().logicalTopInFlowThread();
}
LayoutUnit LayoutMultiColumnSet::logicalBottomInFlowThread() const {
return lastFragmentainerGroup().logicalBottomInFlowThread();
}
LayoutRect LayoutMultiColumnSet::flowThreadPortionOverflowRect() const {
return overflowRectForFlowThreadPortion(flowThreadPortionRect(),
!previousSiblingMultiColumnSet(),
!nextSiblingMultiColumnSet());
}
LayoutRect LayoutMultiColumnSet::overflowRectForFlowThreadPortion(
const LayoutRect& flowThreadPortionRect,
bool isFirstPortion,
bool isLastPortion) const {
if (hasOverflowClip())
return flowThreadPortionRect;
LayoutRect flowThreadOverflow = m_flowThread->visualOverflowRect();
// Only clip along the flow thread axis.
LayoutRect clipRect;
if (m_flowThread->isHorizontalWritingMode()) {
LayoutUnit minY =
isFirstPortion ? flowThreadOverflow.y() : flowThreadPortionRect.y();
LayoutUnit maxY = isLastPortion ? std::max(flowThreadPortionRect.maxY(),
flowThreadOverflow.maxY())
: flowThreadPortionRect.maxY();
LayoutUnit minX =
std::min(flowThreadPortionRect.x(), flowThreadOverflow.x());
LayoutUnit maxX =
std::max(flowThreadPortionRect.maxX(), flowThreadOverflow.maxX());
clipRect = LayoutRect(minX, minY, maxX - minX, maxY - minY);
} else {
LayoutUnit minX =
isFirstPortion ? flowThreadOverflow.x() : flowThreadPortionRect.x();
LayoutUnit maxX = isLastPortion ? std::max(flowThreadPortionRect.maxX(),
flowThreadOverflow.maxX())
: flowThreadPortionRect.maxX();
LayoutUnit minY =
std::min(flowThreadPortionRect.y(), (flowThreadOverflow.y()));
LayoutUnit maxY =
std::max(flowThreadPortionRect.y(), (flowThreadOverflow.maxY()));
clipRect = LayoutRect(minX, minY, maxX - minX, maxY - minY);
}
return clipRect;
}
bool LayoutMultiColumnSet::heightIsAuto() const {
LayoutMultiColumnFlowThread* flowThread = multiColumnFlowThread();
if (!flowThread->isLayoutPagedFlowThread()) {
// If support for the column-fill property isn't enabled, we want to behave
// as if column-fill were auto, so that multicol containers with specified
// height don't get their columns balanced (auto-height multicol containers
// will still get their columns balanced, even if column-fill isn't
// 'balance' - in accordance with the spec).
// Pretending that column-fill is auto also matches the old multicol
// implementation, which has no support for this property.
if (multiColumnBlockFlow()->style()->getColumnFill() == ColumnFillBalance)
return true;
if (LayoutBox* next = nextSiblingBox()) {
if (next->isLayoutMultiColumnSpannerPlaceholder()) {
// If we're followed by a spanner, we need to balance.
return true;
}
}
}
return !flowThread->columnHeightAvailable();
}
LayoutSize LayoutMultiColumnSet::flowThreadTranslationAtOffset(
LayoutUnit blockOffset,
PageBoundaryRule rule,
CoordinateSpaceConversion mode) const {
return fragmentainerGroupAtFlowThreadOffset(blockOffset, rule)
.flowThreadTranslationAtOffset(blockOffset, rule, mode);
}
LayoutPoint LayoutMultiColumnSet::visualPointToFlowThreadPoint(
const LayoutPoint& visualPoint) const {
const MultiColumnFragmentainerGroup& row =
fragmentainerGroupAtVisualPoint(visualPoint);
return row.visualPointToFlowThreadPoint(visualPoint -
row.offsetFromColumnSet());
}
LayoutUnit LayoutMultiColumnSet::pageLogicalTopForOffset(
LayoutUnit offset) const {
return fragmentainerGroupAtFlowThreadOffset(offset, AssociateWithLatterPage)
.columnLogicalTopForOffset(offset);
}
bool LayoutMultiColumnSet::recalculateColumnHeight() {
if (m_oldLogicalTop != logicalTop() &&
multiColumnFlowThread()->enclosingFragmentationContext()) {
// Preceding spanners or column sets have been moved or resized. This means
// that the fragmentainer groups that we have inserted need to be
// re-inserted. Restart column balancing.
resetColumnHeight();
return true;
}
bool changed = false;
for (auto& group : m_fragmentainerGroups)
changed = group.recalculateColumnHeight(*this) || changed;
m_initialHeightCalculated = true;
return changed;
}
void LayoutMultiColumnSet::resetColumnHeight() {
m_fragmentainerGroups.deleteExtraGroups();
m_fragmentainerGroups.first().resetColumnHeight();
m_tallestUnbreakableLogicalHeight = LayoutUnit();
m_initialHeightCalculated = false;
}
void LayoutMultiColumnSet::beginFlow(LayoutUnit offsetInFlowThread) {
// At this point layout is exactly at the beginning of this set. Store block
// offset from flow thread start.
m_fragmentainerGroups.first().setLogicalTopInFlowThread(offsetInFlowThread);
}
void LayoutMultiColumnSet::endFlow(LayoutUnit offsetInFlowThread) {
// At this point layout is exactly at the end of this set. Store block offset
// from flow thread start. This set is now considered "flowed", although we
// may have to revisit it later (with beginFlow()), e.g. if a subtree in the
// flow thread has to be laid out over again because the initial margin
// collapsing estimates were wrong.
m_fragmentainerGroups.last().setLogicalBottomInFlowThread(offsetInFlowThread);
}
void LayoutMultiColumnSet::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
LayoutBlockFlow::styleDidChange(diff, oldStyle);
// column-rule is specified on the parent (the multicol container) of this
// object, but it's the column sets that are in charge of painting them.
// A column rule is pretty much like any other box decoration, like borders.
// We need to say that we have box decorations here, so that the columnn set
// is invalidated when it gets laid out. We cannot check here whether the
// multicol container actually has a visible column rule or not, because we
// may not have been inserted into the tree yet. Painting a column set is
// cheap anyway, because the only thing it can paint is the column rule, while
// actual multicol content is handled by the flow thread.
setHasBoxDecorationBackground(true);
}
void LayoutMultiColumnSet::layout() {
if (recalculateColumnHeight())
multiColumnFlowThread()->setColumnHeightsChanged();
LayoutBlockFlow::layout();
}
void LayoutMultiColumnSet::computeIntrinsicLogicalWidths(
LayoutUnit& minLogicalWidth,
LayoutUnit& maxLogicalWidth) const {
minLogicalWidth = m_flowThread->minPreferredLogicalWidth();
maxLogicalWidth = m_flowThread->maxPreferredLogicalWidth();
}
void LayoutMultiColumnSet::computeLogicalHeight(
LayoutUnit,
LayoutUnit logicalTop,
LogicalExtentComputedValues& computedValues) const {
LayoutUnit logicalHeight;
for (const auto& group : m_fragmentainerGroups)
logicalHeight += group.logicalHeight();
computedValues.m_extent = logicalHeight;
computedValues.m_position = logicalTop;
}
PositionWithAffinity LayoutMultiColumnSet::positionForPoint(
const LayoutPoint& point) {
// Convert the visual point to a flow thread point.
const MultiColumnFragmentainerGroup& row =
fragmentainerGroupAtVisualPoint(point);
LayoutPoint flowThreadPoint = row.visualPointToFlowThreadPoint(
point + row.offsetFromColumnSet(),
MultiColumnFragmentainerGroup::SnapToColumn);
// Then drill into the flow thread, where we'll find the actual content.
return flowThread()->positionForPoint(flowThreadPoint);
}
LayoutUnit LayoutMultiColumnSet::columnGap() const {
LayoutBlockFlow* parentBlock = multiColumnBlockFlow();
if (parentBlock->style()->hasNormalColumnGap()) {
// "1em" is recommended as the normal gap setting. Matches <p> margins.
return LayoutUnit(
parentBlock->style()->getFontDescription().computedPixelSize());
}
return LayoutUnit(parentBlock->style()->columnGap());
}
unsigned LayoutMultiColumnSet::actualColumnCount() const {
// FIXME: remove this method. It's a meaningless question to ask the set "how
// many columns do you actually have?", since that may vary for each row.
return firstFragmentainerGroup().actualColumnCount();
}
void LayoutMultiColumnSet::paintObject(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
MultiColumnSetPainter(*this).paintObject(paintInfo, paintOffset);
}
LayoutRect LayoutMultiColumnSet::fragmentsBoundingBox(
const LayoutRect& boundingBoxInFlowThread) const {
LayoutRect result;
for (const auto& group : m_fragmentainerGroups)
result.unite(group.fragmentsBoundingBox(boundingBoxInFlowThread));
return result;
}
void LayoutMultiColumnSet::addOverflowFromChildren() {
LayoutRect overflowRect;
for (const auto& group : m_fragmentainerGroups) {
LayoutRect rect = group.calculateOverflow();
rect.move(group.offsetFromColumnSet());
overflowRect.unite(rect);
}
addLayoutOverflow(overflowRect);
addContentsVisualOverflow(overflowRect);
}
void LayoutMultiColumnSet::insertedIntoTree() {
LayoutBlockFlow::insertedIntoTree();
attachToFlowThread();
}
void LayoutMultiColumnSet::willBeRemovedFromTree() {
LayoutBlockFlow::willBeRemovedFromTree();
detachFromFlowThread();
}
void LayoutMultiColumnSet::attachToFlowThread() {
if (documentBeingDestroyed())
return;
if (!m_flowThread)
return;
m_flowThread->addColumnSetToThread(this);
}
void LayoutMultiColumnSet::detachFromFlowThread() {
if (m_flowThread) {
m_flowThread->removeColumnSetFromThread(this);
m_flowThread = 0;
}
}
LayoutRect LayoutMultiColumnSet::flowThreadPortionRect() const {
LayoutRect portionRect(LayoutUnit(), logicalTopInFlowThread(),
pageLogicalWidth(), logicalHeightInFlowThread());
if (!isHorizontalWritingMode())
return portionRect.transposedRect();
return portionRect;
}
bool LayoutMultiColumnSet::computeColumnRuleBounds(
const LayoutPoint& paintOffset,
Vector<LayoutRect>& columnRuleBounds) const {
if (flowThread()->isLayoutPagedFlowThread())
return false;
// Reference: https://www.w3.org/TR/css3-multicol/#column-gaps-and-rules
const ComputedStyle& blockStyle = multiColumnBlockFlow()->styleRef();
bool ruleTransparent = blockStyle.columnRuleIsTransparent();
EBorderStyle ruleStyle = blockStyle.columnRuleStyle();
LayoutUnit ruleThickness(blockStyle.columnRuleWidth());
LayoutUnit colGap = columnGap();
bool renderRule = ruleStyle > BorderStyleHidden && !ruleTransparent;
if (!renderRule)
return false;
unsigned colCount = actualColumnCount();
if (colCount <= 1)
return false;
bool leftToRight = style()->isLeftToRightDirection();
LayoutUnit currLogicalLeftOffset =
leftToRight ? LayoutUnit() : contentLogicalWidth();
LayoutUnit ruleAdd = borderAndPaddingLogicalLeft();
LayoutUnit ruleLogicalLeft =
leftToRight ? LayoutUnit() : contentLogicalWidth();
LayoutUnit inlineDirectionSize = pageLogicalWidth();
for (unsigned i = 0; i < colCount; i++) {
// Move to the next position.
if (leftToRight) {
ruleLogicalLeft += inlineDirectionSize + colGap / 2;
currLogicalLeftOffset += inlineDirectionSize + colGap;
} else {
ruleLogicalLeft -= (inlineDirectionSize + colGap / 2);
currLogicalLeftOffset -= (inlineDirectionSize + colGap);
}
// Now compute the final bounds.
if (i < colCount - 1) {
LayoutUnit ruleLeft, ruleRight, ruleTop, ruleBottom;
if (isHorizontalWritingMode()) {
ruleLeft =
paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd;
ruleRight = ruleLeft + ruleThickness;
ruleTop = paintOffset.y() + borderTop() + paddingTop();
ruleBottom = ruleTop + contentHeight();
} else {
ruleLeft = paintOffset.x() + borderLeft() + paddingLeft();
ruleRight = ruleLeft + contentWidth();
ruleTop =
paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd;
ruleBottom = ruleTop + ruleThickness;
}
columnRuleBounds.push_back(LayoutRect(
ruleLeft, ruleTop, ruleRight - ruleLeft, ruleBottom - ruleTop));
}
ruleLogicalLeft = currLogicalLeftOffset;
}
return true;
}
LayoutRect LayoutMultiColumnSet::localVisualRect() const {
LayoutRect blockFlowBounds = LayoutBlockFlow::localVisualRect();
// Now add in column rule bounds, if present.
Vector<LayoutRect> columnRuleBounds;
if (computeColumnRuleBounds(LayoutPoint(), columnRuleBounds)) {
for (auto& bound : columnRuleBounds)
blockFlowBounds.unite(bound);
}
return blockFlowBounds;
}
} // namespace blink