Chromium Code Reviews| Index: Source/core/layout/MultiColumnFragmentainerGroup.cpp |
| diff --git a/Source/core/layout/MultiColumnFragmentainerGroup.cpp b/Source/core/layout/MultiColumnFragmentainerGroup.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..e0acffd15b4621588225404b6a965b598f2ff456 |
| --- /dev/null |
| +++ b/Source/core/layout/MultiColumnFragmentainerGroup.cpp |
| @@ -0,0 +1,509 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "config.h" |
| + |
| +#include "core/layout/MultiColumnFragmentainerGroup.h" |
| + |
| +#include "core/rendering/RenderMultiColumnSet.h" |
| + |
| +namespace blink { |
| + |
| +MultiColumnFragmentainerGroup::MultiColumnFragmentainerGroup(RenderMultiColumnSet& columnSet) |
| + : m_columnSet(columnSet) |
| +{ |
| +} |
| + |
| +bool MultiColumnFragmentainerGroup::isLastGroup() const |
| +{ |
| + return &m_columnSet.lastFragmentainerGroup() == this; |
| +} |
| + |
| +LayoutSize MultiColumnFragmentainerGroup::offsetFromColumnSet() const |
| +{ |
| + LayoutSize offset(LayoutUnit(), logicalTop()); |
| + if (!m_columnSet.flowThread()->isHorizontalWritingMode()) |
| + return offset.transposedSize(); |
| + return offset; |
| +} |
| + |
| +bool MultiColumnFragmentainerGroup::heightIsAuto() const |
| +{ |
| + // Only the last row may have auto height, and thus be balanced. There are no good reasons to |
| + // balance the preceding rows, and that could potentially lead to an insane number of layout |
| + // passes as well. |
|
Julien - ping for review
2015/02/11 07:25:29
Maybe there is some ASSERT we could land to avoid
mstensho (USE GERRIT)
2015/02/11 09:49:54
This very code already makes sure that we only bal
|
| + return isLastGroup() && m_columnSet.heightIsAuto(); |
| +} |
| + |
| +void MultiColumnFragmentainerGroup::resetColumnHeight() |
| +{ |
| + // Nuke previously stored minimum column height. Contents may have changed for all we know. |
| + m_minimumColumnHeight = 0; |
| + |
| + m_maxColumnHeight = calculateMaxColumnHeight(); |
| + |
| + LayoutUnit oldColumnHeight = m_columnHeight; |
| + |
| + if (heightIsAuto()) |
| + m_columnHeight = LayoutUnit(); |
| + else |
| + setAndConstrainColumnHeight(heightAdjustedForRowOffset(m_columnSet.multiColumnFlowThread()->columnHeightAvailable())); |
| + |
| + if (m_columnHeight != oldColumnHeight) |
| + m_columnSet.setChildNeedsLayout(MarkOnlyThis); |
| + |
| + // Content runs are only needed in the initial layout pass, in order to find an initial column |
| + // height, and should have been deleted afterwards. We're about to rebuild the content runs, so |
| + // the list needs to be empty. |
| + ASSERT(m_contentRuns.isEmpty()); |
| +} |
| + |
| +void MultiColumnFragmentainerGroup::addContentRun(LayoutUnit endOffsetInFlowThread) |
| +{ |
| + if (!m_contentRuns.isEmpty() && endOffsetInFlowThread <= m_contentRuns.last().breakOffset()) |
| + return; |
| + // Append another item as long as we haven't exceeded used column count. What ends up in the |
| + // overflow area shouldn't affect column balancing. |
| + if (m_contentRuns.size() < m_columnSet.usedColumnCount()) |
| + m_contentRuns.append(ContentRun(endOffsetInFlowThread)); |
| +} |
| + |
| +void MultiColumnFragmentainerGroup::recordSpaceShortage(LayoutUnit spaceShortage) |
| +{ |
| + if (spaceShortage >= m_minSpaceShortage) |
| + return; |
| + |
| + // The space shortage is what we use as our stretch amount. We need a positive number here in |
| + // order to get anywhere. |
| + ASSERT(spaceShortage > 0); |
| + |
| + m_minSpaceShortage = spaceShortage; |
| +} |
| + |
| +bool MultiColumnFragmentainerGroup::recalculateColumnHeight(BalancedColumnHeightCalculation calculationMode) |
| +{ |
| + LayoutUnit oldColumnHeight = m_columnHeight; |
| + |
| + m_maxColumnHeight = calculateMaxColumnHeight(); |
| + |
| + if (heightIsAuto()) { |
| + if (calculationMode == GuessFromFlowThreadPortion) { |
| + // Post-process the content runs and find out where the implicit breaks will occur. |
| + distributeImplicitBreaks(); |
| + } |
| + LayoutUnit newColumnHeight = calculateColumnHeight(calculationMode); |
| + setAndConstrainColumnHeight(newColumnHeight); |
| + // After having calculated an initial column height, the multicol container typically needs at |
| + // least one more layout pass with a new column height, but if a height was specified, we only |
| + // need to do this if we think that we need less space than specified. Conversely, if we |
| + // determined that the columns need to be as tall as the specified height of the container, we |
| + // have already laid it out correctly, and there's no need for another pass. |
| + } else { |
| + // The position of the column set may have changed, in which case height available for |
| + // columns may have changed as well. |
| + setAndConstrainColumnHeight(m_columnHeight); |
| + } |
| + |
| + // We can get rid of the content runs now, if we haven't already done so. They are only needed |
| + // to calculate the initial balanced column height. In fact, we have to get rid of them before |
| + // the next layout pass, since each pass will rebuild this. |
| + m_contentRuns.clear(); |
| + |
| + if (m_columnHeight == oldColumnHeight) |
| + return false; // No change. We're done. |
| + |
| + m_minSpaceShortage = RenderFlowThread::maxLogicalHeight(); |
| + return true; // Need another pass. |
| +} |
| + |
| +void MultiColumnFragmentainerGroup::expandToEncompassFlowThreadOverflow() |
| +{ |
| + ASSERT(isLastGroup()); |
| + // Get the offset within the flow thread in its block progression direction. Then get the |
| + // flow thread's remaining logical height including its overflow and expand our rect |
| + // to encompass that remaining height and overflow. The idea is that we will generate |
| + // additional columns and pages to hold that overflow, since people do write bad |
| + // content like <body style="height:0px"> in multi-column layouts. |
| + RenderMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(); |
| + LayoutRect layoutRect = flowThread->layoutOverflowRect(); |
| + m_logicalBottomInFlowThread = flowThread->isHorizontalWritingMode() ? layoutRect.maxY() : layoutRect.maxX(); |
| +} |
| + |
| +LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread) const |
| +{ |
| + unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread); |
| + LayoutRect portionRect(flowThreadPortionRectAt(columnIndex)); |
| + m_columnSet.flipForWritingMode(portionRect); |
| + LayoutRect columnRect(columnRectAt(columnIndex)); |
| + m_columnSet.flipForWritingMode(columnRect); |
| + return columnRect.location() - portionRect.location(); |
| +} |
| + |
| +LayoutUnit MultiColumnFragmentainerGroup::columnLogicalTopForOffset(LayoutUnit offsetInFlowThread) const |
| +{ |
| + unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread, AssumeNewColumns); |
| + return m_logicalTopInFlowThread + columnIndex * m_columnHeight; |
| +} |
| + |
| +void MultiColumnFragmentainerGroup::collectLayerFragments(LayerFragments& fragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) const |
| +{ |
| + // |layerBoundingBox| is in the flow thread coordinate space, relative to the top/left edge of |
| + // the flow thread, but note that it has been converted with respect to writing mode (so that |
| + // it's visual/physical in that sense). |
| + // |
| + // |dirtyRect| is visual, relative to the multicol container. |
| + // |
| + // Then there's the output from this method - the stuff we put into the list of fragments. The |
| + // fragment.paginationOffset point is the actual visual translation required to get from a |
| + // location in the flow thread to a location in a given column. The fragment.paginationClip |
| + // rectangle, on the other hand, is in flow thread coordinates. |
| + // |
| + // All other rectangles in this method are sized physically, and the inline direction coordinate |
| + // is physical too, but the block direction coordinate is "logical top". This is the same as |
| + // e.g. RenderBox::frameRect(). These rectangles also pretend that there's only one long column, |
| + // i.e. they are for the flow thread. |
| + |
| + RenderMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(); |
| + bool isHorizontalWritingMode = m_columnSet.isHorizontalWritingMode(); |
| + |
| + // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in |
| + // a renderer, most rectangles are represented this way. |
| + LayoutRect layerBoundsInFlowThread(layerBoundingBox); |
| + flowThread->flipForWritingMode(layerBoundsInFlowThread); |
| + |
| + // Now we can compare with the flow thread portions owned by each column. First let's |
| + // see if the rect intersects our flow thread portion at all. |
| + LayoutRect clippedRect(layerBoundsInFlowThread); |
| + clippedRect.intersect(m_columnSet.RenderRegion::flowThreadPortionOverflowRect()); // FIXME: clean up this mess. |
| + if (clippedRect.isEmpty()) |
| + return; |
| + |
| + // Now we know we intersect at least one column. Let's figure out the logical top and logical |
| + // bottom of the area we're checking. |
| + LayoutUnit layerLogicalTop = isHorizontalWritingMode ? layerBoundsInFlowThread.y() : layerBoundsInFlowThread.x(); |
| + LayoutUnit layerLogicalBottom = (isHorizontalWritingMode ? layerBoundsInFlowThread.maxY() : layerBoundsInFlowThread.maxX()) - 1; |
| + |
| + // Figure out the start and end columns and only check within that range so that we don't walk the |
| + // entire column row. |
| + unsigned startColumn = columnIndexAtOffset(layerLogicalTop); |
| + unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); |
| + |
| + LayoutUnit colLogicalWidth = m_columnSet.pageLogicalWidth(); |
| + LayoutUnit colGap = m_columnSet.columnGap(); |
| + unsigned colCount = actualColumnCount(); |
| + |
| + bool progressionIsInline = flowThread->progressionIsInline(); |
| + bool leftToRight = m_columnSet.style()->isLeftToRightDirection(); |
| + |
| + LayoutUnit initialBlockOffset = m_columnSet.logicalTop() + logicalTop() - flowThread->logicalTop(); |
| + |
| + for (unsigned i = startColumn; i <= endColumn; i++) { |
| + // Get the portion of the flow thread that corresponds to this column. |
| + LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); |
| + |
| + // Now get the overflow rect that corresponds to the column. |
| + LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flowThreadPortion, i, colCount, colGap); |
| + |
| + // In order to create a fragment we must intersect the portion painted by this column. |
| + LayoutRect clippedRect(layerBoundsInFlowThread); |
| + clippedRect.intersect(flowThreadOverflowPortion); |
| + if (clippedRect.isEmpty()) |
| + continue; |
| + |
| + // We also need to intersect the dirty rect. We have to apply a translation and shift based off |
| + // our column index. |
| + LayoutPoint translationOffset; |
| + LayoutUnit inlineOffset = progressionIsInline ? i * (colLogicalWidth + colGap) : LayoutUnit(); |
| + if (!leftToRight) |
| + inlineOffset = -inlineOffset; |
| + translationOffset.setX(inlineOffset); |
| + LayoutUnit blockOffset; |
| + if (progressionIsInline) { |
| + blockOffset = initialBlockOffset + (isHorizontalWritingMode ? -flowThreadPortion.y() : -flowThreadPortion.x()); |
| + } else { |
| + // Column gap can apply in the block direction for page fragmentainers. |
| + // There is currently no spec which calls for column-gap to apply |
| + // for page fragmentainers at all, but it's applied here for compatibility |
| + // with the old multicolumn implementation. |
| + blockOffset = i * colGap; |
| + } |
| + if (isFlippedBlocksWritingMode(m_columnSet.style()->writingMode())) |
| + blockOffset = -blockOffset; |
| + translationOffset.setY(blockOffset); |
| + if (!isHorizontalWritingMode) |
| + translationOffset = translationOffset.transposedPoint(); |
| + |
| + // Shift the dirty rect to be in flow thread coordinates with this translation applied. |
| + LayoutRect translatedDirtyRect(dirtyRect); |
| + translatedDirtyRect.moveBy(-translationOffset); |
| + |
| + // See if we intersect the dirty rect. |
| + clippedRect = layerBoundingBox; |
| + clippedRect.intersect(translatedDirtyRect); |
| + if (clippedRect.isEmpty()) |
| + continue; |
| + |
| + // Something does need to paint in this column. Make a fragment now and supply the physical translation |
| + // offset and the clip rect for the column with that offset applied. |
| + LayerFragment fragment; |
| + fragment.paginationOffset = translationOffset; |
| + |
| + LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); |
| + // Flip it into more a physical (RenderLayer-style) rectangle. |
| + flowThread->flipForWritingMode(flippedFlowThreadOverflowPortion); |
| + fragment.paginationClip = flippedFlowThreadOverflowPortion; |
| + fragments.append(fragment); |
| + } |
| +} |
| + |
| +LayoutRect MultiColumnFragmentainerGroup::calculateOverflow() const |
| +{ |
| + unsigned columnCount = actualColumnCount(); |
| + if (!columnCount) |
| + return LayoutRect(); |
| + return columnRectAt(columnCount - 1); |
| +} |
| + |
| +unsigned MultiColumnFragmentainerGroup::actualColumnCount() const |
| +{ |
| + // We must always return a value of 1 or greater. Column count = 0 is a meaningless situation, |
| + // and will confuse and cause problems in other parts of the code. |
| + if (!m_columnHeight) |
| + return 1; |
| + |
| + // Our flow thread portion determines our column count. We have as many columns as needed to fit |
| + // all the content. |
| + LayoutUnit flowThreadPortionHeight = logicalHeightInFlowThread(); |
| + if (!flowThreadPortionHeight) |
| + return 1; |
| + |
| + unsigned count = ceil(flowThreadPortionHeight.toFloat() / m_columnHeight.toFloat()); |
| + ASSERT(count >= 1); |
| + return count; |
| +} |
| + |
| +LayoutUnit MultiColumnFragmentainerGroup::heightAdjustedForRowOffset(LayoutUnit height) const |
| +{ |
| + // Adjust for the top offset within the content box of the multicol container (containing |
| + // block), unless we're in the first set. We know that the top offset for the first set will be |
| + // zero, but if the multicol container has non-zero top border or padding, the set's top offset |
| + // (initially being 0 and relative to the border box) will be negative until it has been laid |
| + // out. Had we used this bogus offset, we would calculate the wrong height, and risk performing |
| + // a wasted layout iteration. Of course all other sets (if any) have this problem in the first |
| + // layout pass too, but there's really nothing we can do there until the flow thread has been |
| + // laid out anyway. |
| + if (m_columnSet.previousSiblingMultiColumnSet()) { |
| + RenderBlockFlow* multicolBlock = m_columnSet.multiColumnBlockFlow(); |
| + LayoutUnit contentLogicalTop = m_columnSet.logicalTop() - multicolBlock->borderAndPaddingBefore(); |
| + height -= contentLogicalTop; |
| + } |
| + height -= logicalTop(); |
| + return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created. |
| +} |
| + |
| +LayoutUnit MultiColumnFragmentainerGroup::calculateMaxColumnHeight() const |
| +{ |
| + RenderBlockFlow* multicolBlock = m_columnSet.multiColumnBlockFlow(); |
| + LayoutStyle* multicolStyle = multicolBlock->style(); |
| + LayoutUnit availableHeight = m_columnSet.multiColumnFlowThread()->columnHeightAvailable(); |
| + LayoutUnit maxColumnHeight = availableHeight ? availableHeight : RenderFlowThread::maxLogicalHeight(); |
| + if (!multicolStyle->logicalMaxHeight().isMaxSizeNone()) { |
| + LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalMaxHeight(), -1); |
| + if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight) |
| + maxColumnHeight = logicalMaxHeight; |
| + } |
| + return heightAdjustedForRowOffset(maxColumnHeight); |
| +} |
| + |
| +void MultiColumnFragmentainerGroup::setAndConstrainColumnHeight(LayoutUnit newHeight) |
| +{ |
| + m_columnHeight = newHeight; |
| + if (m_columnHeight > m_maxColumnHeight) |
| + m_columnHeight = m_maxColumnHeight; |
| + // FIXME: the height may also be affected by the enclosing pagination context, if any. |
| +} |
| + |
| +unsigned MultiColumnFragmentainerGroup::findRunWithTallestColumns() const |
| +{ |
| + unsigned indexWithLargestHeight = 0; |
| + LayoutUnit largestHeight; |
| + LayoutUnit previousOffset = m_logicalTopInFlowThread; |
| + size_t runCount = m_contentRuns.size(); |
| + ASSERT(runCount); |
| + for (size_t i = 0; i < runCount; i++) { |
| + const ContentRun& run = m_contentRuns[i]; |
| + LayoutUnit height = run.columnLogicalHeight(previousOffset); |
| + if (largestHeight < height) { |
| + largestHeight = height; |
| + indexWithLargestHeight = i; |
| + } |
| + previousOffset = run.breakOffset(); |
| + } |
| + return indexWithLargestHeight; |
| +} |
| + |
| +void MultiColumnFragmentainerGroup::distributeImplicitBreaks() |
| +{ |
| +#if ENABLE(ASSERT) |
| + // There should be no implicit breaks assumed at this point. |
| + for (unsigned i = 0; i < m_contentRuns.size(); i++) |
| + ASSERT(!m_contentRuns[i].assumedImplicitBreaks()); |
| +#endif // ENABLE(ASSERT) |
| + |
| + // Insert a final content run to encompass all content. This will include overflow if this is |
| + // the last set. |
| + addContentRun(m_logicalBottomInFlowThread); |
| + unsigned columnCount = m_contentRuns.size(); |
| + |
| + // If there is room for more breaks (to reach the used value of column-count), imagine that we |
| + // insert implicit breaks at suitable locations. At any given time, the content run with the |
| + // currently tallest columns will get another implicit break "inserted", which will increase its |
| + // column count by one and shrink its columns' height. Repeat until we have the desired total |
| + // number of breaks. The largest column height among the runs will then be the initial column |
| + // height for the balancer to use. |
| + while (columnCount < m_columnSet.usedColumnCount()) { |
| + unsigned index = findRunWithTallestColumns(); |
| + m_contentRuns[index].assumeAnotherImplicitBreak(); |
| + columnCount++; |
| + } |
| +} |
| + |
| +LayoutUnit MultiColumnFragmentainerGroup::calculateColumnHeight(BalancedColumnHeightCalculation calculationMode) const |
| +{ |
| + if (calculationMode == GuessFromFlowThreadPortion) { |
| + // Initial balancing. Start with the lowest imaginable column height. We use the tallest |
| + // content run (after having "inserted" implicit breaks), and find its start offset (by |
| + // looking at the previous run's end offset, or, if there's no previous run, the set's start |
| + // offset in the flow thread). |
| + unsigned index = findRunWithTallestColumns(); |
| + LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset() : m_logicalTopInFlowThread; |
| + return std::max<LayoutUnit>(m_contentRuns[index].columnLogicalHeight(startOffset), m_minimumColumnHeight); |
| + } |
| + |
| + if (actualColumnCount() <= m_columnSet.usedColumnCount()) { |
| + // With the current column height, the content fits without creating overflowing columns. We're done. |
| + return m_columnHeight; |
| + } |
| + |
| + if (m_contentRuns.size() >= m_columnSet.usedColumnCount()) { |
| + // Too many forced breaks to allow any implicit breaks. Initial balancing should already |
| + // have set a good height. There's nothing more we should do. |
| + return m_columnHeight; |
| + } |
| + |
| + // If the initial guessed column height wasn't enough, stretch it now. Stretch by the lowest |
| + // amount of space shortage found during layout. |
| + |
| + ASSERT(m_minSpaceShortage > 0); // We should never _shrink_ the height! |
| + ASSERT(m_minSpaceShortage != RenderFlowThread::maxLogicalHeight()); // If this happens, we probably have a bug. |
| + if (m_minSpaceShortage == RenderFlowThread::maxLogicalHeight()) |
| + return m_columnHeight; // So bail out rather than looping infinitely. |
| + |
| + return m_columnHeight + m_minSpaceShortage; |
| +} |
| + |
| +LayoutRect MultiColumnFragmentainerGroup::columnRectAt(unsigned columnIndex) const |
| +{ |
| + LayoutUnit columnLogicalWidth = m_columnSet.pageLogicalWidth(); |
| + LayoutUnit columnLogicalHeight = m_columnHeight; |
| + LayoutUnit columnLogicalTop; |
| + LayoutUnit columnLogicalLeft; |
| + LayoutUnit columnGap = m_columnSet.columnGap(); |
| + |
| + if (m_columnSet.multiColumnFlowThread()->progressionIsInline()) { |
| + if (m_columnSet.style()->isLeftToRightDirection()) |
| + columnLogicalLeft += columnIndex * (columnLogicalWidth + columnGap); |
| + else |
| + columnLogicalLeft += m_columnSet.contentLogicalWidth() - columnLogicalWidth - columnIndex * (columnLogicalWidth + columnGap); |
| + } else { |
| + columnLogicalTop += columnIndex * (columnLogicalHeight + columnGap); |
| + } |
| + |
| + LayoutRect columnRect(columnLogicalLeft, columnLogicalTop, columnLogicalWidth, columnLogicalHeight); |
| + if (!m_columnSet.isHorizontalWritingMode()) |
| + return columnRect.transposedRect(); |
| + return columnRect; |
| +} |
| + |
| +LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionRectAt(unsigned columnIndex) const |
| +{ |
| + LayoutUnit logicalTop = m_logicalTopInFlowThread + columnIndex * m_columnHeight; |
| + if (m_columnSet.isHorizontalWritingMode()) |
| + return LayoutRect(LayoutUnit(), logicalTop, m_columnSet.pageLogicalWidth(), m_columnHeight); |
| + return LayoutRect(logicalTop, LayoutUnit(), m_columnHeight, m_columnSet.pageLogicalWidth()); |
| +} |
| + |
| +LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionOverflowRect(const LayoutRect& portionRect, unsigned columnIndex, unsigned columnCount, LayoutUnit columnGap) const |
| +{ |
| + // This function determines the portion of the flow thread that paints for the column. Along the inline axis, columns are |
| + // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column |
| + // gap along interior edges. |
| + // |
| + // In the block direction, we will not clip overflow out of the top of the first column, or out of the bottom of |
| + // the last column. This applies only to the true first column and last column across all column sets. |
| + // |
| + // FIXME: Eventually we will know overflow on a per-column basis, but we can't do this until we have a painting |
| + // mode that understands not to paint contents from a previous column in the overflow area of a following column. |
| + // This problem applies to regions and pages as well and is not unique to columns. |
| + bool isFirstColumn = !columnIndex; |
| + bool isLastColumn = columnIndex == columnCount - 1; |
| + bool isLTR = m_columnSet.style()->isLeftToRightDirection(); |
| + bool isLeftmostColumn = isLTR ? isFirstColumn : isLastColumn; |
| + bool isRightmostColumn = isLTR ? isLastColumn : isFirstColumn; |
| + |
| + // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical |
| + // top/bottom unless it's the first/last column. |
| + LayoutRect overflowRect = m_columnSet.overflowRectForFlowThreadPortion(portionRect, isFirstColumn && m_columnSet.isFirstRegion(), isLastColumn && m_columnSet.isLastRegion()); |
| + |
| + // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column |
| + // gaps. Also make sure that we avoid rounding errors. |
| + if (m_columnSet.isHorizontalWritingMode()) { |
| + if (!isLeftmostColumn) |
| + overflowRect.shiftXEdgeTo(portionRect.x() - columnGap / 2); |
| + if (!isRightmostColumn) |
| + overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + columnGap - columnGap / 2); |
| + } else { |
| + if (!isLeftmostColumn) |
| + overflowRect.shiftYEdgeTo(portionRect.y() - columnGap / 2); |
| + if (!isRightmostColumn) |
| + overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + columnGap - columnGap / 2); |
| + } |
| + return overflowRect; |
| +} |
| + |
| +unsigned MultiColumnFragmentainerGroup::columnIndexAtOffset(LayoutUnit offsetInFlowThread, ColumnIndexCalculationMode mode) const |
| +{ |
| + // Handle the offset being out of range. |
| + if (offsetInFlowThread < m_logicalTopInFlowThread) |
| + return 0; |
| + // If we're laying out right now, we cannot constrain against some logical bottom, since it |
| + // isn't known yet. Otherwise, just return the last column if we're past the logical bottom. |
| + if (mode == ClampToExistingColumns) { |
| + if (offsetInFlowThread >= m_logicalBottomInFlowThread) |
| + return actualColumnCount() - 1; |
| + } |
| + |
| + if (m_columnHeight) |
| + return (offsetInFlowThread - m_logicalTopInFlowThread).toFloat() / m_columnHeight.toFloat(); |
| + return 0; |
| +} |
| + |
| +MultiColumnFragmentainerGroupList::MultiColumnFragmentainerGroupList(RenderMultiColumnSet& columnSet) |
| + : m_columnSet(columnSet) |
| +{ |
| + append(MultiColumnFragmentainerGroup(m_columnSet)); |
| +} |
| + |
| +MultiColumnFragmentainerGroup& MultiColumnFragmentainerGroupList::addExtraGroup() |
| +{ |
| + append(MultiColumnFragmentainerGroup(m_columnSet)); |
| + return last(); |
| +} |
| + |
| +void MultiColumnFragmentainerGroupList::deleteExtraGroups() |
| +{ |
| + shrink(1); |
| +} |
| + |
| +} // namespace blink |