blob: 3bacd490d4ec1a851be613a0f9ec31f375ca95dc [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 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 "core/rendering/RenderMultiColumnFlowThread.h"
#include "core/rendering/RenderMultiColumnSet.h"
namespace blink {
RenderMultiColumnFlowThread::RenderMultiColumnFlowThread()
: m_columnCount(1)
, m_columnHeightAvailable(0)
, m_inBalancingPass(false)
, m_needsColumnHeightsRecalculation(false)
, m_progressionIsInline(true)
{
setFlowThreadState(InsideInFlowThread);
}
RenderMultiColumnFlowThread::~RenderMultiColumnFlowThread()
{
}
RenderMultiColumnFlowThread* RenderMultiColumnFlowThread::createAnonymous(Document& document, RenderStyle* parentStyle)
{
RenderMultiColumnFlowThread* renderer = new RenderMultiColumnFlowThread();
renderer->setDocumentForAnonymous(&document);
renderer->setStyle(RenderStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK));
return renderer;
}
RenderMultiColumnSet* RenderMultiColumnFlowThread::firstMultiColumnSet() const
{
for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->nextSibling()) {
if (sibling->isRenderMultiColumnSet())
return toRenderMultiColumnSet(sibling);
}
return 0;
}
RenderMultiColumnSet* RenderMultiColumnFlowThread::lastMultiColumnSet() const
{
for (RenderObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; sibling = sibling->previousSibling()) {
if (sibling->isRenderMultiColumnSet())
return toRenderMultiColumnSet(sibling);
}
return 0;
}
void RenderMultiColumnFlowThread::addChild(RenderObject* newChild, RenderObject* beforeChild)
{
RenderBlockFlow::addChild(newChild, beforeChild);
if (firstMultiColumnSet())
return;
// For now we only create one column set. It's created as soon as the multicol container gets
// any content at all.
RenderMultiColumnSet* newSet = RenderMultiColumnSet::createAnonymous(this, multiColumnBlockFlow()->style());
// Need to skip RenderBlockFlow's implementation of addChild(), or we'd get redirected right
// back here.
multiColumnBlockFlow()->RenderBlock::addChild(newSet);
invalidateRegions();
}
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.
multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), this, true);
}
void RenderMultiColumnFlowThread::evacuateAndDestroy()
{
RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
// Remove all sets.
while (RenderMultiColumnSet* columnSet = firstMultiColumnSet())
columnSet->destroy();
ASSERT(!previousSibling());
ASSERT(!nextSibling());
// Finally we can promote all flow thread's children. 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->resetMultiColumnFlowThread();
moveAllChildrenTo(multicolContainer, true);
// FIXME: it's scary that neither destroy() nor the move*Children* methods take care of this,
// and instead leave you with dangling root line box pointers. But since this is how it is done
// in other parts of the code that deal with reparenting renderers, let's do the cleanup on our
// own here as well.
deleteLineBoxTree();
destroy();
}
LayoutSize RenderMultiColumnFlowThread::columnOffset(const LayoutPoint& point) const
{
if (!hasValidRegionInfo())
return LayoutSize(0, 0);
LayoutPoint flowThreadPoint(point);
flipForWritingMode(flowThreadPoint);
LayoutUnit blockOffset = isHorizontalWritingMode() ? flowThreadPoint.y() : flowThreadPoint.x();
RenderMultiColumnSet* columnSet = columnSetAtBlockOffset(blockOffset);
if (!columnSet)
return LayoutSize(0, 0);
return columnSet->flowThreadTranslationAtOffset(blockOffset);
}
bool RenderMultiColumnFlowThread::needsNewWidth() const
{
LayoutUnit newWidth;
unsigned dummyColumnCount; // We only care if used column-width changes.
calculateColumnCountAndWidth(newWidth, dummyColumnCount);
return newWidth != logicalWidth();
}
void RenderMultiColumnFlowThread::layoutColumns(bool relayoutChildren, SubtreeLayoutScope& layoutScope)
{
if (relayoutChildren)
layoutScope.setChildNeedsLayout(this);
if (!needsLayout()) {
// Just before the multicol container (our parent RenderBlockFlow) finishes laying out, it
// will call recalculateColumnHeights() on us unconditionally, but we only want that method
// to do any work if we actually laid out the flow thread. Otherwise, the balancing
// machinery would kick in needlessly, and trigger additional layout passes. Furthermore, we
// actually depend on a proper flowthread layout pass in order to do balancing, since it's
// flowthread layout that sets up content runs.
m_needsColumnHeightsRecalculation = false;
return;
}
for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) {
if (!m_inBalancingPass) {
// This is the initial layout pass. We need to reset the column height, because contents
// typically have changed.
columnSet->resetColumnHeight();
}
}
invalidateRegions();
m_needsColumnHeightsRecalculation = heightIsAuto();
layout();
}
bool RenderMultiColumnFlowThread::recalculateColumnHeights()
{
// All column sets that needed layout have now been laid out, so we can finally validate them.
validateRegions();
if (!m_needsColumnHeightsRecalculation)
return false;
// Column heights may change here because of balancing. We may have to do multiple layout
// passes, depending on how the contents is fitted to the changed column heights. In most
// cases, laying out again twice or even just once will suffice. Sometimes we need more
// passes than that, though, but the number of retries should not exceed the number of
// columns, unless we have a bug.
bool needsRelayout = false;
for (RenderMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) {
needsRelayout |= multicolSet->recalculateColumnHeight(m_inBalancingPass ? RenderMultiColumnSet::StretchBySpaceShortage : RenderMultiColumnSet::GuessFromFlowThreadPortion);
if (needsRelayout) {
// Once a column set gets a new column height, that column set and all successive column
// sets need to be laid out over again, since their logical top will be affected by
// this, and therefore their column heights may change as well, at least if the multicol
// height is constrained.
multicolSet->setChildNeedsLayout(MarkOnlyThis);
}
}
if (needsRelayout)
setChildNeedsLayout(MarkOnlyThis);
m_inBalancingPass = needsRelayout;
return needsRelayout;
}
void RenderMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit& width, unsigned& count) const
{
RenderBlock* columnBlock = multiColumnBlockFlow();
const RenderStyle* columnStyle = columnBlock->style();
LayoutUnit availableWidth = columnBlock->contentLogicalWidth();
LayoutUnit columnGap = columnBlock->columnGap();
LayoutUnit computedColumnWidth = max<LayoutUnit>(1, LayoutUnit(columnStyle->columnWidth()));
unsigned computedColumnCount = max<int>(1, columnStyle->columnCount());
ASSERT(!columnStyle->hasAutoColumnCount() || !columnStyle->hasAutoColumnWidth());
if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) {
count = computedColumnCount;
width = std::max<LayoutUnit>(0, (availableWidth - ((count - 1) * columnGap)) / count);
} else if (!columnStyle->hasAutoColumnWidth() && columnStyle->hasAutoColumnCount()) {
count = std::max<LayoutUnit>(1, (availableWidth + columnGap) / (computedColumnWidth + columnGap));
width = ((availableWidth + columnGap) / count) - columnGap;
} else {
count = std::max<LayoutUnit>(std::min<LayoutUnit>(computedColumnCount, (availableWidth + columnGap) / (computedColumnWidth + columnGap)), 1);
width = ((availableWidth + columnGap) / count) - columnGap;
}
}
const char* RenderMultiColumnFlowThread::renderName() const
{
return "RenderMultiColumnFlowThread";
}
void RenderMultiColumnFlowThread::addRegionToThread(RenderMultiColumnSet* columnSet)
{
if (RenderMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) {
RenderMultiColumnSetList::iterator it = m_multiColumnSetList.find(nextSet);
ASSERT(it != m_multiColumnSetList.end());
m_multiColumnSetList.insertBefore(it, columnSet);
} else {
m_multiColumnSetList.add(columnSet);
}
columnSet->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()->resetMultiColumnFlowThread();
RenderFlowThread::willBeRemovedFromTree();
}
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;
}
void RenderMultiColumnFlowThread::updateLogicalWidth()
{
LayoutUnit columnWidth;
calculateColumnCountAndWidth(columnWidth, m_columnCount);
setLogicalWidth(columnWidth);
}
void RenderMultiColumnFlowThread::layout()
{
RenderFlowThread::layout();
if (RenderMultiColumnSet* lastSet = lastMultiColumnSet())
lastSet->expandToEncompassFlowThreadContentsIfNeeded();
}
void RenderMultiColumnFlowThread::setPageBreak(LayoutUnit offset, LayoutUnit spaceShortage)
{
// 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. Ignore this, and also
// ignore any negative values, which may occur when we set an early break in order to honor
// widows in the next column.
if (spaceShortage <= 0)
return;
if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset))
multicolSet->recordSpaceShortage(spaceShortage);
}
void RenderMultiColumnFlowThread::updateMinimumPageHeight(LayoutUnit offset, LayoutUnit minHeight)
{
if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset))
multicolSet->updateMinimumColumnHeight(minHeight);
}
RenderMultiColumnSet* RenderMultiColumnFlowThread::columnSetAtBlockOffset(LayoutUnit /*offset*/) const
{
// For now there's only one column set, so this is easy:
return firstMultiColumnSet();
}
bool RenderMultiColumnFlowThread::addForcedRegionBreak(LayoutUnit offset, RenderObject* /*breakChild*/, bool /*isBefore*/, LayoutUnit* offsetBreakAdjustment)
{
if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset)) {
multicolSet->addContentRun(offset);
if (offsetBreakAdjustment)
*offsetBreakAdjustment = pageLogicalHeightForOffset(offset) ? pageRemainingLogicalHeightForOffset(offset, IncludePageBoundary) : LayoutUnit();
return true;
}
return false;
}
bool RenderMultiColumnFlowThread::isPageLogicalHeightKnown() const
{
if (RenderMultiColumnSet* columnSet = lastMultiColumnSet())
return columnSet->pageLogicalHeight();
return false;
}
}