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::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; }
void InitialColumnHeightFinder::distributeImplicitBreaks() { // Insert a final content run to encompass all content. This will include // overflow if we're at the end of the multicol container. addContentRun(logicalBottomInFlowThread()); unsigned columnCount = m_contentRuns.size(); // If there is room for more breaks (to reach the used value of column-count), // imagine that we insert implicit breaks at suitable locations. At any given // time, the content run with the currently tallest columns will get another // implicit break "inserted", which will increase its column count by one and // shrink its columns' height. Repeat until we have the desired total number // of breaks. The largest column height among the runs will then be the // initial column height for the balancer to use. if (columnCount > columnSet().usedColumnCount()) { // If we exceed used column-count (which we are allowed to do if we're at // the initial balancing pass for a multicol that lives inside another // to-be-balanced outer multicol container), we only care about content that // could end up in the last row. We need to pad up the number of columns, so // that all rows will contain as many columns as used column-count dictates. columnCount %= columnSet().usedColumnCount(); // If there are just enough explicit breaks to fill all rows with the right // amount of columns, we won't be needing any implicit breaks. if (!columnCount) return; } while (columnCount < columnSet().usedColumnCount()) { unsigned index = contentRunIndexWithTallestColumns(); m_contentRuns[index].assumeAnotherImplicitBreak(); columnCount++; } }
void RenderMultiColumnSet::distributeImplicitBreaks() { #ifndef NDEBUG // There should be no implicit breaks assumed at this point. for (unsigned i = 0; i < m_contentRuns.size(); i++) ASSERT(!m_contentRuns[i].assumedImplicitBreaks()); #endif // NDEBUG // Insert a final content run to encompass all content. This will include overflow if this is // the last set. addContentRun(logicalBottomInFlowThread()); unsigned columnCount = m_contentRuns.size(); // If there is room for more breaks (to reach the used value of column-count), imagine that we // insert implicit breaks at suitable locations. At any given time, the content run with the // currently tallest columns will get another implicit break "inserted", which will increase its // column count by one and shrink its columns' height. Repeat until we have the desired total // number of breaks. The largest column height among the runs will then be the initial column // height for the balancer to use. while (columnCount < usedColumnCount()) { unsigned index = findRunWithTallestColumns(); m_contentRuns[index].assumeAnotherImplicitBreak(); columnCount++; } }
void ColumnBalancer::traverseLines(const LayoutBlockFlow& blockFlow) { for (const RootInlineBox* line = blockFlow.firstRootBox(); line; line = line->nextRootBox()) { LayoutUnit lineTopInFlowThread = m_flowThreadOffset + line->lineTopWithLeading(); if (lineTopInFlowThread < logicalTopInFlowThread()) continue; if (lineTopInFlowThread >= logicalBottomInFlowThread()) break; examineLine(*line); } }
bool MultiColumnFragmentainerGroup::recalculateColumnHeight( LayoutMultiColumnSet& columnSet) { 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() && columnSet.heightIsAuto()) { LayoutUnit newColumnHeight; if (!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( columnSet, logicalTopInFlowThread(), logicalBottomInFlowThread()); LayoutUnit tallestUnbreakableLogicalHeight = initialHeightFinder.tallestUnbreakableLogicalHeight(); 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. }
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()) { // The bounding box doesn't intersect this fragmentainer group. return LayoutRect(); } 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), LayoutBox::AssociateWithLatterPage, CoordinateSpaceConversion::Containing)); 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), LayoutBox::AssociateWithLatterPage, CoordinateSpaceConversion::Containing)); return unionRect(startColumnRect, endColumnRect); }
void InitialColumnHeightFinder::distributeImplicitBreaks() { // Insert a final content run to encompass all content. This will include // overflow if we're at the end of the multicol container. addContentRun(logicalBottomInFlowThread()); unsigned columnCount = m_contentRuns.size(); // If there is room for more breaks (to reach the used value of column-count), // imagine that we insert implicit breaks at suitable locations. At any given // time, the content run with the currently tallest columns will get another // implicit break "inserted", which will increase its column count by one and // shrink its columns' height. Repeat until we have the desired total number // of breaks. The largest column height among the runs will then be the // initial column height for the balancer to use. while (columnCount < columnSet().usedColumnCount()) { unsigned index = contentRunIndexWithTallestColumns(); m_contentRuns[index].assumeAnotherImplicitBreak(); columnCount++; } }
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( columnSet(), logicalTopInFlowThread(), logicalBottomInFlowThread()); 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; }
void ColumnBalancer::traverseChildren(const LayoutObject& object) { // The break-after value from the previous in-flow block-level object to be // joined with the break-before value of the next in-flow block-level sibling. EBreak previousBreakAfterValue = BreakAuto; for (const LayoutObject* child = object.slowFirstChild(); child; child = child->nextSibling()) { if (!child->isBox()) { // Keep traversing inside inlines. There may be floats there. if (child->isLayoutInline()) traverseChildren(*child); continue; } const LayoutBox& childBox = toLayoutBox(*child); LayoutUnit borderEdgeOffset; LayoutUnit logicalTop = childBox.logicalTop(); LayoutUnit logicalHeight = childBox.logicalHeightWithVisibleOverflow(); // Floats' margins don't collapse with column boundaries, and we don't want // to break inside them, or separate them from the float's border box. Set // the offset to the margin-before edge (rather than border-before edge), // and include the block direction margins in the child height. if (childBox.isFloating()) { LayoutUnit marginBefore = childBox.marginBefore(object.style()); LayoutUnit marginAfter = childBox.marginAfter(object.style()); logicalHeight = std::max(logicalHeight, childBox.logicalHeight() + marginAfter); logicalTop -= marginBefore; logicalHeight += marginBefore; // As soon as we want to process content inside this child, though, we // need to get to its border-before edge. borderEdgeOffset = marginBefore; } if (m_flowThreadOffset + logicalTop + logicalHeight <= logicalTopInFlowThread()) { // This child is fully above the flow thread portion we're examining. continue; } if (m_flowThreadOffset + logicalTop >= logicalBottomInFlowThread()) { // This child is fully below the flow thread portion we're examining. We // cannot just stop here, though, thanks to negative margins. // So keep looking. continue; } if (childBox.isOutOfFlowPositioned() || childBox.isColumnSpanAll()) continue; // Tables are wicked. Both table rows and table cells are relative to their // table section. LayoutUnit offsetForThisChild = childBox.isTableRow() ? LayoutUnit() : logicalTop; m_flowThreadOffset += offsetForThisChild; examineBoxAfterEntering(childBox, logicalHeight, previousBreakAfterValue); // Unless the child is unsplittable, or if the child establishes an inner // multicol container, we descend into its subtree for further examination. if (childBox.getPaginationBreakability() != LayoutBox::ForbidBreaks && (!childBox.isLayoutBlockFlow() || !toLayoutBlockFlow(childBox).multiColumnFlowThread())) { // We need to get to the border edge before processing content inside // this child. If the child is floated, we're currently at the margin // edge. m_flowThreadOffset += borderEdgeOffset; traverseSubtree(childBox); m_flowThreadOffset -= borderEdgeOffset; } previousBreakAfterValue = childBox.breakAfter(); examineBoxBeforeLeaving(childBox, logicalHeight); m_flowThreadOffset -= offsetForThisChild; } }
LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset( LayoutUnit offsetInFlowThread, LayoutBox::PageBoundaryRule rule, CoordinateSpaceConversion mode) const { LayoutMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread(); // A column out of range doesn't have a flow thread portion, so we need to // clamp to make sure that we stay within the actual columns. This means that // content in the overflow area will be mapped to the last actual column, // instead of being mapped to an imaginary column further ahead. unsigned columnIndex = offsetInFlowThread >= logicalBottomInFlowThread() ? actualColumnCount() - 1 : columnIndexAtOffset(offsetInFlowThread, rule); LayoutRect portionRect(flowThreadPortionRectAt(columnIndex)); flowThread->flipForWritingMode(portionRect); portionRect.moveBy(flowThread->topLeftLocation()); LayoutRect columnRect(columnRectAt(columnIndex)); columnRect.move(offsetFromColumnSet()); m_columnSet.flipForWritingMode(columnRect); columnRect.moveBy(m_columnSet.topLeftLocation()); LayoutSize translationRelativeToFlowThread = columnRect.location() - portionRect.location(); if (mode == CoordinateSpaceConversion::Containing) return translationRelativeToFlowThread; LayoutSize enclosingTranslation; if (LayoutMultiColumnFlowThread* enclosingFlowThread = flowThread->enclosingFlowThread()) { const MultiColumnFragmentainerGroup& firstRow = flowThread->firstMultiColumnSet()->firstFragmentainerGroup(); // 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( firstRow.blockOffsetInEnclosingFragmentationContext(), LayoutBox::AssociateWithLatterPage, mode); // 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(), LayoutBox::AssociateWithLatterPage, mode); // 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 + translationRelativeToFlowThread; }
void ColumnBalancer::traverseSubtree(const LayoutBox& box) { if (box.childrenInline() && box.isLayoutBlockFlow()) { // Look for breaks between lines. for (const RootInlineBox* line = toLayoutBlockFlow(box).firstRootBox(); line; line = line->nextRootBox()) { LayoutUnit lineTopInFlowThread = m_flowThreadOffset + line->lineTopWithLeading(); if (lineTopInFlowThread < logicalTopInFlowThread()) continue; if (lineTopInFlowThread >= logicalBottomInFlowThread()) break; examineLine(*line); } } const LayoutFlowThread* flowThread = columnSet().flowThread(); bool isHorizontalWritingMode = flowThread->isHorizontalWritingMode(); // The break-after value from the previous in-flow block-level object to be // joined with the break-before value of the next in-flow block-level sibling. EBreak previousBreakAfterValue = BreakAuto; // Look for breaks between and inside block-level children. Even if this is a // block flow with inline children, there may be interesting floats to examine // here. for (const LayoutObject* child = box.slowFirstChild(); child; child = child->nextSibling()) { if (!child->isBox() || child->isInline()) continue; const LayoutBox& childBox = toLayoutBox(*child); LayoutRect overflowRect = childBox.layoutOverflowRect(); LayoutUnit childLogicalBottomWithOverflow = childBox.logicalTop() + (isHorizontalWritingMode ? overflowRect.maxY() : overflowRect.maxX()); if (m_flowThreadOffset + childLogicalBottomWithOverflow <= logicalTopInFlowThread()) { // This child is fully above the flow thread portion we're examining. continue; } LayoutUnit childLogicalTopWithOverflow = childBox.logicalTop() + (isHorizontalWritingMode ? overflowRect.y() : overflowRect.x()); if (m_flowThreadOffset + childLogicalTopWithOverflow >= logicalBottomInFlowThread()) { // This child is fully below the flow thread portion we're examining. We // cannot just stop here, though, thanks to negative margins. // So keep looking. continue; } if (childBox.isOutOfFlowPositioned() || childBox.isColumnSpanAll()) continue; // Tables are wicked. Both table rows and table cells are relative to their // table section. LayoutUnit offsetForThisChild = childBox.isTableRow() ? LayoutUnit() : childBox.logicalTop(); m_flowThreadOffset += offsetForThisChild; examineBoxAfterEntering(childBox, previousBreakAfterValue); // Unless the child is unsplittable, or if the child establishes an inner // multicol container, we descend into its subtree for further examination. if (childBox.getPaginationBreakability() != LayoutBox::ForbidBreaks && (!childBox.isLayoutBlockFlow() || !toLayoutBlockFlow(childBox).multiColumnFlowThread())) traverseSubtree(childBox); previousBreakAfterValue = childBox.breakAfter(); examineBoxBeforeLeaving(childBox); m_flowThreadOffset -= offsetForThisChild; } }