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