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 InitialColumnHeightFinder::recordStrutBeforeOffset( LayoutUnit offsetInFlowThread, LayoutUnit strut) { ASSERT(columnSet().usedColumnCount() >= 1); unsigned columnCount = columnSet().usedColumnCount(); ASSERT(m_shortestStruts.size() == columnCount); unsigned index = groupAtOffset(offsetInFlowThread) .columnIndexAtOffset(offsetInFlowThread - strut, LayoutBox::AssociateWithLatterPage); if (index >= columnCount) return; m_shortestStruts[index] = std::min(m_shortestStruts[index], strut); }
void InitialColumnHeightFinder::addContentRun( LayoutUnit endOffsetInFlowThread) { endOffsetInFlowThread -= spaceUsedByStrutsAt(endOffsetInFlowThread); if (!m_contentRuns.isEmpty() && endOffsetInFlowThread <= m_contentRuns.back().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. However, if // we're in a nested fragmentation context, we may still need to record all // runs, since there'll be no overflow area in the inline direction then, but // rather additional rows of columns in multiple outer fragmentainers. if (m_contentRuns.size() >= columnSet().usedColumnCount()) { const auto* flowThread = columnSet().multiColumnFlowThread(); if (!flowThread->enclosingFragmentationContext() || columnSet().newFragmentainerGroupsAllowed()) return; } m_contentRuns.append(ContentRun(endOffsetInFlowThread)); }
void InitialColumnHeightFinder::addContentRun( LayoutUnit endOffsetInFlowThread) { endOffsetInFlowThread -= spaceUsedByStrutsAt(endOffsetInFlowThread); if (!m_contentRuns.isEmpty() && endOffsetInFlowThread <= 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() < columnSet().usedColumnCount()) m_contentRuns.append(ContentRun(endOffsetInFlowThread)); }
LayoutUnit InitialColumnHeightFinder::initialMinimalBalancedHeight() const { LayoutUnit rowLogicalTop; if (m_contentRuns.size() > columnSet().usedColumnCount()) { // We have not inserted additional fragmentainer groups yet (because we // aren't able to calculate their constraints yet), but we already know for // sure that there'll be more than one of them, due to the number of forced // breaks in a nested multicol container. We will now attempt to take all // the imaginary rows into account and calculate a minimal balanced logical // height for everything. unsigned stride = columnSet().usedColumnCount(); LayoutUnit rowStartOffset = logicalTopInFlowThread(); for (unsigned i = 0; i < firstContentRunIndexInLastRow(); i += stride) { LayoutUnit rowEndOffset = m_contentRuns[i + stride - 1].breakOffset(); float rowHeight = float(rowEndOffset - rowStartOffset) / float(stride); rowLogicalTop += LayoutUnit::fromFloatCeil(rowHeight); rowStartOffset = rowEndOffset; } } unsigned index = contentRunIndexWithTallestColumns(); LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset() : logicalTopInFlowThread(); LayoutUnit height = m_contentRuns[index].columnLogicalHeight(startOffset); return rowLogicalTop + std::max(height, m_tallestUnbreakableLogicalHeight); }
LayoutUnit InitialColumnHeightFinder::spaceUsedByStrutsAt( LayoutUnit offsetInFlowThread) const { unsigned stopBeforeColumn = groupAtOffset(offsetInFlowThread) .columnIndexAtOffset(offsetInFlowThread, LayoutBox::AssociateWithLatterPage) + 1; stopBeforeColumn = std::min(stopBeforeColumn, columnSet().usedColumnCount()); ASSERT(stopBeforeColumn <= m_shortestStruts.size()); LayoutUnit totalStrutSpace; for (unsigned i = 0; i < stopBeforeColumn; i++) { if (m_shortestStruts[i] != LayoutUnit::max()) totalStrutSpace += m_shortestStruts[i]; } return totalStrutSpace; }
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::traverse() { traverseSubtree(*columnSet().flowThread()); ASSERT(!flowThreadOffset()); }
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; } }