LayoutUnit LayoutMultiColumnSet::nextLogicalTopForUnbreakableContent(LayoutUnit flowThreadOffset, LayoutUnit contentLogicalHeight) const { ASSERT(pageLogicalTopForOffset(flowThreadOffset) == flowThreadOffset); LayoutMultiColumnFlowThread* enclosingFlowThread = multiColumnFlowThread()->enclosingFlowThread(); if (!enclosingFlowThread) { // 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() + firstRow.logicalHeight() * usedColumnCount(); if (flowThreadOffset >= firstRowLogicalBottomInFlowThread) return flowThreadOffset; // We're not in the first row. Give up. LayoutUnit newLogicalHeight = enclosingFlowThread->pageLogicalHeightForOffset(firstRowLogicalBottomInFlowThread); 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; }
bool RenderMultiColumnSet::recalculateColumnHeight(BalancedHeightCalculation calculationMode) { ASSERT(multiColumnFlowThread()->requiresBalancing()); LayoutUnit oldColumnHeight = m_columnHeight; 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. // 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. }
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).logicalHeight(); }
void LayoutMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded() { ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this); // FIXME: this may result in the need for creating additional rows, since there may not be // enough space remaining in the currently last row. m_fragmentainerGroups.last().expandToEncompassFlowThreadOverflow(); }
void RenderMultiColumnSet::addForcedBreak(LayoutUnit offsetFromFirstPage) { if (!multiColumnFlowThread()->requiresBalancing()) return; if (!m_contentRuns.isEmpty() && offsetFromFirstPage <= 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_computedColumnCount) m_contentRuns.append(ContentRun(offsetFromFirstPage)); }
LayoutUnit RenderMultiColumnSet::calculateMaxColumnHeight() const { RenderBlockFlow* multicolBlock = multiColumnBlockFlow(); RenderStyle* multicolStyle = multicolBlock->style(); LayoutUnit availableHeight = multiColumnFlowThread()->columnHeightAvailable(); LayoutUnit maxColumnHeight = availableHeight ? availableHeight : RenderFlowThread::maxLogicalHeight(); if (!multicolStyle->logicalMaxHeight().isUndefined()) { LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalMaxHeight(), -1); if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight) maxColumnHeight = logicalMaxHeight; } return heightAdjustedForSetOffset(maxColumnHeight); }
void RenderMultiColumnSet::resetColumnHeight() { // Nuke previously stored minimum column height. Contents may have changed for all we know. m_minimumColumnHeight = 0; m_maxColumnHeight = calculateMaxColumnHeight(); LayoutUnit oldColumnHeight = pageLogicalHeight(); if (multiColumnFlowThread()->requiresBalancing()) m_columnHeight = 0; else setAndConstrainColumnHeight(heightAdjustedForSetOffset(multiColumnFlowThread()->columnHeightAvailable())); if (pageLogicalHeight() != oldColumnHeight) 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()); }
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; }
bool LayoutMultiColumnSet::recalculateColumnHeight() { if (m_oldLogicalTop != logicalTop() && multiColumnFlowThread()->enclosingFlowThread()) { // 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() || changed; m_initialHeightCalculated = true; return changed; }
void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded() { ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this); LayoutRect rect(flowThreadPortionRect()); // 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. bool isHorizontal = flowThread()->isHorizontalWritingMode(); LayoutUnit logicalTopOffset = isHorizontal ? rect.y() : rect.x(); LayoutRect layoutRect = flowThread()->layoutOverflowRect(); LayoutUnit logicalHeightWithOverflow = (isHorizontal ? layoutRect.maxY() : layoutRect.maxX()) - logicalTopOffset; setFlowThreadPortionRect(LayoutRect(rect.x(), rect.y(), isHorizontal ? rect.width() : logicalHeightWithOverflow, isHorizontal ? logicalHeightWithOverflow : rect.height())); }
void LayoutMultiColumnSet::recordSpaceShortage(LayoutUnit offsetInFlowThread, LayoutUnit spaceShortage) { MultiColumnFragmentainerGroup& row = fragmentainerGroupAtFlowThreadOffset(offsetInFlowThread); row.recordSpaceShortage(spaceShortage); // Since we're at a potential break here, take the opportunity to check if we need another // fragmentainer group. If we've run out of columns in the last fragmentainer group (column // row), we need to insert another fragmentainer group to hold more columns. if (!row.isLastGroup()) return; LayoutMultiColumnFlowThread* flowThread = multiColumnFlowThread(); if (!flowThread->multiColumnBlockFlow()->isInsideFlowThread()) return; // Early bail. We're not nested, so waste no more time on this. if (!flowThread->isInInitialLayoutPass()) return; // Move the offset to where the next column starts, if we're not there already. offsetInFlowThread += flowThread->pageRemainingLogicalHeightForOffset(offsetInFlowThread, AssociateWithFormerPage); flowThread->appendNewFragmentainerGroupIfNeeded(offsetInFlowThread); }
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(); }
void RenderMultiColumnSet::updateLogicalWidth() { RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread(); setComputedColumnWidthAndCount(flowThread->columnWidth(), flowThread->columnCount()); // FIXME: When we add regions support, we'll start it off at the width of the multi-column // block in that particular region. setLogicalWidth(parentBox()->contentLogicalWidth()); // If we overflow, increase our logical width. unsigned colCount = columnCount(); LayoutUnit colGap = columnGap(); LayoutUnit minimumContentLogicalWidth = colCount * computedColumnWidth() + (colCount - 1) * colGap; LayoutUnit currentContentLogicalWidth = contentLogicalWidth(); LayoutUnit delta = max(LayoutUnit(), minimumContentLogicalWidth - currentContentLogicalWidth); if (!delta) return; // Increase our logical width by the delta. setLogicalWidth(logicalWidth() + delta); }
bool RenderMultiColumnSet::recalculateBalancedHeight(bool initial) { ASSERT(multiColumnFlowThread()->requiresBalancing()); LayoutUnit oldColumnHeight = m_computedColumnHeight; if (initial) distributeImplicitBreaks(); LayoutUnit newColumnHeight = calculateBalancedHeight(initial); 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. if (m_computedColumnHeight == oldColumnHeight) return false; // No change. We're done. m_minSpaceShortage = RenderFlowThread::maxLogicalHeight(); clearForcedBreaks(); return true; // Need another pass. }
void RenderSVGText::layout() { StackStats::LayoutCheckPoint layoutCheckPoint; ASSERT(needsLayout()); LayoutRepainter repainter(*this, SVGRenderSupport::checkForSVGRepaintDuringLayout(*this)); bool updateCachedBoundariesInParents = false; if (m_needsTransformUpdate) { m_localTransform = textElement().animatedLocalTransform(); m_needsTransformUpdate = false; updateCachedBoundariesInParents = true; } if (!everHadLayout()) { // When laying out initially, collect all layout attributes, build the character data map, // and propogate resulting SVGLayoutAttributes to all RenderSVGInlineText children in the subtree. ASSERT(m_layoutAttributes.isEmpty()); collectLayoutAttributes(this, m_layoutAttributes); updateFontInAllDescendants(this); m_layoutAttributesBuilder.buildLayoutAttributesForForSubtree(*this); m_needsReordering = true; m_needsTextMetricsUpdate = false; m_needsPositioningValuesUpdate = false; updateCachedBoundariesInParents = true; } else if (m_needsPositioningValuesUpdate) { // When the x/y/dx/dy/rotate lists change, recompute the layout attributes, and eventually // update the on-screen font objects as well in all descendants. if (m_needsTextMetricsUpdate) { updateFontInAllDescendants(this); m_needsTextMetricsUpdate = false; } m_layoutAttributesBuilder.buildLayoutAttributesForForSubtree(*this); m_needsReordering = true; m_needsPositioningValuesUpdate = false; updateCachedBoundariesInParents = true; } else if (m_needsTextMetricsUpdate || SVGRenderSupport::findTreeRootObject(*this).isLayoutSizeChanged()) { // If the root layout size changed (eg. window size changes) or the transform to the root // context has changed then recompute the on-screen font size. updateFontInAllDescendants(this, &m_layoutAttributesBuilder); ASSERT(!m_needsReordering); ASSERT(!m_needsPositioningValuesUpdate); m_needsTextMetricsUpdate = false; updateCachedBoundariesInParents = true; } checkLayoutAttributesConsistency(this, m_layoutAttributes); // Reduced version of RenderBlock::layoutBlock(), which only takes care of SVG text. // All if branches that could cause early exit in RenderBlocks layoutBlock() method are turned into assertions. ASSERT(!isInline()); ASSERT(!simplifiedLayout()); ASSERT(!scrollsOverflow()); ASSERT(!hasControlClip()); ASSERT(!multiColumnFlowThread()); ASSERT(!positionedObjects()); ASSERT(!m_overflow); ASSERT(!isAnonymousBlock()); if (!firstChild()) setChildrenInline(true); // FIXME: We need to find a way to only layout the child boxes, if needed. FloatRect oldBoundaries = objectBoundingBox(); ASSERT(childrenInline()); LayoutUnit repaintLogicalTop = 0; LayoutUnit repaintLogicalBottom = 0; rebuildFloatingObjectSetFromIntrudingFloats(); layoutInlineChildren(true, repaintLogicalTop, repaintLogicalBottom); if (m_needsReordering) m_needsReordering = false; if (!updateCachedBoundariesInParents) updateCachedBoundariesInParents = oldBoundaries != objectBoundingBox(); // Invalidate all resources of this client if our layout changed. if (everHadLayout() && selfNeedsLayout()) SVGResourcesCache::clientLayoutChanged(*this); // If our bounds changed, notify the parents. if (updateCachedBoundariesInParents) RenderSVGBlock::setNeedsBoundariesUpdate(); repainter.repaintAfterLayout(); clearNeedsLayout(); }
void LayoutMultiColumnSet::layout() { if (recalculateColumnHeight()) multiColumnFlowThread()->setColumnHeightsChanged(); LayoutBlockFlow::layout(); }