| /* |
| * Copyright (C) 2012 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/layout/LayoutMultiColumnSet.h" |
| |
| #include "core/editing/PositionWithAffinity.h" |
| #include "core/layout/LayoutMultiColumnFlowThread.h" |
| #include "core/layout/MultiColumnFragmentainerGroup.h" |
| #include "core/paint/MultiColumnSetPainter.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| |
| namespace blink { |
| |
| LayoutMultiColumnSet::LayoutMultiColumnSet(LayoutFlowThread* flowThread) |
| : LayoutBlockFlow(nullptr), |
| m_fragmentainerGroups(*this), |
| m_flowThread(flowThread), |
| m_initialHeightCalculated(false) {} |
| |
| LayoutMultiColumnSet* LayoutMultiColumnSet::createAnonymous( |
| LayoutFlowThread& flowThread, |
| const ComputedStyle& parentStyle) { |
| Document& document = flowThread.document(); |
| LayoutMultiColumnSet* layoutObject = new LayoutMultiColumnSet(&flowThread); |
| layoutObject->setDocumentForAnonymous(&document); |
| layoutObject->setStyle(ComputedStyle::createAnonymousStyleWithDisplay( |
| parentStyle, EDisplay::Block)); |
| return layoutObject; |
| } |
| |
| unsigned LayoutMultiColumnSet::fragmentainerGroupIndexAtFlowThreadOffset( |
| LayoutUnit flowThreadOffset, |
| PageBoundaryRule rule) const { |
| ASSERT(m_fragmentainerGroups.size() > 0); |
| if (flowThreadOffset <= 0) |
| return 0; |
| // TODO(mstensho): Introduce an interval tree or similar to speed up this. |
| for (unsigned index = 0; index < m_fragmentainerGroups.size(); index++) { |
| const auto& row = m_fragmentainerGroups[index]; |
| if (rule == AssociateWithLatterPage) { |
| if (row.logicalTopInFlowThread() <= flowThreadOffset && |
| row.logicalBottomInFlowThread() > flowThreadOffset) |
| return index; |
| } else if (row.logicalTopInFlowThread() < flowThreadOffset && |
| row.logicalBottomInFlowThread() >= flowThreadOffset) { |
| return index; |
| } |
| } |
| return m_fragmentainerGroups.size() - 1; |
| } |
| |
| const MultiColumnFragmentainerGroup& |
| LayoutMultiColumnSet::fragmentainerGroupAtVisualPoint( |
| const LayoutPoint& visualPoint) const { |
| ASSERT(m_fragmentainerGroups.size() > 0); |
| LayoutUnit blockOffset = |
| isHorizontalWritingMode() ? visualPoint.y() : visualPoint.x(); |
| for (unsigned index = 0; index < m_fragmentainerGroups.size(); index++) { |
| const auto& row = m_fragmentainerGroups[index]; |
| if (row.logicalTop() + row.logicalHeight() > blockOffset) |
| return row; |
| } |
| return m_fragmentainerGroups.last(); |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::pageLogicalHeightForOffset( |
| LayoutUnit offsetInFlowThread) const { |
| const MultiColumnFragmentainerGroup& lastRow = lastFragmentainerGroup(); |
| if (!lastRow.logicalHeight()) { |
| // In the first layout pass of an auto-height multicol container, height |
| // isn't set. No need to perform the series of complicated dance steps below |
| // to figure out that we should simply return 0. Bail now. |
| ASSERT(m_fragmentainerGroups.size() == 1); |
| return LayoutUnit(); |
| } |
| if (offsetInFlowThread >= |
| lastRow.logicalTopInFlowThread() + fragmentainerGroupCapacity(lastRow)) { |
| // The offset is outside the bounds of the fragmentainer groups that we have |
| // established at this point. If we're nested inside another fragmentation |
| // context, we need to calculate the height on our own. |
| const LayoutMultiColumnFlowThread* flowThread = multiColumnFlowThread(); |
| if (FragmentationContext* enclosingFragmentationContext = |
| flowThread->enclosingFragmentationContext()) { |
| // We'd ideally like to translate |offsetInFlowThread| to an offset in the |
| // coordinate space of the enclosing fragmentation context here, but |
| // that's hard, since the offset is out of bounds. So just use the bottom |
| // we have found so far. |
| LayoutUnit enclosingContextBottom = |
| lastRow.blockOffsetInEnclosingFragmentationContext() + |
| lastRow.logicalHeight(); |
| LayoutUnit enclosingFragmentainerHeight = |
| enclosingFragmentationContext->fragmentainerLogicalHeightAt( |
| enclosingContextBottom); |
| // Constrain against specified height / max-height. |
| LayoutUnit currentMulticolHeight = logicalTopFromMulticolContentEdge() + |
| lastRow.logicalTop() + |
| lastRow.logicalHeight(); |
| LayoutUnit multicolHeightWithExtraRow = |
| currentMulticolHeight + enclosingFragmentainerHeight; |
| multicolHeightWithExtraRow = std::min( |
| multicolHeightWithExtraRow, flowThread->maxColumnLogicalHeight()); |
| return std::max(LayoutUnit(1), |
| multicolHeightWithExtraRow - currentMulticolHeight); |
| } |
| } |
| return fragmentainerGroupAtFlowThreadOffset(offsetInFlowThread, |
| AssociateWithLatterPage) |
| .logicalHeight(); |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::pageRemainingLogicalHeightForOffset( |
| LayoutUnit offsetInFlowThread, |
| PageBoundaryRule pageBoundaryRule) const { |
| const MultiColumnFragmentainerGroup& row = |
| fragmentainerGroupAtFlowThreadOffset(offsetInFlowThread, |
| pageBoundaryRule); |
| LayoutUnit pageLogicalHeight = row.logicalHeight(); |
| // It's not allowed to call this method if the height is unknown. |
| DCHECK(pageLogicalHeight); |
| LayoutUnit pageLogicalBottom = |
| row.columnLogicalTopForOffset(offsetInFlowThread) + pageLogicalHeight; |
| LayoutUnit remainingLogicalHeight = pageLogicalBottom - offsetInFlowThread; |
| |
| if (pageBoundaryRule == AssociateWithFormerPage) { |
| // An offset exactly at a column boundary will act as being part of the |
| // former column in question (i.e. no remaining space), rather than being |
| // part of the latter (i.e. one whole column length of remaining space). |
| remainingLogicalHeight = intMod(remainingLogicalHeight, pageLogicalHeight); |
| } else if (!remainingLogicalHeight) { |
| // When pageBoundaryRule is AssociateWithLatterPage, we should never return |
| // 0, because if there's no space left, it means that we should be at a |
| // column boundary, in which case we should return the amount of space |
| // remaining in the *next* column. But this is not true if the offset is |
| // "infinite" (saturated), so allow this to happen in that case. |
| ASSERT(offsetInFlowThread.mightBeSaturated()); |
| remainingLogicalHeight = pageLogicalHeight; |
| } |
| return remainingLogicalHeight; |
| } |
| |
| bool LayoutMultiColumnSet::isPageLogicalHeightKnown() const { |
| return firstFragmentainerGroup().logicalHeight(); |
| } |
| |
| bool LayoutMultiColumnSet::newFragmentainerGroupsAllowed() const { |
| if (!isPageLogicalHeightKnown()) { |
| // If we have no clue about the height of the multicol container, bail. This |
| // situation occurs initially when an auto-height multicol container is |
| // nested inside another auto-height multicol container. We need at least an |
| // estimated height of the outer multicol container before we can check what |
| // an inner fragmentainer group has room for. |
| // Its height is indefinite for now. |
| return false; |
| } |
| if (isInitialHeightCalculated()) { |
| // We only insert additional fragmentainer groups in the initial layout |
| // pass. We only want to balance columns in the last fragmentainer group (if |
| // we need to balance at all), so we want that last fragmentainer group to |
| // be the same one in all layout passes that follow. |
| return false; |
| } |
| return true; |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::nextLogicalTopForUnbreakableContent( |
| LayoutUnit flowThreadOffset, |
| LayoutUnit contentLogicalHeight) const { |
| ASSERT(flowThreadOffset.mightBeSaturated() || |
| pageLogicalTopForOffset(flowThreadOffset) == flowThreadOffset); |
| FragmentationContext* enclosingFragmentationContext = |
| multiColumnFlowThread()->enclosingFragmentationContext(); |
| if (!enclosingFragmentationContext) { |
| // If there's no enclosing fragmentation context, there'll ever be only one |
| // row, and all columns there will have the same height. |
| return flowThreadOffset; |
| } |
| |
| // Assert the problematic situation. If we have no problem with the column |
| // height, why are we even here? |
| ASSERT(pageLogicalHeightForOffset(flowThreadOffset) < contentLogicalHeight); |
| |
| // There's a likelihood for subsequent rows to be taller than the first one. |
| // TODO(mstensho): if we're doubly nested (e.g. multicol in multicol in |
| // multicol), we need to look beyond the first row here. |
| const MultiColumnFragmentainerGroup& firstRow = firstFragmentainerGroup(); |
| LayoutUnit firstRowLogicalBottomInFlowThread = |
| firstRow.logicalTopInFlowThread() + fragmentainerGroupCapacity(firstRow); |
| if (flowThreadOffset >= firstRowLogicalBottomInFlowThread) |
| return flowThreadOffset; // We're not in the first row. Give up. |
| LayoutUnit newLogicalHeight = |
| enclosingFragmentationContext->fragmentainerLogicalHeightAt( |
| firstRow.blockOffsetInEnclosingFragmentationContext() + |
| firstRow.logicalHeight()); |
| if (contentLogicalHeight > newLogicalHeight) { |
| // The next outer column or page doesn't have enough space either. Give up |
| // and stay where we are. |
| return flowThreadOffset; |
| } |
| return firstRowLogicalBottomInFlowThread; |
| } |
| |
| LayoutMultiColumnSet* LayoutMultiColumnSet::nextSiblingMultiColumnSet() const { |
| for (LayoutObject* sibling = nextSibling(); sibling; |
| sibling = sibling->nextSibling()) { |
| if (sibling->isLayoutMultiColumnSet()) |
| return toLayoutMultiColumnSet(sibling); |
| } |
| return nullptr; |
| } |
| |
| LayoutMultiColumnSet* LayoutMultiColumnSet::previousSiblingMultiColumnSet() |
| const { |
| for (LayoutObject* sibling = previousSibling(); sibling; |
| sibling = sibling->previousSibling()) { |
| if (sibling->isLayoutMultiColumnSet()) |
| return toLayoutMultiColumnSet(sibling); |
| } |
| return nullptr; |
| } |
| |
| bool LayoutMultiColumnSet::hasFragmentainerGroupForColumnAt( |
| LayoutUnit offsetInFlowThread, |
| PageBoundaryRule pageBoundaryRule) const { |
| const MultiColumnFragmentainerGroup& lastRow = lastFragmentainerGroup(); |
| LayoutUnit maxLogicalBottomInFlowThread = |
| lastRow.logicalTopInFlowThread() + fragmentainerGroupCapacity(lastRow); |
| if (pageBoundaryRule == AssociateWithFormerPage) |
| return offsetInFlowThread <= maxLogicalBottomInFlowThread; |
| return offsetInFlowThread < maxLogicalBottomInFlowThread; |
| } |
| |
| MultiColumnFragmentainerGroup& |
| LayoutMultiColumnSet::appendNewFragmentainerGroup() { |
| MultiColumnFragmentainerGroup newGroup(*this); |
| { // Extra scope here for previousGroup; it's potentially invalid once we |
| // modify the m_fragmentainerGroups Vector. |
| MultiColumnFragmentainerGroup& previousGroup = m_fragmentainerGroups.last(); |
| |
| // This is the flow thread block offset where |previousGroup| ends and |
| // |newGroup| takes over. |
| LayoutUnit blockOffsetInFlowThread = |
| previousGroup.logicalTopInFlowThread() + |
| fragmentainerGroupCapacity(previousGroup); |
| previousGroup.setLogicalBottomInFlowThread(blockOffsetInFlowThread); |
| newGroup.setLogicalTopInFlowThread(blockOffsetInFlowThread); |
| newGroup.setLogicalTop(previousGroup.logicalTop() + |
| previousGroup.logicalHeight()); |
| newGroup.resetColumnHeight(); |
| } |
| m_fragmentainerGroups.append(newGroup); |
| return m_fragmentainerGroups.last(); |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::logicalTopFromMulticolContentEdge() const { |
| // We subtract the position of the first column set or spanner placeholder, |
| // rather than the "before" border+padding of the multicol container. This |
| // distinction doesn't matter after layout, but during layout it does: |
| // The flow thread (i.e. the multicol contents) is laid out before the column |
| // sets and spanner placeholders, which means that compesating for a top |
| // border+padding that hasn't yet been baked into the offset will produce the |
| // wrong results in the first layout pass, and we'd end up performing a wasted |
| // layout pass in many cases. |
| const LayoutBox& firstColumnBox = |
| *multiColumnFlowThread()->firstMultiColumnBox(); |
| // The top margin edge of the first column set or spanner placeholder is flush |
| // with the top content edge of the multicol container. The margin here never |
| // collapses with other margins, so we can just subtract it. Column sets never |
| // have margins, but spanner placeholders may. |
| LayoutUnit firstColumnBoxMarginEdge = |
| firstColumnBox.logicalTop() - |
| multiColumnBlockFlow()->marginBeforeForChild(firstColumnBox); |
| return logicalTop() - firstColumnBoxMarginEdge; |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::logicalTopInFlowThread() const { |
| return firstFragmentainerGroup().logicalTopInFlowThread(); |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::logicalBottomInFlowThread() const { |
| return lastFragmentainerGroup().logicalBottomInFlowThread(); |
| } |
| |
| LayoutRect LayoutMultiColumnSet::flowThreadPortionOverflowRect() const { |
| return overflowRectForFlowThreadPortion(flowThreadPortionRect(), |
| !previousSiblingMultiColumnSet(), |
| !nextSiblingMultiColumnSet()); |
| } |
| |
| LayoutRect LayoutMultiColumnSet::overflowRectForFlowThreadPortion( |
| const LayoutRect& flowThreadPortionRect, |
| bool isFirstPortion, |
| bool isLastPortion) const { |
| if (hasOverflowClip()) |
| return flowThreadPortionRect; |
| |
| LayoutRect flowThreadOverflow = m_flowThread->visualOverflowRect(); |
| |
| // Only clip along the flow thread axis. |
| LayoutRect clipRect; |
| if (m_flowThread->isHorizontalWritingMode()) { |
| LayoutUnit minY = |
| isFirstPortion ? flowThreadOverflow.y() : flowThreadPortionRect.y(); |
| LayoutUnit maxY = isLastPortion ? std::max(flowThreadPortionRect.maxY(), |
| flowThreadOverflow.maxY()) |
| : flowThreadPortionRect.maxY(); |
| LayoutUnit minX = |
| std::min(flowThreadPortionRect.x(), flowThreadOverflow.x()); |
| LayoutUnit maxX = |
| std::max(flowThreadPortionRect.maxX(), flowThreadOverflow.maxX()); |
| clipRect = LayoutRect(minX, minY, maxX - minX, maxY - minY); |
| } else { |
| LayoutUnit minX = |
| isFirstPortion ? flowThreadOverflow.x() : flowThreadPortionRect.x(); |
| LayoutUnit maxX = isLastPortion ? std::max(flowThreadPortionRect.maxX(), |
| flowThreadOverflow.maxX()) |
| : flowThreadPortionRect.maxX(); |
| LayoutUnit minY = |
| std::min(flowThreadPortionRect.y(), (flowThreadOverflow.y())); |
| LayoutUnit maxY = |
| std::max(flowThreadPortionRect.y(), (flowThreadOverflow.maxY())); |
| clipRect = LayoutRect(minX, minY, maxX - minX, maxY - minY); |
| } |
| |
| return clipRect; |
| } |
| |
| bool LayoutMultiColumnSet::heightIsAuto() const { |
| LayoutMultiColumnFlowThread* flowThread = multiColumnFlowThread(); |
| if (!flowThread->isLayoutPagedFlowThread()) { |
| // If support for the column-fill property isn't enabled, we want to behave |
| // as if column-fill were auto, so that multicol containers with specified |
| // height don't get their columns balanced (auto-height multicol containers |
| // will still get their columns balanced, even if column-fill isn't |
| // 'balance' - in accordance with the spec). |
| // Pretending that column-fill is auto also matches the old multicol |
| // implementation, which has no support for this property. |
| if (multiColumnBlockFlow()->style()->getColumnFill() == ColumnFillBalance) |
| return true; |
| if (LayoutBox* next = nextSiblingBox()) { |
| if (next->isLayoutMultiColumnSpannerPlaceholder()) { |
| // If we're followed by a spanner, we need to balance. |
| return true; |
| } |
| } |
| } |
| return !flowThread->columnHeightAvailable(); |
| } |
| |
| LayoutSize LayoutMultiColumnSet::flowThreadTranslationAtOffset( |
| LayoutUnit blockOffset, |
| PageBoundaryRule rule, |
| CoordinateSpaceConversion mode) const { |
| return fragmentainerGroupAtFlowThreadOffset(blockOffset, rule) |
| .flowThreadTranslationAtOffset(blockOffset, rule, mode); |
| } |
| |
| LayoutPoint LayoutMultiColumnSet::visualPointToFlowThreadPoint( |
| const LayoutPoint& visualPoint) const { |
| const MultiColumnFragmentainerGroup& row = |
| fragmentainerGroupAtVisualPoint(visualPoint); |
| return row.visualPointToFlowThreadPoint(visualPoint - |
| row.offsetFromColumnSet()); |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::pageLogicalTopForOffset( |
| LayoutUnit offset) const { |
| return fragmentainerGroupAtFlowThreadOffset(offset, AssociateWithLatterPage) |
| .columnLogicalTopForOffset(offset); |
| } |
| |
| bool LayoutMultiColumnSet::recalculateColumnHeight() { |
| if (m_oldLogicalTop != logicalTop() && |
| multiColumnFlowThread()->enclosingFragmentationContext()) { |
| // Preceding spanners or column sets have been moved or resized. This means |
| // that the fragmentainer groups that we have inserted need to be |
| // re-inserted. Restart column balancing. |
| resetColumnHeight(); |
| return true; |
| } |
| |
| bool changed = false; |
| for (auto& group : m_fragmentainerGroups) |
| changed = group.recalculateColumnHeight(*this) || changed; |
| m_initialHeightCalculated = true; |
| return changed; |
| } |
| |
| void LayoutMultiColumnSet::resetColumnHeight() { |
| m_fragmentainerGroups.deleteExtraGroups(); |
| m_fragmentainerGroups.first().resetColumnHeight(); |
| m_tallestUnbreakableLogicalHeight = LayoutUnit(); |
| m_initialHeightCalculated = false; |
| } |
| |
| void LayoutMultiColumnSet::beginFlow(LayoutUnit offsetInFlowThread) { |
| // At this point layout is exactly at the beginning of this set. Store block |
| // offset from flow thread start. |
| m_fragmentainerGroups.first().setLogicalTopInFlowThread(offsetInFlowThread); |
| } |
| |
| void LayoutMultiColumnSet::endFlow(LayoutUnit offsetInFlowThread) { |
| // At this point layout is exactly at the end of this set. Store block offset |
| // from flow thread start. This set is now considered "flowed", although we |
| // may have to revisit it later (with beginFlow()), e.g. if a subtree in the |
| // flow thread has to be laid out over again because the initial margin |
| // collapsing estimates were wrong. |
| m_fragmentainerGroups.last().setLogicalBottomInFlowThread(offsetInFlowThread); |
| } |
| |
| void LayoutMultiColumnSet::styleDidChange(StyleDifference diff, |
| const ComputedStyle* oldStyle) { |
| LayoutBlockFlow::styleDidChange(diff, oldStyle); |
| |
| // column-rule is specified on the parent (the multicol container) of this |
| // object, but it's the column sets that are in charge of painting them. |
| // A column rule is pretty much like any other box decoration, like borders. |
| // We need to say that we have box decorations here, so that the columnn set |
| // is invalidated when it gets laid out. We cannot check here whether the |
| // multicol container actually has a visible column rule or not, because we |
| // may not have been inserted into the tree yet. Painting a column set is |
| // cheap anyway, because the only thing it can paint is the column rule, while |
| // actual multicol content is handled by the flow thread. |
| setHasBoxDecorationBackground(true); |
| } |
| |
| void LayoutMultiColumnSet::layout() { |
| if (recalculateColumnHeight()) |
| multiColumnFlowThread()->setColumnHeightsChanged(); |
| LayoutBlockFlow::layout(); |
| } |
| |
| void LayoutMultiColumnSet::computeIntrinsicLogicalWidths( |
| LayoutUnit& minLogicalWidth, |
| LayoutUnit& maxLogicalWidth) const { |
| minLogicalWidth = m_flowThread->minPreferredLogicalWidth(); |
| maxLogicalWidth = m_flowThread->maxPreferredLogicalWidth(); |
| } |
| |
| void LayoutMultiColumnSet::computeLogicalHeight( |
| LayoutUnit, |
| LayoutUnit logicalTop, |
| LogicalExtentComputedValues& computedValues) const { |
| LayoutUnit logicalHeight; |
| for (const auto& group : m_fragmentainerGroups) |
| logicalHeight += group.logicalHeight(); |
| computedValues.m_extent = logicalHeight; |
| computedValues.m_position = logicalTop; |
| } |
| |
| PositionWithAffinity LayoutMultiColumnSet::positionForPoint( |
| const LayoutPoint& point) { |
| // Convert the visual point to a flow thread point. |
| const MultiColumnFragmentainerGroup& row = |
| fragmentainerGroupAtVisualPoint(point); |
| LayoutPoint flowThreadPoint = row.visualPointToFlowThreadPoint( |
| point + row.offsetFromColumnSet(), |
| MultiColumnFragmentainerGroup::SnapToColumn); |
| // Then drill into the flow thread, where we'll find the actual content. |
| return flowThread()->positionForPoint(flowThreadPoint); |
| } |
| |
| LayoutUnit LayoutMultiColumnSet::columnGap() const { |
| LayoutBlockFlow* parentBlock = multiColumnBlockFlow(); |
| |
| if (parentBlock->style()->hasNormalColumnGap()) { |
| // "1em" is recommended as the normal gap setting. Matches <p> margins. |
| return LayoutUnit( |
| parentBlock->style()->getFontDescription().computedPixelSize()); |
| } |
| return LayoutUnit(parentBlock->style()->columnGap()); |
| } |
| |
| unsigned LayoutMultiColumnSet::actualColumnCount() const { |
| // FIXME: remove this method. It's a meaningless question to ask the set "how |
| // many columns do you actually have?", since that may vary for each row. |
| return firstFragmentainerGroup().actualColumnCount(); |
| } |
| |
| void LayoutMultiColumnSet::paintObject(const PaintInfo& paintInfo, |
| const LayoutPoint& paintOffset) const { |
| MultiColumnSetPainter(*this).paintObject(paintInfo, paintOffset); |
| } |
| |
| LayoutRect LayoutMultiColumnSet::fragmentsBoundingBox( |
| const LayoutRect& boundingBoxInFlowThread) const { |
| LayoutRect result; |
| for (const auto& group : m_fragmentainerGroups) |
| result.unite(group.fragmentsBoundingBox(boundingBoxInFlowThread)); |
| return result; |
| } |
| |
| void LayoutMultiColumnSet::addOverflowFromChildren() { |
| LayoutRect overflowRect; |
| for (const auto& group : m_fragmentainerGroups) { |
| LayoutRect rect = group.calculateOverflow(); |
| rect.move(group.offsetFromColumnSet()); |
| overflowRect.unite(rect); |
| } |
| addLayoutOverflow(overflowRect); |
| addContentsVisualOverflow(overflowRect); |
| } |
| |
| void LayoutMultiColumnSet::insertedIntoTree() { |
| LayoutBlockFlow::insertedIntoTree(); |
| attachToFlowThread(); |
| } |
| |
| void LayoutMultiColumnSet::willBeRemovedFromTree() { |
| LayoutBlockFlow::willBeRemovedFromTree(); |
| detachFromFlowThread(); |
| } |
| |
| void LayoutMultiColumnSet::attachToFlowThread() { |
| if (documentBeingDestroyed()) |
| return; |
| |
| if (!m_flowThread) |
| return; |
| |
| m_flowThread->addColumnSetToThread(this); |
| } |
| |
| void LayoutMultiColumnSet::detachFromFlowThread() { |
| if (m_flowThread) { |
| m_flowThread->removeColumnSetFromThread(this); |
| m_flowThread = 0; |
| } |
| } |
| |
| LayoutRect LayoutMultiColumnSet::flowThreadPortionRect() const { |
| LayoutRect portionRect(LayoutUnit(), logicalTopInFlowThread(), |
| pageLogicalWidth(), logicalHeightInFlowThread()); |
| if (!isHorizontalWritingMode()) |
| return portionRect.transposedRect(); |
| return portionRect; |
| } |
| |
| bool LayoutMultiColumnSet::computeColumnRuleBounds( |
| const LayoutPoint& paintOffset, |
| Vector<LayoutRect>& columnRuleBounds) const { |
| if (flowThread()->isLayoutPagedFlowThread()) |
| return false; |
| |
| // Reference: https://www.w3.org/TR/css3-multicol/#column-gaps-and-rules |
| const ComputedStyle& blockStyle = multiColumnBlockFlow()->styleRef(); |
| bool ruleTransparent = blockStyle.columnRuleIsTransparent(); |
| EBorderStyle ruleStyle = blockStyle.columnRuleStyle(); |
| LayoutUnit ruleThickness(blockStyle.columnRuleWidth()); |
| LayoutUnit colGap = columnGap(); |
| bool renderRule = ruleStyle > BorderStyleHidden && !ruleTransparent; |
| if (!renderRule) |
| return false; |
| |
| unsigned colCount = actualColumnCount(); |
| if (colCount <= 1) |
| return false; |
| |
| bool leftToRight = style()->isLeftToRightDirection(); |
| LayoutUnit currLogicalLeftOffset = |
| leftToRight ? LayoutUnit() : contentLogicalWidth(); |
| LayoutUnit ruleAdd = borderAndPaddingLogicalLeft(); |
| LayoutUnit ruleLogicalLeft = |
| leftToRight ? LayoutUnit() : contentLogicalWidth(); |
| LayoutUnit inlineDirectionSize = pageLogicalWidth(); |
| |
| for (unsigned i = 0; i < colCount; i++) { |
| // Move to the next position. |
| if (leftToRight) { |
| ruleLogicalLeft += inlineDirectionSize + colGap / 2; |
| currLogicalLeftOffset += inlineDirectionSize + colGap; |
| } else { |
| ruleLogicalLeft -= (inlineDirectionSize + colGap / 2); |
| currLogicalLeftOffset -= (inlineDirectionSize + colGap); |
| } |
| |
| // Now compute the final bounds. |
| if (i < colCount - 1) { |
| LayoutUnit ruleLeft, ruleRight, ruleTop, ruleBottom; |
| if (isHorizontalWritingMode()) { |
| ruleLeft = |
| paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd; |
| ruleRight = ruleLeft + ruleThickness; |
| ruleTop = paintOffset.y() + borderTop() + paddingTop(); |
| ruleBottom = ruleTop + contentHeight(); |
| } else { |
| ruleLeft = paintOffset.x() + borderLeft() + paddingLeft(); |
| ruleRight = ruleLeft + contentWidth(); |
| ruleTop = |
| paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd; |
| ruleBottom = ruleTop + ruleThickness; |
| } |
| |
| columnRuleBounds.push_back(LayoutRect( |
| ruleLeft, ruleTop, ruleRight - ruleLeft, ruleBottom - ruleTop)); |
| } |
| |
| ruleLogicalLeft = currLogicalLeftOffset; |
| } |
| return true; |
| } |
| |
| LayoutRect LayoutMultiColumnSet::localVisualRect() const { |
| LayoutRect blockFlowBounds = LayoutBlockFlow::localVisualRect(); |
| |
| // Now add in column rule bounds, if present. |
| Vector<LayoutRect> columnRuleBounds; |
| if (computeColumnRuleBounds(LayoutPoint(), columnRuleBounds)) { |
| for (auto& bound : columnRuleBounds) |
| blockFlowBounds.unite(bound); |
| } |
| return blockFlowBounds; |
| } |
| |
| } // namespace blink |