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 INTERRUPTION) 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/RenderMultiColumnSet.h"
     28 
     29 #include "core/rendering/PaintInfo.h"
     30 #include "core/rendering/RenderLayer.h"
     31 #include "core/rendering/RenderMultiColumnFlowThread.h"
     32 
     33 using namespace std;
     34 
     35 namespace WebCore {
     36 
     37 RenderMultiColumnSet::RenderMultiColumnSet(RenderFlowThread* flowThread)
     38     : RenderRegion(0, flowThread)
     39     , m_columnHeight(0)
     40     , m_maxColumnHeight(RenderFlowThread::maxLogicalHeight())
     41     , m_minSpaceShortage(RenderFlowThread::maxLogicalHeight())
     42     , m_minimumColumnHeight(0)
     43 {
     44 }
     45 
     46 RenderMultiColumnSet* RenderMultiColumnSet::createAnonymous(RenderFlowThread* flowThread, RenderStyle* parentStyle)
     47 {
     48     Document& document = flowThread->document();
     49     RenderMultiColumnSet* renderer = new RenderMultiColumnSet(flowThread);
     50     renderer->setDocumentForAnonymous(&document);
     51     renderer->setStyle(RenderStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK));
     52     return renderer;
     53 }
     54 
     55 RenderMultiColumnSet* RenderMultiColumnSet::nextSiblingMultiColumnSet() 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* RenderMultiColumnSet::previousSiblingMultiColumnSet() const
     65 {
     66     for (RenderObject* sibling = previousSibling(); sibling; sibling = sibling->previousSibling()) {
     67         if (sibling->isRenderMultiColumnSet())
     68             return toRenderMultiColumnSet(sibling);
     69     }
     70     return 0;
     71 }
     72 
     73 LayoutSize RenderMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockOffset) const
     74 {
     75     unsigned columnIndex = columnIndexAtOffset(blockOffset);
     76     LayoutRect portionRect(flowThreadPortionRectAt(columnIndex));
     77     flipForWritingMode(portionRect);
     78     LayoutRect columnRect(columnRectAt(columnIndex));
     79     flipForWritingMode(columnRect);
     80     return contentBoxRect().location() + columnRect.location() - portionRect.location();
     81 }
     82 
     83 LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) const
     84 {
     85     // Adjust for the top offset within the content box of the multicol container (containing
     86     // block), unless this is the first set. We know that the top offset for the first set will be
     87     // zero, but if the multicol container has non-zero top border or padding, the set's top offset
     88     // (initially being 0 and relative to the border box) will be negative until it has been laid
     89     // out. Had we used this bogus offset, we would calculate the wrong height, and risk performing
     90     // a wasted layout iteration. Of course all other sets (if any) have this problem in the first
     91     // layout pass too, but there's really nothing we can do there until the flow thread has been
     92     // laid out anyway.
     93     if (previousSiblingMultiColumnSet()) {
     94         RenderBlockFlow* multicolBlock = multiColumnBlockFlow();
     95         LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderAndPaddingBefore();
     96         height -= contentLogicalTop;
     97     }
     98     return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created.
     99 }
    100 
    101 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) const
    102 {
    103     unsigned columnIndex = columnIndexAtOffset(offset, AssumeNewColumns);
    104     return logicalTopInFlowThread() + columnIndex * pageLogicalHeight();
    105 }
    106 
    107 void RenderMultiColumnSet::setAndConstrainColumnHeight(LayoutUnit newHeight)
    108 {
    109     m_columnHeight = newHeight;
    110     if (m_columnHeight > m_maxColumnHeight)
    111         m_columnHeight = m_maxColumnHeight;
    112     // FIXME: the height may also be affected by the enclosing pagination context, if any.
    113 }
    114 
    115 unsigned RenderMultiColumnSet::findRunWithTallestColumns() const
    116 {
    117     unsigned indexWithLargestHeight = 0;
    118     LayoutUnit largestHeight;
    119     LayoutUnit previousOffset = logicalTopInFlowThread();
    120     size_t runCount = m_contentRuns.size();
    121     ASSERT(runCount);
    122     for (size_t i = 0; i < runCount; i++) {
    123         const ContentRun& run = m_contentRuns[i];
    124         LayoutUnit height = run.columnLogicalHeight(previousOffset);
    125         if (largestHeight < height) {
    126             largestHeight = height;
    127             indexWithLargestHeight = i;
    128         }
    129         previousOffset = run.breakOffset();
    130     }
    131     return indexWithLargestHeight;
    132 }
    133 
    134 void RenderMultiColumnSet::distributeImplicitBreaks()
    135 {
    136 #ifndef NDEBUG
    137     // There should be no implicit breaks assumed at this point.
    138     for (unsigned i = 0; i < m_contentRuns.size(); i++)
    139         ASSERT(!m_contentRuns[i].assumedImplicitBreaks());
    140 #endif // NDEBUG
    141 
    142     // Insert a final content run to encompass all content. This will include overflow if this is
    143     // the last set.
    144     addContentRun(logicalBottomInFlowThread());
    145     unsigned columnCount = m_contentRuns.size();
    146 
    147     // If there is room for more breaks (to reach the used value of column-count), imagine that we
    148     // insert implicit breaks at suitable locations. At any given time, the content run with the
    149     // currently tallest columns will get another implicit break "inserted", which will increase its
    150     // column count by one and shrink its columns' height. Repeat until we have the desired total
    151     // number of breaks. The largest column height among the runs will then be the initial column
    152     // height for the balancer to use.
    153     while (columnCount < usedColumnCount()) {
    154         unsigned index = findRunWithTallestColumns();
    155         m_contentRuns[index].assumeAnotherImplicitBreak();
    156         columnCount++;
    157     }
    158 }
    159 
    160 LayoutUnit RenderMultiColumnSet::calculateColumnHeight(BalancedHeightCalculation calculationMode) const
    161 {
    162     if (calculationMode == GuessFromFlowThreadPortion) {
    163         // Initial balancing. Start with the lowest imaginable column height. We use the tallest
    164         // content run (after having "inserted" implicit breaks), and find its start offset (by
    165         // looking at the previous run's end offset, or, if there's no previous run, the set's start
    166         // offset in the flow thread).
    167         unsigned index = findRunWithTallestColumns();
    168         LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset() : logicalTopInFlowThread();
    169         return std::max<LayoutUnit>(m_contentRuns[index].columnLogicalHeight(startOffset), m_minimumColumnHeight);
    170     }
    171 
    172     if (actualColumnCount() <= usedColumnCount()) {
    173         // With the current column height, the content fits without creating overflowing columns. We're done.
    174         return m_columnHeight;
    175     }
    176 
    177     if (m_contentRuns.size() >= usedColumnCount()) {
    178         // Too many forced breaks to allow any implicit breaks. Initial balancing should already
    179         // have set a good height. There's nothing more we should do.
    180         return m_columnHeight;
    181     }
    182 
    183     // If the initial guessed column height wasn't enough, stretch it now. Stretch by the lowest
    184     // amount of space shortage found during layout.
    185 
    186     ASSERT(m_minSpaceShortage > 0); // We should never _shrink_ the height!
    187     ASSERT(m_minSpaceShortage != RenderFlowThread::maxLogicalHeight()); // If this happens, we probably have a bug.
    188     if (m_minSpaceShortage == RenderFlowThread::maxLogicalHeight())
    189         return m_columnHeight; // So bail out rather than looping infinitely.
    190 
    191     return m_columnHeight + m_minSpaceShortage;
    192 }
    193 
    194 void RenderMultiColumnSet::addContentRun(LayoutUnit endOffsetFromFirstPage)
    195 {
    196     if (!multiColumnFlowThread()->requiresBalancing())
    197         return;
    198     if (!m_contentRuns.isEmpty() && endOffsetFromFirstPage <= m_contentRuns.last().breakOffset())
    199         return;
    200     // Append another item as long as we haven't exceeded used column count. What ends up in the
    201     // overflow area shouldn't affect column balancing.
    202     if (m_contentRuns.size() < usedColumnCount())
    203         m_contentRuns.append(ContentRun(endOffsetFromFirstPage));
    204 }
    205 
    206 bool RenderMultiColumnSet::recalculateColumnHeight(BalancedHeightCalculation calculationMode)
    207 {
    208     ASSERT(multiColumnFlowThread()->requiresBalancing());
    209 
    210     LayoutUnit oldColumnHeight = m_columnHeight;
    211     if (calculationMode == GuessFromFlowThreadPortion) {
    212         // Post-process the content runs and find out where the implicit breaks will occur.
    213         distributeImplicitBreaks();
    214     }
    215     LayoutUnit newColumnHeight = calculateColumnHeight(calculationMode);
    216     setAndConstrainColumnHeight(newColumnHeight);
    217 
    218     // After having calculated an initial column height, the multicol container typically needs at
    219     // least one more layout pass with a new column height, but if a height was specified, we only
    220     // need to do this if we think that we need less space than specified. Conversely, if we
    221     // determined that the columns need to be as tall as the specified height of the container, we
    222     // have already laid it out correctly, and there's no need for another pass.
    223 
    224     // We can get rid of the content runs now, if we haven't already done so. They are only needed
    225     // to calculate the initial balanced column height. In fact, we have to get rid of them before
    226     // the next layout pass, since each pass will rebuild this.
    227     m_contentRuns.clear();
    228 
    229     if (m_columnHeight == oldColumnHeight)
    230         return false; // No change. We're done.
    231 
    232     m_minSpaceShortage = RenderFlowThread::maxLogicalHeight();
    233     return true; // Need another pass.
    234 }
    235 
    236 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit spaceShortage)
    237 {
    238     if (spaceShortage >= m_minSpaceShortage)
    239         return;
    240 
    241     // The space shortage is what we use as our stretch amount. We need a positive number here in
    242     // order to get anywhere.
    243     ASSERT(spaceShortage > 0);
    244 
    245     m_minSpaceShortage = spaceShortage;
    246 }
    247 
    248 void RenderMultiColumnSet::resetColumnHeight()
    249 {
    250     // Nuke previously stored minimum column height. Contents may have changed for all we know.
    251     m_minimumColumnHeight = 0;
    252 
    253     m_maxColumnHeight = calculateMaxColumnHeight();
    254 
    255     LayoutUnit oldColumnHeight = pageLogicalHeight();
    256 
    257     if (multiColumnFlowThread()->requiresBalancing())
    258         m_columnHeight = 0;
    259     else
    260         setAndConstrainColumnHeight(heightAdjustedForSetOffset(multiColumnFlowThread()->columnHeightAvailable()));
    261 
    262     if (pageLogicalHeight() != oldColumnHeight)
    263         setChildNeedsLayout(MarkOnlyThis);
    264 
    265     // Content runs are only needed in the initial layout pass, in order to find an initial column
    266     // height, and should have been deleted afterwards. We're about to rebuild the content runs, so
    267     // the list needs to be empty.
    268     ASSERT(m_contentRuns.isEmpty());
    269 }
    270 
    271 void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded()
    272 {
    273     ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this);
    274     LayoutRect rect(flowThreadPortionRect());
    275 
    276     // Get the offset within the flow thread in its block progression direction. Then get the
    277     // flow thread's remaining logical height including its overflow and expand our rect
    278     // to encompass that remaining height and overflow. The idea is that we will generate
    279     // additional columns and pages to hold that overflow, since people do write bad
    280     // content like <body style="height:0px"> in multi-column layouts.
    281     bool isHorizontal = flowThread()->isHorizontalWritingMode();
    282     LayoutUnit logicalTopOffset = isHorizontal ? rect.y() : rect.x();
    283     LayoutRect layoutRect = flowThread()->layoutOverflowRect();
    284     LayoutUnit logicalHeightWithOverflow = (isHorizontal ? layoutRect.maxY() : layoutRect.maxX()) - logicalTopOffset;
    285     setFlowThreadPortionRect(LayoutRect(rect.x(), rect.y(), isHorizontal ? rect.width() : logicalHeightWithOverflow, isHorizontal ? logicalHeightWithOverflow : rect.height()));
    286 }
    287 
    288 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
    289 {
    290     computedValues.m_extent = m_columnHeight;
    291     computedValues.m_position = logicalTop;
    292 }
    293 
    294 LayoutUnit RenderMultiColumnSet::calculateMaxColumnHeight() const
    295 {
    296     RenderBlockFlow* multicolBlock = multiColumnBlockFlow();
    297     RenderStyle* multicolStyle = multicolBlock->style();
    298     LayoutUnit availableHeight = multiColumnFlowThread()->columnHeightAvailable();
    299     LayoutUnit maxColumnHeight = availableHeight ? availableHeight : RenderFlowThread::maxLogicalHeight();
    300     if (!multicolStyle->logicalMaxHeight().isUndefined()) {
    301         LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalMaxHeight(), -1);
    302         if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight)
    303             maxColumnHeight = logicalMaxHeight;
    304     }
    305     return heightAdjustedForSetOffset(maxColumnHeight);
    306 }
    307 
    308 LayoutUnit RenderMultiColumnSet::columnGap() const
    309 {
    310     RenderBlockFlow* parentBlock = multiColumnBlockFlow();
    311     if (parentBlock->style()->hasNormalColumnGap())
    312         return parentBlock->style()->fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins.
    313     return parentBlock->style()->columnGap();
    314 }
    315 
    316 unsigned RenderMultiColumnSet::actualColumnCount() const
    317 {
    318     // We must always return a value of 1 or greater. Column count = 0 is a meaningless situation,
    319     // and will confuse and cause problems in other parts of the code.
    320     if (!pageLogicalHeight())
    321         return 1;
    322 
    323     // Our portion rect determines our column count. We have as many columns as needed to fit all the content.
    324     LayoutUnit logicalHeightInColumns = flowThread()->isHorizontalWritingMode() ? flowThreadPortionRect().height() : flowThreadPortionRect().width();
    325     if (!logicalHeightInColumns)
    326         return 1;
    327 
    328     unsigned count = ceil(logicalHeightInColumns.toFloat() / pageLogicalHeight().toFloat());
    329     ASSERT(count >= 1);
    330     return count;
    331 }
    332 
    333 LayoutRect RenderMultiColumnSet::columnRectAt(unsigned index) const
    334 {
    335     LayoutUnit colLogicalWidth = pageLogicalWidth();
    336     LayoutUnit colLogicalHeight = pageLogicalHeight();
    337     LayoutUnit colLogicalTop = borderBefore() + paddingBefore();
    338     LayoutUnit colLogicalLeft = borderAndPaddingLogicalLeft();
    339     LayoutUnit colGap = columnGap();
    340     if (style()->isLeftToRightDirection())
    341         colLogicalLeft += index * (colLogicalWidth + colGap);
    342     else
    343         colLogicalLeft += contentLogicalWidth() - colLogicalWidth - index * (colLogicalWidth + colGap);
    344 
    345     if (isHorizontalWritingMode())
    346         return LayoutRect(colLogicalLeft, colLogicalTop, colLogicalWidth, colLogicalHeight);
    347     return LayoutRect(colLogicalTop, colLogicalLeft, colLogicalHeight, colLogicalWidth);
    348 }
    349 
    350 unsigned RenderMultiColumnSet::columnIndexAtOffset(LayoutUnit offset, ColumnIndexCalculationMode mode) const
    351 {
    352     LayoutRect portionRect(flowThreadPortionRect());
    353 
    354     // Handle the offset being out of range.
    355     LayoutUnit flowThreadLogicalTop = isHorizontalWritingMode() ? portionRect.y() : portionRect.x();
    356     if (offset < flowThreadLogicalTop)
    357         return 0;
    358     // If we're laying out right now, we cannot constrain against some logical bottom, since it
    359     // isn't known yet. Otherwise, just return the last column if we're past the logical bottom.
    360     if (mode == ClampToExistingColumns) {
    361         LayoutUnit flowThreadLogicalBottom = isHorizontalWritingMode() ? portionRect.maxY() : portionRect.maxX();
    362         if (offset >= flowThreadLogicalBottom)
    363             return actualColumnCount() - 1;
    364     }
    365 
    366     // Just divide by the column height to determine the correct column.
    367     return (offset - flowThreadLogicalTop).toFloat() / pageLogicalHeight().toFloat();
    368 }
    369 
    370 LayoutRect RenderMultiColumnSet::flowThreadPortionRectAt(unsigned index) const
    371 {
    372     LayoutRect portionRect = flowThreadPortionRect();
    373     if (isHorizontalWritingMode())
    374         portionRect = LayoutRect(portionRect.x(), portionRect.y() + index * pageLogicalHeight(), portionRect.width(), pageLogicalHeight());
    375     else
    376         portionRect = LayoutRect(portionRect.x() + index * pageLogicalHeight(), portionRect.y(), pageLogicalHeight(), portionRect.height());
    377     return portionRect;
    378 }
    379 
    380 LayoutRect RenderMultiColumnSet::flowThreadPortionOverflowRect(const LayoutRect& portionRect, unsigned index, unsigned colCount, LayoutUnit colGap) const
    381 {
    382     // This function determines the portion of the flow thread that paints for the column. Along the inline axis, columns are
    383     // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column
    384     // gap along interior edges.
    385     //
    386     // In the block direction, we will not clip overflow out of the top of the first column, or out of the bottom of
    387     // the last column. This applies only to the true first column and last column across all column sets.
    388     //
    389     // FIXME: Eventually we will know overflow on a per-column basis, but we can't do this until we have a painting
    390     // mode that understands not to paint contents from a previous column in the overflow area of a following column.
    391     // This problem applies to regions and pages as well and is not unique to columns.
    392     bool isFirstColumn = !index;
    393     bool isLastColumn = index == colCount - 1;
    394     bool isLeftmostColumn = style()->isLeftToRightDirection() ? isFirstColumn : isLastColumn;
    395     bool isRightmostColumn = style()->isLeftToRightDirection() ? isLastColumn : isFirstColumn;
    396 
    397     // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical
    398     // top/bottom unless it's the first/last column.
    399     LayoutRect overflowRect = overflowRectForFlowThreadPortion(portionRect, isFirstColumn && isFirstRegion(), isLastColumn && isLastRegion());
    400 
    401     // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column
    402     // gaps. Also make sure that we avoid rounding errors.
    403     if (isHorizontalWritingMode()) {
    404         if (!isLeftmostColumn)
    405             overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2);
    406         if (!isRightmostColumn)
    407             overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap - colGap / 2);
    408     } else {
    409         if (!isLeftmostColumn)
    410             overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2);
    411         if (!isRightmostColumn)
    412             overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap - colGap / 2);
    413     }
    414     return overflowRect;
    415 }
    416 
    417 void RenderMultiColumnSet::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    418 {
    419     if (style()->visibility() != VISIBLE)
    420         return;
    421 
    422     RenderBlockFlow::paintObject(paintInfo, paintOffset);
    423 
    424     // FIXME: Right now we're only painting in the foreground phase.
    425     // Columns should technically respect phases and allow for background/float/foreground overlap etc., just like
    426     // RenderBlocks do. Note this is a pretty minor issue, since the old column implementation clipped columns
    427     // anyway, thus making it impossible for them to overlap one another. It's also really unlikely that the columns
    428     // would overlap another block.
    429     if (!m_flowThread || !isValid() || (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection))
    430         return;
    431 
    432     paintColumnRules(paintInfo, paintOffset);
    433 }
    434 
    435 void RenderMultiColumnSet::paintColumnRules(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    436 {
    437     if (paintInfo.context->paintingDisabled())
    438         return;
    439 
    440     RenderStyle* blockStyle = multiColumnBlockFlow()->style();
    441     const Color& ruleColor = resolveColor(blockStyle, CSSPropertyWebkitColumnRuleColor);
    442     bool ruleTransparent = blockStyle->columnRuleIsTransparent();
    443     EBorderStyle ruleStyle = blockStyle->columnRuleStyle();
    444     LayoutUnit ruleThickness = blockStyle->columnRuleWidth();
    445     LayoutUnit colGap = columnGap();
    446     bool renderRule = ruleStyle > BHIDDEN && !ruleTransparent;
    447     if (!renderRule)
    448         return;
    449 
    450     unsigned colCount = actualColumnCount();
    451     if (colCount <= 1)
    452         return;
    453 
    454     bool antialias = shouldAntialiasLines(paintInfo.context);
    455 
    456     bool leftToRight = style()->isLeftToRightDirection();
    457     LayoutUnit currLogicalLeftOffset = leftToRight ? LayoutUnit() : contentLogicalWidth();
    458     LayoutUnit ruleAdd = borderAndPaddingLogicalLeft();
    459     LayoutUnit ruleLogicalLeft = leftToRight ? LayoutUnit() : contentLogicalWidth();
    460     LayoutUnit inlineDirectionSize = pageLogicalWidth();
    461     BoxSide boxSide = isHorizontalWritingMode()
    462         ? leftToRight ? BSLeft : BSRight
    463         : leftToRight ? BSTop : BSBottom;
    464 
    465     for (unsigned i = 0; i < colCount; i++) {
    466         // Move to the next position.
    467         if (leftToRight) {
    468             ruleLogicalLeft += inlineDirectionSize + colGap / 2;
    469             currLogicalLeftOffset += inlineDirectionSize + colGap;
    470         } else {
    471             ruleLogicalLeft -= (inlineDirectionSize + colGap / 2);
    472             currLogicalLeftOffset -= (inlineDirectionSize + colGap);
    473         }
    474 
    475         // Now paint the column rule.
    476         if (i < colCount - 1) {
    477             LayoutUnit ruleLeft = isHorizontalWritingMode() ? paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd : paintOffset.x() + borderLeft() + paddingLeft();
    478             LayoutUnit ruleRight = isHorizontalWritingMode() ? ruleLeft + ruleThickness : ruleLeft + contentWidth();
    479             LayoutUnit ruleTop = isHorizontalWritingMode() ? paintOffset.y() + borderTop() + paddingTop() : paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd;
    480             LayoutUnit ruleBottom = isHorizontalWritingMode() ? ruleTop + contentHeight() : ruleTop + ruleThickness;
    481             IntRect pixelSnappedRuleRect = pixelSnappedIntRectFromEdges(ruleLeft, ruleTop, ruleRight, ruleBottom);
    482             drawLineForBoxSide(paintInfo.context, pixelSnappedRuleRect.x(), pixelSnappedRuleRect.y(), pixelSnappedRuleRect.maxX(), pixelSnappedRuleRect.maxY(), boxSide, ruleColor, ruleStyle, 0, 0, antialias);
    483         }
    484 
    485         ruleLogicalLeft = currLogicalLeftOffset;
    486     }
    487 }
    488 
    489 void RenderMultiColumnSet::repaintFlowThreadContent(const LayoutRect& repaintRect) const
    490 {
    491     // Figure out the start and end columns and only check within that range so that we don't walk the
    492     // entire column set. Put the repaint rect into flow thread coordinates by flipping it first.
    493     LayoutRect flowThreadRepaintRect(repaintRect);
    494     flowThread()->flipForWritingMode(flowThreadRepaintRect);
    495 
    496     // Now we can compare this rect with the flow thread portions owned by each column. First let's
    497     // just see if the repaint rect intersects our flow thread portion at all.
    498     LayoutRect clippedRect(flowThreadRepaintRect);
    499     clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect());
    500     if (clippedRect.isEmpty())
    501         return;
    502 
    503     // Now we know we intersect at least one column. Let's figure out the logical top and logical
    504     // bottom of the area we're repainting.
    505     LayoutUnit repaintLogicalTop = isHorizontalWritingMode() ? flowThreadRepaintRect.y() : flowThreadRepaintRect.x();
    506     LayoutUnit repaintLogicalBottom = (isHorizontalWritingMode() ? flowThreadRepaintRect.maxY() : flowThreadRepaintRect.maxX()) - 1;
    507 
    508     unsigned startColumn = columnIndexAtOffset(repaintLogicalTop);
    509     unsigned endColumn = columnIndexAtOffset(repaintLogicalBottom);
    510 
    511     LayoutUnit colGap = columnGap();
    512     unsigned colCount = actualColumnCount();
    513     for (unsigned i = startColumn; i <= endColumn; i++) {
    514         LayoutRect colRect = columnRectAt(i);
    515 
    516         // Get the portion of the flow thread that corresponds to this column.
    517         LayoutRect flowThreadPortion = flowThreadPortionRectAt(i);
    518 
    519         // Now get the overflow rect that corresponds to the column.
    520         LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap);
    521 
    522         // Do a repaint for this specific column.
    523         repaintFlowThreadContentRectangle(repaintRect, flowThreadPortion, flowThreadOverflowPortion, colRect.location());
    524     }
    525 }
    526 
    527 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect)
    528 {
    529     // The two rectangles passed to this method are physical, except that we pretend that there's
    530     // only one long column (that's how a flow thread works).
    531     //
    532     // Then there's the output from this method - the stuff we put into the list of fragments. The
    533     // fragment.paginationOffset point is the actual physical translation required to get from a
    534     // location in the flow thread to a location in a given column. The fragment.paginationClip
    535     // rectangle, on the other hand, is in the same coordinate system as the two rectangles passed
    536     // to this method (flow thread coordinates).
    537     //
    538     // All other rectangles in this method are sized physically, and the inline direction coordinate
    539     // is physical too, but the block direction coordinate is "logical top". This is the same as
    540     // e.g. RenderBox::frameRect(). These rectangles also pretend that there's only one long column,
    541     // i.e. they are for the flow thread.
    542 
    543     // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in
    544     // a renderer, most rectangles are represented this way.
    545     LayoutRect layerBoundsInFlowThread(layerBoundingBox);
    546     flowThread()->flipForWritingMode(layerBoundsInFlowThread);
    547 
    548     // Now we can compare with the flow thread portions owned by each column. First let's
    549     // see if the rect intersects our flow thread portion at all.
    550     LayoutRect clippedRect(layerBoundsInFlowThread);
    551     clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect());
    552     if (clippedRect.isEmpty())
    553         return;
    554 
    555     // Now we know we intersect at least one column. Let's figure out the logical top and logical
    556     // bottom of the area we're checking.
    557     LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x();
    558     LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1;
    559 
    560     // Figure out the start and end columns and only check within that range so that we don't walk the
    561     // entire column set.
    562     unsigned startColumn = columnIndexAtOffset(layerLogicalTop);
    563     unsigned endColumn = columnIndexAtOffset(layerLogicalBottom);
    564 
    565     LayoutUnit colLogicalWidth = pageLogicalWidth();
    566     LayoutUnit colGap = columnGap();
    567     unsigned colCount = actualColumnCount();
    568 
    569     for (unsigned i = startColumn; i <= endColumn; i++) {
    570         // Get the portion of the flow thread that corresponds to this column.
    571         LayoutRect flowThreadPortion = flowThreadPortionRectAt(i);
    572 
    573         // Now get the overflow rect that corresponds to the column.
    574         LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap);
    575 
    576         // In order to create a fragment we must intersect the portion painted by this column.
    577         LayoutRect clippedRect(layerBoundsInFlowThread);
    578         clippedRect.intersect(flowThreadOverflowPortion);
    579         if (clippedRect.isEmpty())
    580             continue;
    581 
    582         // We also need to intersect the dirty rect. We have to apply a translation and shift based off
    583         // our column index.
    584         LayoutPoint translationOffset;
    585         LayoutUnit inlineOffset = i * (colLogicalWidth + colGap);
    586         if (!style()->isLeftToRightDirection())
    587             inlineOffset = -inlineOffset;
    588         translationOffset.setX(inlineOffset);
    589         LayoutUnit blockOffset = isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x();
    590         if (isFlippedBlocksWritingMode(style()->writingMode()))
    591             blockOffset = -blockOffset;
    592         translationOffset.setY(blockOffset);
    593         if (!isHorizontalWritingMode())
    594             translationOffset = translationOffset.transposedPoint();
    595         // FIXME: The translation needs to include the multicolumn set's content offset within the
    596         // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets.
    597 
    598         // Shift the dirty rect to be in flow thread coordinates with this translation applied.
    599         LayoutRect translatedDirtyRect(dirtyRect);
    600         translatedDirtyRect.moveBy(-translationOffset);
    601 
    602         // See if we intersect the dirty rect.
    603         clippedRect = layerBoundingBox;
    604         clippedRect.intersect(translatedDirtyRect);
    605         if (clippedRect.isEmpty())
    606             continue;
    607 
    608         // Something does need to paint in this column. Make a fragment now and supply the physical translation
    609         // offset and the clip rect for the column with that offset applied.
    610         LayerFragment fragment;
    611         fragment.paginationOffset = translationOffset;
    612 
    613         LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion);
    614         // Flip it into more a physical (RenderLayer-style) rectangle.
    615         flowThread()->flipForWritingMode(flippedFlowThreadOverflowPortion);
    616         fragment.paginationClip = flippedFlowThreadOverflowPortion;
    617         fragments.append(fragment);
    618     }
    619 }
    620 
    621 void RenderMultiColumnSet::addOverflowFromChildren()
    622 {
    623     unsigned colCount = actualColumnCount();
    624     if (!colCount)
    625         return;
    626 
    627     LayoutRect lastRect = columnRectAt(colCount - 1);
    628     addLayoutOverflow(lastRect);
    629     if (!hasOverflowClip())
    630         addVisualOverflow(lastRect);
    631 }
    632 
    633 const char* RenderMultiColumnSet::renderName() const
    634 {
    635     return "RenderMultiColumnSet";
    636 }
    637 
    638 }
    639