2 * Copyright (C) 2012 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "core/rendering/RenderMultiColumnSet.h"
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"
38 RenderMultiColumnSet::RenderMultiColumnSet(RenderFlowThread* flowThread)
39 : RenderRegion(0, flowThread)
41 , m_maxColumnHeight(RenderFlowThread::maxLogicalHeight())
42 , m_minSpaceShortage(RenderFlowThread::maxLogicalHeight())
43 , m_minimumColumnHeight(0)
47 RenderMultiColumnSet* RenderMultiColumnSet::createAnonymous(RenderFlowThread* flowThread, RenderStyle* parentStyle)
49 Document& document = flowThread->document();
50 RenderMultiColumnSet* renderer = new RenderMultiColumnSet(flowThread);
51 renderer->setDocumentForAnonymous(&document);
52 renderer->setStyle(RenderStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK));
56 RenderMultiColumnSet* RenderMultiColumnSet::nextSiblingMultiColumnSet() const
58 for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->nextSibling()) {
59 if (sibling->isRenderMultiColumnSet())
60 return toRenderMultiColumnSet(sibling);
65 RenderMultiColumnSet* RenderMultiColumnSet::previousSiblingMultiColumnSet() const
67 for (RenderObject* sibling = previousSibling(); sibling; sibling = sibling->previousSibling()) {
68 if (sibling->isRenderMultiColumnSet())
69 return toRenderMultiColumnSet(sibling);
74 LayoutSize RenderMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockOffset) const
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();
84 LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) const
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
94 if (previousSiblingMultiColumnSet()) {
95 RenderBlockFlow* multicolBlock = multiColumnBlockFlow();
96 LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderAndPaddingBefore();
97 height -= contentLogicalTop;
99 return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created.
102 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) const
104 unsigned columnIndex = columnIndexAtOffset(offset, AssumeNewColumns);
105 return logicalTopInFlowThread() + columnIndex * pageLogicalHeight();
108 void RenderMultiColumnSet::setAndConstrainColumnHeight(LayoutUnit newHeight)
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.
116 unsigned RenderMultiColumnSet::findRunWithTallestColumns() const
118 unsigned indexWithLargestHeight = 0;
119 LayoutUnit largestHeight;
120 LayoutUnit previousOffset = logicalTopInFlowThread();
121 size_t runCount = m_contentRuns.size();
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;
130 previousOffset = run.breakOffset();
132 return indexWithLargestHeight;
135 void RenderMultiColumnSet::distributeImplicitBreaks()
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)
143 // Insert a final content run to encompass all content. This will include overflow if this is
145 addContentRun(logicalBottomInFlowThread());
146 unsigned columnCount = m_contentRuns.size();
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();
161 LayoutUnit RenderMultiColumnSet::calculateColumnHeight(BalancedHeightCalculation calculationMode) const
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);
173 if (actualColumnCount() <= usedColumnCount()) {
174 // With the current column height, the content fits without creating overflowing columns. We're done.
175 return m_columnHeight;
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;
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.
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.
192 return m_columnHeight + m_minSpaceShortage;
195 void RenderMultiColumnSet::addContentRun(LayoutUnit endOffsetFromFirstPage)
197 if (!multiColumnFlowThread()->heightIsAuto())
199 if (!m_contentRuns.isEmpty() && endOffsetFromFirstPage <= m_contentRuns.last().breakOffset())
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));
207 bool RenderMultiColumnSet::recalculateColumnHeight(BalancedHeightCalculation calculationMode)
209 if (previousSiblingMultiColumnSet()) {
210 // FIXME: column spanner layout is not yet implemented. Until it's in place, we only operate
211 // on the first set during layout. We need to ignore the others here, or assertions will
215 ASSERT(multiColumnFlowThread()->heightIsAuto());
217 LayoutUnit oldColumnHeight = m_columnHeight;
218 if (calculationMode == GuessFromFlowThreadPortion) {
219 // Post-process the content runs and find out where the implicit breaks will occur.
220 distributeImplicitBreaks();
222 LayoutUnit newColumnHeight = calculateColumnHeight(calculationMode);
223 setAndConstrainColumnHeight(newColumnHeight);
225 // After having calculated an initial column height, the multicol container typically needs at
226 // least one more layout pass with a new column height, but if a height was specified, we only
227 // need to do this if we think that we need less space than specified. Conversely, if we
228 // determined that the columns need to be as tall as the specified height of the container, we
229 // have already laid it out correctly, and there's no need for another pass.
231 // We can get rid of the content runs now, if we haven't already done so. They are only needed
232 // to calculate the initial balanced column height. In fact, we have to get rid of them before
233 // the next layout pass, since each pass will rebuild this.
234 m_contentRuns.clear();
236 if (m_columnHeight == oldColumnHeight)
237 return false; // No change. We're done.
239 m_minSpaceShortage = RenderFlowThread::maxLogicalHeight();
240 return true; // Need another pass.
243 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit spaceShortage)
245 if (spaceShortage >= m_minSpaceShortage)
248 // The space shortage is what we use as our stretch amount. We need a positive number here in
249 // order to get anywhere.
250 ASSERT(spaceShortage > 0);
252 m_minSpaceShortage = spaceShortage;
255 void RenderMultiColumnSet::resetColumnHeight()
257 // Nuke previously stored minimum column height. Contents may have changed for all we know.
258 m_minimumColumnHeight = 0;
260 m_maxColumnHeight = calculateMaxColumnHeight();
262 LayoutUnit oldColumnHeight = pageLogicalHeight();
264 if (multiColumnFlowThread()->heightIsAuto())
267 setAndConstrainColumnHeight(heightAdjustedForSetOffset(multiColumnFlowThread()->columnHeightAvailable()));
269 if (pageLogicalHeight() != oldColumnHeight)
270 setChildNeedsLayout(MarkOnlyThis);
272 // Content runs are only needed in the initial layout pass, in order to find an initial column
273 // height, and should have been deleted afterwards. We're about to rebuild the content runs, so
274 // the list needs to be empty.
275 ASSERT(m_contentRuns.isEmpty());
278 void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded()
280 ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this);
281 LayoutRect rect(flowThreadPortionRect());
283 // Get the offset within the flow thread in its block progression direction. Then get the
284 // flow thread's remaining logical height including its overflow and expand our rect
285 // to encompass that remaining height and overflow. The idea is that we will generate
286 // additional columns and pages to hold that overflow, since people do write bad
287 // content like <body style="height:0px"> in multi-column layouts.
288 bool isHorizontal = flowThread()->isHorizontalWritingMode();
289 LayoutUnit logicalTopOffset = isHorizontal ? rect.y() : rect.x();
290 LayoutRect layoutRect = flowThread()->layoutOverflowRect();
291 LayoutUnit logicalHeightWithOverflow = (isHorizontal ? layoutRect.maxY() : layoutRect.maxX()) - logicalTopOffset;
292 setFlowThreadPortionRect(LayoutRect(rect.x(), rect.y(), isHorizontal ? rect.width() : logicalHeightWithOverflow, isHorizontal ? logicalHeightWithOverflow : rect.height()));
295 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
297 computedValues.m_extent = m_columnHeight;
298 computedValues.m_position = logicalTop;
301 LayoutUnit RenderMultiColumnSet::calculateMaxColumnHeight() const
303 RenderBlockFlow* multicolBlock = multiColumnBlockFlow();
304 RenderStyle* multicolStyle = multicolBlock->style();
305 LayoutUnit availableHeight = multiColumnFlowThread()->columnHeightAvailable();
306 LayoutUnit maxColumnHeight = availableHeight ? availableHeight : RenderFlowThread::maxLogicalHeight();
307 if (!multicolStyle->logicalMaxHeight().isMaxSizeNone()) {
308 LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalMaxHeight(), -1);
309 if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight)
310 maxColumnHeight = logicalMaxHeight;
312 return heightAdjustedForSetOffset(maxColumnHeight);
315 LayoutUnit RenderMultiColumnSet::columnGap() const
317 RenderBlockFlow* parentBlock = multiColumnBlockFlow();
318 if (parentBlock->style()->hasNormalColumnGap())
319 return parentBlock->style()->fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins.
320 return parentBlock->style()->columnGap();
323 unsigned RenderMultiColumnSet::actualColumnCount() const
325 // We must always return a value of 1 or greater. Column count = 0 is a meaningless situation,
326 // and will confuse and cause problems in other parts of the code.
327 if (!pageLogicalHeight())
330 // Our portion rect determines our column count. We have as many columns as needed to fit all the content.
331 LayoutUnit logicalHeightInColumns = flowThread()->isHorizontalWritingMode() ? flowThreadPortionRect().height() : flowThreadPortionRect().width();
332 if (!logicalHeightInColumns)
335 unsigned count = ceil(logicalHeightInColumns.toFloat() / pageLogicalHeight().toFloat());
340 LayoutRect RenderMultiColumnSet::columnRectAt(unsigned index) const
342 LayoutUnit colLogicalWidth = pageLogicalWidth();
343 LayoutUnit colLogicalHeight = pageLogicalHeight();
344 LayoutUnit colLogicalTop = borderBefore() + paddingBefore();
345 LayoutUnit colLogicalLeft = borderAndPaddingLogicalLeft();
346 LayoutUnit colGap = columnGap();
348 if (multiColumnFlowThread()->progressionIsInline()) {
349 if (style()->isLeftToRightDirection())
350 colLogicalLeft += index * (colLogicalWidth + colGap);
352 colLogicalLeft += contentLogicalWidth() - colLogicalWidth - index * (colLogicalWidth + colGap);
354 colLogicalTop += index * (colLogicalHeight + colGap);
357 if (isHorizontalWritingMode())
358 return LayoutRect(colLogicalLeft, colLogicalTop, colLogicalWidth, colLogicalHeight);
359 return LayoutRect(colLogicalTop, colLogicalLeft, colLogicalHeight, colLogicalWidth);
362 unsigned RenderMultiColumnSet::columnIndexAtOffset(LayoutUnit offset, ColumnIndexCalculationMode mode) const
364 LayoutRect portionRect(flowThreadPortionRect());
366 // Handle the offset being out of range.
367 LayoutUnit flowThreadLogicalTop = isHorizontalWritingMode() ? portionRect.y() : portionRect.x();
368 if (offset < flowThreadLogicalTop)
370 // If we're laying out right now, we cannot constrain against some logical bottom, since it
371 // isn't known yet. Otherwise, just return the last column if we're past the logical bottom.
372 if (mode == ClampToExistingColumns) {
373 LayoutUnit flowThreadLogicalBottom = isHorizontalWritingMode() ? portionRect.maxY() : portionRect.maxX();
374 if (offset >= flowThreadLogicalBottom)
375 return actualColumnCount() - 1;
378 // Just divide by the column height to determine the correct column.
379 return (offset - flowThreadLogicalTop).toFloat() / pageLogicalHeight().toFloat();
382 LayoutRect RenderMultiColumnSet::flowThreadPortionRectAt(unsigned index) const
384 LayoutRect portionRect = flowThreadPortionRect();
385 if (isHorizontalWritingMode())
386 portionRect = LayoutRect(portionRect.x(), portionRect.y() + index * pageLogicalHeight(), portionRect.width(), pageLogicalHeight());
388 portionRect = LayoutRect(portionRect.x() + index * pageLogicalHeight(), portionRect.y(), pageLogicalHeight(), portionRect.height());
392 LayoutRect RenderMultiColumnSet::flowThreadPortionOverflowRect(const LayoutRect& portionRect, unsigned index, unsigned colCount, LayoutUnit colGap) const
394 // This function determines the portion of the flow thread that paints for the column. Along the inline axis, columns are
395 // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column
396 // gap along interior edges.
398 // In the block direction, we will not clip overflow out of the top of the first column, or out of the bottom of
399 // the last column. This applies only to the true first column and last column across all column sets.
401 // FIXME: Eventually we will know overflow on a per-column basis, but we can't do this until we have a painting
402 // mode that understands not to paint contents from a previous column in the overflow area of a following column.
403 // This problem applies to regions and pages as well and is not unique to columns.
404 bool isFirstColumn = !index;
405 bool isLastColumn = index == colCount - 1;
406 bool isLeftmostColumn = style()->isLeftToRightDirection() ? isFirstColumn : isLastColumn;
407 bool isRightmostColumn = style()->isLeftToRightDirection() ? isLastColumn : isFirstColumn;
409 // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical
410 // top/bottom unless it's the first/last column.
411 LayoutRect overflowRect = overflowRectForFlowThreadPortion(portionRect, isFirstColumn && isFirstRegion(), isLastColumn && isLastRegion());
413 // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column
414 // gaps. Also make sure that we avoid rounding errors.
415 if (isHorizontalWritingMode()) {
416 if (!isLeftmostColumn)
417 overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2);
418 if (!isRightmostColumn)
419 overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap - colGap / 2);
421 if (!isLeftmostColumn)
422 overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2);
423 if (!isRightmostColumn)
424 overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap - colGap / 2);
429 void RenderMultiColumnSet::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
431 MultiColumnSetPainter(*this).paintObject(paintInfo, paintOffset);
434 void RenderMultiColumnSet::paintInvalidationForFlowThreadContent(const LayoutRect& paintInvalidationRect) const
436 // Figure out the start and end columns and only check within that range so that we don't walk the
437 // entire column set. Put the paint invalidation rect into flow thread coordinates by flipping it first.
438 LayoutRect flowThreadPaintInvalidationRect(paintInvalidationRect);
439 flowThread()->flipForWritingMode(flowThreadPaintInvalidationRect);
441 // Now we can compare this rect with the flow thread portions owned by each column. First let's
442 // just see if the paint invalidation rect intersects our flow thread portion at all.
443 LayoutRect clippedRect(flowThreadPaintInvalidationRect);
444 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect());
445 if (clippedRect.isEmpty())
448 // Now we know we intersect at least one column. Let's figure out the logical top and logical
449 // bottom of the area in which we're issuing paint invalidations.
450 LayoutUnit paintInvalidationLogicalTop = isHorizontalWritingMode() ? flowThreadPaintInvalidationRect.y() : flowThreadPaintInvalidationRect.x();
451 LayoutUnit paintInvalidationLogicalBottom = (isHorizontalWritingMode() ? flowThreadPaintInvalidationRect.maxY() : flowThreadPaintInvalidationRect.maxX()) - 1;
453 unsigned startColumn = columnIndexAtOffset(paintInvalidationLogicalTop);
454 unsigned endColumn = columnIndexAtOffset(paintInvalidationLogicalBottom);
456 LayoutUnit colGap = columnGap();
457 unsigned colCount = actualColumnCount();
458 for (unsigned i = startColumn; i <= endColumn; i++) {
459 LayoutRect colRect = columnRectAt(i);
461 // Get the portion of the flow thread that corresponds to this column.
462 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i);
464 // Now get the overflow rect that corresponds to the column.
465 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap);
467 // Do a paint invalidation for this specific column.
468 paintInvalidationOfFlowThreadContentRectangle(paintInvalidationRect, flowThreadPortion, flowThreadOverflowPortion, colRect.location());
472 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect)
474 // |layerBoundingBox| is in the flow thread coordinate space, relative to the top/left edge of
475 // the flow thread, but note that it has been converted with respect to writing mode (so that
476 // it's visual/physical in that sense).
478 // |dirtyRect| is visual, relative to the multicol container.
480 // Then there's the output from this method - the stuff we put into the list of fragments. The
481 // fragment.paginationOffset point is the actual visual translation required to get from a
482 // location in the flow thread to a location in a given column. The fragment.paginationClip
483 // rectangle, on the other hand, is in flow thread coordinates.
485 // All other rectangles in this method are sized physically, and the inline direction coordinate
486 // is physical too, but the block direction coordinate is "logical top". This is the same as
487 // e.g. RenderBox::frameRect(). These rectangles also pretend that there's only one long column,
488 // i.e. they are for the flow thread.
490 // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in
491 // a renderer, most rectangles are represented this way.
492 LayoutRect layerBoundsInFlowThread(layerBoundingBox);
493 flowThread()->flipForWritingMode(layerBoundsInFlowThread);
495 // Now we can compare with the flow thread portions owned by each column. First let's
496 // see if the rect intersects our flow thread portion at all.
497 LayoutRect clippedRect(layerBoundsInFlowThread);
498 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect());
499 if (clippedRect.isEmpty())
502 // Now we know we intersect at least one column. Let's figure out the logical top and logical
503 // bottom of the area we're checking.
504 LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x();
505 LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1;
507 // Figure out the start and end columns and only check within that range so that we don't walk the
508 // entire column set.
509 unsigned startColumn = columnIndexAtOffset(layerLogicalTop);
510 unsigned endColumn = columnIndexAtOffset(layerLogicalBottom);
512 LayoutUnit colLogicalWidth = pageLogicalWidth();
513 LayoutUnit colGap = columnGap();
514 unsigned colCount = actualColumnCount();
516 RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread();
517 bool progressionIsInline = flowThread->progressionIsInline();
518 bool leftToRight = style()->isLeftToRightDirection();
520 LayoutUnit initialBlockOffset = logicalTop() - flowThread->logicalTop();
522 for (unsigned i = startColumn; i <= endColumn; i++) {
523 // Get the portion of the flow thread that corresponds to this column.
524 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i);
526 // Now get the overflow rect that corresponds to the column.
527 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap);
529 // In order to create a fragment we must intersect the portion painted by this column.
530 LayoutRect clippedRect(layerBoundsInFlowThread);
531 clippedRect.intersect(flowThreadOverflowPortion);
532 if (clippedRect.isEmpty())
535 // We also need to intersect the dirty rect. We have to apply a translation and shift based off
537 LayoutPoint translationOffset;
538 LayoutUnit inlineOffset = progressionIsInline ? i * (colLogicalWidth + colGap) : LayoutUnit();
540 inlineOffset = -inlineOffset;
541 translationOffset.setX(inlineOffset);
542 LayoutUnit blockOffset;
543 if (progressionIsInline) {
544 blockOffset = initialBlockOffset + (isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x());
546 // Column gap can apply in the block direction for page fragmentainers.
547 // There is currently no spec which calls for column-gap to apply
548 // for page fragmentainers at all, but it's applied here for compatibility
549 // with the old multicolumn implementation.
550 blockOffset = i * colGap;
552 if (isFlippedBlocksWritingMode(style()->writingMode()))
553 blockOffset = -blockOffset;
554 translationOffset.setY(blockOffset);
555 if (!isHorizontalWritingMode())
556 translationOffset = translationOffset.transposedPoint();
557 // FIXME: The translation needs to include the multicolumn set's content offset within the
558 // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets.
560 // Shift the dirty rect to be in flow thread coordinates with this translation applied.
561 LayoutRect translatedDirtyRect(dirtyRect);
562 translatedDirtyRect.moveBy(-translationOffset);
564 // See if we intersect the dirty rect.
565 clippedRect = layerBoundingBox;
566 clippedRect.intersect(translatedDirtyRect);
567 if (clippedRect.isEmpty())
570 // Something does need to paint in this column. Make a fragment now and supply the physical translation
571 // offset and the clip rect for the column with that offset applied.
572 LayerFragment fragment;
573 fragment.paginationOffset = translationOffset;
575 LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion);
576 // Flip it into more a physical (RenderLayer-style) rectangle.
577 flowThread->flipForWritingMode(flippedFlowThreadOverflowPortion);
578 fragment.paginationClip = flippedFlowThreadOverflowPortion;
579 fragments.append(fragment);
583 void RenderMultiColumnSet::addOverflowFromChildren()
585 unsigned colCount = actualColumnCount();
589 LayoutRect lastRect = columnRectAt(colCount - 1);
590 addLayoutOverflow(lastRect);
591 if (!hasOverflowClip())
592 addVisualOverflow(lastRect);
595 const char* RenderMultiColumnSet::renderName() const
597 return "RenderMultiColumnSet";
600 void RenderMultiColumnSet::insertedIntoTree()
602 RenderRegion::insertedIntoTree();
607 void RenderMultiColumnSet::willBeRemovedFromTree()
609 RenderRegion::willBeRemovedFromTree();
614 void RenderMultiColumnSet::attachRegion()
616 if (documentBeingDestroyed())
619 // A region starts off invalid.
625 // Only after adding the region to the thread, the region is marked to be valid.
626 m_flowThread->addRegionToThread(this);
629 void RenderMultiColumnSet::detachRegion()
632 m_flowThread->removeRegionFromThread(this);