Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2012 Apple Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS IN..0TERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "core/rendering/RenderMultiColumnFlowThread.h"
     28 
     29 #include "core/rendering/RenderMultiColumnSet.h"
     30 
     31 namespace WebCore {
     32 
     33 RenderMultiColumnFlowThread::RenderMultiColumnFlowThread()
     34     : m_columnCount(1)
     35     , m_columnHeightAvailable(0)
     36     , m_inBalancingPass(false)
     37     , m_needsColumnHeightsRecalculation(false)
     38 {
     39     setFlowThreadState(InsideInFlowThread);
     40 }
     41 
     42 RenderMultiColumnFlowThread::~RenderMultiColumnFlowThread()
     43 {
     44 }
     45 
     46 RenderMultiColumnFlowThread* RenderMultiColumnFlowThread::createAnonymous(Document& document, RenderStyle* parentStyle)
     47 {
     48     RenderMultiColumnFlowThread* renderer = new RenderMultiColumnFlowThread();
     49     renderer->setDocumentForAnonymous(&document);
     50     renderer->setStyle(RenderStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK));
     51     return renderer;
     52 }
     53 
     54 RenderMultiColumnSet* RenderMultiColumnFlowThread::firstMultiColumnSet() const
     55 {
     56     for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->nextSibling()) {
     57         if (sibling->isRenderMultiColumnSet())
     58             return toRenderMultiColumnSet(sibling);
     59     }
     60     return 0;
     61 }
     62 
     63 RenderMultiColumnSet* RenderMultiColumnFlowThread::lastMultiColumnSet() const
     64 {
     65     for (RenderObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; sibling = sibling->previousSibling()) {
     66         if (sibling->isRenderMultiColumnSet())
     67             return toRenderMultiColumnSet(sibling);
     68     }
     69     return 0;
     70 }
     71 
     72 void RenderMultiColumnFlowThread::addChild(RenderObject* newChild, RenderObject* beforeChild)
     73 {
     74     RenderBlockFlow::addChild(newChild, beforeChild);
     75     if (firstMultiColumnSet())
     76         return;
     77 
     78     // For now we only create one column set. It's created as soon as the multicol container gets
     79     // any content at all.
     80     RenderMultiColumnSet* newSet = RenderMultiColumnSet::createAnonymous(this, multiColumnBlockFlow()->style());
     81 
     82     // Need to skip RenderBlockFlow's implementation of addChild(), or we'd get redirected right
     83     // back here.
     84     multiColumnBlockFlow()->RenderBlock::addChild(newSet);
     85 
     86     invalidateRegions();
     87 }
     88 
     89 void RenderMultiColumnFlowThread::populate()
     90 {
     91     RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
     92     ASSERT(!nextSibling());
     93     // Reparent children preceding the flow thread into the flow thread. It's multicol content
     94     // now. At this point there's obviously nothing after the flow thread, but renderers (column
     95     // sets and spanners) will be inserted there as we insert elements into the flow thread.
     96     multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), this, true);
     97 }
     98 
     99 void RenderMultiColumnFlowThread::evacuateAndDestroy()
    100 {
    101     RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
    102 
    103     // Remove all sets.
    104     while (RenderMultiColumnSet* columnSet = firstMultiColumnSet())
    105         columnSet->destroy();
    106 
    107     ASSERT(!previousSibling());
    108     ASSERT(!nextSibling());
    109 
    110     // Finally we can promote all flow thread's children. Before we move them to the flow thread's
    111     // container, we need to unregister the flow thread, so that they aren't just re-added again to
    112     // the flow thread that we're trying to empty.
    113     multicolContainer->resetMultiColumnFlowThread();
    114     moveAllChildrenTo(multicolContainer, true);
    115 
    116     // FIXME: it's scary that neither destroy() nor the move*Children* methods take care of this,
    117     // and instead leave you with dangling root line box pointers. But since this is how it is done
    118     // in other parts of the code that deal with reparenting renderers, let's do the cleanup on our
    119     // own here as well.
    120     deleteLineBoxTree();
    121 
    122     destroy();
    123 }
    124 
    125 LayoutSize RenderMultiColumnFlowThread::columnOffset(const LayoutPoint& point) const
    126 {
    127     if (!hasValidRegionInfo())
    128         return LayoutSize(0, 0);
    129 
    130     LayoutPoint flowThreadPoint(point);
    131     flipForWritingMode(flowThreadPoint);
    132     LayoutUnit blockOffset = isHorizontalWritingMode() ? flowThreadPoint.y() : flowThreadPoint.x();
    133     RenderRegion* renderRegion = regionAtBlockOffset(blockOffset);
    134     if (!renderRegion)
    135         return LayoutSize(0, 0);
    136     return toRenderMultiColumnSet(renderRegion)->flowThreadTranslationAtOffset(blockOffset);
    137 }
    138 
    139 bool RenderMultiColumnFlowThread::needsNewWidth() const
    140 {
    141     LayoutUnit newWidth;
    142     unsigned dummyColumnCount; // We only care if used column-width changes.
    143     calculateColumnCountAndWidth(newWidth, dummyColumnCount);
    144     return newWidth != logicalWidth();
    145 }
    146 
    147 void RenderMultiColumnFlowThread::layoutColumns(bool relayoutChildren, SubtreeLayoutScope& layoutScope)
    148 {
    149     if (relayoutChildren)
    150         layoutScope.setChildNeedsLayout(this);
    151 
    152     if (!needsLayout()) {
    153         // Just before the multicol container (our parent RenderBlockFlow) finishes laying out, it
    154         // will call recalculateColumnHeights() on us unconditionally, but we only want that method
    155         // to do any work if we actually laid out the flow thread. Otherwise, the balancing
    156         // machinery would kick in needlessly, and trigger additional layout passes. Furthermore, we
    157         // actually depend on a proper flowthread layout pass in order to do balancing, since it's
    158         // flowthread layout that sets up content runs.
    159         m_needsColumnHeightsRecalculation = false;
    160         return;
    161     }
    162 
    163     for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) {
    164         if (!m_inBalancingPass) {
    165             // This is the initial layout pass. We need to reset the column height, because contents
    166             // typically have changed.
    167             columnSet->resetColumnHeight();
    168         }
    169     }
    170 
    171     invalidateRegions();
    172     m_needsColumnHeightsRecalculation = requiresBalancing();
    173     layout();
    174 }
    175 
    176 bool RenderMultiColumnFlowThread::recalculateColumnHeights()
    177 {
    178     // All column sets that needed layout have now been laid out, so we can finally validate them.
    179     validateRegions();
    180 
    181     if (!m_needsColumnHeightsRecalculation)
    182         return false;
    183 
    184     // Column heights may change here because of balancing. We may have to do multiple layout
    185     // passes, depending on how the contents is fitted to the changed column heights. In most
    186     // cases, laying out again twice or even just once will suffice. Sometimes we need more
    187     // passes than that, though, but the number of retries should not exceed the number of
    188     // columns, unless we have a bug.
    189     bool needsRelayout = false;
    190     for (RenderMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) {
    191         needsRelayout |= multicolSet->recalculateColumnHeight(m_inBalancingPass ? RenderMultiColumnSet::StretchBySpaceShortage : RenderMultiColumnSet::GuessFromFlowThreadPortion);
    192         if (needsRelayout) {
    193             // Once a column set gets a new column height, that column set and all successive column
    194             // sets need to be laid out over again, since their logical top will be affected by
    195             // this, and therefore their column heights may change as well, at least if the multicol
    196             // height is constrained.
    197             multicolSet->setChildNeedsLayout(MarkOnlyThis);
    198         }
    199     }
    200 
    201     if (needsRelayout)
    202         setChildNeedsLayout(MarkOnlyThis);
    203 
    204     m_inBalancingPass = needsRelayout;
    205     return needsRelayout;
    206 }
    207 
    208 void RenderMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit& width, unsigned& count) const
    209 {
    210     RenderBlock* columnBlock = multiColumnBlockFlow();
    211     const RenderStyle* columnStyle = columnBlock->style();
    212     LayoutUnit availableWidth = columnBlock->contentLogicalWidth();
    213     LayoutUnit columnGap = columnBlock->columnGap();
    214     LayoutUnit computedColumnWidth = max<LayoutUnit>(1, LayoutUnit(columnStyle->columnWidth()));
    215     unsigned computedColumnCount = max<int>(1, columnStyle->columnCount());
    216 
    217     ASSERT(!columnStyle->hasAutoColumnCount() || !columnStyle->hasAutoColumnWidth());
    218     if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) {
    219         count = computedColumnCount;
    220         width = std::max<LayoutUnit>(0, (availableWidth - ((count - 1) * columnGap)) / count);
    221     } else if (!columnStyle->hasAutoColumnWidth() && columnStyle->hasAutoColumnCount()) {
    222         count = std::max<LayoutUnit>(1, (availableWidth + columnGap) / (computedColumnWidth + columnGap));
    223         width = ((availableWidth + columnGap) / count) - columnGap;
    224     } else {
    225         count = std::max<LayoutUnit>(std::min<LayoutUnit>(computedColumnCount, (availableWidth + columnGap) / (computedColumnWidth + columnGap)), 1);
    226         width = ((availableWidth + columnGap) / count) - columnGap;
    227     }
    228 }
    229 
    230 const char* RenderMultiColumnFlowThread::renderName() const
    231 {
    232     return "RenderMultiColumnFlowThread";
    233 }
    234 
    235 void RenderMultiColumnFlowThread::addRegionToThread(RenderRegion* renderRegion)
    236 {
    237     RenderMultiColumnSet* columnSet = toRenderMultiColumnSet(renderRegion);
    238     if (RenderMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) {
    239         RenderRegionList::iterator it = m_regionList.find(nextSet);
    240         ASSERT(it != m_regionList.end());
    241         m_regionList.insertBefore(it, columnSet);
    242     } else {
    243         m_regionList.add(columnSet);
    244     }
    245     renderRegion->setIsValid(true);
    246 }
    247 
    248 void RenderMultiColumnFlowThread::willBeRemovedFromTree()
    249 {
    250     // Detach all column sets from the flow thread. Cannot destroy them at this point, since they
    251     // are siblings of this object, and there may be pointers to this object's sibling somewhere
    252     // further up on the call stack.
    253     for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet())
    254         columnSet->detachRegion();
    255     multiColumnBlockFlow()->resetMultiColumnFlowThread();
    256     RenderFlowThread::willBeRemovedFromTree();
    257 }
    258 
    259 void RenderMultiColumnFlowThread::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
    260 {
    261     // We simply remain at our intrinsic height.
    262     computedValues.m_extent = logicalHeight;
    263     computedValues.m_position = logicalTop;
    264 }
    265 
    266 void RenderMultiColumnFlowThread::updateLogicalWidth()
    267 {
    268     LayoutUnit columnWidth;
    269     calculateColumnCountAndWidth(columnWidth, m_columnCount);
    270     setLogicalWidth(columnWidth);
    271 }
    272 
    273 void RenderMultiColumnFlowThread::layout()
    274 {
    275     RenderFlowThread::layout();
    276     if (RenderMultiColumnSet* lastSet = lastMultiColumnSet())
    277         lastSet->expandToEncompassFlowThreadContentsIfNeeded();
    278 }
    279 
    280 void RenderMultiColumnFlowThread::setPageBreak(LayoutUnit offset, LayoutUnit spaceShortage)
    281 {
    282     // Only positive values are interesting (and allowed) here. Zero space shortage may be reported
    283     // when we're at the top of a column and the element has zero height. Ignore this, and also
    284     // ignore any negative values, which may occur when we set an early break in order to honor
    285     // widows in the next column.
    286     if (spaceShortage <= 0)
    287         return;
    288 
    289     if (RenderMultiColumnSet* multicolSet = toRenderMultiColumnSet(regionAtBlockOffset(offset)))
    290         multicolSet->recordSpaceShortage(spaceShortage);
    291 }
    292 
    293 void RenderMultiColumnFlowThread::updateMinimumPageHeight(LayoutUnit offset, LayoutUnit minHeight)
    294 {
    295     if (RenderMultiColumnSet* multicolSet = toRenderMultiColumnSet(regionAtBlockOffset(offset)))
    296         multicolSet->updateMinimumColumnHeight(minHeight);
    297 }
    298 
    299 RenderRegion* RenderMultiColumnFlowThread::regionAtBlockOffset(LayoutUnit /*offset*/) const
    300 {
    301     // For now there's only one column set, so this is easy:
    302     return firstMultiColumnSet();
    303 }
    304 
    305 bool RenderMultiColumnFlowThread::addForcedRegionBreak(LayoutUnit offset, RenderObject* /*breakChild*/, bool /*isBefore*/, LayoutUnit* offsetBreakAdjustment)
    306 {
    307     if (RenderMultiColumnSet* multicolSet = toRenderMultiColumnSet(regionAtBlockOffset(offset))) {
    308         multicolSet->addContentRun(offset);
    309         if (offsetBreakAdjustment)
    310             *offsetBreakAdjustment = pageLogicalHeightForOffset(offset) ? pageRemainingLogicalHeightForOffset(offset, IncludePageBoundary) : LayoutUnit();
    311         return true;
    312     }
    313     return false;
    314 }
    315 
    316 bool RenderMultiColumnFlowThread::isPageLogicalHeightKnown() const
    317 {
    318     if (RenderMultiColumnSet* columnSet = lastMultiColumnSet())
    319         return columnSet->pageLogicalHeight();
    320     return false;
    321 }
    322 
    323 }
    324