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