blob: 2668063ae26cfb9b9340e37ff34e6e2455ea9e5a [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 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 IN..0TERRUPTION) 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 "config.h"
#include "RenderMultiColumnFlowThread.h"
#include "HitTestResult.h"
#include "LayoutState.h"
#include "RenderIterator.h"
#include "RenderMultiColumnSet.h"
#include "RenderMultiColumnSpannerPlaceholder.h"
#include "RenderView.h"
#include "TransformState.h"
namespace WebCore {
bool RenderMultiColumnFlowThread::gShiftingSpanner = false;
RenderMultiColumnFlowThread::RenderMultiColumnFlowThread(Document& document, Ref<RenderStyle>&& style)
: RenderFlowThread(document, WTF::move(style))
, m_lastSetWorkedOn(nullptr)
, m_columnCount(1)
, m_columnWidth(0)
, m_columnHeightAvailable(0)
, m_inLayout(false)
, m_inBalancingPass(false)
, m_needsHeightsRecalculation(false)
, m_progressionIsInline(false)
, m_progressionIsReversed(false)
, m_beingEvacuated(false)
{
setFlowThreadState(InsideInFlowThread);
}
RenderMultiColumnFlowThread::~RenderMultiColumnFlowThread()
{
}
void RenderMultiColumnFlowThread::removeFlowChildInfo(RenderObject* child)
{
RenderFlowThread::removeFlowChildInfo(child);
flowThreadRelativeWillBeRemoved(child);
}
const char* RenderMultiColumnFlowThread::renderName() const
{
return "RenderMultiColumnFlowThread";
}
RenderMultiColumnSet* RenderMultiColumnFlowThread::firstMultiColumnSet() const
{
for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->nextSibling()) {
if (is<RenderMultiColumnSet>(*sibling))
return downcast<RenderMultiColumnSet>(sibling);
}
return nullptr;
}
RenderMultiColumnSet* RenderMultiColumnFlowThread::lastMultiColumnSet() const
{
for (RenderObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; sibling = sibling->previousSibling()) {
if (is<RenderMultiColumnSet>(*sibling))
return downcast<RenderMultiColumnSet>(sibling);
}
return nullptr;
}
RenderBox* RenderMultiColumnFlowThread::firstColumnSetOrSpanner() const
{
if (RenderObject* sibling = nextSibling()) {
ASSERT(is<RenderBox>(*sibling));
ASSERT(is<RenderMultiColumnSet>(*sibling) || findColumnSpannerPlaceholder(downcast<RenderBox>(sibling)));
return downcast<RenderBox>(sibling);
}
return nullptr;
}
RenderBox* RenderMultiColumnFlowThread::nextColumnSetOrSpannerSiblingOf(const RenderBox* child)
{
if (!child)
return nullptr;
if (RenderObject* sibling = child->nextSibling())
return downcast<RenderBox>(sibling);
return nullptr;
}
RenderBox* RenderMultiColumnFlowThread::previousColumnSetOrSpannerSiblingOf(const RenderBox* child)
{
if (!child)
return nullptr;
if (RenderObject* sibling = child->previousSibling()) {
if (is<RenderFlowThread>(*sibling))
return nullptr;
return downcast<RenderBox>(sibling);
}
return nullptr;
}
void RenderMultiColumnFlowThread::layout()
{
ASSERT(!m_inLayout);
m_inLayout = true;
m_lastSetWorkedOn = nullptr;
if (RenderBox* first = firstColumnSetOrSpanner()) {
if (is<RenderMultiColumnSet>(*first)) {
m_lastSetWorkedOn = downcast<RenderMultiColumnSet>(first);
m_lastSetWorkedOn->beginFlow(this);
}
}
RenderFlowThread::layout();
if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) {
if (!nextColumnSetOrSpannerSiblingOf(lastSet))
lastSet->endFlow(this, logicalHeight());
lastSet->expandToEncompassFlowThreadContentsIfNeeded();
}
m_inLayout = false;
m_lastSetWorkedOn = nullptr;
}
RenderMultiColumnSet* RenderMultiColumnFlowThread::findSetRendering(RenderObject* renderer) const
{
for (RenderMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) {
if (multicolSet->containsRendererInFlowThread(renderer))
return multicolSet;
}
return nullptr;
}
void RenderMultiColumnFlowThread::populate()
{
RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
ASSERT(!nextSibling());
// Reparent children preceding the flow thread into the flow thread. It's multicol content
// now. At this point there's obviously nothing after the flow thread, but renderers (column
// sets and spanners) will be inserted there as we insert elements into the flow thread.
LayoutStateDisabler layoutStateDisabler(&view());
multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), this, true);
}
void RenderMultiColumnFlowThread::evacuateAndDestroy()
{
RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
m_beingEvacuated = true;
// Delete the line box tree.
deleteLines();
LayoutStateDisabler layoutStateDisabler(&view());
// First promote all children of the flow thread. Before we move them to the flow thread's
// container, we need to unregister the flow thread, so that they aren't just re-added again to
// the flow thread that we're trying to empty.
multicolContainer->setMultiColumnFlowThread(nullptr);
moveAllChildrenTo(multicolContainer, true);
// Move spanners back to their original DOM position in the tree, and destroy the placeholders.
SpannerMap::iterator it;
while ((it = m_spannerMap.begin()) != m_spannerMap.end()) {
RenderBox* spanner = it->key;
RenderMultiColumnSpannerPlaceholder* placeholder = it->value;
RenderBlockFlow& originalContainer = downcast<RenderBlockFlow>(*placeholder->parent());
multicolContainer->removeChild(*spanner);
originalContainer.addChild(spanner, placeholder);
placeholder->destroy();
m_spannerMap.remove(it);
}
// Remove all sets.
while (RenderMultiColumnSet* columnSet = firstMultiColumnSet())
columnSet->destroy();
destroy();
}
void RenderMultiColumnFlowThread::addRegionToThread(RenderRegion* renderRegion)
{
auto* columnSet = downcast<RenderMultiColumnSet>(renderRegion);
if (RenderMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) {
RenderRegionList::iterator it = m_regionList.find(nextSet);
ASSERT(it != m_regionList.end());
m_regionList.insertBefore(it, columnSet);
} else
m_regionList.add(columnSet);
renderRegion->setIsValid(true);
}
void RenderMultiColumnFlowThread::willBeRemovedFromTree()
{
// Detach all column sets from the flow thread. Cannot destroy them at this point, since they
// are siblings of this object, and there may be pointers to this object's sibling somewhere
// further up on the call stack.
for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet())
columnSet->detachRegion();
multiColumnBlockFlow()->setMultiColumnFlowThread(nullptr);
RenderFlowThread::willBeRemovedFromTree();
}
RenderObject* RenderMultiColumnFlowThread::resolveMovedChild(RenderObject* child) const
{
if (child->style().columnSpan() != ColumnSpanAll || !is<RenderBox>(*child)) {
// We only need to resolve for column spanners.
return child;
}
// The renderer for the actual DOM node that establishes a spanner is moved from its original
// location in the render tree to becoming a sibling of the column sets. In other words, it's
// moved out from the flow thread (and becomes a sibling of it). When we for instance want to
// create and insert a renderer for the sibling node immediately preceding the spanner, we need
// to map that spanner renderer to the spanner's placeholder, which is where the new inserted
// renderer belongs.
if (RenderMultiColumnSpannerPlaceholder* placeholder = findColumnSpannerPlaceholder(downcast<RenderBox>(child)))
return placeholder;
// This is an invalid spanner, or its placeholder hasn't been created yet. This happens when
// moving an entire subtree into the flow thread, when we are processing the insertion of this
// spanner's preceding sibling, and we obviously haven't got as far as processing this spanner
// yet.
return child;
}
static bool isValidColumnSpanner(RenderMultiColumnFlowThread* flowThread, RenderObject* descendant)
{
// We assume that we're inside the flow thread. This function is not to be called otherwise.
ASSERT(descendant->isDescendantOf(flowThread));
// First make sure that the renderer itself has the right properties for becoming a spanner.
RenderStyle& style = descendant->style();
if (style.columnSpan() != ColumnSpanAll || !is<RenderBox>(*descendant) || descendant->isFloatingOrOutOfFlowPositioned())
return false;
RenderElement* container = descendant->parent();
if (!is<RenderBlockFlow>(*container) || container->childrenInline()) {
// Needs to be block-level.
return false;
}
// We need to have the flow thread as the containing block. A spanner cannot break out of the flow thread.
RenderFlowThread* enclosingFlowThread = descendant->flowThreadContainingBlock();
if (enclosingFlowThread != flowThread)
return false;
// This looks like a spanner, but if we're inside something unbreakable, it's not to be treated as one.
for (RenderBox* ancestor = downcast<RenderBox>(*descendant).containingBlock(); ancestor; ancestor = ancestor->containingBlock()) {
if (ancestor->isRenderFlowThread()) {
// Don't allow any intervening non-multicol fragmentation contexts. The spec doesn't say
// anything about disallowing this, but it's just going to be too complicated to
// implement (not to mention specify behavior).
return ancestor == flowThread;
}
ASSERT(ancestor->style().columnSpan() != ColumnSpanAll || !isValidColumnSpanner(flowThread, ancestor));
if (ancestor->isUnsplittableForPagination())
return false;
}
ASSERT_NOT_REACHED();
return false;
}
RenderObject* RenderMultiColumnFlowThread::processPossibleSpannerDescendant(RenderObject*& subtreeRoot, RenderObject* descendant)
{
RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
RenderObject* nextRendererInFlowThread = descendant->nextInPreOrderAfterChildren(this);
RenderObject* insertBeforeMulticolChild = nullptr;
RenderObject* nextDescendant = descendant;
if (isValidColumnSpanner(this, descendant)) {
// This is a spanner (column-span:all). Such renderers are moved from where they would
// otherwise occur in the render tree to becoming a direct child of the multicol container,
// so that they live among the column sets. This simplifies the layout implementation, and
// basically just relies on regular block layout done by the RenderBlockFlow that
// establishes the multicol container.
RenderBlockFlow* container = downcast<RenderBlockFlow>(descendant->parent());
RenderMultiColumnSet* setToSplit = nullptr;
if (nextRendererInFlowThread) {
setToSplit = findSetRendering(descendant);
if (setToSplit) {
setToSplit->setNeedsLayout();
insertBeforeMulticolChild = setToSplit->nextSibling();
}
}
// Moving a spanner's renderer so that it becomes a sibling of the column sets requires us
// to insert an anonymous placeholder in the tree where the spanner's renderer otherwise
// would have been. This is needed for a two reasons: We need a way of separating inline
// content before and after the spanner, so that it becomes separate line boxes. Secondly,
// this placeholder serves as a break point for column sets, so that, when encountered, we
// end flowing one column set and move to the next one.
RenderMultiColumnSpannerPlaceholder* placeholder = RenderMultiColumnSpannerPlaceholder::createAnonymous(this, downcast<RenderBox>(descendant), &container->style());
container->addChild(placeholder, descendant->nextSibling());
container->removeChild(*descendant);
// This is a guard to stop an ancestor flow thread from processing the spanner.
gShiftingSpanner = true;
multicolContainer->RenderBlock::addChild(descendant, insertBeforeMulticolChild);
gShiftingSpanner = false;
// The spanner has now been moved out from the flow thread, but we don't want to
// examine its children anyway. They are all part of the spanner and shouldn't trigger
// creation of column sets or anything like that. Continue at its original position in
// the tree, i.e. where the placeholder was just put.
if (subtreeRoot == descendant)
subtreeRoot = placeholder;
nextDescendant = placeholder;
} else {
// This is regular multicol content, i.e. not part of a spanner.
if (is<RenderMultiColumnSpannerPlaceholder>(nextRendererInFlowThread)) {
// Inserted right before a spanner. Is there a set for us there?
RenderMultiColumnSpannerPlaceholder& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*nextRendererInFlowThread);
if (RenderObject* previous = placeholder.spanner()->previousSibling()) {
if (is<RenderMultiColumnSet>(*previous))
return nextDescendant; // There's already a set there. Nothing to do.
}
insertBeforeMulticolChild = placeholder.spanner();
} else if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) {
// This child is not an immediate predecessor of a spanner, which means that if this
// child precedes a spanner at all, there has to be a column set created for us there
// already. If it doesn't precede any spanner at all, on the other hand, we need a
// column set at the end of the multicol container. We don't really check here if the
// child inserted precedes any spanner or not (as that's an expensive operation). Just
// make sure we have a column set at the end. It's no big deal if it remains unused.
if (!lastSet->nextSibling())
return nextDescendant;
}
}
// Need to create a new column set when there's no set already created. We also always insert
// another column set after a spanner. Even if it turns out that there are no renderers
// following the spanner, there may be bottom margins there, which take up space.
RenderMultiColumnSet* newSet = new RenderMultiColumnSet(*this, RenderStyle::createAnonymousStyleWithDisplay(&multicolContainer->style(), BLOCK));
newSet->initializeStyle();
multicolContainer->RenderBlock::addChild(newSet, insertBeforeMulticolChild);
invalidateRegions();
// We cannot handle immediate column set siblings at the moment (and there's no need for
// it, either). There has to be at least one spanner separating them.
ASSERT(!previousColumnSetOrSpannerSiblingOf(newSet) || !previousColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet());
ASSERT(!nextColumnSetOrSpannerSiblingOf(newSet) || !nextColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet());
return nextDescendant;
}
void RenderMultiColumnFlowThread::flowThreadDescendantInserted(RenderObject* descendant)
{
if (gShiftingSpanner || m_beingEvacuated || descendant->isInFlowRenderFlowThread())
return;
RenderObject* subtreeRoot = descendant;
for (; descendant; descendant = (descendant ? descendant->nextInPreOrder(subtreeRoot) : nullptr)) {
if (is<RenderMultiColumnSpannerPlaceholder>(*descendant)) {
// A spanner's placeholder has been inserted. The actual spanner renderer is moved from
// where it would otherwise occur (if it weren't a spanner) to becoming a sibling of the
// column sets.
RenderMultiColumnSpannerPlaceholder& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*descendant);
if (placeholder.flowThread() != this) {
// This isn't our spanner! It shifted here from an ancestor multicolumn block. It's going to end up
// becoming our spanner instead, but for it to do that we first have to nuke the original spanner,
// and get the spanner content back into this flow thread.
RenderBox* spanner = placeholder.spanner();
// Insert after the placeholder, but don't let a notification happen.
gShiftingSpanner = true;
RenderBlockFlow& ancestorBlock = downcast<RenderBlockFlow>(*spanner->parent());
ancestorBlock.moveChildTo(placeholder.parentBox(), spanner, placeholder.nextSibling(), true);
gShiftingSpanner = false;
// We have to nuke the placeholder, since the ancestor already lost the mapping to it when
// we shifted the placeholder down into this flow thread.
if (subtreeRoot == descendant)
subtreeRoot = spanner;
placeholder.parent()->removeChild(placeholder);
// Now we process the spanner.
descendant = processPossibleSpannerDescendant(subtreeRoot, spanner);
continue;
}
ASSERT(!m_spannerMap.get(placeholder.spanner()));
m_spannerMap.add(placeholder.spanner(), &placeholder);
ASSERT(!placeholder.firstChild()); // There should be no children here, but if there are, we ought to skip them.
continue;
}
descendant = processPossibleSpannerDescendant(subtreeRoot, descendant);
}
}
void RenderMultiColumnFlowThread::handleSpannerRemoval(RenderObject* spanner)
{
// The placeholder may already have been removed, but if it hasn't, do so now.
if (RenderMultiColumnSpannerPlaceholder* placeholder = m_spannerMap.get(downcast<RenderBox>(spanner))) {
placeholder->parent()->removeChild(*placeholder);
m_spannerMap.remove(downcast<RenderBox>(spanner));
}
if (RenderObject* next = spanner->nextSibling()) {
if (RenderObject* previous = spanner->previousSibling()) {
if (previous->isRenderMultiColumnSet() && next->isRenderMultiColumnSet()) {
// Merge two sets that no longer will be separated by a spanner.
next->destroy();
previous->setNeedsLayout();
}
}
}
}
void RenderMultiColumnFlowThread::flowThreadRelativeWillBeRemoved(RenderObject* relative)
{
if (m_beingEvacuated)
return;
invalidateRegions();
if (is<RenderMultiColumnSpannerPlaceholder>(*relative)) {
// Remove the map entry for this spanner, but leave the actual spanner renderer alone. Also
// keep the reference to the spanner, since the placeholder may be about to be re-inserted
// in the tree.
ASSERT(relative->isDescendantOf(this));
m_spannerMap.remove(downcast<RenderMultiColumnSpannerPlaceholder>(*relative).spanner());
return;
}
if (relative->style().columnSpan() == ColumnSpanAll) {
if (relative->parent() != parent())
return; // not a valid spanner.
handleSpannerRemoval(relative);
}
// Note that we might end up with empty column sets if all column content is removed. That's no
// big deal though (and locating them would be expensive), and they will be found and re-used if
// content is added again later.
}
void RenderMultiColumnFlowThread::flowThreadDescendantBoxLaidOut(RenderBox* descendant)
{
if (!is<RenderMultiColumnSpannerPlaceholder>(*descendant))
return;
auto& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*descendant);
RenderBlock* container = placeholder.containingBlock();
for (RenderBox* prev = previousColumnSetOrSpannerSiblingOf(placeholder.spanner()); prev; prev = previousColumnSetOrSpannerSiblingOf(prev)) {
if (is<RenderMultiColumnSet>(*prev)) {
downcast<RenderMultiColumnSet>(*prev).endFlow(container, placeholder.logicalTop());
break;
}
}
for (RenderBox* next = nextColumnSetOrSpannerSiblingOf(placeholder.spanner()); next; next = nextColumnSetOrSpannerSiblingOf(next)) {
if (is<RenderMultiColumnSet>(*next)) {
m_lastSetWorkedOn = downcast<RenderMultiColumnSet>(next);
m_lastSetWorkedOn->beginFlow(container);
break;
}
}
}
void RenderMultiColumnFlowThread::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
{
// We simply remain at our intrinsic height.
computedValues.m_extent = logicalHeight;
computedValues.m_position = logicalTop;
}
LayoutUnit RenderMultiColumnFlowThread::initialLogicalWidth() const
{
return columnWidth();
}
void RenderMultiColumnFlowThread::setPageBreak(const RenderBlock* block, LayoutUnit offset, LayoutUnit spaceShortage)
{
if (auto* multicolSet = downcast<RenderMultiColumnSet>(regionAtBlockOffset(block, offset)))
multicolSet->recordSpaceShortage(spaceShortage);
}
void RenderMultiColumnFlowThread::updateMinimumPageHeight(const RenderBlock* block, LayoutUnit offset, LayoutUnit minHeight)
{
if (auto* multicolSet = downcast<RenderMultiColumnSet>(regionAtBlockOffset(block, offset)))
multicolSet->updateMinimumColumnHeight(minHeight);
}
RenderRegion* RenderMultiColumnFlowThread::regionAtBlockOffset(const RenderBox* box, LayoutUnit offset, bool extendLastRegion) const
{
if (!m_inLayout)
return RenderFlowThread::regionAtBlockOffset(box, offset, extendLastRegion);
// Layout in progress. We are calculating the set heights as we speak, so the region range
// information is not up-to-date.
RenderMultiColumnSet* columnSet = m_lastSetWorkedOn ? m_lastSetWorkedOn : firstMultiColumnSet();
if (!columnSet) {
// If there's no set, bail. This multicol is empty or only consists of spanners. There
// are no regions.
return nullptr;
}
// The last set worked on is a good guess. But if we're not within the bounds, search for the
// right one.
if (offset < columnSet->logicalTopInFlowThread()) {
do {
if (RenderMultiColumnSet* prev = columnSet->previousSiblingMultiColumnSet())
columnSet = prev;
else
break;
} while (offset < columnSet->logicalTopInFlowThread());
} else {
while (offset >= columnSet->logicalBottomInFlowThread()) {
RenderMultiColumnSet* next = columnSet->nextSiblingMultiColumnSet();
if (!next || !next->hasBeenFlowed())
break;
columnSet = next;
}
}
return columnSet;
}
void RenderMultiColumnFlowThread::setRegionRangeForBox(const RenderBox* box, RenderRegion* startRegion, RenderRegion* endRegion)
{
// Some column sets may have zero height, which means that two or more sets may start at the
// exact same flow thread position, which means that some parts of the code may believe that a
// given box lives in sets that it doesn't really live in. Make some adjustments here and
// include such sets if they are adjacent to the start and/or end regions.
for (RenderMultiColumnSet* columnSet = downcast<RenderMultiColumnSet>(*startRegion).previousSiblingMultiColumnSet(); columnSet; columnSet = columnSet->previousSiblingMultiColumnSet()) {
if (columnSet->logicalHeightInFlowThread())
break;
startRegion = columnSet;
}
for (RenderMultiColumnSet* columnSet = downcast<RenderMultiColumnSet>(*startRegion).nextSiblingMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) {
if (columnSet->logicalHeightInFlowThread())
break;
endRegion = columnSet;
}
RenderFlowThread::setRegionRangeForBox(box, startRegion, endRegion);
}
bool RenderMultiColumnFlowThread::addForcedRegionBreak(const RenderBlock* block, LayoutUnit offset, RenderBox* /*breakChild*/, bool /*isBefore*/, LayoutUnit* offsetBreakAdjustment)
{
if (auto* multicolSet = downcast<RenderMultiColumnSet>(regionAtBlockOffset(block, offset))) {
multicolSet->addForcedBreak(offset);
if (offsetBreakAdjustment)
*offsetBreakAdjustment = pageLogicalHeightForOffset(offset) ? pageRemainingLogicalHeightForOffset(offset, IncludePageBoundary) : LayoutUnit::fromPixel(0);
return true;
}
return false;
}
void RenderMultiColumnFlowThread::computeLineGridPaginationOrigin(LayoutState& layoutState) const
{
if (!progressionIsInline())
return;
// We need to cache a line grid pagination origin so that we understand how to reset the line grid
// at the top of each column.
// Get the current line grid and offset.
const auto lineGrid = layoutState.lineGrid();
if (!lineGrid)
return;
// Get the hypothetical line box used to establish the grid.
auto lineGridBox = lineGrid->lineGridBox();
if (!lineGridBox)
return;
bool isHorizontalWritingMode = lineGrid->isHorizontalWritingMode();
LayoutUnit lineGridBlockOffset = isHorizontalWritingMode ? layoutState.lineGridOffset().height() : layoutState.lineGridOffset().width();
// Now determine our position on the grid. Our baseline needs to be adjusted to the nearest baseline multiple
// as established by the line box.
// FIXME: Need to handle crazy line-box-contain values that cause the root line box to not be considered. I assume
// the grid should honor line-box-contain.
LayoutUnit gridLineHeight = lineGridBox->lineBottomWithLeading() - lineGridBox->lineTopWithLeading();
if (!gridLineHeight)
return;
LayoutUnit firstLineTopWithLeading = lineGridBlockOffset + lineGridBox->lineTopWithLeading();
if (layoutState.isPaginated() && layoutState.pageLogicalHeight()) {
LayoutUnit pageLogicalTop = isHorizontalWritingMode ? layoutState.pageOffset().height() : layoutState.pageOffset().width();
if (pageLogicalTop > firstLineTopWithLeading) {
// Shift to the next highest line grid multiple past the page logical top. Cache the delta
// between this new value and the page logical top as the pagination origin.
LayoutUnit remainder = roundToInt(pageLogicalTop - firstLineTopWithLeading) % roundToInt(gridLineHeight);
LayoutUnit paginationDelta = gridLineHeight - remainder;
if (isHorizontalWritingMode)
layoutState.setLineGridPaginationOrigin(LayoutSize(layoutState.lineGridPaginationOrigin().width(), paginationDelta));
else
layoutState.setLineGridPaginationOrigin(LayoutSize(paginationDelta, layoutState.lineGridPaginationOrigin().height()));
}
}
}
LayoutSize RenderMultiColumnFlowThread::offsetFromContainer(RenderElement& enclosingContainer, const LayoutPoint& physicalPoint, bool* offsetDependsOnPoint) const
{
ASSERT(&enclosingContainer == container());
if (offsetDependsOnPoint)
*offsetDependsOnPoint = true;
LayoutPoint translatedPhysicalPoint(physicalPoint);
if (RenderRegion* region = physicalTranslationFromFlowToRegion(translatedPhysicalPoint))
translatedPhysicalPoint.moveBy(region->topLeftLocation());
LayoutSize offset(translatedPhysicalPoint.x(), translatedPhysicalPoint.y());
if (is<RenderBox>(enclosingContainer))
offset -= downcast<RenderBox>(enclosingContainer).scrolledContentOffset();
return offset;
}
void RenderMultiColumnFlowThread::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const
{
// First get the transform state's point into the block flow thread's physical coordinate space.
parent()->mapAbsoluteToLocalPoint(mode, transformState);
LayoutPoint transformPoint(transformState.mappedPoint());
// Now walk through each region.
const RenderMultiColumnSet* candidateColumnSet = nullptr;
LayoutPoint candidatePoint;
LayoutSize candidateContainerOffset;
for (const auto& columnSet : childrenOfType<RenderMultiColumnSet>(*parent())) {
candidateContainerOffset = columnSet.offsetFromContainer(*parent(), LayoutPoint());
candidatePoint = transformPoint - candidateContainerOffset;
candidateColumnSet = &columnSet;
// We really have no clue what to do with overflow. We'll just use the closest region to the point in that case.
LayoutUnit pointOffset = isHorizontalWritingMode() ? candidatePoint.y() : candidatePoint.x();
LayoutUnit regionOffset = isHorizontalWritingMode() ? columnSet.topLeftLocation().y() : columnSet.topLeftLocation().x();
if (pointOffset < regionOffset + columnSet.logicalHeight())
break;
}
// Once we have a good guess as to which region we hit tested through (and yes, this was just a heuristic, but it's
// the best we could do), then we can map from the region into the flow thread.
LayoutSize translationOffset = physicalTranslationFromRegionToFlow(candidateColumnSet, candidatePoint) + candidateContainerOffset;
bool preserve3D = mode & UseTransforms && (parent()->style().preserves3D() || style().preserves3D());
if (mode & UseTransforms && shouldUseTransformFromContainer(parent())) {
TransformationMatrix t;
getTransformFromContainer(parent(), translationOffset, t);
transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform);
} else
transformState.move(translationOffset.width(), translationOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform);
}
LayoutSize RenderMultiColumnFlowThread::physicalTranslationFromRegionToFlow(const RenderMultiColumnSet* columnSet, const LayoutPoint& physicalPoint) const
{
LayoutPoint logicalPoint = columnSet->flipForWritingMode(physicalPoint);
LayoutPoint translatedPoint = columnSet->translateRegionPointToFlowThread(logicalPoint);
LayoutPoint physicalTranslatedPoint = columnSet->flipForWritingMode(translatedPoint);
return physicalPoint - physicalTranslatedPoint;
}
RenderRegion* RenderMultiColumnFlowThread::mapFromFlowToRegion(TransformState& transformState) const
{
if (!hasValidRegionInfo())
return nullptr;
// Get back into our local flow thread space.
LayoutRect boxRect = transformState.mappedQuad().enclosingBoundingBox();
flipForWritingMode(boxRect);
// FIXME: We need to refactor RenderObject::absoluteQuads to be able to split the quads across regions,
// for now we just take the center of the mapped enclosing box and map it to a column.
LayoutPoint centerPoint = boxRect.center();
LayoutUnit centerLogicalOffset = isHorizontalWritingMode() ? centerPoint.y() : centerPoint.x();
RenderRegion* renderRegion = regionAtBlockOffset(this, centerLogicalOffset, true);
if (!renderRegion)
return nullptr;
transformState.move(physicalTranslationOffsetFromFlowToRegion(renderRegion, centerLogicalOffset));
return renderRegion;
}
LayoutSize RenderMultiColumnFlowThread::physicalTranslationOffsetFromFlowToRegion(const RenderRegion* renderRegion, const LayoutUnit logicalOffset) const
{
// Now that we know which multicolumn set we hit, we need to get the appropriate translation offset for the column.
const auto* columnSet = downcast<RenderMultiColumnSet>(renderRegion);
LayoutPoint translationOffset = columnSet->columnTranslationForOffset(logicalOffset);
// Now we know how we want the rect to be translated into the region. At this point we're converting
// back to physical coordinates.
if (style().isFlippedBlocksWritingMode()) {
LayoutRect portionRect(columnSet->flowThreadPortionRect());
LayoutRect columnRect = columnSet->columnRectAt(0);
LayoutUnit physicalDeltaFromPortionBottom = logicalHeight() - columnSet->logicalBottomInFlowThread();
if (isHorizontalWritingMode())
columnRect.setHeight(portionRect.height());
else
columnRect.setWidth(portionRect.width());
columnSet->flipForWritingMode(columnRect);
if (isHorizontalWritingMode())
translationOffset.move(0, columnRect.y() - portionRect.y() - physicalDeltaFromPortionBottom);
else
translationOffset.move(columnRect.x() - portionRect.x() - physicalDeltaFromPortionBottom, 0);
}
return LayoutSize(translationOffset.x(), translationOffset.y());
}
RenderRegion* RenderMultiColumnFlowThread::physicalTranslationFromFlowToRegion(LayoutPoint& physicalPoint) const
{
if (!hasValidRegionInfo())
return nullptr;
// Put the physical point into the flow thread's coordinate space.
LayoutPoint logicalPoint = flipForWritingMode(physicalPoint);
// Now get the region that we are in.
LayoutUnit logicalOffset = isHorizontalWritingMode() ? logicalPoint.y() : logicalPoint.x();
RenderRegion* renderRegion = regionAtBlockOffset(this, logicalOffset, true);
if (!renderRegion)
return nullptr;
// Translate to the coordinate space of the region.
LayoutSize translationOffset = physicalTranslationOffsetFromFlowToRegion(renderRegion, logicalOffset);
// Now shift the physical point into the region's coordinate space.
physicalPoint += translationOffset;
return renderRegion;
}
bool RenderMultiColumnFlowThread::isPageLogicalHeightKnown() const
{
if (RenderMultiColumnSet* columnSet = lastMultiColumnSet())
return columnSet->computedColumnHeight();
return false;
}
bool RenderMultiColumnFlowThread::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
{
// You cannot be inside an in-flow RenderFlowThread without a corresponding DOM node. It's better to
// just let the ancestor figure out where we are instead.
if (hitTestAction == HitTestBlockBackground)
return false;
bool inside = RenderFlowThread::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction);
if (inside && !result.innerNode())
return false;
return inside;
}
bool RenderMultiColumnFlowThread::shouldCheckColumnBreaks() const
{
if (!parent()->isRenderView())
return true;
return view().frameView().pagination().behavesLikeColumns;
}
}