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/rendering/PaintInfo.h"
30 #include "core/rendering/RenderLayer.h"
31 #include "core/rendering/RenderMultiColumnBlock.h"
32 #include "core/rendering/RenderMultiColumnFlowThread.h"
38 RenderMultiColumnSet::RenderMultiColumnSet(RenderFlowThread* flowThread)
39 : RenderRegionSet(0, flowThread)
40 , m_computedColumnCount(1)
41 , m_computedColumnWidth(0)
42 , m_computedColumnHeight(0)
43 , m_maxColumnHeight(RenderFlowThread::maxLogicalHeight())
44 , m_minSpaceShortage(RenderFlowThread::maxLogicalHeight())
45 , m_minimumColumnHeight(0)
49 RenderMultiColumnSet* RenderMultiColumnSet::createAnonymous(RenderFlowThread* flowThread)
51 Document& document = flowThread->document();
52 RenderMultiColumnSet* renderer = new RenderMultiColumnSet(flowThread);
53 renderer->setDocumentForAnonymous(&document);
57 LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) const
59 RenderMultiColumnBlock* multicolBlock = toRenderMultiColumnBlock(parent());
60 LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderBefore() - multicolBlock->paddingBefore();
62 height -= contentLogicalTop;
63 return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created.
66 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) const
68 LayoutUnit portionLogicalTop = (isHorizontalWritingMode() ? flowThreadPortionRect().y() : flowThreadPortionRect().x());
69 unsigned columnIndex = columnIndexAtOffset(offset, AssumeNewColumns);
70 return portionLogicalTop + columnIndex * computedColumnHeight();
73 void RenderMultiColumnSet::setAndConstrainColumnHeight(LayoutUnit newHeight)
75 m_computedColumnHeight = newHeight;
76 if (m_computedColumnHeight > m_maxColumnHeight)
77 m_computedColumnHeight = m_maxColumnHeight;
78 // FIXME: the height may also be affected by the enclosing pagination context, if any.
81 unsigned RenderMultiColumnSet::findRunWithTallestColumns() const
83 unsigned indexWithLargestHeight = 0;
84 LayoutUnit largestHeight;
85 LayoutUnit previousOffset;
86 size_t runCount = m_contentRuns.size();
88 for (size_t i = 0; i < runCount; i++) {
89 const ContentRun& run = m_contentRuns[i];
90 LayoutUnit height = run.columnLogicalHeight(previousOffset);
91 if (largestHeight < height) {
92 largestHeight = height;
93 indexWithLargestHeight = i;
95 previousOffset = run.breakOffset();
97 return indexWithLargestHeight;
100 void RenderMultiColumnSet::distributeImplicitBreaks()
102 unsigned breakCount = forcedBreaksCount();
105 // There should be no implicit breaks assumed at this point.
106 for (unsigned i = 0; i < breakCount; i++)
107 ASSERT(!m_contentRuns[i].assumedImplicitBreaks());
110 // There will always be at least one break, since the flow thread reports a "forced break" at
112 ASSERT(breakCount >= 1);
114 // If there is room for more breaks (to reach the used value of column-count), imagine that we
115 // insert implicit breaks at suitable locations. At any given time, the content run with the
116 // currently tallest columns will get another implicit break "inserted", which will increase its
117 // column count by one and shrink its columns' height. Repeat until we have the desired total
118 // number of breaks. The largest column height among the runs will then be the initial column
119 // height for the balancer to use.
120 while (breakCount < m_computedColumnCount) {
121 unsigned index = findRunWithTallestColumns();
122 m_contentRuns[index].assumeAnotherImplicitBreak();
127 LayoutUnit RenderMultiColumnSet::calculateBalancedHeight(bool initial) const
130 // Start with the lowest imaginable column height.
131 unsigned index = findRunWithTallestColumns();
132 LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset() : LayoutUnit();
133 return std::max<LayoutUnit>(m_contentRuns[index].columnLogicalHeight(startOffset), m_minimumColumnHeight);
136 if (columnCount() <= computedColumnCount()) {
137 // With the current column height, the content fits without creating overflowing columns. We're done.
138 return m_computedColumnHeight;
141 if (forcedBreaksCount() > 1 && forcedBreaksCount() >= computedColumnCount()) {
142 // Too many forced breaks to allow any implicit breaks. Initial balancing should already
143 // have set a good height. There's nothing more we should do.
144 return m_computedColumnHeight;
147 // If the initial guessed column height wasn't enough, stretch it now. Stretch by the lowest
148 // amount of space shortage found during layout.
150 ASSERT(m_minSpaceShortage > 0); // We should never _shrink_ the height!
151 ASSERT(m_minSpaceShortage != RenderFlowThread::maxLogicalHeight()); // If this happens, we probably have a bug.
152 if (m_minSpaceShortage == RenderFlowThread::maxLogicalHeight())
153 return m_computedColumnHeight; // So bail out rather than looping infinitely.
155 return m_computedColumnHeight + m_minSpaceShortage;
158 void RenderMultiColumnSet::clearForcedBreaks()
160 m_contentRuns.clear();
163 void RenderMultiColumnSet::addForcedBreak(LayoutUnit offsetFromFirstPage)
165 if (!toRenderMultiColumnBlock(parent())->requiresBalancing())
167 if (!m_contentRuns.isEmpty() && offsetFromFirstPage <= m_contentRuns.last().breakOffset())
169 // Append another item as long as we haven't exceeded used column count. What ends up in the
170 // overflow area shouldn't affect column balancing.
171 if (m_contentRuns.size() < m_computedColumnCount)
172 m_contentRuns.append(ContentRun(offsetFromFirstPage));
175 bool RenderMultiColumnSet::recalculateBalancedHeight(bool initial)
177 ASSERT(toRenderMultiColumnBlock(parent())->requiresBalancing());
179 LayoutUnit oldColumnHeight = m_computedColumnHeight;
181 distributeImplicitBreaks();
182 LayoutUnit newColumnHeight = calculateBalancedHeight(initial);
183 setAndConstrainColumnHeight(newColumnHeight);
185 // After having calculated an initial column height, the multicol container typically needs at
186 // least one more layout pass with a new column height, but if a height was specified, we only
187 // need to do this if we think that we need less space than specified. Conversely, if we
188 // determined that the columns need to be as tall as the specified height of the container, we
189 // have already laid it out correctly, and there's no need for another pass.
191 if (m_computedColumnHeight == oldColumnHeight)
192 return false; // No change. We're done.
194 m_minSpaceShortage = RenderFlowThread::maxLogicalHeight();
196 return true; // Need another pass.
199 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit spaceShortage)
201 if (spaceShortage >= m_minSpaceShortage)
204 // The space shortage is what we use as our stretch amount. We need a positive number here in
205 // order to get anywhere.
206 ASSERT(spaceShortage > 0);
208 m_minSpaceShortage = spaceShortage;
211 void RenderMultiColumnSet::updateLogicalWidth()
213 RenderMultiColumnBlock* parentBlock = toRenderMultiColumnBlock(parent());
214 setComputedColumnWidthAndCount(parentBlock->columnWidth(), parentBlock->columnCount()); // FIXME: This will eventually vary if we are contained inside regions.
216 // FIXME: When we add regions support, we'll start it off at the width of the multi-column
217 // block in that particular region.
218 setLogicalWidth(parentBox()->contentLogicalWidth());
220 // If we overflow, increase our logical width.
221 unsigned colCount = columnCount();
222 LayoutUnit colGap = columnGap();
223 LayoutUnit minimumContentLogicalWidth = colCount * computedColumnWidth() + (colCount - 1) * colGap;
224 LayoutUnit currentContentLogicalWidth = contentLogicalWidth();
225 LayoutUnit delta = max(LayoutUnit(), minimumContentLogicalWidth - currentContentLogicalWidth);
229 // Increase our logical width by the delta.
230 setLogicalWidth(logicalWidth() + delta);
233 void RenderMultiColumnSet::prepareForLayout()
235 RenderMultiColumnBlock* multicolBlock = toRenderMultiColumnBlock(parent());
236 RenderStyle* multicolStyle = multicolBlock->style();
238 // Set box logical top.
239 ASSERT(!previousSiblingBox() || !previousSiblingBox()->isRenderMultiColumnSet()); // FIXME: multiple set not implemented; need to examine previous set to calculate the correct logical top.
240 setLogicalTop(multicolBlock->borderBefore() + multicolBlock->paddingBefore());
243 updateLogicalWidth();
245 if (multicolBlock->requiresBalancing()) {
246 // Set maximum column height. We will not stretch beyond this.
247 m_maxColumnHeight = RenderFlowThread::maxLogicalHeight();
248 if (!multicolStyle->logicalHeight().isAuto()) {
249 m_maxColumnHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalHeight(), -1);
250 if (m_maxColumnHeight == -1)
251 m_maxColumnHeight = RenderFlowThread::maxLogicalHeight();
253 if (!multicolStyle->logicalMaxHeight().isUndefined()) {
254 LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalMaxHeight(), -1);
255 if (logicalMaxHeight != -1 && m_maxColumnHeight > logicalMaxHeight)
256 m_maxColumnHeight = logicalMaxHeight;
258 m_maxColumnHeight = heightAdjustedForSetOffset(m_maxColumnHeight);
259 m_computedColumnHeight = 0; // Restart balancing.
261 setAndConstrainColumnHeight(heightAdjustedForSetOffset(multicolBlock->columnHeightAvailable()));
266 // Nuke previously stored minimum column height. Contents may have changed for all we know.
267 m_minimumColumnHeight = 0;
270 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
272 computedValues.m_extent = m_computedColumnHeight;
273 computedValues.m_position = logicalTop;
276 LayoutUnit RenderMultiColumnSet::columnGap() const
278 // FIXME: Eventually we will cache the column gap when the widths of columns start varying, but for now we just
279 // go to the parent block to get the gap.
280 RenderMultiColumnBlock* parentBlock = toRenderMultiColumnBlock(parent());
281 if (parentBlock->style()->hasNormalColumnGap())
282 return parentBlock->style()->fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins.
283 return parentBlock->style()->columnGap();
286 unsigned RenderMultiColumnSet::columnCount() const
288 // We must always return a value of 1 or greater. Column count = 0 is a meaningless situation,
289 // and will confuse and cause problems in other parts of the code.
290 if (!computedColumnHeight())
293 // Our portion rect determines our column count. We have as many columns as needed to fit all the content.
294 LayoutUnit logicalHeightInColumns = flowThread()->isHorizontalWritingMode() ? flowThreadPortionRect().height() : flowThreadPortionRect().width();
295 unsigned count = ceil(static_cast<float>(logicalHeightInColumns) / computedColumnHeight());
300 LayoutRect RenderMultiColumnSet::columnRectAt(unsigned index) const
302 LayoutUnit colLogicalWidth = computedColumnWidth();
303 LayoutUnit colLogicalHeight = computedColumnHeight();
304 LayoutUnit colLogicalTop = borderBefore() + paddingBefore();
305 LayoutUnit colLogicalLeft = borderAndPaddingLogicalLeft();
306 LayoutUnit colGap = columnGap();
307 if (style()->isLeftToRightDirection())
308 colLogicalLeft += index * (colLogicalWidth + colGap);
310 colLogicalLeft += contentLogicalWidth() - colLogicalWidth - index * (colLogicalWidth + colGap);
312 if (isHorizontalWritingMode())
313 return LayoutRect(colLogicalLeft, colLogicalTop, colLogicalWidth, colLogicalHeight);
314 return LayoutRect(colLogicalTop, colLogicalLeft, colLogicalHeight, colLogicalWidth);
317 unsigned RenderMultiColumnSet::columnIndexAtOffset(LayoutUnit offset, ColumnIndexCalculationMode mode) const
319 LayoutRect portionRect(flowThreadPortionRect());
321 // Handle the offset being out of range.
322 LayoutUnit flowThreadLogicalTop = isHorizontalWritingMode() ? portionRect.y() : portionRect.x();
323 if (offset < flowThreadLogicalTop)
325 // If we're laying out right now, we cannot constrain against some logical bottom, since it
326 // isn't known yet. Otherwise, just return the last column if we're past the logical bottom.
327 if (mode == ClampToExistingColumns) {
328 LayoutUnit flowThreadLogicalBottom = isHorizontalWritingMode() ? portionRect.maxY() : portionRect.maxX();
329 if (offset >= flowThreadLogicalBottom)
330 return columnCount() - 1;
333 // Just divide by the column height to determine the correct column.
334 return static_cast<float>(offset - flowThreadLogicalTop) / computedColumnHeight();
337 LayoutRect RenderMultiColumnSet::flowThreadPortionRectAt(unsigned index) const
339 LayoutRect portionRect = flowThreadPortionRect();
340 if (isHorizontalWritingMode())
341 portionRect = LayoutRect(portionRect.x(), portionRect.y() + index * computedColumnHeight(), portionRect.width(), computedColumnHeight());
343 portionRect = LayoutRect(portionRect.x() + index * computedColumnHeight(), portionRect.y(), computedColumnHeight(), portionRect.height());
347 LayoutRect RenderMultiColumnSet::flowThreadPortionOverflowRect(const LayoutRect& portionRect, unsigned index, unsigned colCount, LayoutUnit colGap) const
349 // This function determines the portion of the flow thread that paints for the column. Along the inline axis, columns are
350 // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column
351 // gap along interior edges.
353 // In the block direction, we will not clip overflow out of the top of the first column, or out of the bottom of
354 // the last column. This applies only to the true first column and last column across all column sets.
356 // FIXME: Eventually we will know overflow on a per-column basis, but we can't do this until we have a painting
357 // mode that understands not to paint contents from a previous column in the overflow area of a following column.
358 // This problem applies to regions and pages as well and is not unique to columns.
359 bool isFirstColumn = !index;
360 bool isLastColumn = index == colCount - 1;
361 bool isLeftmostColumn = style()->isLeftToRightDirection() ? isFirstColumn : isLastColumn;
362 bool isRightmostColumn = style()->isLeftToRightDirection() ? isLastColumn : isFirstColumn;
364 // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical
365 // top/bottom unless it's the first/last column.
366 LayoutRect overflowRect = overflowRectForFlowThreadPortion(portionRect, isFirstColumn && isFirstRegion(), isLastColumn && isLastRegion());
368 // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column
369 // gaps. Also make sure that we avoid rounding errors.
370 if (isHorizontalWritingMode()) {
371 if (!isLeftmostColumn)
372 overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2);
373 if (!isRightmostColumn)
374 overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap - colGap / 2);
376 if (!isLeftmostColumn)
377 overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2);
378 if (!isRightmostColumn)
379 overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap - colGap / 2);
384 void RenderMultiColumnSet::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
386 if (style()->visibility() != VISIBLE)
389 RenderBlockFlow::paintObject(paintInfo, paintOffset);
391 // FIXME: Right now we're only painting in the foreground phase.
392 // Columns should technically respect phases and allow for background/float/foreground overlap etc., just like
393 // RenderBlocks do. Note this is a pretty minor issue, since the old column implementation clipped columns
394 // anyway, thus making it impossible for them to overlap one another. It's also really unlikely that the columns
395 // would overlap another block.
396 if (!m_flowThread || !isValid() || (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection))
399 paintColumnRules(paintInfo, paintOffset);
402 void RenderMultiColumnSet::paintColumnRules(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
404 if (paintInfo.context->paintingDisabled())
407 RenderStyle* blockStyle = toRenderMultiColumnBlock(parent())->style();
408 const Color& ruleColor = resolveColor(blockStyle, CSSPropertyWebkitColumnRuleColor);
409 bool ruleTransparent = blockStyle->columnRuleIsTransparent();
410 EBorderStyle ruleStyle = blockStyle->columnRuleStyle();
411 LayoutUnit ruleThickness = blockStyle->columnRuleWidth();
412 LayoutUnit colGap = columnGap();
413 bool renderRule = ruleStyle > BHIDDEN && !ruleTransparent;
417 unsigned colCount = columnCount();
421 bool antialias = shouldAntialiasLines(paintInfo.context);
423 bool leftToRight = style()->isLeftToRightDirection();
424 LayoutUnit currLogicalLeftOffset = leftToRight ? LayoutUnit() : contentLogicalWidth();
425 LayoutUnit ruleAdd = borderAndPaddingLogicalLeft();
426 LayoutUnit ruleLogicalLeft = leftToRight ? LayoutUnit() : contentLogicalWidth();
427 LayoutUnit inlineDirectionSize = computedColumnWidth();
428 BoxSide boxSide = isHorizontalWritingMode()
429 ? leftToRight ? BSLeft : BSRight
430 : leftToRight ? BSTop : BSBottom;
432 for (unsigned i = 0; i < colCount; i++) {
433 // Move to the next position.
435 ruleLogicalLeft += inlineDirectionSize + colGap / 2;
436 currLogicalLeftOffset += inlineDirectionSize + colGap;
438 ruleLogicalLeft -= (inlineDirectionSize + colGap / 2);
439 currLogicalLeftOffset -= (inlineDirectionSize + colGap);
442 // Now paint the column rule.
443 if (i < colCount - 1) {
444 LayoutUnit ruleLeft = isHorizontalWritingMode() ? paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd : paintOffset.x() + borderLeft() + paddingLeft();
445 LayoutUnit ruleRight = isHorizontalWritingMode() ? ruleLeft + ruleThickness : ruleLeft + contentWidth();
446 LayoutUnit ruleTop = isHorizontalWritingMode() ? paintOffset.y() + borderTop() + paddingTop() : paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd;
447 LayoutUnit ruleBottom = isHorizontalWritingMode() ? ruleTop + contentHeight() : ruleTop + ruleThickness;
448 IntRect pixelSnappedRuleRect = pixelSnappedIntRectFromEdges(ruleLeft, ruleTop, ruleRight, ruleBottom);
449 drawLineForBoxSide(paintInfo.context, pixelSnappedRuleRect.x(), pixelSnappedRuleRect.y(), pixelSnappedRuleRect.maxX(), pixelSnappedRuleRect.maxY(), boxSide, ruleColor, ruleStyle, 0, 0, antialias);
452 ruleLogicalLeft = currLogicalLeftOffset;
456 void RenderMultiColumnSet::repaintFlowThreadContent(const LayoutRect& repaintRect) const
458 // Figure out the start and end columns and only check within that range so that we don't walk the
459 // entire column set. Put the repaint rect into flow thread coordinates by flipping it first.
460 LayoutRect flowThreadRepaintRect(repaintRect);
461 flowThread()->flipForWritingMode(flowThreadRepaintRect);
463 // Now we can compare this rect with the flow thread portions owned by each column. First let's
464 // just see if the repaint rect intersects our flow thread portion at all.
465 LayoutRect clippedRect(flowThreadRepaintRect);
466 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect());
467 if (clippedRect.isEmpty())
470 // Now we know we intersect at least one column. Let's figure out the logical top and logical
471 // bottom of the area we're repainting.
472 LayoutUnit repaintLogicalTop = isHorizontalWritingMode() ? flowThreadRepaintRect.y() : flowThreadRepaintRect.x();
473 LayoutUnit repaintLogicalBottom = (isHorizontalWritingMode() ? flowThreadRepaintRect.maxY() : flowThreadRepaintRect.maxX()) - 1;
475 unsigned startColumn = columnIndexAtOffset(repaintLogicalTop);
476 unsigned endColumn = columnIndexAtOffset(repaintLogicalBottom);
478 LayoutUnit colGap = columnGap();
479 unsigned colCount = columnCount();
480 for (unsigned i = startColumn; i <= endColumn; i++) {
481 LayoutRect colRect = columnRectAt(i);
483 // Get the portion of the flow thread that corresponds to this column.
484 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i);
486 // Now get the overflow rect that corresponds to the column.
487 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap);
489 // Do a repaint for this specific column.
490 repaintFlowThreadContentRectangle(repaintRect, flowThreadPortion, flowThreadOverflowPortion, colRect.location());
494 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect)
496 // The two rectangles passed to this method are physical, except that we pretend that there's
497 // only one long column (that's how a flow thread works).
499 // Then there's the output from this method - the stuff we put into the list of fragments. The
500 // fragment.paginationOffset point is the actual physical translation required to get from a
501 // location in the flow thread to a location in a given column. The fragment.paginationClip
502 // rectangle, on the other hand, is in the same coordinate system as the two rectangles passed
503 // to this method (flow thread coordinates).
505 // All other rectangles in this method are sized physically, and the inline direction coordinate
506 // is physical too, but the block direction coordinate is "logical top". This is the same as
507 // e.g. RenderBox::frameRect(). These rectangles also pretend that there's only one long column,
508 // i.e. they are for the flow thread.
510 // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in
511 // a renderer, most rectangles are represented this way.
512 LayoutRect layerBoundsInFlowThread(layerBoundingBox);
513 flowThread()->flipForWritingMode(layerBoundsInFlowThread);
515 // Now we can compare with the flow thread portions owned by each column. First let's
516 // see if the rect intersects our flow thread portion at all.
517 LayoutRect clippedRect(layerBoundsInFlowThread);
518 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect());
519 if (clippedRect.isEmpty())
522 // Now we know we intersect at least one column. Let's figure out the logical top and logical
523 // bottom of the area we're checking.
524 LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x();
525 LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1;
527 // Figure out the start and end columns and only check within that range so that we don't walk the
528 // entire column set.
529 unsigned startColumn = columnIndexAtOffset(layerLogicalTop);
530 unsigned endColumn = columnIndexAtOffset(layerLogicalBottom);
532 LayoutUnit colLogicalWidth = computedColumnWidth();
533 LayoutUnit colGap = columnGap();
534 unsigned colCount = columnCount();
536 for (unsigned i = startColumn; i <= endColumn; i++) {
537 // Get the portion of the flow thread that corresponds to this column.
538 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i);
540 // Now get the overflow rect that corresponds to the column.
541 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap);
543 // In order to create a fragment we must intersect the portion painted by this column.
544 LayoutRect clippedRect(layerBoundsInFlowThread);
545 clippedRect.intersect(flowThreadOverflowPortion);
546 if (clippedRect.isEmpty())
549 // We also need to intersect the dirty rect. We have to apply a translation and shift based off
551 LayoutPoint translationOffset;
552 LayoutUnit inlineOffset = i * (colLogicalWidth + colGap);
553 if (!style()->isLeftToRightDirection())
554 inlineOffset = -inlineOffset;
555 translationOffset.setX(inlineOffset);
556 LayoutUnit blockOffset = isHorizontalWritingMode() ? -flowThreadPortion.y() : -flowThreadPortion.x();
557 if (isFlippedBlocksWritingMode(style()->writingMode()))
558 blockOffset = -blockOffset;
559 translationOffset.setY(blockOffset);
560 if (!isHorizontalWritingMode())
561 translationOffset = translationOffset.transposedPoint();
562 // FIXME: The translation needs to include the multicolumn set's content offset within the
563 // multicolumn block as well. This won't be an issue until we start creating multiple multicolumn sets.
565 // Shift the dirty rect to be in flow thread coordinates with this translation applied.
566 LayoutRect translatedDirtyRect(dirtyRect);
567 translatedDirtyRect.moveBy(-translationOffset);
569 // See if we intersect the dirty rect.
570 clippedRect = layerBoundingBox;
571 clippedRect.intersect(translatedDirtyRect);
572 if (clippedRect.isEmpty())
575 // Something does need to paint in this column. Make a fragment now and supply the physical translation
576 // offset and the clip rect for the column with that offset applied.
577 LayerFragment fragment;
578 fragment.paginationOffset = translationOffset;
580 LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion);
581 // Flip it into more a physical (RenderLayer-style) rectangle.
582 flowThread()->flipForWritingMode(flippedFlowThreadOverflowPortion);
583 fragment.paginationClip = flippedFlowThreadOverflowPortion;
584 fragments.append(fragment);
588 const char* RenderMultiColumnSet::renderName() const
590 return "RenderMultiColumnSet";