diff options
author | Konstantin Tokarev <annulen@yandex.ru> | 2016-08-25 19:20:41 +0300 |
---|---|---|
committer | Konstantin Tokarev <annulen@yandex.ru> | 2017-02-02 12:30:55 +0000 |
commit | 6882a04fb36642862b11efe514251d32070c3d65 (patch) | |
tree | b7959826000b061fd5ccc7512035c7478742f7b0 /Source/WebCore/rendering/RenderBlockLineLayout.cpp | |
parent | ab6df191029eeeb0b0f16f127d553265659f739e (diff) | |
download | qtwebkit-6882a04fb36642862b11efe514251d32070c3d65.tar.gz |
Imported QtWebKit TP3 (git b57bc6801f1876c3220d5a4bfea33d620d477443)
Change-Id: I3b1d8a2808782c9f34d50240000e20cb38d3680f
Reviewed-by: Konstantin Tokarev <annulen@yandex.ru>
Diffstat (limited to 'Source/WebCore/rendering/RenderBlockLineLayout.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderBlockLineLayout.cpp | 3376 |
1 files changed, 960 insertions, 2416 deletions
diff --git a/Source/WebCore/rendering/RenderBlockLineLayout.cpp b/Source/WebCore/rendering/RenderBlockLineLayout.cpp index c6f283a73..451de09c9 100644 --- a/Source/WebCore/rendering/RenderBlockLineLayout.cpp +++ b/Source/WebCore/rendering/RenderBlockLineLayout.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All right reserved. * Copyright (C) 2010 Google Inc. All rights reserved. * Copyright (C) 2013 ChangSeok Oh <shivamidow@gmail.com> + * Copyright (C) 2013 Adobe Systems Inc. All right reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -23,383 +24,45 @@ #include "config.h" +#include "AXObjectCache.h" #include "BidiResolver.h" -#include "Hyphenation.h" +#include "BreakingContext.h" +#include "FloatingObjects.h" +#include "InlineElementBox.h" #include "InlineIterator.h" #include "InlineTextBox.h" +#include "InlineTextBoxStyle.h" +#include "LineLayoutState.h" #include "Logging.h" -#include "RenderArena.h" -#include "RenderCombineText.h" -#include "RenderCounter.h" +#include "RenderBlockFlow.h" #include "RenderFlowThread.h" -#include "RenderInline.h" -#include "RenderLayer.h" -#include "RenderListMarker.h" +#include "RenderLineBreak.h" #include "RenderRegion.h" -#include "RenderRubyRun.h" +#include "RenderRubyBase.h" +#include "RenderRubyText.h" #include "RenderView.h" +#include "SVGRootInlineBox.h" #include "Settings.h" +#include "SimpleLineLayoutFunctions.h" #include "TrailingFloatsRootInlineBox.h" #include "VerticalPositionCache.h" -#include "break_lines.h" #include <wtf/RefCountedLeakCounter.h> #include <wtf/StdLibExtras.h> -#include <wtf/Vector.h> -#include <wtf/unicode/CharacterNames.h> - -#if ENABLE(CSS_SHAPES) -#include "ShapeInsideInfo.h" -#endif - -#if ENABLE(SVG) -#include "RenderSVGInlineText.h" -#include "SVGRootInlineBox.h" -#endif - -using namespace std; -using namespace WTF; -using namespace Unicode; namespace WebCore { -// We don't let our line box tree for a single line get any deeper than this. -const unsigned cMaxLineDepth = 200; - -static LayoutUnit logicalHeightForLine(const RenderBlock* block, bool isFirstLine, LayoutUnit replacedHeight = 0) -{ - if (!block->document()->inNoQuirksMode() && replacedHeight) - return replacedHeight; - - if (!(block->style(isFirstLine)->lineBoxContain() & LineBoxContainBlock)) - return 0; - - return max<LayoutUnit>(replacedHeight, block->lineHeight(isFirstLine, block->isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes)); -} - -#if ENABLE(CSS_SHAPES) -ShapeInsideInfo* RenderBlock::layoutShapeInsideInfo() const -{ - ShapeInsideInfo* shapeInsideInfo = view()->layoutState()->shapeInsideInfo(); - - if (!shapeInsideInfo && flowThreadContainingBlock() && allowsShapeInsideInfoSharing()) { - // regionAtBlockOffset returns regions like an array first={0,N-1}, second={N,M-1}, ... - LayoutUnit offset = logicalHeight() + logicalHeightForLine(this, false) - LayoutUnit(1); - RenderRegion* region = regionAtBlockOffset(offset); - if (region) - shapeInsideInfo = region->shapeInsideInfo(); - } - - return shapeInsideInfo; -} -#endif - -enum IndentTextOrNot { DoNotIndentText, IndentText }; - -class LineWidth { -public: - LineWidth(RenderBlock* block, bool isFirstLine, IndentTextOrNot shouldIndentText) - : m_block(block) - , m_uncommittedWidth(0) - , m_committedWidth(0) - , m_overhangWidth(0) - , m_trailingWhitespaceWidth(0) - , m_trailingCollapsedWhitespaceWidth(0) - , m_left(0) - , m_right(0) - , m_availableWidth(0) -#if ENABLE(CSS_SHAPES) - , m_segment(0) -#endif - , m_isFirstLine(isFirstLine) - , m_shouldIndentText(shouldIndentText) - { - ASSERT(block); -#if ENABLE(CSS_SHAPES) - if (ShapeInsideInfo* shapeInsideInfo = m_block->layoutShapeInsideInfo()) - m_segment = shapeInsideInfo->currentSegment(); -#endif - updateAvailableWidth(); - } - bool fitsOnLine(bool ignoringTrailingSpace = false) - { - return ignoringTrailingSpace ? fitsOnLineExcludingTrailingCollapsedWhitespace() : fitsOnLineIncludingExtraWidth(0); - } - bool fitsOnLineIncludingExtraWidth(float extra) const { return currentWidth() + extra <= m_availableWidth; } - bool fitsOnLineExcludingTrailingWhitespace(float extra) const { return currentWidth() - m_trailingWhitespaceWidth + extra <= m_availableWidth; } - - float currentWidth() const { return m_committedWidth + m_uncommittedWidth; } - // FIXME: We should eventually replace these three functions by ones that work on a higher abstraction. - float uncommittedWidth() const { return m_uncommittedWidth; } - float committedWidth() const { return m_committedWidth; } - float availableWidth() const { return m_availableWidth; } - - void updateAvailableWidth(LayoutUnit minimumHeight = 0); - void shrinkAvailableWidthForNewFloatIfNeeded(RenderBlock::FloatingObject*); - void addUncommittedWidth(float delta) { m_uncommittedWidth += delta; } - void commit() - { - m_committedWidth += m_uncommittedWidth; - m_uncommittedWidth = 0; - } - void applyOverhang(RenderRubyRun*, RenderObject* startRenderer, RenderObject* endRenderer); - void fitBelowFloats(); - void setTrailingWhitespaceWidth(float collapsedWhitespace, float borderPaddingMargin = 0) { m_trailingCollapsedWhitespaceWidth = collapsedWhitespace; m_trailingWhitespaceWidth = collapsedWhitespace + borderPaddingMargin; } - - bool shouldIndentText() const { return m_shouldIndentText == IndentText; } - -private: - void computeAvailableWidthFromLeftAndRight() - { - m_availableWidth = max(0.0f, m_right - m_left) + m_overhangWidth; - } - bool fitsOnLineExcludingTrailingCollapsedWhitespace() const { return currentWidth() - m_trailingCollapsedWhitespaceWidth <= m_availableWidth; } - -private: - RenderBlock* m_block; - float m_uncommittedWidth; - float m_committedWidth; - float m_overhangWidth; // The amount by which |m_availableWidth| has been inflated to account for possible contraction due to ruby overhang. - float m_trailingWhitespaceWidth; - float m_trailingCollapsedWhitespaceWidth; - float m_left; - float m_right; - float m_availableWidth; -#if ENABLE(CSS_SHAPES) - const LineSegment* m_segment; -#endif - bool m_isFirstLine; - IndentTextOrNot m_shouldIndentText; -}; - -inline void LineWidth::updateAvailableWidth(LayoutUnit replacedHeight) -{ - LayoutUnit height = m_block->logicalHeight(); - LayoutUnit logicalHeight = logicalHeightForLine(m_block, m_isFirstLine, replacedHeight); - m_left = m_block->logicalLeftOffsetForLine(height, shouldIndentText(), logicalHeight); - m_right = m_block->logicalRightOffsetForLine(height, shouldIndentText(), logicalHeight); - -#if ENABLE(CSS_SHAPES) - if (m_segment) { - m_left = max<float>(m_segment->logicalLeft, m_left); - m_right = min<float>(m_segment->logicalRight, m_right); - } -#endif - - computeAvailableWidthFromLeftAndRight(); -} - -inline void LineWidth::shrinkAvailableWidthForNewFloatIfNeeded(RenderBlock::FloatingObject* newFloat) -{ - LayoutUnit height = m_block->logicalHeight(); - if (height < m_block->logicalTopForFloat(newFloat) || height >= m_block->logicalBottomForFloat(newFloat)) - return; - -#if ENABLE(CSS_SHAPES) - // When floats with shape outside are stacked, the floats are positioned based on the margin box of the float, - // not the shape's contour. Since we computed the width based on the shape contour when we added the float, - // when we add a subsequent float on the same line, we need to undo the shape delta in order to position - // based on the margin box. In order to do this, we need to walk back through the floating object list to find - // the first previous float that is on the same side as our newFloat. - ShapeOutsideInfo* previousShapeOutsideInfo = 0; - const RenderBlock::FloatingObjectSet& floatingObjectSet = m_block->m_floatingObjects->set(); - RenderBlock::FloatingObjectSetIterator it = floatingObjectSet.end(); - RenderBlock::FloatingObjectSetIterator begin = floatingObjectSet.begin(); - for (--it; it != begin; --it) { - RenderBlock::FloatingObject* previousFloat = *it; - if (previousFloat != newFloat && previousFloat->type() == newFloat->type()) { - previousShapeOutsideInfo = previousFloat->renderer()->shapeOutsideInfo(); - if (previousShapeOutsideInfo) { - previousShapeOutsideInfo->computeSegmentsForContainingBlockLine(m_block->logicalHeight(), m_block->logicalTopForFloat(previousFloat), logicalHeightForLine(m_block, m_isFirstLine)); - } - break; - } - } - - ShapeOutsideInfo* shapeOutsideInfo = newFloat->renderer()->shapeOutsideInfo(); - if (shapeOutsideInfo) { - shapeOutsideInfo->computeSegmentsForContainingBlockLine(m_block->logicalHeight(), m_block->logicalTopForFloat(newFloat), logicalHeightForLine(m_block, m_isFirstLine)); - } -#endif - - if (newFloat->type() == RenderBlock::FloatingObject::FloatLeft) { - float newLeft = m_block->logicalRightForFloat(newFloat); -#if ENABLE(CSS_SHAPES) - if (previousShapeOutsideInfo) - newLeft -= previousShapeOutsideInfo->rightSegmentMarginBoxDelta(); - if (shapeOutsideInfo) - newLeft += shapeOutsideInfo->rightSegmentMarginBoxDelta(); -#endif - - if (shouldIndentText() && m_block->style()->isLeftToRightDirection()) - newLeft += floorToInt(m_block->textIndentOffset()); - m_left = max<float>(m_left, newLeft); - } else { - float newRight = m_block->logicalLeftForFloat(newFloat); -#if ENABLE(CSS_SHAPES) - if (previousShapeOutsideInfo) - newRight -= previousShapeOutsideInfo->leftSegmentMarginBoxDelta(); - if (shapeOutsideInfo) - newRight += shapeOutsideInfo->leftSegmentMarginBoxDelta(); -#endif - - if (shouldIndentText() && !m_block->style()->isLeftToRightDirection()) - newRight -= floorToInt(m_block->textIndentOffset()); - m_right = min<float>(m_right, newRight); - } - - computeAvailableWidthFromLeftAndRight(); -} - -void LineWidth::applyOverhang(RenderRubyRun* rubyRun, RenderObject* startRenderer, RenderObject* endRenderer) -{ - int startOverhang; - int endOverhang; - rubyRun->getOverhang(m_isFirstLine, startRenderer, endRenderer, startOverhang, endOverhang); - - startOverhang = min<int>(startOverhang, m_committedWidth); - m_availableWidth += startOverhang; - - endOverhang = max(min<int>(endOverhang, m_availableWidth - currentWidth()), 0); - m_availableWidth += endOverhang; - m_overhangWidth += startOverhang + endOverhang; -} - -void LineWidth::fitBelowFloats() -{ - ASSERT(!m_committedWidth); - ASSERT(!fitsOnLine()); - - LayoutUnit floatLogicalBottom; - LayoutUnit lastFloatLogicalBottom = m_block->logicalHeight(); - float newLineWidth = m_availableWidth; - float newLineLeft = m_left; - float newLineRight = m_right; - while (true) { - floatLogicalBottom = m_block->nextFloatLogicalBottomBelow(lastFloatLogicalBottom); - if (floatLogicalBottom <= lastFloatLogicalBottom) - break; - - newLineLeft = m_block->logicalLeftOffsetForLine(floatLogicalBottom, shouldIndentText()); - newLineRight = m_block->logicalRightOffsetForLine(floatLogicalBottom, shouldIndentText()); - newLineWidth = max(0.0f, newLineRight - newLineLeft); - lastFloatLogicalBottom = floatLogicalBottom; - if (newLineWidth >= m_uncommittedWidth) - break; - } - - if (newLineWidth > m_availableWidth) { - m_block->setLogicalHeight(lastFloatLogicalBottom); - m_availableWidth = newLineWidth + m_overhangWidth; - m_left = newLineLeft; - m_right = newLineRight; - } -} - -class LineInfo { -public: - LineInfo() - : m_isFirstLine(true) - , m_isLastLine(false) - , m_isEmpty(true) - , m_previousLineBrokeCleanly(true) - , m_floatPaginationStrut(0) - , m_runsFromLeadingWhitespace(0) - { } - - bool isFirstLine() const { return m_isFirstLine; } - bool isLastLine() const { return m_isLastLine; } - bool isEmpty() const { return m_isEmpty; } - bool previousLineBrokeCleanly() const { return m_previousLineBrokeCleanly; } - LayoutUnit floatPaginationStrut() const { return m_floatPaginationStrut; } - unsigned runsFromLeadingWhitespace() const { return m_runsFromLeadingWhitespace; } - void resetRunsFromLeadingWhitespace() { m_runsFromLeadingWhitespace = 0; } - void incrementRunsFromLeadingWhitespace() { m_runsFromLeadingWhitespace++; } - - void setFirstLine(bool firstLine) { m_isFirstLine = firstLine; } - void setLastLine(bool lastLine) { m_isLastLine = lastLine; } - void setEmpty(bool empty, RenderBlock* block = 0, LineWidth* lineWidth = 0) - { - if (m_isEmpty == empty) - return; - m_isEmpty = empty; - if (!empty && block && floatPaginationStrut()) { - block->setLogicalHeight(block->logicalHeight() + floatPaginationStrut()); - setFloatPaginationStrut(0); - lineWidth->updateAvailableWidth(); - } - } - - void setPreviousLineBrokeCleanly(bool previousLineBrokeCleanly) { m_previousLineBrokeCleanly = previousLineBrokeCleanly; } - void setFloatPaginationStrut(LayoutUnit strut) { m_floatPaginationStrut = strut; } - -private: - bool m_isFirstLine; - bool m_isLastLine; - bool m_isEmpty; - bool m_previousLineBrokeCleanly; - LayoutUnit m_floatPaginationStrut; - unsigned m_runsFromLeadingWhitespace; -}; - -static inline LayoutUnit borderPaddingMarginStart(RenderInline* child) -{ - return child->marginStart() + child->paddingStart() + child->borderStart(); -} - -static inline LayoutUnit borderPaddingMarginEnd(RenderInline* child) -{ - return child->marginEnd() + child->paddingEnd() + child->borderEnd(); -} - -static inline bool shouldAddBorderPaddingMargin(RenderObject* child) -{ - // When deciding whether we're at the edge of an inline, adjacent collapsed whitespace is the same as no sibling at all. - return !child || (child->isText() && !toRenderText(child)->textLength()); -} - -static RenderObject* previousInFlowSibling(RenderObject* child) -{ - child = child->previousSibling(); - while (child && child->isOutOfFlowPositioned()) - child = child->previousSibling(); - return child; -} - -static LayoutUnit inlineLogicalWidth(RenderObject* child, bool checkStartEdge = true, bool checkEndEdge = true) -{ - unsigned lineDepth = 1; - LayoutUnit extraWidth = 0; - RenderObject* parent = child->parent(); - while (parent->isRenderInline() && lineDepth++ < cMaxLineDepth) { - RenderInline* parentAsRenderInline = toRenderInline(parent); - if (!isEmptyInline(parentAsRenderInline)) { - checkStartEdge = checkStartEdge && shouldAddBorderPaddingMargin(previousInFlowSibling(child)); - if (checkStartEdge) - extraWidth += borderPaddingMarginStart(parentAsRenderInline); - checkEndEdge = checkEndEdge && shouldAddBorderPaddingMargin(child->nextSibling()); - if (checkEndEdge) - extraWidth += borderPaddingMarginEnd(parentAsRenderInline); - if (!checkStartEdge && !checkEndEdge) - return extraWidth; - } - child = parent; - parent = child->parent(); - } - return extraWidth; -} - static void determineDirectionality(TextDirection& dir, InlineIterator iter) { while (!iter.atEnd()) { if (iter.atParagraphSeparator()) return; if (UChar current = iter.current()) { - Direction charDirection = direction(current); - if (charDirection == LeftToRight) { + UCharDirection charDirection = u_charDirection(current); + if (charDirection == U_LEFT_TO_RIGHT) { dir = LTR; return; } - if (charDirection == RightToLeft || charDirection == RightToLeftArabic) { + if (charDirection == U_RIGHT_TO_LEFT || charDirection == U_RIGHT_TO_LEFT_ARABIC) { dir = RTL; return; } @@ -408,149 +71,107 @@ static void determineDirectionality(TextDirection& dir, InlineIterator iter) } } -static void checkMidpoints(LineMidpointState& lineMidpointState, InlineIterator& lBreak) +inline BidiRun* createRun(int start, int end, RenderObject& obj, InlineBidiResolver& resolver) { - // Check to see if our last midpoint is a start point beyond the line break. If so, - // shave it off the list, and shave off a trailing space if the previous end point doesn't - // preserve whitespace. - if (lBreak.m_obj && lineMidpointState.numMidpoints && !(lineMidpointState.numMidpoints % 2)) { - InlineIterator* midpoints = lineMidpointState.midpoints.data(); - InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints - 2]; - const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints - 1]; - InlineIterator currpoint = endpoint; - while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) - currpoint.increment(); - if (currpoint == lBreak) { - // We hit the line break before the start point. Shave off the start point. - lineMidpointState.numMidpoints--; - if (endpoint.m_obj->style()->collapseWhiteSpace() && endpoint.m_obj->isText()) - endpoint.m_pos--; - } - } -} - -// Don't call this directly. Use one of the descriptive helper functions below. -static void deprecatedAddMidpoint(LineMidpointState& lineMidpointState, const InlineIterator& midpoint) -{ - if (lineMidpointState.midpoints.size() <= lineMidpointState.numMidpoints) - lineMidpointState.midpoints.grow(lineMidpointState.numMidpoints + 10); - - InlineIterator* midpoints = lineMidpointState.midpoints.data(); - midpoints[lineMidpointState.numMidpoints++] = midpoint; -} - -static inline void startIgnoringSpaces(LineMidpointState& lineMidpointState, const InlineIterator& midpoint) -{ - ASSERT(!(lineMidpointState.numMidpoints % 2)); - deprecatedAddMidpoint(lineMidpointState, midpoint); -} - -static inline void stopIgnoringSpaces(LineMidpointState& lineMidpointState, const InlineIterator& midpoint) -{ - ASSERT(lineMidpointState.numMidpoints % 2); - deprecatedAddMidpoint(lineMidpointState, midpoint); + return new BidiRun(start, end, obj, resolver.context(), resolver.dir()); } -// When ignoring spaces, this needs to be called for objects that need line boxes such as RenderInlines or -// hard line breaks to ensure that they're not ignored. -static inline void ensureLineBoxInsideIgnoredSpaces(LineMidpointState& lineMidpointState, RenderObject* renderer) -{ - InlineIterator midpoint(0, renderer, 0); - stopIgnoringSpaces(lineMidpointState, midpoint); - startIgnoringSpaces(lineMidpointState, midpoint); -} - -// Adding a pair of midpoints before a character will split it out into a new line box. -static inline void ensureCharacterGetsLineBox(LineMidpointState& lineMidpointState, InlineIterator& textParagraphSeparator) -{ - InlineIterator midpoint(0, textParagraphSeparator.m_obj, textParagraphSeparator.m_pos); - startIgnoringSpaces(lineMidpointState, InlineIterator(0, textParagraphSeparator.m_obj, textParagraphSeparator.m_pos - 1)); - stopIgnoringSpaces(lineMidpointState, InlineIterator(0, textParagraphSeparator.m_obj, textParagraphSeparator.m_pos)); -} - -static inline BidiRun* createRun(int start, int end, RenderObject* obj, InlineBidiResolver& resolver) -{ - return new (obj->renderArena()) BidiRun(start, end, obj, resolver.context(), resolver.dir()); -} - -void RenderBlock::appendRunsForObject(BidiRunList<BidiRun>& runs, int start, int end, RenderObject* obj, InlineBidiResolver& resolver) +void RenderBlockFlow::appendRunsForObject(BidiRunList<BidiRun>* runs, int start, int end, RenderObject& obj, InlineBidiResolver& resolver) { if (start > end || shouldSkipCreatingRunsForObject(obj)) return; LineMidpointState& lineMidpointState = resolver.midpointState(); - bool haveNextMidpoint = (lineMidpointState.currentMidpoint < lineMidpointState.numMidpoints); + bool haveNextMidpoint = (lineMidpointState.currentMidpoint() < lineMidpointState.numMidpoints()); InlineIterator nextMidpoint; if (haveNextMidpoint) - nextMidpoint = lineMidpointState.midpoints[lineMidpointState.currentMidpoint]; - if (lineMidpointState.betweenMidpoints) { - if (!(haveNextMidpoint && nextMidpoint.m_obj == obj)) + nextMidpoint = lineMidpointState.midpoints()[lineMidpointState.currentMidpoint()]; + if (lineMidpointState.betweenMidpoints()) { + if (!haveNextMidpoint || (&obj != nextMidpoint.renderer())) return; // This is a new start point. Stop ignoring objects and // adjust our start. - lineMidpointState.betweenMidpoints = false; - start = nextMidpoint.m_pos; - lineMidpointState.currentMidpoint++; - if (start < end) - return appendRunsForObject(runs, start, end, obj, resolver); + start = nextMidpoint.offset(); + lineMidpointState.incrementCurrentMidpoint(); + if (start < end) { + appendRunsForObject(runs, start, end, obj, resolver); + return; + } } else { - if (!haveNextMidpoint || (obj != nextMidpoint.m_obj)) { - runs.addRun(createRun(start, end, obj, resolver)); + if (!haveNextMidpoint || (&obj != nextMidpoint.renderer())) { + if (runs) + runs->addRun(createRun(start, end, obj, resolver)); return; } - // An end midpoint has been encountered within our object. We - // need to go ahead and append a run with our endpoint. - if (static_cast<int>(nextMidpoint.m_pos + 1) <= end) { - lineMidpointState.betweenMidpoints = true; - lineMidpointState.currentMidpoint++; - if (nextMidpoint.m_pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it. - if (static_cast<int>(nextMidpoint.m_pos + 1) > start) - runs.addRun(createRun(start, nextMidpoint.m_pos + 1, obj, resolver)); - return appendRunsForObject(runs, nextMidpoint.m_pos + 1, end, obj, resolver); - } - } else - runs.addRun(createRun(start, end, obj, resolver)); + // An end midpoint has been encountered within our object. We need to append a run with our endpoint. + if (static_cast<int>(nextMidpoint.offset() + 1) <= end) { + lineMidpointState.incrementCurrentMidpoint(); + // The end of the line is before the object we're inspecting. Skip everything and return + if (nextMidpoint.refersToEndOfPreviousNode()) + return; + if (static_cast<int>(nextMidpoint.offset() + 1) > start && runs) + runs->addRun(createRun(start, nextMidpoint.offset() + 1, obj, resolver)); + appendRunsForObject(runs, nextMidpoint.offset() + 1, end, obj, resolver); + } else if (runs) + runs->addRun(createRun(start, end, obj, resolver)); } } -static inline InlineBox* createInlineBoxForRenderer(RenderObject* obj, bool isRootLineBox, bool isOnlyRun = false) +std::unique_ptr<RootInlineBox> RenderBlockFlow::createRootInlineBox() { - if (isRootLineBox) - return toRenderBlock(obj)->createAndAppendRootInlineBox(); + return std::make_unique<RootInlineBox>(*this); +} - if (obj->isText()) { - InlineTextBox* textBox = toRenderText(obj)->createInlineTextBox(); - // We only treat a box as text for a <br> if we are on a line by ourself or in strict mode - // (Note the use of strict mode. In "almost strict" mode, we don't treat the box for <br> as text.) - if (obj->isBR()) - textBox->setIsText(isOnlyRun || obj->document()->inNoQuirksMode()); - return textBox; - } +RootInlineBox* RenderBlockFlow::createAndAppendRootInlineBox() +{ + auto newRootBox = createRootInlineBox(); + RootInlineBox* rootBox = newRootBox.get(); + m_lineBoxes.appendLineBox(WTFMove(newRootBox)); - if (obj->isBox()) - return toRenderBox(obj)->createInlineBox(); + if (UNLIKELY(AXObjectCache::accessibilityEnabled()) && firstRootBox() == rootBox) { + if (AXObjectCache* cache = document().existingAXObjectCache()) + cache->recomputeIsIgnored(this); + } - return toRenderInline(obj)->createAndAppendInlineFlowBox(); + return rootBox; } -// FIXME: Don't let counters mark themselves as needing pref width recalcs during layout -// so we don't need this hack. -static inline void updateCounterIfNeeded(RenderText* o) +static inline InlineBox* createInlineBoxForRenderer(RenderObject* renderer, bool isRootLineBox, bool isOnlyRun = false) { - if (!o->preferredLogicalWidthsDirty() || !o->isCounter()) - return; - toRenderCounter(o)->updateCounter(); + if (isRootLineBox) + return downcast<RenderBlockFlow>(*renderer).createAndAppendRootInlineBox(); + + if (is<RenderText>(*renderer)) + return downcast<RenderText>(*renderer).createInlineTextBox(); + + if (is<RenderBox>(*renderer)) { + // FIXME: This is terrible. This branch returns an *owned* pointer! + return downcast<RenderBox>(*renderer).createInlineBox().release(); + } + + if (is<RenderLineBreak>(*renderer)) { + // FIXME: This is terrible. This branch returns an *owned* pointer! + auto inlineBox = downcast<RenderLineBreak>(*renderer).createInlineBox().release(); + // We only treat a box as text for a <br> if we are on a line by ourself or in strict mode + // (Note the use of strict mode. In "almost strict" mode, we don't treat the box for <br> as text.) + inlineBox->setBehavesLikeText(isOnlyRun || renderer->document().inNoQuirksMode() || renderer->isLineBreakOpportunity()); + return inlineBox; + } + + return downcast<RenderInline>(*renderer).createAndAppendInlineFlowBox(); } -static inline void dirtyLineBoxesForRenderer(RenderObject* o, bool fullLayout) +static inline void dirtyLineBoxesForRenderer(RenderObject& renderer, bool fullLayout) { - if (o->isText()) { - RenderText* renderText = toRenderText(o); + if (is<RenderText>(renderer)) { + RenderText& renderText = downcast<RenderText>(renderer); updateCounterIfNeeded(renderText); - renderText->dirtyLineBoxes(fullLayout); - } else - toRenderInline(o)->dirtyLineBoxes(fullLayout); + renderText.dirtyLineBoxes(fullLayout); + } else if (is<RenderLineBreak>(renderer)) + downcast<RenderLineBreak>(renderer).dirtyLineBoxes(fullLayout); + else + downcast<RenderInline>(renderer).dirtyLineBoxes(fullLayout); } static bool parentIsConstructedOrHaveNext(InlineFlowBox* parentBox) @@ -563,21 +184,21 @@ static bool parentIsConstructedOrHaveNext(InlineFlowBox* parentBox) return false; } -InlineFlowBox* RenderBlock::createLineBoxes(RenderObject* obj, const LineInfo& lineInfo, InlineBox* childBox, bool startNewSegment) +InlineFlowBox* RenderBlockFlow::createLineBoxes(RenderObject* obj, const LineInfo& lineInfo, InlineBox* childBox) { // See if we have an unconstructed line box for this object that is also // the last item on the line. unsigned lineDepth = 1; - InlineFlowBox* parentBox = 0; - InlineFlowBox* result = 0; - bool hasDefaultLineBoxContain = style()->lineBoxContain() == RenderStyle::initialLineBoxContain(); + InlineFlowBox* parentBox = nullptr; + InlineFlowBox* result = nullptr; + bool hasDefaultLineBoxContain = style().lineBoxContain() == RenderStyle::initialLineBoxContain(); do { - ASSERT_WITH_SECURITY_IMPLICATION(obj->isRenderInline() || obj == this); + ASSERT_WITH_SECURITY_IMPLICATION(is<RenderInline>(*obj) || obj == this); - RenderInline* inlineFlow = (obj != this) ? toRenderInline(obj) : 0; + RenderInline* inlineFlow = obj != this ? downcast<RenderInline>(obj) : nullptr; // Get the last box we made for this render object. - parentBox = inlineFlow ? inlineFlow->lastLineBox() : toRenderBlock(obj)->lastLineBox(); + parentBox = inlineFlow ? inlineFlow->lastLineBox() : downcast<RenderBlockFlow>(*obj).lastRootBox(); // If this box or its ancestor is constructed then it is from a previous line, and we need // to make a new box for our line. If this box or its ancestor is unconstructed but it has @@ -586,15 +207,13 @@ InlineFlowBox* RenderBlock::createLineBoxes(RenderObject* obj, const LineInfo& l // the same line (this can happen with very fancy language mixtures). bool constructedNewBox = false; bool allowedToConstructNewBox = !hasDefaultLineBoxContain || !inlineFlow || inlineFlow->alwaysCreateLineBoxes(); - bool mustCreateBoxesToRoot = startNewSegment && !(parentBox && parentBox->isRootInlineBox()); - bool canUseExistingParentBox = parentBox && !parentIsConstructedOrHaveNext(parentBox) && !mustCreateBoxesToRoot; + bool canUseExistingParentBox = parentBox && !parentIsConstructedOrHaveNext(parentBox); if (allowedToConstructNewBox && !canUseExistingParentBox) { // We need to make a new box for this render object. Once // made, we need to place it at the end of the current line. InlineBox* newBox = createInlineBoxForRenderer(obj, obj == this); - ASSERT_WITH_SECURITY_IMPLICATION(newBox->isInlineFlowBox()); - parentBox = toInlineFlowBox(newBox); - parentBox->setFirstLineStyleBit(lineInfo.isFirstLine()); + parentBox = downcast<InlineFlowBox>(newBox); + parentBox->setIsFirstLine(lineInfo.isFirstLine()); parentBox->setIsHorizontal(isHorizontalWritingMode()); if (!hasDefaultLineBoxContain) parentBox->clearDescendantsHaveSameLineHeightAndBaseline(); @@ -643,105 +262,130 @@ static bool reachedEndOfTextRenderer(const BidiRunList<BidiRun>& bidiRuns) if (!run) return true; unsigned pos = run->stop(); - RenderObject* r = run->m_object; - if (!r->isText() || r->isBR()) + const RenderObject& renderer = run->renderer(); + if (!is<RenderText>(renderer)) return false; - RenderText* renderText = toRenderText(r); - unsigned length = renderText->textLength(); + const RenderText& renderText = downcast<RenderText>(renderer); + unsigned length = renderText.textLength(); if (pos >= length) return true; - if (renderText->is8Bit()) - return endsWithASCIISpaces(renderText->characters8(), pos, length); - return endsWithASCIISpaces(renderText->characters16(), pos, length); + if (renderText.is8Bit()) + return endsWithASCIISpaces(renderText.characters8(), pos, length); + return endsWithASCIISpaces(renderText.characters16(), pos, length); } -RootInlineBox* RenderBlock::constructLine(BidiRunList<BidiRun>& bidiRuns, const LineInfo& lineInfo) +RootInlineBox* RenderBlockFlow::constructLine(BidiRunList<BidiRun>& bidiRuns, const LineInfo& lineInfo) { ASSERT(bidiRuns.firstRun()); bool rootHasSelectedChildren = false; InlineFlowBox* parentBox = 0; int runCount = bidiRuns.runCount() - lineInfo.runsFromLeadingWhitespace(); + for (BidiRun* r = bidiRuns.firstRun(); r; r = r->next()) { // Create a box for our object. bool isOnlyRun = (runCount == 1); - if (runCount == 2 && !r->m_object->isListMarker()) - isOnlyRun = (!style()->isLeftToRightDirection() ? bidiRuns.lastRun() : bidiRuns.firstRun())->m_object->isListMarker(); + if (runCount == 2 && !r->renderer().isListMarker()) + isOnlyRun = (!style().isLeftToRightDirection() ? bidiRuns.lastRun() : bidiRuns.firstRun())->renderer().isListMarker(); if (lineInfo.isEmpty()) continue; - InlineBox* box = createInlineBoxForRenderer(r->m_object, false, isOnlyRun); - r->m_box = box; - - ASSERT(box); - if (!box) - continue; + InlineBox* box = createInlineBoxForRenderer(&r->renderer(), false, isOnlyRun); + r->setBox(box); - if (!rootHasSelectedChildren && box->renderer()->selectionState() != RenderObject::SelectionNone) + if (!rootHasSelectedChildren && box->renderer().selectionState() != RenderObject::SelectionNone) rootHasSelectedChildren = true; - + // If we have no parent box yet, or if the run is not simply a sibling, // then we need to construct inline boxes as necessary to properly enclose the // run's inline box. Segments can only be siblings at the root level, as // they are positioned separately. -#if ENABLE(CSS_SHAPES) - bool runStartsSegment = r->m_startsSegment; -#else - bool runStartsSegment = false; -#endif - if (!parentBox || parentBox->renderer() != r->m_object->parent() || runStartsSegment) + if (!parentBox || &parentBox->renderer() != r->renderer().parent()) { // Create new inline boxes all the way back to the appropriate insertion point. - parentBox = createLineBoxes(r->m_object->parent(), lineInfo, box, runStartsSegment); - else { + RenderObject* parentToUse = r->renderer().parent(); + parentBox = createLineBoxes(parentToUse, lineInfo, box); + } else { // Append the inline box to this line. parentBox->addToLine(box); } - bool visuallyOrdered = r->m_object->style()->rtlOrdering() == VisualOrder; + bool visuallyOrdered = r->renderer().style().rtlOrdering() == VisualOrder; box->setBidiLevel(r->level()); - if (box->isInlineTextBox()) { - InlineTextBox* text = toInlineTextBox(box); - text->setStart(r->m_start); - text->setLen(r->m_stop - r->m_start); - text->setDirOverride(r->dirOverride(visuallyOrdered)); + if (is<InlineTextBox>(*box)) { + auto& textBox = downcast<InlineTextBox>(*box); + textBox.setStart(r->m_start); + textBox.setLen(r->m_stop - r->m_start); + textBox.setDirOverride(r->dirOverride(visuallyOrdered)); if (r->m_hasHyphen) - text->setHasHyphen(true); + textBox.setHasHyphen(true); } } // We should have a root inline box. It should be unconstructed and // be the last continuation of our line list. - ASSERT(lastLineBox() && !lastLineBox()->isConstructed()); + ASSERT(lastRootBox() && !lastRootBox()->isConstructed()); // Set the m_selectedChildren flag on the root inline box if one of the leaf inline box // from the bidi runs walk above has a selection state. if (rootHasSelectedChildren) - lastLineBox()->root()->setHasSelectedChildren(true); + lastRootBox()->root().setHasSelectedChildren(true); // Set bits on our inline flow boxes that indicate which sides should // paint borders/margins/padding. This knowledge will ultimately be used when // we determine the horizontal positions and widths of all the inline boxes on // the line. - bool isLogicallyLastRunWrapped = bidiRuns.logicallyLastRun()->m_object && bidiRuns.logicallyLastRun()->m_object->isText() ? !reachedEndOfTextRenderer(bidiRuns) : true; - lastLineBox()->determineSpacingForFlowBoxes(lineInfo.isLastLine(), isLogicallyLastRunWrapped, bidiRuns.logicallyLastRun()->m_object); + bool isLogicallyLastRunWrapped = bidiRuns.logicallyLastRun()->renderer().isText() ? !reachedEndOfTextRenderer(bidiRuns) : true; + lastRootBox()->determineSpacingForFlowBoxes(lineInfo.isLastLine(), isLogicallyLastRunWrapped, &bidiRuns.logicallyLastRun()->renderer()); // Now mark the line boxes as being constructed. - lastLineBox()->setConstructed(); + lastRootBox()->setConstructed(); // Return the last line. return lastRootBox(); } -ETextAlign RenderBlock::textAlignmentForLine(bool endsWithSoftBreak) const +ETextAlign RenderBlockFlow::textAlignmentForLine(bool endsWithSoftBreak) const { - ETextAlign alignment = style()->textAlign(); - if (!endsWithSoftBreak && alignment == JUSTIFY) - alignment = TASTART; + ETextAlign alignment = style().textAlign(); +#if ENABLE(CSS3_TEXT) + TextJustify textJustify = style().textJustify(); + if (alignment == JUSTIFY && textJustify == TextJustifyNone) + return style().direction() == LTR ? LEFT : RIGHT; +#endif + + if (endsWithSoftBreak) + return alignment; +#if !ENABLE(CSS3_TEXT) + return (alignment == JUSTIFY) ? TASTART : alignment; +#else + if (alignment != JUSTIFY) + return alignment; + + TextAlignLast alignmentLast = style().textAlignLast(); + switch (alignmentLast) { + case TextAlignLastStart: + return TASTART; + case TextAlignLastEnd: + return TAEND; + case TextAlignLastLeft: + return LEFT; + case TextAlignLastRight: + return RIGHT; + case TextAlignLastCenter: + return CENTER; + case TextAlignLastJustify: + return JUSTIFY; + case TextAlignLastAuto: + if (textJustify == TextJustifyDistribute) + return JUSTIFY; + return TASTART; + } return alignment; +#endif } static void updateLogicalWidthForLeftAlignedBlock(bool isLeftToRightDirection, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float availableLogicalWidth) @@ -750,12 +394,12 @@ static void updateLogicalWidthForLeftAlignedBlock(bool isLeftToRightDirection, B // In particular with RTL blocks, wide lines should still spill out to the left. if (isLeftToRightDirection) { if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) - trailingSpaceRun->m_box->setLogicalWidth(max<float>(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); + trailingSpaceRun->box()->setLogicalWidth(std::max<float>(0, trailingSpaceRun->box()->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); return; } if (trailingSpaceRun) - trailingSpaceRun->m_box->setLogicalWidth(0); + trailingSpaceRun->box()->setLogicalWidth(0); else if (totalLogicalWidth > availableLogicalWidth) logicalLeft -= (totalLogicalWidth - availableLogicalWidth); } @@ -767,17 +411,16 @@ static void updateLogicalWidthForRightAlignedBlock(bool isLeftToRightDirection, // side of the block. if (isLeftToRightDirection) { if (trailingSpaceRun) { - totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); - trailingSpaceRun->m_box->setLogicalWidth(0); + totalLogicalWidth -= trailingSpaceRun->box()->logicalWidth(); + trailingSpaceRun->box()->setLogicalWidth(0); } - if (totalLogicalWidth < availableLogicalWidth) - logicalLeft += availableLogicalWidth - totalLogicalWidth; + logicalLeft += std::max(0.f, availableLogicalWidth - totalLogicalWidth); return; } if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) { - trailingSpaceRun->m_box->setLogicalWidth(max<float>(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); - totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); + trailingSpaceRun->box()->setLogicalWidth(std::max<float>(0, trailingSpaceRun->box()->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); + totalLogicalWidth -= trailingSpaceRun->box()->logicalWidth(); } else logicalLeft += availableLogicalWidth - totalLogicalWidth; } @@ -786,68 +429,45 @@ static void updateLogicalWidthForCenterAlignedBlock(bool isLeftToRightDirection, { float trailingSpaceWidth = 0; if (trailingSpaceRun) { - totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); - trailingSpaceWidth = min(trailingSpaceRun->m_box->logicalWidth(), (availableLogicalWidth - totalLogicalWidth + 1) / 2); - trailingSpaceRun->m_box->setLogicalWidth(max<float>(0, trailingSpaceWidth)); + totalLogicalWidth -= trailingSpaceRun->box()->logicalWidth(); + trailingSpaceWidth = std::min(trailingSpaceRun->box()->logicalWidth(), (availableLogicalWidth - totalLogicalWidth + 1) / 2); + trailingSpaceRun->box()->setLogicalWidth(std::max<float>(0, trailingSpaceWidth)); } if (isLeftToRightDirection) - logicalLeft += max<float>((availableLogicalWidth - totalLogicalWidth) / 2, 0); + logicalLeft += std::max<float>((availableLogicalWidth - totalLogicalWidth) / 2, 0); else logicalLeft += totalLogicalWidth > availableLogicalWidth ? (availableLogicalWidth - totalLogicalWidth) : (availableLogicalWidth - totalLogicalWidth) / 2 - trailingSpaceWidth; } -void RenderBlock::setMarginsForRubyRun(BidiRun* run, RenderRubyRun* renderer, RenderObject* previousObject, const LineInfo& lineInfo) +void RenderBlockFlow::setMarginsForRubyRun(BidiRun* run, RenderRubyRun& renderer, RenderObject* previousObject, const LineInfo& lineInfo) { - int startOverhang; - int endOverhang; + float startOverhang; + float endOverhang; RenderObject* nextObject = 0; for (BidiRun* runWithNextObject = run->next(); runWithNextObject; runWithNextObject = runWithNextObject->next()) { - if (!runWithNextObject->m_object->isOutOfFlowPositioned() && !runWithNextObject->m_box->isLineBreak()) { - nextObject = runWithNextObject->m_object; + if (!runWithNextObject->renderer().isOutOfFlowPositioned() && !runWithNextObject->box()->isLineBreak()) { + nextObject = &runWithNextObject->renderer(); break; } } - renderer->getOverhang(lineInfo.isFirstLine(), renderer->style()->isLeftToRightDirection() ? previousObject : nextObject, renderer->style()->isLeftToRightDirection() ? nextObject : previousObject, startOverhang, endOverhang); + renderer.getOverhang(lineInfo.isFirstLine(), renderer.style().isLeftToRightDirection() ? previousObject : nextObject, renderer.style().isLeftToRightDirection() ? nextObject : previousObject, startOverhang, endOverhang); setMarginStartForChild(renderer, -startOverhang); setMarginEndForChild(renderer, -endOverhang); } -static inline float measureHyphenWidth(RenderText* renderer, const Font& font, HashSet<const SimpleFontData*>* fallbackFonts = 0) -{ - RenderStyle* style = renderer->style(); - return font.width(RenderBlock::constructTextRun(renderer, font, style->hyphenString().string(), style), fallbackFonts); -} - -class WordMeasurement { -public: - WordMeasurement() - : renderer(0) - , width(0) - , startOffset(0) - , endOffset(0) - { - } - - RenderText* renderer; - float width; - int startOffset; - int endOffset; - HashSet<const SimpleFontData*> fallbackFonts; -}; - -static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* run, RenderText* renderer, float xPos, const LineInfo& lineInfo, +static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* run, RenderText& renderer, float xPos, const LineInfo& lineInfo, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements) { - HashSet<const SimpleFontData*> fallbackFonts; + HashSet<const Font*> fallbackFonts; GlyphOverflow glyphOverflow; - - const Font& font = renderer->style(lineInfo.isFirstLine())->font(); + + const FontCascade& font = lineStyle(*renderer.parent(), lineInfo).fontCascade(); // Always compute glyph overflow if the block's line-box-contain value is "glyphs". if (lineBox->fitsToGlyphs()) { // If we don't stick out of the root line's font box, then don't bother computing our glyph overflow. This optimization // will keep us from computing glyph bounds in nearly all cases. bool includeRootLine = lineBox->includesRootLineBoxFontOrLeading(); - int baselineShift = lineBox->verticalPositionForBox(run->m_box, verticalPositionCache); + int baselineShift = lineBox->verticalPositionForBox(run->box(), verticalPositionCache); int rootDescent = includeRootLine ? font.fontMetrics().descent() : 0; int rootAscent = includeRootLine ? font.fontMetrics().ascent() : 0; int boxAscent = font.fontMetrics().ascent() - baselineShift; @@ -857,14 +477,13 @@ static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* ru } LayoutUnit hyphenWidth = 0; - if (toInlineTextBox(run->m_box)->hasHyphen()) { - const Font& font = renderer->style(lineInfo.isFirstLine())->font(); + if (downcast<InlineTextBox>(*run->box()).hasHyphen()) hyphenWidth = measureHyphenWidth(renderer, font, &fallbackFonts); - } + float measuredWidth = 0; - bool kerningIsEnabled = font.typesettingFeatures() & Kerning; - bool canUseSimpleFontCodePath = renderer->canUseSimpleFontCodePath(); + bool kerningIsEnabled = font.enableKerning(); + bool canUseSimpleFontCodePath = renderer.canUseSimpleFontCodePath(); // Since we don't cache glyph overflows, we need to re-measure the run if // the style is linebox-contain: glyph. @@ -875,23 +494,23 @@ static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* ru WordMeasurement& wordMeasurement = wordMeasurements[i]; if (wordMeasurement.width <= 0 || wordMeasurement.startOffset == wordMeasurement.endOffset) continue; - if (wordMeasurement.renderer != renderer || wordMeasurement.startOffset != lastEndOffset || wordMeasurement.endOffset > run->m_stop) + if (wordMeasurement.renderer != &renderer || wordMeasurement.startOffset != lastEndOffset || wordMeasurement.endOffset > run->m_stop) continue; lastEndOffset = wordMeasurement.endOffset; if (kerningIsEnabled && lastEndOffset == run->m_stop) { int wordLength = lastEndOffset - wordMeasurement.startOffset; GlyphOverflow overflow; - measuredWidth += renderer->width(wordMeasurement.startOffset, wordLength, xPos + measuredWidth, lineInfo.isFirstLine(), + measuredWidth += renderer.width(wordMeasurement.startOffset, wordLength, xPos + measuredWidth, lineInfo.isFirstLine(), &wordMeasurement.fallbackFonts, &overflow); - UChar c = renderer->characterAt(wordMeasurement.startOffset); + UChar c = renderer.characterAt(wordMeasurement.startOffset); if (i > 0 && wordLength == 1 && (c == ' ' || c == '\t')) - measuredWidth += renderer->style()->wordSpacing(); + measuredWidth += renderer.style().fontCascade().wordSpacing(); } else measuredWidth += wordMeasurement.width; if (!wordMeasurement.fallbackFonts.isEmpty()) { - HashSet<const SimpleFontData*>::const_iterator end = wordMeasurement.fallbackFonts.end(); - for (HashSet<const SimpleFontData*>::const_iterator it = wordMeasurement.fallbackFonts.begin(); it != end; ++it) + HashSet<const Font*>::const_iterator end = wordMeasurement.fallbackFonts.end(); + for (HashSet<const Font*>::const_iterator it = wordMeasurement.fallbackFonts.begin(); it != end; ++it) fallbackFonts.add(*it); } } @@ -903,60 +522,114 @@ static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* ru } if (!measuredWidth) - measuredWidth = renderer->width(run->m_start, run->m_stop - run->m_start, xPos, lineInfo.isFirstLine(), &fallbackFonts, &glyphOverflow); + measuredWidth = renderer.width(run->m_start, run->m_stop - run->m_start, xPos, lineInfo.isFirstLine(), &fallbackFonts, &glyphOverflow); - run->m_box->setLogicalWidth(measuredWidth + hyphenWidth); + run->box()->setLogicalWidth(measuredWidth + hyphenWidth); if (!fallbackFonts.isEmpty()) { - ASSERT(run->m_box->isText()); - GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(toInlineTextBox(run->m_box), make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).iterator; + ASSERT(run->box()->behavesLikeText()); + GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(downcast<InlineTextBox>(run->box()), std::make_pair(Vector<const Font*>(), GlyphOverflow())).iterator; ASSERT(it->value.first.isEmpty()); copyToVector(fallbackFonts, it->value.first); - run->m_box->parent()->clearDescendantsHaveSameLineHeightAndBaseline(); + run->box()->parent()->clearDescendantsHaveSameLineHeightAndBaseline(); } - if ((glyphOverflow.top || glyphOverflow.bottom || glyphOverflow.left || glyphOverflow.right)) { - ASSERT(run->m_box->isText()); - GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(toInlineTextBox(run->m_box), make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).iterator; + + // Include text decoration visual overflow as part of the glyph overflow. + if (renderer.style().textDecorationsInEffect() != TextDecorationNone) + glyphOverflow.extendTo(visualOverflowForDecorations(run->box()->lineStyle(), downcast<InlineTextBox>(run->box()))); + + if (!glyphOverflow.isEmpty()) { + ASSERT(run->box()->behavesLikeText()); + GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(downcast<InlineTextBox>(run->box()), std::make_pair(Vector<const Font*>(), GlyphOverflow())).iterator; it->value.second = glyphOverflow; - run->m_box->clearKnownToHaveNoOverflow(); + run->box()->clearKnownToHaveNoOverflow(); } } -static inline void computeExpansionForJustifiedText(BidiRun* firstRun, BidiRun* trailingSpaceRun, Vector<unsigned, 16>& expansionOpportunities, unsigned expansionOpportunityCount, float& totalLogicalWidth, float availableLogicalWidth) +void RenderBlockFlow::updateRubyForJustifiedText(RenderRubyRun& rubyRun, BidiRun& r, const Vector<unsigned, 16>& expansionOpportunities, unsigned& expansionOpportunityCount, float& totalLogicalWidth, float availableLogicalWidth, size_t& i) +{ + if (!rubyRun.rubyBase() || !rubyRun.rubyBase()->firstRootBox() || rubyRun.rubyBase()->firstRootBox()->nextRootBox() || !r.renderer().style().collapseWhiteSpace()) + return; + + auto& rubyBase = *rubyRun.rubyBase(); + auto& rootBox = *rubyBase.firstRootBox(); + + float totalExpansion = 0; + unsigned totalOpportunitiesInRun = 0; + for (auto* leafChild = rootBox.firstLeafChild(); leafChild; leafChild = leafChild->nextLeafChild()) { + if (!leafChild->isInlineTextBox()) + continue; + + unsigned opportunitiesInRun = expansionOpportunities[i++]; + ASSERT(opportunitiesInRun <= expansionOpportunityCount); + auto expansion = (availableLogicalWidth - totalLogicalWidth) * opportunitiesInRun / expansionOpportunityCount; + totalExpansion += expansion; + totalOpportunitiesInRun += opportunitiesInRun; + } + + ASSERT(!rubyRun.hasOverrideLogicalContentWidth()); + float newBaseWidth = rubyRun.logicalWidth() + totalExpansion + marginStartForChild(rubyRun) + marginEndForChild(rubyRun); + float newRubyRunWidth = rubyRun.logicalWidth() + totalExpansion; + rubyBase.setInitialOffset((newRubyRunWidth - newBaseWidth) / 2); + rubyRun.setOverrideLogicalContentWidth(newRubyRunWidth); + rubyRun.setNeedsLayout(MarkOnlyThis); + rootBox.markDirty(); + if (RenderRubyText* rubyText = rubyRun.rubyText()) { + if (RootInlineBox* textRootBox = rubyText->firstRootBox()) + textRootBox->markDirty(); + } + rubyRun.layoutBlock(true); + rubyRun.clearOverrideLogicalContentWidth(); + r.box()->setExpansion(newRubyRunWidth - r.box()->logicalWidth()); + + // This relayout caused the size of the RenderRubyText and the RenderRubyBase to change, dependent on the line's current expansion. Next time we relayout the + // RenderRubyRun, make sure that we relayout the RenderRubyBase and RenderRubyText as well. + rubyBase.setNeedsLayout(MarkOnlyThis); + if (RenderRubyText* rubyText = rubyRun.rubyText()) + rubyText->setNeedsLayout(MarkOnlyThis); + + totalLogicalWidth += totalExpansion; + expansionOpportunityCount -= totalOpportunitiesInRun; +} + +void RenderBlockFlow::computeExpansionForJustifiedText(BidiRun* firstRun, BidiRun* trailingSpaceRun, const Vector<unsigned, 16>& expansionOpportunities, unsigned expansionOpportunityCount, float totalLogicalWidth, float availableLogicalWidth) { if (!expansionOpportunityCount || availableLogicalWidth <= totalLogicalWidth) return; size_t i = 0; - for (BidiRun* r = firstRun; r; r = r->next()) { -#if ENABLE(CSS_SHAPES) - // This method is called once per segment, do not move past the current segment. - if (r->m_startsSegment) - break; -#endif - if (!r->m_box || r == trailingSpaceRun) + for (BidiRun* run = firstRun; run; run = run->next()) { + if (!run->box() || run == trailingSpaceRun) continue; - if (r->m_object->isText()) { + if (is<RenderText>(run->renderer())) { unsigned opportunitiesInRun = expansionOpportunities[i++]; ASSERT(opportunitiesInRun <= expansionOpportunityCount); // Only justify text if whitespace is collapsed. - if (r->m_object->style()->collapseWhiteSpace()) { - InlineTextBox* textBox = toInlineTextBox(r->m_box); - int expansion = (availableLogicalWidth - totalLogicalWidth) * opportunitiesInRun / expansionOpportunityCount; - textBox->setExpansion(expansion); + if (run->renderer().style().collapseWhiteSpace()) { + InlineTextBox& textBox = downcast<InlineTextBox>(*run->box()); + float expansion = (availableLogicalWidth - totalLogicalWidth) * opportunitiesInRun / expansionOpportunityCount; + textBox.setExpansion(expansion); totalLogicalWidth += expansion; } expansionOpportunityCount -= opportunitiesInRun; - if (!expansionOpportunityCount) - break; - } + } else if (is<RenderRubyRun>(run->renderer())) + updateRubyForJustifiedText(downcast<RenderRubyRun>(run->renderer()), *run, expansionOpportunities, expansionOpportunityCount, totalLogicalWidth, availableLogicalWidth, i); + + if (!expansionOpportunityCount) + break; } } -void RenderBlock::updateLogicalWidthForAlignment(const ETextAlign& textAlign, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float& availableLogicalWidth, int expansionOpportunityCount) +void RenderBlockFlow::updateLogicalWidthForAlignment(const ETextAlign& textAlign, const RootInlineBox* rootInlineBox, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float& availableLogicalWidth, int expansionOpportunityCount) { + TextDirection direction; + if (rootInlineBox && style().unicodeBidi() == Plaintext) + direction = rootInlineBox->direction(); + else + direction = style().direction(); + // Armed with the total width of the line (without justification), // we now examine our text-align property in order to determine where to position the // objects horizontally. The total width of the line can be increased if we end up @@ -964,69 +637,56 @@ void RenderBlock::updateLogicalWidthForAlignment(const ETextAlign& textAlign, Bi switch (textAlign) { case LEFT: case WEBKIT_LEFT: - updateLogicalWidthForLeftAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); + updateLogicalWidthForLeftAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case RIGHT: case WEBKIT_RIGHT: - updateLogicalWidthForRightAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); + updateLogicalWidthForRightAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case CENTER: case WEBKIT_CENTER: - updateLogicalWidthForCenterAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); + updateLogicalWidthForCenterAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case JUSTIFY: adjustInlineDirectionLineBounds(expansionOpportunityCount, logicalLeft, availableLogicalWidth); if (expansionOpportunityCount) { if (trailingSpaceRun) { - totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); - trailingSpaceRun->m_box->setLogicalWidth(0); + totalLogicalWidth -= trailingSpaceRun->box()->logicalWidth(); + trailingSpaceRun->box()->setLogicalWidth(0); } break; } - // Fall through + FALLTHROUGH; case TASTART: - if (style()->isLeftToRightDirection()) - updateLogicalWidthForLeftAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); + if (direction == LTR) + updateLogicalWidthForLeftAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); else - updateLogicalWidthForRightAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); + updateLogicalWidthForRightAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case TAEND: - if (style()->isLeftToRightDirection()) - updateLogicalWidthForRightAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); + if (direction == LTR) + updateLogicalWidthForRightAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); else - updateLogicalWidthForLeftAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); + updateLogicalWidthForLeftAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; } } -static IndentTextOrNot requiresIndent(bool isFirstLine, bool isAfterHardLineBreak, RenderStyle* style) -{ - IndentTextOrNot shouldIndentText = DoNotIndentText; - if (isFirstLine) - shouldIndentText = IndentText; -#if ENABLE(CSS3_TEXT) - else if (isAfterHardLineBreak && style->textIndentLine() == TextIndentEachLine) - shouldIndentText = IndentText; - - if (style->textIndentType() == TextIndentHanging) - shouldIndentText = shouldIndentText == IndentText ? DoNotIndentText : IndentText; -#else - UNUSED_PARAM(isAfterHardLineBreak); - UNUSED_PARAM(style); -#endif - return shouldIndentText; -} - -static void updateLogicalInlinePositions(RenderBlock* block, float& lineLogicalLeft, float& lineLogicalRight, float& availableLogicalWidth, bool firstLine, IndentTextOrNot shouldIndentText, LayoutUnit boxLogicalHeight) +static void updateLogicalInlinePositions(RenderBlockFlow& block, float& lineLogicalLeft, float& lineLogicalRight, float& availableLogicalWidth, bool firstLine, + IndentTextOrNot shouldIndentText, LayoutUnit boxLogicalHeight, RootInlineBox* rootBox) { - LayoutUnit lineLogicalHeight = logicalHeightForLine(block, firstLine, boxLogicalHeight); - lineLogicalLeft = block->pixelSnappedLogicalLeftOffsetForLine(block->logicalHeight(), shouldIndentText == IndentText, lineLogicalHeight); - lineLogicalRight = block->pixelSnappedLogicalRightOffsetForLine(block->logicalHeight(), shouldIndentText == IndentText, lineLogicalHeight); + LayoutUnit lineLogicalHeight = block.minLineHeightForReplacedRenderer(firstLine, boxLogicalHeight); + if (rootBox->hasAnonymousInlineBlock()) { + lineLogicalLeft = block.logicalLeftOffsetForContent(block.logicalHeight()); + lineLogicalRight = block.logicalRightOffsetForContent(block.logicalHeight()); + } else { + lineLogicalLeft = block.logicalLeftOffsetForLine(block.logicalHeight(), shouldIndentText, lineLogicalHeight); + lineLogicalRight = block.logicalRightOffsetForLine(block.logicalHeight(), shouldIndentText, lineLogicalHeight); + } availableLogicalWidth = lineLogicalRight - lineLogicalLeft; } -void RenderBlock::computeInlineDirectionPositionsForLine(RootInlineBox* lineBox, const LineInfo& lineInfo, BidiRun* firstRun, BidiRun* trailingSpaceRun, bool reachedEnd, - GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements) +void RenderBlockFlow::computeInlineDirectionPositionsForLine(RootInlineBox* lineBox, const LineInfo& lineInfo, BidiRun* firstRun, BidiRun* trailingSpaceRun, bool reachedEnd, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements) { ETextAlign textAlign = textAlignmentForLine(!reachedEnd && !lineBox->endsWithBreak()); @@ -1040,109 +700,185 @@ void RenderBlock::computeInlineDirectionPositionsForLine(RootInlineBox* lineBox, float lineLogicalLeft; float lineLogicalRight; float availableLogicalWidth; - updateLogicalInlinePositions(this, lineLogicalLeft, lineLogicalRight, availableLogicalWidth, isFirstLine, shouldIndentText, 0); + updateLogicalInlinePositions(*this, lineLogicalLeft, lineLogicalRight, availableLogicalWidth, isFirstLine, shouldIndentText, 0, lineBox); bool needsWordSpacing; -#if ENABLE(CSS_SHAPES) - ShapeInsideInfo* shapeInsideInfo = layoutShapeInsideInfo(); - if (shapeInsideInfo && shapeInsideInfo->hasSegments()) { - BidiRun* segmentStart = firstRun; - const SegmentList& segments = shapeInsideInfo->segments(); - float logicalLeft = max<float>(roundToInt(segments[0].logicalLeft), lineLogicalLeft); - float logicalRight = min<float>(floorToInt(segments[0].logicalRight), lineLogicalRight); - float startLogicalLeft = logicalLeft; - float endLogicalRight = logicalLeft; - float minLogicalLeft = logicalLeft; - float maxLogicalRight = logicalLeft; - lineBox->beginPlacingBoxRangesInInlineDirection(logicalLeft); - for (size_t i = 0; i < segments.size(); i++) { - if (i) { - logicalLeft = max<float>(roundToInt(segments[i].logicalLeft), lineLogicalLeft); - logicalRight = min<float>(floorToInt(segments[i].logicalRight), lineLogicalRight); - } - availableLogicalWidth = logicalRight - logicalLeft; - BidiRun* newSegmentStart = computeInlineDirectionPositionsForSegment(lineBox, lineInfo, textAlign, logicalLeft, availableLogicalWidth, segmentStart, trailingSpaceRun, textBoxDataMap, verticalPositionCache, wordMeasurements); - needsWordSpacing = false; - endLogicalRight = lineBox->placeBoxRangeInInlineDirection(segmentStart->m_box, newSegmentStart ? newSegmentStart->m_box : 0, logicalLeft, minLogicalLeft, maxLogicalRight, needsWordSpacing, textBoxDataMap); - if (!newSegmentStart || !newSegmentStart->next()) - break; - ASSERT(newSegmentStart->m_startsSegment); - // Discard the empty segment start marker bidi runs - segmentStart = newSegmentStart->next(); - } - lineBox->endPlacingBoxRangesInInlineDirection(startLogicalLeft, endLogicalRight, minLogicalLeft, maxLogicalRight); - return; - } -#endif - if (firstRun && firstRun->m_object->isReplaced()) { - RenderBox* renderBox = toRenderBox(firstRun->m_object); - updateLogicalInlinePositions(this, lineLogicalLeft, lineLogicalRight, availableLogicalWidth, isFirstLine, shouldIndentText, renderBox->logicalHeight()); + if (firstRun && firstRun->renderer().isReplaced()) { + RenderBox& renderBox = downcast<RenderBox>(firstRun->renderer()); + updateLogicalInlinePositions(*this, lineLogicalLeft, lineLogicalRight, availableLogicalWidth, isFirstLine, shouldIndentText, renderBox.logicalHeight(), lineBox); } computeInlineDirectionPositionsForSegment(lineBox, lineInfo, textAlign, lineLogicalLeft, availableLogicalWidth, firstRun, trailingSpaceRun, textBoxDataMap, verticalPositionCache, wordMeasurements); // The widths of all runs are now known. We can now place every inline box (and // compute accurate widths for the inline flow boxes). needsWordSpacing = false; - lineBox->placeBoxesInInlineDirection(lineLogicalLeft, needsWordSpacing, textBoxDataMap); + lineBox->placeBoxesInInlineDirection(lineLogicalLeft, needsWordSpacing); +} + +static inline ExpansionBehavior expansionBehaviorForInlineTextBox(RenderBlockFlow& block, InlineTextBox& textBox, BidiRun* previousRun, BidiRun* nextRun, ETextAlign textAlign, bool isAfterExpansion) +{ + // Tatechuyoko is modeled as the Object Replacement Character (U+FFFC), which can never have expansion opportunities inside nor intrinsically adjacent to it. + if (textBox.renderer().style().textCombine() == TextCombineHorizontal) + return ForbidLeadingExpansion | ForbidTrailingExpansion; + + ExpansionBehavior result = 0; + bool setLeadingExpansion = false; + bool setTrailingExpansion = false; + if (textAlign == JUSTIFY) { + // If the next box is ruby, and we're justifying, and the first box in the ruby base has a leading expansion, and we are a text box, then force a trailing expansion. + if (nextRun && is<RenderRubyRun>(nextRun->renderer()) && downcast<RenderRubyRun>(nextRun->renderer()).rubyBase() && nextRun->renderer().style().collapseWhiteSpace()) { + auto& rubyBase = *downcast<RenderRubyRun>(nextRun->renderer()).rubyBase(); + if (rubyBase.firstRootBox() && !rubyBase.firstRootBox()->nextRootBox()) { + if (auto* leafChild = rubyBase.firstRootBox()->firstLeafChild()) { + if (is<InlineTextBox>(*leafChild)) { + // FIXME: This leadingExpansionOpportunity doesn't actually work because it doesn't perform the UBA + if (FontCascade::leadingExpansionOpportunity(downcast<RenderText>(leafChild->renderer()).stringView(), leafChild->direction())) { + setTrailingExpansion = true; + result |= ForceTrailingExpansion; + } + } + } + } + } + // Same thing, except if we're following a ruby + if (previousRun && is<RenderRubyRun>(previousRun->renderer()) && downcast<RenderRubyRun>(previousRun->renderer()).rubyBase() && previousRun->renderer().style().collapseWhiteSpace()) { + auto& rubyBase = *downcast<RenderRubyRun>(previousRun->renderer()).rubyBase(); + if (rubyBase.firstRootBox() && !rubyBase.firstRootBox()->nextRootBox()) { + if (auto* leafChild = rubyBase.firstRootBox()->lastLeafChild()) { + if (is<InlineTextBox>(*leafChild)) { + // FIXME: This leadingExpansionOpportunity doesn't actually work because it doesn't perform the UBA + if (FontCascade::trailingExpansionOpportunity(downcast<RenderText>(leafChild->renderer()).stringView(), leafChild->direction())) { + setLeadingExpansion = true; + result |= ForceLeadingExpansion; + } + } + } + } + } + // If we're the first box inside a ruby base, forbid a leading expansion, and vice-versa + if (is<RenderRubyBase>(block)) { + RenderRubyBase& rubyBase = downcast<RenderRubyBase>(block); + if (&textBox == rubyBase.firstRootBox()->firstLeafChild()) { + setLeadingExpansion = true; + result |= ForbidLeadingExpansion; + } if (&textBox == rubyBase.firstRootBox()->lastLeafChild()) { + setTrailingExpansion = true; + result |= ForbidTrailingExpansion; + } + } + } + if (!setLeadingExpansion) + result |= isAfterExpansion ? ForbidLeadingExpansion : AllowLeadingExpansion; + if (!setTrailingExpansion) + result |= AllowTrailingExpansion; + return result; } -BidiRun* RenderBlock::computeInlineDirectionPositionsForSegment(RootInlineBox* lineBox, const LineInfo& lineInfo, ETextAlign textAlign, float& logicalLeft, +static inline void applyExpansionBehavior(InlineTextBox& textBox, ExpansionBehavior expansionBehavior) +{ + switch (expansionBehavior & LeadingExpansionMask) { + case ForceLeadingExpansion: + textBox.setForceLeadingExpansion(); + break; + case ForbidLeadingExpansion: + textBox.setCanHaveLeadingExpansion(false); + break; + case AllowLeadingExpansion: + textBox.setCanHaveLeadingExpansion(true); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + switch (expansionBehavior & TrailingExpansionMask) { + case ForceTrailingExpansion: + textBox.setForceTrailingExpansion(); + break; + case ForbidTrailingExpansion: + textBox.setCanHaveTrailingExpansion(false); + break; + case AllowTrailingExpansion: + textBox.setCanHaveTrailingExpansion(true); + break; + default: + ASSERT_NOT_REACHED(); + break; + } +} + +BidiRun* RenderBlockFlow::computeInlineDirectionPositionsForSegment(RootInlineBox* lineBox, const LineInfo& lineInfo, ETextAlign textAlign, float& logicalLeft, float& availableLogicalWidth, BidiRun* firstRun, BidiRun* trailingSpaceRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements) { bool needsWordSpacing = false; float totalLogicalWidth = lineBox->getFlowSpacingLogicalWidth(); unsigned expansionOpportunityCount = 0; - bool isAfterExpansion = true; + bool isAfterExpansion = is<RenderRubyBase>(*this) ? downcast<RenderRubyBase>(*this).isAfterExpansion() : true; Vector<unsigned, 16> expansionOpportunities; - RenderObject* previousObject = 0; - - BidiRun* r = firstRun; - for (; r; r = r->next()) { -#if ENABLE(CSS_SHAPES) - // Once we have reached the start of the next segment, we have finished - // computing the positions for this segment's contents. - if (r->m_startsSegment) - break; -#endif - if (!r->m_box || r->m_object->isOutOfFlowPositioned() || r->m_box->isLineBreak()) + + BidiRun* run = firstRun; + BidiRun* previousRun = nullptr; + for (; run; run = run->next()) { + if (!run->box() || run->renderer().isOutOfFlowPositioned() || run->box()->isLineBreak()) { continue; // Positioned objects are only participating to figure out their // correct static x position. They have no effect on the width. // Similarly, line break boxes have no effect on the width. - if (r->m_object->isText()) { - RenderText* rt = toRenderText(r->m_object); - if (textAlign == JUSTIFY && r != trailingSpaceRun) { - if (!isAfterExpansion) - toInlineTextBox(r->m_box)->setCanHaveLeadingExpansion(true); + } + if (is<RenderText>(run->renderer())) { + auto& renderText = downcast<RenderText>(run->renderer()); + auto& textBox = downcast<InlineTextBox>(*run->box()); + if (textAlign == JUSTIFY && run != trailingSpaceRun) { + ExpansionBehavior expansionBehavior = expansionBehaviorForInlineTextBox(*this, textBox, previousRun, run->next(), textAlign, isAfterExpansion); + applyExpansionBehavior(textBox, expansionBehavior); unsigned opportunitiesInRun; - if (rt->is8Bit()) - opportunitiesInRun = Font::expansionOpportunityCount(rt->characters8() + r->m_start, r->m_stop - r->m_start, r->m_box->direction(), isAfterExpansion); - else - opportunitiesInRun = Font::expansionOpportunityCount(rt->characters16() + r->m_start, r->m_stop - r->m_start, r->m_box->direction(), isAfterExpansion); + std::tie(opportunitiesInRun, isAfterExpansion) = FontCascade::expansionOpportunityCount(renderText.stringView(run->m_start, run->m_stop), run->box()->direction(), expansionBehavior); expansionOpportunities.append(opportunitiesInRun); expansionOpportunityCount += opportunitiesInRun; } - if (int length = rt->textLength()) { - if (!r->m_start && needsWordSpacing && isSpaceOrNewline(rt->characterAt(r->m_start))) - totalLogicalWidth += rt->style(lineInfo.isFirstLine())->font().wordSpacing(); - needsWordSpacing = !isSpaceOrNewline(rt->characterAt(r->m_stop - 1)) && r->m_stop == length; + if (int length = renderText.textLength()) { + if (!run->m_start && needsWordSpacing && isSpaceOrNewline(renderText.characterAt(run->m_start))) + totalLogicalWidth += lineStyle(*renderText.parent(), lineInfo).fontCascade().wordSpacing(); + needsWordSpacing = !isSpaceOrNewline(renderText.characterAt(run->m_stop - 1)) && run->m_stop == length; } - setLogicalWidthForTextRun(lineBox, r, rt, totalLogicalWidth, lineInfo, textBoxDataMap, verticalPositionCache, wordMeasurements); + setLogicalWidthForTextRun(lineBox, run, renderText, totalLogicalWidth, lineInfo, textBoxDataMap, verticalPositionCache, wordMeasurements); } else { - isAfterExpansion = false; - if (!r->m_object->isRenderInline()) { - RenderBox* renderBox = toRenderBox(r->m_object); - if (renderBox->isRubyRun()) - setMarginsForRubyRun(r, toRenderRubyRun(renderBox), previousObject, lineInfo); - r->m_box->setLogicalWidth(logicalWidthForChild(renderBox)); + bool encounteredJustifiedRuby = false; + if (is<RenderRubyRun>(run->renderer()) && textAlign == JUSTIFY && run != trailingSpaceRun && downcast<RenderRubyRun>(run->renderer()).rubyBase()) { + auto* rubyBase = downcast<RenderRubyRun>(run->renderer()).rubyBase(); + if (rubyBase->firstRootBox() && !rubyBase->firstRootBox()->nextRootBox() && run->renderer().style().collapseWhiteSpace()) { + rubyBase->setIsAfterExpansion(isAfterExpansion); + for (auto* leafChild = rubyBase->firstRootBox()->firstLeafChild(); leafChild; leafChild = leafChild->nextLeafChild()) { + if (!is<InlineTextBox>(*leafChild)) + continue; + auto& textBox = downcast<InlineTextBox>(*leafChild); + encounteredJustifiedRuby = true; + auto& renderText = downcast<RenderText>(leafChild->renderer()); + ExpansionBehavior expansionBehavior = expansionBehaviorForInlineTextBox(*rubyBase, textBox, nullptr, nullptr, textAlign, isAfterExpansion); + applyExpansionBehavior(textBox, expansionBehavior); + unsigned opportunitiesInRun; + std::tie(opportunitiesInRun, isAfterExpansion) = FontCascade::expansionOpportunityCount(renderText.stringView(), leafChild->direction(), expansionBehavior); + expansionOpportunities.append(opportunitiesInRun); + expansionOpportunityCount += opportunitiesInRun; + } + } + } + + if (!encounteredJustifiedRuby) + isAfterExpansion = false; + + if (!is<RenderInline>(run->renderer())) { + auto& renderBox = downcast<RenderBox>(run->renderer()); + if (is<RenderRubyRun>(renderBox)) + setMarginsForRubyRun(run, downcast<RenderRubyRun>(renderBox), previousRun ? &previousRun->renderer() : nullptr, lineInfo); + run->box()->setLogicalWidth(logicalWidthForChild(renderBox)); totalLogicalWidth += marginStartForChild(renderBox) + marginEndForChild(renderBox); } } - totalLogicalWidth += r->m_box->logicalWidth(); - previousObject = r->m_object; + totalLogicalWidth += run->box()->logicalWidth(); + previousRun = run; } if (isAfterExpansion && !expansionOpportunities.isEmpty()) { @@ -1150,72 +886,93 @@ BidiRun* RenderBlock::computeInlineDirectionPositionsForSegment(RootInlineBox* l expansionOpportunityCount--; } - updateLogicalWidthForAlignment(textAlign, trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth, expansionOpportunityCount); + if (is<RenderRubyBase>(*this) && !expansionOpportunityCount) + textAlign = CENTER; + + updateLogicalWidthForAlignment(textAlign, lineBox, trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth, expansionOpportunityCount); computeExpansionForJustifiedText(firstRun, trailingSpaceRun, expansionOpportunities, expansionOpportunityCount, totalLogicalWidth, availableLogicalWidth); - return r; + return run; } -void RenderBlock::computeBlockDirectionPositionsForLine(RootInlineBox* lineBox, BidiRun* firstRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, +void RenderBlockFlow::removeInlineBox(BidiRun& run, const RootInlineBox& rootLineBox) const +{ + auto* inlineBox = run.box(); +#if !ASSERT_DISABLED + auto* inlineParent = inlineBox->parent(); + while (inlineParent && inlineParent != &rootLineBox) { + ASSERT(!inlineParent->isDirty()); + inlineParent = inlineParent->parent(); + } + ASSERT(!rootLineBox.isDirty()); +#endif + auto* parent = inlineBox->parent(); + inlineBox->removeFromParent(); + + auto& renderer = run.renderer(); + if (is<RenderText>(renderer)) + downcast<RenderText>(renderer).removeTextBox(downcast<InlineTextBox>(*inlineBox)); + delete inlineBox; + run.setBox(nullptr); + // removeFromParent() unnecessarily dirties the ancestor subtree. + auto* ancestor = parent; + while (ancestor) { + ancestor->markDirty(false); + if (ancestor == &rootLineBox) + break; + ancestor = ancestor->parent(); + } +} + +void RenderBlockFlow::computeBlockDirectionPositionsForLine(RootInlineBox* lineBox, BidiRun* firstRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache) { setLogicalHeight(lineBox->alignBoxesInBlockDirection(logicalHeight(), textBoxDataMap, verticalPositionCache)); // Now make sure we place replaced render objects correctly. - for (BidiRun* r = firstRun; r; r = r->next()) { - ASSERT(r->m_box); - if (!r->m_box) + for (auto* run = firstRun; run; run = run->next()) { + ASSERT(run->box()); + if (!run->box()) continue; // Skip runs with no line boxes. // Align positioned boxes with the top of the line box. This is // a reasonable approximation of an appropriate y position. - if (r->m_object->isOutOfFlowPositioned()) - r->m_box->setLogicalTop(logicalHeight()); + auto& renderer = run->renderer(); + if (renderer.isOutOfFlowPositioned()) + run->box()->setLogicalTop(logicalHeight()); // Position is used to properly position both replaced elements and // to update the static normal flow x/y of positioned elements. - if (r->m_object->isText()) - toRenderText(r->m_object)->positionLineBox(r->m_box); - else if (r->m_object->isBox()) - toRenderBox(r->m_object)->positionLineBox(r->m_box); - } - // Positioned objects and zero-length text nodes destroy their boxes in - // position(), which unnecessarily dirties the line. - lineBox->markDirty(false); + bool inlineBoxIsRedundant = false; + if (is<RenderText>(renderer)) { + auto& inlineTextBox = downcast<InlineTextBox>(*run->box()); + downcast<RenderText>(renderer).positionLineBox(inlineTextBox); + inlineBoxIsRedundant = !inlineTextBox.len(); + } else if (is<RenderBox>(renderer)) { + downcast<RenderBox>(renderer).positionLineBox(downcast<InlineElementBox>(*run->box())); + inlineBoxIsRedundant = renderer.isOutOfFlowPositioned(); + } else if (is<RenderLineBreak>(renderer)) + downcast<RenderLineBreak>(renderer).replaceInlineBoxWrapper(downcast<InlineElementBox>(*run->box())); + // Check if we need to keep this box on the line at all. + if (inlineBoxIsRedundant) + removeInlineBox(*run, *lineBox); + } } -static inline bool isCollapsibleSpace(UChar character, RenderText* renderer) +static inline bool isCollapsibleSpace(UChar character, const RenderText& renderer) { if (character == ' ' || character == '\t' || character == softHyphen) return true; if (character == '\n') - return !renderer->style()->preserveNewline(); + return !renderer.style().preserveNewline(); if (character == noBreakSpace) - return renderer->style()->nbspMode() == SPACE; + return renderer.style().nbspMode() == SPACE; return false; } - -static void setStaticPositions(RenderBlock* block, RenderBox* child) -{ - // FIXME: The math here is actually not really right. It's a best-guess approximation that - // will work for the common cases - RenderObject* containerBlock = child->container(); - LayoutUnit blockHeight = block->logicalHeight(); - if (containerBlock->isRenderInline()) { - // A relative positioned inline encloses us. In this case, we also have to determine our - // position as though we were an inline. Set |staticInlinePosition| and |staticBlockPosition| on the relative positioned - // inline so that we can obtain the value later. - toRenderInline(containerBlock)->layer()->setStaticInlinePosition(block->startAlignedOffsetForLine(blockHeight, false)); - toRenderInline(containerBlock)->layer()->setStaticBlockPosition(blockHeight); - } - block->updateStaticInlinePositionForChild(child, blockHeight); - child->layer()->setStaticBlockPosition(blockHeight); -} - template <typename CharacterType> -static inline int findFirstTrailingSpace(RenderText* lastText, const CharacterType* characters, int start, int stop) +static inline int findFirstTrailingSpace(const RenderText& lastText, const CharacterType* characters, int start, int stop) { int firstSpace = stop; while (firstSpace > start) { @@ -1228,36 +985,36 @@ static inline int findFirstTrailingSpace(RenderText* lastText, const CharacterTy return firstSpace; } -inline BidiRun* RenderBlock::handleTrailingSpaces(BidiRunList<BidiRun>& bidiRuns, BidiContext* currentContext) +inline BidiRun* RenderBlockFlow::handleTrailingSpaces(BidiRunList<BidiRun>& bidiRuns, BidiContext* currentContext) { if (!bidiRuns.runCount() - || !bidiRuns.logicallyLastRun()->m_object->style()->breakOnlyAfterWhiteSpace() - || !bidiRuns.logicallyLastRun()->m_object->style()->autoWrap()) - return 0; + || !bidiRuns.logicallyLastRun()->renderer().style().breakOnlyAfterWhiteSpace() + || !bidiRuns.logicallyLastRun()->renderer().style().autoWrap()) + return nullptr; BidiRun* trailingSpaceRun = bidiRuns.logicallyLastRun(); - RenderObject* lastObject = trailingSpaceRun->m_object; - if (!lastObject->isText()) - return 0; + const RenderObject& lastObject = trailingSpaceRun->renderer(); + if (!is<RenderText>(lastObject)) + return nullptr; - RenderText* lastText = toRenderText(lastObject); + const RenderText& lastText = downcast<RenderText>(lastObject); int firstSpace; - if (lastText->is8Bit()) - firstSpace = findFirstTrailingSpace(lastText, lastText->characters8(), trailingSpaceRun->start(), trailingSpaceRun->stop()); + if (lastText.is8Bit()) + firstSpace = findFirstTrailingSpace(lastText, lastText.characters8(), trailingSpaceRun->start(), trailingSpaceRun->stop()); else - firstSpace = findFirstTrailingSpace(lastText, lastText->characters16(), trailingSpaceRun->start(), trailingSpaceRun->stop()); + firstSpace = findFirstTrailingSpace(lastText, lastText.characters16(), trailingSpaceRun->start(), trailingSpaceRun->stop()); if (firstSpace == trailingSpaceRun->stop()) - return 0; + return nullptr; - TextDirection direction = style()->direction(); + TextDirection direction = style().direction(); bool shouldReorder = trailingSpaceRun != (direction == LTR ? bidiRuns.lastRun() : bidiRuns.firstRun()); if (firstSpace != trailingSpaceRun->start()) { BidiContext* baseContext = currentContext; while (BidiContext* parent = baseContext->parent()) baseContext = parent; - BidiRun* newTrailingRun = new (renderArena()) BidiRun(firstSpace, trailingSpaceRun->m_stop, trailingSpaceRun->m_object, baseContext, OtherNeutral); + BidiRun* newTrailingRun = new BidiRun(firstSpace, trailingSpaceRun->m_stop, trailingSpaceRun->renderer(), baseContext, U_OTHER_NEUTRAL); trailingSpaceRun->m_stop = firstSpace; if (direction == LTR) bidiRuns.addRun(newTrailingRun); @@ -1279,21 +1036,30 @@ inline BidiRun* RenderBlock::handleTrailingSpaces(BidiRunList<BidiRun>& bidiRuns return trailingSpaceRun; } -void RenderBlock::appendFloatingObjectToLastLine(FloatingObject* floatingObject) +void RenderBlockFlow::appendFloatingObjectToLastLine(FloatingObject* floatingObject) { - ASSERT(!floatingObject->m_originatingLine); - floatingObject->m_originatingLine = lastRootBox(); + ASSERT_WITH_SECURITY_IMPLICATION(!floatingObject->originatingLine()); + floatingObject->setOriginatingLine(lastRootBox()); lastRootBox()->appendFloat(floatingObject->renderer()); } -// FIXME: This should be a BidiStatus constructor or create method. -static inline BidiStatus statusWithDirection(TextDirection textDirection, bool isOverride) +static inline void notifyResolverToResumeInIsolate(InlineBidiResolver& resolver, RenderObject* root, RenderObject* startObject) { - WTF::Unicode::Direction direction = textDirection == LTR ? LeftToRight : RightToLeft; - RefPtr<BidiContext> context = BidiContext::create(textDirection == LTR ? 0 : 1, direction, isOverride, FromStyleOrDOM); + if (root != startObject) { + RenderObject* parent = startObject->parent(); + notifyResolverToResumeInIsolate(resolver, root, parent); + notifyObserverEnteredObject(&resolver, startObject); + } +} + +static inline void setUpResolverToResumeInIsolate(InlineBidiResolver& resolver, InlineBidiResolver& topResolver, BidiRun& isolatedRun, RenderObject* root, RenderObject* startObject) +{ + // Set up m_midpointState + resolver.midpointState() = topResolver.midpointState(); + resolver.midpointState().setCurrentMidpoint(topResolver.midpointForIsolatedRun(isolatedRun)); - // This copies BidiStatus and may churn the ref on BidiContext. I doubt it matters. - return BidiStatus(direction, direction, direction, context.release()); + // Set up m_nestedIsolateCount + notifyResolverToResumeInIsolate(resolver, root, startObject); } // FIXME: BidiResolver should have this logic. @@ -1308,37 +1074,37 @@ static inline void constructBidiRunsForSegment(InlineBidiResolver& topResolver, while (!topResolver.isolatedRuns().isEmpty()) { // It does not matter which order we resolve the runs as long as we resolve them all. - BidiRun* isolatedRun = topResolver.isolatedRuns().last(); + auto isolatedRun = WTFMove(topResolver.isolatedRuns().last()); topResolver.isolatedRuns().removeLast(); + currentRoot = &isolatedRun.root; - RenderObject* startObj = isolatedRun->object(); + RenderObject& startObject = isolatedRun.object; // Only inlines make sense with unicode-bidi: isolate (blocks are already isolated). // FIXME: Because enterIsolate is not passed a RenderObject, we have to crawl up the // tree to see which parent inline is the isolate. We could change enterIsolate // to take a RenderObject and do this logic there, but that would be a layering // violation for BidiResolver (which knows nothing about RenderObject). - RenderInline* isolatedInline = toRenderInline(containingIsolate(startObj, currentRoot)); + RenderInline* isolatedInline = downcast<RenderInline>(highestContainingIsolateWithinRoot(startObject, currentRoot)); + ASSERT(isolatedInline); + InlineBidiResolver isolatedResolver; - EUnicodeBidi unicodeBidi = isolatedInline->style()->unicodeBidi(); + EUnicodeBidi unicodeBidi = isolatedInline->style().unicodeBidi(); TextDirection direction; if (unicodeBidi == Plaintext) - determineDirectionality(direction, InlineIterator(isolatedInline, isolatedRun->object(), 0)); + determineDirectionality(direction, InlineIterator(isolatedInline, &isolatedRun.object, 0)); else { ASSERT(unicodeBidi == Isolate || unicodeBidi == IsolateOverride); - direction = isolatedInline->style()->direction(); + direction = isolatedInline->style().direction(); } - isolatedResolver.setStatus(statusWithDirection(direction, isOverride(unicodeBidi))); + isolatedResolver.setStatus(BidiStatus(direction, isOverride(unicodeBidi))); - // FIXME: The fact that we have to construct an Iterator here - // currently prevents this code from moving into BidiResolver. - if (!bidiFirstSkippingEmptyInlines(isolatedInline, &isolatedResolver)) - continue; + setUpResolverToResumeInIsolate(isolatedResolver, topResolver, isolatedRun.runToReplace, isolatedInline, &startObject); // The starting position is the beginning of the first run within the isolate that was identified // during the earlier call to createBidiRunsForLine. This can be but is not necessarily the // first run within the isolate. - InlineIterator iter = InlineIterator(isolatedInline, startObj, isolatedRun->m_start); + InlineIterator iter = InlineIterator(isolatedInline, &startObject, isolatedRun.position); isolatedResolver.setPositionIgnoringNestedIsolates(iter); // We stop at the next end of line; we may re-enter this isolate in the next call to constructBidiRuns(). @@ -1350,73 +1116,36 @@ static inline void constructBidiRunsForSegment(InlineBidiResolver& topResolver, // itself to be turned into an InlineBox. We can't remove it here without potentially losing track of // the logically last run. if (isolatedResolver.runs().runCount()) - bidiRuns.replaceRunWithRuns(isolatedRun, isolatedResolver.runs()); + bidiRuns.replaceRunWithRuns(&isolatedRun.runToReplace, isolatedResolver.runs()); // If we encountered any nested isolate runs, just move them // to the top resolver's list for later processing. - if (!isolatedResolver.isolatedRuns().isEmpty()) { - topResolver.isolatedRuns().appendVector(isolatedResolver.isolatedRuns()); - isolatedResolver.isolatedRuns().clear(); - } - } -} - -static inline void constructBidiRunsForLine(const RenderBlock* block, InlineBidiResolver& topResolver, BidiRunList<BidiRun>& bidiRuns, const InlineIterator& endOfLine, VisualDirectionOverride override, bool previousLineBrokeCleanly) -{ -#if !ENABLE(CSS_SHAPES) - UNUSED_PARAM(block); - constructBidiRunsForSegment(topResolver, bidiRuns, endOfLine, override, previousLineBrokeCleanly); -#else - ShapeInsideInfo* shapeInsideInfo = block->layoutShapeInsideInfo(); - if (!shapeInsideInfo || !shapeInsideInfo->hasSegments()) { - constructBidiRunsForSegment(topResolver, bidiRuns, endOfLine, override, previousLineBrokeCleanly); - return; - } - - const SegmentRangeList& segmentRanges = shapeInsideInfo->segmentRanges(); - ASSERT(segmentRanges.size()); - - for (size_t i = 0; i < segmentRanges.size(); i++) { - LineSegmentIterator iterator = segmentRanges[i].start; - InlineIterator segmentStart(iterator.root, iterator.object, iterator.offset); - iterator = segmentRanges[i].end; - InlineIterator segmentEnd(iterator.root, iterator.object, iterator.offset); - if (i) { - ASSERT(segmentStart.m_obj); - BidiRun* segmentMarker = createRun(segmentStart.m_pos, segmentStart.m_pos, segmentStart.m_obj, topResolver); - segmentMarker->m_startsSegment = true; - bidiRuns.addRun(segmentMarker); - // Do not collapse midpoints between segments - topResolver.midpointState().betweenMidpoints = false; + while (!isolatedResolver.isolatedRuns().isEmpty()) { + auto runWithContext = WTFMove(isolatedResolver.isolatedRuns().last()); + isolatedResolver.isolatedRuns().removeLast(); + topResolver.setMidpointForIsolatedRun(runWithContext.runToReplace, isolatedResolver.midpointForIsolatedRun(runWithContext.runToReplace)); + topResolver.isolatedRuns().append(WTFMove(runWithContext)); } - if (segmentStart == segmentEnd) - continue; - topResolver.setPosition(segmentStart, numberOfIsolateAncestors(segmentStart)); - constructBidiRunsForSegment(topResolver, bidiRuns, segmentEnd, override, previousLineBrokeCleanly); } -#endif } // This function constructs line boxes for all of the text runs in the resolver and computes their position. -RootInlineBox* RenderBlock::createLineBoxesFromBidiRuns(BidiRunList<BidiRun>& bidiRuns, const InlineIterator& end, LineInfo& lineInfo, VerticalPositionCache& verticalPositionCache, BidiRun* trailingSpaceRun, WordMeasurements& wordMeasurements) +RootInlineBox* RenderBlockFlow::createLineBoxesFromBidiRuns(unsigned bidiLevel, BidiRunList<BidiRun>& bidiRuns, const InlineIterator& end, LineInfo& lineInfo, VerticalPositionCache& verticalPositionCache, BidiRun* trailingSpaceRun, WordMeasurements& wordMeasurements) { if (!bidiRuns.runCount()) - return 0; + return nullptr; // FIXME: Why is this only done when we had runs? - lineInfo.setLastLine(!end.m_obj); + lineInfo.setLastLine(!end.renderer()); RootInlineBox* lineBox = constructLine(bidiRuns, lineInfo); if (!lineBox) - return 0; + return nullptr; + lineBox->setBidiLevel(bidiLevel); lineBox->setEndsWithBreak(lineInfo.previousLineBrokeCleanly()); -#if ENABLE(SVG) - bool isSVGRootInlineBox = lineBox->isSVGRootInlineBox(); -#else - bool isSVGRootInlineBox = false; -#endif + bool isSVGRootInlineBox = is<SVGRootInlineBox>(*lineBox); GlyphOverflowAndFallbackFontsMap textBoxDataMap; @@ -1427,136 +1156,43 @@ RootInlineBox* RenderBlock::createLineBoxesFromBidiRuns(BidiRunList<BidiRun>& bi // Now position our text runs vertically. computeBlockDirectionPositionsForLine(lineBox, bidiRuns.firstRun(), textBoxDataMap, verticalPositionCache); -#if ENABLE(SVG) // SVG text layout code computes vertical & horizontal positions on its own. // Note that we still need to execute computeVerticalPositionsForLine() as // it calls InlineTextBox::positionLineBox(), which tracks whether the box // contains reversed text or not. If we wouldn't do that editing and thus // text selection in RTL boxes would not work as expected. if (isSVGRootInlineBox) { - ASSERT(isSVGText()); - static_cast<SVGRootInlineBox*>(lineBox)->computePerCharacterLayoutInformation(); + ASSERT_WITH_SECURITY_IMPLICATION(isSVGText()); + downcast<SVGRootInlineBox>(*lineBox).computePerCharacterLayoutInformation(); } -#endif // Compute our overflow now. lineBox->computeOverflow(lineBox->lineTop(), lineBox->lineBottom(), textBoxDataMap); -#if PLATFORM(MAC) - // Highlight acts as an overflow inflation. - if (style()->highlight() != nullAtom) - lineBox->addHighlightOverflow(); -#endif return lineBox; } -// Like LayoutState for layout(), LineLayoutState keeps track of global information -// during an entire linebox tree layout pass (aka layoutInlineChildren). -class LineLayoutState { -public: - LineLayoutState(bool fullLayout, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom, RenderFlowThread* flowThread) - : m_lastFloat(0) - , m_endLine(0) - , m_floatIndex(0) - , m_endLineLogicalTop(0) - , m_endLineMatched(false) - , m_checkForFloatsFromLastLine(false) - , m_isFullLayout(fullLayout) - , m_repaintLogicalTop(repaintLogicalTop) - , m_repaintLogicalBottom(repaintLogicalBottom) - , m_adjustedLogicalLineTop(0) - , m_usesRepaintBounds(false) - , m_flowThread(flowThread) - { } - - void markForFullLayout() { m_isFullLayout = true; } - bool isFullLayout() const { return m_isFullLayout; } - - bool usesRepaintBounds() const { return m_usesRepaintBounds; } - - void setRepaintRange(LayoutUnit logicalHeight) - { - m_usesRepaintBounds = true; - m_repaintLogicalTop = m_repaintLogicalBottom = logicalHeight; - } - - void updateRepaintRangeFromBox(RootInlineBox* box, LayoutUnit paginationDelta = 0) - { - m_usesRepaintBounds = true; - m_repaintLogicalTop = min(m_repaintLogicalTop, box->logicalTopVisualOverflow() + min<LayoutUnit>(paginationDelta, 0)); - m_repaintLogicalBottom = max(m_repaintLogicalBottom, box->logicalBottomVisualOverflow() + max<LayoutUnit>(paginationDelta, 0)); - } - - bool endLineMatched() const { return m_endLineMatched; } - void setEndLineMatched(bool endLineMatched) { m_endLineMatched = endLineMatched; } - - bool checkForFloatsFromLastLine() const { return m_checkForFloatsFromLastLine; } - void setCheckForFloatsFromLastLine(bool check) { m_checkForFloatsFromLastLine = check; } - - LineInfo& lineInfo() { return m_lineInfo; } - const LineInfo& lineInfo() const { return m_lineInfo; } - - LayoutUnit endLineLogicalTop() const { return m_endLineLogicalTop; } - void setEndLineLogicalTop(LayoutUnit logicalTop) { m_endLineLogicalTop = logicalTop; } - - RootInlineBox* endLine() const { return m_endLine; } - void setEndLine(RootInlineBox* line) { m_endLine = line; } - - RenderBlock::FloatingObject* lastFloat() const { return m_lastFloat; } - void setLastFloat(RenderBlock::FloatingObject* lastFloat) { m_lastFloat = lastFloat; } - - Vector<RenderBlock::FloatWithRect>& floats() { return m_floats; } - - unsigned floatIndex() const { return m_floatIndex; } - void setFloatIndex(unsigned floatIndex) { m_floatIndex = floatIndex; } - - LayoutUnit adjustedLogicalLineTop() const { return m_adjustedLogicalLineTop; } - void setAdjustedLogicalLineTop(LayoutUnit value) { m_adjustedLogicalLineTop = value; } - - RenderFlowThread* flowThread() const { return m_flowThread; } - void setFlowThread(RenderFlowThread* thread) { m_flowThread = thread; } - -private: - Vector<RenderBlock::FloatWithRect> m_floats; - RenderBlock::FloatingObject* m_lastFloat; - RootInlineBox* m_endLine; - LineInfo m_lineInfo; - unsigned m_floatIndex; - LayoutUnit m_endLineLogicalTop; - bool m_endLineMatched; - bool m_checkForFloatsFromLastLine; - - bool m_isFullLayout; - - // FIXME: Should this be a range object instead of two ints? - LayoutUnit& m_repaintLogicalTop; - LayoutUnit& m_repaintLogicalBottom; - - LayoutUnit m_adjustedLogicalLineTop; - - bool m_usesRepaintBounds; - - RenderFlowThread* m_flowThread; -}; - -static void deleteLineRange(LineLayoutState& layoutState, RenderArena* arena, RootInlineBox* startLine, RootInlineBox* stopLine = 0) +static void deleteLineRange(LineLayoutState& layoutState, RootInlineBox* startLine, RootInlineBox* stopLine = 0) { RootInlineBox* boxToDelete = startLine; while (boxToDelete && boxToDelete != stopLine) { layoutState.updateRepaintRangeFromBox(boxToDelete); - // Note: deleteLineRange(renderArena(), firstRootBox()) is not identical to deleteLineBoxTree(). + // Note: deleteLineRange(firstRootBox()) is not identical to deleteLineBoxTree(). // deleteLineBoxTree uses nextLineBox() instead of nextRootBox() when traversing. RootInlineBox* next = boxToDelete->nextRootBox(); - boxToDelete->deleteLine(arena); + boxToDelete->deleteLine(); boxToDelete = next; } } -void RenderBlock::layoutRunsAndFloats(LineLayoutState& layoutState, bool hasInlineChild) +void RenderBlockFlow::layoutRunsAndFloats(LineLayoutState& layoutState, bool hasInlineChild) { // We want to skip ahead to the first dirty line InlineBidiResolver resolver; RootInlineBox* startLine = determineStartPosition(layoutState, resolver); + + if (startLine) + marginCollapseLinesFromStart(layoutState, startLine); unsigned consecutiveHyphenatedLines = 0; if (startLine) { @@ -1568,19 +1204,18 @@ void RenderBlock::layoutRunsAndFloats(LineLayoutState& layoutState, bool hasInli // determineStartPosition can change the fullLayout flag we have to do this here. Failure to call // determineStartPosition first will break fast/repaint/line-flow-with-floats-9.html. if (layoutState.isFullLayout() && hasInlineChild && !selfNeedsLayout()) { - setNeedsLayout(true, MarkOnlyThis); // Mark as needing a full layout to force us to repaint. - RenderView* v = view(); - if (v && !v->doingFullRepaint() && hasLayer()) { + setNeedsLayout(MarkOnlyThis); // Mark as needing a full layout to force us to repaint. + if (!view().doingFullRepaint() && hasLayer()) { // Because we waited until we were already inside layout to discover // that the block really needed a full layout, we missed our chance to repaint the layer // before layout started. Luckily the layer has cached the repaint rect for its original // position and size, and so we can use that to make a repaint happen now. - repaintUsingContainer(containerForRepaint(), pixelSnappedIntRect(layer()->repaintRect())); + repaintUsingContainer(containerForRepaint(), layer()->repaintRect()); } } if (containsFloats()) - layoutState.setLastFloat(m_floatingObjects->set().last()); + layoutState.setLastFloat(m_floatingObjects->set().last().get()); // We also find the first clean line and extract these lines. We will add them back // if we determine that we're able to synchronize after handling all our dirty lines. @@ -1592,7 +1227,7 @@ void RenderBlock::layoutRunsAndFloats(LineLayoutState& layoutState, bool hasInli if (startLine) { if (!layoutState.usesRepaintBounds()) layoutState.setRepaintRange(logicalHeight()); - deleteLineRange(layoutState, renderArena(), startLine); + deleteLineRange(layoutState, startLine); } if (!layoutState.isFullLayout() && lastRootBox() && lastRootBox()->endsWithBreak()) { @@ -1600,13 +1235,13 @@ void RenderBlock::layoutRunsAndFloats(LineLayoutState& layoutState, bool hasInli // adjust the height accordingly. // A line break can be either the first or the last object on a line, depending on its direction. if (InlineBox* lastLeafChild = lastRootBox()->lastLeafChild()) { - RenderObject* lastObject = lastLeafChild->renderer(); + RenderObject* lastObject = &lastLeafChild->renderer(); if (!lastObject->isBR()) - lastObject = lastRootBox()->firstLeafChild()->renderer(); + lastObject = &lastRootBox()->firstLeafChild()->renderer(); if (lastObject->isBR()) { - EClear clear = lastObject->style()->clear(); + EClear clear = lastObject->style().clear(); if (clear != CNONE) - newLine(clear); + clearFloats(clear); } } } @@ -1616,18 +1251,8 @@ void RenderBlock::layoutRunsAndFloats(LineLayoutState& layoutState, bool hasInli repaintDirtyFloats(layoutState.floats()); } -RenderBlock::RenderTextInfo::RenderTextInfo() - : m_text(0) - , m_font(0) -{ -} - -RenderBlock::RenderTextInfo::~RenderTextInfo() -{ -} - // Before restarting the layout loop with a new logicalHeight, remove all floats that were added and reset the resolver. -inline const InlineIterator& RenderBlock::restartLayoutRunsAndFloatsInRange(LayoutUnit oldLogicalHeight, LayoutUnit newLogicalHeight, FloatingObject* lastFloatFromPreviousLine, InlineBidiResolver& resolver, const InlineIterator& oldEnd) +inline const InlineIterator& RenderBlockFlow::restartLayoutRunsAndFloatsInRange(LayoutUnit oldLogicalHeight, LayoutUnit newLogicalHeight, FloatingObject* lastFloatFromPreviousLine, InlineBidiResolver& resolver, const InlineIterator& oldEnd) { removeFloatingObjectsBelow(lastFloatFromPreviousLine, oldLogicalHeight); setLogicalHeight(newLogicalHeight); @@ -1635,189 +1260,17 @@ inline const InlineIterator& RenderBlock::restartLayoutRunsAndFloatsInRange(Layo return oldEnd; } -#if ENABLE(CSS_SHAPES) -static inline float firstPositiveWidth(const WordMeasurements& wordMeasurements) -{ - for (size_t i = 0; i < wordMeasurements.size(); ++i) { - if (wordMeasurements[i].width > 0) - return wordMeasurements[i].width; - } - return 0; -} - -static inline LayoutUnit adjustLogicalLineTop(ShapeInsideInfo* shapeInsideInfo, InlineIterator start, InlineIterator end, const WordMeasurements& wordMeasurements) -{ - if (!shapeInsideInfo || end != start) - return 0; - - float minWidth = firstPositiveWidth(wordMeasurements); - ASSERT(minWidth || wordMeasurements.isEmpty()); - if (minWidth > 0 && shapeInsideInfo->adjustLogicalLineTop(minWidth)) - return shapeInsideInfo->logicalLineTop(); - - return shapeInsideInfo->shapeLogicalBottom(); -} - -static inline void pushShapeContentOverflowBelowTheContentBox(RenderBlock* block, ShapeInsideInfo* shapeInsideInfo, LayoutUnit lineTop, LayoutUnit lineHeight) -{ - ASSERT(shapeInsideInfo); - - LayoutUnit logicalLineBottom = lineTop + lineHeight; - LayoutUnit shapeLogicalBottom = shapeInsideInfo->shapeLogicalBottom(); - LayoutUnit shapeContainingBlockHeight = shapeInsideInfo->shapeContainingBlockHeight(); - - bool isOverflowPositionedAlready = (shapeContainingBlockHeight - shapeInsideInfo->owner()->borderAndPaddingAfter() + lineHeight) <= lineTop; - - // If the last line overlaps with the shape, we don't need the segments anymore - if (lineTop < shapeLogicalBottom && shapeLogicalBottom < logicalLineBottom) - shapeInsideInfo->clearSegments(); - - if (logicalLineBottom <= shapeLogicalBottom || !shapeContainingBlockHeight || isOverflowPositionedAlready) - return; - - LayoutUnit newLogicalHeight = block->logicalHeight() + (shapeContainingBlockHeight - (lineTop + shapeInsideInfo->owner()->borderAndPaddingAfter())); - block->setLogicalHeight(newLogicalHeight); -} - -void RenderBlock::updateShapeAndSegmentsForCurrentLine(ShapeInsideInfo*& shapeInsideInfo, LayoutUnit& absoluteLogicalTop, LineLayoutState& layoutState) -{ - if (layoutState.flowThread()) - return updateShapeAndSegmentsForCurrentLineInFlowThread(shapeInsideInfo, layoutState); - - if (!shapeInsideInfo) - return; - - LayoutUnit lineTop = logicalHeight() + absoluteLogicalTop; - LayoutUnit lineHeight = this->lineHeight(layoutState.lineInfo().isFirstLine(), isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); - - // FIXME: Bug 95361: It is possible for a line to grow beyond lineHeight, in which case these segments may be incorrect. - shapeInsideInfo->computeSegmentsForLine(lineTop, lineHeight); - - pushShapeContentOverflowBelowTheContentBox(this, shapeInsideInfo, lineTop, lineHeight); -} - -void RenderBlock::updateShapeAndSegmentsForCurrentLineInFlowThread(ShapeInsideInfo*& shapeInsideInfo, LineLayoutState& layoutState) +void RenderBlockFlow::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, InlineBidiResolver& resolver, const InlineIterator& cleanLineStart, const BidiStatus& cleanLineBidiStatus, unsigned consecutiveHyphenatedLines) { - ASSERT(layoutState.flowThread()); - - LayoutUnit lineHeight = this->lineHeight(layoutState.lineInfo().isFirstLine(), isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); - - RenderRegion* currentRegion = regionAtBlockOffset(logicalHeight()); - if (!currentRegion) - return; - - shapeInsideInfo = currentRegion->shapeInsideInfo(); - - LayoutUnit logicalLineTopInFlowThread = logicalHeight() + offsetFromLogicalTopOfFirstPage(); - LayoutUnit logicalLineBottomInFlowThread = logicalLineTopInFlowThread + lineHeight; - LayoutUnit logicalRegionTopInFlowThread = currentRegion->logicalTopForFlowThreadContent(); - LayoutUnit logicalRegionBottomInFlowThread = logicalRegionTopInFlowThread + currentRegion->logicalHeight() - currentRegion->borderAndPaddingBefore() - currentRegion->borderAndPaddingAfter(); - - // We only want to deal regions with shapes, so we look up for the next region whether it has a shape - if (!shapeInsideInfo && !currentRegion->isLastRegion()) { - LayoutUnit deltaToNextRegion = logicalHeight() + logicalRegionBottomInFlowThread - logicalLineTopInFlowThread; - RenderRegion* lookupForNextRegion = regionAtBlockOffset(logicalHeight() + deltaToNextRegion); - if (!lookupForNextRegion->shapeInsideInfo()) - return; - } - - LayoutUnit shapeBottomInFlowThread = LayoutUnit::max(); - if (shapeInsideInfo) - shapeBottomInFlowThread = shapeInsideInfo->shapeLogicalBottom() + currentRegion->logicalTopForFlowThreadContent(); - - // If the line is between two shapes/regions we position the line to the top of the next shape/region - RenderRegion* nextRegion = regionAtBlockOffset(logicalHeight() + lineHeight); - if ((currentRegion != nextRegion && (logicalLineBottomInFlowThread > logicalRegionBottomInFlowThread)) || (!currentRegion->isLastRegion() && shapeBottomInFlowThread < logicalLineBottomInFlowThread)) { - LayoutUnit deltaToNextRegion = logicalRegionBottomInFlowThread - logicalLineTopInFlowThread; - nextRegion = regionAtBlockOffset(logicalHeight() + deltaToNextRegion); - - ASSERT(currentRegion != nextRegion); - - shapeInsideInfo = nextRegion->shapeInsideInfo(); - setLogicalHeight(logicalHeight() + deltaToNextRegion); - - currentRegion = nextRegion; - - logicalLineTopInFlowThread = logicalHeight() + offsetFromLogicalTopOfFirstPage(); - logicalLineBottomInFlowThread = logicalLineTopInFlowThread + lineHeight; - logicalRegionTopInFlowThread = currentRegion->logicalTopForFlowThreadContent(); - logicalRegionBottomInFlowThread = logicalRegionTopInFlowThread + currentRegion->logicalHeight() - currentRegion->borderAndPaddingBefore() - currentRegion->borderAndPaddingAfter(); - } - - if (!shapeInsideInfo) - return; - - // We position the first line to the top of the shape in the region or to the previously adjusted position in the shape - if (logicalLineBottomInFlowThread <= (logicalRegionTopInFlowThread + lineHeight) || (logicalLineTopInFlowThread - logicalRegionTopInFlowThread) < (layoutState.adjustedLogicalLineTop() - currentRegion->borderAndPaddingBefore())) { - LayoutUnit shapeTopOffset = layoutState.adjustedLogicalLineTop(); - if (!shapeTopOffset) - shapeTopOffset = shapeInsideInfo->shapeLogicalTop(); - - LayoutUnit shapePositionInFlowThread = currentRegion->logicalTopForFlowThreadContent() + shapeTopOffset; - LayoutUnit shapeTopLineTopDelta = shapePositionInFlowThread - logicalLineTopInFlowThread - currentRegion->borderAndPaddingBefore(); - - setLogicalHeight(logicalHeight() + shapeTopLineTopDelta); - logicalLineTopInFlowThread += shapeTopLineTopDelta; - layoutState.setAdjustedLogicalLineTop(0); - } - - LayoutUnit lineTop = logicalLineTopInFlowThread - currentRegion->logicalTopForFlowThreadContent() + currentRegion->borderAndPaddingBefore(); - shapeInsideInfo->computeSegmentsForLine(lineTop, lineHeight); - - if (currentRegion->isLastRegion()) - pushShapeContentOverflowBelowTheContentBox(this, shapeInsideInfo, lineTop, lineHeight); -} - -bool RenderBlock::adjustLogicalLineTopAndLogicalHeightIfNeeded(ShapeInsideInfo* shapeInsideInfo, LayoutUnit absoluteLogicalTop, LineLayoutState& layoutState, InlineBidiResolver& resolver, FloatingObject* lastFloatFromPreviousLine, InlineIterator& end, WordMeasurements& wordMeasurements) -{ - LayoutUnit adjustedLogicalLineTop = adjustLogicalLineTop(shapeInsideInfo, resolver.position(), end, wordMeasurements); - if (!adjustedLogicalLineTop) - return false; - - LayoutUnit newLogicalHeight = adjustedLogicalLineTop - absoluteLogicalTop; - - if (layoutState.flowThread()) { - layoutState.setAdjustedLogicalLineTop(adjustedLogicalLineTop); - newLogicalHeight = logicalHeight(); - } - - - end = restartLayoutRunsAndFloatsInRange(logicalHeight(), newLogicalHeight, lastFloatFromPreviousLine, resolver, end); - return true; -} -#endif - -void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, InlineBidiResolver& resolver, const InlineIterator& cleanLineStart, const BidiStatus& cleanLineBidiStatus, unsigned consecutiveHyphenatedLines) -{ - RenderStyle* styleToUse = style(); - bool paginated = view()->layoutState() && view()->layoutState()->isPaginated(); + const RenderStyle& styleToUse = style(); + bool paginated = view().layoutState() && view().layoutState()->isPaginated(); LineMidpointState& lineMidpointState = resolver.midpointState(); InlineIterator end = resolver.position(); bool checkForEndLineMatch = layoutState.endLine(); RenderTextInfo renderTextInfo; VerticalPositionCache verticalPositionCache; - LineBreaker lineBreaker(this); - -#if ENABLE(CSS_SHAPES) - LayoutUnit absoluteLogicalTop; - ShapeInsideInfo* shapeInsideInfo = layoutShapeInsideInfo(); - if (shapeInsideInfo) { - ASSERT(shapeInsideInfo->owner() == this || allowsShapeInsideInfoSharing()); - if (shapeInsideInfo != this->shapeInsideInfo()) { - // FIXME Bug 100284: If subsequent LayoutStates are pushed, we will have to add - // their offsets from the original shape-inside container. - absoluteLogicalTop = logicalTop(); - } - // Begin layout at the logical top of our shape inside. - if (logicalHeight() + absoluteLogicalTop < shapeInsideInfo->shapeLogicalTop()) { - LayoutUnit logicalHeight = shapeInsideInfo->shapeLogicalTop() - absoluteLogicalTop; - if (layoutState.flowThread()) - logicalHeight -= shapeInsideInfo->owner()->borderAndPaddingBefore(); - setLogicalHeight(logicalHeight); - } - } -#endif + LineBreaker lineBreaker(*this); while (!end.atEnd()) { // FIXME: Is this check necessary before the first iteration or can it be moved to the end? @@ -1825,6 +1278,7 @@ void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, Inlin layoutState.setEndLineMatched(matchedEndLine(layoutState, resolver, cleanLineStart, cleanLineBidiStatus)); if (layoutState.endLineMatched()) { resolver.setPosition(InlineIterator(resolver.position().root(), 0, 0), 0); + layoutState.marginInfo().clearMargin(); break; } } @@ -1836,14 +1290,12 @@ void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, Inlin const InlineIterator oldEnd = end; bool isNewUBAParagraph = layoutState.lineInfo().previousLineBrokeCleanly(); - FloatingObject* lastFloatFromPreviousLine = (containsFloats()) ? m_floatingObjects->set().last() : 0; + FloatingObject* lastFloatFromPreviousLine = (containsFloats()) ? m_floatingObjects->set().last().get() : nullptr; -#if ENABLE(CSS_SHAPES) - updateShapeAndSegmentsForCurrentLine(shapeInsideInfo, absoluteLogicalTop, layoutState); -#endif WordMeasurements wordMeasurements; - end = lineBreaker.nextLineBreak(resolver, layoutState.lineInfo(), renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); - renderTextInfo.m_lineBreakIterator.resetPriorContext(); + end = lineBreaker.nextLineBreak(resolver, layoutState.lineInfo(), layoutState, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); + cachePriorCharactersIfNeeded(renderTextInfo.lineBreakIterator); + renderTextInfo.lineBreakIterator.resetPriorContext(); if (resolver.position().atEnd()) { // FIXME: We shouldn't be creating any runs in nextLineBreak to begin with! // Once BidiRunList is separated from BidiResolver this will not be needed. @@ -1854,30 +1306,26 @@ void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, Inlin break; } -#if ENABLE(CSS_SHAPES) - if (adjustLogicalLineTopAndLogicalHeightIfNeeded(shapeInsideInfo, absoluteLogicalTop, layoutState, resolver, lastFloatFromPreviousLine, end, wordMeasurements)) - continue; -#endif ASSERT(end != resolver.position()); // This is a short-cut for empty lines. if (layoutState.lineInfo().isEmpty()) { if (lastRootBox()) - lastRootBox()->setLineBreakInfo(end.m_obj, end.m_pos, resolver.status()); + lastRootBox()->setLineBreakInfo(end.renderer(), end.offset(), resolver.status()); } else { - VisualDirectionOverride override = (styleToUse->rtlOrdering() == VisualOrder ? (styleToUse->direction() == LTR ? VisualLeftToRightOverride : VisualRightToLeftOverride) : NoVisualOverride); + VisualDirectionOverride override = (styleToUse.rtlOrdering() == VisualOrder ? (styleToUse.direction() == LTR ? VisualLeftToRightOverride : VisualRightToLeftOverride) : NoVisualOverride); - if (isNewUBAParagraph && styleToUse->unicodeBidi() == Plaintext && !resolver.context()->parent()) { - TextDirection direction = styleToUse->direction(); + if (isNewUBAParagraph && styleToUse.unicodeBidi() == Plaintext && !resolver.context()->parent()) { + TextDirection direction = styleToUse.direction(); determineDirectionality(direction, resolver.position()); - resolver.setStatus(BidiStatus(direction, isOverride(styleToUse->unicodeBidi()))); + resolver.setStatus(BidiStatus(direction, isOverride(styleToUse.unicodeBidi()))); } // FIXME: This ownership is reversed. We should own the BidiRunList and pass it to createBidiRunsForLine. BidiRunList<BidiRun>& bidiRuns = resolver.runs(); - constructBidiRunsForLine(this, resolver, bidiRuns, end, override, layoutState.lineInfo().previousLineBrokeCleanly()); + constructBidiRunsForSegment(resolver, bidiRuns, end, override, layoutState.lineInfo().previousLineBrokeCleanly()); ASSERT(resolver.position() == end); - BidiRun* trailingSpaceRun = !layoutState.lineInfo().previousLineBrokeCleanly() ? handleTrailingSpaces(bidiRuns, resolver.context()) : 0; + BidiRun* trailingSpaceRun = !layoutState.lineInfo().previousLineBrokeCleanly() ? handleTrailingSpaces(bidiRuns, resolver.context()) : nullptr; if (bidiRuns.runCount() && lineBreaker.lineWasHyphenated()) { bidiRuns.logicallyLastRun()->m_hasHyphen = true; @@ -1890,76 +1338,109 @@ void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, Inlin // inline flow boxes. LayoutUnit oldLogicalHeight = logicalHeight(); - RootInlineBox* lineBox = createLineBoxesFromBidiRuns(bidiRuns, end, layoutState.lineInfo(), verticalPositionCache, trailingSpaceRun, wordMeasurements); + RootInlineBox* lineBox = createLineBoxesFromBidiRuns(resolver.status().context->level(), bidiRuns, end, layoutState.lineInfo(), verticalPositionCache, trailingSpaceRun, wordMeasurements); bidiRuns.deleteRuns(); resolver.markCurrentRunEmpty(); // FIXME: This can probably be replaced by an ASSERT (or just removed). if (lineBox) { - lineBox->setLineBreakInfo(end.m_obj, end.m_pos, resolver.status()); + lineBox->setLineBreakInfo(end.renderer(), end.offset(), resolver.status()); if (layoutState.usesRepaintBounds()) layoutState.updateRepaintRangeFromBox(lineBox); + + LayoutUnit adjustment = 0; + bool overflowsRegion = false; + + // If our previous line was an anonymous block and we are not an anonymous block, + // simulate a margin collapse now so that we get the proper + // increased height. We also have to simulate a margin collapse to propagate margins + // through to the top of our block. + if (!lineBox->hasAnonymousInlineBlock()) { + RootInlineBox* prevRoot = lineBox->prevRootBox(); + if (prevRoot && prevRoot->hasAnonymousInlineBlock()) { + LayoutUnit currentLogicalHeight = logicalHeight(); + setLogicalHeight(oldLogicalHeight); + collapseMarginsWithChildInfo(nullptr, nullptr, layoutState.marginInfo()); + adjustment = logicalHeight() - oldLogicalHeight; + setLogicalHeight(currentLogicalHeight); + } + layoutState.marginInfo().setAtBeforeSideOfBlock(false); + } + if (paginated) + adjustLinePositionForPagination(lineBox, adjustment, overflowsRegion, layoutState.flowThread()); + if (adjustment) { + IndentTextOrNot shouldIndentText = layoutState.lineInfo().isFirstLine() ? IndentText : DoNotIndentText; + LayoutUnit oldLineWidth = availableLogicalWidthForLine(oldLogicalHeight, shouldIndentText); + lineBox->adjustBlockDirectionPosition(adjustment); + if (layoutState.usesRepaintBounds()) + layoutState.updateRepaintRangeFromBox(lineBox); + + if (availableLogicalWidthForLine(oldLogicalHeight + adjustment, shouldIndentText) != oldLineWidth) { + // We have to delete this line, remove all floats that got added, and let line layout re-run. + lineBox->deleteLine(); + end = restartLayoutRunsAndFloatsInRange(oldLogicalHeight, oldLogicalHeight + adjustment, lastFloatFromPreviousLine, resolver, oldEnd); + continue; + } + + setLogicalHeight(lineBox->lineBottomWithLeading()); + } + if (paginated) { - LayoutUnit adjustment = 0; - adjustLinePositionForPagination(lineBox, adjustment, layoutState.flowThread()); - if (adjustment) { - LayoutUnit oldLineWidth = availableLogicalWidthForLine(oldLogicalHeight, layoutState.lineInfo().isFirstLine()); - lineBox->adjustBlockDirectionPosition(adjustment); - if (layoutState.usesRepaintBounds()) - layoutState.updateRepaintRangeFromBox(lineBox); - - if (availableLogicalWidthForLine(oldLogicalHeight + adjustment, layoutState.lineInfo().isFirstLine()) != oldLineWidth) { - // We have to delete this line, remove all floats that got added, and let line layout re-run. - lineBox->deleteLine(renderArena()); - end = restartLayoutRunsAndFloatsInRange(oldLogicalHeight, oldLogicalHeight + adjustment, lastFloatFromPreviousLine, resolver, oldEnd); - continue; + if (RenderFlowThread* flowThread = flowThreadContainingBlock()) { + if (flowThread->isRenderNamedFlowThread() && overflowsRegion && hasNextPage(lineBox->lineTop())) { + // Limit the height of this block to the end of the current region because + // it is also fragmented into the next region. + LayoutUnit remainingLogicalHeight = pageRemainingLogicalHeightForOffset(logicalTop(), ExcludePageBoundary); + if (logicalHeight() > remainingLogicalHeight) + setLogicalHeight(remainingLogicalHeight); } - - setLogicalHeight(lineBox->lineBottomWithLeading()); } if (layoutState.flowThread()) - lineBox->setContainingRegion(regionAtBlockOffset(lineBox->lineTopWithLeading())); + updateRegionForLine(lineBox); } } } for (size_t i = 0; i < lineBreaker.positionedObjects().size(); ++i) - setStaticPositions(this, lineBreaker.positionedObjects()[i]); + setStaticPositions(*this, *lineBreaker.positionedObjects()[i], DoNotIndentText); if (!layoutState.lineInfo().isEmpty()) { layoutState.lineInfo().setFirstLine(false); - newLine(lineBreaker.clear()); + clearFloats(lineBreaker.clear()); } if (m_floatingObjects && lastRootBox()) { const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); - FloatingObjectSetIterator it = floatingObjectSet.begin(); - FloatingObjectSetIterator end = floatingObjectSet.end(); + auto it = floatingObjectSet.begin(); + auto end = floatingObjectSet.end(); if (layoutState.lastFloat()) { - FloatingObjectSetIterator lastFloatIterator = floatingObjectSet.find(layoutState.lastFloat()); + auto lastFloatIterator = floatingObjectSet.find<FloatingObject&, FloatingObjectHashTranslator>(*layoutState.lastFloat()); ASSERT(lastFloatIterator != end); ++lastFloatIterator; it = lastFloatIterator; } for (; it != end; ++it) { - FloatingObject* f = *it; + FloatingObject* f = it->get(); appendFloatingObjectToLastLine(f); - ASSERT(f->m_renderer == layoutState.floats()[layoutState.floatIndex()].object); + ASSERT(&f->renderer() == &layoutState.floats()[layoutState.floatIndex()].object); // If a float's geometry has changed, give up on syncing with clean lines. if (layoutState.floats()[layoutState.floatIndex()].rect != f->frameRect()) checkForEndLineMatch = false; layoutState.setFloatIndex(layoutState.floatIndex() + 1); } - layoutState.setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last() : 0); + layoutState.setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last().get() : nullptr); } lineMidpointState.reset(); resolver.setPosition(end, numberOfIsolateAncestors(end)); } - if (paginated && !style()->hasAutoWidows()) { + // In case we already adjusted the line positions during this layout to avoid widows + // then we need to ignore the possibility of having a new widows situation. + // Otherwise, we risk leaving empty containers which is against the block fragmentation principles. + if (paginated && !style().hasAutoWidows() && !didBreakAtLineToAvoidWidow()) { // Check the line boxes to make sure we didn't create unacceptable widows. // However, we'll prioritize orphans - so nothing we do here should create // a new orphan. @@ -1979,11 +1460,11 @@ void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, Inlin if (!lineBox || !lineBox->isFirstAfterPageBreak() || lineBox == firstLineInBlock) return; - if (numLinesHanging < style()->widows()) { + if (numLinesHanging < style().widows()) { // We have detected a widow. Now we need to work out how many // lines there are on the previous page, and how many we need // to steal. - int numLinesNeeded = style()->widows() - numLinesHanging; + int numLinesNeeded = style().widows() - numLinesHanging; RootInlineBox* currentFirstLineOfNewPage = lineBox; // Count the number of lines in the previous page. @@ -1999,58 +1480,73 @@ void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, Inlin // This means that setting widows implies we also care about orphans, but given // the specification says the initial orphan value is non-zero, this is ok. The // author is always free to set orphans explicitly as well. - int orphans = style()->hasAutoOrphans() ? style()->initialOrphans() : style()->orphans(); + int orphans = style().hasAutoOrphans() ? style().initialOrphans() : style().orphans(); int numLinesAvailable = numLinesInPreviousPage - orphans; if (numLinesAvailable <= 0) return; - int numLinesToTake = min(numLinesAvailable, numLinesNeeded); + int numLinesToTake = std::min(numLinesAvailable, numLinesNeeded); // Wind back from our first widowed line. lineBox = currentFirstLineOfNewPage; for (int i = 0; i < numLinesToTake; ++i) lineBox = lineBox->prevRootBox(); // We now want to break at this line. Remember for next layout and trigger relayout. - setBreakAtLineToAvoidWidow(lineBox); + setBreakAtLineToAvoidWidow(lineCount(lineBox)); markLinesDirtyInBlockRange(lastRootBox()->lineBottomWithLeading(), lineBox->lineBottomWithLeading(), lineBox); } } + clearDidBreakAtLineToAvoidWidow(); } -void RenderBlock::linkToEndLineIfNeeded(LineLayoutState& layoutState) +void RenderBlockFlow::reattachCleanLineFloats(RootInlineBox& cleanLine, LayoutUnit delta, bool isFirstCleanLine) { - if (layoutState.endLine()) { + auto* cleanLineFloats = cleanLine.floatsPtr(); + if (!cleanLineFloats) + return; + + for (auto* floatingBox : *cleanLineFloats) { + auto* floatingObject = insertFloatingObject(*floatingBox); + if (isFirstCleanLine && floatingObject->originatingLine()) { + // Float box does not belong to this line anymore. + ASSERT_WITH_SECURITY_IMPLICATION(cleanLine.prevRootBox() == floatingObject->originatingLine()); + cleanLine.removeFloat(*floatingBox); + continue; + } + ASSERT_WITH_SECURITY_IMPLICATION(!floatingObject->originatingLine()); + floatingObject->setOriginatingLine(&cleanLine); + setLogicalHeight(logicalTopForChild(*floatingBox) - marginBeforeForChild(*floatingBox) + delta); + positionNewFloats(); + } +} + +void RenderBlockFlow::linkToEndLineIfNeeded(LineLayoutState& layoutState) +{ + auto* firstCleanLine = layoutState.endLine(); + if (firstCleanLine) { if (layoutState.endLineMatched()) { - bool paginated = view()->layoutState() && view()->layoutState()->isPaginated(); + bool paginated = view().layoutState() && view().layoutState()->isPaginated(); // Attach all the remaining lines, and then adjust their y-positions as needed. LayoutUnit delta = logicalHeight() - layoutState.endLineLogicalTop(); - for (RootInlineBox* line = layoutState.endLine(); line; line = line->nextRootBox()) { + for (auto* line = firstCleanLine; line; line = line->nextRootBox()) { line->attachLine(); if (paginated) { delta -= line->paginationStrut(); - adjustLinePositionForPagination(line, delta, layoutState.flowThread()); + bool overflowsRegion; + adjustLinePositionForPagination(line, delta, overflowsRegion, layoutState.flowThread()); } if (delta) { layoutState.updateRepaintRangeFromBox(line, delta); line->adjustBlockDirectionPosition(delta); } if (layoutState.flowThread()) - line->setContainingRegion(regionAtBlockOffset(line->lineTopWithLeading())); - if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { - Vector<RenderBox*>::iterator end = cleanLineFloats->end(); - for (Vector<RenderBox*>::iterator f = cleanLineFloats->begin(); f != end; ++f) { - FloatingObject* floatingObject = insertFloatingObject(*f); - ASSERT(!floatingObject->m_originatingLine); - floatingObject->m_originatingLine = line; - setLogicalHeight(logicalTopForChild(*f) - marginBeforeForChild(*f) + delta); - positionNewFloats(); - } - } + updateRegionForLine(line); + reattachCleanLineFloats(*line, delta, line == firstCleanLine); } setLogicalHeight(lastRootBox()->lineBottomWithLeading()); } else { // Delete all the remaining lines. - deleteLineRange(layoutState, renderArena(), layoutState.endLine()); + deleteLineRange(layoutState, layoutState.endLine()); } } @@ -2061,8 +1557,9 @@ void RenderBlock::linkToEndLineIfNeeded(LineLayoutState& layoutState) if (layoutState.checkForFloatsFromLastLine()) { LayoutUnit bottomVisualOverflow = lastRootBox()->logicalBottomVisualOverflow(); LayoutUnit bottomLayoutOverflow = lastRootBox()->logicalBottomLayoutOverflow(); - TrailingFloatsRootInlineBox* trailingFloatsLineBox = new (renderArena()) TrailingFloatsRootInlineBox(this); - m_lineBoxes.appendLineBox(trailingFloatsLineBox); + auto newLineBox = std::make_unique<TrailingFloatsRootInlineBox>(*this); + auto trailingFloatsLineBox = newLineBox.get(); + m_lineBoxes.appendLineBox(WTFMove(newLineBox)); trailingFloatsLineBox->setConstructed(); GlyphOverflowAndFallbackFontsMap textBoxDataMap; VerticalPositionCache verticalPositionCache; @@ -2074,25 +1571,25 @@ void RenderBlock::linkToEndLineIfNeeded(LineLayoutState& layoutState) LayoutRect logicalVisualOverflow(0, blockLogicalHeight, 1, bottomVisualOverflow - blockLogicalHeight); trailingFloatsLineBox->setOverflowFromLogicalRects(logicalLayoutOverflow, logicalVisualOverflow, trailingFloatsLineBox->lineTop(), trailingFloatsLineBox->lineBottom()); if (layoutState.flowThread()) - trailingFloatsLineBox->setContainingRegion(regionAtBlockOffset(trailingFloatsLineBox->lineTopWithLeading())); + updateRegionForLine(trailingFloatsLineBox); } const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); - FloatingObjectSetIterator it = floatingObjectSet.begin(); - FloatingObjectSetIterator end = floatingObjectSet.end(); + auto it = floatingObjectSet.begin(); + auto end = floatingObjectSet.end(); if (layoutState.lastFloat()) { - FloatingObjectSetIterator lastFloatIterator = floatingObjectSet.find(layoutState.lastFloat()); + auto lastFloatIterator = floatingObjectSet.find<FloatingObject&, FloatingObjectHashTranslator>(*layoutState.lastFloat()); ASSERT(lastFloatIterator != end); ++lastFloatIterator; it = lastFloatIterator; } for (; it != end; ++it) - appendFloatingObjectToLastLine(*it); - layoutState.setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last() : 0); + appendFloatingObjectToLastLine(it->get()); + layoutState.setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last().get() : nullptr); } } -void RenderBlock::repaintDirtyFloats(Vector<FloatWithRect>& floats) +void RenderBlockFlow::repaintDirtyFloats(Vector<FloatWithRect>& floats) { size_t floatCount = floats.size(); // Floats that did not have layout did not repaint when we laid them out. They would have @@ -2100,31 +1597,33 @@ void RenderBlock::repaintDirtyFloats(Vector<FloatWithRect>& floats) // painted. for (size_t i = 0; i < floatCount; ++i) { if (!floats[i].everHadLayout) { - RenderBox* f = floats[i].object; - if (!f->x() && !f->y() && f->checkForRepaintDuringLayout()) - f->repaint(); + RenderBox& box = floats[i].object; + if (!box.x() && !box.y() && box.checkForRepaintDuringLayout()) + box.repaint(); } } } -void RenderBlock::layoutInlineChildren(bool relayoutChildren, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom) +void RenderBlockFlow::layoutLineBoxes(bool relayoutChildren, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom) { + ASSERT(!m_simpleLineLayout); + setLogicalHeight(borderAndPaddingBefore()); // Lay out our hypothetical grid line as though it occurs at the top of the block. - if (view()->layoutState() && view()->layoutState()->lineGrid() == this) + if (view().layoutState() && view().layoutState()->lineGrid() == this) layoutLineGridBox(); RenderFlowThread* flowThread = flowThreadContainingBlock(); - bool clearLinesForPagination = firstLineBox() && flowThread && !flowThread->hasRegions(); + bool clearLinesForPagination = firstRootBox() && flowThread && !flowThread->hasRegions(); // Figure out if we should clear out our line boxes. // FIXME: Handle resize eventually! - bool isFullLayout = !firstLineBox() || selfNeedsLayout() || relayoutChildren || clearLinesForPagination; - LineLayoutState layoutState(isFullLayout, repaintLogicalTop, repaintLogicalBottom, flowThread); + bool isFullLayout = !firstRootBox() || selfNeedsLayout() || relayoutChildren || clearLinesForPagination; + LineLayoutState layoutState(*this, isFullLayout, repaintLogicalTop, repaintLogicalBottom, flowThread); if (isFullLayout) - lineBoxes()->deleteLineBoxes(renderArena()); + lineBoxes().deleteLineBoxes(); // Text truncation kicks in in two cases: // 1) If your overflow isn't visible and your text-overflow-mode isn't clip. @@ -2132,8 +1631,8 @@ void RenderBlock::layoutInlineChildren(bool relayoutChildren, LayoutUnit& repain // FIXME: CSS3 says that descendants that are clipped must also know how to truncate. This is insanely // difficult to figure out in general (especially in the middle of doing layout), so we only handle the // simple case of an anonymous block truncating when it's parent is clipped. - bool hasTextOverflow = (style()->textOverflow() && hasOverflowClip()) - || (isAnonymousBlock() && parent() && parent()->isRenderBlock() && parent()->style()->textOverflow() && parent()->hasOverflowClip()); + bool hasTextOverflow = (style().textOverflow() && hasOverflowClip()) + || (isAnonymousBlock() && parent() && parent()->isRenderBlock() && parent()->style().textOverflow() && parent()->hasOverflowClip()); // Walk all the lines and delete our ellipsis line boxes if they exist. if (hasTextOverflow) @@ -2147,39 +1646,42 @@ void RenderBlock::layoutInlineChildren(bool relayoutChildren, LayoutUnit& repain // elements at the same time. bool hasInlineChild = false; Vector<RenderBox*> replacedChildren; - for (InlineWalker walker(this); !walker.atEnd(); walker.advance()) { - RenderObject* o = walker.current(); - if (!hasInlineChild && o->isInline()) + for (InlineWalker walker(*this); !walker.atEnd(); walker.advance()) { + RenderObject& o = *walker.current(); + + if (!hasInlineChild && o.isInline()) hasInlineChild = true; - if (o->isReplaced() || o->isFloating() || o->isOutOfFlowPositioned()) { - RenderBox* box = toRenderBox(o); + if (o.isReplaced() || o.isFloating() || o.isOutOfFlowPositioned()) { + RenderBox& box = downcast<RenderBox>(o); - if (relayoutChildren || box->hasRelativeDimensions()) - o->setChildNeedsLayout(true, MarkOnlyThis); + if (relayoutChildren || box.hasRelativeDimensions()) + box.setChildNeedsLayout(MarkOnlyThis); // If relayoutChildren is set and the child has percentage padding or an embedded content box, we also need to invalidate the childs pref widths. - if (relayoutChildren && box->needsPreferredWidthsRecalculation()) - o->setPreferredLogicalWidthsDirty(true, MarkOnlyThis); + if (relayoutChildren && box.needsPreferredWidthsRecalculation()) + box.setPreferredLogicalWidthsDirty(true, MarkOnlyThis); - if (o->isOutOfFlowPositioned()) - o->containingBlock()->insertPositionedObject(box); - else if (o->isFloating()) + if (box.isOutOfFlowPositioned()) + box.containingBlock()->insertPositionedObject(box); + else if (box.isFloating()) layoutState.floats().append(FloatWithRect(box)); - else if (isFullLayout || o->needsLayout()) { + else if (isFullLayout || box.needsLayout()) { // Replaced element. - box->dirtyLineBoxes(isFullLayout); - if (isFullLayout) - replacedChildren.append(box); - else - o->layoutIfNeeded(); + box.dirtyLineBoxes(isFullLayout); + if (!o.isAnonymousInlineBlock()) { + if (isFullLayout) + replacedChildren.append(&box); + else + box.layoutIfNeeded(); + } } - } else if (o->isText() || (o->isRenderInline() && !walker.atEndOfInline())) { - if (!o->isText()) - toRenderInline(o)->updateAlwaysCreateLineBoxes(layoutState.isFullLayout()); - if (layoutState.isFullLayout() || o->selfNeedsLayout()) + } else if (o.isTextOrLineBreak() || (is<RenderInline>(o) && !walker.atEndOfInline())) { + if (is<RenderInline>(o)) + downcast<RenderInline>(o).updateAlwaysCreateLineBoxes(layoutState.isFullLayout()); + if (layoutState.isFullLayout() || o.selfNeedsLayout()) dirtyLineBoxesForRenderer(o, layoutState.isFullLayout()); - o->setNeedsLayout(false); + o.clearNeedsLayout(); } } @@ -2192,17 +1694,25 @@ void RenderBlock::layoutInlineChildren(bool relayoutChildren, LayoutUnit& repain // Expand the last line to accommodate Ruby and emphasis marks. int lastLineAnnotationsAdjustment = 0; if (lastRootBox()) { - LayoutUnit lowestAllowedPosition = max(lastRootBox()->lineBottom(), logicalHeight() + paddingAfter()); - if (!style()->isFlippedLinesWritingMode()) + LayoutUnit lowestAllowedPosition = std::max(lastRootBox()->lineBottom(), logicalHeight() + paddingAfter()); + if (!style().isFlippedLinesWritingMode()) lastLineAnnotationsAdjustment = lastRootBox()->computeUnderAnnotationAdjustment(lowestAllowedPosition); else lastLineAnnotationsAdjustment = lastRootBox()->computeOverAnnotationAdjustment(lowestAllowedPosition); } + + // Now do the handling of the bottom of the block, adding in our bottom border/padding and + // determining the correct collapsed bottom margin information. This collapse is only necessary + // if our last child was an anonymous inline block that might need to propagate margin information out to + // us. + LayoutUnit beforeEdge = borderAndPaddingBefore(); + LayoutUnit afterEdge = borderAndPaddingAfter() + scrollbarLogicalHeight() + lastLineAnnotationsAdjustment; + if (lastRootBox() && lastRootBox()->hasAnonymousInlineBlock()) + handleAfterSideOfBlock(beforeEdge, afterEdge, layoutState.marginInfo()); + else + setLogicalHeight(logicalHeight() + afterEdge); - // Now add in the bottom border/padding. - setLogicalHeight(logicalHeight() + lastLineAnnotationsAdjustment + borderAndPaddingAfter() + scrollbarLogicalHeight()); - - if (!firstLineBox() && hasLineIfEmpty()) + if (!firstRootBox() && hasLineIfEmpty()) setLogicalHeight(logicalHeight() + lineHeight(true, isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes)); // See if we have any lines that spill out of our block. If we do, then we will possibly need to @@ -2211,28 +1721,33 @@ void RenderBlock::layoutInlineChildren(bool relayoutChildren, LayoutUnit& repain checkLinesForTextOverflow(); } -void RenderBlock::checkFloatsInCleanLine(RootInlineBox* line, Vector<FloatWithRect>& floats, size_t& floatIndex, bool& encounteredNewFloat, bool& dirtiedByFloat) +void RenderBlockFlow::checkFloatsInCleanLine(RootInlineBox* line, Vector<FloatWithRect>& floats, size_t& floatIndex, bool& encounteredNewFloat, bool& dirtiedByFloat) { Vector<RenderBox*>* cleanLineFloats = line->floatsPtr(); if (!cleanLineFloats) return; + + if (!floats.size()) { + encounteredNewFloat = true; + return; + } - Vector<RenderBox*>::iterator end = cleanLineFloats->end(); - for (Vector<RenderBox*>::iterator it = cleanLineFloats->begin(); it != end; ++it) { + for (auto it = cleanLineFloats->begin(), end = cleanLineFloats->end(); it != end; ++it) { RenderBox* floatingBox = *it; floatingBox->layoutIfNeeded(); - LayoutSize newSize(floatingBox->width() + floatingBox->marginWidth(), floatingBox->height() + floatingBox->marginHeight()); + LayoutSize newSize(floatingBox->width() + floatingBox->horizontalMarginExtent(), floatingBox->height() + floatingBox->verticalMarginExtent()); ASSERT_WITH_SECURITY_IMPLICATION(floatIndex < floats.size()); - if (floats[floatIndex].object != floatingBox) { + if (&floats[floatIndex].object != floatingBox) { encounteredNewFloat = true; return; } - - if (floats[floatIndex].rect.size() != newSize) { + + // We have to reset the cap-height alignment done by the first-letter floats when initial-letter is set, so just always treat first-letter floats + // as dirty. + if (floats[floatIndex].rect.size() != newSize || (floatingBox->style().styleType() == FIRST_LETTER && floatingBox->style().initialLetterDrop() > 0)) { LayoutUnit floatTop = isHorizontalWritingMode() ? floats[floatIndex].rect.y() : floats[floatIndex].rect.x(); - LayoutUnit floatHeight = isHorizontalWritingMode() ? max(floats[floatIndex].rect.height(), newSize.height()) - : max(floats[floatIndex].rect.width(), newSize.width()); - floatHeight = min(floatHeight, LayoutUnit::max() - floatTop); + LayoutUnit floatHeight = isHorizontalWritingMode() ? std::max(floats[floatIndex].rect.height(), newSize.height()) : std::max(floats[floatIndex].rect.width(), newSize.width()); + floatHeight = std::min(floatHeight, LayoutUnit::max() - floatTop); line->markDirty(); markLinesDirtyInBlockRange(line->lineBottomWithLeading(), floatTop + floatHeight, line); floats[floatIndex].rect.setSize(newSize); @@ -2242,7 +1757,7 @@ void RenderBlock::checkFloatsInCleanLine(RootInlineBox* line, Vector<FloatWithRe } } -RootInlineBox* RenderBlock::determineStartPosition(LineLayoutState& layoutState, InlineBidiResolver& resolver) +RootInlineBox* RenderBlockFlow::determineStartPosition(LineLayoutState& layoutState, InlineBidiResolver& resolver) { RootInlineBox* curr = 0; RootInlineBox* last = 0; @@ -2251,7 +1766,7 @@ RootInlineBox* RenderBlock::determineStartPosition(LineLayoutState& layoutState, bool dirtiedByFloat = false; if (!layoutState.isFullLayout()) { // Paginate all of the clean lines. - bool paginated = view()->layoutState() && view()->layoutState()->isPaginated(); + bool paginated = view().layoutState() && view().layoutState()->isPaginated(); LayoutUnit paginationDelta = 0; size_t floatIndex = 0; for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextRootBox()) { @@ -2261,7 +1776,8 @@ RootInlineBox* RenderBlock::determineStartPosition(LineLayoutState& layoutState, break; } paginationDelta -= curr->paginationStrut(); - adjustLinePositionForPagination(curr, paginationDelta, layoutState.flowThread()); + bool overflowsRegion; + adjustLinePositionForPagination(curr, paginationDelta, overflowsRegion, layoutState.flowThread()); if (paginationDelta) { if (containsFloats() || !layoutState.floats().isEmpty()) { // FIXME: Do better eventually. For now if we ever shift because of pagination and floats are present just go to a full layout. @@ -2273,7 +1789,7 @@ RootInlineBox* RenderBlock::determineStartPosition(LineLayoutState& layoutState, curr->adjustBlockDirectionPosition(paginationDelta); } if (layoutState.flowThread()) - curr->setContainingRegion(regionAtBlockOffset(curr->lineTopWithLeading())); + updateRegionForLine(curr); } // If a new float has been inserted before this line or before its last known float, just do a full layout. @@ -2291,27 +1807,22 @@ RootInlineBox* RenderBlock::determineStartPosition(LineLayoutState& layoutState, } if (layoutState.isFullLayout()) { - m_lineBoxes.deleteLineBoxTree(renderArena()); + m_lineBoxes.deleteLineBoxTree(); curr = 0; - ASSERT(!firstLineBox() && !lastLineBox()); + ASSERT(!firstRootBox() && !lastRootBox()); } else { if (curr) { // We have a dirty line. if (RootInlineBox* prevRootBox = curr->prevRootBox()) { // We have a previous line. - if (!dirtiedByFloat && (!prevRootBox->endsWithBreak() || !prevRootBox->lineBreakObj() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= toRenderText(prevRootBox->lineBreakObj())->textLength()))) + if (!dirtiedByFloat && !curr->hasAnonymousInlineBlock() && (!prevRootBox->endsWithBreak() || !prevRootBox->lineBreakObj() || (is<RenderText>(*prevRootBox->lineBreakObj()) && prevRootBox->lineBreakPos() >= downcast<RenderText>(*prevRootBox->lineBreakObj()).textLength()))) { // The previous line didn't break cleanly or broke at a newline // that has been deleted, so treat it as dirty too. curr = prevRootBox; + } } - } else { - // No dirty lines were found. - // If the last line didn't break cleanly, treat it as dirty. - if (lastRootBox() && !lastRootBox()->endsWithBreak()) - curr = lastRootBox(); } - // If we have no dirty lines, then last is just the last root box. last = curr ? curr->prevRootBox() : lastRootBox(); } @@ -2323,14 +1834,14 @@ RootInlineBox* RenderBlock::determineStartPosition(LineLayoutState& layoutState, RootInlineBox* line = firstRootBox(); while (line != curr) { if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { - Vector<RenderBox*>::iterator end = cleanLineFloats->end(); - for (Vector<RenderBox*>::iterator f = cleanLineFloats->begin(); f != end; ++f) { - FloatingObject* floatingObject = insertFloatingObject(*f); - ASSERT(!floatingObject->m_originatingLine); - floatingObject->m_originatingLine = line; - setLogicalHeight(logicalTopForChild(*f) - marginBeforeForChild(*f)); + for (auto it = cleanLineFloats->begin(), end = cleanLineFloats->end(); it != end; ++it) { + RenderBox* floatingBox = *it; + FloatingObject* floatingObject = insertFloatingObject(*floatingBox); + ASSERT_WITH_SECURITY_IMPLICATION(!floatingObject->originatingLine()); + floatingObject->setOriginatingLine(line); + setLogicalHeight(logicalTopForChild(*floatingBox) - marginBeforeForChild(*floatingBox)); positionNewFloats(); - ASSERT(layoutState.floats()[numCleanFloats].object == *f); + ASSERT(&layoutState.floats()[numCleanFloats].object == floatingBox); numCleanFloats++; } } @@ -2349,17 +1860,17 @@ RootInlineBox* RenderBlock::determineStartPosition(LineLayoutState& layoutState, resolver.setPosition(iter, numberOfIsolateAncestors(iter)); resolver.setStatus(last->lineBreakBidiStatus()); } else { - TextDirection direction = style()->direction(); - if (style()->unicodeBidi() == Plaintext) - determineDirectionality(direction, InlineIterator(this, bidiFirstSkippingEmptyInlines(this), 0)); - resolver.setStatus(BidiStatus(direction, isOverride(style()->unicodeBidi()))); - InlineIterator iter = InlineIterator(this, bidiFirstSkippingEmptyInlines(this, &resolver), 0); + TextDirection direction = style().direction(); + if (style().unicodeBidi() == Plaintext) + determineDirectionality(direction, InlineIterator(this, bidiFirstSkippingEmptyInlines(*this), 0)); + resolver.setStatus(BidiStatus(direction, isOverride(style().unicodeBidi()))); + InlineIterator iter = InlineIterator(this, bidiFirstSkippingEmptyInlines(*this, &resolver), 0); resolver.setPosition(iter, numberOfIsolateAncestors(iter)); } return curr; } -void RenderBlock::determineEndPosition(LineLayoutState& layoutState, RootInlineBox* startLine, InlineIterator& cleanLineStart, BidiStatus& cleanLineBidiStatus) +void RenderBlockFlow::determineEndPosition(LineLayoutState& layoutState, RootInlineBox* startLine, InlineIterator& cleanLineStart, BidiStatus& cleanLineBidiStatus) { ASSERT(!layoutState.endLine()); size_t floatIndex = layoutState.floatIndex(); @@ -2396,11 +1907,11 @@ void RenderBlock::determineEndPosition(LineLayoutState& layoutState, RootInlineB layoutState.setEndLine(last); } -bool RenderBlock::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutState) +bool RenderBlockFlow::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutState) { LayoutUnit lineDelta = logicalHeight() - layoutState.endLineLogicalTop(); - bool paginated = view()->layoutState() && view()->layoutState()->isPaginated(); + bool paginated = view().layoutState() && view().layoutState()->isPaginated(); if (paginated && layoutState.flowThread()) { // Check all lines from here to the end, and see if the hypothetical new position for the lines will result // in a different available line width. @@ -2409,8 +1920,9 @@ bool RenderBlock::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutState // This isn't the real move we're going to do, so don't update the line box's pagination // strut yet. LayoutUnit oldPaginationStrut = lineBox->paginationStrut(); + bool overflowsRegion; lineDelta -= oldPaginationStrut; - adjustLinePositionForPagination(lineBox, lineDelta, layoutState.flowThread()); + adjustLinePositionForPagination(lineBox, lineDelta, overflowsRegion, layoutState.flowThread()); lineBox->setPaginationStrut(oldPaginationStrut); } if (lineWidthForPaginatedLineChanged(lineBox, lineDelta, layoutState.flowThread())) @@ -2422,7 +1934,7 @@ bool RenderBlock::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutState return true; // See if any floats end in the range along which we want to shift the lines vertically. - LayoutUnit logicalTop = min(logicalHeight(), layoutState.endLineLogicalTop()); + LayoutUnit logicalTop = std::min(logicalHeight(), layoutState.endLineLogicalTop()); RootInlineBox* lastLine = layoutState.endLine(); while (RootInlineBox* nextLine = lastLine->nextRootBox()) @@ -2431,17 +1943,29 @@ bool RenderBlock::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutState LayoutUnit logicalBottom = lastLine->lineBottomWithLeading() + absoluteValue(lineDelta); const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); - FloatingObjectSetIterator end = floatingObjectSet.end(); - for (FloatingObjectSetIterator it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* f = *it; - if (logicalBottomForFloat(f) >= logicalTop && logicalBottomForFloat(f) < logicalBottom) + auto end = floatingObjectSet.end(); + for (auto it = floatingObjectSet.begin(); it != end; ++it) { + const auto& floatingObject = *it->get(); + if (logicalBottomForFloat(floatingObject) >= logicalTop && logicalBottomForFloat(floatingObject) < logicalBottom) return false; } return true; } -bool RenderBlock::matchedEndLine(LineLayoutState& layoutState, const InlineBidiResolver& resolver, const InlineIterator& endLineStart, const BidiStatus& endLineStatus) +bool RenderBlockFlow::lineWidthForPaginatedLineChanged(RootInlineBox* rootBox, LayoutUnit lineDelta, RenderFlowThread* flowThread) const +{ + if (!flowThread) + return false; + + RenderRegion* currentRegion = regionAtBlockOffset(rootBox->lineTopWithLeading() + lineDelta); + // Just bail if the region didn't change. + if (rootBox->containingRegion() == currentRegion) + return false; + return rootBox->paginatedLineWidth() != availableLogicalWidthForContent(currentRegion); +} + +bool RenderBlockFlow::matchedEndLine(LineLayoutState& layoutState, const InlineBidiResolver& resolver, const InlineIterator& endLineStart, const BidiStatus& endLineStatus) { if (resolver.position() == endLineStart) { if (resolver.status() != endLineStatus) @@ -2451,11 +1975,11 @@ bool RenderBlock::matchedEndLine(LineLayoutState& layoutState, const InlineBidiR // The first clean line doesn't match, but we can check a handful of following lines to try // to match back up. - static int numLines = 8; // The # of lines we're willing to match against. + static const int numLines = 8; // The # of lines we're willing to match against. RootInlineBox* originalEndLine = layoutState.endLine(); RootInlineBox* line = originalEndLine; for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) { - if (line->lineBreakObj() == resolver.position().m_obj && line->lineBreakPos() == resolver.position().m_pos) { + if (line->lineBreakObj() == resolver.position().renderer() && line->lineBreakPos() == resolver.position().offset() && !line->hasAnonymousInlineBlock()) { // We have a match. if (line->lineBreakBidiStatus() != resolver.status()) return false; // ...but the bidi state doesn't match. @@ -2469,7 +1993,7 @@ bool RenderBlock::matchedEndLine(LineLayoutState& layoutState, const InlineBidiR } // Now delete the lines that we failed to sync. - deleteLineRange(layoutState, renderArena(), originalEndLine, result); + deleteLineRange(layoutState, originalEndLine, result); return matched; } } @@ -2477,80 +2001,6 @@ bool RenderBlock::matchedEndLine(LineLayoutState& layoutState, const InlineBidiR return false; } -static inline bool skipNonBreakingSpace(const InlineIterator& it, const LineInfo& lineInfo) -{ - if (it.m_obj->style()->nbspMode() != SPACE || it.current() != noBreakSpace) - return false; - - // FIXME: This is bad. It makes nbsp inconsistent with space and won't work correctly - // with m_minWidth/m_maxWidth. - // Do not skip a non-breaking space if it is the first character - // on a line after a clean line break (or on the first line, since previousLineBrokeCleanly starts off - // |true|). - if (lineInfo.isEmpty() && lineInfo.previousLineBrokeCleanly()) - return false; - - return true; -} - -enum WhitespacePosition { LeadingWhitespace, TrailingWhitespace }; -static inline bool shouldCollapseWhiteSpace(const RenderStyle* style, const LineInfo& lineInfo, WhitespacePosition whitespacePosition) -{ - // CSS2 16.6.1 - // If a space (U+0020) at the beginning of a line has 'white-space' set to 'normal', 'nowrap', or 'pre-line', it is removed. - // If a space (U+0020) at the end of a line has 'white-space' set to 'normal', 'nowrap', or 'pre-line', it is also removed. - // If spaces (U+0020) or tabs (U+0009) at the end of a line have 'white-space' set to 'pre-wrap', UAs may visually collapse them. - return style->collapseWhiteSpace() - || (whitespacePosition == TrailingWhitespace && style->whiteSpace() == PRE_WRAP && (!lineInfo.isEmpty() || !lineInfo.previousLineBrokeCleanly())); -} - -static bool requiresLineBoxForContent(RenderInline* flow, const LineInfo& lineInfo) -{ - RenderObject* parent = flow->parent(); - if (flow->document()->inNoQuirksMode() - && (flow->style(lineInfo.isFirstLine())->lineHeight() != parent->style(lineInfo.isFirstLine())->lineHeight() - || flow->style()->verticalAlign() != parent->style()->verticalAlign() - || !parent->style()->font().fontMetrics().hasIdenticalAscentDescentAndLineGap(flow->style()->font().fontMetrics()))) - return true; - return false; -} - -static bool hasInlineDirectionBordersPaddingOrMargin(RenderInline* flow) -{ - // Where an empty inline is split across anonymous blocks we should only give lineboxes to the 'sides' of the - // inline that have borders, padding or margin. - bool shouldApplyStartBorderPaddingOrMargin = !flow->parent()->isAnonymousBlock() || !flow->isInlineElementContinuation(); - if (shouldApplyStartBorderPaddingOrMargin && (flow->borderStart() || flow->marginStart() || flow->paddingStart())) - return true; - - bool shouldApplyEndBorderPaddingOrMargin = !flow->parent()->isAnonymousBlock() || flow->isInlineElementContinuation() || !flow->inlineElementContinuation(); - return shouldApplyEndBorderPaddingOrMargin && (flow->borderEnd() || flow->marginEnd() || flow->paddingEnd()); -} - -static bool alwaysRequiresLineBox(RenderObject* flow) -{ - // FIXME: Right now, we only allow line boxes for inlines that are truly empty. - // We need to fix this, though, because at the very least, inlines containing only - // ignorable whitespace should should also have line boxes. - return isEmptyInline(flow) && hasInlineDirectionBordersPaddingOrMargin(toRenderInline(flow)); -} - -static bool requiresLineBox(const InlineIterator& it, const LineInfo& lineInfo = LineInfo(), WhitespacePosition whitespacePosition = LeadingWhitespace) -{ - if (it.m_obj->isFloatingOrOutOfFlowPositioned()) - return false; - - if (it.m_obj->isRenderInline() && !alwaysRequiresLineBox(it.m_obj) && !requiresLineBoxForContent(toRenderInline(it.m_obj), lineInfo)) - return false; - - if (!shouldCollapseWhiteSpace(it.m_obj->style(), lineInfo, whitespacePosition) || it.m_obj->isBR()) - return true; - - UChar current = it.current(); - bool notJustWhitespace = current != ' ' && current != '\t' && current != softHyphen && (current != '\n' || it.m_obj->preservesNewline()) && !skipNonBreakingSpace(it, lineInfo); - return notJustWhitespace || isEmptyInline(it.m_obj); -} - bool RenderBlock::generatesLineBoxesForInlineChild(RenderObject* inlineObj) { ASSERT(inlineObj->parent() == this); @@ -2563,1059 +2013,90 @@ bool RenderBlock::generatesLineBoxesForInlineChild(RenderObject* inlineObj) return !it.atEnd(); } -// FIXME: The entire concept of the skipTrailingWhitespace function is flawed, since we really need to be building -// line boxes even for containers that may ultimately collapse away. Otherwise we'll never get positioned -// elements quite right. In other words, we need to build this function's work into the normal line -// object iteration process. -// NB. this function will insert any floating elements that would otherwise -// be skipped but it will not position them. -void RenderBlock::LineBreaker::skipTrailingWhitespace(InlineIterator& iterator, const LineInfo& lineInfo) -{ - while (!iterator.atEnd() && !requiresLineBox(iterator, lineInfo, TrailingWhitespace)) { - RenderObject* object = iterator.m_obj; - if (object->isOutOfFlowPositioned()) - setStaticPositions(m_block, toRenderBox(object)); - else if (object->isFloating()) - m_block->insertFloatingObject(toRenderBox(object)); - iterator.increment(); - } -} - -void RenderBlock::LineBreaker::skipLeadingWhitespace(InlineBidiResolver& resolver, LineInfo& lineInfo, - FloatingObject* lastFloatFromPreviousLine, LineWidth& width) +void RenderBlockFlow::addOverflowFromInlineChildren() { - while (!resolver.position().atEnd() && !requiresLineBox(resolver.position(), lineInfo, LeadingWhitespace)) { - RenderObject* object = resolver.position().m_obj; - if (object->isOutOfFlowPositioned()) { - setStaticPositions(m_block, toRenderBox(object)); - if (object->style()->isOriginalDisplayInlineType()) { - resolver.runs().addRun(createRun(0, 1, object, resolver)); - lineInfo.incrementRunsFromLeadingWhitespace(); - } - } else if (object->isFloating()) { - // The top margin edge of a self-collapsing block that clears a float intrudes up into it by the height of the margin, - // so in order to place this first child float at the top content edge of the self-collapsing block add the margin back in before placement. - LayoutUnit marginOffset = (!object->previousSibling() && m_block->isSelfCollapsingBlock() && m_block->style()->clear() && m_block->getClearDelta(m_block, LayoutUnit())) ? m_block->collapsedMarginBeforeForChild(m_block) : LayoutUnit(); - LayoutUnit oldLogicalHeight = m_block->logicalHeight(); - m_block->setLogicalHeight(oldLogicalHeight + marginOffset); - m_block->positionNewFloatOnLine(m_block->insertFloatingObject(toRenderBox(object)), lastFloatFromPreviousLine, lineInfo, width); - m_block->setLogicalHeight(oldLogicalHeight); - } else if (object->isText() && object->style()->hasTextCombine() && object->isCombineText() && !toRenderCombineText(object)->isCombined()) { - toRenderCombineText(object)->combineText(); - if (toRenderCombineText(object)->isCombined()) - continue; - } - resolver.increment(); - } - resolver.commitExplicitEmbedding(); -} - -// This is currently just used for list markers and inline flows that have line boxes. Neither should -// have an effect on whitespace at the start of the line. -static bool shouldSkipWhitespaceAfterStartObject(RenderBlock* block, RenderObject* o, LineMidpointState& lineMidpointState) -{ - RenderObject* next = bidiNextSkippingEmptyInlines(block, o); - while (next && next->isFloatingOrOutOfFlowPositioned()) - next = bidiNextSkippingEmptyInlines(block, next); - - if (next && !next->isBR() && next->isText() && toRenderText(next)->textLength() > 0) { - RenderText* nextText = toRenderText(next); - UChar nextChar = nextText->characterAt(0); - if (nextText->style()->isCollapsibleWhiteSpace(nextChar)) { - startIgnoringSpaces(lineMidpointState, InlineIterator(0, o, 0)); - return true; - } - } - - return false; -} - -static ALWAYS_INLINE float textWidth(RenderText* text, unsigned from, unsigned len, const Font& font, float xPos, bool isFixedPitch, bool collapseWhiteSpace, HashSet<const SimpleFontData*>& fallbackFonts, TextLayout* layout = 0) -{ - GlyphOverflow glyphOverflow; - if (isFixedPitch || (!from && len == text->textLength()) || text->style()->hasTextCombine()) - return text->width(from, len, font, xPos, &fallbackFonts, &glyphOverflow); - - if (layout) - return Font::width(*layout, from, len, &fallbackFonts); - - TextRun run = RenderBlock::constructTextRun(text, font, text, from, len, text->style()); - run.setCharactersLength(text->textLength() - from); - ASSERT(run.charactersLength() >= run.length()); - - run.setCharacterScanForCodePath(!text->canUseSimpleFontCodePath()); - run.setTabSize(!collapseWhiteSpace, text->style()->tabSize()); - run.setXPos(xPos); - return font.width(run, &fallbackFonts, &glyphOverflow); -} - -static void tryHyphenating(RenderText* text, const Font& font, const AtomicString& localeIdentifier, unsigned consecutiveHyphenatedLines, int consecutiveHyphenatedLinesLimit, int minimumPrefixLimit, int minimumSuffixLimit, unsigned lastSpace, unsigned pos, float xPos, int availableWidth, bool isFixedPitch, bool collapseWhiteSpace, int lastSpaceWordSpacing, InlineIterator& lineBreak, int nextBreakable, bool& hyphenated) -{ - // Map 'hyphenate-limit-{before,after}: auto;' to 2. - unsigned minimumPrefixLength; - unsigned minimumSuffixLength; - - if (minimumPrefixLimit < 0) - minimumPrefixLength = 2; - else - minimumPrefixLength = static_cast<unsigned>(minimumPrefixLimit); - - if (minimumSuffixLimit < 0) - minimumSuffixLength = 2; - else - minimumSuffixLength = static_cast<unsigned>(minimumSuffixLimit); - - if (pos - lastSpace <= minimumSuffixLength) - return; - - if (consecutiveHyphenatedLinesLimit >= 0 && consecutiveHyphenatedLines >= static_cast<unsigned>(consecutiveHyphenatedLinesLimit)) - return; - - int hyphenWidth = measureHyphenWidth(text, font); - - float maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing; - // If the maximum width available for the prefix before the hyphen is small, then it is very unlikely - // that an hyphenation opportunity exists, so do not bother to look for it. - if (maxPrefixWidth <= font.pixelSize() * 5 / 4) - return; - - TextRun run = RenderBlock::constructTextRun(text, font, text, lastSpace, pos - lastSpace, text->style()); - run.setCharactersLength(text->textLength() - lastSpace); - ASSERT(run.charactersLength() >= run.length()); - - run.setTabSize(!collapseWhiteSpace, text->style()->tabSize()); - run.setXPos(xPos + lastSpaceWordSpacing); - - unsigned prefixLength = font.offsetForPosition(run, maxPrefixWidth, false); - if (prefixLength < minimumPrefixLength) - return; - - prefixLength = lastHyphenLocation(text->characters() + lastSpace, pos - lastSpace, min(prefixLength, pos - lastSpace - minimumSuffixLength) + 1, localeIdentifier); - if (!prefixLength || prefixLength < minimumPrefixLength) - return; - - // When lastSapce is a space, which it always is except sometimes at the beginning of a line or after collapsed - // space, it should not count towards hyphenate-limit-before. - if (prefixLength == minimumPrefixLength) { - UChar characterAtLastSpace = text->characterAt(lastSpace); - if (characterAtLastSpace == ' ' || characterAtLastSpace == '\n' || characterAtLastSpace == '\t' || characterAtLastSpace == noBreakSpace) - return; - } - - ASSERT(pos - lastSpace - prefixLength >= minimumSuffixLength); - -#if !ASSERT_DISABLED - HashSet<const SimpleFontData*> fallbackFonts; - float prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace, fallbackFonts) + lastSpaceWordSpacing; - ASSERT(xPos + prefixWidth <= availableWidth); -#else - UNUSED_PARAM(isFixedPitch); -#endif - - lineBreak.moveTo(text, lastSpace + prefixLength, nextBreakable); - hyphenated = true; -} - -class TrailingObjects { -public: - TrailingObjects(); - void setTrailingWhitespace(RenderText*); - void clear(); - void appendBoxIfNeeded(RenderBoxModelObject*); - - enum CollapseFirstSpaceOrNot { DoNotCollapseFirstSpace, CollapseFirstSpace }; - - void updateMidpointsForTrailingBoxes(LineMidpointState&, const InlineIterator& lBreak, CollapseFirstSpaceOrNot); - -private: - RenderText* m_whitespace; - Vector<RenderBoxModelObject*, 4> m_boxes; -}; - -TrailingObjects::TrailingObjects() - : m_whitespace(0) -{ -} - -inline void TrailingObjects::setTrailingWhitespace(RenderText* whitespace) -{ - ASSERT(whitespace); - m_whitespace = whitespace; -} - -inline void TrailingObjects::clear() -{ - m_whitespace = 0; - m_boxes.shrink(0); // Use shrink(0) instead of clear() to retain our capacity. -} - -inline void TrailingObjects::appendBoxIfNeeded(RenderBoxModelObject* box) -{ - if (m_whitespace) - m_boxes.append(box); -} - -void TrailingObjects::updateMidpointsForTrailingBoxes(LineMidpointState& lineMidpointState, const InlineIterator& lBreak, CollapseFirstSpaceOrNot collapseFirstSpace) -{ - if (!m_whitespace) + if (auto layout = simpleLineLayout()) { + ASSERT(!hasOverflowClip()); + SimpleLineLayout::collectFlowOverflow(*this, *layout); return; - - // This object is either going to be part of the last midpoint, or it is going to be the actual endpoint. - // In both cases we just decrease our pos by 1 level to exclude the space, allowing it to - in effect - collapse into the newline. - if (lineMidpointState.numMidpoints % 2) { - // Find the trailing space object's midpoint. - int trailingSpaceMidpoint = lineMidpointState.numMidpoints - 1; - for ( ; trailingSpaceMidpoint > 0 && lineMidpointState.midpoints[trailingSpaceMidpoint].m_obj != m_whitespace; --trailingSpaceMidpoint) { } - ASSERT(trailingSpaceMidpoint >= 0); - if (collapseFirstSpace == CollapseFirstSpace) - lineMidpointState.midpoints[trailingSpaceMidpoint].m_pos--; - - // Now make sure every single trailingPositionedBox following the trailingSpaceMidpoint properly stops and starts - // ignoring spaces. - size_t currentMidpoint = trailingSpaceMidpoint + 1; - for (size_t i = 0; i < m_boxes.size(); ++i) { - if (currentMidpoint >= lineMidpointState.numMidpoints) { - // We don't have a midpoint for this box yet. - ensureLineBoxInsideIgnoredSpaces(lineMidpointState, m_boxes[i]); - } else { - ASSERT(lineMidpointState.midpoints[currentMidpoint].m_obj == m_boxes[i]); - ASSERT(lineMidpointState.midpoints[currentMidpoint + 1].m_obj == m_boxes[i]); - } - currentMidpoint += 2; - } - } else if (!lBreak.m_obj) { - ASSERT(m_whitespace->isText()); - ASSERT(collapseFirstSpace == CollapseFirstSpace); - // Add a new end midpoint that stops right at the very end. - unsigned length = m_whitespace->textLength(); - unsigned pos = length >= 2 ? length - 2 : UINT_MAX; - InlineIterator endMid(0, m_whitespace, pos); - startIgnoringSpaces(lineMidpointState, endMid); - for (size_t i = 0; i < m_boxes.size(); ++i) { - ensureLineBoxInsideIgnoredSpaces(lineMidpointState, m_boxes[i]); - } - } -} - -void RenderBlock::LineBreaker::reset() -{ - m_positionedObjects.clear(); - m_hyphenated = false; - m_clear = CNONE; -} - -InlineIterator RenderBlock::LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo& lineInfo, RenderTextInfo& renderTextInfo, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements& wordMeasurements) -{ -#if !ENABLE(CSS_SHAPES) - return nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); -#else - ShapeInsideInfo* shapeInsideInfo = m_block->layoutShapeInsideInfo(); - - if (!shapeInsideInfo || !shapeInsideInfo->lineOverlapsShapeBounds()) - return nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); - - InlineIterator end = resolver.position(); - InlineIterator oldEnd = end; - - if (!shapeInsideInfo->hasSegments()) { - end = nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); - resolver.setPositionIgnoringNestedIsolates(oldEnd); - return oldEnd; - } - - const SegmentList& segments = shapeInsideInfo->segments(); - SegmentRangeList& segmentRanges = shapeInsideInfo->segmentRanges(); - - for (unsigned i = 0; i < segments.size() && !end.atEnd(); i++) { - InlineIterator segmentStart = resolver.position(); - end = nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); - - ASSERT(segmentRanges.size() == i); - if (resolver.position().atEnd()) { - segmentRanges.append(LineSegmentRange(segmentStart, end)); - break; - } - if (resolver.position() == end) { - // Nothing fit this segment - end = segmentStart; - segmentRanges.append(LineSegmentRange(segmentStart, segmentStart)); - resolver.setPositionIgnoringNestedIsolates(segmentStart); - } else { - // Note that resolver.position is already skipping some of the white space at the beginning of the line, - // so that's why segmentStart might be different than resolver.position(). - LineSegmentRange range(resolver.position(), end); - segmentRanges.append(range); - resolver.setPosition(end, numberOfIsolateAncestors(end)); - - if (lineInfo.previousLineBrokeCleanly()) { - // If we hit a new line break, just stop adding anything to this line. - break; - } - } - } - resolver.setPositionIgnoringNestedIsolates(oldEnd); - return end; -#endif -} - -static inline bool iteratorIsBeyondEndOfRenderCombineText(const InlineIterator& iter, RenderCombineText* renderer) -{ - return iter.m_obj == renderer && iter.m_pos >= renderer->textLength(); -} - -static inline void commitLineBreakAtCurrentWidth(LineWidth& width, InlineIterator& lBreak, RenderObject* object, unsigned offset = 0, int nextBreak = -1) -{ - width.commit(); - lBreak.moveTo(object, offset, nextBreak); -} - -static bool textBeginsWithBreakablePosition(RenderObject* next) -{ - ASSERT(next->isText()); - RenderText* nextText = toRenderText(next); - if (nextText->isWordBreak()) - return true; - if (!nextText->textLength()) - return false; - UChar c = nextText->characterAt(0); - return c == ' ' || c == '\t' || (c == '\n' && !nextText->preservesNewline()); -} - -static bool canBreakAtThisPosition(bool autoWrap, LineWidth& width, InlineIterator& lBreak, RenderObject* next, const InlineIterator& current, EWhiteSpace currWS, bool currentCharacterIsSpace, bool autoWrapWasEverTrueOnLine) -{ - // If we are no-wrap and have found a line-breaking opportunity already then we should take it. - if (width.committedWidth() && !width.fitsOnLine(currentCharacterIsSpace) && currWS == NOWRAP) - return true; - - // Avoid breaking before empty inlines. - if (next && isEmptyInline(next)) - return false; - - // Return early if we autowrap and the current character is a space as we will always want to break at such a position. - if (autoWrap && currentCharacterIsSpace) - return true; - - bool nextIsText = (next && (current.m_obj->isText() || isEmptyInline(current.m_obj)) && next->isText() && !next->isBR() && (autoWrap || next->style()->autoWrap())); - if (!nextIsText) - return autoWrap; - - bool canBreakHere = !currentCharacterIsSpace && textBeginsWithBreakablePosition(next); - - // See if attempting to fit below floats creates more available width on the line. - if (!width.fitsOnLine() && !width.committedWidth()) - width.fitBelowFloats(); - - bool canPlaceOnLine = width.fitsOnLine() || !autoWrapWasEverTrueOnLine; - - if (canPlaceOnLine && canBreakHere) - commitLineBreakAtCurrentWidth(width, lBreak, next); - - return canBreakHere; -} - -InlineIterator RenderBlock::LineBreaker::nextSegmentBreak(InlineBidiResolver& resolver, LineInfo& lineInfo, RenderTextInfo& renderTextInfo, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements& wordMeasurements) -{ - reset(); - - ASSERT(resolver.position().root() == m_block); - - bool appliedStartWidth = resolver.position().m_pos > 0; - bool includeEndWidth = true; - LineMidpointState& lineMidpointState = resolver.midpointState(); - - LineWidth width(m_block, lineInfo.isFirstLine(), requiresIndent(lineInfo.isFirstLine(), lineInfo.previousLineBrokeCleanly(), m_block->style())); - - skipLeadingWhitespace(resolver, lineInfo, lastFloatFromPreviousLine, width); - - if (resolver.position().atEnd()) - return resolver.position(); - - // This variable is used only if whitespace isn't set to PRE, and it tells us whether - // or not we are currently ignoring whitespace. - bool ignoringSpaces = false; - InlineIterator ignoreStart; - - // This variable tracks whether the very last character we saw was a space. We use - // this to detect when we encounter a second space so we know we have to terminate - // a run. - bool currentCharacterIsSpace = false; - bool currentCharacterIsWS = false; - TrailingObjects trailingObjects; - - InlineIterator lBreak = resolver.position(); - - // FIXME: It is error-prone to split the position object out like this. - // Teach this code to work with objects instead of this split tuple. - InlineIterator current = resolver.position(); - RenderObject* last = current.m_obj; - bool atStart = true; - - bool startingNewParagraph = lineInfo.previousLineBrokeCleanly(); - lineInfo.setPreviousLineBrokeCleanly(false); - - bool autoWrapWasEverTrueOnLine = false; - bool floatsFitOnLine = true; - - // Firefox and Opera will allow a table cell to grow to fit an image inside it under - // very specific circumstances (in order to match common WinIE renderings). - // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.) - RenderStyle* blockStyle = m_block->style(); - bool allowImagesToBreak = !m_block->document()->inQuirksMode() || !m_block->isTableCell() || !blockStyle->logicalWidth().isIntrinsicOrAuto(); - - EWhiteSpace currWS = blockStyle->whiteSpace(); - EWhiteSpace lastWS = currWS; - while (current.m_obj) { - RenderStyle* currentStyle = current.m_obj->style(); - RenderObject* next = bidiNextSkippingEmptyInlines(m_block, current.m_obj); - if (next && next->parent() && !next->parent()->isDescendantOf(current.m_obj->parent())) - includeEndWidth = true; - - currWS = current.m_obj->isReplaced() ? current.m_obj->parent()->style()->whiteSpace() : currentStyle->whiteSpace(); - lastWS = last->isReplaced() ? last->parent()->style()->whiteSpace() : last->style()->whiteSpace(); - - bool autoWrap = RenderStyle::autoWrap(currWS); - autoWrapWasEverTrueOnLine = autoWrapWasEverTrueOnLine || autoWrap; - -#if ENABLE(SVG) - bool preserveNewline = current.m_obj->isSVGInlineText() ? false : RenderStyle::preserveNewline(currWS); -#else - bool preserveNewline = RenderStyle::preserveNewline(currWS); -#endif - - bool collapseWhiteSpace = RenderStyle::collapseWhiteSpace(currWS); - - if (current.m_obj->isBR()) { - if (width.fitsOnLine()) { - lBreak.moveToStartOf(current.m_obj); - lBreak.increment(); - - // A <br> always breaks a line, so don't let the line be collapsed - // away. Also, the space at the end of a line with a <br> does not - // get collapsed away. It only does this if the previous line broke - // cleanly. Otherwise the <br> has no effect on whether the line is - // empty or not. - if (startingNewParagraph) - lineInfo.setEmpty(false, m_block, &width); - trailingObjects.clear(); - lineInfo.setPreviousLineBrokeCleanly(true); - - // A <br> with clearance always needs a linebox in case the lines below it get dirtied later and - // need to check for floats to clear - so if we're ignoring spaces, stop ignoring them and add a - // run for this object. - if (ignoringSpaces && currentStyle->clear() != CNONE) - ensureLineBoxInsideIgnoredSpaces(lineMidpointState, current.m_obj); - - if (!lineInfo.isEmpty()) - m_clear = currentStyle->clear(); - } - goto end; - } - - if (current.m_obj->isOutOfFlowPositioned()) { - // If our original display wasn't an inline type, then we can - // go ahead and determine our static inline position now. - RenderBox* box = toRenderBox(current.m_obj); - bool isInlineType = box->style()->isOriginalDisplayInlineType(); - if (!isInlineType) - m_block->setStaticInlinePositionForChild(box, m_block->logicalHeight(), m_block->startOffsetForContent(m_block->logicalHeight())); - else { - // If our original display was an INLINE type, then we can go ahead - // and determine our static y position now. - box->layer()->setStaticBlockPosition(m_block->logicalHeight()); - } - - // If we're ignoring spaces, we have to stop and include this object and - // then start ignoring spaces again. - if (isInlineType || current.m_obj->container()->isRenderInline()) { - if (ignoringSpaces) - ensureLineBoxInsideIgnoredSpaces(lineMidpointState, current.m_obj); - trailingObjects.appendBoxIfNeeded(box); - } else - m_positionedObjects.append(box); - width.addUncommittedWidth(inlineLogicalWidth(current.m_obj)); - // Reset prior line break context characters. - renderTextInfo.m_lineBreakIterator.resetPriorContext(); - } else if (current.m_obj->isFloating()) { - RenderBox* floatBox = toRenderBox(current.m_obj); - FloatingObject* f = m_block->insertFloatingObject(floatBox); - // check if it fits in the current line. - // If it does, position it now, otherwise, position - // it after moving to next line (in newLine() func) - // FIXME: Bug 110372: Properly position multiple stacked floats with non-rectangular shape outside. - if (floatsFitOnLine && width.fitsOnLineExcludingTrailingWhitespace(m_block->logicalWidthForFloat(f))) { - m_block->positionNewFloatOnLine(f, lastFloatFromPreviousLine, lineInfo, width); - if (lBreak.m_obj == current.m_obj) { - ASSERT(!lBreak.m_pos); - lBreak.increment(); - } - } else - floatsFitOnLine = false; - // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for floating element. - renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter); - } else if (current.m_obj->isRenderInline()) { - // Right now, we should only encounter empty inlines here. - ASSERT(isEmptyInline(current.m_obj)); - - RenderInline* flowBox = toRenderInline(current.m_obj); - - // Now that some inline flows have line boxes, if we are already ignoring spaces, we need - // to make sure that we stop to include this object and then start ignoring spaces again. - // If this object is at the start of the line, we need to behave like list markers and - // start ignoring spaces. - bool requiresLineBox = alwaysRequiresLineBox(current.m_obj); - if (requiresLineBox || requiresLineBoxForContent(flowBox, lineInfo)) { - // An empty inline that only has line-height, vertical-align or font-metrics will only get a - // line box to affect the height of the line if the rest of the line is not empty. - if (requiresLineBox) - lineInfo.setEmpty(false, m_block, &width); - if (ignoringSpaces) { - trailingObjects.clear(); - ensureLineBoxInsideIgnoredSpaces(lineMidpointState, current.m_obj); - } else if (blockStyle->collapseWhiteSpace() && resolver.position().m_obj == current.m_obj - && shouldSkipWhitespaceAfterStartObject(m_block, current.m_obj, lineMidpointState)) { - // Like with list markers, we start ignoring spaces to make sure that any - // additional spaces we see will be discarded. - currentCharacterIsSpace = true; - currentCharacterIsWS = true; - ignoringSpaces = true; - } else { - trailingObjects.appendBoxIfNeeded(flowBox); - } - } - - width.addUncommittedWidth(inlineLogicalWidth(current.m_obj) + borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox)); - } else if (current.m_obj->isReplaced()) { - RenderBox* replacedBox = toRenderBox(current.m_obj); - - if (atStart) - width.updateAvailableWidth(replacedBox->logicalHeight()); - - // Break on replaced elements if either has normal white-space. - if ((autoWrap || RenderStyle::autoWrap(lastWS)) && (!current.m_obj->isImage() || allowImagesToBreak)) - commitLineBreakAtCurrentWidth(width, lBreak, current.m_obj); - - if (ignoringSpaces) - stopIgnoringSpaces(lineMidpointState, InlineIterator(0, current.m_obj, 0)); - - lineInfo.setEmpty(false, m_block, &width); - ignoringSpaces = false; - currentCharacterIsSpace = false; - currentCharacterIsWS = false; - trailingObjects.clear(); - - // Optimize for a common case. If we can't find whitespace after the list - // item, then this is all moot. - LayoutUnit replacedLogicalWidth = m_block->logicalWidthForChild(replacedBox) + m_block->marginStartForChild(replacedBox) + m_block->marginEndForChild(replacedBox) + inlineLogicalWidth(current.m_obj); - if (current.m_obj->isListMarker()) { - if (blockStyle->collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(m_block, current.m_obj, lineMidpointState)) { - // Like with inline flows, we start ignoring spaces to make sure that any - // additional spaces we see will be discarded. - currentCharacterIsSpace = true; - currentCharacterIsWS = true; - ignoringSpaces = true; - } - if (toRenderListMarker(current.m_obj)->isInside()) - width.addUncommittedWidth(replacedLogicalWidth); - } else - width.addUncommittedWidth(replacedLogicalWidth); - if (current.m_obj->isRubyRun()) - width.applyOverhang(toRenderRubyRun(current.m_obj), last, next); - // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for replaced element. - renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter); - } else if (current.m_obj->isText()) { - if (!current.m_pos) - appliedStartWidth = false; - - RenderText* t = toRenderText(current.m_obj); - -#if ENABLE(SVG) - bool isSVGText = t->isSVGInlineText(); -#endif - - // If we have left a no-wrap inline and entered an autowrap inline while ignoring spaces - // then we need to mark the start of the autowrap inline as a potential linebreak now. - if (autoWrap && !RenderStyle::autoWrap(lastWS) && ignoringSpaces) - commitLineBreakAtCurrentWidth(width, lBreak, current.m_obj); - - if (t->style()->hasTextCombine() && current.m_obj->isCombineText() && !toRenderCombineText(current.m_obj)->isCombined()) { - RenderCombineText* combineRenderer = toRenderCombineText(current.m_obj); - combineRenderer->combineText(); - // The length of the renderer's text may have changed. Increment stale iterator positions - if (iteratorIsBeyondEndOfRenderCombineText(lBreak, combineRenderer)) { - ASSERT(iteratorIsBeyondEndOfRenderCombineText(resolver.position(), combineRenderer)); - lBreak.increment(); - resolver.increment(); - } - } - - RenderStyle* style = t->style(lineInfo.isFirstLine()); - const Font& f = style->font(); - bool isFixedPitch = f.isFixedPitch(); - bool canHyphenate = style->hyphens() == HyphensAuto && WebCore::canHyphenate(style->locale()); - - unsigned lastSpace = current.m_pos; - float wordSpacing = currentStyle->wordSpacing(); - float lastSpaceWordSpacing = 0; - float wordSpacingForWordMeasurement = 0; - - float wrapW = width.uncommittedWidth() + inlineLogicalWidth(current.m_obj, !appliedStartWidth, true); - float charWidth = 0; - bool breakNBSP = autoWrap && currentStyle->nbspMode() == SPACE; - // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word, - // which is only possible if the word is the first thing on the line, that is, if |w| is zero. - bool breakWords = currentStyle->breakWords() && ((autoWrap && !width.committedWidth()) || currWS == PRE); - bool midWordBreak = false; - bool breakAll = currentStyle->wordBreak() == BreakAllWordBreak && autoWrap; - float hyphenWidth = 0; -#if ENABLE(SVG) - if (isSVGText) { - breakWords = false; - breakAll = false; - } -#endif - - if (t->isWordBreak()) { - commitLineBreakAtCurrentWidth(width, lBreak, current.m_obj); - ASSERT(current.m_pos == t->textLength()); - } - - if (renderTextInfo.m_text != t) { - updateCounterIfNeeded(t); - renderTextInfo.m_text = t; - renderTextInfo.m_font = &f; - renderTextInfo.m_layout = f.createLayout(t, width.currentWidth(), collapseWhiteSpace); - renderTextInfo.m_lineBreakIterator.resetStringAndReleaseIterator(t->text(), style->locale()); - } else if (renderTextInfo.m_layout && renderTextInfo.m_font != &f) { - renderTextInfo.m_font = &f; - renderTextInfo.m_layout = f.createLayout(t, width.currentWidth(), collapseWhiteSpace); - } - - TextLayout* textLayout = renderTextInfo.m_layout.get(); - - // Non-zero only when kerning is enabled and TextLayout isn't used, in which case we measure - // words with their trailing space, then subtract its width. - HashSet<const SimpleFontData*> fallbackFonts; - float wordTrailingSpaceWidth = (f.typesettingFeatures() & Kerning) && !textLayout ? f.width(constructTextRun(t, f, &space, 1, style), &fallbackFonts) + wordSpacing : 0; - - UChar lastCharacter = renderTextInfo.m_lineBreakIterator.lastCharacter(); - UChar secondToLastCharacter = renderTextInfo.m_lineBreakIterator.secondToLastCharacter(); - for (; current.m_pos < t->textLength(); current.fastIncrementInTextNode()) { - bool previousCharacterIsSpace = currentCharacterIsSpace; - bool previousCharacterIsWS = currentCharacterIsWS; - UChar c = current.current(); - currentCharacterIsSpace = c == ' ' || c == '\t' || (!preserveNewline && (c == '\n')); - - if (!collapseWhiteSpace || !currentCharacterIsSpace) - lineInfo.setEmpty(false, m_block, &width); - - if (c == softHyphen && autoWrap && !hyphenWidth && style->hyphens() != HyphensNone) { - hyphenWidth = measureHyphenWidth(t, f, &fallbackFonts); - width.addUncommittedWidth(hyphenWidth); - } - - bool applyWordSpacing = false; - - currentCharacterIsWS = currentCharacterIsSpace || (breakNBSP && c == noBreakSpace); - - if ((breakAll || breakWords) && !midWordBreak) { - wrapW += charWidth; - bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && current.m_pos + 1 < t->textLength() && U16_IS_TRAIL(t->characters()[current.m_pos + 1]); - charWidth = textWidth(t, current.m_pos, midWordBreakIsBeforeSurrogatePair ? 2 : 1, f, width.committedWidth() + wrapW, isFixedPitch, collapseWhiteSpace, fallbackFonts, textLayout); - midWordBreak = width.committedWidth() + wrapW + charWidth > width.availableWidth(); - } - - bool betweenWords = c == '\n' || (currWS != PRE && !atStart && isBreakable(renderTextInfo.m_lineBreakIterator, current.m_pos, current.m_nextBreakablePosition, breakNBSP) - && (style->hyphens() != HyphensNone || (current.previousInSameNode() != softHyphen))); - - if (betweenWords || midWordBreak) { - bool stoppedIgnoringSpaces = false; - if (ignoringSpaces) { - lastSpaceWordSpacing = 0; - if (!currentCharacterIsSpace) { - // Stop ignoring spaces and begin at this - // new point. - ignoringSpaces = false; - wordSpacingForWordMeasurement = 0; - lastSpace = current.m_pos; // e.g., "Foo goo", don't add in any of the ignored spaces. - stopIgnoringSpaces(lineMidpointState, InlineIterator(0, current.m_obj, current.m_pos)); - stoppedIgnoringSpaces = true; - } else { - // Just keep ignoring these spaces. - goto nextCharacter; - } - } - - wordMeasurements.grow(wordMeasurements.size() + 1); - WordMeasurement& wordMeasurement = wordMeasurements.last(); - - wordMeasurement.renderer = t; - wordMeasurement.endOffset = current.m_pos; - wordMeasurement.startOffset = lastSpace; - - float additionalTempWidth; - if (wordTrailingSpaceWidth && c == ' ') - additionalTempWidth = textWidth(t, lastSpace, current.m_pos + 1 - lastSpace, f, width.currentWidth(), isFixedPitch, collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) - wordTrailingSpaceWidth; - else - additionalTempWidth = textWidth(t, lastSpace, current.m_pos - lastSpace, f, width.currentWidth(), isFixedPitch, collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout); - - if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty()) - wordMeasurement.fallbackFonts.swap(fallbackFonts); - fallbackFonts.clear(); - - wordMeasurement.width = additionalTempWidth + wordSpacingForWordMeasurement; - additionalTempWidth += lastSpaceWordSpacing; - width.addUncommittedWidth(additionalTempWidth); - - if (collapseWhiteSpace && previousCharacterIsSpace && currentCharacterIsSpace && additionalTempWidth) - width.setTrailingWhitespaceWidth(additionalTempWidth); - - if (!appliedStartWidth) { - width.addUncommittedWidth(inlineLogicalWidth(current.m_obj, true, false)); - appliedStartWidth = true; - } - - applyWordSpacing = wordSpacing && currentCharacterIsSpace; - - if (!width.committedWidth() && autoWrap && !width.fitsOnLine()) - width.fitBelowFloats(); - - if (autoWrap || breakWords) { - // If we break only after white-space, consider the current character - // as candidate width for this line. - bool lineWasTooWide = false; - if (width.fitsOnLine() && currentCharacterIsWS && currentStyle->breakOnlyAfterWhiteSpace() && !midWordBreak) { - float charWidth = textWidth(t, current.m_pos, 1, f, width.currentWidth(), isFixedPitch, collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) + (applyWordSpacing ? wordSpacing : 0); - // Check if line is too big even without the extra space - // at the end of the line. If it is not, do nothing. - // If the line needs the extra whitespace to be too long, - // then move the line break to the space and skip all - // additional whitespace. - if (!width.fitsOnLineIncludingExtraWidth(charWidth)) { - lineWasTooWide = true; - lBreak.moveTo(current.m_obj, current.m_pos, current.m_nextBreakablePosition); - skipTrailingWhitespace(lBreak, lineInfo); - } - } - if (lineWasTooWide || !width.fitsOnLine()) { - if (canHyphenate && !width.fitsOnLine()) { - tryHyphenating(t, f, style->locale(), consecutiveHyphenatedLines, blockStyle->hyphenationLimitLines(), style->hyphenationLimitBefore(), style->hyphenationLimitAfter(), lastSpace, current.m_pos, width.currentWidth() - additionalTempWidth, width.availableWidth(), isFixedPitch, collapseWhiteSpace, lastSpaceWordSpacing, lBreak, current.m_nextBreakablePosition, m_hyphenated); - if (m_hyphenated) - goto end; - } - if (lBreak.atTextParagraphSeparator()) { - if (!stoppedIgnoringSpaces && current.m_pos > 0) - ensureCharacterGetsLineBox(lineMidpointState, current); - lBreak.increment(); - lineInfo.setPreviousLineBrokeCleanly(true); - wordMeasurement.endOffset = lBreak.m_pos; - } - if (lBreak.m_obj && lBreak.m_pos && lBreak.m_obj->isText() && toRenderText(lBreak.m_obj)->textLength() && toRenderText(lBreak.m_obj)->characterAt(lBreak.m_pos - 1) == softHyphen && style->hyphens() != HyphensNone) - m_hyphenated = true; - if (lBreak.m_pos && lBreak.m_pos != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) { - if (charWidth) { - wordMeasurement.endOffset = lBreak.m_pos; - wordMeasurement.width = charWidth; - } - } - // Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace. - if (ignoringSpaces || !collapseWhiteSpace || !currentCharacterIsSpace || !previousCharacterIsSpace) - goto end; - } else { - if (!betweenWords || (midWordBreak && !autoWrap)) - width.addUncommittedWidth(-additionalTempWidth); - if (hyphenWidth) { - // Subtract the width of the soft hyphen out since we fit on a line. - width.addUncommittedWidth(-hyphenWidth); - hyphenWidth = 0; - } - } - } - - if (c == '\n' && preserveNewline) { - if (!stoppedIgnoringSpaces && current.m_pos > 0) - ensureCharacterGetsLineBox(lineMidpointState, current); - lBreak.moveTo(current.m_obj, current.m_pos, current.m_nextBreakablePosition); - lBreak.increment(); - lineInfo.setPreviousLineBrokeCleanly(true); - return lBreak; - } - - if (autoWrap && betweenWords) { - wrapW = 0; - commitLineBreakAtCurrentWidth(width, lBreak, current.m_obj, current.m_pos, current.m_nextBreakablePosition); - // Auto-wrapping text should not wrap in the middle of a word once it has had an - // opportunity to break after a word. - breakWords = false; - } - - if (midWordBreak && !U16_IS_TRAIL(c) && !(category(c) & (Mark_NonSpacing | Mark_Enclosing | Mark_SpacingCombining))) { - // Remember this as a breakable position in case - // adding the end width forces a break. - lBreak.moveTo(current.m_obj, current.m_pos, current.m_nextBreakablePosition); - midWordBreak &= (breakWords || breakAll); - } - - if (betweenWords) { - lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; - wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0; - lastSpace = current.m_pos; - } - - if (!ignoringSpaces && currentStyle->collapseWhiteSpace()) { - // If we encounter a newline, or if we encounter a - // second space, we need to go ahead and break up this - // run and enter a mode where we start collapsing spaces. - if (currentCharacterIsSpace && previousCharacterIsSpace) { - ignoringSpaces = true; - - // We just entered a mode where we are ignoring - // spaces. Create a midpoint to terminate the run - // before the second space. - startIgnoringSpaces(lineMidpointState, ignoreStart); - trailingObjects.updateMidpointsForTrailingBoxes(lineMidpointState, InlineIterator(), TrailingObjects::DoNotCollapseFirstSpace); - } - } - } else if (ignoringSpaces) { - // Stop ignoring spaces and begin at this - // new point. - ignoringSpaces = false; - lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; - wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurements.last().width) ? wordSpacing : 0; - lastSpace = current.m_pos; // e.g., "Foo goo", don't add in any of the ignored spaces. - stopIgnoringSpaces(lineMidpointState, InlineIterator(0, current.m_obj, current.m_pos)); - } - -#if ENABLE(SVG) - if (isSVGText && current.m_pos > 0) { - // Force creation of new InlineBoxes for each absolute positioned character (those that start new text chunks). - if (toRenderSVGInlineText(t)->characterStartsNewTextChunk(current.m_pos)) - ensureCharacterGetsLineBox(lineMidpointState, current); - } -#endif - - if (currentCharacterIsSpace && !previousCharacterIsSpace) { - ignoreStart.m_obj = current.m_obj; - ignoreStart.m_pos = current.m_pos; - } - - if (!currentCharacterIsWS && previousCharacterIsWS) { - if (autoWrap && currentStyle->breakOnlyAfterWhiteSpace()) - lBreak.moveTo(current.m_obj, current.m_pos, current.m_nextBreakablePosition); - } - - if (collapseWhiteSpace && currentCharacterIsSpace && !ignoringSpaces) - trailingObjects.setTrailingWhitespace(toRenderText(current.m_obj)); - else if (!currentStyle->collapseWhiteSpace() || !currentCharacterIsSpace) - trailingObjects.clear(); - - atStart = false; - nextCharacter: - secondToLastCharacter = lastCharacter; - lastCharacter = c; - } - - renderTextInfo.m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter); - - wordMeasurements.grow(wordMeasurements.size() + 1); - WordMeasurement& wordMeasurement = wordMeasurements.last(); - wordMeasurement.renderer = t; - - // IMPORTANT: current.m_pos is > length here! - float additionalTempWidth = ignoringSpaces ? 0 : textWidth(t, lastSpace, current.m_pos - lastSpace, f, width.currentWidth(), isFixedPitch, collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout); - wordMeasurement.startOffset = lastSpace; - wordMeasurement.endOffset = current.m_pos; - wordMeasurement.width = ignoringSpaces ? 0 : additionalTempWidth + wordSpacingForWordMeasurement; - additionalTempWidth += lastSpaceWordSpacing; - - float inlineLogicalTempWidth = inlineLogicalWidth(current.m_obj, !appliedStartWidth, includeEndWidth); - width.addUncommittedWidth(additionalTempWidth + inlineLogicalTempWidth); - - if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty()) - wordMeasurement.fallbackFonts.swap(fallbackFonts); - fallbackFonts.clear(); - - if (collapseWhiteSpace && currentCharacterIsSpace && additionalTempWidth) - width.setTrailingWhitespaceWidth(additionalTempWidth, inlineLogicalTempWidth); - - includeEndWidth = false; - - if (!width.fitsOnLine()) { - if (canHyphenate) - tryHyphenating(t, f, style->locale(), consecutiveHyphenatedLines, blockStyle->hyphenationLimitLines(), style->hyphenationLimitBefore(), style->hyphenationLimitAfter(), lastSpace, current.m_pos, width.currentWidth() - additionalTempWidth, width.availableWidth(), isFixedPitch, collapseWhiteSpace, lastSpaceWordSpacing, lBreak, current.m_nextBreakablePosition, m_hyphenated); - - if (!m_hyphenated && lBreak.previousInSameNode() == softHyphen && style->hyphens() != HyphensNone) - m_hyphenated = true; - - if (m_hyphenated) - goto end; - } - } else - ASSERT_NOT_REACHED(); - - bool canBreakHere = canBreakAtThisPosition(autoWrap, width, lBreak, next, current, currWS, currentCharacterIsSpace, autoWrapWasEverTrueOnLine); - if (canBreakHere && !width.fitsOnLine(ignoringSpaces)) { - // if we have floats, try to get below them. - if (currentCharacterIsSpace && !ignoringSpaces && currentStyle->collapseWhiteSpace()) - trailingObjects.clear(); - - if (width.committedWidth()) - goto end; - - width.fitBelowFloats(); - - // |width| may have been adjusted because we got shoved down past a float (thus - // giving us more room), so we need to retest, and only jump to - // the end label if we still don't fit on the line. -dwh - if (!width.fitsOnLine(ignoringSpaces)) - goto end; - } else if (blockStyle->autoWrap() && !width.fitsOnLine() && !width.committedWidth()) { - // If the container autowraps but the current child does not then we still need to ensure that it - // wraps and moves below any floats. - width.fitBelowFloats(); - } - - if (!current.m_obj->isFloatingOrOutOfFlowPositioned()) { - last = current.m_obj; - if (last->isReplaced() && autoWrap && (!last->isImage() || allowImagesToBreak) && (!last->isListMarker() || toRenderListMarker(last)->isInside())) - commitLineBreakAtCurrentWidth(width, lBreak, next); - } - - // Clear out our character space bool, since inline <pre>s don't collapse whitespace - // with adjacent inline normal/nowrap spans. - if (!collapseWhiteSpace) - currentCharacterIsSpace = false; - - current.moveToStartOf(next); - atStart = false; - } - - if (width.fitsOnLine(true) || lastWS == NOWRAP) - lBreak.clear(); - - end: -#if ENABLE(CSS_SHAPES) - ShapeInsideInfo* shapeInfo = m_block->layoutShapeInsideInfo(); - bool segmentAllowsOverflow = !shapeInfo || !shapeInfo->hasSegments(); -#else - bool segmentAllowsOverflow = true; -#endif - - if (segmentAllowsOverflow) { - if (lBreak == resolver.position()) { - if (!lBreak.m_obj || !lBreak.m_obj->isBR()) { - // we just add as much as possible - if (blockStyle->whiteSpace() == PRE && !current.m_pos) { - lBreak.moveTo(last, last->isText() ? last->length() : 0); - } else if (lBreak.m_obj) { - // Don't ever break in the middle of a word if we can help it. - // There's no room at all. We just have to be on this line, - // even though we'll spill out. - lBreak.moveTo(current.m_obj, current.m_pos); - } - } - // make sure we consume at least one char/object. - if (lBreak == resolver.position()) - lBreak.increment(); - } else if (!width.committedWidth() && (!current.m_obj || !current.m_obj->isBR()) && !current.m_pos) { - // Do not push the current object to the next line, when this line has some content, but it is still considered empty. - // Empty inline elements like <span></span> can produce such lines and now we just ignore these break opportunities - // at the start of a line, if no width has been committed yet. - // Behave as if it was actually empty and consume at least one object. - lBreak.increment(); - } - } - - // Sanity check our midpoints. - checkMidpoints(lineMidpointState, lBreak); - - trailingObjects.updateMidpointsForTrailingBoxes(lineMidpointState, lBreak, TrailingObjects::CollapseFirstSpace); - - // We might have made lBreak an iterator that points past the end - // of the object. Do this adjustment to make it point to the start - // of the next object instead to avoid confusing the rest of the - // code. - if (lBreak.m_pos > 0) { - lBreak.m_pos--; - lBreak.increment(); } - - return lBreak; -} - -void RenderBlock::addOverflowFromInlineChildren() -{ LayoutUnit endPadding = hasOverflowClip() ? paddingEnd() : LayoutUnit(); // FIXME: Need to find another way to do this, since scrollbars could show when we don't want them to. - if (hasOverflowClip() && !endPadding && node() && node()->isRootEditableElement() && style()->isLeftToRightDirection()) + if (hasOverflowClip() && !endPadding && element() && element()->isRootEditableElement() && style().isLeftToRightDirection()) endPadding = 1; for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { addLayoutOverflow(curr->paddedLayoutOverflowRect(endPadding)); - if (!hasOverflowClip()) - addVisualOverflow(curr->visualOverflowRect(curr->lineTop(), curr->lineBottom())); + RenderRegion* region = flowThreadContainingBlock() ? curr->containingRegion() : nullptr; + if (region) + region->addLayoutOverflowForBox(this, curr->paddedLayoutOverflowRect(endPadding)); + if (!hasOverflowClip()) { + LayoutRect childVisualOverflowRect = curr->visualOverflowRect(curr->lineTop(), curr->lineBottom()); + addVisualOverflow(childVisualOverflowRect); + if (region) + region->addVisualOverflowForBox(this, childVisualOverflowRect); + } } } -void RenderBlock::deleteEllipsisLineBoxes() +void RenderBlockFlow::deleteEllipsisLineBoxes() { - ETextAlign textAlign = style()->textAlign(); - bool ltr = style()->isLeftToRightDirection(); - bool firstLine = true; + ETextAlign textAlign = style().textAlign(); + bool ltr = style().isLeftToRightDirection(); + IndentTextOrNot shouldIndentText = IndentText; for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { if (curr->hasEllipsisBox()) { curr->clearTruncation(); // Shift the line back where it belongs if we cannot accomodate an ellipsis. - float logicalLeft = pixelSnappedLogicalLeftOffsetForLine(curr->lineTop(), firstLine); - float availableLogicalWidth = logicalRightOffsetForLine(curr->lineTop(), false) - logicalLeft; + float logicalLeft = logicalLeftOffsetForLine(curr->lineTop(), shouldIndentText); + float availableLogicalWidth = logicalRightOffsetForLine(curr->lineTop(), DoNotIndentText) - logicalLeft; float totalLogicalWidth = curr->logicalWidth(); - updateLogicalWidthForAlignment(textAlign, 0, logicalLeft, totalLogicalWidth, availableLogicalWidth, 0); + updateLogicalWidthForAlignment(textAlign, curr, 0, logicalLeft, totalLogicalWidth, availableLogicalWidth, 0); if (ltr) curr->adjustLogicalPosition((logicalLeft - curr->logicalLeft()), 0); else curr->adjustLogicalPosition(-(curr->logicalLeft() - logicalLeft), 0); } - firstLine = false; + shouldIndentText = DoNotIndentText; } } -void RenderBlock::checkLinesForTextOverflow() +void RenderBlockFlow::checkLinesForTextOverflow() { // Determine the width of the ellipsis using the current font. // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if horizontal ellipsis is "not renderable" - const Font& font = style()->font(); - DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, (&horizontalEllipsis, 1)); - const Font& firstLineFont = firstLineStyle()->font(); - int firstLineEllipsisWidth = firstLineFont.width(constructTextRun(this, firstLineFont, &horizontalEllipsis, 1, firstLineStyle())); - int ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.width(constructTextRun(this, font, &horizontalEllipsis, 1, style())); + const FontCascade& font = style().fontCascade(); + static NeverDestroyed<AtomicString> ellipsisStr(&horizontalEllipsis, 1); + const FontCascade& firstLineFont = firstLineStyle().fontCascade(); + float firstLineEllipsisWidth = firstLineFont.width(constructTextRun(this, firstLineFont, &horizontalEllipsis, 1, firstLineStyle())); + float ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.width(constructTextRun(this, font, &horizontalEllipsis, 1, style())); // For LTR text truncation, we want to get the right edge of our padding box, and then we want to see // if the right edge of a line box exceeds that. For RTL, we use the left edge of the padding box and // check the left edge of the line box to see if it is less // Include the scrollbar for overflow blocks, which means we want to use "contentWidth()" - bool ltr = style()->isLeftToRightDirection(); - ETextAlign textAlign = style()->textAlign(); + bool ltr = style().isLeftToRightDirection(); + ETextAlign textAlign = style().textAlign(); bool firstLine = true; for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { - // FIXME: Use pixelSnappedLogicalRightOffsetForLine instead of snapping it ourselves once the column workaround in said method has been fixed. - // https://bugs.webkit.org/show_bug.cgi?id=105461 - int blockRightEdge = snapSizeToPixel(logicalRightOffsetForLine(curr->lineTop(), firstLine), curr->x()); - int blockLeftEdge = pixelSnappedLogicalLeftOffsetForLine(curr->lineTop(), firstLine); - int lineBoxEdge = ltr ? snapSizeToPixel(curr->x() + curr->logicalWidth(), curr->x()) : snapSizeToPixel(curr->x(), 0); + IndentTextOrNot shouldIndentText = firstLine ? IndentText : DoNotIndentText; + LayoutUnit blockRightEdge = logicalRightOffsetForLine(curr->lineTop(), shouldIndentText); + LayoutUnit blockLeftEdge = logicalLeftOffsetForLine(curr->lineTop(), shouldIndentText); + LayoutUnit lineBoxEdge = ltr ? curr->x() + curr->logicalWidth() : curr->x(); if ((ltr && lineBoxEdge > blockRightEdge) || (!ltr && lineBoxEdge < blockLeftEdge)) { // This line spills out of our box in the appropriate direction. Now we need to see if the line // can be truncated. In order for truncation to be possible, the line must have sufficient space to // accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis // space. - LayoutUnit width = firstLine ? firstLineEllipsisWidth : ellipsisWidth; LayoutUnit blockEdge = ltr ? blockRightEdge : blockLeftEdge; if (curr->lineCanAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width)) { float totalLogicalWidth = curr->placeEllipsis(ellipsisStr, ltr, blockLeftEdge, blockRightEdge, width); float logicalLeft = 0; // We are only interested in the delta from the base position. - float truncatedWidth = pixelSnappedLogicalRightOffsetForLine(curr->lineTop(), firstLine); - updateLogicalWidthForAlignment(textAlign, 0, logicalLeft, totalLogicalWidth, truncatedWidth, 0); + float truncatedWidth = availableLogicalWidthForLine(curr->lineTop(), shouldIndentText); + updateLogicalWidthForAlignment(textAlign, curr, nullptr, logicalLeft, totalLogicalWidth, truncatedWidth, 0); if (ltr) curr->adjustLogicalPosition(logicalLeft, 0); else @@ -3626,7 +2107,7 @@ void RenderBlock::checkLinesForTextOverflow() } } -bool RenderBlock::positionNewFloatOnLine(FloatingObject* newFloat, FloatingObject* lastFloatFromPreviousLine, LineInfo& lineInfo, LineWidth& width) +bool RenderBlockFlow::positionNewFloatOnLine(const FloatingObject& newFloat, FloatingObject* lastFloatFromPreviousLine, LineInfo& lineInfo, LineWidth& width) { if (!positionNewFloats()) return false; @@ -3636,39 +2117,43 @@ bool RenderBlock::positionNewFloatOnLine(FloatingObject* newFloat, FloatingObjec // We only connect floats to lines for pagination purposes if the floats occur at the start of // the line and the previous line had a hard break (so this line is either the first in the block // or follows a <br>). - if (!newFloat->m_paginationStrut || !lineInfo.previousLineBrokeCleanly() || !lineInfo.isEmpty()) + if (!newFloat.paginationStrut() || !lineInfo.previousLineBrokeCleanly() || !lineInfo.isEmpty()) return true; const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); - ASSERT(floatingObjectSet.last() == newFloat); + ASSERT(floatingObjectSet.last().get() == &newFloat); LayoutUnit floatLogicalTop = logicalTopForFloat(newFloat); - int paginationStrut = newFloat->m_paginationStrut; + LayoutUnit paginationStrut = newFloat.paginationStrut(); if (floatLogicalTop - paginationStrut != logicalHeight() + lineInfo.floatPaginationStrut()) return true; - FloatingObjectSetIterator it = floatingObjectSet.end(); + auto it = floatingObjectSet.end(); --it; // Last float is newFloat, skip that one. - FloatingObjectSetIterator begin = floatingObjectSet.begin(); + auto begin = floatingObjectSet.begin(); while (it != begin) { --it; - FloatingObject* f = *it; - if (f == lastFloatFromPreviousLine) + auto& floatingObject = *it->get(); + if (&floatingObject == lastFloatFromPreviousLine) break; - if (logicalTopForFloat(f) == logicalHeight() + lineInfo.floatPaginationStrut()) { - f->m_paginationStrut += paginationStrut; - RenderBox* o = f->m_renderer; - setLogicalTopForChild(o, logicalTopForChild(o) + marginBeforeForChild(o) + paginationStrut); - if (o->isRenderBlock()) - toRenderBlock(o)->setChildNeedsLayout(true, MarkOnlyThis); - o->layoutIfNeeded(); + if (logicalTopForFloat(floatingObject) == logicalHeight() + lineInfo.floatPaginationStrut()) { + floatingObject.setPaginationStrut(paginationStrut + floatingObject.paginationStrut()); + RenderBox& floatBox = floatingObject.renderer(); + setLogicalTopForChild(floatBox, logicalTopForChild(floatBox) + marginBeforeForChild(floatBox) + paginationStrut); + + if (updateRegionRangeForBoxChild(floatBox)) + floatBox.setNeedsLayout(MarkOnlyThis); + else if (is<RenderBlock>(floatBox)) + downcast<RenderBlock>(floatBox).setChildNeedsLayout(MarkOnlyThis); + floatBox.layoutIfNeeded(); + // Save the old logical top before calling removePlacedObject which will set // isPlaced to false. Otherwise it will trigger an assert in logicalTopForFloat. - LayoutUnit oldLogicalTop = logicalTopForFloat(f); - m_floatingObjects->removePlacedObject(f); - setLogicalTopForFloat(f, oldLogicalTop + paginationStrut); - m_floatingObjects->addPlacedObject(f); + LayoutUnit oldLogicalTop = logicalTopForFloat(floatingObject); + m_floatingObjects->removePlacedObject(&floatingObject); + setLogicalTopForFloat(floatingObject, oldLogicalTop + paginationStrut); + m_floatingObjects->addPlacedObject(&floatingObject); } } @@ -3678,46 +2163,105 @@ bool RenderBlock::positionNewFloatOnLine(FloatingObject* newFloat, FloatingObjec return true; } -LayoutUnit RenderBlock::startAlignedOffsetForLine(LayoutUnit position, bool firstLine) +LayoutUnit RenderBlockFlow::startAlignedOffsetForLine(LayoutUnit position, IndentTextOrNot shouldIndentText) { - ETextAlign textAlign = style()->textAlign(); - - if (textAlign == TASTART) // FIXME: Handle TAEND here - return startOffsetForLine(position, firstLine); + ETextAlign textAlign = style().textAlign(); + bool shouldApplyIndentText = false; + switch (textAlign) { + case LEFT: + case WEBKIT_LEFT: + shouldApplyIndentText = style().isLeftToRightDirection(); + break; + case RIGHT: + case WEBKIT_RIGHT: + shouldApplyIndentText = !style().isLeftToRightDirection(); + break; + case TASTART: + shouldApplyIndentText = true; + break; + default: + shouldApplyIndentText = false; + } + // <rdar://problem/15427571> + // https://bugs.webkit.org/show_bug.cgi?id=124522 + // This quirk is for legacy content that doesn't work properly with the center positioning scheme + // being honored (e.g., epubs). + if (shouldApplyIndentText || document().settings()->useLegacyTextAlignPositionedElementBehavior()) // FIXME: Handle TAEND here + return startOffsetForLine(position, shouldIndentText); // updateLogicalWidthForAlignment() handles the direction of the block so no need to consider it here float totalLogicalWidth = 0; - float logicalLeft = logicalLeftOffsetForLine(logicalHeight(), false); - float availableLogicalWidth = logicalRightOffsetForLine(logicalHeight(), false) - logicalLeft; - updateLogicalWidthForAlignment(textAlign, 0, logicalLeft, totalLogicalWidth, availableLogicalWidth, 0); + float logicalLeft = logicalLeftOffsetForLine(logicalHeight(), DoNotIndentText); + float availableLogicalWidth = logicalRightOffsetForLine(logicalHeight(), DoNotIndentText) - logicalLeft; + + // FIXME: Bug 129311: We need to pass a valid RootInlineBox here, considering the bidi level used to construct the line. + updateLogicalWidthForAlignment(textAlign, 0, 0, logicalLeft, totalLogicalWidth, availableLogicalWidth, 0); - if (!style()->isLeftToRightDirection()) + if (!style().isLeftToRightDirection()) return logicalWidth() - logicalLeft; return logicalLeft; } - -void RenderBlock::layoutLineGridBox() +void RenderBlockFlow::updateRegionForLine(RootInlineBox* lineBox) const { - if (style()->lineGrid() == RenderStyle::initialLineGrid()) { - setLineGridBox(0); + ASSERT(lineBox); + + if (!hasRegionRangeInFlowThread()) + lineBox->clearContainingRegion(); + else { + if (auto containingRegion = regionAtBlockOffset(lineBox->lineTopWithLeading())) + lineBox->setContainingRegion(*containingRegion); + else + lineBox->clearContainingRegion(); + } + + RootInlineBox* prevLineBox = lineBox->prevRootBox(); + if (!prevLineBox) return; + + // This check is more accurate than the one in |adjustLinePositionForPagination| because it takes into + // account just the container changes between lines. The before mentioned function doesn't set the flag + // correctly if the line is positioned at the top of the last fragment container. + if (lineBox->containingRegion() != prevLineBox->containingRegion()) + lineBox->setIsFirstAfterPageBreak(true); +} + +void RenderBlockFlow::marginCollapseLinesFromStart(LineLayoutState& layoutState, RootInlineBox* stopLine) +{ + // We have to handle an anonymous inline block streak at the start of the block in order to make sure our own margins are + // correct. We only have to do this if the children can propagate margins out to us. + bool resetLogicalHeight = false; + if (layoutState.marginInfo().canCollapseWithMarginBefore()) { + RootInlineBox* startLine = firstRootBox(); + RootInlineBox* curr; + for (curr = startLine; curr && curr->hasAnonymousInlineBlock() && layoutState.marginInfo().canCollapseWithMarginBefore(); curr = curr->nextRootBox()) { + if (curr == stopLine) + return; + if (!resetLogicalHeight) { + setLogicalHeight(borderAndPaddingBefore()); + resetLogicalHeight = true; + } + layoutBlockChild(*curr->anonymousInlineBlock(), layoutState.marginInfo(), + layoutState.prevFloatBottomFromAnonymousInlineBlock(), layoutState.maxFloatBottomFromAnonymousInlineBlock()); + } } - setLineGridBox(0); + // Now that we've handled the top of the block, if the stopLine isn't an anonymous block, then we're done. + if (!stopLine->hasAnonymousInlineBlock()) + return; - RootInlineBox* lineGridBox = new (renderArena()) RootInlineBox(this); - lineGridBox->setHasTextChildren(); // Needed to make the line ascent/descent actually be honored in quirks mode. - lineGridBox->setConstructed(); - GlyphOverflowAndFallbackFontsMap textBoxDataMap; - VerticalPositionCache verticalPositionCache; - lineGridBox->alignBoxesInBlockDirection(logicalHeight(), textBoxDataMap, verticalPositionCache); - - setLineGridBox(lineGridBox); + // Re-run margin collapsing on the block sequence that stopLine is a part of. + // First go backwards to get the entire sequence. + RootInlineBox* prev = stopLine; + for ( ; prev->hasAnonymousInlineBlock(); prev = prev->prevRootBox()) { + }; + + // Update the block height to the correct state. + setLogicalHeight(prev->lineBottomWithLeading()); - // FIXME: If any of the characteristics of the box change compared to the old one, then we need to do a deep dirtying - // (similar to what happens when the page height changes). Ideally, though, we only do this if someone is actually snapping - // to this grid. + // Now run margin collapsing on those lines. + for (prev = prev->nextRootBox(); prev != stopLine; prev = prev->nextRootBox()) + layoutBlockChild(*prev->anonymousInlineBlock(), layoutState.marginInfo(), layoutState.prevFloatBottomFromAnonymousInlineBlock(), layoutState.maxFloatBottomFromAnonymousInlineBlock()); } } |