LayoutRect LayoutMultiColumnSet::flowThreadPortionRect() const { LayoutRect portionRect(LayoutUnit(), logicalTopInFlowThread(), pageLogicalWidth(), logicalHeightInFlowThread()); if (!isHorizontalWritingMode()) return portionRect.transposedRect(); return portionRect; }
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()) return LayoutRect(); // The bounding box doesn't intersect this fragmentainer group. 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))); 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))); return unionRect(startColumnRect, endColumnRect); }
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 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); } }
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); }
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. }
unsigned InitialColumnHeightFinder::contentRunIndexWithTallestColumns() const { unsigned indexWithLargestHeight = 0; LayoutUnit largestHeight; LayoutUnit previousOffset = logicalTopInFlowThread(); size_t runCount = m_contentRuns.size(); ASSERT(runCount); for (size_t i = firstContentRunIndexInLastRow(); i < runCount; i++) { const ContentRun& run = m_contentRuns[i]; LayoutUnit height = run.columnLogicalHeight(previousOffset); if (largestHeight < height) { largestHeight = height; indexWithLargestHeight = i; } previousOffset = run.breakOffset(); } return indexWithLargestHeight; }
unsigned RenderMultiColumnSet::findRunWithTallestColumns() const { unsigned indexWithLargestHeight = 0; LayoutUnit largestHeight; LayoutUnit previousOffset = logicalTopInFlowThread(); size_t runCount = m_contentRuns.size(); ASSERT(runCount); for (size_t i = 0; i < runCount; i++) { const ContentRun& run = m_contentRuns[i]; LayoutUnit height = run.columnLogicalHeight(previousOffset); if (largestHeight < height) { largestHeight = height; indexWithLargestHeight = i; } previousOffset = run.breakOffset(); } return indexWithLargestHeight; }
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; } }
LayoutUnit InitialColumnHeightFinder::initialMinimalBalancedHeight() const { unsigned index = contentRunIndexWithTallestColumns(); LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset() : logicalTopInFlowThread(); return m_contentRuns[index].columnLogicalHeight(startOffset); }
LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) const { unsigned columnIndex = columnIndexAtOffset(offset, AssumeNewColumns); return logicalTopInFlowThread() + columnIndex * pageLogicalHeight(); }
LayoutUnit RenderMultiColumnSet::calculateColumnHeight(BalancedHeightCalculation calculationMode) const { if (calculationMode == GuessFromFlowThreadPortion) { // Initial balancing. Start with the lowest imaginable column height. We use the tallest // content run (after having "inserted" implicit breaks), and find its start offset (by // looking at the previous run's end offset, or, if there's no previous run, the set's start // offset in the flow thread). unsigned index = findRunWithTallestColumns(); LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffset() : logicalTopInFlowThread(); return std::max<LayoutUnit>(m_contentRuns[index].columnLogicalHeight(startOffset), m_minimumColumnHeight); } if (actualColumnCount() <= usedColumnCount()) { // With the current column height, the content fits without creating overflowing columns. We're done. return m_columnHeight; } if (m_contentRuns.size() >= 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 shortage found during layout. ASSERT(m_minSpaceShortage > 0); // We should never _shrink_ the height! ASSERT(m_minSpaceShortage != RenderFlowThread::maxLogicalHeight()); // If this happens, we probably have a bug. if (m_minSpaceShortage == RenderFlowThread::maxLogicalHeight()) return m_columnHeight; // So bail out rather than looping infinitely. return m_columnHeight + m_minSpaceShortage; }
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; } }