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 LayoutRect overflowRect(portionRect); 286 if (isHorizontalWritingMode()) { 287 if (isLeftmostColumn) { 288 // Shift to the logical left overflow of the flow thread to make sure it's all covered. 289 overflowRect.shiftXEdgeTo(min(flowThread()->visualOverflowRect().x(), portionRect.x())); 290 } else { 291 // Expand into half of the logical left column gap. 292 overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2); 293 } 294 if (isRightmostColumn) { 295 // Shift to the logical right overflow of the flow thread to ensure content can spill out of the column. 296 overflowRect.shiftMaxXEdgeTo(max(flowThread()->visualOverflowRect().maxX(), portionRect.maxX())); 297 } else { 298 // Expand into half of the logical right column gap. 299 overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap / 2); 300 } 301 } else { 302 if (isLeftmostColumn) { 303 // Shift to the logical left overflow of the flow thread to make sure it's all covered. 304 overflowRect.shiftYEdgeTo(min(flowThread()->visualOverflowRect().y(), portionRect.y())); 305 } else { 306 // Expand into half of the logical left column gap. 307 overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2); 308 } 309 if (isRightmostColumn) { 310 // Shift to the logical right overflow of the flow thread to ensure content can spill out of the column. 311 overflowRect.shiftMaxYEdgeTo(max(flowThread()->visualOverflowRect().maxY(), portionRect.maxY())); 312 } else { 313 // Expand into half of the logical right column gap. 314 overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap / 2); 315 } 316 } 317 return overflowRectForFlowThreadPortion(overflowRect, isFirstRegion() && isFirstColumn, isLastRegion() && isLastColumn); 318 } 319 320 void RenderMultiColumnSet::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 321 { 322 if (style()->visibility() != VISIBLE) 323 return; 324 325 RenderBlock::paintObject(paintInfo, paintOffset); 326 327 // FIXME: Right now we're only painting in the foreground phase. 328 // Columns should technically respect phases and allow for background/float/foreground overlap etc., just like 329 // RenderBlocks do. Note this is a pretty minor issue, since the old column implementation clipped columns 330 // anyway, thus making it impossible for them to overlap one another. It's also really unlikely that the columns 331 // would overlap another block. 332 if (!m_flowThread || !isValid() || (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection)) 333 return; 334 335 paintColumnRules(paintInfo, paintOffset); 336 } 337 338 void RenderMultiColumnSet::paintColumnRules(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 339 { 340 if (paintInfo.context->paintingDisabled()) 341 return; 342 343 RenderStyle* blockStyle = toRenderMultiColumnBlock(parent())->style(); 344 const Color& ruleColor = resolveColor(blockStyle, CSSPropertyWebkitColumnRuleColor); 345 bool ruleTransparent = blockStyle->columnRuleIsTransparent(); 346 EBorderStyle ruleStyle = blockStyle->columnRuleStyle(); 347 LayoutUnit ruleThickness = blockStyle->columnRuleWidth(); 348 LayoutUnit colGap = columnGap(); 349 bool renderRule = ruleStyle > BHIDDEN && !ruleTransparent; 350 if (!renderRule) 351 return; 352 353 unsigned colCount = columnCount(); 354 if (colCount <= 1) 355 return; 356 357 bool antialias = shouldAntialiasLines(paintInfo.context); 358 359 bool leftToRight = style()->isLeftToRightDirection(); 360 LayoutUnit currLogicalLeftOffset = leftToRight ? LayoutUnit() : contentLogicalWidth(); 361 LayoutUnit ruleAdd = borderAndPaddingLogicalLeft(); 362 LayoutUnit ruleLogicalLeft = leftToRight ? LayoutUnit() : contentLogicalWidth(); 363 LayoutUnit inlineDirectionSize = computedColumnWidth(); 364 BoxSide boxSide = isHorizontalWritingMode() 365 ? leftToRight ? BSLeft : BSRight 366 : leftToRight ? BSTop : BSBottom; 367 368 for (unsigned i = 0; i < colCount; i++) { 369 // Move to the next position. 370 if (leftToRight) { 371 ruleLogicalLeft += inlineDirectionSize + colGap / 2; 372 currLogicalLeftOffset += inlineDirectionSize + colGap; 373 } else { 374 ruleLogicalLeft -= (inlineDirectionSize + colGap / 2); 375 currLogicalLeftOffset -= (inlineDirectionSize + colGap); 376 } 377 378 // Now paint the column rule. 379 if (i < colCount - 1) { 380 LayoutUnit ruleLeft = isHorizontalWritingMode() ? paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd : paintOffset.x() + borderLeft() + paddingLeft(); 381 LayoutUnit ruleRight = isHorizontalWritingMode() ? ruleLeft + ruleThickness : ruleLeft + contentWidth(); 382 LayoutUnit ruleTop = isHorizontalWritingMode() ? paintOffset.y() + borderTop() + paddingTop() : paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd; 383 LayoutUnit ruleBottom = isHorizontalWritingMode() ? ruleTop + contentHeight() : ruleTop + ruleThickness; 384 IntRect pixelSnappedRuleRect = pixelSnappedIntRectFromEdges(ruleLeft, ruleTop, ruleRight, ruleBottom); 385 drawLineForBoxSide(paintInfo.context, pixelSnappedRuleRect.x(), pixelSnappedRuleRect.y(), pixelSnappedRuleRect.maxX(), pixelSnappedRuleRect.maxY(), boxSide, ruleColor, ruleStyle, 0, 0, antialias); 386 } 387 388 ruleLogicalLeft = currLogicalLeftOffset; 389 } 390 } 391 392 void RenderMultiColumnSet::repaintFlowThreadContent(const LayoutRect& repaintRect) const 393 { 394 // Figure out the start and end columns and only check within that range so that we don't walk the 395 // entire column set. Put the repaint rect into flow thread coordinates by flipping it first. 396 LayoutRect flowThreadRepaintRect(repaintRect); 397 flowThread()->flipForWritingMode(flowThreadRepaintRect); 398 399 // Now we can compare this rect with the flow thread portions owned by each column. First let's 400 // just see if the repaint rect intersects our flow thread portion at all. 401 LayoutRect clippedRect(flowThreadRepaintRect); 402 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); 403 if (clippedRect.isEmpty()) 404 return; 405 406 // Now we know we intersect at least one column. Let's figure out the logical top and logical 407 // bottom of the area we're repainting. 408 LayoutUnit repaintLogicalTop = isHorizontalWritingMode() ? flowThreadRepaintRect.y() : flowThreadRepaintRect.x(); 409 LayoutUnit repaintLogicalBottom = (isHorizontalWritingMode() ? flowThreadRepaintRect.maxY() : flowThreadRepaintRect.maxX()) - 1; 410 411 unsigned startColumn = columnIndexAtOffset(repaintLogicalTop); 412 unsigned endColumn = columnIndexAtOffset(repaintLogicalBottom); 413 414 LayoutUnit colGap = columnGap(); 415 unsigned colCount = columnCount(); 416 for (unsigned i = startColumn; i <= endColumn; i++) { 417 LayoutRect colRect = columnRectAt(i); 418 419 // Get the portion of the flow thread that corresponds to this column. 420 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); 421 422 // Now get the overflow rect that corresponds to the column. 423 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); 424 425 // Do a repaint for this specific column. 426 repaintFlowThreadContentRectangle(repaintRect, flowThreadPortion, flowThreadOverflowPortion, colRect.location()); 427 } 428 } 429 430 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) 431 { 432 // Put the layer bounds into flow thread-local coordinates by flipping it first. 433 LayoutRect layerBoundsInFlowThread(layerBoundingBox); 434 flowThread()->flipForWritingMode(layerBoundsInFlowThread); 435 436 // Do the same for the dirty rect. 437 LayoutRect dirtyRectInFlowThread(dirtyRect); 438 flowThread()->flipForWritingMode(dirtyRectInFlowThread); 439 440 // Now we can compare with the flow thread portions owned by each column. First let's 441 // see if the rect intersects our flow thread portion at all. 442 LayoutRect clippedRect(layerBoundsInFlowThread); 443 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); 444 if (clippedRect.isEmpty()) 445 return; 446 447 // Now we know we intersect at least one column. Let's figure out the logical top and logical 448 // bottom of the area we're checking. 449 LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x(); 450 LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1; 451 452 // Figure out the start and end columns and only check within that range so that we don't walk the 453 // entire column set. 454 unsigned startColumn = columnIndexAtOffset(layerLogicalTop); 455 unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); 456 457 LayoutUnit colLogicalWidth = computedColumnWidth(); 458 LayoutUnit colGap = columnGap(); 459 unsigned colCount = columnCount(); 460 461 for (unsigned i = startColumn; i <= endColumn; i++) { 462 // Get the portion of the flow thread that corresponds to this column. 463 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); 464 465 // Now get the overflow rect that corresponds to the column. 466 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); 467 468 // In order to create a fragment we must intersect the portion painted by this column. 469 LayoutRect clippedRect(layerBoundsInFlowThread); 470 clippedRect.intersect(flowThreadOverflowPortion); 471 if (clippedRect.isEmpty()) 472 continue; 473 474 // We also need to intersect the dirty rect. We have to apply a translation and shift based off 475 // our column index. 476 LayoutPoint translationOffset; 477 LayoutUnit inlineOffset = i * (colLogicalWidth + colGap); 478 if (!style()->isLeftToRightDirection()) 479 inlineOffset = -inlineOffset; 480 translationOffset.setX(inlineOffset); 481 LayoutUnit blockOffset = isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x(); 482 if (isFlippedBlocksWritingMode(style()->writingMode())) 483 blockOffset = -blockOffset; 484 translationOffset.setY(blockOffset); 485 if (!isHorizontalWritingMode()) 486 translationOffset = translationOffset.transposedPoint(); 487 // FIXME: The translation needs to include the multicolumn set's content offset within the 488 // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets. 489 490 // Shift the dirty rect to be in flow thread coordinates with this translation applied. 491 LayoutRect translatedDirtyRect(dirtyRectInFlowThread); 492 translatedDirtyRect.moveBy(-translationOffset); 493 494 // See if we intersect the dirty rect. 495 clippedRect = layerBoundsInFlowThread; 496 clippedRect.intersect(translatedDirtyRect); 497 if (clippedRect.isEmpty()) 498 continue; 499 500 // Something does need to paint in this column. Make a fragment now and supply the physical translation 501 // offset and the clip rect for the column with that offset applied. 502 LayerFragment fragment; 503 fragment.paginationOffset = translationOffset; 504 505 LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); 506 flipForWritingMode(flippedFlowThreadOverflowPortion); 507 fragment.paginationClip = flippedFlowThreadOverflowPortion; 508 fragments.append(fragment); 509 } 510 } 511 512 const char* RenderMultiColumnSet::renderName() const 513 { 514 return "RenderMultiColumnSet"; 515 } 516 517 } 518