| /* |
| * 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; |
| } |
| |
| } |