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/RenderMultiColumnBlock.h" 32 #include "core/rendering/RenderMultiColumnFlowThread.h" 33 34 using namespace std; 35 36 namespace WebCore { 37 38 RenderMultiColumnSet::RenderMultiColumnSet(RenderFlowThread* flowThread) 39 : RenderRegionSet(0, flowThread) 40 , m_computedColumnCount(1) 41 , m_computedColumnWidth(0) 42 , m_computedColumnHeight(0) 43 , m_maxColumnHeight(LayoutUnit::max()) 44 , m_minSpaceShortage(LayoutUnit::max()) 45 , m_minimumColumnHeight(0) 46 , m_forcedBreaksCount(0) 47 , m_maximumDistanceBetweenForcedBreaks(0) 48 , m_forcedBreakOffset(0) 49 { 50 } 51 52 RenderMultiColumnSet* RenderMultiColumnSet::createAnonymous(RenderFlowThread* flowThread) 53 { 54 Document& document = flowThread->document(); 55 RenderMultiColumnSet* renderer = new RenderMultiColumnSet(flowThread); 56 renderer->setDocumentForAnonymous(&document); 57 return renderer; 58 } 59 60 LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) const 61 { 62 RenderMultiColumnBlock* multicolBlock = toRenderMultiColumnBlock(parent()); 63 LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderBefore() - multicolBlock->paddingBefore(); 64 65 height -= contentLogicalTop; 66 return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created. 67 } 68 69 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) const 70 { 71 LayoutUnit portionLogicalTop = (isHorizontalWritingMode() ? flowThreadPortionRect().y() : flowThreadPortionRect().x()); 72 unsigned columnIndex = columnIndexAtOffset(offset, AssumeNewColumns); 73 return portionLogicalTop + columnIndex * computedColumnHeight(); 74 } 75 76 void RenderMultiColumnSet::setAndConstrainColumnHeight(LayoutUnit newHeight) 77 { 78 m_computedColumnHeight = newHeight; 79 if (m_computedColumnHeight > m_maxColumnHeight) 80 m_computedColumnHeight = m_maxColumnHeight; 81 // FIXME: the height may also be affected by the enclosing pagination context, if any. 82 } 83 84 bool RenderMultiColumnSet::calculateBalancedHeight(bool initial) 85 { 86 ASSERT(toRenderMultiColumnBlock(parent())->requiresBalancing()); 87 LayoutUnit oldColumnHeight = m_computedColumnHeight; 88 LayoutUnit currentMinSpaceShortage = m_minSpaceShortage; 89 m_minSpaceShortage = LayoutUnit::max(); 90 91 if (initial) { 92 // Start with the lowest imaginable column height. 93 LayoutUnit logicalHeightGuess = ceilf(float(flowThread()->logicalHeight()) / float(m_computedColumnCount)); 94 logicalHeightGuess = max(logicalHeightGuess, m_minimumColumnHeight); 95 setAndConstrainColumnHeight(logicalHeightGuess); 96 97 // The multicol container now typically needs at least one more layout pass with a new 98 // column height, but if height was specified, we only need to do this if we found that we 99 // might need less space than that. On the other hand, if we determined that the columns 100 // need to be as tall as the specified height of the container, we have already laid it out 101 // correctly, and there's no need for another pass. 102 return m_computedColumnHeight != oldColumnHeight; 103 } 104 105 if (columnCount() <= computedColumnCount()) { 106 // With the current column height, the content fits without creating overflowing columns. We're done. 107 return false; 108 } 109 110 // If the initial guessed column height wasn't enough, stretch it now. Stretch by the lowest 111 // amount of space shortage found during layout. 112 113 ASSERT(currentMinSpaceShortage != LayoutUnit::max()); // If this can actually happen, we probably have a bug. 114 if (currentMinSpaceShortage == LayoutUnit::max()) 115 return false; // So bail out rather than looping infinitely. 116 117 setAndConstrainColumnHeight(m_computedColumnHeight + currentMinSpaceShortage); 118 119 // If we reach the maximum column height (typically set by the height or max-height property), 120 // we may not be allowed to stretch further. Return true only if stretching 121 // succeeded. Otherwise, we're done. 122 ASSERT(m_computedColumnHeight >= oldColumnHeight); // We shouldn't be able to shrink the height! 123 return m_computedColumnHeight > oldColumnHeight; 124 } 125 126 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit spaceShortage) 127 { 128 if (spaceShortage >= m_minSpaceShortage) 129 return; 130 131 // The space shortage is what we use as our stretch amount. We need a positive number here in 132 // order to get anywhere. 133 ASSERT(spaceShortage > 0); 134 135 m_minSpaceShortage = spaceShortage; 136 } 137 138 void RenderMultiColumnSet::updateLogicalWidth() 139 { 140 RenderMultiColumnBlock* parentBlock = toRenderMultiColumnBlock(parent()); 141 setComputedColumnWidthAndCount(parentBlock->columnWidth(), parentBlock->columnCount()); // FIXME: This will eventually vary if we are contained inside regions. 142 143 // FIXME: When we add regions support, we'll start it off at the width of the multi-column 144 // block in that particular region. 145 setLogicalWidth(parentBox()->contentLogicalWidth()); 146 147 // If we overflow, increase our logical width. 148 unsigned colCount = columnCount(); 149 LayoutUnit colGap = columnGap(); 150 LayoutUnit minimumContentLogicalWidth = colCount * computedColumnWidth() + (colCount - 1) * colGap; 151 LayoutUnit currentContentLogicalWidth = contentLogicalWidth(); 152 LayoutUnit delta = max(LayoutUnit(), minimumContentLogicalWidth - currentContentLogicalWidth); 153 if (!delta) 154 return; 155 156 // Increase our logical width by the delta. 157 setLogicalWidth(logicalWidth() + delta); 158 } 159 160 void RenderMultiColumnSet::prepareForLayout() 161 { 162 RenderMultiColumnBlock* multicolBlock = toRenderMultiColumnBlock(parent()); 163 RenderStyle* multicolStyle = multicolBlock->style(); 164 165 // Set box logical top. 166 ASSERT(!previousSiblingBox() || !previousSiblingBox()->isRenderMultiColumnSet()); // FIXME: multiple set not implemented; need to examine previous set to calculate the correct logical top. 167 setLogicalTop(multicolBlock->borderBefore() + multicolBlock->paddingBefore()); 168 169 // Set box width. 170 updateLogicalWidth(); 171 172 if (multicolBlock->requiresBalancing()) { 173 // Set maximum column height. We will not stretch beyond this. 174 m_maxColumnHeight = LayoutUnit::max(); 175 if (!multicolStyle->logicalHeight().isAuto()) 176 m_maxColumnHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalHeight(), -1); 177 if (!multicolStyle->logicalMaxHeight().isUndefined()) { 178 LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalMaxHeight(), -1); 179 if (m_maxColumnHeight > logicalMaxHeight) 180 m_maxColumnHeight = logicalMaxHeight; 181 } 182 m_maxColumnHeight = heightAdjustedForSetOffset(m_maxColumnHeight); 183 m_computedColumnHeight = 0; // Restart balancing. 184 } else { 185 setAndConstrainColumnHeight(heightAdjustedForSetOffset(multicolBlock->columnHeightAvailable())); 186 } 187 188 // Nuke previously stored minimum column height. Contents may have changed for all we know. 189 m_minimumColumnHeight = 0; 190 } 191 192 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const 193 { 194 computedValues.m_extent = m_computedColumnHeight; 195 computedValues.m_position = logicalTop; 196 } 197 198 LayoutUnit RenderMultiColumnSet::columnGap() const 199 { 200 // FIXME: Eventually we will cache the column gap when the widths of columns start varying, but for now we just 201 // go to the parent block to get the gap. 202 RenderMultiColumnBlock* parentBlock = toRenderMultiColumnBlock(parent()); 203 if (parentBlock->style()->hasNormalColumnGap()) 204 return parentBlock->style()->fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins. 205 return parentBlock->style()->columnGap(); 206 } 207 208 unsigned RenderMultiColumnSet::columnCount() const 209 { 210 // We must always return a value of 1 or greater. Column count = 0 is a meaningless situation, 211 // and will confuse and cause problems in other parts of the code. 212 if (!computedColumnHeight()) 213 return 1; 214 215 // Our portion rect determines our column count. We have as many columns as needed to fit all the content. 216 LayoutUnit logicalHeightInColumns = flowThread()->isHorizontalWritingMode() ? flowThreadPortionRect().height() : flowThreadPortionRect().width(); 217 unsigned count = ceil(static_cast<float>(logicalHeightInColumns) / computedColumnHeight()); 218 ASSERT(count >= 1); 219 return count; 220 } 221 222 LayoutRect RenderMultiColumnSet::columnRectAt(unsigned index) const 223 { 224 LayoutUnit colLogicalWidth = computedColumnWidth(); 225 LayoutUnit colLogicalHeight = computedColumnHeight(); 226 LayoutUnit colLogicalTop = borderBefore() + paddingBefore(); 227 LayoutUnit colLogicalLeft = borderAndPaddingLogicalLeft(); 228 LayoutUnit colGap = columnGap(); 229 if (style()->isLeftToRightDirection()) 230 colLogicalLeft += index * (colLogicalWidth + colGap); 231 else 232 colLogicalLeft += contentLogicalWidth() - colLogicalWidth - index * (colLogicalWidth + colGap); 233 234 if (isHorizontalWritingMode()) 235 return LayoutRect(colLogicalLeft, colLogicalTop, colLogicalWidth, colLogicalHeight); 236 return LayoutRect(colLogicalTop, colLogicalLeft, colLogicalHeight, colLogicalWidth); 237 } 238 239 unsigned RenderMultiColumnSet::columnIndexAtOffset(LayoutUnit offset, ColumnIndexCalculationMode mode) const 240 { 241 LayoutRect portionRect(flowThreadPortionRect()); 242 243 // Handle the offset being out of range. 244 LayoutUnit flowThreadLogicalTop = isHorizontalWritingMode() ? portionRect.y() : portionRect.x(); 245 if (offset < flowThreadLogicalTop) 246 return 0; 247 // If we're laying out right now, we cannot constrain against some logical bottom, since it 248 // isn't known yet. Otherwise, just return the last column if we're past the logical bottom. 249 if (mode == ClampToExistingColumns) { 250 LayoutUnit flowThreadLogicalBottom = isHorizontalWritingMode() ? portionRect.maxY() : portionRect.maxX(); 251 if (offset >= flowThreadLogicalBottom) 252 return columnCount() - 1; 253 } 254 255 // Just divide by the column height to determine the correct column. 256 return static_cast<float>(offset - flowThreadLogicalTop) / computedColumnHeight(); 257 } 258 259 LayoutRect RenderMultiColumnSet::flowThreadPortionRectAt(unsigned index) const 260 { 261 LayoutRect portionRect = flowThreadPortionRect(); 262 if (isHorizontalWritingMode()) 263 portionRect = LayoutRect(portionRect.x(), portionRect.y() + index * computedColumnHeight(), portionRect.width(), computedColumnHeight()); 264 else 265 portionRect = LayoutRect(portionRect.x() + index * computedColumnHeight(), portionRect.y(), computedColumnHeight(), portionRect.height()); 266 return portionRect; 267 } 268 269 LayoutRect RenderMultiColumnSet::flowThreadPortionOverflowRect(const LayoutRect& portionRect, unsigned index, unsigned colCount, LayoutUnit colGap) const 270 { 271 // This function determines the portion of the flow thread that paints for the column. Along the inline axis, columns are 272 // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column 273 // gap along interior edges. 274 // 275 // In the block direction, we will not clip overflow out of the top of the first column, or out of the bottom of 276 // the last column. This applies only to the true first column and last column across all column sets. 277 // 278 // FIXME: Eventually we will know overflow on a per-column basis, but we can't do this until we have a painting 279 // mode that understands not to paint contents from a previous column in the overflow area of a following column. 280 // This problem applies to regions and pages as well and is not unique to columns. 281 bool isFirstColumn = !index; 282 bool isLastColumn = index == colCount - 1; 283 bool isLeftmostColumn = style()->isLeftToRightDirection() ? isFirstColumn : isLastColumn; 284 bool isRightmostColumn = style()->isLeftToRightDirection() ? isLastColumn : isFirstColumn; 285 286 // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical 287 // top/bottom unless it's the first/last column. 288 LayoutRect overflowRect = overflowRectForFlowThreadPortion(portionRect, isFirstColumn && isFirstRegion(), isLastColumn && isLastRegion()); 289 290 // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column 291 // gaps. Also make sure that we avoid rounding errors. 292 if (isHorizontalWritingMode()) { 293 if (!isLeftmostColumn) 294 overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2); 295 if (!isRightmostColumn) 296 overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap - colGap / 2); 297 } else { 298 if (!isLeftmostColumn) 299 overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2); 300 if (!isRightmostColumn) 301 overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap - colGap / 2); 302 } 303 return overflowRect; 304 } 305 306 void RenderMultiColumnSet::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 307 { 308 if (style()->visibility() != VISIBLE) 309 return; 310 311 RenderBlock::paintObject(paintInfo, paintOffset); 312 313 // FIXME: Right now we're only painting in the foreground phase. 314 // Columns should technically respect phases and allow for background/float/foreground overlap etc., just like 315 // RenderBlocks do. Note this is a pretty minor issue, since the old column implementation clipped columns 316 // anyway, thus making it impossible for them to overlap one another. It's also really unlikely that the columns 317 // would overlap another block. 318 if (!m_flowThread || !isValid() || (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection)) 319 return; 320 321 paintColumnRules(paintInfo, paintOffset); 322 } 323 324 void RenderMultiColumnSet::paintColumnRules(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 325 { 326 if (paintInfo.context->paintingDisabled()) 327 return; 328 329 RenderStyle* blockStyle = toRenderMultiColumnBlock(parent())->style(); 330 const Color& ruleColor = resolveColor(blockStyle, CSSPropertyWebkitColumnRuleColor); 331 bool ruleTransparent = blockStyle->columnRuleIsTransparent(); 332 EBorderStyle ruleStyle = blockStyle->columnRuleStyle(); 333 LayoutUnit ruleThickness = blockStyle->columnRuleWidth(); 334 LayoutUnit colGap = columnGap(); 335 bool renderRule = ruleStyle > BHIDDEN && !ruleTransparent; 336 if (!renderRule) 337 return; 338 339 unsigned colCount = columnCount(); 340 if (colCount <= 1) 341 return; 342 343 bool antialias = shouldAntialiasLines(paintInfo.context); 344 345 bool leftToRight = style()->isLeftToRightDirection(); 346 LayoutUnit currLogicalLeftOffset = leftToRight ? LayoutUnit() : contentLogicalWidth(); 347 LayoutUnit ruleAdd = borderAndPaddingLogicalLeft(); 348 LayoutUnit ruleLogicalLeft = leftToRight ? LayoutUnit() : contentLogicalWidth(); 349 LayoutUnit inlineDirectionSize = computedColumnWidth(); 350 BoxSide boxSide = isHorizontalWritingMode() 351 ? leftToRight ? BSLeft : BSRight 352 : leftToRight ? BSTop : BSBottom; 353 354 for (unsigned i = 0; i < colCount; i++) { 355 // Move to the next position. 356 if (leftToRight) { 357 ruleLogicalLeft += inlineDirectionSize + colGap / 2; 358 currLogicalLeftOffset += inlineDirectionSize + colGap; 359 } else { 360 ruleLogicalLeft -= (inlineDirectionSize + colGap / 2); 361 currLogicalLeftOffset -= (inlineDirectionSize + colGap); 362 } 363 364 // Now paint the column rule. 365 if (i < colCount - 1) { 366 LayoutUnit ruleLeft = isHorizontalWritingMode() ? paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd : paintOffset.x() + borderLeft() + paddingLeft(); 367 LayoutUnit ruleRight = isHorizontalWritingMode() ? ruleLeft + ruleThickness : ruleLeft + contentWidth(); 368 LayoutUnit ruleTop = isHorizontalWritingMode() ? paintOffset.y() + borderTop() + paddingTop() : paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd; 369 LayoutUnit ruleBottom = isHorizontalWritingMode() ? ruleTop + contentHeight() : ruleTop + ruleThickness; 370 IntRect pixelSnappedRuleRect = pixelSnappedIntRectFromEdges(ruleLeft, ruleTop, ruleRight, ruleBottom); 371 drawLineForBoxSide(paintInfo.context, pixelSnappedRuleRect.x(), pixelSnappedRuleRect.y(), pixelSnappedRuleRect.maxX(), pixelSnappedRuleRect.maxY(), boxSide, ruleColor, ruleStyle, 0, 0, antialias); 372 } 373 374 ruleLogicalLeft = currLogicalLeftOffset; 375 } 376 } 377 378 void RenderMultiColumnSet::repaintFlowThreadContent(const LayoutRect& repaintRect) const 379 { 380 // Figure out the start and end columns and only check within that range so that we don't walk the 381 // entire column set. Put the repaint rect into flow thread coordinates by flipping it first. 382 LayoutRect flowThreadRepaintRect(repaintRect); 383 flowThread()->flipForWritingMode(flowThreadRepaintRect); 384 385 // Now we can compare this rect with the flow thread portions owned by each column. First let's 386 // just see if the repaint rect intersects our flow thread portion at all. 387 LayoutRect clippedRect(flowThreadRepaintRect); 388 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); 389 if (clippedRect.isEmpty()) 390 return; 391 392 // Now we know we intersect at least one column. Let's figure out the logical top and logical 393 // bottom of the area we're repainting. 394 LayoutUnit repaintLogicalTop = isHorizontalWritingMode() ? flowThreadRepaintRect.y() : flowThreadRepaintRect.x(); 395 LayoutUnit repaintLogicalBottom = (isHorizontalWritingMode() ? flowThreadRepaintRect.maxY() : flowThreadRepaintRect.maxX()) - 1; 396 397 unsigned startColumn = columnIndexAtOffset(repaintLogicalTop); 398 unsigned endColumn = columnIndexAtOffset(repaintLogicalBottom); 399 400 LayoutUnit colGap = columnGap(); 401 unsigned colCount = columnCount(); 402 for (unsigned i = startColumn; i <= endColumn; i++) { 403 LayoutRect colRect = columnRectAt(i); 404 405 // Get the portion of the flow thread that corresponds to this column. 406 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); 407 408 // Now get the overflow rect that corresponds to the column. 409 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); 410 411 // Do a repaint for this specific column. 412 repaintFlowThreadContentRectangle(repaintRect, flowThreadPortion, flowThreadOverflowPortion, colRect.location()); 413 } 414 } 415 416 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) 417 { 418 // The two rectangles passed to this method are physical, except that we pretend that there's 419 // only one long column (that's how a flow thread works). 420 // 421 // Then there's the output from this method - the stuff we put into the list of fragments. The 422 // fragment.paginationOffset point is the actual physical translation required to get from a 423 // location in the flow thread to a location in a given column. The fragment.paginationClip 424 // rectangle, on the other hand, is in the same coordinate system as the two rectangles passed 425 // to this method (flow thread coordinates). 426 // 427 // All other rectangles in this method are sized physically, and the inline direction coordinate 428 // is physical too, but the block direction coordinate is "logical top". This is the same as 429 // e.g. RenderBox::frameRect(). These rectangles also pretend that there's only one long column, 430 // i.e. they are for the flow thread. 431 432 // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in 433 // a renderer, most rectangles are represented this way. 434 LayoutRect layerBoundsInFlowThread(layerBoundingBox); 435 flowThread()->flipForWritingMode(layerBoundsInFlowThread); 436 437 // Now we can compare with the flow thread portions owned by each column. First let's 438 // see if the rect intersects our flow thread portion at all. 439 LayoutRect clippedRect(layerBoundsInFlowThread); 440 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); 441 if (clippedRect.isEmpty()) 442 return; 443 444 // Now we know we intersect at least one column. Let's figure out the logical top and logical 445 // bottom of the area we're checking. 446 LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x(); 447 LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1; 448 449 // Figure out the start and end columns and only check within that range so that we don't walk the 450 // entire column set. 451 unsigned startColumn = columnIndexAtOffset(layerLogicalTop); 452 unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); 453 454 LayoutUnit colLogicalWidth = computedColumnWidth(); 455 LayoutUnit colGap = columnGap(); 456 unsigned colCount = columnCount(); 457 458 for (unsigned i = startColumn; i <= endColumn; i++) { 459 // Get the portion of the flow thread that corresponds to this column. 460 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); 461 462 // Now get the overflow rect that corresponds to the column. 463 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); 464 465 // In order to create a fragment we must intersect the portion painted by this column. 466 LayoutRect clippedRect(layerBoundsInFlowThread); 467 clippedRect.intersect(flowThreadOverflowPortion); 468 if (clippedRect.isEmpty()) 469 continue; 470 471 // We also need to intersect the dirty rect. We have to apply a translation and shift based off 472 // our column index. 473 LayoutPoint translationOffset; 474 LayoutUnit inlineOffset = i * (colLogicalWidth + colGap); 475 if (!style()->isLeftToRightDirection()) 476 inlineOffset = -inlineOffset; 477 translationOffset.setX(inlineOffset); 478 LayoutUnit blockOffset = isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x(); 479 if (isFlippedBlocksWritingMode(style()->writingMode())) 480 blockOffset = -blockOffset; 481 translationOffset.setY(blockOffset); 482 if (!isHorizontalWritingMode()) 483 translationOffset = translationOffset.transposedPoint(); 484 // FIXME: The translation needs to include the multicolumn set's content offset within the 485 // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets. 486 487 // Shift the dirty rect to be in flow thread coordinates with this translation applied. 488 LayoutRect translatedDirtyRect(dirtyRect); 489 translatedDirtyRect.moveBy(-translationOffset); 490 491 // See if we intersect the dirty rect. 492 clippedRect = layerBoundingBox; 493 clippedRect.intersect(translatedDirtyRect); 494 if (clippedRect.isEmpty()) 495 continue; 496 497 // Something does need to paint in this column. Make a fragment now and supply the physical translation 498 // offset and the clip rect for the column with that offset applied. 499 LayerFragment fragment; 500 fragment.paginationOffset = translationOffset; 501 502 LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); 503 // Flip it into more a physical (RenderLayer-style) rectangle. 504 flowThread()->flipForWritingMode(flippedFlowThreadOverflowPortion); 505 fragment.paginationClip = flippedFlowThreadOverflowPortion; 506 fragments.append(fragment); 507 } 508 } 509 510 const char* RenderMultiColumnSet::renderName() const 511 { 512 return "RenderMultiColumnSet"; 513 } 514 515 } 516