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 blink {
     32 
     33 RenderMultiColumnFlowThread::RenderMultiColumnFlowThread()
     34     : m_columnCount(1)
     35     , m_columnHeightAvailable(0)
     36     , m_inBalancingPass(false)
     37     , m_needsColumnHeightsRecalculation(false)
     38     , m_progressionIsInline(true)
     39 {
     40     setFlowThreadState(InsideInFlowThread);
     41 }
     42 
     43 RenderMultiColumnFlowThread::~RenderMultiColumnFlowThread()
     44 {
     45 }
     46 
     47 RenderMultiColumnFlowThread* RenderMultiColumnFlowThread::createAnonymous(Document& document, RenderStyle* parentStyle)
     48 {
     49     RenderMultiColumnFlowThread* renderer = new RenderMultiColumnFlowThread();
     50     renderer->setDocumentForAnonymous(&document);
     51     renderer->setStyle(RenderStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK));
     52     return renderer;
     53 }
     54 
     55 RenderMultiColumnSet* RenderMultiColumnFlowThread::firstMultiColumnSet() const
     56 {
     57     for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->nextSibling()) {
     58         if (sibling->isRenderMultiColumnSet())
     59             return toRenderMultiColumnSet(sibling);
     60     }
     61     return 0;
     62 }
     63 
     64 RenderMultiColumnSet* RenderMultiColumnFlowThread::lastMultiColumnSet() const
     65 {
     66     for (RenderObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; sibling = sibling->previousSibling()) {
     67         if (sibling->isRenderMultiColumnSet())
     68             return toRenderMultiColumnSet(sibling);
     69     }
     70     return 0;
     71 }
     72 
     73 void RenderMultiColumnFlowThread::addChild(RenderObject* newChild, RenderObject* beforeChild)
     74 {
     75     RenderBlockFlow::addChild(newChild, beforeChild);
     76     if (firstMultiColumnSet())
     77         return;
     78 
     79     // For now we only create one column set. It's created as soon as the multicol container gets
     80     // any content at all.
     81     RenderMultiColumnSet* newSet = RenderMultiColumnSet::createAnonymous(this, multiColumnBlockFlow()->style());
     82 
     83     // Need to skip RenderBlockFlow's implementation of addChild(), or we'd get redirected right
     84     // back here.
     85     multiColumnBlockFlow()->RenderBlock::addChild(newSet);
     86 
     87     invalidateRegions();
     88 }
     89 
     90 void RenderMultiColumnFlowThread::populate()
     91 {
     92     RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
     93     ASSERT(!nextSibling());
     94     // Reparent children preceding the flow thread into the flow thread. It's multicol content
     95     // now. At this point there's obviously nothing after the flow thread, but renderers (column
     96     // sets and spanners) will be inserted there as we insert elements into the flow thread.
     97     multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), this, true);
     98 }
     99 
    100 void RenderMultiColumnFlowThread::evacuateAndDestroy()
    101 {
    102     RenderBlockFlow* multicolContainer = multiColumnBlockFlow();
    103 
    104     // Remove all sets.
    105     while (RenderMultiColumnSet* columnSet = firstMultiColumnSet())
    106         columnSet->destroy();
    107 
    108     ASSERT(!previousSibling());
    109     ASSERT(!nextSibling());
    110 
    111     // Finally we can promote all flow thread's children. Before we move them to the flow thread's
    112     // container, we need to unregister the flow thread, so that they aren't just re-added again to
    113     // the flow thread that we're trying to empty.
    114     multicolContainer->resetMultiColumnFlowThread();
    115     moveAllChildrenTo(multicolContainer, true);
    116 
    117     // FIXME: it's scary that neither destroy() nor the move*Children* methods take care of this,
    118     // and instead leave you with dangling root line box pointers. But since this is how it is done
    119     // in other parts of the code that deal with reparenting renderers, let's do the cleanup on our
    120     // own here as well.
    121     deleteLineBoxTree();
    122 
    123     destroy();
    124 }
    125 
    126 LayoutSize RenderMultiColumnFlowThread::columnOffset(const LayoutPoint& point) const
    127 {
    128     if (!hasValidRegionInfo())
    129         return LayoutSize(0, 0);
    130 
    131     LayoutPoint flowThreadPoint(point);
    132     flipForWritingMode(flowThreadPoint);
    133     LayoutUnit blockOffset = isHorizontalWritingMode() ? flowThreadPoint.y() : flowThreadPoint.x();
    134     RenderMultiColumnSet* columnSet = columnSetAtBlockOffset(blockOffset);
    135     if (!columnSet)
    136         return LayoutSize(0, 0);
    137     return columnSet->flowThreadTranslationAtOffset(blockOffset);
    138 }
    139 
    140 bool RenderMultiColumnFlowThread::needsNewWidth() const
    141 {
    142     LayoutUnit newWidth;
    143     unsigned dummyColumnCount; // We only care if used column-width changes.
    144     calculateColumnCountAndWidth(newWidth, dummyColumnCount);
    145     return newWidth != logicalWidth();
    146 }
    147 
    148 void RenderMultiColumnFlowThread::layoutColumns(bool relayoutChildren, SubtreeLayoutScope& layoutScope)
    149 {
    150     if (relayoutChildren)
    151         layoutScope.setChildNeedsLayout(this);
    152 
    153     if (!needsLayout()) {
    154         // Just before the multicol container (our parent RenderBlockFlow) finishes laying out, it
    155         // will call recalculateColumnHeights() on us unconditionally, but we only want that method
    156         // to do any work if we actually laid out the flow thread. Otherwise, the balancing
    157         // machinery would kick in needlessly, and trigger additional layout passes. Furthermore, we
    158         // actually depend on a proper flowthread layout pass in order to do balancing, since it's
    159         // flowthread layout that sets up content runs.
    160         m_needsColumnHeightsRecalculation = false;
    161         return;
    162     }
    163 
    164     for (RenderMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) {
    165         if (!m_inBalancingPass) {
    166             // This is the initial layout pass. We need to reset the column height, because contents
    167             // typically have changed.
    168             columnSet->resetColumnHeight();
    169         }
    170     }
    171 
    172     invalidateRegions();
    173     m_needsColumnHeightsRecalculation = heightIsAuto();
    174     layout();
    175 }
    176 
    177 bool RenderMultiColumnFlowThread::recalculateColumnHeights()
    178 {
    179     // All column sets that needed layout have now been laid out, so we can finally validate them.
    180     validateRegions();
    181 
    182     if (!m_needsColumnHeightsRecalculation)
    183         return false;
    184 
    185     // Column heights may change here because of balancing. We may have to do multiple layout
    186     // passes, depending on how the contents is fitted to the changed column heights. In most
    187     // cases, laying out again twice or even just once will suffice. Sometimes we need more
    188     // passes than that, though, but the number of retries should not exceed the number of
    189     // columns, unless we have a bug.
    190     bool needsRelayout = false;
    191     for (RenderMultiColumnSet* multicolSet = firstMultiColumnSet(); multicolSet; multicolSet = multicolSet->nextSiblingMultiColumnSet()) {
    192         needsRelayout |= multicolSet->recalculateColumnHeight(m_inBalancingPass ? RenderMultiColumnSet::StretchBySpaceShortage : RenderMultiColumnSet::GuessFromFlowThreadPortion);
    193         if (needsRelayout) {
    194             // Once a column set gets a new column height, that column set and all successive column
    195             // sets need to be laid out over again, since their logical top will be affected by
    196             // this, and therefore their column heights may change as well, at least if the multicol
    197             // height is constrained.
    198             multicolSet->setChildNeedsLayout(MarkOnlyThis);
    199         }
    200     }
    201 
    202     if (needsRelayout)
    203         setChildNeedsLayout(MarkOnlyThis);
    204 
    205     m_inBalancingPass = needsRelayout;
    206     return needsRelayout;
    207 }
    208 
    209 void RenderMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit& width, unsigned& count) const
    210 {
    211     RenderBlock* columnBlock = multiColumnBlockFlow();
    212     const RenderStyle* columnStyle = columnBlock->style();
    213     LayoutUnit availableWidth = columnBlock->contentLogicalWidth();
    214     LayoutUnit columnGap = columnBlock->columnGap();
    215     LayoutUnit computedColumnWidth = max<LayoutUnit>(1, LayoutUnit(columnStyle->columnWidth()));
    216     unsigned computedColumnCount = max<int>(1, columnStyle->columnCount());
    217 
    218     ASSERT(!columnStyle->hasAutoColumnCount() || !columnStyle->hasAutoColumnWidth());
    219     if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) {
    220         count = computedColumnCount;
    221         width = std::max<LayoutUnit>(0, (availableWidth - ((count - 1) * columnGap)) / count);
    222     } else if (!columnStyle->hasAutoColumnWidth() && columnStyle->hasAutoColumnCount()) {
    223         count = std::max<LayoutUnit>(1, (availableWidth + columnGap) / (computedColumnWidth + columnGap));
    224         width = ((availableWidth + columnGap) / count) - columnGap;
    225     } else {
    226         count = std::max<LayoutUnit>(std::min<LayoutUnit>(computedColumnCount, (availableWidth + columnGap) / (computedColumnWidth + columnGap)), 1);
    227         width = ((availableWidth + columnGap) / count) - columnGap;
    228     }
    229 }
    230 
    231 const char* RenderMultiColumnFlowThread::renderName() const
    232 {
    233     return "RenderMultiColumnFlowThread";
    234 }
    235 
    236 void RenderMultiColumnFlowThread::addRegionToThread(RenderMultiColumnSet* columnSet)
    237 {
    238     if (RenderMultiColumnSet* nextSet = columnSet->nextSiblingMultiColumnSet()) {
    239         RenderMultiColumnSetList::iterator it = m_multiColumnSetList.find(nextSet);
    240         ASSERT(it != m_multiColumnSetList.end());
    241         m_multiColumnSetList.insertBefore(it, columnSet);
    242     } else {
    243         m_multiColumnSetList.add(columnSet);
    244     }
    245     columnSet->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 = columnSetAtBlockOffset(offset))
    290         multicolSet->recordSpaceShortage(spaceShortage);
    291 }
    292 
    293 void RenderMultiColumnFlowThread::updateMinimumPageHeight(LayoutUnit offset, LayoutUnit minHeight)
    294 {
    295     if (RenderMultiColumnSet* multicolSet = columnSetAtBlockOffset(offset))
    296         multicolSet->updateMinimumColumnHeight(minHeight);
    297 }
    298 
    299 RenderMultiColumnSet* RenderMultiColumnFlowThread::columnSetAtBlockOffset(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 = columnSetAtBlockOffset(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