| // 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/layout/ColumnBalancer.h" |
| #include "core/layout/FragmentationContext.h" |
| #include "core/layout/LayoutMultiColumnSet.h" |
| |
| namespace blink { |
| |
| MultiColumnFragmentainerGroup::MultiColumnFragmentainerGroup(LayoutMultiColumnSet& columnSet) |
| : m_columnSet(columnSet) |
| { |
| } |
| |
| bool MultiColumnFragmentainerGroup::isFirstGroup() const |
| { |
| return &m_columnSet.firstFragmentainerGroup() == this; |
| } |
| |
| 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; |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::blockOffsetInEnclosingFragmentationContext() const |
| { |
| return logicalTop() + m_columnSet.logicalTop() + m_columnSet.multiColumnFlowThread()->blockOffsetInEnclosingFragmentationContext(); |
| } |
| |
| void MultiColumnFragmentainerGroup::resetColumnHeight() |
| { |
| m_maxColumnHeight = calculateMaxColumnHeight(); |
| |
| LayoutMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(); |
| if (m_columnSet.heightIsAuto()) { |
| FragmentationContext* enclosingFragmentationContext = flowThread->enclosingFragmentationContext(); |
| if (enclosingFragmentationContext && enclosingFragmentationContext->isFragmentainerLogicalHeightKnown()) { |
| // Even if height is auto, we set an initial height, in order to tell how much content |
| // this MultiColumnFragmentainerGroup can hold, and when we need to append a new one. |
| m_columnHeight = m_maxColumnHeight; |
| } else { |
| m_columnHeight = LayoutUnit(); |
| } |
| } else { |
| setAndConstrainColumnHeight(heightAdjustedForRowOffset(flowThread->columnHeightAvailable())); |
| } |
| } |
| |
| bool MultiColumnFragmentainerGroup::recalculateColumnHeight() |
| { |
| LayoutUnit oldColumnHeight = m_columnHeight; |
| |
| m_maxColumnHeight = calculateMaxColumnHeight(); |
| |
| // 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. |
| if (isLastGroup() && m_columnSet.heightIsAuto()) { |
| LayoutUnit newColumnHeight; |
| if (!m_columnSet.isInitialHeightCalculated()) { |
| // Initial balancing: Start with the lowest imaginable column height. Also calculate the |
| // height of the tallest piece of unbreakable content. Columns should never get any |
| // shorter than that (unless constrained by max-height). Propagate this to our |
| // containing column set, in case there is an outer multicol container that also needs |
| // to balance. After having calculated the initial column height, the multicol container |
| // needs another layout pass with the column height that we just calculated. |
| InitialColumnHeightFinder initialHeightFinder(*this); |
| LayoutUnit tallestUnbreakableLogicalHeight = initialHeightFinder.tallestUnbreakableLogicalHeight(); |
| m_columnSet.propagateTallestUnbreakableLogicalHeight(tallestUnbreakableLogicalHeight); |
| newColumnHeight = std::max(initialHeightFinder.initialMinimalBalancedHeight(), tallestUnbreakableLogicalHeight); |
| } else { |
| // Rebalancing: After having laid out again, we'll need to rebalance if the height |
| // wasn't enough and we're allowed to stretch it, and then re-lay out. There are further |
| // details on the column balancing machinery in ColumnBalancer and its derivates. |
| newColumnHeight = rebalanceColumnHeightIfNeeded(); |
| } |
| setAndConstrainColumnHeight(newColumnHeight); |
| } 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); |
| } |
| |
| if (m_columnHeight == oldColumnHeight) |
| return false; // No change. We're done. |
| |
| return true; // Need another pass. |
| } |
| |
| LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread) const |
| { |
| LayoutMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(); |
| unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread); |
| LayoutRect portionRect(flowThreadPortionRectAt(columnIndex)); |
| flowThread->flipForWritingMode(portionRect); |
| LayoutRect columnRect(columnRectAt(columnIndex)); |
| m_columnSet.flipForWritingMode(columnRect); |
| LayoutSize translationRelativeToGroup = columnRect.location() - portionRect.location(); |
| |
| LayoutSize enclosingTranslation; |
| if (LayoutMultiColumnFlowThread* enclosingFlowThread = flowThread->enclosingFlowThread()) { |
| // Translation that would map points in the coordinate space of the outermost flow thread to |
| // visual points in the first column in the first fragmentainer group (row) in our multicol |
| // container. |
| LayoutSize enclosingTranslationOrigin = enclosingFlowThread->flowThreadTranslationAtOffset(flowThread->blockOffsetInEnclosingFragmentationContext()); |
| |
| // Translation that would map points in the coordinate space of the outermost flow thread to |
| // visual points in the first column in this fragmentainer group. |
| enclosingTranslation = enclosingFlowThread->flowThreadTranslationAtOffset(blockOffsetInEnclosingFragmentationContext()); |
| |
| // What we ultimately return from this method is a translation that maps points in the |
| // coordinate space of our flow thread to a visual point in a certain column in this |
| // fragmentainer group. We had to go all the way up to the outermost flow thread, since this |
| // fragmentainer group may be in a different outer column than the first outer column that |
| // this multicol container lives in. It's the visual distance between the first |
| // fragmentainer group and this fragmentainer group that we need to add to the translation. |
| enclosingTranslation -= enclosingTranslationOrigin; |
| } |
| |
| return enclosingTranslation + translationRelativeToGroup + offsetFromColumnSet() + m_columnSet.topLeftLocationOffset() - flowThread->topLeftLocationOffset(); |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::columnLogicalTopForOffset(LayoutUnit offsetInFlowThread) const |
| { |
| unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread, AssumeNewColumns); |
| return logicalTopInFlowThreadAt(columnIndex); |
| } |
| |
| LayoutPoint MultiColumnFragmentainerGroup::visualPointToFlowThreadPoint(const LayoutPoint& visualPoint) const |
| { |
| unsigned columnIndex = columnIndexAtVisualPoint(visualPoint); |
| LayoutRect columnRect = columnRectAt(columnIndex); |
| LayoutPoint localPoint(visualPoint); |
| localPoint.moveBy(-columnRect.location()); |
| // Before converting to a flow thread position, if the block direction coordinate is outside the |
| // column, snap to the bounds of the column, and reset the inline direction coordinate to the |
| // start position in the column. The effect of this is that if the block position is before the |
| // column rectangle, we'll get to the beginning of this column, while if the block position is |
| // after the column rectangle, we'll get to the beginning of the next column. |
| if (!m_columnSet.isHorizontalWritingMode()) { |
| LayoutUnit columnStart = m_columnSet.style()->isLeftToRightDirection() ? LayoutUnit() : columnRect.height(); |
| if (localPoint.x() < 0) |
| localPoint = LayoutPoint(LayoutUnit(), columnStart); |
| else if (localPoint.x() > logicalHeight()) |
| localPoint = LayoutPoint(logicalHeight(), columnStart); |
| return LayoutPoint(localPoint.x() + logicalTopInFlowThreadAt(columnIndex), localPoint.y()); |
| } |
| LayoutUnit columnStart = m_columnSet.style()->isLeftToRightDirection() ? LayoutUnit() : columnRect.width(); |
| if (localPoint.y() < 0) |
| localPoint = LayoutPoint(columnStart, LayoutUnit()); |
| else if (localPoint.y() > logicalHeight()) |
| localPoint = LayoutPoint(columnStart, logicalHeight()); |
| return LayoutPoint(localPoint.x(), localPoint.y() + logicalTopInFlowThreadAt(columnIndex)); |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::fragmentsBoundingBox(const LayoutRect& boundingBoxInFlowThread) const |
| { |
| // Find the start and end column intersected by the bounding box. |
| LayoutRect flippedBoundingBoxInFlowThread(boundingBoxInFlowThread); |
| LayoutFlowThread* flowThread = m_columnSet.flowThread(); |
| flowThread->flipForWritingMode(flippedBoundingBoxInFlowThread); |
| bool isHorizontalWritingMode = m_columnSet.isHorizontalWritingMode(); |
| LayoutUnit boundingBoxLogicalTop = isHorizontalWritingMode ? flippedBoundingBoxInFlowThread.y() : flippedBoundingBoxInFlowThread.x(); |
| LayoutUnit boundingBoxLogicalBottom = isHorizontalWritingMode ? flippedBoundingBoxInFlowThread.maxY() : flippedBoundingBoxInFlowThread.maxX(); |
| if (boundingBoxLogicalBottom <= logicalTopInFlowThread() || boundingBoxLogicalTop >= logicalBottomInFlowThread()) |
| return LayoutRect(); // The bounding box doesn't intersect this fragmentainer group. |
| unsigned startColumn; |
| unsigned endColumn; |
| columnIntervalForBlockRangeInFlowThread(boundingBoxLogicalTop, boundingBoxLogicalBottom, startColumn, endColumn); |
| |
| LayoutRect startColumnFlowThreadOverflowPortion = flowThreadPortionOverflowRectAt(startColumn); |
| flowThread->flipForWritingMode(startColumnFlowThreadOverflowPortion); |
| LayoutRect startColumnRect(boundingBoxInFlowThread); |
| startColumnRect.intersect(startColumnFlowThreadOverflowPortion); |
| startColumnRect.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(startColumn))); |
| if (startColumn == endColumn) |
| return startColumnRect; // It all takes place in one column. We're done. |
| |
| LayoutRect endColumnFlowThreadOverflowPortion = flowThreadPortionOverflowRectAt(endColumn); |
| flowThread->flipForWritingMode(endColumnFlowThreadOverflowPortion); |
| LayoutRect endColumnRect(boundingBoxInFlowThread); |
| endColumnRect.intersect(endColumnFlowThreadOverflowPortion); |
| endColumnRect.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(endColumn))); |
| return unionRect(startColumnRect, endColumnRect); |
| } |
| |
| void MultiColumnFragmentainerGroup::collectLayerFragments(PaintLayerFragments& 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, but otherwise completely |
| // physical in terms of writing mode. |
| |
| LayoutMultiColumnFlowThread* 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 layoutObject, 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.flowThreadPortionOverflowRect()); |
| 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()); |
| |
| // Figure out the start and end columns for the layer and only check within that range so that |
| // we don't walk the entire column row. |
| unsigned startColumn; |
| unsigned endColumn; |
| columnIntervalForBlockRangeInFlowThread(layerLogicalTop, layerLogicalBottom, startColumn, endColumn); |
| |
| // Now intersect with the columns actually occupied by the dirty rect, to narrow it down even further. |
| unsigned firstColumnInDirtyRect, lastColumnInDirtyRect; |
| columnIntervalForVisualRect(dirtyRect, firstColumnInDirtyRect, lastColumnInDirtyRect); |
| if (firstColumnInDirtyRect > endColumn || lastColumnInDirtyRect < startColumn) |
| return; // The two column intervals are disjoint. There's nothing to collect. |
| if (startColumn < firstColumnInDirtyRect) |
| startColumn = firstColumnInDirtyRect; |
| if (endColumn > lastColumnInDirtyRect) |
| endColumn = lastColumnInDirtyRect; |
| ASSERT(endColumn >= startColumn); |
| |
| for (unsigned i = startColumn; i <= endColumn; i++) { |
| PaintLayerFragment fragment; |
| |
| // Set the physical translation offset. |
| fragment.paginationOffset = toLayoutPoint(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(i))); |
| |
| // Set the overflow clip rect that corresponds to the column. |
| fragment.paginationClip = flowThreadPortionOverflowRectAt(i); |
| // Flip it into more a physical (PaintLayer-style) rectangle. |
| flowThread->flipForWritingMode(fragment.paginationClip); |
| |
| 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 = (flowThreadPortionHeight / m_columnHeight).floor(); |
| // flowThreadPortionHeight may be saturated, so detect the remainder manually. |
| if (count * m_columnHeight < flowThreadPortionHeight) |
| count++; |
| 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()) { |
| LayoutBlockFlow* 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 |
| { |
| LayoutBlockFlow* multicolBlock = m_columnSet.multiColumnBlockFlow(); |
| const ComputedStyle& multicolStyle = multicolBlock->styleRef(); |
| LayoutMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(); |
| LayoutUnit availableHeight = flowThread->columnHeightAvailable(); |
| LayoutUnit maxColumnHeight = availableHeight ? availableHeight : LayoutUnit::max(); |
| if (!multicolStyle.logicalMaxHeight().isMaxSizeNone()) { |
| LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(MaxSize, multicolStyle.logicalMaxHeight(), -1); |
| if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight) |
| maxColumnHeight = logicalMaxHeight; |
| } |
| LayoutUnit maxHeight = heightAdjustedForRowOffset(maxColumnHeight); |
| if (FragmentationContext* enclosingFragmentationContext = flowThread->enclosingFragmentationContext()) { |
| if (enclosingFragmentationContext->isFragmentainerLogicalHeightKnown()) { |
| // We're nested inside another fragmentation context whose fragmentainer heights are |
| // known. This constrains the max height. |
| LayoutUnit remainingOuterLogicalHeight = enclosingFragmentationContext->remainingLogicalHeightAt(blockOffsetInEnclosingFragmentationContext()); |
| ASSERT(remainingOuterLogicalHeight > 0); |
| if (maxHeight > remainingOuterLogicalHeight) |
| maxHeight = remainingOuterLogicalHeight; |
| } |
| } |
| return maxHeight; |
| } |
| |
| void MultiColumnFragmentainerGroup::setAndConstrainColumnHeight(LayoutUnit newHeight) |
| { |
| m_columnHeight = newHeight; |
| if (m_columnHeight > m_maxColumnHeight) |
| m_columnHeight = m_maxColumnHeight; |
| } |
| |
| LayoutUnit MultiColumnFragmentainerGroup::rebalanceColumnHeightIfNeeded() const |
| { |
| 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_columnHeight >= m_maxColumnHeight) { |
| // We cannot stretch any further. We'll just have to live with the overflowing columns. This |
| // typically happens if the max column height is less than the height of the tallest piece |
| // of unbreakable content (e.g. lines). |
| return m_columnHeight; |
| } |
| |
| MinimumSpaceShortageFinder shortageFinder(*this); |
| |
| if (shortageFinder.forcedBreaksCount() + 1 >= 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. |
| LayoutUnit minSpaceShortage = shortageFinder.minimumSpaceShortage(); |
| |
| ASSERT(minSpaceShortage > 0); // We should never _shrink_ the height! |
| ASSERT(minSpaceShortage != LayoutUnit::max()); // If this happens, we probably have a bug. |
| if (minSpaceShortage == LayoutUnit::max()) |
| return m_columnHeight; // So bail out rather than looping infinitely. |
| |
| return m_columnHeight + 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(); |
| LayoutUnit portionOutsideFlowThread = logicalTopInFlowThread() + (columnIndex + 1) * columnLogicalHeight - logicalBottomInFlowThread(); |
| if (portionOutsideFlowThread > 0) { |
| // The last column may not be using all available space. |
| ASSERT(columnIndex + 1 == actualColumnCount()); |
| columnLogicalHeight -= portionOutsideFlowThread; |
| ASSERT(columnLogicalHeight >= 0); |
| } |
| |
| 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 * (m_columnHeight + columnGap); |
| } |
| |
| LayoutRect columnRect(columnLogicalLeft, columnLogicalTop, columnLogicalWidth, columnLogicalHeight); |
| if (!m_columnSet.isHorizontalWritingMode()) |
| return columnRect.transposedRect(); |
| return columnRect; |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionRectAt(unsigned columnIndex) const |
| { |
| LayoutUnit logicalTop = logicalTopInFlowThreadAt(columnIndex); |
| LayoutUnit logicalBottom = logicalTop + m_columnHeight; |
| if (logicalBottom > logicalBottomInFlowThread()) { |
| // The last column may not be using all available space. |
| ASSERT(columnIndex + 1 == actualColumnCount()); |
| logicalBottom = logicalBottomInFlowThread(); |
| ASSERT(logicalBottom >= logicalTop); |
| } |
| LayoutUnit portionLogicalHeight = logicalBottom - logicalTop; |
| if (m_columnSet.isHorizontalWritingMode()) |
| return LayoutRect(LayoutUnit(), logicalTop, m_columnSet.pageLogicalWidth(), portionLogicalHeight); |
| return LayoutRect(logicalTop, LayoutUnit(), portionLogicalHeight, m_columnSet.pageLogicalWidth()); |
| } |
| |
| LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionOverflowRectAt(unsigned columnIndex) 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. |
| bool isFirstColumnInRow = !columnIndex; |
| bool isLastColumnInRow = columnIndex == actualColumnCount() - 1; |
| bool isLTR = m_columnSet.style()->isLeftToRightDirection(); |
| bool isLeftmostColumn = isLTR ? isFirstColumnInRow : isLastColumnInRow; |
| bool isRightmostColumn = isLTR ? isLastColumnInRow : isFirstColumnInRow; |
| |
| LayoutRect portionRect = flowThreadPortionRectAt(columnIndex); |
| bool isFirstColumnInMulticolContainer = isFirstColumnInRow && this == &m_columnSet.firstFragmentainerGroup() && !m_columnSet.previousSiblingMultiColumnSet(); |
| bool isLastColumnInMulticolContainer = isLastColumnInRow && this == &m_columnSet.lastFragmentainerGroup() && !m_columnSet.nextSiblingMultiColumnSet(); |
| // 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, isFirstColumnInMulticolContainer, isLastColumnInMulticolContainer); |
| |
| // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column |
| // gaps. Also make sure that we avoid rounding errors. |
| LayoutUnit columnGap = m_columnSet.columnGap(); |
| 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) / m_columnHeight).floor(); |
| return 0; |
| } |
| |
| unsigned MultiColumnFragmentainerGroup::columnIndexAtVisualPoint(const LayoutPoint& visualPoint) const |
| { |
| bool isColumnProgressionInline = m_columnSet.multiColumnFlowThread()->progressionIsInline(); |
| bool isHorizontalWritingMode = m_columnSet.isHorizontalWritingMode(); |
| LayoutUnit columnLengthInColumnProgressionDirection = isColumnProgressionInline ? m_columnSet.pageLogicalWidth() : logicalHeight(); |
| LayoutUnit offsetInColumnProgressionDirection = isHorizontalWritingMode == isColumnProgressionInline ? visualPoint.x() : visualPoint.y(); |
| if (!m_columnSet.style()->isLeftToRightDirection() && isColumnProgressionInline) |
| offsetInColumnProgressionDirection = m_columnSet.logicalWidth() - offsetInColumnProgressionDirection; |
| LayoutUnit columnGap = m_columnSet.columnGap(); |
| if (columnLengthInColumnProgressionDirection + columnGap <= 0) |
| return 0; |
| // Column boundaries are in the middle of the column gap. |
| int index = (offsetInColumnProgressionDirection + columnGap / 2) / (columnLengthInColumnProgressionDirection + columnGap); |
| if (index < 0) |
| return 0; |
| return std::min(unsigned(index), actualColumnCount() - 1); |
| } |
| |
| void MultiColumnFragmentainerGroup::columnIntervalForBlockRangeInFlowThread(LayoutUnit logicalTopInFlowThread, LayoutUnit logicalBottomInFlowThread, unsigned& firstColumn, unsigned& lastColumn) const |
| { |
| ASSERT(logicalTopInFlowThread <= logicalBottomInFlowThread); |
| firstColumn = columnIndexAtOffset(logicalTopInFlowThread); |
| lastColumn = columnIndexAtOffset(logicalBottomInFlowThread); |
| // logicalBottomInFlowThread is an exclusive endpoint, so some additional adjustments may be necessary. |
| if (lastColumn > firstColumn && logicalTopInFlowThreadAt(lastColumn) == logicalBottomInFlowThread) |
| lastColumn--; |
| } |
| |
| void MultiColumnFragmentainerGroup::columnIntervalForVisualRect(const LayoutRect& rect, unsigned& firstColumn, unsigned& lastColumn) const |
| { |
| bool isColumnProgressionInline = m_columnSet.multiColumnFlowThread()->progressionIsInline(); |
| bool isFlippedColumnProgression = !m_columnSet.style()->isLeftToRightDirection() && isColumnProgressionInline; |
| if (m_columnSet.isHorizontalWritingMode() == isColumnProgressionInline) { |
| if (isFlippedColumnProgression) { |
| firstColumn = columnIndexAtVisualPoint(rect.maxXMinYCorner()); |
| lastColumn = columnIndexAtVisualPoint(rect.minXMinYCorner()); |
| } else { |
| firstColumn = columnIndexAtVisualPoint(rect.minXMinYCorner()); |
| lastColumn = columnIndexAtVisualPoint(rect.maxXMinYCorner()); |
| } |
| } else { |
| if (isFlippedColumnProgression) { |
| firstColumn = columnIndexAtVisualPoint(rect.minXMaxYCorner()); |
| lastColumn = columnIndexAtVisualPoint(rect.minXMinYCorner()); |
| } else { |
| firstColumn = columnIndexAtVisualPoint(rect.minXMinYCorner()); |
| lastColumn = columnIndexAtVisualPoint(rect.minXMaxYCorner()); |
| } |
| } |
| ASSERT(firstColumn <= lastColumn); |
| } |
| |
| MultiColumnFragmentainerGroupList::MultiColumnFragmentainerGroupList(LayoutMultiColumnSet& columnSet) |
| : m_columnSet(columnSet) |
| { |
| append(MultiColumnFragmentainerGroup(m_columnSet)); |
| } |
| |
| // An explicit empty destructor of MultiColumnFragmentainerGroupList should be in |
| // MultiColumnFragmentainerGroup.cpp, because if an implicit destructor is used, |
| // msvc 2015 tries to generate its destructor (because the class is dll-exported class) |
| // and causes a compile error because of lack of MultiColumnFragmentainerGroup::operator=. |
| // Since MultiColumnFragmentainerGroup is non-copyable, we cannot define the operator=. |
| MultiColumnFragmentainerGroupList::~MultiColumnFragmentainerGroupList() |
| { |
| } |
| |
| MultiColumnFragmentainerGroup& MultiColumnFragmentainerGroupList::addExtraGroup() |
| { |
| append(MultiColumnFragmentainerGroup(m_columnSet)); |
| return last(); |
| } |
| |
| void MultiColumnFragmentainerGroupList::deleteExtraGroups() |
| { |
| shrink(1); |
| } |
| |
| } // namespace blink |