LayoutMultiColumnFlowThread* LayoutMultiColumnFlowThread::enclosingFlowThread() const { if (isLayoutPagedFlowThread()) { // Paged overflow containers should never be fragmented by enclosing fragmentation // contexts. They are to be treated as unbreakable content. return nullptr; } if (multiColumnBlockFlow()->isInsideFlowThread()) return toLayoutMultiColumnFlowThread(locateFlowThreadContainingBlockOf(*multiColumnBlockFlow())); return nullptr; }
LayoutUnit LayoutMultiColumnSet::columnGap() const { LayoutBlockFlow* parentBlock = multiColumnBlockFlow(); if (parentBlock->style()->hasNormalColumnGap()) return parentBlock->style()->fontDescription().computedPixelSize(); // "1em" is recommended as the normal gap setting. Matches <p> margins. return parentBlock->style()->columnGap(); }
void RenderMultiColumnFlowThread::evacuateAndDestroy() { RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); m_beingEvacuated = true; // Delete the line box tree. deleteLines(); LayoutStateDisabler layoutStateDisabler(&view()); // First promote all children of the flow thread. Before we move them to the flow thread's // container, we need to unregister the flow thread, so that they aren't just re-added again to // the flow thread that we're trying to empty. multicolContainer->setMultiColumnFlowThread(nullptr); moveAllChildrenTo(multicolContainer, true); // Move spanners back to their original DOM position in the tree, and destroy the placeholders. SpannerMap::iterator it; while ((it = m_spannerMap.begin()) != m_spannerMap.end()) { RenderBox* spanner = it->key; RenderMultiColumnSpannerPlaceholder* placeholder = it->value; RenderBlockFlow* originalContainer = toRenderBlockFlow(placeholder->parent()); multicolContainer->removeChild(*spanner); originalContainer->addChild(spanner, placeholder); placeholder->destroy(); m_spannerMap.remove(it); } // Remove all sets. while (RenderMultiColumnSet* columnSet = firstMultiColumnSet()) columnSet->destroy(); destroy(); }
void LayoutMultiColumnFlowThread::layoutColumns(SubtreeLayoutScope& layoutScope) { // Since we ended up here, it means that the multicol container (our parent) needed // layout. Since contents of the multicol container are diverted to the flow thread, the flow // thread needs layout as well. layoutScope.setChildNeedsLayout(this); m_blockOffsetInEnclosingFragmentationContext = enclosingFragmentationContext() ? multiColumnBlockFlow()->offsetFromLogicalTopOfFirstPage() : LayoutUnit(); for (LayoutBox* columnBox = firstMultiColumnBox(); columnBox; columnBox = columnBox->nextSiblingMultiColumnBox()) { if (!columnBox->isLayoutMultiColumnSet()) { ASSERT(columnBox->isLayoutMultiColumnSpannerPlaceholder()); // no other type is expected. continue; } LayoutMultiColumnSet* columnSet = toLayoutMultiColumnSet(columnBox); layoutScope.setChildNeedsLayout(columnSet); if (!m_columnHeightsChanged) { // This is the initial layout pass. We need to reset the column height, because contents // typically have changed. columnSet->resetColumnHeight(); } // Since column sets are regular block flow objects, and their position is changed in // regular block layout code (with no means for the multicol code to notice unless we add // hooks there), store the previous position now. If it changes in the imminent layout // pass, we may have to rebalance its columns. columnSet->storeOldPosition(); } m_columnHeightsChanged = false; invalidateColumnSets(); layout(); validateColumnSets(); }
void LayoutMultiColumnFlowThread::computePreferredLogicalWidths() { LayoutFlowThread::computePreferredLogicalWidths(); // The min/max intrinsic widths calculated really tell how much space elements need when // laid out inside the columns. In order to eventually end up with the desired column width, // we need to convert them to values pertaining to the multicol container. const LayoutBlockFlow* multicolContainer = multiColumnBlockFlow(); const ComputedStyle* multicolStyle = multicolContainer->style(); int columnCount = multicolStyle->hasAutoColumnCount() ? 1 : multicolStyle->columnCount(); LayoutUnit columnWidth; LayoutUnit gapExtra = (columnCount - 1) * multicolContainer->columnGap(); if (multicolStyle->hasAutoColumnWidth()) { m_minPreferredLogicalWidth = m_minPreferredLogicalWidth * columnCount + gapExtra; } else { columnWidth = multicolStyle->columnWidth(); m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, columnWidth); } // Note that if column-count is auto here, we should resolve it to calculate the maximum // intrinsic width, instead of pretending that it's 1. The only way to do that is by performing // a layout pass, but this is not an appropriate time or place for layout. The good news is that // if height is unconstrained and there are no explicit breaks, the resolved column-count really // should be 1. m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, columnWidth) * columnCount + gapExtra; }
void LayoutMultiColumnFlowThread::evacuateAndDestroy() { LayoutBlockFlow* multicolContainer = multiColumnBlockFlow(); m_isBeingEvacuated = true; // Remove all sets and spanners. while (LayoutBox* columnBox = firstMultiColumnBox()) { ASSERT(columnBox->isAnonymous()); columnBox->destroy(); } ASSERT(!previousSibling()); ASSERT(!nextSibling()); // Finally we can promote all flow thread's children. Before we move them to the flow thread's // container, we need to unregister the flow thread, so that they aren't just re-added again to // the flow thread that we're trying to empty. multicolContainer->resetMultiColumnFlowThread(); moveAllChildrenTo(multicolContainer, true); // We used to manually nuke the line box tree here, but that should happen automatically when // moving children around (the code above). ASSERT(!firstLineBox()); destroy(); }
LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) const { RenderBlockFlow* multicolBlock = multiColumnBlockFlow(); LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderBefore() - multicolBlock->paddingBefore(); height -= contentLogicalTop; return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created. }
LayoutMultiColumnSet* LayoutMultiColumnFlowThread::lastMultiColumnSet() const { for (LayoutObject* sibling = multiColumnBlockFlow()->lastChild(); sibling; sibling = sibling->previousSibling()) { if (sibling->isLayoutMultiColumnSet()) return toLayoutMultiColumnSet(sibling); } return nullptr; }
void LayoutMultiColumnFlowThread::populate() { LayoutBlockFlow* multicolContainer = multiColumnBlockFlow(); ASSERT(!nextSibling()); // Reparent children preceding the flow thread into the flow thread. It's multicol content // now. At this point there's obviously nothing after the flow thread, but layoutObjects (column // sets and spanners) will be inserted there as we insert elements into the flow thread. multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), this, true); }
void LayoutMultiColumnFlowThread::willBeRemovedFromTree() { // Detach all column sets from the flow thread. Cannot destroy them at this point, since they // are siblings of this object, and there may be pointers to this object's sibling somewhere // further up on the call stack. for (LayoutMultiColumnSet* columnSet = firstMultiColumnSet(); columnSet; columnSet = columnSet->nextSiblingMultiColumnSet()) columnSet->detachFromFlowThread(); multiColumnBlockFlow()->resetMultiColumnFlowThread(); LayoutFlowThread::willBeRemovedFromTree(); }
void LayoutMultiColumnFlowThread::createAndInsertMultiColumnSet(LayoutBox* insertBefore) { LayoutBlockFlow* multicolContainer = multiColumnBlockFlow(); LayoutMultiColumnSet* newSet = LayoutMultiColumnSet::createAnonymous(*this, multicolContainer->styleRef()); multicolContainer->LayoutBlock::addChild(newSet, insertBefore); invalidateColumnSets(); // We cannot handle immediate column set siblings (and there's no need for it, either). // There has to be at least one spanner separating them. ASSERT(!newSet->previousSiblingMultiColumnBox() || !newSet->previousSiblingMultiColumnBox()->isLayoutMultiColumnSet()); ASSERT(!newSet->nextSiblingMultiColumnBox() || !newSet->nextSiblingMultiColumnBox()->isLayoutMultiColumnSet()); }
void RenderMultiColumnSet::paintColumnRules(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (paintInfo.context->paintingDisabled()) return; RenderStyle* blockStyle = multiColumnBlockFlow()->style(); const Color& ruleColor = resolveColor(blockStyle, CSSPropertyWebkitColumnRuleColor); bool ruleTransparent = blockStyle->columnRuleIsTransparent(); EBorderStyle ruleStyle = blockStyle->columnRuleStyle(); LayoutUnit ruleThickness = blockStyle->columnRuleWidth(); LayoutUnit colGap = columnGap(); bool renderRule = ruleStyle > BHIDDEN && !ruleTransparent; if (!renderRule) return; unsigned colCount = columnCount(); if (colCount <= 1) return; bool antialias = shouldAntialiasLines(paintInfo.context); bool leftToRight = style()->isLeftToRightDirection(); LayoutUnit currLogicalLeftOffset = leftToRight ? LayoutUnit() : contentLogicalWidth(); LayoutUnit ruleAdd = borderAndPaddingLogicalLeft(); LayoutUnit ruleLogicalLeft = leftToRight ? LayoutUnit() : contentLogicalWidth(); LayoutUnit inlineDirectionSize = computedColumnWidth(); BoxSide boxSide = isHorizontalWritingMode() ? leftToRight ? BSLeft : BSRight : leftToRight ? BSTop : BSBottom; 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 paint the column rule. if (i < colCount - 1) { LayoutUnit ruleLeft = isHorizontalWritingMode() ? paintOffset.x() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd : paintOffset.x() + borderLeft() + paddingLeft(); LayoutUnit ruleRight = isHorizontalWritingMode() ? ruleLeft + ruleThickness : ruleLeft + contentWidth(); LayoutUnit ruleTop = isHorizontalWritingMode() ? paintOffset.y() + borderTop() + paddingTop() : paintOffset.y() + ruleLogicalLeft - ruleThickness / 2 + ruleAdd; LayoutUnit ruleBottom = isHorizontalWritingMode() ? ruleTop + contentHeight() : ruleTop + ruleThickness; IntRect pixelSnappedRuleRect = pixelSnappedIntRectFromEdges(ruleLeft, ruleTop, ruleRight, ruleBottom); drawLineForBoxSide(paintInfo.context, pixelSnappedRuleRect.x(), pixelSnappedRuleRect.y(), pixelSnappedRuleRect.maxX(), pixelSnappedRuleRect.maxY(), boxSide, ruleColor, ruleStyle, 0, 0, antialias); } ruleLogicalLeft = currLogicalLeftOffset; } }
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); }
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 RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) const { // Adjust for the top offset within the content box of the multicol container (containing // block), unless this is 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 (previousSiblingMultiColumnSet()) { RenderBlockFlow* multicolBlock = multiColumnBlockFlow(); LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderAndPaddingBefore(); height -= contentLogicalTop; } return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created. }
void LayoutMultiColumnFlowThread::contentWasLaidOut(LayoutUnit logicalTopInFlowThreadAfterPagination) { // 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. // First figure out if there's any chance that we're nested at all. If we can be sure that // we're not, bail early. This code is run very often, and since locating a containing flow // thread has some cost (depending on tree depth), avoid calling // enclosingFragmentationContext() right away. This test may give some false positives (hence // the "mayBe"), if we're in an out-of-flow subtree and have an outer multicol container that // doesn't affect us, but that's okay. We'll discover that further down the road when trying to // locate our enclosing flow thread for real. bool mayBeNested = multiColumnBlockFlow()->isInsideFlowThread() || view()->fragmentationContext(); if (!mayBeNested) return; appendNewFragmentainerGroupIfNeeded(logicalTopInFlowThreadAfterPagination); }
void LayoutMultiColumnFlowThread::createAndInsertSpannerPlaceholder(LayoutBox* spannerObjectInFlowThread, LayoutObject* insertedBeforeInFlowThread) { LayoutBox* insertBeforeColumnBox = nullptr; LayoutMultiColumnSet* setToSplit = nullptr; if (insertedBeforeInFlowThread) { // The spanner is inserted before something. Figure out what this entails. If the // next object is a spanner too, it means that we can simply insert a new spanner // placeholder in front of its placeholder. insertBeforeColumnBox = insertedBeforeInFlowThread->spannerPlaceholder(); if (!insertBeforeColumnBox) { // The next object isn't a spanner; it's regular column content. Examine what // comes right before us in the flow thread, then. LayoutObject* previousLayoutObject = previousInPreOrderSkippingOutOfFlow(this, spannerObjectInFlowThread); if (!previousLayoutObject || previousLayoutObject == this) { // The spanner is inserted as the first child of the multicol container, // which means that we simply insert a new spanner placeholder at the // beginning. insertBeforeColumnBox = firstMultiColumnBox(); } else if (LayoutMultiColumnSpannerPlaceholder* previousPlaceholder = containingColumnSpannerPlaceholder(previousLayoutObject)) { // Before us is another spanner. We belong right after it then. insertBeforeColumnBox = previousPlaceholder->nextSiblingMultiColumnBox(); } else { // We're inside regular column content with both feet. Find out which column // set this is. It needs to be split it into two sets, so that we can insert // a new spanner placeholder between them. setToSplit = mapDescendantToColumnSet(previousLayoutObject); ASSERT(setToSplit == mapDescendantToColumnSet(insertedBeforeInFlowThread)); insertBeforeColumnBox = setToSplit->nextSiblingMultiColumnBox(); // We've found out which set that needs to be split. Now proceed to // inserting the spanner placeholder, and then insert a second column set. } } ASSERT(setToSplit || insertBeforeColumnBox); } LayoutBlockFlow* multicolContainer = multiColumnBlockFlow(); LayoutMultiColumnSpannerPlaceholder* newPlaceholder = LayoutMultiColumnSpannerPlaceholder::createAnonymous(multicolContainer->styleRef(), *spannerObjectInFlowThread); ASSERT(!insertBeforeColumnBox || insertBeforeColumnBox->parent() == multicolContainer); multicolContainer->LayoutBlock::addChild(newPlaceholder, insertBeforeColumnBox); spannerObjectInFlowThread->setSpannerPlaceholder(*newPlaceholder); if (setToSplit) createAndInsertMultiColumnSet(insertBeforeColumnBox); }
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 LayoutMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit& width, unsigned& count) const { LayoutBlock* columnBlock = multiColumnBlockFlow(); const ComputedStyle* columnStyle = columnBlock->style(); LayoutUnit availableWidth = columnBlock->contentLogicalWidth(); LayoutUnit columnGap = columnBlock->columnGap(); LayoutUnit computedColumnWidth = max<LayoutUnit>(1, LayoutUnit(columnStyle->columnWidth())); unsigned computedColumnCount = max<int>(1, columnStyle->columnCount()); ASSERT(!columnStyle->hasAutoColumnCount() || !columnStyle->hasAutoColumnWidth()); if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) { count = computedColumnCount; width = std::max<LayoutUnit>(0, (availableWidth - ((count - 1) * columnGap)) / count); } else if (!columnStyle->hasAutoColumnWidth() && columnStyle->hasAutoColumnCount()) { count = std::max<LayoutUnit>(1, (availableWidth + columnGap) / (computedColumnWidth + columnGap)); width = ((availableWidth + columnGap) / count) - columnGap; } else { count = std::max<LayoutUnit>(std::min<LayoutUnit>(computedColumnCount, (availableWidth + columnGap) / (computedColumnWidth + columnGap)), 1); width = ((availableWidth + columnGap) / count) - columnGap; } }
void RenderMultiColumnSet::prepareForLayout() { RenderBlockFlow* multicolBlock = multiColumnBlockFlow(); RenderStyle* multicolStyle = multicolBlock->style(); // Set box logical top. ASSERT(!previousSiblingBox() || !previousSiblingBox()->isRenderMultiColumnSet()); // FIXME: multiple set not implemented; need to examine previous set to calculate the correct logical top. setLogicalTop(multicolBlock->borderBefore() + multicolBlock->paddingBefore()); // Set box width. updateLogicalWidth(); if (multicolBlock->multiColumnFlowThread()->requiresBalancing()) { // Set maximum column height. We will not stretch beyond this. m_maxColumnHeight = RenderFlowThread::maxLogicalHeight(); if (!multicolStyle->logicalHeight().isAuto()) { m_maxColumnHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalHeight(), -1); if (m_maxColumnHeight == -1) m_maxColumnHeight = RenderFlowThread::maxLogicalHeight(); } if (!multicolStyle->logicalMaxHeight().isUndefined()) { LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight(multicolStyle->logicalMaxHeight(), -1); if (logicalMaxHeight != -1 && m_maxColumnHeight > logicalMaxHeight) m_maxColumnHeight = logicalMaxHeight; } m_maxColumnHeight = heightAdjustedForSetOffset(m_maxColumnHeight); m_computedColumnHeight = 0; // Restart balancing. } else { setAndConstrainColumnHeight(heightAdjustedForSetOffset(multicolBlock->multiColumnFlowThread()->columnHeightAvailable())); } clearForcedBreaks(); // Nuke previously stored minimum column height. Contents may have changed for all we know. m_minimumColumnHeight = 0; }
void RenderMultiColumnFlowThread::flowThreadDescendantInserted(RenderObject* descendant) { if (gShiftingSpanner || m_beingEvacuated || descendant->isInFlowRenderFlowThread()) return; RenderObject* subtreeRoot = descendant; for (; descendant; descendant = (descendant ? descendant->nextInPreOrder(subtreeRoot) : nullptr)) { if (descendant->isRenderMultiColumnSpannerPlaceholder()) { // A spanner's placeholder has been inserted. The actual spanner renderer is moved from // where it would otherwise occur (if it weren't a spanner) to becoming a sibling of the // column sets. RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColumnSpannerPlaceholder(descendant); if (placeholder->flowThread() != this) { // This isn't our spanner! It shifted here from an ancestor multicolumn block. It's going to end up // becoming our spanner instead, but for it to do that we first have to nuke the original spanner, // and get the spanner content back into this flow thread. RenderBox* spanner = placeholder->spanner(); // Get info for the move of the original content back into our flow thread. RenderBoxModelObject* placeholderParent = toRenderBoxModelObject(placeholder->parent()); // We have to nuke the placeholder, since the ancestor already lost the mapping to it when // we shifted the placeholder down into this flow thread. RenderObject* placeholderNextSibling = placeholderParent->removeChild(*placeholder); // Get the ancestor multicolumn flow thread to clean up its mess. RenderBlockFlow* ancestorBlock = toRenderBlockFlow(spanner->parent()); ancestorBlock->multiColumnFlowThread()->flowThreadRelativeWillBeRemoved(spanner); // Now move the original content into our flow thread. It will end up calling flowThreadDescendantInserted // on the new content only, and everything will get set up properly. ancestorBlock->moveChildTo(placeholderParent, spanner, placeholderNextSibling, true); // Advance descendant. descendant = placeholderNextSibling; // If the spanner was the subtree root, then we're done, since there is nothing else left to insert. if (!descendant) return; // Now that we have done this, we can continue past the spanning content, since we advanced // descendant already. if (descendant) descendant = descendant->previousInPreOrder(subtreeRoot); continue; } ASSERT(!m_spannerMap.get(placeholder->spanner())); m_spannerMap.add(placeholder->spanner(), placeholder); ASSERT(!placeholder->firstChild()); // There should be no children here, but if there are, we ought to skip them. continue; } RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); RenderObject* nextRendererInFlowThread = descendant->nextInPreOrderAfterChildren(this); RenderObject* insertBeforeMulticolChild = nullptr; if (isValidColumnSpanner(this, descendant)) { // This is a spanner (column-span:all). Such renderers are moved from where they would // otherwise occur in the render tree to becoming a direct child of the multicol container, // so that they live among the column sets. This simplifies the layout implementation, and // basically just relies on regular block layout done by the RenderBlockFlow that // establishes the multicol container. RenderBlockFlow* container = toRenderBlockFlow(descendant->parent()); RenderMultiColumnSet* setToSplit = nullptr; if (nextRendererInFlowThread) { setToSplit = findSetRendering(descendant); if (setToSplit) { setToSplit->setNeedsLayout(); insertBeforeMulticolChild = setToSplit->nextSibling(); } } // Moving a spanner's renderer so that it becomes a sibling of the column sets requires us // to insert an anonymous placeholder in the tree where the spanner's renderer otherwise // would have been. This is needed for a two reasons: We need a way of separating inline // content before and after the spanner, so that it becomes separate line boxes. Secondly, // this placeholder serves as a break point for column sets, so that, when encountered, we // end flowing one column set and move to the next one. RenderMultiColumnSpannerPlaceholder* placeholder = RenderMultiColumnSpannerPlaceholder::createAnonymous(this, toRenderBox(descendant), &container->style()); container->addChild(placeholder, descendant->nextSibling()); container->removeChild(*descendant); // This is a guard to stop an ancestor flow thread from processing the spanner. gShiftingSpanner = true; multicolContainer->RenderBlock::addChild(descendant, insertBeforeMulticolChild); gShiftingSpanner = false; // The spanner has now been moved out from the flow thread, but we don't want to // examine its children anyway. They are all part of the spanner and shouldn't trigger // creation of column sets or anything like that. Continue at its original position in // the tree, i.e. where the placeholder was just put. if (subtreeRoot == descendant) subtreeRoot = placeholder; descendant = placeholder; } else { // This is regular multicol content, i.e. not part of a spanner. if (nextRendererInFlowThread && nextRendererInFlowThread->isRenderMultiColumnSpannerPlaceholder()) { // Inserted right before a spanner. Is there a set for us there? RenderMultiColumnSpannerPlaceholder* placeholder = toRenderMultiColumnSpannerPlaceholder(nextRendererInFlowThread); if (RenderObject* previous = placeholder->spanner()->previousSibling()) { if (previous->isRenderMultiColumnSet()) continue; // There's already a set there. Nothing to do. } insertBeforeMulticolChild = placeholder->spanner(); } else if (RenderMultiColumnSet* lastSet = lastMultiColumnSet()) { // This child is not an immediate predecessor of a spanner, which means that if this // child precedes a spanner at all, there has to be a column set created for us there // already. If it doesn't precede any spanner at all, on the other hand, we need a // column set at the end of the multicol container. We don't really check here if the // child inserted precedes any spanner or not (as that's an expensive operation). Just // make sure we have a column set at the end. It's no big deal if it remains unused. if (!lastSet->nextSibling()) continue; } } // Need to create a new column set when there's no set already created. We also always insert // another column set after a spanner. Even if it turns out that there are no renderers // following the spanner, there may be bottom margins there, which take up space. RenderMultiColumnSet* newSet = new RenderMultiColumnSet(*this, RenderStyle::createAnonymousStyleWithDisplay(&multicolContainer->style(), BLOCK)); newSet->initializeStyle(); multicolContainer->RenderBlock::addChild(newSet, insertBeforeMulticolChild); invalidateRegions(); // We cannot handle immediate column set siblings at the moment (and there's no need for // it, either). There has to be at least one spanner separating them. ASSERT(!previousColumnSetOrSpannerSiblingOf(newSet) || !previousColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet()); ASSERT(!nextColumnSetOrSpannerSiblingOf(newSet) || !nextColumnSetOrSpannerSiblingOf(newSet)->isRenderMultiColumnSet()); } }