OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "config.h" |
| 6 |
| 7 #include "core/rendering/MultiColumnRow.h" |
| 8 |
| 9 #include "core/rendering/RenderMultiColumnSet.h" |
| 10 |
| 11 namespace blink { |
| 12 |
| 13 MultiColumnRow::MultiColumnRow(RenderMultiColumnSet& columnSet) |
| 14 : m_columnSet(columnSet) |
| 15 { |
| 16 } |
| 17 |
| 18 LayoutSize MultiColumnRow::offsetFromColumnSet() const |
| 19 { |
| 20 LayoutSize offset(LayoutUnit(), logicalTop()); |
| 21 if (!m_columnSet.flowThread()->isHorizontalWritingMode()) |
| 22 return offset.transposedSize(); |
| 23 return offset; |
| 24 } |
| 25 |
| 26 bool MultiColumnRow::heightIsAuto() const |
| 27 { |
| 28 // Only the last row may have auto height, and thus be balanced. There are n
o good reasons to |
| 29 // balance the preceding rows, and that could potentially lead to an insane
number of layout |
| 30 // passes as well. |
| 31 return !nextRow() && m_columnSet.heightIsAuto(); |
| 32 } |
| 33 |
| 34 void MultiColumnRow::resetColumnHeight() |
| 35 { |
| 36 // Nuke previously stored minimum column height. Contents may have changed f
or all we know. |
| 37 m_minimumColumnHeight = 0; |
| 38 |
| 39 m_maxColumnHeight = calculateMaxColumnHeight(); |
| 40 |
| 41 LayoutUnit oldColumnHeight = m_columnHeight; |
| 42 |
| 43 if (heightIsAuto()) |
| 44 m_columnHeight = LayoutUnit(); |
| 45 else |
| 46 setAndConstrainColumnHeight(heightAdjustedForRowOffset(m_columnSet.multi
ColumnFlowThread()->columnHeightAvailable())); |
| 47 |
| 48 if (m_columnHeight != oldColumnHeight) |
| 49 m_columnSet.setChildNeedsLayout(MarkOnlyThis); |
| 50 |
| 51 // Content runs are only needed in the initial layout pass, in order to find
an initial column |
| 52 // height, and should have been deleted afterwards. We're about to rebuild t
he content runs, so |
| 53 // the list needs to be empty. |
| 54 ASSERT(m_contentRuns.isEmpty()); |
| 55 } |
| 56 |
| 57 void MultiColumnRow::addContentRun(LayoutUnit endOffsetInFlowThread) |
| 58 { |
| 59 if (!m_contentRuns.isEmpty() && endOffsetInFlowThread <= m_contentRuns.last(
).breakOffset()) |
| 60 return; |
| 61 // Append another item as long as we haven't exceeded used column count. Wha
t ends up in the |
| 62 // overflow area shouldn't affect column balancing. |
| 63 if (m_contentRuns.size() < m_columnSet.usedColumnCount()) |
| 64 m_contentRuns.append(ContentRun(endOffsetInFlowThread)); |
| 65 } |
| 66 |
| 67 void MultiColumnRow::recordSpaceShortage(LayoutUnit spaceShortage) |
| 68 { |
| 69 if (spaceShortage >= m_minSpaceShortage) |
| 70 return; |
| 71 |
| 72 // The space shortage is what we use as our stretch amount. We need a positi
ve number here in |
| 73 // order to get anywhere. |
| 74 ASSERT(spaceShortage > 0); |
| 75 |
| 76 m_minSpaceShortage = spaceShortage; |
| 77 } |
| 78 |
| 79 bool MultiColumnRow::recalculateColumnHeight(BalancedColumnHeightCalculation cal
culationMode) |
| 80 { |
| 81 LayoutUnit oldColumnHeight = m_columnHeight; |
| 82 |
| 83 m_maxColumnHeight = calculateMaxColumnHeight(); |
| 84 |
| 85 if (heightIsAuto()) { |
| 86 if (calculationMode == GuessFromFlowThreadPortion) { |
| 87 // Post-process the content runs and find out where the implicit bre
aks will occur. |
| 88 distributeImplicitBreaks(); |
| 89 } |
| 90 LayoutUnit newColumnHeight = calculateColumnHeight(calculationMode); |
| 91 setAndConstrainColumnHeight(newColumnHeight); |
| 92 // After having calculated an initial column height, the multicol contai
ner typically needs at |
| 93 // least one more layout pass with a new column height, but if a height
was specified, we only |
| 94 // need to do this if we think that we need less space than specified. C
onversely, if we |
| 95 // determined that the columns need to be as tall as the specified heigh
t of the container, we |
| 96 // have already laid it out correctly, and there's no need for another p
ass. |
| 97 } else { |
| 98 // The position of the column set may have changed, in which case height
available for |
| 99 // columns may have changed as well. |
| 100 setAndConstrainColumnHeight(m_columnHeight); |
| 101 } |
| 102 |
| 103 // We can get rid of the content runs now, if we haven't already done so. Th
ey are only needed |
| 104 // to calculate the initial balanced column height. In fact, we have to get
rid of them before |
| 105 // the next layout pass, since each pass will rebuild this. |
| 106 m_contentRuns.clear(); |
| 107 |
| 108 if (m_columnHeight == oldColumnHeight) |
| 109 return false; // No change. We're done. |
| 110 |
| 111 m_minSpaceShortage = RenderFlowThread::maxLogicalHeight(); |
| 112 return true; // Need another pass. |
| 113 } |
| 114 |
| 115 void MultiColumnRow::expandToEncompassFlowThreadOverflow() |
| 116 { |
| 117 ASSERT(!nextRow()); |
| 118 // Get the offset within the flow thread in its block progression direction.
Then get the |
| 119 // flow thread's remaining logical height including its overflow and expand
our rect |
| 120 // to encompass that remaining height and overflow. The idea is that we will
generate |
| 121 // additional columns and pages to hold that overflow, since people do write
bad |
| 122 // content like <body style="height:0px"> in multi-column layouts. |
| 123 RenderMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(
); |
| 124 LayoutRect layoutRect = flowThread->layoutOverflowRect(); |
| 125 m_logicalBottomInFlowThread = flowThread->isHorizontalWritingMode() ? layout
Rect.maxY() : layoutRect.maxX(); |
| 126 } |
| 127 |
| 128 LayoutSize MultiColumnRow::flowThreadTranslationAtOffset(LayoutUnit offsetInFlow
Thread) const |
| 129 { |
| 130 unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread); |
| 131 LayoutRect portionRect(flowThreadPortionRectAt(columnIndex)); |
| 132 m_columnSet.flipForWritingMode(portionRect); |
| 133 LayoutRect columnRect(columnRectAt(columnIndex)); |
| 134 m_columnSet.flipForWritingMode(columnRect); |
| 135 return columnRect.location() - portionRect.location(); |
| 136 } |
| 137 |
| 138 LayoutUnit MultiColumnRow::columnLogicalTopForOffset(LayoutUnit offsetInFlowThre
ad) const |
| 139 { |
| 140 unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread, AssumeNewColu
mns); |
| 141 return m_logicalTopInFlowThread + columnIndex * m_columnHeight; |
| 142 } |
| 143 |
| 144 void MultiColumnRow::collectLayerFragments(LayerFragments& fragments, const Layo
utRect& layerBoundingBox, const LayoutRect& dirtyRect) |
| 145 { |
| 146 // |layerBoundingBox| is in the flow thread coordinate space, relative to th
e top/left edge of |
| 147 // the flow thread, but note that it has been converted with respect to writ
ing mode (so that |
| 148 // it's visual/physical in that sense). |
| 149 // |
| 150 // |dirtyRect| is visual, relative to the multicol container. |
| 151 // |
| 152 // Then there's the output from this method - the stuff we put into the list
of fragments. The |
| 153 // fragment.paginationOffset point is the actual visual translation required
to get from a |
| 154 // location in the flow thread to a location in a given column. The fragment
.paginationClip |
| 155 // rectangle, on the other hand, is in flow thread coordinates. |
| 156 // |
| 157 // All other rectangles in this method are sized physically, and the inline
direction coordinate |
| 158 // is physical too, but the block direction coordinate is "logical top". Thi
s is the same as |
| 159 // e.g. RenderBox::frameRect(). These rectangles also pretend that there's o
nly one long column, |
| 160 // i.e. they are for the flow thread. |
| 161 |
| 162 RenderMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(
); |
| 163 bool isHorizontalWritingMode = m_columnSet.isHorizontalWritingMode(); |
| 164 |
| 165 // Put the layer bounds into flow thread-local coordinates by flipping it fi
rst. Since we're in |
| 166 // a renderer, most rectangles are represented this way. |
| 167 LayoutRect layerBoundsInFlowThread(layerBoundingBox); |
| 168 flowThread->flipForWritingMode(layerBoundsInFlowThread); |
| 169 |
| 170 // Now we can compare with the flow thread portions owned by each column. Fi
rst let's |
| 171 // see if the rect intersects our flow thread portion at all. |
| 172 LayoutRect clippedRect(layerBoundsInFlowThread); |
| 173 clippedRect.intersect(m_columnSet.RenderRegion::flowThreadPortionOverflowRec
t()); // FIXME: clean up this mess. |
| 174 if (clippedRect.isEmpty()) |
| 175 return; |
| 176 |
| 177 // Now we know we intersect at least one column. Let's figure out the logica
l top and logical |
| 178 // bottom of the area we're checking. |
| 179 LayoutUnit layerLogicalTop = isHorizontalWritingMode ? layerBoundsInFlowThre
ad.y() : layerBoundsInFlowThread.x(); |
| 180 LayoutUnit layerLogicalBottom = (isHorizontalWritingMode ? layerBoundsInFlow
Thread.maxY() : layerBoundsInFlowThread.maxX()) - 1; |
| 181 |
| 182 // Figure out the start and end columns and only check within that range so
that we don't walk the |
| 183 // entire column row. |
| 184 unsigned startColumn = columnIndexAtOffset(layerLogicalTop); |
| 185 unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); |
| 186 |
| 187 LayoutUnit colLogicalWidth = m_columnSet.pageLogicalWidth(); |
| 188 LayoutUnit colGap = m_columnSet.columnGap(); |
| 189 unsigned colCount = actualColumnCount(); |
| 190 |
| 191 bool progressionIsInline = flowThread->progressionIsInline(); |
| 192 bool leftToRight = m_columnSet.style()->isLeftToRightDirection(); |
| 193 |
| 194 LayoutUnit initialBlockOffset = m_columnSet.logicalTop() + logicalTop() - fl
owThread->logicalTop(); |
| 195 |
| 196 for (unsigned i = startColumn; i <= endColumn; i++) { |
| 197 // Get the portion of the flow thread that corresponds to this column. |
| 198 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); |
| 199 |
| 200 // Now get the overflow rect that corresponds to the column. |
| 201 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flo
wThreadPortion, i, colCount, colGap); |
| 202 |
| 203 // In order to create a fragment we must intersect the portion painted b
y this column. |
| 204 LayoutRect clippedRect(layerBoundsInFlowThread); |
| 205 clippedRect.intersect(flowThreadOverflowPortion); |
| 206 if (clippedRect.isEmpty()) |
| 207 continue; |
| 208 |
| 209 // We also need to intersect the dirty rect. We have to apply a translat
ion and shift based off |
| 210 // our column index. |
| 211 LayoutPoint translationOffset; |
| 212 LayoutUnit inlineOffset = progressionIsInline ? i * (colLogicalWidth + c
olGap) : LayoutUnit(); |
| 213 if (!leftToRight) |
| 214 inlineOffset = -inlineOffset; |
| 215 translationOffset.setX(inlineOffset); |
| 216 LayoutUnit blockOffset; |
| 217 if (progressionIsInline) { |
| 218 blockOffset = initialBlockOffset + (isHorizontalWritingMode ? -flowT
hreadPortion.y() : -flowThreadPortion.x()); |
| 219 } else { |
| 220 // Column gap can apply in the block direction for page fragmentaine
rs. |
| 221 // There is currently no spec which calls for column-gap to apply |
| 222 // for page fragmentainers at all, but it's applied here for compati
bility |
| 223 // with the old multicolumn implementation. |
| 224 blockOffset = i * colGap; |
| 225 } |
| 226 if (isFlippedBlocksWritingMode(m_columnSet.style()->writingMode())) |
| 227 blockOffset = -blockOffset; |
| 228 translationOffset.setY(blockOffset); |
| 229 if (!isHorizontalWritingMode) |
| 230 translationOffset = translationOffset.transposedPoint(); |
| 231 |
| 232 // Shift the dirty rect to be in flow thread coordinates with this trans
lation applied. |
| 233 LayoutRect translatedDirtyRect(dirtyRect); |
| 234 translatedDirtyRect.moveBy(-translationOffset); |
| 235 |
| 236 // See if we intersect the dirty rect. |
| 237 clippedRect = layerBoundingBox; |
| 238 clippedRect.intersect(translatedDirtyRect); |
| 239 if (clippedRect.isEmpty()) |
| 240 continue; |
| 241 |
| 242 // Something does need to paint in this column. Make a fragment now and
supply the physical translation |
| 243 // offset and the clip rect for the column with that offset applied. |
| 244 LayerFragment fragment; |
| 245 fragment.paginationOffset = translationOffset; |
| 246 |
| 247 LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); |
| 248 // Flip it into more a physical (RenderLayer-style) rectangle. |
| 249 flowThread->flipForWritingMode(flippedFlowThreadOverflowPortion); |
| 250 fragment.paginationClip = flippedFlowThreadOverflowPortion; |
| 251 fragments.append(fragment); |
| 252 } |
| 253 } |
| 254 |
| 255 LayoutRect MultiColumnRow::calculateOverflow() const |
| 256 { |
| 257 unsigned columnCount = actualColumnCount(); |
| 258 if (!columnCount) |
| 259 return LayoutRect(); |
| 260 return columnRectAt(columnCount - 1); |
| 261 } |
| 262 |
| 263 unsigned MultiColumnRow::actualColumnCount() const |
| 264 { |
| 265 // We must always return a value of 1 or greater. Column count = 0 is a mean
ingless situation, |
| 266 // and will confuse and cause problems in other parts of the code. |
| 267 if (!m_columnHeight) |
| 268 return 1; |
| 269 |
| 270 // Our flow thread portion determines our column count. We have as many colu
mns as needed to fit |
| 271 // all the content. |
| 272 LayoutUnit flowThreadPortionHeight = logicalHeightInFlowThread(); |
| 273 if (!flowThreadPortionHeight) |
| 274 return 1; |
| 275 |
| 276 unsigned count = ceil(flowThreadPortionHeight.toFloat() / m_columnHeight.toF
loat()); |
| 277 ASSERT(count >= 1); |
| 278 return count; |
| 279 } |
| 280 |
| 281 LayoutUnit MultiColumnRow::heightAdjustedForRowOffset(LayoutUnit height) const |
| 282 { |
| 283 // Adjust for the top offset within the content box of the multicol containe
r (containing |
| 284 // block), unless we're in the first set. We know that the top offset for th
e first set will be |
| 285 // zero, but if the multicol container has non-zero top border or padding, t
he set's top offset |
| 286 // (initially being 0 and relative to the border box) will be negative until
it has been laid |
| 287 // out. Had we used this bogus offset, we would calculate the wrong height,
and risk performing |
| 288 // a wasted layout iteration. Of course all other sets (if any) have this pr
oblem in the first |
| 289 // layout pass too, but there's really nothing we can do there until the flo
w thread has been |
| 290 // laid out anyway. |
| 291 if (m_columnSet.previousSiblingMultiColumnSet()) { |
| 292 RenderBlockFlow* multicolBlock = m_columnSet.multiColumnBlockFlow(); |
| 293 LayoutUnit contentLogicalTop = m_columnSet.logicalTop() - multicolBlock-
>borderAndPaddingBefore(); |
| 294 height -= contentLogicalTop; |
| 295 } |
| 296 height -= logicalTop(); |
| 297 return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would
probably cause an infinite amount of columns to be created. |
| 298 } |
| 299 |
| 300 LayoutUnit MultiColumnRow::calculateMaxColumnHeight() const |
| 301 { |
| 302 RenderBlockFlow* multicolBlock = m_columnSet.multiColumnBlockFlow(); |
| 303 RenderStyle* multicolStyle = multicolBlock->style(); |
| 304 LayoutUnit availableHeight = m_columnSet.multiColumnFlowThread()->columnHeig
htAvailable(); |
| 305 LayoutUnit maxColumnHeight = availableHeight ? availableHeight : RenderFlowT
hread::maxLogicalHeight(); |
| 306 if (!multicolStyle->logicalMaxHeight().isMaxSizeNone()) { |
| 307 LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight
(multicolStyle->logicalMaxHeight(), -1); |
| 308 if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight) |
| 309 maxColumnHeight = logicalMaxHeight; |
| 310 } |
| 311 return heightAdjustedForRowOffset(maxColumnHeight); |
| 312 } |
| 313 |
| 314 void MultiColumnRow::setAndConstrainColumnHeight(LayoutUnit newHeight) |
| 315 { |
| 316 m_columnHeight = newHeight; |
| 317 if (m_columnHeight > m_maxColumnHeight) |
| 318 m_columnHeight = m_maxColumnHeight; |
| 319 // FIXME: the height may also be affected by the enclosing pagination contex
t, if any. |
| 320 } |
| 321 |
| 322 unsigned MultiColumnRow::findRunWithTallestColumns() const |
| 323 { |
| 324 unsigned indexWithLargestHeight = 0; |
| 325 LayoutUnit largestHeight; |
| 326 LayoutUnit previousOffset = m_logicalTopInFlowThread; |
| 327 size_t runCount = m_contentRuns.size(); |
| 328 ASSERT(runCount); |
| 329 for (size_t i = 0; i < runCount; i++) { |
| 330 const ContentRun& run = m_contentRuns[i]; |
| 331 LayoutUnit height = run.columnLogicalHeight(previousOffset); |
| 332 if (largestHeight < height) { |
| 333 largestHeight = height; |
| 334 indexWithLargestHeight = i; |
| 335 } |
| 336 previousOffset = run.breakOffset(); |
| 337 } |
| 338 return indexWithLargestHeight; |
| 339 } |
| 340 |
| 341 void MultiColumnRow::distributeImplicitBreaks() |
| 342 { |
| 343 #if ENABLE(ASSERT) |
| 344 // There should be no implicit breaks assumed at this point. |
| 345 for (unsigned i = 0; i < m_contentRuns.size(); i++) |
| 346 ASSERT(!m_contentRuns[i].assumedImplicitBreaks()); |
| 347 #endif // ENABLE(ASSERT) |
| 348 |
| 349 // Insert a final content run to encompass all content. This will include ov
erflow if this is |
| 350 // the last set. |
| 351 addContentRun(m_logicalBottomInFlowThread); |
| 352 unsigned columnCount = m_contentRuns.size(); |
| 353 |
| 354 // If there is room for more breaks (to reach the used value of column-count
), imagine that we |
| 355 // insert implicit breaks at suitable locations. At any given time, the cont
ent run with the |
| 356 // currently tallest columns will get another implicit break "inserted", whi
ch will increase its |
| 357 // column count by one and shrink its columns' height. Repeat until we have
the desired total |
| 358 // number of breaks. The largest column height among the runs will then be t
he initial column |
| 359 // height for the balancer to use. |
| 360 while (columnCount < m_columnSet.usedColumnCount()) { |
| 361 unsigned index = findRunWithTallestColumns(); |
| 362 m_contentRuns[index].assumeAnotherImplicitBreak(); |
| 363 columnCount++; |
| 364 } |
| 365 } |
| 366 |
| 367 LayoutUnit MultiColumnRow::calculateColumnHeight(BalancedColumnHeightCalculation
calculationMode) const |
| 368 { |
| 369 if (calculationMode == GuessFromFlowThreadPortion) { |
| 370 // Initial balancing. Start with the lowest imaginable column height. We
use the tallest |
| 371 // content run (after having "inserted" implicit breaks), and find its s
tart offset (by |
| 372 // looking at the previous run's end offset, or, if there's no previous
run, the set's start |
| 373 // offset in the flow thread). |
| 374 unsigned index = findRunWithTallestColumns(); |
| 375 LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffse
t() : m_logicalTopInFlowThread; |
| 376 return std::max<LayoutUnit>(m_contentRuns[index].columnLogicalHeight(sta
rtOffset), m_minimumColumnHeight); |
| 377 } |
| 378 |
| 379 if (actualColumnCount() <= m_columnSet.usedColumnCount()) { |
| 380 // With the current column height, the content fits without creating ove
rflowing columns. We're done. |
| 381 return m_columnHeight; |
| 382 } |
| 383 |
| 384 if (m_contentRuns.size() >= m_columnSet.usedColumnCount()) { |
| 385 // Too many forced breaks to allow any implicit breaks. Initial balancin
g should already |
| 386 // have set a good height. There's nothing more we should do. |
| 387 return m_columnHeight; |
| 388 } |
| 389 |
| 390 // If the initial guessed column height wasn't enough, stretch it now. Stret
ch by the lowest |
| 391 // amount of space shortage found during layout. |
| 392 |
| 393 ASSERT(m_minSpaceShortage > 0); // We should never _shrink_ the height! |
| 394 ASSERT(m_minSpaceShortage != RenderFlowThread::maxLogicalHeight()); // If th
is happens, we probably have a bug. |
| 395 if (m_minSpaceShortage == RenderFlowThread::maxLogicalHeight()) |
| 396 return m_columnHeight; // So bail out rather than looping infinitely. |
| 397 |
| 398 return m_columnHeight + m_minSpaceShortage; |
| 399 } |
| 400 |
| 401 LayoutRect MultiColumnRow::columnRectAt(unsigned columnIndex) const |
| 402 { |
| 403 LayoutUnit columnLogicalWidth = m_columnSet.pageLogicalWidth(); |
| 404 LayoutUnit columnLogicalHeight = m_columnHeight; |
| 405 LayoutUnit columnLogicalTop; |
| 406 LayoutUnit columnLogicalLeft; |
| 407 LayoutUnit columnGap = m_columnSet.columnGap(); |
| 408 |
| 409 if (m_columnSet.multiColumnFlowThread()->progressionIsInline()) { |
| 410 if (m_columnSet.style()->isLeftToRightDirection()) |
| 411 columnLogicalLeft += columnIndex * (columnLogicalWidth + columnGap); |
| 412 else |
| 413 columnLogicalLeft += m_columnSet.contentLogicalWidth() - columnLogic
alWidth - columnIndex * (columnLogicalWidth + columnGap); |
| 414 } else { |
| 415 columnLogicalTop += columnIndex * (columnLogicalHeight + columnGap); |
| 416 } |
| 417 |
| 418 LayoutRect columnRect(columnLogicalLeft, columnLogicalTop, columnLogicalWidt
h, columnLogicalHeight); |
| 419 if (!m_columnSet.isHorizontalWritingMode()) |
| 420 return columnRect.transposedRect(); |
| 421 return columnRect; |
| 422 } |
| 423 |
| 424 LayoutRect MultiColumnRow::flowThreadPortionRectAt(unsigned columnIndex) const |
| 425 { |
| 426 LayoutUnit logicalTop = m_logicalTopInFlowThread + columnIndex * m_columnHei
ght; |
| 427 if (m_columnSet.isHorizontalWritingMode()) |
| 428 return LayoutRect(LayoutUnit(), logicalTop, m_columnSet.pageLogicalWidth
(), m_columnHeight); |
| 429 return LayoutRect(logicalTop, LayoutUnit(), m_columnHeight, m_columnSet.page
LogicalWidth()); |
| 430 } |
| 431 |
| 432 LayoutRect MultiColumnRow::flowThreadPortionOverflowRect(const LayoutRect& porti
onRect, unsigned columnIndex, unsigned columnCount, LayoutUnit columnGap) const |
| 433 { |
| 434 // This function determines the portion of the flow thread that paints for t
he column. Along the inline axis, columns are |
| 435 // unclipped at outside edges (i.e., the first and last column in the set),
and they clip to half the column |
| 436 // gap along interior edges. |
| 437 // |
| 438 // In the block direction, we will not clip overflow out of the top of the f
irst column, or out of the bottom of |
| 439 // the last column. This applies only to the true first column and last colu
mn across all column sets. |
| 440 // |
| 441 // FIXME: Eventually we will know overflow on a per-column basis, but we can
't do this until we have a painting |
| 442 // mode that understands not to paint contents from a previous column in the
overflow area of a following column. |
| 443 // This problem applies to regions and pages as well and is not unique to co
lumns. |
| 444 bool isFirstColumn = !columnIndex; |
| 445 bool isLastColumn = columnIndex == columnCount - 1; |
| 446 bool isLTR = m_columnSet.style()->isLeftToRightDirection(); |
| 447 bool isLeftmostColumn = isLTR ? isFirstColumn : isLastColumn; |
| 448 bool isRightmostColumn = isLTR ? isLastColumn : isFirstColumn; |
| 449 |
| 450 // Calculate the overflow rectangle, based on the flow thread's, clipped at
column logical |
| 451 // top/bottom unless it's the first/last column. |
| 452 LayoutRect overflowRect = m_columnSet.overflowRectForFlowThreadPortion(porti
onRect, isFirstColumn && m_columnSet.isFirstRegion(), isLastColumn && m_columnSe
t.isLastRegion()); |
| 453 |
| 454 // Avoid overflowing into neighboring columns, by clipping in the middle of
adjacent column |
| 455 // gaps. Also make sure that we avoid rounding errors. |
| 456 if (m_columnSet.isHorizontalWritingMode()) { |
| 457 if (!isLeftmostColumn) |
| 458 overflowRect.shiftXEdgeTo(portionRect.x() - columnGap / 2); |
| 459 if (!isRightmostColumn) |
| 460 overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + columnGap - column
Gap / 2); |
| 461 } else { |
| 462 if (!isLeftmostColumn) |
| 463 overflowRect.shiftYEdgeTo(portionRect.y() - columnGap / 2); |
| 464 if (!isRightmostColumn) |
| 465 overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + columnGap - column
Gap / 2); |
| 466 } |
| 467 return overflowRect; |
| 468 } |
| 469 |
| 470 unsigned MultiColumnRow::columnIndexAtOffset(LayoutUnit offsetInFlowThread, Colu
mnIndexCalculationMode mode) const |
| 471 { |
| 472 // Handle the offset being out of range. |
| 473 if (offsetInFlowThread < m_logicalTopInFlowThread) |
| 474 return 0; |
| 475 // If we're laying out right now, we cannot constrain against some logical b
ottom, since it |
| 476 // isn't known yet. Otherwise, just return the last column if we're past the
logical bottom. |
| 477 if (mode == ClampToExistingColumns) { |
| 478 if (offsetInFlowThread >= m_logicalBottomInFlowThread) |
| 479 return actualColumnCount() - 1; |
| 480 } |
| 481 |
| 482 if (m_columnHeight) |
| 483 return (offsetInFlowThread - m_logicalTopInFlowThread).toFloat() / m_col
umnHeight.toFloat(); |
| 484 return 0; |
| 485 } |
| 486 |
| 487 } // namespace blink |
OLD | NEW |