diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/rendering/RenderBlockLineLayout.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/rendering/RenderBlockLineLayout.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderBlockLineLayout.cpp | 1616 |
1 files changed, 874 insertions, 742 deletions
diff --git a/Source/WebCore/rendering/RenderBlockLineLayout.cpp b/Source/WebCore/rendering/RenderBlockLineLayout.cpp index 3e546abec..085937b57 100644 --- a/Source/WebCore/rendering/RenderBlockLineLayout.cpp +++ b/Source/WebCore/rendering/RenderBlockLineLayout.cpp @@ -26,29 +26,28 @@ #include "AXObjectCache.h" #include "BidiResolver.h" -#include "BreakingContextInlineHeaders.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 "RenderBlockFlow.h" #include "RenderFlowThread.h" #include "RenderLineBreak.h" #include "RenderRegion.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 <wtf/RefCountedLeakCounter.h> #include <wtf/StdLibExtras.h> -#if ENABLE(SVG) -#include "SVGRootInlineBox.h" -#endif - namespace WebCore { static void determineDirectionality(TextDirection& dir, InlineIterator iter) @@ -71,50 +70,50 @@ static void determineDirectionality(TextDirection& dir, InlineIterator iter) } } -inline BidiRun* createRun(int start, int end, RenderObject* obj, InlineBidiResolver& resolver) +inline std::unique_ptr<BidiRun> createRun(int start, int end, RenderObject& obj, InlineBidiResolver& resolver) { - ASSERT(obj); - return new BidiRun(start, end, *obj, resolver.context(), resolver.dir()); + return std::make_unique<BidiRun>(start, end, obj, resolver.context(), resolver.dir()); } -void RenderBlockFlow::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()); - InlineIterator nextMidpoint; - if (haveNextMidpoint) - nextMidpoint = lineMidpointState.midpoints()[lineMidpointState.currentMidpoint()]; - if (lineMidpointState.betweenMidpoints()) { - if (!(haveNextMidpoint && nextMidpoint.renderer() == obj)) + LineWhitespaceCollapsingState& lineWhitespaceCollapsingState = resolver.whitespaceCollapsingState(); + bool haveNextTransition = (lineWhitespaceCollapsingState.currentTransition() < lineWhitespaceCollapsingState.numTransitions()); + InlineIterator nextTransition; + if (haveNextTransition) + nextTransition = lineWhitespaceCollapsingState.transitions()[lineWhitespaceCollapsingState.currentTransition()]; + if (lineWhitespaceCollapsingState.betweenTransitions()) { + if (!haveNextTransition || (&obj != nextTransition.renderer())) return; // This is a new start point. Stop ignoring objects and // adjust our start. - lineMidpointState.setBetweenMidpoints(false); - start = nextMidpoint.offset(); - lineMidpointState.incrementCurrentMidpoint(); - if (start < end) - return appendRunsForObject(runs, start, end, obj, resolver); + start = nextTransition.offset(); + lineWhitespaceCollapsingState.incrementCurrentTransition(); + if (start < end) { + appendRunsForObject(runs, start, end, obj, resolver); + return; + } } else { - if (!haveNextMidpoint || (obj != nextMidpoint.renderer())) { - runs.addRun(createRun(start, end, obj, resolver)); + if (!haveNextTransition || (&obj != nextTransition.renderer())) { + if (runs) + runs->appendRun(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.offset() + 1) <= end) { - lineMidpointState.setBetweenMidpoints(true); - lineMidpointState.incrementCurrentMidpoint(); - if (nextMidpoint.offset() != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it. - if (static_cast<int>(nextMidpoint.offset() + 1) > start) - runs.addRun(createRun(start, nextMidpoint.offset() + 1, obj, resolver)); - return appendRunsForObject(runs, nextMidpoint.offset() + 1, end, obj, resolver); - } - } else - runs.addRun(createRun(start, end, obj, resolver)); + // An end transition has been encountered within our object. We need to append a run with our endpoint. + if (static_cast<int>(nextTransition.offset() + 1) <= end) { + lineWhitespaceCollapsingState.incrementCurrentTransition(); + // The end of the line is before the object we're inspecting. Skip everything and return + if (nextTransition.refersToEndOfPreviousNode()) + return; + if (static_cast<int>(nextTransition.offset() + 1) > start && runs) + runs->appendRun(createRun(start, nextTransition.offset() + 1, obj, resolver)); + appendRunsForObject(runs, nextTransition.offset() + 1, end, obj, resolver); + } else if (runs) + runs->appendRun(createRun(start, end, obj, resolver)); } } @@ -127,51 +126,51 @@ RootInlineBox* RenderBlockFlow::createAndAppendRootInlineBox() { auto newRootBox = createRootInlineBox(); RootInlineBox* rootBox = newRootBox.get(); - m_lineBoxes.appendLineBox(std::move(newRootBox)); + m_lineBoxes.appendLineBox(WTFMove(newRootBox)); if (UNLIKELY(AXObjectCache::accessibilityEnabled()) && firstRootBox() == rootBox) { if (AXObjectCache* cache = document().existingAXObjectCache()) - cache->recomputeIsIgnored(this); + cache->recomputeDeferredIsIgnored(*this); } return rootBox; } -static inline InlineBox* createInlineBoxForRenderer(RenderObject* obj, bool isRootLineBox, bool isOnlyRun = false) +static inline InlineBox* createInlineBoxForRenderer(RenderObject* renderer, bool isRootLineBox, bool isOnlyRun = false) { if (isRootLineBox) - return toRenderBlockFlow(obj)->createAndAppendRootInlineBox(); + return downcast<RenderBlockFlow>(*renderer).createAndAppendRootInlineBox(); - if (obj->isText()) - return toRenderText(obj)->createInlineTextBox(); + if (is<RenderText>(*renderer)) + return downcast<RenderText>(*renderer).createInlineTextBox(); - if (obj->isBox()) { + if (is<RenderBox>(*renderer)) { // FIXME: This is terrible. This branch returns an *owned* pointer! - return toRenderBox(obj)->createInlineBox().release(); + return downcast<RenderBox>(*renderer).createInlineBox().release(); } - if (obj->isLineBreak()) { + if (is<RenderLineBreak>(*renderer)) { // FIXME: This is terrible. This branch returns an *owned* pointer! - auto inlineBox = toRenderLineBreak(obj)->createInlineBox().release(); + 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 || obj->document().inNoQuirksMode() || obj->isLineBreakOpportunity()); + inlineBox->setBehavesLikeText(isOnlyRun || renderer->document().inNoQuirksMode() || renderer->isLineBreakOpportunity()); return inlineBox; } - return toRenderInline(obj)->createAndAppendInlineFlowBox(); + return downcast<RenderInline>(*renderer).createAndAppendInlineFlowBox(); } static inline void dirtyLineBoxesForRenderer(RenderObject& renderer, bool fullLayout) { - if (renderer.isText()) { - RenderText& renderText = toRenderText(renderer); + if (is<RenderText>(renderer)) { + RenderText& renderText = downcast<RenderText>(renderer); updateCounterIfNeeded(renderText); renderText.dirtyLineBoxes(fullLayout); - } else if (renderer.isLineBreak()) - toRenderLineBreak(renderer).dirtyLineBoxes(fullLayout); + } else if (is<RenderLineBreak>(renderer)) + downcast<RenderLineBreak>(renderer).dirtyLineBoxes(fullLayout); else - toRenderInline(renderer).dirtyLineBoxes(fullLayout); + downcast<RenderInline>(renderer).dirtyLineBoxes(fullLayout); } static bool parentIsConstructedOrHaveNext(InlineFlowBox* parentBox) @@ -184,21 +183,21 @@ static bool parentIsConstructedOrHaveNext(InlineFlowBox* parentBox) return false; } -InlineFlowBox* RenderBlockFlow::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; + 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() : toRenderBlockFlow(obj)->lastRootBox(); + 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 @@ -207,14 +206,12 @@ InlineFlowBox* RenderBlockFlow::createLineBoxes(RenderObject* obj, const LineInf // 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 = downcast<InlineFlowBox>(newBox); parentBox->setIsFirstLine(lineInfo.isFirstLine()); parentBox->setIsHorizontal(isHorizontalWritingMode()); if (!hasDefaultLineBoxContain) @@ -264,10 +261,10 @@ static bool reachedEndOfTextRenderer(const BidiRunList<BidiRun>& bidiRuns) if (!run) return true; unsigned pos = run->stop(); - const RenderObject& r = run->renderer(); - if (!r.isText()) + const RenderObject& renderer = run->renderer(); + if (!is<RenderText>(renderer)) return false; - const RenderText& renderText = toRenderText(r); + const RenderText& renderText = downcast<RenderText>(renderer); unsigned length = renderText.textLength(); if (pos >= length) return true; @@ -284,6 +281,7 @@ RootInlineBox* RenderBlockFlow::constructLine(BidiRunList<BidiRun>& bidiRuns, co 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); @@ -294,24 +292,20 @@ RootInlineBox* RenderBlockFlow::constructLine(BidiRunList<BidiRun>& bidiRuns, co continue; InlineBox* box = createInlineBoxForRenderer(&r->renderer(), false, isOnlyRun); - r->setBox(*box); + r->setBox(box); 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->renderer().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->renderer().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); } @@ -319,13 +313,13 @@ RootInlineBox* RenderBlockFlow::constructLine(BidiRunList<BidiRun>& bidiRuns, co 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); } } @@ -342,7 +336,7 @@ RootInlineBox* RenderBlockFlow::constructLine(BidiRunList<BidiRun>& bidiRuns, co // 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()->renderer().isText() ? !reachedEndOfTextRenderer(bidiRuns) : true; + bool isLogicallyLastRunWrapped = bidiRuns.logicallyLastRun()->renderer().isText() ? !reachedEndOfTextRenderer(bidiRuns) : !is<RenderInline>(bidiRuns.logicallyLastRun()->renderer()); lastRootBox()->determineSpacingForFlowBoxes(lineInfo.isLastLine(), isLogicallyLastRunWrapped, &bidiRuns.logicallyLastRun()->renderer()); // Now mark the line boxes as being constructed. @@ -355,6 +349,12 @@ RootInlineBox* RenderBlockFlow::constructLine(BidiRunList<BidiRun>& bidiRuns, co ETextAlign RenderBlockFlow::textAlignmentForLine(bool endsWithSoftBreak) const { 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; @@ -379,7 +379,7 @@ ETextAlign RenderBlockFlow::textAlignmentForLine(bool endsWithSoftBreak) const case TextAlignLastJustify: return JUSTIFY; case TextAlignLastAuto: - if (style().textJustify() == TextJustifyDistribute) + if (textJustify == TextJustifyDistribute) return JUSTIFY; return TASTART; } @@ -413,8 +413,7 @@ static void updateLogicalWidthForRightAlignedBlock(bool isLeftToRightDirection, totalLogicalWidth -= trailingSpaceRun->box()->logicalWidth(); trailingSpaceRun->box()->setLogicalWidth(0); } - if (totalLogicalWidth < availableLogicalWidth) - logicalLeft += availableLogicalWidth - totalLogicalWidth; + logicalLeft += std::max(0.f, availableLogicalWidth - totalLogicalWidth); return; } @@ -441,8 +440,8 @@ static void updateLogicalWidthForCenterAlignedBlock(bool isLeftToRightDirection, 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->renderer().isOutOfFlowPositioned() && !runWithNextObject->box()->isLineBreak()) { @@ -455,13 +454,13 @@ void RenderBlockFlow::setMarginsForRubyRun(BidiRun* run, RenderRubyRun& renderer setMarginEndForChild(renderer, -endOverhang); } -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 = lineStyle(*renderer->parent(), lineInfo).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 @@ -477,40 +476,43 @@ static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* ru } LayoutUnit hyphenWidth = 0; - if (toInlineTextBox(run->box())->hasHyphen()) + 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. - if (!lineBox->fitsToGlyphs() && canUseSimpleFontCodePath) { - int lastEndOffset = run->m_start; + unsigned lastEndOffset = run->m_start; + bool atFirstWordMeasurement = true; for (size_t i = 0, size = wordMeasurements.size(); i < size && lastEndOffset < run->m_stop; ++i) { 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); - if (i > 0 && wordLength == 1 && (c == ' ' || c == '\t')) - measuredWidth += renderer->style().font().wordSpacing(); + UChar c = renderer.characterAt(wordMeasurement.startOffset); + // renderer.width() omits word-spacing value for leading whitespace, so let's just add it back here. + if (!atFirstWordMeasurement && (c == ' ' || c == '\t')) + measuredWidth += renderer.style().fontCascade().wordSpacing(); } else measuredWidth += wordMeasurement.width; + atFirstWordMeasurement = false; + 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); } } @@ -522,60 +524,108 @@ 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->box()->setLogicalWidth(measuredWidth + hyphenWidth); if (!fallbackFonts.isEmpty()) { ASSERT(run->box()->behavesLikeText()); - GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(toInlineTextBox(run->box()), std::make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).iterator; + 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->box()->parent()->clearDescendantsHaveSameLineHeightAndBaseline(); } - if ((glyphOverflow.top || glyphOverflow.bottom || glyphOverflow.left || glyphOverflow.right)) { + + // 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(toInlineTextBox(run->box()), std::make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).iterator; + GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(downcast<InlineTextBox>(run->box()), std::make_pair(Vector<const Font*>(), GlyphOverflow())).iterator; it->value.second = glyphOverflow; 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()); + + 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->box() || r == trailingSpaceRun) + for (BidiRun* run = firstRun; run; run = run->next()) { + if (!run->box() || run == trailingSpaceRun) continue; - if (r->renderer().isText()) { + if (is<RenderText>(run->renderer())) { unsigned opportunitiesInRun = expansionOpportunities[i++]; ASSERT(opportunitiesInRun <= expansionOpportunityCount); // Only justify text if whitespace is collapsed. - if (r->renderer().style().collapseWhiteSpace()) { - InlineTextBox* textBox = toInlineTextBox(r->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 RenderBlockFlow::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 @@ -604,13 +654,13 @@ void RenderBlockFlow::updateLogicalWidthForAlignment(const ETextAlign& textAlign } FALLTHROUGH; case TASTART: - if (style().isLeftToRightDirection()) + if (direction == LTR) updateLogicalWidthForLeftAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); else updateLogicalWidthForRightAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case TAEND: - if (style().isLeftToRightDirection()) + if (direction == LTR) updateLogicalWidthForRightAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); else updateLogicalWidthForLeftAlignedBlock(style().isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); @@ -618,11 +668,17 @@ void RenderBlockFlow::updateLogicalWidthForAlignment(const ETextAlign& textAlign } } -static void updateLogicalInlinePositions(RenderBlockFlow& 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 = block.minLineHeightForReplacedRenderer(firstLine, boxLogicalHeight); - lineLogicalLeft = block.pixelSnappedLogicalLeftOffsetForLine(block.logicalHeight(), shouldIndentText == IndentText, lineLogicalHeight); - lineLogicalRight = block.pixelSnappedLogicalRightOffsetForLine(block.logicalHeight(), shouldIndentText == IndentText, lineLogicalHeight); + 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; } @@ -640,121 +696,295 @@ void RenderBlockFlow::computeInlineDirectionPositionsForLine(RootInlineBox* line 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 = std::max<float>(roundToInt(segments[0].logicalLeft), lineLogicalLeft); - float logicalRight = std::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 = std::max<float>(roundToInt(segments[i].logicalLeft), lineLogicalLeft); - logicalRight = std::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->box(), newSegmentStart ? newSegmentStart->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->renderer().isReplaced()) { - RenderBox& renderBox = toRenderBox(firstRun->renderer()); - updateLogicalInlinePositions(*this, lineLogicalLeft, lineLogicalRight, availableLogicalWidth, isFirstLine, shouldIndentText, renderBox.logicalHeight()); + 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; +} + +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; + } } +static bool inlineAncestorHasStartBorderPaddingOrMargin(const RenderBlockFlow& block, const InlineBox& box) +{ + bool isLTR = block.style().isLeftToRightDirection(); + for (auto* currentBox = box.parent(); currentBox; currentBox = currentBox->parent()) { + if ((isLTR && currentBox->marginBorderPaddingLogicalLeft() > 0) + || (!isLTR && currentBox->marginBorderPaddingLogicalRight() > 0)) + return true; + } + return false; +} + +static bool inlineAncestorHasEndBorderPaddingOrMargin(const RenderBlockFlow& block, const InlineBox& box) +{ + bool isLTR = block.style().isLeftToRightDirection(); + for (auto* currentBox = box.parent(); currentBox; currentBox = currentBox->parent()) { + if ((isLTR && currentBox->marginBorderPaddingLogicalRight() > 0) + || (!isLTR && currentBox->marginBorderPaddingLogicalLeft() > 0)) + return true; + } + return false; +} + +static bool isLastInFlowRun(BidiRun& runToCheck) +{ + for (auto* run = runToCheck.next(); run; run = run->next()) { + if (!run->box() || run->renderer().isOutOfFlowPositioned() || run->box()->isLineBreak()) + continue; + return false; + } + return true; +} + 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; + bool canHangPunctuationAtStart = style().hangingPunctuation() & FirstHangingPunctuation; + bool canHangPunctuationAtEnd = style().hangingPunctuation() & LastHangingPunctuation; + bool isLTR = style().isLeftToRightDirection(); 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->box() || r->renderer().isOutOfFlowPositioned() || r->box()->isLineBreak()) + + BidiRun* run = firstRun; + BidiRun* previousRun = nullptr; + for (; run; run = run->next()) { + auto computeExpansionOpportunities = [&expansionOpportunities, &expansionOpportunityCount, textAlign, &isAfterExpansion] (RenderBlockFlow& block, + InlineTextBox& textBox, BidiRun* previousRun, BidiRun* nextRun, const StringView& stringView, TextDirection direction) + { + if (stringView.isEmpty()) { + // Empty runs should still produce an entry in expansionOpportunities list so that the number of items matches the number of runs. + expansionOpportunities.append(0); + return; + } + ExpansionBehavior expansionBehavior = expansionBehaviorForInlineTextBox(block, textBox, previousRun, nextRun, textAlign, isAfterExpansion); + applyExpansionBehavior(textBox, expansionBehavior); + unsigned opportunitiesInRun; + std::tie(opportunitiesInRun, isAfterExpansion) = FontCascade::expansionOpportunityCount(stringView, direction, expansionBehavior); + expansionOpportunities.append(opportunitiesInRun); + expansionOpportunityCount += opportunitiesInRun; + }; + 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->renderer().isText()) { - RenderText& rt = toRenderText(r->renderer()); - if (textAlign == JUSTIFY && r != trailingSpaceRun) { - if (!isAfterExpansion) - toInlineTextBox(r->box())->setCanHaveLeadingExpansion(true); - unsigned opportunitiesInRun; - if (rt.is8Bit()) - opportunitiesInRun = Font::expansionOpportunityCount(rt.characters8() + r->m_start, r->m_stop - r->m_start, r->box()->direction(), isAfterExpansion); - else - opportunitiesInRun = Font::expansionOpportunityCount(rt.characters16() + r->m_start, r->m_stop - r->m_start, r->box()->direction(), isAfterExpansion); - expansionOpportunities.append(opportunitiesInRun); - expansionOpportunityCount += opportunitiesInRun; + } + if (is<RenderText>(run->renderer())) { + auto& renderText = downcast<RenderText>(run->renderer()); + auto& textBox = downcast<InlineTextBox>(*run->box()); + if (canHangPunctuationAtStart && lineInfo.isFirstLine() && (isLTR || isLastInFlowRun(*run)) + && !inlineAncestorHasStartBorderPaddingOrMargin(*this, *run->box())) { + float hangStartWidth = renderText.hangablePunctuationStartWidth(run->m_start); + availableLogicalWidth += hangStartWidth; + if (style().isLeftToRightDirection()) + logicalLeft -= hangStartWidth; + canHangPunctuationAtStart = false; } - - if (int length = rt.textLength()) { - if (!r->m_start && needsWordSpacing && isSpaceOrNewline(rt.characterAt(r->m_start))) - totalLogicalWidth += lineStyle(*rt.parent(), lineInfo).font().wordSpacing(); - needsWordSpacing = !isSpaceOrNewline(rt.characterAt(r->m_stop - 1)) && r->m_stop == length; + + if (canHangPunctuationAtEnd && lineInfo.isLastLine() && run->m_stop > 0 && (!isLTR || isLastInFlowRun(*run)) + && !inlineAncestorHasEndBorderPaddingOrMargin(*this, *run->box())) { + float hangEndWidth = renderText.hangablePunctuationEndWidth(run->m_stop - 1); + availableLogicalWidth += hangEndWidth; + if (!style().isLeftToRightDirection()) + logicalLeft -= hangEndWidth; + canHangPunctuationAtEnd = false; + } + + if (textAlign == JUSTIFY && run != trailingSpaceRun) + computeExpansionOpportunities(*this, textBox, previousRun, run->next(), renderText.stringView(run->m_start, run->m_stop), run->box()->direction()); + + if (unsigned length = renderText.textLength()) { + if (!run->m_start && needsWordSpacing && isSpaceOrNewline(renderText.characterAt(run->m_start))) + totalLogicalWidth += lineStyle(*renderText.parent(), lineInfo).fontCascade().wordSpacing(); + // run->m_start == run->m_stop should only be true iff the run is a replaced run for bidi: isolate. + ASSERT(run->m_stop > 0 || run->m_start == run->m_stop); + needsWordSpacing = run->m_stop == length && !isSpaceOrNewline(renderText.characterAt(run->m_stop - 1)); } - setLogicalWidthForTextRun(lineBox, r, &rt, totalLogicalWidth, lineInfo, textBoxDataMap, verticalPositionCache, wordMeasurements); + setLogicalWidthForTextRun(lineBox, run, renderText, totalLogicalWidth, lineInfo, textBoxDataMap, verticalPositionCache, wordMeasurements); } else { - isAfterExpansion = false; - if (!r->renderer().isRenderInline()) { - RenderBox& renderBox = toRenderBox(r->renderer()); - if (renderBox.isRubyRun()) - setMarginsForRubyRun(r, toRenderRubyRun(renderBox), previousObject, lineInfo); - r->box()->setLogicalWidth(logicalWidthForChild(renderBox)); + canHangPunctuationAtStart = false; + 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; + encounteredJustifiedRuby = true; + computeExpansionOpportunities(*rubyBase, downcast<InlineTextBox>(*leafChild), nullptr, nullptr, + downcast<RenderText>(leafChild->renderer()).stringView(), leafChild->direction()); + } + } + } + + 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->box()->logicalWidth(); - previousObject = &r->renderer(); + totalLogicalWidth += run->box()->logicalWidth(); + previousRun = run; } if (isAfterExpansion && !expansionOpportunities.isEmpty()) { - expansionOpportunities.last()--; - expansionOpportunityCount--; + // FIXME: see <webkit.org/b/139393#c11> + int lastValidExpansionOpportunitiesIndex = expansionOpportunities.size() - 1; + while (lastValidExpansionOpportunitiesIndex >= 0 && !expansionOpportunities.at(lastValidExpansionOpportunitiesIndex)) + --lastValidExpansionOpportunitiesIndex; + if (lastValidExpansionOpportunitiesIndex >= 0) { + ASSERT(expansionOpportunities.at(lastValidExpansionOpportunitiesIndex)); + expansionOpportunities.at(lastValidExpansionOpportunitiesIndex)--; + 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 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, @@ -763,30 +993,33 @@ void RenderBlockFlow::computeBlockDirectionPositionsForLine(RootInlineBox* lineB 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->box()); - if (!r->box()) + for (auto* run = firstRun; run; run = run->next()) { + ASSERT(run->box()); + if (!run->box()) continue; // Skip runs with no line boxes. - InlineBox& box = *r->box(); - // Align positioned boxes with the top of the line box. This is // a reasonable approximation of an appropriate y position. - if (r->renderer().isOutOfFlowPositioned()) - 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->renderer().isText()) - toRenderText(r->renderer()).positionLineBox(toInlineTextBox(box)); - else if (r->renderer().isBox()) - toRenderBox(r->renderer()).positionLineBox(toInlineElementBox(box)); - else if (r->renderer().isLineBreak()) - toRenderLineBreak(r->renderer()).replaceInlineBoxWrapper(toInlineElementBox(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, const RenderText& renderer) @@ -801,9 +1034,9 @@ static inline bool isCollapsibleSpace(UChar character, const RenderText& rendere } template <typename CharacterType> -static inline int findFirstTrailingSpace(const RenderText& lastText, const CharacterType* characters, int start, int stop) +static inline unsigned findFirstTrailingSpace(const RenderText& lastText, const CharacterType* characters, unsigned start, unsigned stop) { - int firstSpace = stop; + unsigned firstSpace = stop; while (firstSpace > start) { UChar current = characters[firstSpace - 1]; if (!isCollapsibleSpace(current, lastText)) @@ -819,22 +1052,22 @@ inline BidiRun* RenderBlockFlow::handleTrailingSpaces(BidiRunList<BidiRun>& bidi if (!bidiRuns.runCount() || !bidiRuns.logicallyLastRun()->renderer().style().breakOnlyAfterWhiteSpace() || !bidiRuns.logicallyLastRun()->renderer().style().autoWrap()) - return 0; + return nullptr; BidiRun* trailingSpaceRun = bidiRuns.logicallyLastRun(); const RenderObject& lastObject = trailingSpaceRun->renderer(); - if (!lastObject.isText()) - return 0; + if (!is<RenderText>(lastObject)) + return nullptr; - const RenderText& lastText = toRenderText(lastObject); - int firstSpace; + const RenderText& lastText = downcast<RenderText>(lastObject); + unsigned firstSpace; if (lastText.is8Bit()) firstSpace = findFirstTrailingSpace(lastText, lastText.characters8(), trailingSpaceRun->start(), trailingSpaceRun->stop()); else firstSpace = findFirstTrailingSpace(lastText, lastText.characters16(), trailingSpaceRun->start(), trailingSpaceRun->stop()); if (firstSpace == trailingSpaceRun->stop()) - return 0; + return nullptr; TextDirection direction = style().direction(); bool shouldReorder = trailingSpaceRun != (direction == LTR ? bidiRuns.lastRun() : bidiRuns.firstRun()); @@ -843,13 +1076,13 @@ inline BidiRun* RenderBlockFlow::handleTrailingSpaces(BidiRunList<BidiRun>& bidi while (BidiContext* parent = baseContext->parent()) baseContext = parent; - BidiRun* newTrailingRun = new BidiRun(firstSpace, trailingSpaceRun->m_stop, trailingSpaceRun->renderer(), baseContext, U_OTHER_NEUTRAL); + std::unique_ptr<BidiRun> newTrailingRun = std::make_unique<BidiRun>(firstSpace, trailingSpaceRun->m_stop, trailingSpaceRun->renderer(), baseContext, U_OTHER_NEUTRAL); trailingSpaceRun->m_stop = firstSpace; + trailingSpaceRun = newTrailingRun.get(); if (direction == LTR) - bidiRuns.addRun(newTrailingRun); + bidiRuns.appendRun(WTFMove(newTrailingRun)); else - bidiRuns.prependRun(newTrailingRun); - trailingSpaceRun = newTrailingRun; + bidiRuns.prependRun(WTFMove(newTrailingRun)); return trailingSpaceRun; } if (!shouldReorder) @@ -865,22 +1098,32 @@ inline BidiRun* RenderBlockFlow::handleTrailingSpaces(BidiRunList<BidiRun>& bidi return trailingSpaceRun; } -void RenderBlockFlow::appendFloatingObjectToLastLine(FloatingObject* floatingObject) +void RenderBlockFlow::appendFloatingObjectToLastLine(FloatingObject& floatingObject) { - ASSERT(!floatingObject->originatingLine()); - floatingObject->setOriginatingLine(lastRootBox()); - lastRootBox()->appendFloat(floatingObject->renderer()); + ASSERT_WITH_SECURITY_IMPLICATION(!floatingObject.originatingLine()); + floatingObject.setOriginatingLine(lastRootBox()); + lastRootBox()->appendFloat(floatingObject.renderer()); } -static inline void setUpResolverToResumeInIsolate(InlineBidiResolver& resolver, RenderObject* root, RenderObject* startObject) +static inline void notifyResolverToResumeInIsolate(InlineBidiResolver& resolver, RenderObject* root, RenderObject* startObject) { if (root != startObject) { RenderObject* parent = startObject->parent(); - setUpResolverToResumeInIsolate(resolver, root, 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_whitespaceCollapsingState + resolver.whitespaceCollapsingState() = topResolver.whitespaceCollapsingState(); + resolver.whitespaceCollapsingState().setCurrentTransition(topResolver.whitespaceCollapsingTransitionForIsolatedRun(isolatedRun)); + + // Set up m_nestedIsolateCount + notifyResolverToResumeInIsolate(resolver, root, startObject); +} + // FIXME: BidiResolver should have this logic. static inline void constructBidiRunsForSegment(InlineBidiResolver& topResolver, BidiRunList<BidiRun>& bidiRuns, const InlineIterator& endOfRuns, VisualDirectionOverride override, bool previousLineBrokeCleanly) { @@ -893,36 +1136,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& startObject = isolatedRun->renderer(); + 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(highestContainingIsolateWithinRoot(startObject, currentRoot)); + RenderInline* isolatedInline = downcast<RenderInline>(highestContainingIsolateWithinRoot(startObject, currentRoot)); ASSERT(isolatedInline); InlineBidiResolver isolatedResolver; EUnicodeBidi unicodeBidi = isolatedInline->style().unicodeBidi(); TextDirection direction; if (unicodeBidi == Plaintext) - determineDirectionality(direction, InlineIterator(isolatedInline, &isolatedRun->renderer(), 0)); + determineDirectionality(direction, InlineIterator(isolatedInline, &isolatedRun.object, 0)); else { ASSERT(unicodeBidi == Isolate || unicodeBidi == IsolateOverride); direction = isolatedInline->style().direction(); } isolatedResolver.setStatus(BidiStatus(direction, isOverride(unicodeBidi))); - setUpResolverToResumeInIsolate(isolatedResolver, isolatedInline, &startObject); + 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, &startObject, 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(). @@ -934,74 +1178,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(); - currentRoot = isolatedInline; - } - } -} - -static inline void constructBidiRunsForLine(const RenderBlockFlow* 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.renderer()); - BidiRun* segmentMarker = createRun(segmentStart.offset(), segmentStart.offset(), segmentStart.renderer(), topResolver); - segmentMarker->m_startsSegment = true; - bidiRuns.addRun(segmentMarker); - // Do not collapse midpoints between segments - topResolver.midpointState().setBetweenMidpoints(false); + while (!isolatedResolver.isolatedRuns().isEmpty()) { + auto runWithContext = WTFMove(isolatedResolver.isolatedRuns().last()); + isolatedResolver.isolatedRuns().removeLast(); + topResolver.setWhitespaceCollapsingTransitionForIsolatedRun(runWithContext.runToReplace, isolatedResolver.whitespaceCollapsingTransitionForIsolatedRun(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* RenderBlockFlow::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.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; @@ -1012,7 +1218,6 @@ RootInlineBox* RenderBlockFlow::createLineBoxesFromBidiRuns(BidiRunList<BidiRun> // 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 @@ -1020,18 +1225,12 @@ RootInlineBox* RenderBlockFlow::createLineBoxesFromBidiRuns(BidiRunList<BidiRun> // text selection in RTL boxes would not work as expected. if (isSVGRootInlineBox) { ASSERT_WITH_SECURITY_IMPLICATION(isSVGText()); - toSVGRootInlineBox(lineBox)->computePerCharacterLayoutInformation(); + 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; } @@ -1048,11 +1247,28 @@ static void deleteLineRange(LineLayoutState& layoutState, RootInlineBox* startLi } } +static void repaintDirtyFloats(LineLayoutState::FloatList& floats) +{ + // Floats that did not have layout did not repaint when we laid them out. They would have + // painted by now if they had moved, but if they stayed at (0, 0), they still need to be + // painted. + for (auto& floatBox : floats) { + if (floatBox->everHadLayout()) + continue; + auto& box = floatBox->renderer(); + if (!box.x() && !box.y() && box.checkForRepaintDuringLayout()) + box.repaint(); + } +} + 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) { @@ -1065,17 +1281,17 @@ void RenderBlockFlow::layoutRunsAndFloats(LineLayoutState& layoutState, bool has // determineStartPosition first will break fast/repaint/line-flow-with-floats-9.html. if (layoutState.isFullLayout() && hasInlineChild && !selfNeedsLayout()) { setNeedsLayout(MarkOnlyThis); // Mark as needing a full layout to force us to repaint. - if (!view().doingFullRepaint() && hasLayer()) { + if (!view().doingFullRepaint() && hasSelfPaintingLayer() && layer()->hasComputedRepaintRect()) { // 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().get()); + layoutState.floatList().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. @@ -1101,24 +1317,14 @@ void RenderBlockFlow::layoutRunsAndFloats(LineLayoutState& layoutState, bool has if (lastObject->isBR()) { EClear clear = lastObject->style().clear(); if (clear != CNONE) - newLine(clear); + clearFloats(clear); } } } layoutRunsAndFloatsInRange(layoutState, resolver, cleanLineStart, cleanLineBidiStatus, consecutiveHyphenatedLines); linkToEndLineIfNeeded(layoutState); - repaintDirtyFloats(layoutState.floats()); -} - -RenderTextInfo::RenderTextInfo() - : m_text(0) - , m_font(0) -{ -} - -RenderTextInfo::~RenderTextInfo() -{ + repaintDirtyFloats(layoutState.floatList()); } // Before restarting the layout loop with a new logicalHeight, remove all floats that were added and reset the resolver. @@ -1130,175 +1336,11 @@ inline const InlineIterator& RenderBlockFlow::restartLayoutRunsAndFloatsInRange( return oldEnd; } -#if ENABLE(CSS_SHAPES) -static inline void pushShapeContentOverflowBelowTheContentBox(RenderBlockFlow* block, ShapeInsideInfo* shapeInsideInfo, LayoutUnit lineTop, LayoutUnit lineHeight) -{ - ASSERT(shapeInsideInfo); - - LayoutUnit logicalLineBottom = lineTop + lineHeight; - LayoutUnit shapeLogicalBottom = shapeInsideInfo->shapeLogicalBottom(); - LayoutUnit shapeContainingBlockLogicalHeight = shapeInsideInfo->shapeContainingBlockLogicalHeight(); - - bool isOverflowPositionedAlready = (shapeContainingBlockLogicalHeight - 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 || !shapeContainingBlockLogicalHeight || isOverflowPositionedAlready) - return; - - LayoutUnit newLogicalHeight = block->logicalHeight() + (shapeContainingBlockLogicalHeight - (lineTop + shapeInsideInfo->owner().borderAndPaddingAfter())); - block->setLogicalHeight(newLogicalHeight); -} - -void RenderBlockFlow::updateShapeAndSegmentsForCurrentLine(ShapeInsideInfo*& shapeInsideInfo, const LayoutSize& logicalOffsetFromShapeContainer, LineLayoutState& layoutState) -{ - if (layoutState.flowThread()) - return updateShapeAndSegmentsForCurrentLineInFlowThread(shapeInsideInfo, layoutState); - - if (!shapeInsideInfo) - return; - - LayoutUnit lineTop = logicalHeight() + logicalOffsetFromShapeContainer.height(); - LayoutUnit lineLeft = logicalOffsetFromShapeContainer.width(); - 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->updateSegmentsForLine(LayoutSize(lineLeft, lineTop), lineHeight); - - pushShapeContentOverflowBelowTheContentBox(this, shapeInsideInfo, lineTop, lineHeight); -} - -void RenderBlockFlow::updateShapeAndSegmentsForCurrentLineInFlowThread(ShapeInsideInfo*& shapeInsideInfo, LineLayoutState& layoutState) -{ - ASSERT(layoutState.flowThread()); - - RenderRegion* currentRegion = regionAtBlockOffset(logicalHeight()); - if (!currentRegion || !currentRegion->logicalHeight()) - return; - - shapeInsideInfo = currentRegion->shapeInsideInfo(); - - RenderRegion* nextRegion = 0; - if (!currentRegion->isLastRegion()) { - RenderRegionList regionList = layoutState.flowThread()->renderRegionList(); - auto it = regionList.find(currentRegion); - nextRegion = *(++it); - } - - // We only want to deal regions with shapes, so we check if the next region has a shape - if (!shapeInsideInfo && nextRegion && !nextRegion->shapeInsideInfo()) - return; - - LayoutUnit lineHeight = this->lineHeight(layoutState.lineInfo().isFirstLine(), isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); - LayoutUnit logicalLineTopInFlowThread = logicalHeight() + offsetFromLogicalTopOfFirstPage(); - LayoutUnit logicalLineBottomInFlowThread = logicalLineTopInFlowThread + lineHeight; - LayoutUnit logicalRegionTopInFlowThread = currentRegion->logicalTopForFlowThreadContent(); - LayoutUnit logicalRegionBottomInFlowThread = logicalRegionTopInFlowThread + currentRegion->logicalHeight() - currentRegion->borderAndPaddingBefore() - currentRegion->borderAndPaddingAfter(); - - LayoutUnit shapeBottomInFlowThread = LayoutUnit::max(); - if (shapeInsideInfo) - shapeBottomInFlowThread = shapeInsideInfo->shapeLogicalBottom() + currentRegion->logicalTopForFlowThreadContent(); - - bool lineOverLapsWithShapeBottom = shapeBottomInFlowThread < logicalLineBottomInFlowThread; - bool lineTopAdjustedIntoNextRegion = layoutState.adjustedLogicalLineTop() >= currentRegion->logicalHeight(); - bool lineOverLapsWithRegionBottom = logicalLineBottomInFlowThread > logicalRegionBottomInFlowThread || lineTopAdjustedIntoNextRegion; - bool overFlowsToNextRegion = nextRegion && (lineOverLapsWithShapeBottom || lineOverLapsWithRegionBottom); - - // If the line is between two shapes/regions we position the line to the top of the next shape/region - if (overFlowsToNextRegion) { - ASSERT(currentRegion != nextRegion); - LayoutUnit deltaToNextRegion = logicalRegionBottomInFlowThread - logicalLineTopInFlowThread; - setLogicalHeight(logicalHeight() + deltaToNextRegion); - - currentRegion = nextRegion; - shapeInsideInfo = currentRegion->shapeInsideInfo(); - - logicalLineTopInFlowThread = logicalHeight() + offsetFromLogicalTopOfFirstPage(); - logicalLineBottomInFlowThread = logicalLineTopInFlowThread + lineHeight; - logicalRegionTopInFlowThread = currentRegion->logicalTopForFlowThreadContent(); - logicalRegionBottomInFlowThread = logicalRegionTopInFlowThread + currentRegion->logicalHeight() - currentRegion->borderAndPaddingBefore() - currentRegion->borderAndPaddingAfter(); - - if (lineTopAdjustedIntoNextRegion) - layoutState.setAdjustedLogicalLineTop(0); - } - - if (!shapeInsideInfo) - return; - - bool isFirstLineInRegion = logicalLineBottomInFlowThread <= (logicalRegionTopInFlowThread + lineHeight); - bool isFirstLineAdjusted = (logicalLineTopInFlowThread - logicalRegionTopInFlowThread) < (layoutState.adjustedLogicalLineTop() - currentRegion->borderAndPaddingBefore()); - // We position the first line to the top of the shape in the region or to the previously adjusted position in the shape - if (isFirstLineInRegion || isFirstLineAdjusted) { - LayoutUnit shapeTopOffset = layoutState.adjustedLogicalLineTop(); - - if (!shapeTopOffset && (shapeInsideInfo->shapeLogicalTop() > 0)) - 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(); - - // FIXME: 118571 - Shape inside on a region does not yet take into account its padding for nested flow blocks - shapeInsideInfo->updateSegmentsForLine(LayoutSize(0, lineTop), lineHeight); - - if (currentRegion->isLastRegion()) - pushShapeContentOverflowBelowTheContentBox(this, shapeInsideInfo, lineTop, lineHeight); -} - -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(); -} - -bool RenderBlockFlow::adjustLogicalLineTopAndLogicalHeightIfNeeded(ShapeInsideInfo* shapeInsideInfo, LayoutUnit absoluteLogicalTop, LineLayoutState& layoutState, InlineBidiResolver& resolver, FloatingObject* lastFloatFromPreviousLine, InlineIterator& end, WordMeasurements& wordMeasurements) -{ - LayoutUnit adjustedLogicalLineTop = adjustLogicalLineTop(shapeInsideInfo, resolver.position(), end, wordMeasurements); - - if (shapeInsideInfo && containsFloats()) { - lastFloatFromPreviousLine = m_floatingObjects->set().last().get(); - if (!wordMeasurements.size()) { - LayoutUnit floatLogicalTopOffset = shapeInsideInfo->computeFirstFitPositionForFloat(logicalSizeForFloat(lastFloatFromPreviousLine)); - if (logicalHeight() < floatLogicalTopOffset) - adjustedLogicalLineTop = floatLogicalTopOffset; - } - } - - 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 RenderBlockFlow::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, InlineBidiResolver& resolver, const InlineIterator& cleanLineStart, const BidiStatus& cleanLineBidiStatus, unsigned consecutiveHyphenatedLines) { const RenderStyle& styleToUse = style(); bool paginated = view().layoutState() && view().layoutState()->isPaginated(); - LineMidpointState& lineMidpointState = resolver.midpointState(); + LineWhitespaceCollapsingState& lineWhitespaceCollapsingState = resolver.whitespaceCollapsingState(); InlineIterator end = resolver.position(); bool checkForEndLineMatch = layoutState.endLine(); RenderTextInfo renderTextInfo; @@ -1306,65 +1348,40 @@ void RenderBlockFlow::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, I LineBreaker lineBreaker(*this); -#if ENABLE(CSS_SHAPES) - LayoutSize logicalOffsetFromShapeContainer; - 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. - logicalOffsetFromShapeContainer = logicalOffsetFromShapeAncestorContainer(&shapeInsideInfo->owner()); - } - // Begin layout at the logical top of our shape inside. - if (logicalHeight() + logicalOffsetFromShapeContainer.height() < shapeInsideInfo->shapeLogicalTop()) { - LayoutUnit logicalHeight = shapeInsideInfo->shapeLogicalTop() - logicalOffsetFromShapeContainer.height(); - if (layoutState.flowThread()) - logicalHeight -= shapeInsideInfo->owner().borderAndPaddingBefore(); - setLogicalHeight(logicalHeight); - } - } -#endif - while (!end.atEnd()) { // FIXME: Is this check necessary before the first iteration or can it be moved to the end? if (checkForEndLineMatch) { layoutState.setEndLineMatched(matchedEndLine(layoutState, resolver, cleanLineStart, cleanLineBidiStatus)); if (layoutState.endLineMatched()) { resolver.setPosition(InlineIterator(resolver.position().root(), 0, 0), 0); + layoutState.marginInfo().clearMargin(); break; } } - lineMidpointState.reset(); + lineWhitespaceCollapsingState.reset(); layoutState.lineInfo().setEmpty(true); layoutState.lineInfo().resetRunsFromLeadingWhitespace(); const InlineIterator oldEnd = end; bool isNewUBAParagraph = layoutState.lineInfo().previousLineBrokeCleanly(); - FloatingObject* lastFloatFromPreviousLine = (containsFloats()) ? m_floatingObjects->set().last().get() : 0; + FloatingObject* lastFloatFromPreviousLine = (containsFloats()) ? m_floatingObjects->set().last().get() : nullptr; -#if ENABLE(CSS_SHAPES) - updateShapeAndSegmentsForCurrentLine(shapeInsideInfo, logicalOffsetFromShapeContainer, 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. - resolver.runs().deleteRuns(); + resolver.runs().clear(); resolver.markCurrentRunEmpty(); // FIXME: This can probably be replaced by an ASSERT (or just removed). layoutState.setCheckForFloatsFromLastLine(true); resolver.setPosition(InlineIterator(resolver.position().root(), 0, 0), 0); break; } -#if ENABLE(CSS_SHAPES) - if (adjustLogicalLineTopAndLogicalHeightIfNeeded(shapeInsideInfo, logicalOffsetFromShapeContainer.height(), layoutState, resolver, lastFloatFromPreviousLine, end, wordMeasurements)) - continue; -#endif ASSERT(end != resolver.position()); // This is a short-cut for empty lines. @@ -1381,10 +1398,10 @@ void RenderBlockFlow::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, I } // 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; @@ -1397,33 +1414,63 @@ void RenderBlockFlow::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, I // 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(); + bidiRuns.clear(); resolver.markCurrentRunEmpty(); // FIXME: This can probably be replaced by an ASSERT (or just removed). if (lineBox) { 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(); - 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()) @@ -1433,36 +1480,35 @@ void RenderBlockFlow::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, I } 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(); auto it = floatingObjectSet.begin(); auto end = floatingObjectSet.end(); - if (layoutState.lastFloat()) { - auto lastFloatIterator = floatingObjectSet.find<FloatingObject&, FloatingObjectHashTranslator>(*layoutState.lastFloat()); + if (auto* lastFloat = layoutState.floatList().lastFloat()) { + auto lastFloatIterator = floatingObjectSet.find<FloatingObject&, FloatingObjectHashTranslator>(*lastFloat); ASSERT(lastFloatIterator != end); ++lastFloatIterator; it = lastFloatIterator; } for (; it != end; ++it) { - FloatingObject* f = it->get(); - appendFloatingObjectToLastLine(f); - ASSERT(&f->renderer() == &layoutState.floats()[layoutState.floatIndex()].object); + auto& floatingObject = *it; + appendFloatingObjectToLastLine(*floatingObject); // If a float's geometry has changed, give up on syncing with clean lines. - if (layoutState.floats()[layoutState.floatIndex()].rect != f->frameRect()) + auto* floatWithRect = layoutState.floatList().floatWithRect(floatingObject->renderer()); + if (!floatWithRect || floatWithRect->rect() != floatingObject->frameRect()) checkForEndLineMatch = false; - layoutState.setFloatIndex(layoutState.floatIndex() + 1); } - layoutState.setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last().get() : nullptr); + layoutState.floatList().setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last().get() : nullptr); } - lineMidpointState.reset(); + lineWhitespaceCollapsingState.reset(); resolver.setPosition(end, numberOfIsolateAncestors(end)); } @@ -1525,22 +1571,44 @@ void RenderBlockFlow::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, I markLinesDirtyInBlockRange(lastRootBox()->lineBottomWithLeading(), lineBox->lineBottomWithLeading(), lineBox); } } - clearDidBreakAtLineToAvoidWidow(); } +void RenderBlockFlow::reattachCleanLineFloats(RootInlineBox& cleanLine, LayoutUnit delta, bool isFirstCleanLine) +{ + 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) { - if (layoutState.endLine()) { + auto* firstCleanLine = layoutState.endLine(); + if (firstCleanLine) { if (layoutState.endLineMatched()) { 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); @@ -1548,16 +1616,7 @@ void RenderBlockFlow::linkToEndLineIfNeeded(LineLayoutState& layoutState) } if (layoutState.flowThread()) updateRegionForLine(line); - if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { - for (auto it = cleanLineFloats->begin(), end = cleanLineFloats->end(); it != end; ++it) { - RenderBox* floatingBox = *it; - FloatingObject* floatingObject = insertFloatingObject(*floatingBox); - ASSERT(!floatingObject->originatingLine()); - floatingObject->setOriginatingLine(line); - setLogicalHeight(logicalTopForChild(*floatingBox) - marginBeforeForChild(*floatingBox) + delta); - positionNewFloats(); - } - } + reattachCleanLineFloats(*line, delta, line == firstCleanLine); } setLogicalHeight(lastRootBox()->lineBottomWithLeading()); } else { @@ -1575,7 +1634,7 @@ void RenderBlockFlow::linkToEndLineIfNeeded(LineLayoutState& layoutState) LayoutUnit bottomLayoutOverflow = lastRootBox()->logicalBottomLayoutOverflow(); auto newLineBox = std::make_unique<TrailingFloatsRootInlineBox>(*this); auto trailingFloatsLineBox = newLineBox.get(); - m_lineBoxes.appendLineBox(std::move(newLineBox)); + m_lineBoxes.appendLineBox(WTFMove(newLineBox)); trailingFloatsLineBox->setConstructed(); GlyphOverflowAndFallbackFontsMap textBoxDataMap; VerticalPositionCache verticalPositionCache; @@ -1593,30 +1652,15 @@ void RenderBlockFlow::linkToEndLineIfNeeded(LineLayoutState& layoutState) const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto it = floatingObjectSet.begin(); auto end = floatingObjectSet.end(); - if (layoutState.lastFloat()) { - auto lastFloatIterator = floatingObjectSet.find<FloatingObject&, FloatingObjectHashTranslator>(*layoutState.lastFloat()); + if (auto* lastFloat = layoutState.floatList().lastFloat()) { + auto lastFloatIterator = floatingObjectSet.find<FloatingObject&, FloatingObjectHashTranslator>(*lastFloat); ASSERT(lastFloatIterator != end); ++lastFloatIterator; it = lastFloatIterator; } for (; it != end; ++it) - appendFloatingObjectToLastLine(it->get()); - layoutState.setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last().get() : nullptr); - } -} - -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 - // painted by now if they had moved, but if they stayed at (0, 0), they still need to be - // painted. - for (size_t i = 0; i < floatCount; ++i) { - if (!floats[i].everHadLayout) { - RenderBox& box = floats[i].object; - if (!box.x() && !box.y() && box.checkForRepaintDuringLayout()) - box.repaint(); - } + appendFloatingObjectToLastLine(**it); + layoutState.floatList().setLastFloat(!floatingObjectSet.isEmpty() ? floatingObjectSet.last().get() : nullptr); } } @@ -1636,7 +1680,7 @@ void RenderBlockFlow::layoutLineBoxes(bool relayoutChildren, LayoutUnit& repaint // Figure out if we should clear out our line boxes. // FIXME: Handle resize eventually! bool isFullLayout = !firstRootBox() || selfNeedsLayout() || relayoutChildren || clearLinesForPagination; - LineLayoutState layoutState(isFullLayout, repaintLogicalTop, repaintLogicalBottom, flowThread); + LineLayoutState layoutState(*this, isFullLayout, repaintLogicalTop, repaintLogicalBottom, flowThread); if (isFullLayout) lineBoxes().deleteLineBoxes(); @@ -1669,7 +1713,7 @@ void RenderBlockFlow::layoutLineBoxes(bool relayoutChildren, LayoutUnit& repaint hasInlineChild = true; if (o.isReplaced() || o.isFloating() || o.isOutOfFlowPositioned()) { - RenderBox& box = toRenderBox(o); + RenderBox& box = downcast<RenderBox>(o); if (relayoutChildren || box.hasRelativeDimensions()) box.setChildNeedsLayout(MarkOnlyThis); @@ -1681,18 +1725,20 @@ void RenderBlockFlow::layoutLineBoxes(bool relayoutChildren, LayoutUnit& repaint if (box.isOutOfFlowPositioned()) box.containingBlock()->insertPositionedObject(box); else if (box.isFloating()) - layoutState.floats().append(FloatWithRect(box)); + layoutState.floatList().append(FloatWithRect::create(box)); else if (isFullLayout || box.needsLayout()) { // Replaced element. box.dirtyLineBoxes(isFullLayout); - if (isFullLayout) - replacedChildren.append(&box); - else - box.layoutIfNeeded(); + if (!o.isAnonymousInlineBlock()) { + if (isFullLayout) + replacedChildren.append(&box); + else + box.layoutIfNeeded(); + } } - } else if (o.isTextOrLineBreak() || (o.isRenderInline() && !walker.atEndOfInline())) { - if (o.isRenderInline()) - toRenderInline(o).updateAlwaysCreateLineBoxes(layoutState.isFullLayout()); + } 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.clearNeedsLayout(); @@ -1714,9 +1760,17 @@ void RenderBlockFlow::layoutLineBoxes(bool relayoutChildren, LayoutUnit& repaint else lastLineAnnotationsAdjustment = lastRootBox()->computeOverAnnotationAdjustment(lowestAllowedPosition); } - - // Now add in the bottom border/padding. - setLogicalHeight(logicalHeight() + lastLineAnnotationsAdjustment + borderAndPaddingAfter() + scrollbarLogicalHeight()); + + // 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); if (!firstRootBox() && hasLineIfEmpty()) setLogicalHeight(logicalHeight() + lineHeight(true, isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes)); @@ -1727,146 +1781,146 @@ void RenderBlockFlow::layoutLineBoxes(bool relayoutChildren, LayoutUnit& repaint checkLinesForTextOverflow(); } -void RenderBlockFlow::checkFloatsInCleanLine(RootInlineBox* line, Vector<FloatWithRect>& floats, size_t& floatIndex, bool& encounteredNewFloat, bool& dirtiedByFloat) +void RenderBlockFlow::checkFloatInCleanLine(RootInlineBox& cleanLine, RenderBox& floatBoxOnCleanLine, FloatWithRect& matchingFloatWithRect, + bool& encounteredNewFloat, bool& dirtiedByFloat) { - Vector<RenderBox*>* cleanLineFloats = line->floatsPtr(); - if (!cleanLineFloats) - return; - - if (!floats.size()) { + ASSERT_WITH_SECURITY_IMPLICATION(!floatBoxOnCleanLine.style().deletionHasBegun()); + if (&matchingFloatWithRect.renderer() != &floatBoxOnCleanLine) { encounteredNewFloat = true; return; } + floatBoxOnCleanLine.layoutIfNeeded(); + LayoutRect originalFloatRect = matchingFloatWithRect.rect(); + LayoutSize newSize( + floatBoxOnCleanLine.width() + floatBoxOnCleanLine.horizontalMarginExtent(), + floatBoxOnCleanLine.height() + floatBoxOnCleanLine.verticalMarginExtent()); + + // 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 (originalFloatRect.size() == newSize && (floatBoxOnCleanLine.style().styleType() != FIRST_LETTER || !floatBoxOnCleanLine.style().initialLetterDrop())) + return; - 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()); - ASSERT_WITH_SECURITY_IMPLICATION(floatIndex < floats.size()); - if (&floats[floatIndex].object != floatingBox) { - encounteredNewFloat = true; - return; - } - - if (floats[floatIndex].rect.size() != newSize) { - LayoutUnit floatTop = isHorizontalWritingMode() ? floats[floatIndex].rect.y() : floats[floatIndex].rect.x(); - 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); - dirtiedByFloat = true; - } - floatIndex++; - } + LayoutUnit floatTop = isHorizontalWritingMode() ? originalFloatRect.y() : originalFloatRect.x(); + LayoutUnit floatHeight = isHorizontalWritingMode() ? std::max(originalFloatRect.height(), newSize.height()) + : std::max(originalFloatRect.width(), newSize.width()); + floatHeight = std::min(floatHeight, LayoutUnit::max() - floatTop); + cleanLine.markDirty(); + markLinesDirtyInBlockRange(cleanLine.lineBottomWithLeading(), floatTop + floatHeight, &cleanLine); + LayoutRect newFloatRect = originalFloatRect; + newFloatRect.setSize(newSize); + matchingFloatWithRect.adjustRect(newFloatRect); + dirtiedByFloat = true; } RootInlineBox* RenderBlockFlow::determineStartPosition(LineLayoutState& layoutState, InlineBidiResolver& resolver) { - RootInlineBox* curr = 0; - RootInlineBox* last = 0; + RootInlineBox* currentLine = nullptr; + RootInlineBox* lastLine = nullptr; // FIXME: This entire float-checking block needs to be broken into a new function. + auto& floats = layoutState.floatList(); bool dirtiedByFloat = false; if (!layoutState.isFullLayout()) { // Paginate all of the clean lines. bool paginated = view().layoutState() && view().layoutState()->isPaginated(); LayoutUnit paginationDelta = 0; - size_t floatIndex = 0; - for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextRootBox()) { + auto floatsIterator = floats.begin(); + auto end = floats.end(); + for (currentLine = firstRootBox(); currentLine && !currentLine->isDirty(); currentLine = currentLine->nextRootBox()) { if (paginated) { - if (lineWidthForPaginatedLineChanged(curr, 0, layoutState.flowThread())) { - curr->markDirty(); + if (lineWidthForPaginatedLineChanged(currentLine, 0, layoutState.flowThread())) { + currentLine->markDirty(); break; } - paginationDelta -= curr->paginationStrut(); - adjustLinePositionForPagination(curr, paginationDelta, layoutState.flowThread()); + paginationDelta -= currentLine->paginationStrut(); + bool overflowsRegion; + adjustLinePositionForPagination(currentLine, paginationDelta, overflowsRegion, layoutState.flowThread()); if (paginationDelta) { - if (containsFloats() || !layoutState.floats().isEmpty()) { + if (containsFloats() || !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. layoutState.markForFullLayout(); break; } - layoutState.updateRepaintRangeFromBox(curr, paginationDelta); - curr->adjustBlockDirectionPosition(paginationDelta); + layoutState.updateRepaintRangeFromBox(currentLine, paginationDelta); + currentLine->adjustBlockDirectionPosition(paginationDelta); } if (layoutState.flowThread()) - updateRegionForLine(curr); + updateRegionForLine(currentLine); } - // If a new float has been inserted before this line or before its last known float, just do a full layout. - bool encounteredNewFloat = false; - checkFloatsInCleanLine(curr, layoutState.floats(), floatIndex, encounteredNewFloat, dirtiedByFloat); - if (encounteredNewFloat) - layoutState.markForFullLayout(); - - if (dirtiedByFloat || layoutState.isFullLayout()) - break; + if (auto* cleanLineFloats = currentLine->floatsPtr()) { + // If a new float has been inserted before this line or before its last known float, just do a full layout. + bool encounteredNewFloat = false; + for (auto* floatBoxOnCleanLine : *cleanLineFloats) { + ASSERT(floatsIterator != end); + checkFloatInCleanLine(*currentLine, *floatBoxOnCleanLine, *floatsIterator, encounteredNewFloat, dirtiedByFloat); + ++floatsIterator; + if (floatsIterator == end || encounteredNewFloat) { + layoutState.markForFullLayout(); + break; + } + } + if (dirtiedByFloat || encounteredNewFloat) + break; + } } // Check if a new float has been inserted after the last known float. - if (!curr && floatIndex < layoutState.floats().size()) + if (!currentLine && floatsIterator != end) layoutState.markForFullLayout(); } if (layoutState.isFullLayout()) { m_lineBoxes.deleteLineBoxTree(); - curr = 0; - + currentLine = nullptr; ASSERT(!firstRootBox() && !lastRootBox()); } else { - if (curr) { + if (currentLine) { // We have a dirty line. - if (RootInlineBox* prevRootBox = curr->prevRootBox()) { + if (RootInlineBox* prevRootBox = currentLine->prevRootBox()) { // We have a previous line. - if (!dirtiedByFloat && (!prevRootBox->endsWithBreak() || !prevRootBox->lineBreakObj() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= toRenderText(prevRootBox->lineBreakObj())->textLength()))) + if (!dirtiedByFloat && !currentLine->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; + currentLine = 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(); + lastLine = currentLine ? currentLine->prevRootBox() : lastRootBox(); } - unsigned numCleanFloats = 0; - if (!layoutState.floats().isEmpty()) { + if (!floats.isEmpty()) { LayoutUnit savedLogicalHeight = logicalHeight(); // Restore floats from clean lines. RootInlineBox* line = firstRootBox(); - while (line != curr) { + while (line != currentLine) { if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { for (auto it = cleanLineFloats->begin(), end = cleanLineFloats->end(); it != end; ++it) { - RenderBox* floatingBox = *it; - FloatingObject* floatingObject = insertFloatingObject(*floatingBox); - ASSERT(!floatingObject->originatingLine()); + auto* floatingBox = *it; + auto* floatingObject = insertFloatingObject(*floatingBox); + ASSERT_WITH_SECURITY_IMPLICATION(!floatingObject->originatingLine()); floatingObject->setOriginatingLine(line); setLogicalHeight(logicalTopForChild(*floatingBox) - marginBeforeForChild(*floatingBox)); positionNewFloats(); - ASSERT(&layoutState.floats()[numCleanFloats].object == floatingBox); - numCleanFloats++; + floats.setLastCleanFloat(*floatingBox); } } line = line->nextRootBox(); } setLogicalHeight(savedLogicalHeight); } - layoutState.setFloatIndex(numCleanFloats); - layoutState.lineInfo().setFirstLine(!last); - layoutState.lineInfo().setPreviousLineBrokeCleanly(!last || last->endsWithBreak()); + layoutState.lineInfo().setFirstLine(!lastLine); + layoutState.lineInfo().setPreviousLineBrokeCleanly(!lastLine || lastLine->endsWithBreak()); - if (last) { - setLogicalHeight(last->lineBottomWithLeading()); - InlineIterator iter = InlineIterator(this, last->lineBreakObj(), last->lineBreakPos()); + if (lastLine) { + setLogicalHeight(lastLine->lineBottomWithLeading()); + InlineIterator iter = InlineIterator(this, lastLine->lineBreakObj(), lastLine->lineBreakPos()); resolver.setPosition(iter, numberOfIsolateAncestors(iter)); - resolver.setStatus(last->lineBreakBidiStatus()); + resolver.setStatus(lastLine->lineBreakBidiStatus()); } else { TextDirection direction = style().direction(); if (style().unicodeBidi() == Plaintext) @@ -1875,44 +1929,59 @@ RootInlineBox* RenderBlockFlow::determineStartPosition(LineLayoutState& layoutSt InlineIterator iter = InlineIterator(this, bidiFirstSkippingEmptyInlines(*this, &resolver), 0); resolver.setPosition(iter, numberOfIsolateAncestors(iter)); } - return curr; + return currentLine; } void RenderBlockFlow::determineEndPosition(LineLayoutState& layoutState, RootInlineBox* startLine, InlineIterator& cleanLineStart, BidiStatus& cleanLineBidiStatus) { + auto iteratorForFirstDirtyFloat = [](LineLayoutState::FloatList& floats) { + auto lastCleanFloat = floats.lastCleanFloat(); + if (!lastCleanFloat) + return floats.begin(); + auto* lastCleanFloatWithRect = floats.floatWithRect(*lastCleanFloat); + ASSERT(lastCleanFloatWithRect); + return ++floats.find(*lastCleanFloatWithRect); + }; + ASSERT(!layoutState.endLine()); - size_t floatIndex = layoutState.floatIndex(); - RootInlineBox* last = 0; - for (RootInlineBox* curr = startLine->nextRootBox(); curr; curr = curr->nextRootBox()) { - if (!curr->isDirty()) { - bool encounteredNewFloat = false; - bool dirtiedByFloat = false; - checkFloatsInCleanLine(curr, layoutState.floats(), floatIndex, encounteredNewFloat, dirtiedByFloat); - if (encounteredNewFloat) - return; + auto floatsIterator = iteratorForFirstDirtyFloat(layoutState.floatList()); + auto end = layoutState.floatList().end(); + RootInlineBox* lastLine = nullptr; + for (RootInlineBox* currentLine = startLine->nextRootBox(); currentLine; currentLine = currentLine->nextRootBox()) { + if (!currentLine->isDirty()) { + if (auto* cleanLineFloats = currentLine->floatsPtr()) { + bool encounteredNewFloat = false; + bool dirtiedByFloat = false; + for (auto* floatBoxOnCleanLine : *cleanLineFloats) { + ASSERT(floatsIterator != end); + checkFloatInCleanLine(*currentLine, *floatBoxOnCleanLine, *floatsIterator, encounteredNewFloat, dirtiedByFloat); + ++floatsIterator; + if (floatsIterator == end || encounteredNewFloat) + return; + } + } } - if (curr->isDirty()) - last = 0; - else if (!last) - last = curr; + if (currentLine->isDirty()) + lastLine = nullptr; + else if (!lastLine) + lastLine = currentLine; } - if (!last) + if (!lastLine) return; // At this point, |last| is the first line in a run of clean lines that ends with the last line // in the block. + RootInlineBox* previousLine = lastLine->prevRootBox(); + cleanLineStart = InlineIterator(this, previousLine->lineBreakObj(), previousLine->lineBreakPos()); + cleanLineBidiStatus = previousLine->lineBreakBidiStatus(); + layoutState.setEndLineLogicalTop(previousLine->lineBottomWithLeading()); - RootInlineBox* prev = last->prevRootBox(); - cleanLineStart = InlineIterator(this, prev->lineBreakObj(), prev->lineBreakPos()); - cleanLineBidiStatus = prev->lineBreakBidiStatus(); - layoutState.setEndLineLogicalTop(prev->lineBottomWithLeading()); - - for (RootInlineBox* line = last; line; line = line->nextRootBox()) - line->extractLine(); // Disconnect all line boxes from their render objects while preserving - // their connections to one another. - - layoutState.setEndLine(last); + for (RootInlineBox* line = lastLine; line; line = line->nextRootBox()) { + // Disconnect all line boxes from their render objects while preserving their connections to one another. + line->extractLine(); + } + layoutState.setEndLine(lastLine); } bool RenderBlockFlow::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutState) @@ -1928,8 +1997,9 @@ bool RenderBlockFlow::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutS // 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())) @@ -1952,7 +2022,7 @@ bool RenderBlockFlow::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutS const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); auto end = floatingObjectSet.end(); for (auto it = floatingObjectSet.begin(); it != end; ++it) { - FloatingObject* floatingObject = it->get(); + const auto& floatingObject = *it->get(); if (logicalBottomForFloat(floatingObject) >= logicalTop && logicalBottomForFloat(floatingObject) < logicalBottom) return false; } @@ -1982,11 +2052,11 @@ bool RenderBlockFlow::matchedEndLine(LineLayoutState& layoutState, const InlineB // 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().renderer() && line->lineBreakPos() == resolver.position().offset()) { + 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. @@ -2033,13 +2103,14 @@ void RenderBlockFlow::addOverflowFromInlineChildren() endPadding = 1; for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { addLayoutOverflow(curr->paddedLayoutOverflowRect(endPadding)); - RenderRegion* region = curr->containingRegion(); + RenderRegion* region = flowThreadContainingBlock() ? curr->containingRegion() : nullptr; if (region) region->addLayoutOverflowForBox(this, curr->paddedLayoutOverflowRect(endPadding)); if (!hasOverflowClip()) { - addVisualOverflow(curr->visualOverflowRect(curr->lineTop(), curr->lineBottom())); + LayoutRect childVisualOverflowRect = curr->visualOverflowRect(curr->lineTop(), curr->lineBottom()); + addVisualOverflow(childVisualOverflowRect); if (region) - region->addVisualOverflowForBox(this, curr->visualOverflowRect(curr->lineTop(), curr->lineBottom())); + region->addVisualOverflowForBox(this, childVisualOverflowRect); } } } @@ -2048,23 +2119,23 @@ void RenderBlockFlow::deleteEllipsisLineBoxes() { ETextAlign textAlign = style().textAlign(); bool ltr = style().isLeftToRightDirection(); - bool firstLine = true; + 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; } } @@ -2072,11 +2143,11 @@ 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(&horizontalEllipsis, 1, firstLineStyle())); + float ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.width(constructTextRun(&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 @@ -2086,25 +2157,23 @@ void RenderBlockFlow::checkLinesForTextOverflow() 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 @@ -2115,7 +2184,7 @@ void RenderBlockFlow::checkLinesForTextOverflow() } } -bool RenderBlockFlow::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; @@ -2125,14 +2194,14 @@ bool RenderBlockFlow::positionNewFloatOnLine(FloatingObject* newFloat, FloatingO // 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->paginationStrut() || !lineInfo.previousLineBrokeCleanly() || !lineInfo.isEmpty()) + if (!newFloat.paginationStrut() || !lineInfo.previousLineBrokeCleanly() || !lineInfo.isEmpty()) return true; const FloatingObjectSet& floatingObjectSet = m_floatingObjects->set(); - ASSERT(floatingObjectSet.last().get() == newFloat); + ASSERT(floatingObjectSet.last().get() == &newFloat); LayoutUnit floatLogicalTop = logicalTopForFloat(newFloat); - int paginationStrut = newFloat->paginationStrut(); + LayoutUnit paginationStrut = newFloat.paginationStrut(); if (floatLogicalTop - paginationStrut != logicalHeight() + lineInfo.floatPaginationStrut()) return true; @@ -2142,26 +2211,26 @@ bool RenderBlockFlow::positionNewFloatOnLine(FloatingObject* newFloat, FloatingO auto begin = floatingObjectSet.begin(); while (it != begin) { --it; - FloatingObject* floatingObject = it->get(); - if (floatingObject == lastFloatFromPreviousLine) + auto& floatingObject = *it->get(); + if (&floatingObject == lastFloatFromPreviousLine) break; if (logicalTopForFloat(floatingObject) == logicalHeight() + lineInfo.floatPaginationStrut()) { - floatingObject->setPaginationStrut(paginationStrut + floatingObject->paginationStrut()); - RenderBox& floatBox = floatingObject->renderer(); + floatingObject.setPaginationStrut(paginationStrut + floatingObject.paginationStrut()); + RenderBox& floatBox = floatingObject.renderer(); setLogicalTopForChild(floatBox, logicalTopForChild(floatBox) + marginBeforeForChild(floatBox) + paginationStrut); - if (updateRegionRangeForBoxChild(floatingObject->renderer())) + if (updateRegionRangeForBoxChild(floatBox)) floatBox.setNeedsLayout(MarkOnlyThis); - else if (floatBox.isRenderBlock()) - toRenderBlock(floatBox).setChildNeedsLayout(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(floatingObject); - m_floatingObjects->removePlacedObject(floatingObject); + m_floatingObjects->removePlacedObject(&floatingObject); setLogicalTopForFloat(floatingObject, oldLogicalTop + paginationStrut); - m_floatingObjects->addPlacedObject(floatingObject); + m_floatingObjects->addPlacedObject(&floatingObject); } } @@ -2171,22 +2240,39 @@ bool RenderBlockFlow::positionNewFloatOnLine(FloatingObject* newFloat, FloatingO return true; } -LayoutUnit RenderBlockFlow::startAlignedOffsetForLine(LayoutUnit position, bool firstLine) +LayoutUnit RenderBlockFlow::startAlignedOffsetForLine(LayoutUnit position, IndentTextOrNot shouldIndentText) { 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 (textAlign == TASTART || document().settings()->useLegacyTextAlignPositionedElementBehavior()) // FIXME: Handle TAEND here - return startOffsetForLine(position, firstLine); + if (shouldApplyIndentText || 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()) return logicalWidth() - logicalLeft; @@ -2197,10 +2283,14 @@ void RenderBlockFlow::updateRegionForLine(RootInlineBox* lineBox) const { ASSERT(lineBox); - if (auto containingRegion = regionAtBlockOffset(lineBox->lineTopWithLeading())) - lineBox->setContainingRegion(*containingRegion); - else + if (!hasRegionRangeInFlowThread()) lineBox->clearContainingRegion(); + else { + if (auto containingRegion = regionAtBlockOffset(lineBox->lineTopWithLeading())) + lineBox->setContainingRegion(*containingRegion); + else + lineBox->clearContainingRegion(); + } RootInlineBox* prevLineBox = lineBox->prevRootBox(); if (!prevLineBox) @@ -2213,4 +2303,46 @@ void RenderBlockFlow::updateRegionForLine(RootInlineBox* lineBox) const 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()); + } + } + + // 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; + + // We already handled top of block with startLine. + if (stopLine == firstRootBox()) + return; + + // 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()); + + // 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()); +} + } |