diff options
Diffstat (limited to 'Source/WebCore/rendering/line')
-rw-r--r-- | Source/WebCore/rendering/line/BreakingContext.h | 1367 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/BreakingContextInlineHeaders.h | 1139 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/LineBreaker.cpp | 81 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/LineBreaker.h | 28 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/LineInfo.h | 7 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/LineInlineHeaders.h | 25 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/LineLayoutState.h | 107 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/LineWidth.cpp | 166 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/LineWidth.h | 59 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/TrailingObjects.cpp | 38 | ||||
-rw-r--r-- | Source/WebCore/rendering/line/TrailingObjects.h | 23 |
11 files changed, 1624 insertions, 1416 deletions
diff --git a/Source/WebCore/rendering/line/BreakingContext.h b/Source/WebCore/rendering/line/BreakingContext.h new file mode 100644 index 000000000..d94d17a65 --- /dev/null +++ b/Source/WebCore/rendering/line/BreakingContext.h @@ -0,0 +1,1367 @@ +/* + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All right reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2013 ChangSeok Oh <shivamidow@gmail.com> + * Copyright (C) 2013 Adobe Systems Inc. All right reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#pragma once + +#include "BreakLines.h" +#include "Hyphenation.h" +#include "LineBreaker.h" +#include "LineInfo.h" +#include "LineLayoutState.h" +#include "LineWidth.h" +#include "RenderCombineText.h" +#include "RenderCounter.h" +#include "RenderInline.h" +#include "RenderLineBreak.h" +#include "RenderListMarker.h" +#include "RenderRubyRun.h" +#include "RenderSVGInlineText.h" +#include "TrailingObjects.h" +#include <wtf/Optional.h> +#include <wtf/text/StringView.h> +#include <wtf/unicode/CharacterNames.h> + +namespace WebCore { + +// We don't let our line box tree for a single line get any deeper than this. +const unsigned cMaxLineDepth = 200; + +struct WordMeasurement { + WordMeasurement() + : renderer(0) + , width(0) + , startOffset(0) + , endOffset(0) + { + } + + RenderText* renderer; + float width; + unsigned startOffset; + unsigned endOffset; + HashSet<const Font*> fallbackFonts; +}; + +struct WordTrailingSpace { + WordTrailingSpace(const RenderStyle& style, TextLayout* textLayout = nullptr) + : m_style(style) + , m_textLayout(textLayout) + { + } + + std::optional<float> width(HashSet<const Font*>& fallbackFonts) + { + if (m_state == WordTrailingSpaceState::Computed) + return m_width; + + const FontCascade& font = m_style.fontCascade(); + if (font.enableKerning() && !m_textLayout) + m_width = font.width(RenderBlock::constructTextRun(&space, 1, m_style), &fallbackFonts) + font.wordSpacing(); + m_state = WordTrailingSpaceState::Computed; + return m_width; + } + +private: + enum class WordTrailingSpaceState { Uninitialized, Computed }; + WordTrailingSpaceState m_state { WordTrailingSpaceState::Uninitialized }; + std::optional<float> m_width; + const RenderStyle& m_style; + TextLayout* m_textLayout { nullptr }; +}; + +class BreakingContext { +public: + BreakingContext(LineBreaker& lineBreaker, InlineBidiResolver& resolver, LineInfo& inLineInfo, LineLayoutState& layoutState, LineWidth& lineWidth, RenderTextInfo& inRenderTextInfo, FloatingObject* inLastFloatFromPreviousLine, bool appliedStartWidth, RenderBlockFlow& block) + : m_lineBreaker(lineBreaker) + , m_resolver(resolver) + , m_current(resolver.position()) +#if ENABLE(CSS_TRAILING_WORD) + , m_lineBreakHistory(InlineIterator(resolver.position()), block.style().trailingWord() == TrailingWord::PartiallyBalanced ? 5 : 1) +#else + , m_lineBreakHistory(InlineIterator(resolver.position()), 1) +#endif + , m_block(block) + , m_lastObject(m_current.renderer()) + , m_nextObject(nullptr) + , m_currentStyle(nullptr) + , m_blockStyle(block.style()) + , m_lineInfo(inLineInfo) + , m_renderTextInfo(inRenderTextInfo) + , m_lastFloatFromPreviousLine(inLastFloatFromPreviousLine) + , m_width(lineWidth) + , m_lineLayoutState(layoutState) + , m_currWS(NORMAL) + , m_lastWS(NORMAL) + , m_preservesNewline(false) + , m_atStart(true) + , m_ignoringSpaces(false) + , m_currentCharacterIsSpace(false) + , m_currentCharacterIsWS(false) + , m_appliedStartWidth(appliedStartWidth) + , m_includeEndWidth(true) + , m_autoWrap(false) + , m_autoWrapWasEverTrueOnLine(false) + , m_floatsFitOnLine(true) + , m_collapseWhiteSpace(false) + , m_startingNewParagraph(m_lineInfo.previousLineBrokeCleanly()) + , m_allowImagesToBreak(!block.document().inQuirksMode() || !block.isTableCell() || !m_blockStyle.logicalWidth().isIntrinsicOrAuto()) + , m_atEnd(false) + , m_hadUncommittedWidthBeforeCurrent(false) + , m_lineWhitespaceCollapsingState(resolver.whitespaceCollapsingState()) + { + m_lineInfo.setPreviousLineBrokeCleanly(false); + } + + RenderObject* currentObject() { return m_current.renderer(); } + InlineIterator lineBreak() { return m_lineBreakHistory.current(); } + LineWidth& lineWidth() { return m_width; } + bool atEnd() { return m_atEnd; } + + bool fitsOnLineOrHangsAtEnd() const { return m_width.fitsOnLine() || m_hangsAtEnd; } + + void initializeForCurrentObject(); + + void increment(); + + void handleBR(EClear&); + void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects); + void handleFloat(); + void handleEmptyInline(); + void handleReplaced(); + bool handleText(WordMeasurements&, bool& hyphenated, unsigned& consecutiveHyphenatedLines); + bool canBreakAtThisPosition(); + void commitAndUpdateLineBreakIfNeeded(); + InlineIterator handleEndOfLine(); +#if ENABLE(CSS_TRAILING_WORD) + InlineIterator optimalLineBreakLocationForTrailingWord(); +#endif + + float computeAdditionalBetweenWordsWidth(RenderText&, TextLayout*, UChar, WordTrailingSpace&, HashSet<const Font*>& fallbackFonts, WordMeasurements&, const FontCascade&, bool isFixedPitch, unsigned lastSpace, float lastSpaceWordSpacing, float wordSpacingForWordMeasurement, unsigned offset); + + void clearLineBreakIfFitsOnLine(bool ignoringTrailingSpace = false) + { + if (m_width.fitsOnLine(ignoringTrailingSpace) || m_lastWS == NOWRAP || m_hangsAtEnd) + m_lineBreakHistory.clear(); + m_hangsAtEnd = false; + } + + void commitLineBreakClear() + { + m_width.commit(); + m_lineBreakHistory.clear(); + m_hangsAtEnd = false; + } + + void commitLineBreakAtCurrentWidth(RenderObject& object, unsigned offset = 0, std::optional<unsigned> nextBreak = std::optional<unsigned>()) + { + m_width.commit(); + m_lineBreakHistory.moveTo(object, offset, nextBreak); + m_hangsAtEnd = false; + } + +private: + // This class keeps a sliding window of the past n locations for an InlineIterator. + class InlineIteratorHistory : private Vector<InlineIterator, 1> { + public: + InlineIteratorHistory() = delete; + InlineIteratorHistory(const InlineIterator& initial, size_t capacity) + : m_capacity(capacity) + { + ASSERT(capacity > 0); + this->append(initial); + } + + void push(std::function<void(InlineIterator& modifyMe)> updater) + { + ASSERT(!this->isEmpty()); + if (m_capacity != 1) + this->insert(0, InlineIterator(this->at(0))); + updater(this->at(0)); + if (m_capacity != 1) + this->resize(m_capacity); + } + + void update(std::function<void(InlineIterator& modifyMe)> updater) + { + ASSERT(!this->isEmpty()); + updater(this->at(0)); + } + + RenderObject* renderer() const { return this->at(0).renderer(); } + unsigned offset() const { return this->at(0).offset(); } + std::optional<unsigned> nextBreakablePosition() const { return this->at(0).nextBreakablePosition(); } + bool atTextParagraphSeparator() const { return this->at(0).atTextParagraphSeparator(); } + UChar previousInSameNode() const { return this->at(0).previousInSameNode(); } + const InlineIterator& get(size_t i) const { return this->at(i); }; + const InlineIterator& current() const { return get(0); } + size_t historyLength() const { return this->size(); } + + void moveTo(RenderObject& object, unsigned offset, std::optional<unsigned> nextBreak = std::nullopt) + { + push([&](InlineIterator& modifyMe) { + modifyMe.moveTo(object, offset, nextBreak); + }); + } + + void increment() + { + update([](InlineIterator& modifyMe) { + modifyMe.increment(); + }); + } + + void clear() + { + push([](InlineIterator& modifyMe) { + modifyMe.clear(); + }); + } + + private: + const size_t m_capacity; + }; + + LineBreaker& m_lineBreaker; + InlineBidiResolver& m_resolver; + + InlineIterator m_current; + InlineIteratorHistory m_lineBreakHistory; + InlineIterator m_startOfIgnoredSpaces; + + RenderBlockFlow& m_block; + RenderObject* m_lastObject; + RenderObject* m_nextObject; + + const RenderStyle* m_currentStyle; + + // Firefox and Opera will allow a table cell to grow to fit an image inside it under + // very specific circumstances (in order to match common WinIE renderings). + // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.) + const RenderStyle& m_blockStyle; + + LineInfo& m_lineInfo; + + RenderTextInfo& m_renderTextInfo; + + FloatingObject* m_lastFloatFromPreviousLine; + + LineWidth m_width; + + LineLayoutState& m_lineLayoutState; + + EWhiteSpace m_currWS; + EWhiteSpace m_lastWS; + + bool m_preservesNewline; + bool m_atStart; + + // This variable is used only if whitespace isn't set to PRE, and it tells us whether + // or not we are currently ignoring whitespace. + bool m_ignoringSpaces; + + // This variable tracks whether the very last character we saw was a space. We use + // this to detect when we encounter a second space so we know we have to terminate + // a run. + bool m_currentCharacterIsSpace; + bool m_currentCharacterIsWS; + bool m_appliedStartWidth; + bool m_includeEndWidth; + bool m_autoWrap; + bool m_autoWrapWasEverTrueOnLine; + bool m_floatsFitOnLine; + bool m_collapseWhiteSpace; + bool m_startingNewParagraph; + bool m_allowImagesToBreak; + bool m_atEnd; + bool m_hadUncommittedWidthBeforeCurrent; + + bool m_hangsAtEnd { false }; + + LineWhitespaceCollapsingState& m_lineWhitespaceCollapsingState; + + TrailingObjects m_trailingObjects; +}; + +inline void BreakingContext::initializeForCurrentObject() +{ + m_hadUncommittedWidthBeforeCurrent = !!m_width.uncommittedWidth(); + + m_currentStyle = &m_current.renderer()->style(); // FIXME: Should this be &lineStyle(*m_current.renderer(), m_lineInfo); ? + + ASSERT(m_currentStyle); + + m_nextObject = bidiNextSkippingEmptyInlines(m_block, m_current.renderer()); + if (m_nextObject && m_nextObject->parent() && !m_nextObject->parent()->isDescendantOf(m_current.renderer()->parent())) + m_includeEndWidth = true; + + m_currWS = m_current.renderer()->isReplaced() ? m_current.renderer()->parent()->style().whiteSpace() : m_currentStyle->whiteSpace(); + m_lastWS = m_lastObject->isReplaced() ? m_lastObject->parent()->style().whiteSpace() : m_lastObject->style().whiteSpace(); + + m_autoWrap = RenderStyle::autoWrap(m_currWS); + m_autoWrapWasEverTrueOnLine = m_autoWrapWasEverTrueOnLine || m_autoWrap; + + m_preservesNewline = m_current.renderer()->isSVGInlineText() ? false : RenderStyle::preserveNewline(m_currWS); + + m_collapseWhiteSpace = RenderStyle::collapseWhiteSpace(m_currWS); +} + +inline void BreakingContext::increment() +{ + // Clear out our character space bool, since inline <pre>s don't collapse whitespace + // with adjacent inline normal/nowrap spans. + if (!m_collapseWhiteSpace) + m_currentCharacterIsSpace = false; + + if (m_nextObject) + m_current.moveToStartOf(*m_nextObject); + else + m_current.clear(); + m_atStart = false; +} + +inline void BreakingContext::handleBR(EClear& clear) +{ + if (fitsOnLineOrHangsAtEnd()) { + RenderObject& br = *m_current.renderer(); + m_lineBreakHistory.push([&](InlineIterator& modifyMe) { + modifyMe.moveToStartOf(br); + modifyMe.increment(); + }); + + // A <br> always breaks a line, so don't let the line be collapsed + // away. Also, the space at the end of a line with a <br> does not + // get collapsed away. It only does this if the previous line broke + // cleanly. Otherwise the <br> has no effect on whether the line is + // empty or not. + if (m_startingNewParagraph) + m_lineInfo.setEmpty(false, &m_block, &m_width); + m_trailingObjects.clear(); + m_lineInfo.setPreviousLineBrokeCleanly(true); + + // A <br> with clearance always needs a linebox in case the lines below it get dirtied later and + // need to check for floats to clear - so if we're ignoring spaces, stop ignoring them and add a + // run for this object. + if (m_ignoringSpaces && m_currentStyle->clear() != CNONE) + m_lineWhitespaceCollapsingState.ensureLineBoxInsideIgnoredSpaces(br); + // If we were preceded by collapsing space and are in a right-aligned container we need to ensure the space gets + // collapsed away so that it doesn't push the text out from the container's right-hand edge. + // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results. + else if (m_ignoringSpaces && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) + m_lineWhitespaceCollapsingState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset())); + + if (!m_lineInfo.isEmpty()) + clear = m_currentStyle->clear(); + } + m_atEnd = true; +} + +inline LayoutUnit borderPaddingMarginStart(const RenderInline& child) +{ + return child.marginStart() + child.paddingStart() + child.borderStart(); +} + +inline LayoutUnit borderPaddingMarginEnd(const RenderInline& child) +{ + return child.marginEnd() + child.paddingEnd() + child.borderEnd(); +} + +inline bool shouldAddBorderPaddingMargin(RenderObject* child) +{ + if (!child) + return true; + // When deciding whether we're at the edge of an inline, adjacent collapsed whitespace is the same as no sibling at all. + if (is<RenderText>(*child) && !downcast<RenderText>(*child).textLength()) + return true; +#if ENABLE(CSS_BOX_DECORATION_BREAK) + if (is<RenderLineBreak>(*child) && child->parent()->style().boxDecorationBreak() == DCLONE) + return true; +#endif + return false; +} + +inline RenderObject* previousInFlowSibling(RenderObject* child) +{ + do { + child = child->previousSibling(); + } while (child && child->isOutOfFlowPositioned()); + return child; +} + +inline LayoutUnit inlineLogicalWidth(RenderObject* child, bool checkStartEdge = true, bool checkEndEdge = true) +{ + unsigned lineDepth = 1; + LayoutUnit extraWidth = 0; + RenderElement* parent = child->parent(); + while (is<RenderInline>(*parent) && lineDepth++ < cMaxLineDepth) { + const auto& parentAsRenderInline = downcast<RenderInline>(*parent); + if (!isEmptyInline(parentAsRenderInline)) { + checkStartEdge = checkStartEdge && shouldAddBorderPaddingMargin(previousInFlowSibling(child)); + if (checkStartEdge) + extraWidth += borderPaddingMarginStart(parentAsRenderInline); + checkEndEdge = checkEndEdge && shouldAddBorderPaddingMargin(child->nextSibling()); + if (checkEndEdge) + extraWidth += borderPaddingMarginEnd(parentAsRenderInline); + if (!checkStartEdge && !checkEndEdge) + return extraWidth; + } + child = parent; + parent = child->parent(); + } + return extraWidth; +} + +inline void BreakingContext::handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects) +{ + // If our original display wasn't an inline type, then we can determine our static inline position now. + auto& box = downcast<RenderBox>(*m_current.renderer()); + bool isInlineType = box.style().isOriginalDisplayInlineType(); + if (!isInlineType) + m_block.setStaticInlinePositionForChild(box, m_block.logicalHeight(), m_block.startOffsetForContent(m_block.logicalHeight())); + else { + // If our original display was an INLINE type, then we can determine our static y position now. + box.layer()->setStaticBlockPosition(m_block.logicalHeight()); + } + + // If we're ignoring spaces, we have to stop and include this object and + // then start ignoring spaces again. + if (isInlineType || box.container()->isRenderInline()) { + if (m_ignoringSpaces) + m_lineWhitespaceCollapsingState.ensureLineBoxInsideIgnoredSpaces(box); + m_trailingObjects.appendBoxIfNeeded(box); + } else + positionedObjects.append(&box); + + m_width.addUncommittedWidth(inlineLogicalWidth(&box)); + // Reset prior line break context characters. + m_renderTextInfo.lineBreakIterator.resetPriorContext(); +} + +inline void BreakingContext::handleFloat() +{ + auto& floatBox = downcast<RenderBox>(*m_current.renderer()); + const auto& floatingObject = *m_lineBreaker.insertFloatingObject(floatBox); + // check if it fits in the current line. + // If it does, position it now, otherwise, position + // it after moving to next line (in clearFloats() func) + if (m_floatsFitOnLine && m_width.fitsOnLineExcludingTrailingWhitespace(m_block.logicalWidthForFloat(floatingObject))) { + m_lineBreaker.positionNewFloatOnLine(floatingObject, m_lastFloatFromPreviousLine, m_lineInfo, m_width); + if (m_lineBreakHistory.renderer() == m_current.renderer()) { + ASSERT(!m_lineBreakHistory.offset()); + m_lineBreakHistory.increment(); + } + } else + m_floatsFitOnLine = false; + // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for floating element. + m_renderTextInfo.lineBreakIterator.updatePriorContext(replacementCharacter); +} + +// This is currently just used for list markers and inline flows that have line boxes. Neither should +// have an effect on whitespace at the start of the line. +inline bool shouldSkipWhitespaceAfterStartObject(RenderBlockFlow& block, RenderObject* o, LineWhitespaceCollapsingState& lineWhitespaceCollapsingState) +{ + RenderObject* next = bidiNextSkippingEmptyInlines(block, o); + while (next && next->isFloatingOrOutOfFlowPositioned()) + next = bidiNextSkippingEmptyInlines(block, next); + + if (is<RenderText>(next) && downcast<RenderText>(*next).textLength() > 0) { + RenderText& nextText = downcast<RenderText>(*next); + UChar nextChar = nextText.characterAt(0); + if (nextText.style().isCollapsibleWhiteSpace(nextChar)) { + lineWhitespaceCollapsingState.startIgnoringSpaces(InlineIterator(nullptr, o, 0)); + return true; + } + } + + return false; +} + +inline void BreakingContext::handleEmptyInline() +{ + RenderInline& flowBox = downcast<RenderInline>(*m_current.renderer()); + + // This should only end up being called on empty inlines + ASSERT(isEmptyInline(flowBox)); + + // Now that some inline flows have line boxes, if we are already ignoring spaces, we need + // to make sure that we stop to include this object and then start ignoring spaces again. + // If this object is at the start of the line, we need to behave like list markers and + // start ignoring spaces. + bool requiresLineBox = alwaysRequiresLineBox(flowBox); + if (requiresLineBox || requiresLineBoxForContent(flowBox, m_lineInfo)) { + // An empty inline that only has line-height, vertical-align or font-metrics will only get a + // line box to affect the height of the line if the rest of the line is not empty. + if (requiresLineBox) + m_lineInfo.setEmpty(false, &m_block, &m_width); + if (m_ignoringSpaces) { + m_trailingObjects.clear(); + m_lineWhitespaceCollapsingState.ensureLineBoxInsideIgnoredSpaces(*m_current.renderer()); + } else if (m_blockStyle.collapseWhiteSpace() && m_resolver.position().renderer() == m_current.renderer() + && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineWhitespaceCollapsingState)) { + // Like with list markers, we start ignoring spaces to make sure that any + // additional spaces we see will be discarded. + m_currentCharacterIsSpace = true; + m_currentCharacterIsWS = true; + m_ignoringSpaces = true; + } else + m_trailingObjects.appendBoxIfNeeded(flowBox); + } + + float inlineWidth = inlineLogicalWidth(m_current.renderer()) + borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox); + m_width.addUncommittedWidth(inlineWidth); + if (m_hangsAtEnd && inlineWidth) + m_hangsAtEnd = false; +} + +inline void BreakingContext::handleReplaced() +{ + auto& replacedBox = downcast<RenderBox>(*m_current.renderer()); + + if (m_atStart) + m_width.updateAvailableWidth(replacedBox.logicalHeight()); + + // Break on replaced elements if either has normal white-space. + if (((m_autoWrap || RenderStyle::autoWrap(m_lastWS)) && (!m_current.renderer()->isImage() || m_allowImagesToBreak) + && (!m_current.renderer()->isRubyRun() || downcast<RenderRubyRun>(m_current.renderer())->canBreakBefore(m_renderTextInfo.lineBreakIterator))) || replacedBox.isAnonymousInlineBlock()) { + if (auto* renderer = m_current.renderer()) + commitLineBreakAtCurrentWidth(*renderer); + else + commitLineBreakClear(); + if (m_width.committedWidth() && replacedBox.isAnonymousInlineBlock()) { + // Always force a break before an anonymous inline block if there is content on the line + // already. + m_atEnd = true; + return; + } + } else + m_hangsAtEnd = false; + + if (replacedBox.isAnonymousInlineBlock()) + m_block.layoutBlockChild(replacedBox, m_lineLayoutState.marginInfo(), + m_lineLayoutState.prevFloatBottomFromAnonymousInlineBlock(), m_lineLayoutState.maxFloatBottomFromAnonymousInlineBlock()); + + if (m_ignoringSpaces) + m_lineWhitespaceCollapsingState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), 0)); + + m_lineInfo.setEmpty(false, &m_block, &m_width); + m_ignoringSpaces = false; + m_currentCharacterIsSpace = false; + m_currentCharacterIsWS = false; + m_trailingObjects.clear(); + + // Optimize for a common case. If we can't find whitespace after the list + // item, then this is all moot. + LayoutUnit replacedLogicalWidth = m_block.logicalWidthForChild(replacedBox) + m_block.marginStartForChild(replacedBox) + m_block.marginEndForChild(replacedBox) + inlineLogicalWidth(m_current.renderer()); + if (is<RenderListMarker>(*m_current.renderer())) { + if (m_blockStyle.collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineWhitespaceCollapsingState)) { + // Like with inline flows, we start ignoring spaces to make sure that any + // additional spaces we see will be discarded. + m_currentCharacterIsSpace = true; + m_currentCharacterIsWS = false; + m_ignoringSpaces = true; + } + if (downcast<RenderListMarker>(*m_current.renderer()).isInside()) + m_width.addUncommittedReplacedWidth(replacedLogicalWidth); + } else + m_width.addUncommittedReplacedWidth(replacedLogicalWidth); + if (is<RenderRubyRun>(*m_current.renderer())) { + m_width.applyOverhang(downcast<RenderRubyRun>(m_current.renderer()), m_lastObject, m_nextObject); + downcast<RenderRubyRun>(m_current.renderer())->updatePriorContextFromCachedBreakIterator(m_renderTextInfo.lineBreakIterator); + } else { + // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for replaced element. + m_renderTextInfo.lineBreakIterator.updatePriorContext(replacementCharacter); + } + + if (replacedBox.isAnonymousInlineBlock()) { + m_atEnd = true; + m_lineInfo.setPreviousLineBrokeCleanly(true); + } +} + +inline float firstPositiveWidth(const WordMeasurements& wordMeasurements) +{ + for (size_t i = 0; i < wordMeasurements.size(); ++i) { + if (wordMeasurements[i].width > 0) + return wordMeasurements[i].width; + } + return 0; +} + +inline bool iteratorIsBeyondEndOfRenderCombineText(const InlineIterator& iter, RenderCombineText& renderer) +{ + return iter.renderer() == &renderer && iter.offset() >= renderer.textLength(); +} + +inline void nextCharacter(UChar& currentCharacter, UChar& lastCharacter, UChar& secondToLastCharacter) +{ + secondToLastCharacter = lastCharacter; + lastCharacter = currentCharacter; +} + +// FIXME: Don't let counters mark themselves as needing pref width recalcs during layout +// so we don't need this hack. +inline void updateCounterIfNeeded(RenderText& renderText) +{ + if (!renderText.preferredLogicalWidthsDirty() || !is<RenderCounter>(renderText)) + return; + downcast<RenderCounter>(renderText).updateCounter(); +} + +inline float measureHyphenWidth(RenderText& renderer, const FontCascade& font, HashSet<const Font*>* fallbackFonts = 0) +{ + const RenderStyle& style = renderer.style(); + return font.width(RenderBlock::constructTextRun(style.hyphenString().string(), style), fallbackFonts); +} + +ALWAYS_INLINE float textWidth(RenderText& text, unsigned from, unsigned len, const FontCascade& font, float xPos, bool isFixedPitch, bool collapseWhiteSpace, HashSet<const Font*>& fallbackFonts, TextLayout* layout = nullptr) +{ + const RenderStyle& style = text.style(); + + GlyphOverflow glyphOverflow; + if (isFixedPitch || (!from && len == text.textLength()) || style.hasTextCombine()) + return text.width(from, len, font, xPos, &fallbackFonts, &glyphOverflow); + + if (layout) + return FontCascade::width(*layout, from, len, &fallbackFonts); + + TextRun run = RenderBlock::constructTextRun(text, from, len, style); + run.setCharactersLength(text.textLength() - from); + ASSERT(run.charactersLength() >= run.length()); + + run.setCharacterScanForCodePath(!text.canUseSimpleFontCodePath()); + run.setTabSize(!collapseWhiteSpace, style.tabSize()); + run.setXPos(xPos); + return font.width(run, &fallbackFonts, &glyphOverflow); +} + +// Adding a pair of whitespace collapsing transitions before a character will split it out into a new line box. +inline void ensureCharacterGetsLineBox(LineWhitespaceCollapsingState& lineWhitespaceCollapsingState, InlineIterator& textParagraphSeparator) +{ + InlineIterator transition(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset()); + lineWhitespaceCollapsingState.startIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset() - 1)); + lineWhitespaceCollapsingState.stopIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset())); +} + +inline void tryHyphenating(RenderText& text, const FontCascade& font, const AtomicString& localeIdentifier, unsigned consecutiveHyphenatedLines, int consecutiveHyphenatedLinesLimit, int minimumPrefixLimit, int minimumSuffixLimit, unsigned lastSpace, unsigned pos, float xPos, float availableWidth, bool isFixedPitch, bool collapseWhiteSpace, int lastSpaceWordSpacing, InlineIterator& lineBreak, std::optional<unsigned> nextBreakable, bool& hyphenated) +{ + // Map 'hyphenate-limit-{before,after}: auto;' to 2. + unsigned minimumPrefixLength; + unsigned minimumSuffixLength; + + if (minimumPrefixLimit < 0) + minimumPrefixLength = 2; + else + minimumPrefixLength = static_cast<unsigned>(minimumPrefixLimit); + + if (minimumSuffixLimit < 0) + minimumSuffixLength = 2; + else + minimumSuffixLength = static_cast<unsigned>(minimumSuffixLimit); + + if (pos - lastSpace <= minimumSuffixLength) + return; + + if (consecutiveHyphenatedLinesLimit >= 0 && consecutiveHyphenatedLines >= static_cast<unsigned>(consecutiveHyphenatedLinesLimit)) + return; + + float hyphenWidth = measureHyphenWidth(text, font); + + float maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing; + if (!enoughWidthForHyphenation(maxPrefixWidth, font.pixelSize())) + return; + + const RenderStyle& style = text.style(); + TextRun run = RenderBlock::constructTextRun(text, lastSpace, pos - lastSpace, style); + run.setCharactersLength(text.textLength() - lastSpace); + ASSERT(run.charactersLength() >= run.length()); + + run.setTabSize(!collapseWhiteSpace, style.tabSize()); + run.setXPos(xPos + lastSpaceWordSpacing); + + unsigned prefixLength = font.offsetForPosition(run, maxPrefixWidth, false); + if (prefixLength < minimumPrefixLength) + return; + + prefixLength = lastHyphenLocation(StringView(text.text()).substring(lastSpace, pos - lastSpace), std::min(prefixLength, pos - lastSpace - minimumSuffixLength) + 1, localeIdentifier); + if (!prefixLength || prefixLength < minimumPrefixLength) + return; + + // When lastSpace is a space, which it always is except sometimes at the beginning of a line or after collapsed + // space, it should not count towards hyphenate-limit-before. + if (prefixLength == minimumPrefixLength) { + UChar characterAtLastSpace = text.characterAt(lastSpace); + if (characterAtLastSpace == ' ' || characterAtLastSpace == '\n' || characterAtLastSpace == '\t' || characterAtLastSpace == noBreakSpace) + return; + } + + ASSERT(pos - lastSpace - prefixLength >= minimumSuffixLength); + +#if !ASSERT_DISABLED + HashSet<const Font*> fallbackFonts; + float prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace, fallbackFonts) + lastSpaceWordSpacing; + ASSERT(xPos + prefixWidth <= availableWidth); +#else + UNUSED_PARAM(isFixedPitch); +#endif + + lineBreak.moveTo(text, lastSpace + prefixLength, nextBreakable); + hyphenated = true; +} + +inline float BreakingContext::computeAdditionalBetweenWordsWidth(RenderText& renderText, TextLayout* textLayout, UChar currentCharacter, WordTrailingSpace& wordTrailingSpace, HashSet<const Font*>& fallbackFonts, WordMeasurements& wordMeasurements, const FontCascade& font, bool isFixedPitch, unsigned lastSpace, float lastSpaceWordSpacing, float wordSpacingForWordMeasurement, unsigned offset) +{ + wordMeasurements.grow(wordMeasurements.size() + 1); + WordMeasurement& wordMeasurement = wordMeasurements.last(); + + wordMeasurement.renderer = &renderText; + wordMeasurement.endOffset = offset; + wordMeasurement.startOffset = lastSpace; + + float additionalTempWidth = 0; + std::optional<float> wordTrailingSpaceWidth; + if (currentCharacter == ' ') + wordTrailingSpaceWidth = wordTrailingSpace.width(fallbackFonts); + if (wordTrailingSpaceWidth) + additionalTempWidth = textWidth(renderText, lastSpace, offset + 1 - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) - wordTrailingSpaceWidth.value(); + else + additionalTempWidth = textWidth(renderText, lastSpace, offset - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout); + + if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty()) + wordMeasurement.fallbackFonts.swap(fallbackFonts); + fallbackFonts.clear(); + + wordMeasurement.width = additionalTempWidth + wordSpacingForWordMeasurement; + additionalTempWidth += lastSpaceWordSpacing; + return additionalTempWidth; +} + +inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated, unsigned& consecutiveHyphenatedLines) +{ + if (!m_current.offset()) + m_appliedStartWidth = false; + + RenderObject& renderObject = *m_current.renderer(); + RenderText& renderText = downcast<RenderText>(renderObject); + + bool isSVGText = renderText.isSVGInlineText(); + + // If we have left a no-wrap inline and entered an autowrap inline while ignoring spaces + // then we need to mark the start of the autowrap inline as a potential linebreak now. + if (m_autoWrap && !RenderStyle::autoWrap(m_lastWS) && m_ignoringSpaces) + commitLineBreakAtCurrentWidth(renderText); + + if (renderText.style().hasTextCombine() && is<RenderCombineText>(*m_current.renderer())) { + auto& combineRenderer = downcast<RenderCombineText>(*m_current.renderer()); + combineRenderer.combineText(); + // The length of the renderer's text may have changed. Increment stale iterator positions + if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreakHistory.current(), combineRenderer)) { + ASSERT(iteratorIsBeyondEndOfRenderCombineText(m_resolver.position(), combineRenderer)); + m_lineBreakHistory.increment(); + m_resolver.increment(); + } + } + + const RenderStyle& style = lineStyle(renderText, m_lineInfo); + const FontCascade& font = style.fontCascade(); + bool isFixedPitch = font.isFixedPitch(); + bool canHyphenate = style.hyphens() == HyphensAuto && WebCore::canHyphenate(style.locale()); + bool canHangPunctuationAtStart = style.hangingPunctuation() & FirstHangingPunctuation; + bool canHangPunctuationAtEnd = style.hangingPunctuation() & LastHangingPunctuation; + bool canHangStopOrCommaAtLineEnd = style.hangingPunctuation() & AllowEndHangingPunctuation; + int endPunctuationIndex = canHangPunctuationAtEnd && m_collapseWhiteSpace ? renderText.lastCharacterIndexStrippingSpaces() : renderText.textLength() - 1; + unsigned lastSpace = m_current.offset(); + float wordSpacing = m_currentStyle->fontCascade().wordSpacing(); + float lastSpaceWordSpacing = 0; + float wordSpacingForWordMeasurement = 0; + + float wrapWidthOffset = m_width.uncommittedWidth() + inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, true); + float wrapW = wrapWidthOffset; + float charWidth = 0; + bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE; + // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word, + // which is only possible if the word is the first thing on the line. + bool breakWords = m_currentStyle->breakWords() && ((m_autoWrap && (!m_width.committedWidth() && !m_width.hasCommittedReplaced())) || m_currWS == PRE); + bool midWordBreak = false; + bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap; + bool keepAllWords = m_currentStyle->wordBreak() == KeepAllWordBreak; + float hyphenWidth = 0; + auto iteratorMode = mapLineBreakToIteratorMode(m_blockStyle.lineBreak()); + bool canUseLineBreakShortcut = iteratorMode == LineBreakIteratorMode::Default; + bool isLineEmpty = m_lineInfo.isEmpty(); + + if (isSVGText) { + breakWords = false; + breakAll = false; + } + + if (m_renderTextInfo.text != &renderText) { + updateCounterIfNeeded(renderText); + m_renderTextInfo.text = &renderText; + m_renderTextInfo.font = &font; + m_renderTextInfo.layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace); + m_renderTextInfo.lineBreakIterator.resetStringAndReleaseIterator(renderText.text(), style.locale(), iteratorMode); + } else if (m_renderTextInfo.layout && m_renderTextInfo.font != &font) { + m_renderTextInfo.font = &font; + m_renderTextInfo.layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace); + } + + TextLayout* textLayout = m_renderTextInfo.layout.get(); + + // Non-zero only when kerning is enabled and TextLayout isn't used, in which case we measure + // words with their trailing space, then subtract its width. + HashSet<const Font*> fallbackFonts; + UChar lastCharacterFromPreviousRenderText = m_renderTextInfo.lineBreakIterator.lastCharacter(); + UChar lastCharacter = m_renderTextInfo.lineBreakIterator.lastCharacter(); + UChar secondToLastCharacter = m_renderTextInfo.lineBreakIterator.secondToLastCharacter(); + WordTrailingSpace wordTrailingSpace(style, textLayout); + for (; m_current.offset() < renderText.textLength(); m_current.fastIncrementInTextNode()) { + bool previousCharacterIsSpace = m_currentCharacterIsSpace; + bool previousCharacterIsWS = m_currentCharacterIsWS; + UChar c = m_current.current(); + m_currentCharacterIsSpace = c == ' ' || c == '\t' || (!m_preservesNewline && (c == '\n')); + + if (canHangPunctuationAtStart && m_width.isFirstLine() && !m_width.committedWidth() && !wrapW && !inlineLogicalWidth(m_current.renderer(), true, false)) { + m_width.addUncommittedWidth(-renderText.hangablePunctuationStartWidth(m_current.offset())); + canHangPunctuationAtStart = false; + } + + if (canHangPunctuationAtEnd && !m_nextObject && (int)m_current.offset() == endPunctuationIndex && !inlineLogicalWidth(m_current.renderer(), false, true)) { + m_width.addUncommittedWidth(-renderText.hangablePunctuationEndWidth(endPunctuationIndex)); + canHangPunctuationAtEnd = false; + } + + if (!m_collapseWhiteSpace || !m_currentCharacterIsSpace) + m_lineInfo.setEmpty(false, &m_block, &m_width); + + if (c == softHyphen && m_autoWrap && !hyphenWidth && style.hyphens() != HyphensNone) { + hyphenWidth = measureHyphenWidth(renderText, font, &fallbackFonts); + m_width.addUncommittedWidth(hyphenWidth); + } + + bool applyWordSpacing = false; + + m_currentCharacterIsWS = m_currentCharacterIsSpace || (breakNBSP && c == noBreakSpace); + + if ((breakAll || breakWords) && !midWordBreak && (!m_currentCharacterIsSpace || style.whiteSpace() != PRE_WRAP)) { + wrapW += charWidth; + bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText.textLength() && U16_IS_TRAIL(renderText[m_current.offset() + 1]); + charWidth = textWidth(renderText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, font, m_width.committedWidth() + wrapW, isFixedPitch, m_collapseWhiteSpace, fallbackFonts, textLayout); + midWordBreak = m_width.committedWidth() + wrapW + charWidth > m_width.availableWidth(); + } + + std::optional<unsigned> nextBreakablePosition = m_current.nextBreakablePosition(); + bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP, canUseLineBreakShortcut, keepAllWords) + && (style.hyphens() != HyphensNone || (m_current.previousInSameNode() != softHyphen))); + m_current.setNextBreakablePosition(nextBreakablePosition); + + if (canHangStopOrCommaAtLineEnd && renderText.isHangableStopOrComma(c) && m_width.fitsOnLine()) { + // We need to see if a measurement that excludes the stop would fit. If so, then we should hang + // the stop/comma at the end. First measure including the comma. + m_hangsAtEnd = false; + float inlineStartWidth = !m_appliedStartWidth ? inlineLogicalWidth(m_current.renderer(), true, false) : LayoutUnit(); + float widthIncludingComma = computeAdditionalBetweenWordsWidth(renderText, textLayout, c, wordTrailingSpace, fallbackFonts, wordMeasurements, font, isFixedPitch, lastSpace, lastSpaceWordSpacing, wordSpacingForWordMeasurement, m_current.offset() + 1) + inlineStartWidth; + m_width.addUncommittedWidth(widthIncludingComma); + if (!m_width.fitsOnLine()) { + // See if we fit without the comma involved. If we do, then this is a potential hang point. + float widthWithoutStopOrComma = computeAdditionalBetweenWordsWidth(renderText, textLayout, lastCharacter, wordTrailingSpace, fallbackFonts, wordMeasurements, font, isFixedPitch, lastSpace, lastSpaceWordSpacing, wordSpacingForWordMeasurement, m_current.offset()) + inlineStartWidth; + m_width.addUncommittedWidth(widthWithoutStopOrComma - widthIncludingComma); + if (m_width.fitsOnLine()) + m_hangsAtEnd = true; + } else + m_width.addUncommittedWidth(-widthIncludingComma); + } + + if (betweenWords || midWordBreak) { + bool stoppedIgnoringSpaces = false; + if (m_ignoringSpaces) { + lastSpaceWordSpacing = 0; + if (!m_currentCharacterIsSpace) { + // Stop ignoring spaces and begin at this new point. + m_ignoringSpaces = false; + wordSpacingForWordMeasurement = 0; + lastSpace = m_current.offset(); // e.g., "Foo goo", don't add in any of the ignored spaces. + m_lineWhitespaceCollapsingState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset())); + stoppedIgnoringSpaces = true; + } else { + // Just keep ignoring these spaces. + nextCharacter(c, lastCharacter, secondToLastCharacter); + continue; + } + } + + float additionalTempWidth = computeAdditionalBetweenWordsWidth(renderText, textLayout, c, wordTrailingSpace, fallbackFonts, wordMeasurements, font, isFixedPitch, lastSpace, lastSpaceWordSpacing, wordSpacingForWordMeasurement, m_current.offset()); + m_width.addUncommittedWidth(additionalTempWidth); + + WordMeasurement& wordMeasurement = wordMeasurements.last(); + + if (m_collapseWhiteSpace && previousCharacterIsSpace && m_currentCharacterIsSpace && additionalTempWidth) + m_width.setTrailingWhitespaceWidth(additionalTempWidth); + + if (!m_appliedStartWidth) { + float inlineStartWidth = inlineLogicalWidth(m_current.renderer(), true, false); + m_width.addUncommittedWidth(inlineStartWidth); + m_appliedStartWidth = true; + if (m_hangsAtEnd && inlineStartWidth) + m_hangsAtEnd = false; + } + + applyWordSpacing = wordSpacing && m_currentCharacterIsSpace; + + if (!m_width.hasCommitted() && m_autoWrap && !fitsOnLineOrHangsAtEnd()) + m_width.fitBelowFloats(m_lineInfo.isFirstLine()); + + if (m_autoWrap || breakWords) { + // If we break only after white-space, consider the current character + // as candidate width for this line. + bool lineWasTooWide = false; + if (fitsOnLineOrHangsAtEnd() && m_currentCharacterIsWS && m_currentStyle->breakOnlyAfterWhiteSpace() && !midWordBreak) { + float charWidth = textWidth(renderText, m_current.offset(), 1, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) + (applyWordSpacing ? wordSpacing : 0); + // Check if line is too big even without the extra space + // at the end of the line. If it is not, do nothing. + // If the line needs the extra whitespace to be too long, + // then move the line break to the space and skip all + // additional whitespace. + if (!m_width.fitsOnLineIncludingExtraWidth(charWidth)) { + lineWasTooWide = true; + m_lineBreakHistory.push([&](InlineIterator& modifyMe) { + modifyMe.moveTo(renderObject, m_current.offset(), m_current.nextBreakablePosition()); + m_lineBreaker.skipTrailingWhitespace(modifyMe, m_lineInfo); + }); + } + } + if ((lineWasTooWide || !m_width.fitsOnLine()) && !m_hangsAtEnd) { + // Don't try to hyphenate at the final break of a block, since this means there is + // no more content, and a hyphenated single word would end up on a line by itself. This looks + // bad so just don't allow it. + if (canHyphenate && !m_width.fitsOnLine() && (m_nextObject || !renderText.containsOnlyWhitespace(m_current.offset(), renderText.textLength() - m_current.offset()) || isLineEmpty)) { + m_lineBreakHistory.push([&](InlineIterator& modifyMe) { + tryHyphenating(renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, modifyMe, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated); + }); + if (m_lineBreaker.m_hyphenated) { + m_atEnd = true; + return false; + } + } + if (m_lineBreakHistory.atTextParagraphSeparator()) { + if (!stoppedIgnoringSpaces && m_current.offset() > 0) + ensureCharacterGetsLineBox(m_lineWhitespaceCollapsingState, m_current); + m_lineBreakHistory.increment(); + m_lineInfo.setPreviousLineBrokeCleanly(true); + wordMeasurement.endOffset = m_lineBreakHistory.offset(); + } + // Check if the last breaking position is a soft-hyphen. + if (!hyphenated && style.hyphens() != HyphensNone) { + std::optional<unsigned> lastBreakingPositon; + const RenderObject* rendererAtBreakingPosition = nullptr; + if (m_lineBreakHistory.offset() || m_lineBreakHistory.nextBreakablePosition()) { + lastBreakingPositon = m_lineBreakHistory.offset(); + rendererAtBreakingPosition = m_lineBreakHistory.renderer(); + } else if (m_current.nextBreakablePosition() && m_current.nextBreakablePosition().value() <= m_current.offset()) { + // We might just be right after the soft-hyphen + lastBreakingPositon = m_current.nextBreakablePosition().value(); + rendererAtBreakingPosition = m_current.renderer(); + } + if (lastBreakingPositon) { + std::optional<UChar> characterBeforeBreakingPosition; + // When last breaking position points to the start of the current context, we need to look at the last character from + // the previous non-empty text renderer. + if (!lastBreakingPositon.value()) + characterBeforeBreakingPosition = lastCharacterFromPreviousRenderText; + else if (is<RenderText>(rendererAtBreakingPosition)) { + const auto& textRenderer = downcast<RenderText>(*rendererAtBreakingPosition); + ASSERT(lastBreakingPositon.value() >= 1 && textRenderer.textLength() > (lastBreakingPositon.value() - 1)); + characterBeforeBreakingPosition = textRenderer.characterAt(lastBreakingPositon.value() - 1); + } + if (characterBeforeBreakingPosition) + hyphenated = characterBeforeBreakingPosition.value() == softHyphen; + } + } + if (m_lineBreakHistory.offset() && m_lineBreakHistory.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) { + if (charWidth) { + wordMeasurement.endOffset = m_lineBreakHistory.offset(); + wordMeasurement.width = charWidth; + } + } + // Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace. + if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) { + m_atEnd = true; + return false; + } + } else { + if (!betweenWords || (midWordBreak && !m_autoWrap)) + m_width.addUncommittedWidth(-additionalTempWidth); + if (hyphenWidth) { + // Subtract the width of the soft hyphen out since we fit on a line. + m_width.addUncommittedWidth(-hyphenWidth); + hyphenWidth = 0; + } + } + } + + if (c == '\n' && m_preservesNewline) { + if (!stoppedIgnoringSpaces && m_current.offset()) + ensureCharacterGetsLineBox(m_lineWhitespaceCollapsingState, m_current); + commitLineBreakAtCurrentWidth(renderObject, m_current.offset(), m_current.nextBreakablePosition()); + m_lineBreakHistory.increment(); + m_lineInfo.setPreviousLineBrokeCleanly(true); + return true; + } + + if (m_autoWrap && betweenWords) { + commitLineBreakAtCurrentWidth(renderObject, m_current.offset(), m_current.nextBreakablePosition()); + wrapWidthOffset = 0; + wrapW = wrapWidthOffset; + // Auto-wrapping text should not wrap in the middle of a word once it has had an + // opportunity to break after a word. + breakWords = false; + } + + if (midWordBreak && !U16_IS_TRAIL(c) && !(U_GET_GC_MASK(c) & U_GC_M_MASK)) { + // Remember this as a breakable position in case + // adding the end width forces a break. + m_lineBreakHistory.moveTo(renderObject, m_current.offset(), m_current.nextBreakablePosition()); + midWordBreak &= (breakWords || breakAll); + } + + if (betweenWords) { + lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; + wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0; + lastSpace = m_current.offset(); + } + + if (!m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) { + // If we encounter a newline, or if we encounter a second space, + // we need to break up this run and enter a mode where we start collapsing spaces. + if (m_currentCharacterIsSpace && previousCharacterIsSpace) { + m_ignoringSpaces = true; + + // We just entered a mode where we are ignoring + // spaces. Create a transition to terminate the run + // before the second space. + m_lineWhitespaceCollapsingState.startIgnoringSpaces(m_startOfIgnoredSpaces); + m_trailingObjects.updateWhitespaceCollapsingTransitionsForTrailingBoxes(m_lineWhitespaceCollapsingState, InlineIterator(), TrailingObjects::DoNotCollapseFirstSpace); + } + } + // Measuring the width of complex text character-by-character, rather than measuring it all together, + // could produce considerably different width values. + if (!renderText.canUseSimpleFontCodePath() && midWordBreak && m_width.fitsOnLine()) { + midWordBreak = false; + wrapW = wrapWidthOffset + additionalTempWidth; + } + isLineEmpty = m_lineInfo.isEmpty(); + } else { + if (m_ignoringSpaces) { + // Stop ignoring spaces and begin at this new point. + m_ignoringSpaces = false; + lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; + wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurements.last().width) ? wordSpacing : 0; + lastSpace = m_current.offset(); // e.g., "Foo goo", don't add in any of the ignored spaces. + m_lineWhitespaceCollapsingState.stopIgnoringSpaces(InlineIterator(nullptr, m_current.renderer(), m_current.offset())); + } + if (m_hangsAtEnd && !renderText.isHangableStopOrComma(c)) + m_hangsAtEnd = false; + } + + if (isSVGText && m_current.offset()) { + // Force creation of new InlineBoxes for each absolute positioned character (those that start new text chunks). + if (downcast<RenderSVGInlineText>(renderText).characterStartsNewTextChunk(m_current.offset())) + ensureCharacterGetsLineBox(m_lineWhitespaceCollapsingState, m_current); + } + + if (m_currentCharacterIsSpace && !previousCharacterIsSpace) { + m_startOfIgnoredSpaces.setRenderer(m_current.renderer()); + m_startOfIgnoredSpaces.setOffset(m_current.offset()); + // Spaces after right-aligned text and before a line-break get collapsed away completely so that the trailing + // space doesn't seem to push the text out from the right-hand edge. + // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results. + if (m_nextObject && m_startOfIgnoredSpaces.offset() && m_nextObject->isBR() && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) { + m_startOfIgnoredSpaces.setOffset(m_startOfIgnoredSpaces.offset() - 1); + // If there's just a single trailing space start ignoring it now so it collapses away. + if (m_current.offset() == renderText.textLength() - 1) + m_lineWhitespaceCollapsingState.startIgnoringSpaces(m_startOfIgnoredSpaces); + } + } + + if (!m_currentCharacterIsWS && previousCharacterIsWS) { + if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace()) + m_lineBreakHistory.moveTo(renderObject, m_current.offset(), m_current.nextBreakablePosition()); + } + + if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces) + m_trailingObjects.setTrailingWhitespace(downcast<RenderText>(m_current.renderer())); + else if (!m_currentStyle->collapseWhiteSpace() || !m_currentCharacterIsSpace) + m_trailingObjects.clear(); + + m_atStart = false; + nextCharacter(c, lastCharacter, secondToLastCharacter); + } + + m_renderTextInfo.lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter); + + wordMeasurements.grow(wordMeasurements.size() + 1); + WordMeasurement& wordMeasurement = wordMeasurements.last(); + wordMeasurement.renderer = &renderText; + + // IMPORTANT: current.m_pos is > length here! + float additionalTempWidth = m_ignoringSpaces ? 0 : textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout); + wordMeasurement.startOffset = lastSpace; + wordMeasurement.endOffset = m_current.offset(); + wordMeasurement.width = m_ignoringSpaces ? 0 : additionalTempWidth + wordSpacingForWordMeasurement; + additionalTempWidth += lastSpaceWordSpacing; + + float inlineLogicalTempWidth = inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, m_includeEndWidth); + m_width.addUncommittedWidth(additionalTempWidth + inlineLogicalTempWidth); + if (m_hangsAtEnd && inlineLogicalTempWidth) + m_hangsAtEnd = false; + + if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty()) + wordMeasurement.fallbackFonts.swap(fallbackFonts); + fallbackFonts.clear(); + + if (m_collapseWhiteSpace && m_currentCharacterIsSpace && additionalTempWidth) + m_width.setTrailingWhitespaceWidth(additionalTempWidth, inlineLogicalTempWidth); + + m_includeEndWidth = false; + + if (!fitsOnLineOrHangsAtEnd()) { + // Don't try to hyphenate at the final break of a block, since this means there is + // no more content, and a hyphenated single word would end up on a line by itself. This looks + // bad so just don't allow it. + if (canHyphenate && (m_nextObject || isLineEmpty)) { + m_lineBreakHistory.push([&](InlineIterator& modifyMe) { + tryHyphenating(renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, modifyMe, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated); + }); + } + + if (!hyphenated && m_lineBreakHistory.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) { + hyphenated = true; + m_atEnd = true; + } + } + return false; +} + +inline bool textBeginsWithBreakablePosition(RenderText& nextText) +{ + UChar c = nextText.characterAt(0); + return c == ' ' || c == '\t' || (c == '\n' && !nextText.preservesNewline()); +} + +inline bool BreakingContext::canBreakAtThisPosition() +{ + // If we are no-wrap and have found a line-breaking opportunity already then we should take it. + if (m_width.committedWidth() && !m_width.fitsOnLine(m_currentCharacterIsSpace) && m_currWS == NOWRAP) + return true; + + // Avoid breaking on empty inlines. + if (is<RenderInline>(*m_current.renderer()) && isEmptyInline(downcast<RenderInline>(*m_current.renderer()))) + return false; + + // Avoid breaking before empty inlines (as long as the current object isn't replaced). + if (!m_current.renderer()->isReplaced() && is<RenderInline>(m_nextObject) && isEmptyInline(downcast<RenderInline>(*m_nextObject))) + return false; + + // Return early if we autowrap and the current character is a space as we will always want to break at such a position. + if (m_autoWrap && m_currentCharacterIsSpace) + return true; + + if (m_nextObject && m_nextObject->isLineBreakOpportunity()) + return m_autoWrap; + + bool nextIsAutoWrappingText = is<RenderText>(m_nextObject) && (m_autoWrap || m_nextObject->style().autoWrap()); + if (!nextIsAutoWrappingText) + return m_autoWrap; + RenderText& nextRenderText = downcast<RenderText>(*m_nextObject); + bool currentIsTextOrEmptyInline = is<RenderText>(*m_current.renderer()) || (is<RenderInline>(*m_current.renderer()) && isEmptyInline(downcast<RenderInline>(*m_current.renderer()))); + if (!currentIsTextOrEmptyInline) + return m_autoWrap && !m_current.renderer()->isRubyRun(); + + bool canBreakHere = !m_currentCharacterIsSpace && textBeginsWithBreakablePosition(nextRenderText); + + // See if attempting to fit below floats creates more available width on the line. + if (!m_width.fitsOnLine() && !m_width.hasCommitted()) + m_width.fitBelowFloats(m_lineInfo.isFirstLine()); + + bool canPlaceOnLine = m_width.fitsOnLine() || !m_autoWrapWasEverTrueOnLine; + + if (canPlaceOnLine && canBreakHere) + commitLineBreakAtCurrentWidth(nextRenderText); + + return canBreakHere; +} + +inline void BreakingContext::commitAndUpdateLineBreakIfNeeded() +{ + bool checkForBreak = canBreakAtThisPosition(); + + if (checkForBreak && !m_width.fitsOnLine(m_ignoringSpaces) && !m_hangsAtEnd) { + // if we have floats, try to get below them. + if (m_currentCharacterIsSpace && !m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) + m_trailingObjects.clear(); + + if (m_width.committedWidth()) { + m_atEnd = true; + return; + } + + if (!m_hangsAtEnd) + m_width.fitBelowFloats(m_lineInfo.isFirstLine()); + + // |width| may have been adjusted because we got shoved down past a float (thus + // giving us more room), so we need to retest, and only jump to + // the end label if we still don't fit on the line. -dwh + if (!m_width.fitsOnLine(m_ignoringSpaces)) { + m_atEnd = true; + return; + } + } else if (m_blockStyle.autoWrap() && !m_width.fitsOnLine() && !m_width.hasCommitted() && !m_hangsAtEnd) { + // If the container autowraps but the current child does not then we still need to ensure that it + // wraps and moves below any floats. + m_width.fitBelowFloats(m_lineInfo.isFirstLine()); + } + + if (!m_current.renderer()->isFloatingOrOutOfFlowPositioned()) { + m_lastObject = m_current.renderer(); + if (m_lastObject->isReplaced() && m_autoWrap && !m_lastObject->isRubyRun() && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!is<RenderListMarker>(*m_lastObject) || downcast<RenderListMarker>(*m_lastObject).isInside())) { + if (m_nextObject) + commitLineBreakAtCurrentWidth(*m_nextObject); + else + commitLineBreakClear(); + } + } +} + +inline TrailingObjects::CollapseFirstSpaceOrNot checkWhitespaceCollapsingTransitions(LineWhitespaceCollapsingState& lineWhitespaceCollapsingState, const InlineIterator& lBreak) +{ + // Check to see if our last transition is a start point beyond the line break. If so, + // shave it off the list, and shave off a trailing space if the previous end point doesn't + // preserve whitespace. + if (lBreak.renderer() && lineWhitespaceCollapsingState.numTransitions() && !(lineWhitespaceCollapsingState.numTransitions() % 2)) { + const InlineIterator* transitions = lineWhitespaceCollapsingState.transitions().data(); + const InlineIterator& endpoint = transitions[lineWhitespaceCollapsingState.numTransitions() - 2]; + const InlineIterator& startpoint = transitions[lineWhitespaceCollapsingState.numTransitions() - 1]; + InlineIterator currpoint = endpoint; + while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) + currpoint.increment(); + if (currpoint == lBreak) { + // We hit the line break before the start point. Shave off the start point. + lineWhitespaceCollapsingState.decrementNumTransitions(); + if (endpoint.renderer()->style().collapseWhiteSpace() && endpoint.renderer()->isText()) { + lineWhitespaceCollapsingState.decrementTransitionAt(lineWhitespaceCollapsingState.numTransitions() - 1); + return TrailingObjects::DoNotCollapseFirstSpace; + } + } + } + return TrailingObjects::CollapseFirstSpace; +} + +inline InlineIterator BreakingContext::handleEndOfLine() +{ + if (m_lineBreakHistory.current() == m_resolver.position()) { + if (!m_lineBreakHistory.renderer() || !m_lineBreakHistory.renderer()->isBR()) { + // we just add as much as possible + if (m_blockStyle.whiteSpace() == PRE && !m_current.offset()) { + if (m_lastObject) + commitLineBreakAtCurrentWidth(*m_lastObject, m_lastObject->isText() ? m_lastObject->length() : 0); + else + commitLineBreakClear(); + } + else if (m_lineBreakHistory.renderer()) { + // Don't ever break in the middle of a word if we can help it. + // There's no room at all. We just have to be on this line, + // even though we'll spill out. + commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset()); + } + } + // make sure we consume at least one char/object. + if (m_lineBreakHistory.current() == m_resolver.position()) + m_lineBreakHistory.increment(); + } else if (!m_current.offset() && !m_width.committedWidth() && m_width.uncommittedWidth() && !m_hadUncommittedWidthBeforeCurrent) { + // Do not push the current object to the next line, when this line has some content, but it is still considered empty. + // Empty inline elements like <span></span> can produce such lines and now we just ignore these break opportunities + // at the start of a line, if no width has been committed yet. + // Behave as if it was actually empty and consume at least one object. + m_lineBreakHistory.increment(); + } + + // Sanity check our whitespace collapsing transitions. + TrailingObjects::CollapseFirstSpaceOrNot collapsed = checkWhitespaceCollapsingTransitions(m_lineWhitespaceCollapsingState, m_lineBreakHistory.current()); + + m_trailingObjects.updateWhitespaceCollapsingTransitionsForTrailingBoxes(m_lineWhitespaceCollapsingState, m_lineBreakHistory.current(), collapsed); + + // We might have made lineBreak an iterator that points past the end + // of the object. Do this adjustment to make it point to the start + // of the next object instead to avoid confusing the rest of the + // code. + if (m_lineBreakHistory.offset()) { + m_lineBreakHistory.update([](InlineIterator& modifyMe) { + modifyMe.setOffset(modifyMe.offset() - 1); + modifyMe.increment(); + }); + } + +#if ENABLE(CSS_TRAILING_WORD) + if (m_blockStyle.trailingWord() == TrailingWord::PartiallyBalanced) + return optimalLineBreakLocationForTrailingWord(); +#endif + return m_lineBreakHistory.current(); +} + +#if ENABLE(CSS_TRAILING_WORD) +inline InlineIterator BreakingContext::optimalLineBreakLocationForTrailingWord() +{ + const unsigned longTrailingWordLength = 20; + const float optimalTrailingLineRatio = 0.1; + InlineIterator lineBreak = m_lineBreakHistory.current(); + if (!lineBreak.renderer() || !m_lineInfo.isFirstLine() || bidiNextSkippingEmptyInlines(*lineBreak.root(), lineBreak.renderer()) || !is<RenderText>(lineBreak.renderer())) + return lineBreak; + RenderText& renderText = downcast<RenderText>(*lineBreak.renderer()); + // Don't even bother measuring if our remaining line has many characters + if (renderText.textLength() == lineBreak.offset() || renderText.textLength() - lineBreak.offset() > longTrailingWordLength) + return lineBreak; + bool canUseLineBreakShortcut = m_renderTextInfo.lineBreakIterator.mode() == LineBreakIteratorMode::Default; + bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE; + std::optional<unsigned> nextBreakablePosition = lineBreak.nextBreakablePosition(); + isBreakable(m_renderTextInfo.lineBreakIterator, lineBreak.offset() + 1, nextBreakablePosition, breakNBSP, canUseLineBreakShortcut, m_currentStyle->wordBreak() == KeepAllWordBreak); + if (!nextBreakablePosition || nextBreakablePosition.value() != renderText.textLength()) + return lineBreak; + const RenderStyle& style = lineStyle(renderText, m_lineInfo); + const FontCascade& font = style.fontCascade(); + HashSet<const Font*> dummyFonts; + InlineIterator best = lineBreak; + for (size_t i = 1; i < m_lineBreakHistory.historyLength(); ++i) { + const InlineIterator& candidate = m_lineBreakHistory.get(i); + if (candidate.renderer() != lineBreak.renderer()) + return best; + float width = textWidth(renderText, candidate.offset(), renderText.textLength() - candidate.offset(), font, 0, font.isFixedPitch(), m_collapseWhiteSpace, dummyFonts); + if (width > m_width.availableWidth()) + return best; + if (width / m_width.availableWidth() > optimalTrailingLineRatio) // Subsequent line is long enough + return candidate; + best = candidate; + } + return best; +} +#endif + +} diff --git a/Source/WebCore/rendering/line/BreakingContextInlineHeaders.h b/Source/WebCore/rendering/line/BreakingContextInlineHeaders.h deleted file mode 100644 index 33d296b28..000000000 --- a/Source/WebCore/rendering/line/BreakingContextInlineHeaders.h +++ /dev/null @@ -1,1139 +0,0 @@ -/* - * Copyright (C) 2000 Lars Knoll (knoll@kde.org) - * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All right reserved. - * Copyright (C) 2010 Google Inc. All rights reserved. - * Copyright (C) 2013 ChangSeok Oh <shivamidow@gmail.com> - * Copyright (C) 2013 Adobe Systems Inc. All right reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - */ - -#ifndef BreakingContextInlineHeaders_h -#define BreakingContextInlineHeaders_h - -#include "Hyphenation.h" -#include "LineBreaker.h" -#include "LineInfo.h" -#include "LineWidth.h" -#include "RenderCombineText.h" -#include "RenderCounter.h" -#include "RenderInline.h" -#include "RenderListMarker.h" -#include "RenderRubyRun.h" -#include "TrailingObjects.h" -#include "break_lines.h" -#include <wtf/unicode/CharacterNames.h> - -#if ENABLE(SVG) -#include "RenderSVGInlineText.h" -#endif - -#if ENABLE(CSS_SHAPES) -#include "ShapeInsideInfo.h" -#endif - -namespace WebCore { - -// We don't let our line box tree for a single line get any deeper than this. -const unsigned cMaxLineDepth = 200; - -struct WordMeasurement { - WordMeasurement() - : renderer(0) - , width(0) - , startOffset(0) - , endOffset(0) - { - } - - RenderText* renderer; - float width; - int startOffset; - int endOffset; - HashSet<const SimpleFontData*> fallbackFonts; -}; - -class BreakingContext { -public: - BreakingContext(LineBreaker& lineBreaker, InlineBidiResolver& resolver, LineInfo& inLineInfo, LineWidth& lineWidth, RenderTextInfo& inRenderTextInfo, FloatingObject* inLastFloatFromPreviousLine, bool appliedStartWidth, RenderBlockFlow& block) - : m_lineBreaker(lineBreaker) - , m_resolver(resolver) - , m_current(resolver.position()) - , m_lineBreak(resolver.position()) - , m_block(block) - , m_lastObject(m_current.renderer()) - , m_nextObject(0) - , m_currentStyle(0) - , m_blockStyle(block.style()) - , m_lineInfo(inLineInfo) - , m_renderTextInfo(inRenderTextInfo) - , m_lastFloatFromPreviousLine(inLastFloatFromPreviousLine) - , m_width(lineWidth) - , m_currWS(NORMAL) - , m_lastWS(NORMAL) - , m_preservesNewline(false) - , m_atStart(true) - , m_ignoringSpaces(false) - , m_currentCharacterIsSpace(false) - , m_currentCharacterIsWS(false) - , m_appliedStartWidth(appliedStartWidth) - , m_includeEndWidth(true) - , m_autoWrap(false) - , m_autoWrapWasEverTrueOnLine(false) - , m_floatsFitOnLine(true) - , m_collapseWhiteSpace(false) - , m_startingNewParagraph(m_lineInfo.previousLineBrokeCleanly()) - , m_allowImagesToBreak(!block.document().inQuirksMode() || !block.isTableCell() || !m_blockStyle.logicalWidth().isIntrinsicOrAuto()) - , m_atEnd(false) - , m_hadUncommittedWidthBeforeCurrent(false) - , m_lineMidpointState(resolver.midpointState()) - { - m_lineInfo.setPreviousLineBrokeCleanly(false); - } - - RenderObject* currentObject() { return m_current.renderer(); } - InlineIterator lineBreak() { return m_lineBreak; } - InlineIterator& lineBreakRef() {return m_lineBreak; } - LineWidth& lineWidth() { return m_width; } - bool atEnd() { return m_atEnd; } - - void initializeForCurrentObject(); - - void increment(); - - void handleBR(EClear&); - void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects); - void handleFloat(); - void handleEmptyInline(); - void handleReplaced(); - bool handleText(WordMeasurements&, bool& hyphenated, unsigned& consecutiveHyphenatedLines); - bool canBreakAtThisPosition(); - void commitAndUpdateLineBreakIfNeeded(); - InlineIterator handleEndOfLine(); - - void clearLineBreakIfFitsOnLine(bool ignoringTrailingSpace = false) - { - if (m_width.fitsOnLine(ignoringTrailingSpace) || m_lastWS == NOWRAP) - m_lineBreak.clear(); - } - - void commitLineBreakAtCurrentWidth(RenderObject* object, unsigned offset = 0, int nextBreak = -1) - { - m_width.commit(); - m_lineBreak.moveTo(object, offset, nextBreak); - } - -private: - LineBreaker& m_lineBreaker; - InlineBidiResolver& m_resolver; - - InlineIterator m_current; - InlineIterator m_lineBreak; - InlineIterator m_startOfIgnoredSpaces; - - RenderBlockFlow& m_block; - RenderObject* m_lastObject; - RenderObject* m_nextObject; - - RenderStyle* m_currentStyle; - - // Firefox and Opera will allow a table cell to grow to fit an image inside it under - // very specific circumstances (in order to match common WinIE renderings). - // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.) - RenderStyle& m_blockStyle; - - LineInfo& m_lineInfo; - - RenderTextInfo& m_renderTextInfo; - - FloatingObject* m_lastFloatFromPreviousLine; - - LineWidth m_width; - - EWhiteSpace m_currWS; - EWhiteSpace m_lastWS; - - bool m_preservesNewline; - bool m_atStart; - - // This variable is used only if whitespace isn't set to PRE, and it tells us whether - // or not we are currently ignoring whitespace. - bool m_ignoringSpaces; - - // This variable tracks whether the very last character we saw was a space. We use - // this to detect when we encounter a second space so we know we have to terminate - // a run. - bool m_currentCharacterIsSpace; - bool m_currentCharacterIsWS; - bool m_appliedStartWidth; - bool m_includeEndWidth; - bool m_autoWrap; - bool m_autoWrapWasEverTrueOnLine; - bool m_floatsFitOnLine; - bool m_collapseWhiteSpace; - bool m_startingNewParagraph; - bool m_allowImagesToBreak; - bool m_atEnd; - bool m_hadUncommittedWidthBeforeCurrent; - - LineMidpointState& m_lineMidpointState; - - TrailingObjects m_trailingObjects; -}; - -inline void BreakingContext::initializeForCurrentObject() -{ - m_hadUncommittedWidthBeforeCurrent = !!m_width.uncommittedWidth(); - - m_currentStyle = &m_current.renderer()->style(); - - ASSERT(m_currentStyle); - - m_nextObject = bidiNextSkippingEmptyInlines(m_block, m_current.renderer()); - if (m_nextObject && m_nextObject->parent() && !m_nextObject->parent()->isDescendantOf(m_current.renderer()->parent())) - m_includeEndWidth = true; - - m_currWS = m_current.renderer()->isReplaced() ? m_current.renderer()->parent()->style().whiteSpace() : m_currentStyle->whiteSpace(); - m_lastWS = m_lastObject->isReplaced() ? m_lastObject->parent()->style().whiteSpace() : m_lastObject->style().whiteSpace(); - - m_autoWrap = RenderStyle::autoWrap(m_currWS); - m_autoWrapWasEverTrueOnLine = m_autoWrapWasEverTrueOnLine || m_autoWrap; - -#if ENABLE(SVG) - m_preservesNewline = m_current.renderer()->isSVGInlineText() ? false : RenderStyle::preserveNewline(m_currWS); -#else - m_preservesNewline = RenderStyle::preserveNewline(m_currWS); -#endif - - m_collapseWhiteSpace = RenderStyle::collapseWhiteSpace(m_currWS); -} - -inline void BreakingContext::increment() -{ - // Clear out our character space bool, since inline <pre>s don't collapse whitespace - // with adjacent inline normal/nowrap spans. - if (!m_collapseWhiteSpace) - m_currentCharacterIsSpace = false; - - m_current.moveToStartOf(m_nextObject); - m_atStart = false; -} - -inline void BreakingContext::handleBR(EClear& clear) -{ - if (m_width.fitsOnLine()) { - RenderObject* br = m_current.renderer(); - m_lineBreak.moveToStartOf(br); - m_lineBreak.increment(); - - // A <br> always breaks a line, so don't let the line be collapsed - // away. Also, the space at the end of a line with a <br> does not - // get collapsed away. It only does this if the previous line broke - // cleanly. Otherwise the <br> has no effect on whether the line is - // empty or not. - if (m_startingNewParagraph) - m_lineInfo.setEmpty(false, &m_block, &m_width); - m_trailingObjects.clear(); - m_lineInfo.setPreviousLineBrokeCleanly(true); - - // A <br> with clearance always needs a linebox in case the lines below it get dirtied later and - // need to check for floats to clear - so if we're ignoring spaces, stop ignoring them and add a - // run for this object. - if (m_ignoringSpaces && m_currentStyle->clear() != CNONE) - m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(br); - // If we were preceded by collapsing space and are in a right-aligned container we need to ensure the space gets - // collapsed away so that it doesn't push the text out from the container's right-hand edge. - // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results. - else if (m_ignoringSpaces && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) - m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset())); - - if (!m_lineInfo.isEmpty()) - clear = m_currentStyle->clear(); - } - m_atEnd = true; -} - -inline LayoutUnit borderPaddingMarginStart(const RenderInline& child) -{ - return child.marginStart() + child.paddingStart() + child.borderStart(); -} - -inline LayoutUnit borderPaddingMarginEnd(const RenderInline& child) -{ - return child.marginEnd() + child.paddingEnd() + child.borderEnd(); -} - -inline bool shouldAddBorderPaddingMargin(RenderObject* child) -{ - // When deciding whether we're at the edge of an inline, adjacent collapsed whitespace is the same as no sibling at all. - return !child || (child->isText() && !toRenderText(child)->textLength()); -} - -inline RenderObject* previousInFlowSibling(RenderObject* child) -{ - child = child->previousSibling(); - while (child && child->isOutOfFlowPositioned()) - child = child->previousSibling(); - return child; -} - -inline LayoutUnit inlineLogicalWidth(RenderObject* child, bool checkStartEdge = true, bool checkEndEdge = true) -{ - unsigned lineDepth = 1; - LayoutUnit extraWidth = 0; - RenderElement* parent = child->parent(); - while (parent->isRenderInline() && lineDepth++ < cMaxLineDepth) { - const RenderInline& parentAsRenderInline = toRenderInline(*parent); - if (!isEmptyInline(parentAsRenderInline)) { - checkStartEdge = checkStartEdge && shouldAddBorderPaddingMargin(previousInFlowSibling(child)); - if (checkStartEdge) - extraWidth += borderPaddingMarginStart(parentAsRenderInline); - checkEndEdge = checkEndEdge && shouldAddBorderPaddingMargin(child->nextSibling()); - if (checkEndEdge) - extraWidth += borderPaddingMarginEnd(parentAsRenderInline); - if (!checkStartEdge && !checkEndEdge) - return extraWidth; - } - child = parent; - parent = child->parent(); - } - return extraWidth; -} - -inline void BreakingContext::handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects) -{ - // If our original display wasn't an inline type, then we can - // go ahead and determine our static inline position now. - RenderBox* box = toRenderBox(m_current.renderer()); - bool isInlineType = box->style().isOriginalDisplayInlineType(); - if (!isInlineType) - m_block.setStaticInlinePositionForChild(*box, m_block.logicalHeight(), m_block.startOffsetForContent(m_block.logicalHeight())); - else { - // If our original display was an INLINE type, then we can go ahead - // and determine our static y position now. - box->layer()->setStaticBlockPosition(m_block.logicalHeight()); - } - - // If we're ignoring spaces, we have to stop and include this object and - // then start ignoring spaces again. - if (isInlineType || box->container()->isRenderInline()) { - if (m_ignoringSpaces) - m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(box); - m_trailingObjects.appendBoxIfNeeded(box); - } else - positionedObjects.append(box); - - m_width.addUncommittedWidth(inlineLogicalWidth(box)); - // Reset prior line break context characters. - m_renderTextInfo.m_lineBreakIterator.resetPriorContext(); -} - -inline void BreakingContext::handleFloat() -{ - RenderBox& floatBox = toRenderBox(*m_current.renderer()); - FloatingObject* floatingObject = m_lineBreaker.insertFloatingObject(floatBox); - // check if it fits in the current line. - // If it does, position it now, otherwise, position - // it after moving to next line (in newLine() func) - // FIXME: Bug 110372: Properly position multiple stacked floats with non-rectangular shape outside. - if (m_floatsFitOnLine && m_width.fitsOnLineExcludingTrailingWhitespace(m_block.logicalWidthForFloat(floatingObject))) { - m_lineBreaker.positionNewFloatOnLine(floatingObject, m_lastFloatFromPreviousLine, m_lineInfo, m_width); - if (m_lineBreak.renderer() == m_current.renderer()) { - ASSERT(!m_lineBreak.offset()); - m_lineBreak.increment(); - } - } else - m_floatsFitOnLine = false; - // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for floating element. - m_renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter); -} - -// This is currently just used for list markers and inline flows that have line boxes. Neither should -// have an effect on whitespace at the start of the line. -inline bool shouldSkipWhitespaceAfterStartObject(RenderBlockFlow& block, RenderObject* o, LineMidpointState& lineMidpointState) -{ - RenderObject* next = bidiNextSkippingEmptyInlines(block, o); - while (next && next->isFloatingOrOutOfFlowPositioned()) - next = bidiNextSkippingEmptyInlines(block, next); - - if (next && next->isText() && toRenderText(next)->textLength() > 0) { - RenderText* nextText = toRenderText(next); - UChar nextChar = nextText->characterAt(0); - if (nextText->style().isCollapsibleWhiteSpace(nextChar)) { - lineMidpointState.startIgnoringSpaces(InlineIterator(0, o, 0)); - return true; - } - } - - return false; -} - -inline void BreakingContext::handleEmptyInline() -{ - RenderInline& flowBox = toRenderInline(*m_current.renderer()); - - // This should only end up being called on empty inlines - ASSERT(isEmptyInline(flowBox)); - - // Now that some inline flows have line boxes, if we are already ignoring spaces, we need - // to make sure that we stop to include this object and then start ignoring spaces again. - // If this object is at the start of the line, we need to behave like list markers and - // start ignoring spaces. - bool requiresLineBox = alwaysRequiresLineBox(flowBox); - if (requiresLineBox || requiresLineBoxForContent(flowBox, m_lineInfo)) { - // An empty inline that only has line-height, vertical-align or font-metrics will only get a - // line box to affect the height of the line if the rest of the line is not empty. - if (requiresLineBox) - m_lineInfo.setEmpty(false, &m_block, &m_width); - if (m_ignoringSpaces) { - m_trailingObjects.clear(); - m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(m_current.renderer()); - } else if (m_blockStyle.collapseWhiteSpace() && m_resolver.position().renderer() == m_current.renderer() - && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) { - // Like with list markers, we start ignoring spaces to make sure that any - // additional spaces we see will be discarded. - m_currentCharacterIsSpace = true; - m_currentCharacterIsWS = true; - m_ignoringSpaces = true; - } else - m_trailingObjects.appendBoxIfNeeded(&flowBox); - } - - m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer()) + borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox)); -} - -inline void BreakingContext::handleReplaced() -{ - RenderBox& replacedBox = toRenderBox(*m_current.renderer()); - - if (m_atStart) - m_width.updateAvailableWidth(replacedBox.logicalHeight()); - - // Break on replaced elements if either has normal white-space. - if ((m_autoWrap || RenderStyle::autoWrap(m_lastWS)) && (!m_current.renderer()->isImage() || m_allowImagesToBreak)) { - m_width.commit(); - m_lineBreak.moveToStartOf(m_current.renderer()); - } - - if (m_ignoringSpaces) - m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), 0)); - - m_lineInfo.setEmpty(false, &m_block, &m_width); - m_ignoringSpaces = false; - m_currentCharacterIsSpace = false; - m_currentCharacterIsWS = false; - m_trailingObjects.clear(); - - // Optimize for a common case. If we can't find whitespace after the list - // item, then this is all moot. - LayoutUnit replacedLogicalWidth = m_block.logicalWidthForChild(replacedBox) + m_block.marginStartForChild(replacedBox) + m_block.marginEndForChild(replacedBox) + inlineLogicalWidth(m_current.renderer()); - if (m_current.renderer()->isListMarker()) { - if (m_blockStyle.collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) { - // Like with inline flows, we start ignoring spaces to make sure that any - // additional spaces we see will be discarded. - m_currentCharacterIsSpace = true; - m_currentCharacterIsWS = false; - m_ignoringSpaces = true; - } - if (toRenderListMarker(*m_current.renderer()).isInside()) - m_width.addUncommittedWidth(replacedLogicalWidth); - } else - m_width.addUncommittedWidth(replacedLogicalWidth); - if (m_current.renderer()->isRubyRun()) - m_width.applyOverhang(toRenderRubyRun(m_current.renderer()), m_lastObject, m_nextObject); - // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for replaced element. - m_renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter); -} - -inline float firstPositiveWidth(const WordMeasurements& wordMeasurements) -{ - for (size_t i = 0; i < wordMeasurements.size(); ++i) { - if (wordMeasurements[i].width > 0) - return wordMeasurements[i].width; - } - return 0; -} - -#if ENABLE(CSS_SHAPES) -inline void updateSegmentsForShapes(RenderBlockFlow& block, const FloatingObject* lastFloatFromPreviousLine, const WordMeasurements& wordMeasurements, LineWidth& width, bool isFirstLine) -{ - ASSERT(lastFloatFromPreviousLine); - - ShapeInsideInfo* shapeInsideInfo = block.layoutShapeInsideInfo(); - if (!lastFloatFromPreviousLine->isPlaced() || !shapeInsideInfo) - return; - - bool isHorizontalWritingMode = block.isHorizontalWritingMode(); - LayoutUnit logicalOffsetFromShapeContainer = block.logicalOffsetFromShapeAncestorContainer(&shapeInsideInfo->owner()).height(); - - LayoutUnit lineLogicalTop = block.logicalHeight() + logicalOffsetFromShapeContainer; - LayoutUnit lineLogicalHeight = block.lineHeight(isFirstLine, isHorizontalWritingMode ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); - LayoutUnit lineLogicalBottom = lineLogicalTop + lineLogicalHeight; - - LayoutUnit floatLogicalTop = block.logicalTopForFloat(lastFloatFromPreviousLine); - LayoutUnit floatLogicalBottom = block.logicalBottomForFloat(lastFloatFromPreviousLine); - - bool lineOverlapsWithFloat = (floatLogicalTop < lineLogicalBottom) && (lineLogicalTop < floatLogicalBottom); - if (!lineOverlapsWithFloat) - return; - - float minSegmentWidth = firstPositiveWidth(wordMeasurements); - - LayoutUnit floatLogicalWidth = block.logicalWidthForFloat(lastFloatFromPreviousLine); - LayoutUnit availableLogicalWidth = block.logicalWidth() - block.logicalRightForFloat(lastFloatFromPreviousLine); - if (availableLogicalWidth < minSegmentWidth) - block.setLogicalHeight(floatLogicalBottom); - - if (block.logicalHeight() < floatLogicalTop) { - shapeInsideInfo->adjustLogicalLineTop(minSegmentWidth + floatLogicalWidth); - block.setLogicalHeight(shapeInsideInfo->logicalLineTop() - logicalOffsetFromShapeContainer); - } - - lineLogicalTop = block.logicalHeight() + logicalOffsetFromShapeContainer; - - shapeInsideInfo->updateSegmentsForLine(lineLogicalTop, lineLogicalHeight); - width.updateCurrentShapeSegment(); - width.updateAvailableWidth(); -} -#endif - -inline bool iteratorIsBeyondEndOfRenderCombineText(const InlineIterator& iter, RenderCombineText& renderer) -{ - return iter.renderer() == &renderer && iter.offset() >= renderer.textLength(); -} - -inline void nextCharacter(UChar& currentCharacter, UChar& lastCharacter, UChar& secondToLastCharacter) -{ - secondToLastCharacter = lastCharacter; - lastCharacter = currentCharacter; -} - -// FIXME: Don't let counters mark themselves as needing pref width recalcs during layout -// so we don't need this hack. -inline void updateCounterIfNeeded(RenderText& renderText) -{ - if (!renderText.preferredLogicalWidthsDirty() || !renderText.isCounter()) - return; - toRenderCounter(renderText).updateCounter(); -} - -inline float measureHyphenWidth(RenderText* renderer, const Font& font, HashSet<const SimpleFontData*>* fallbackFonts = 0) -{ - const RenderStyle& style = renderer->style(); - return font.width(RenderBlock::constructTextRun(renderer, font, style.hyphenString().string(), style), fallbackFonts); -} - -ALWAYS_INLINE float textWidth(RenderText* text, unsigned from, unsigned len, const Font& font, float xPos, bool isFixedPitch, bool collapseWhiteSpace, HashSet<const SimpleFontData*>& fallbackFonts, TextLayout* layout = 0) -{ - const RenderStyle& style = text->style(); - - GlyphOverflow glyphOverflow; - if (isFixedPitch || (!from && len == text->textLength()) || style.hasTextCombine()) - return text->width(from, len, font, xPos, &fallbackFonts, &glyphOverflow); - - if (layout) - return Font::width(*layout, from, len, &fallbackFonts); - - TextRun run = RenderBlock::constructTextRun(text, font, text, from, len, style); - run.setCharactersLength(text->textLength() - from); - ASSERT(run.charactersLength() >= run.length()); - - run.setCharacterScanForCodePath(!text->canUseSimpleFontCodePath()); - run.setTabSize(!collapseWhiteSpace, style.tabSize()); - run.setXPos(xPos); - return font.width(run, &fallbackFonts, &glyphOverflow); -} - -// Adding a pair of midpoints before a character will split it out into a new line box. -inline void ensureCharacterGetsLineBox(LineMidpointState& lineMidpointState, InlineIterator& textParagraphSeparator) -{ - InlineIterator midpoint(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset()); - lineMidpointState.startIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset() - 1)); - lineMidpointState.stopIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset())); -} - -inline void tryHyphenating(RenderText* text, const Font& font, const AtomicString& localeIdentifier, unsigned consecutiveHyphenatedLines, int consecutiveHyphenatedLinesLimit, int minimumPrefixLimit, int minimumSuffixLimit, unsigned lastSpace, unsigned pos, float xPos, int availableWidth, bool isFixedPitch, bool collapseWhiteSpace, int lastSpaceWordSpacing, InlineIterator& lineBreak, int nextBreakable, bool& hyphenated) -{ - // Map 'hyphenate-limit-{before,after}: auto;' to 2. - unsigned minimumPrefixLength; - unsigned minimumSuffixLength; - - if (minimumPrefixLimit < 0) - minimumPrefixLength = 2; - else - minimumPrefixLength = static_cast<unsigned>(minimumPrefixLimit); - - if (minimumSuffixLimit < 0) - minimumSuffixLength = 2; - else - minimumSuffixLength = static_cast<unsigned>(minimumSuffixLimit); - - if (pos - lastSpace <= minimumSuffixLength) - return; - - if (consecutiveHyphenatedLinesLimit >= 0 && consecutiveHyphenatedLines >= static_cast<unsigned>(consecutiveHyphenatedLinesLimit)) - return; - - int hyphenWidth = measureHyphenWidth(text, font); - - float maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing; - // If the maximum width available for the prefix before the hyphen is small, then it is very unlikely - // that an hyphenation opportunity exists, so do not bother to look for it. - if (maxPrefixWidth <= font.pixelSize() * 5 / 4) - return; - - const RenderStyle& style = text->style(); - TextRun run = RenderBlock::constructTextRun(text, font, text, lastSpace, pos - lastSpace, style); - run.setCharactersLength(text->textLength() - lastSpace); - ASSERT(run.charactersLength() >= run.length()); - - run.setTabSize(!collapseWhiteSpace, style.tabSize()); - run.setXPos(xPos + lastSpaceWordSpacing); - - unsigned prefixLength = font.offsetForPosition(run, maxPrefixWidth, false); - if (prefixLength < minimumPrefixLength) - return; - - prefixLength = lastHyphenLocation(text->deprecatedCharacters() + lastSpace, pos - lastSpace, std::min(prefixLength, pos - lastSpace - minimumSuffixLength) + 1, localeIdentifier); - if (!prefixLength || prefixLength < minimumPrefixLength) - return; - - // When lastSapce is a space, which it always is except sometimes at the beginning of a line or after collapsed - // space, it should not count towards hyphenate-limit-before. - if (prefixLength == minimumPrefixLength) { - UChar characterAtLastSpace = text->characterAt(lastSpace); - if (characterAtLastSpace == ' ' || characterAtLastSpace == '\n' || characterAtLastSpace == '\t' || characterAtLastSpace == noBreakSpace) - return; - } - - ASSERT(pos - lastSpace - prefixLength >= minimumSuffixLength); - -#if !ASSERT_DISABLED - HashSet<const SimpleFontData*> fallbackFonts; - float prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace, fallbackFonts) + lastSpaceWordSpacing; - ASSERT(xPos + prefixWidth <= availableWidth); -#else - UNUSED_PARAM(isFixedPitch); -#endif - - lineBreak.moveTo(text, lastSpace + prefixLength, nextBreakable); - hyphenated = true; -} - -inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated, unsigned& consecutiveHyphenatedLines) -{ - if (!m_current.offset()) - m_appliedStartWidth = false; - - RenderText* renderText = toRenderText(m_current.renderer()); - -#if ENABLE(SVG) - bool isSVGText = renderText->isSVGInlineText(); -#endif - - // If we have left a no-wrap inline and entered an autowrap inline while ignoring spaces - // then we need to mark the start of the autowrap inline as a potential linebreak now. - if (m_autoWrap && !RenderStyle::autoWrap(m_lastWS) && m_ignoringSpaces) - commitLineBreakAtCurrentWidth(m_current.renderer()); - - if (renderText->style().hasTextCombine() && m_current.renderer()->isCombineText() && !toRenderCombineText(*m_current.renderer()).isCombined()) { - RenderCombineText& combineRenderer = toRenderCombineText(*m_current.renderer()); - combineRenderer.combineText(); - // The length of the renderer's text may have changed. Increment stale iterator positions - if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreak, combineRenderer)) { - ASSERT(iteratorIsBeyondEndOfRenderCombineText(m_resolver.position(), combineRenderer)); - m_lineBreak.increment(); - m_resolver.increment(); - } - } - - const RenderStyle& style = lineStyle(*renderText->parent(), m_lineInfo); - const Font& font = style.font(); - bool isFixedPitch = font.isFixedPitch(); - bool canHyphenate = style.hyphens() == HyphensAuto && WebCore::canHyphenate(style.locale()); - - unsigned lastSpace = m_current.offset(); - float wordSpacing = m_currentStyle->font().wordSpacing(); - float lastSpaceWordSpacing = 0; - float wordSpacingForWordMeasurement = 0; - - float wrapW = m_width.uncommittedWidth() + inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, true); - float charWidth = 0; - bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE; - // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word, - // which is only possible if the word is the first thing on the line, that is, if |w| is zero. - bool breakWords = m_currentStyle->breakWords() && ((m_autoWrap && !m_width.committedWidth()) || m_currWS == PRE); - bool midWordBreak = false; - bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap; - float hyphenWidth = 0; -#if ENABLE(SVG) - if (isSVGText) { - breakWords = false; - breakAll = false; - } -#endif - - if (m_renderTextInfo.m_text != renderText) { - updateCounterIfNeeded(*renderText); - m_renderTextInfo.m_text = renderText; - m_renderTextInfo.m_font = &font; - m_renderTextInfo.m_layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace); - m_renderTextInfo.m_lineBreakIterator.resetStringAndReleaseIterator(renderText->text(), style.locale()); - } else if (m_renderTextInfo.m_layout && m_renderTextInfo.m_font != &font) { - m_renderTextInfo.m_font = &font; - m_renderTextInfo.m_layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace); - } - - TextLayout* textLayout = m_renderTextInfo.m_layout.get(); - - // Non-zero only when kerning is enabled and TextLayout isn't used, in which case we measure - // words with their trailing space, then subtract its width. - HashSet<const SimpleFontData*> fallbackFonts; - float wordTrailingSpaceWidth = (font.typesettingFeatures() & Kerning) && !textLayout ? font.width(RenderBlock::constructTextRun(renderText, font, &space, 1, style), &fallbackFonts) + wordSpacing : 0; - - UChar lastCharacter = m_renderTextInfo.m_lineBreakIterator.lastCharacter(); - UChar secondToLastCharacter = m_renderTextInfo.m_lineBreakIterator.secondToLastCharacter(); - for (; m_current.offset() < renderText->textLength(); m_current.fastIncrementInTextNode()) { - bool previousCharacterIsSpace = m_currentCharacterIsSpace; - bool previousCharacterIsWS = m_currentCharacterIsWS; - UChar c = m_current.current(); - m_currentCharacterIsSpace = c == ' ' || c == '\t' || (!m_preservesNewline && (c == '\n')); - - if (!m_collapseWhiteSpace || !m_currentCharacterIsSpace) - m_lineInfo.setEmpty(false, &m_block, &m_width); - - if (c == softHyphen && m_autoWrap && !hyphenWidth && style.hyphens() != HyphensNone) { - hyphenWidth = measureHyphenWidth(renderText, font, &fallbackFonts); - m_width.addUncommittedWidth(hyphenWidth); - } - - bool applyWordSpacing = false; - - m_currentCharacterIsWS = m_currentCharacterIsSpace || (breakNBSP && c == noBreakSpace); - - if ((breakAll || breakWords) && !midWordBreak && (!m_currentCharacterIsSpace || style.whiteSpace() != PRE_WRAP)) { - wrapW += charWidth; - bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText->textLength() && U16_IS_TRAIL((*renderText)[m_current.offset() + 1]); - charWidth = textWidth(renderText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, font, m_width.committedWidth() + wrapW, isFixedPitch, m_collapseWhiteSpace, fallbackFonts, textLayout); - midWordBreak = m_width.committedWidth() + wrapW + charWidth > m_width.availableWidth(); - } - - int nextBreakablePosition = m_current.nextBreakablePosition(); - bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.m_lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP) - && (style.hyphens() != HyphensNone || (m_current.previousInSameNode() != softHyphen))); - m_current.setNextBreakablePosition(nextBreakablePosition); - - if (betweenWords || midWordBreak) { - bool stoppedIgnoringSpaces = false; - if (m_ignoringSpaces) { - lastSpaceWordSpacing = 0; - if (!m_currentCharacterIsSpace) { - // Stop ignoring spaces and begin at this - // new point. - m_ignoringSpaces = false; - wordSpacingForWordMeasurement = 0; - lastSpace = m_current.offset(); // e.g., "Foo goo", don't add in any of the ignored spaces. - m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset())); - stoppedIgnoringSpaces = true; - } else { - // Just keep ignoring these spaces. - nextCharacter(c, lastCharacter, secondToLastCharacter); - continue; - } - } - - wordMeasurements.grow(wordMeasurements.size() + 1); - WordMeasurement& wordMeasurement = wordMeasurements.last(); - - wordMeasurement.renderer = renderText; - wordMeasurement.endOffset = m_current.offset(); - wordMeasurement.startOffset = lastSpace; - - float additionalTempWidth; - if (wordTrailingSpaceWidth && c == ' ') - additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() + 1 - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) - wordTrailingSpaceWidth; - else - additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout); - - if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty()) - wordMeasurement.fallbackFonts.swap(fallbackFonts); - fallbackFonts.clear(); - - wordMeasurement.width = additionalTempWidth + wordSpacingForWordMeasurement; - additionalTempWidth += lastSpaceWordSpacing; - m_width.addUncommittedWidth(additionalTempWidth); - - if (m_collapseWhiteSpace && previousCharacterIsSpace && m_currentCharacterIsSpace && additionalTempWidth) - m_width.setTrailingWhitespaceWidth(additionalTempWidth); - - if (!m_appliedStartWidth) { - m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer(), true, false)); - m_appliedStartWidth = true; - } - -#if ENABLE(CSS_SHAPES) - if (m_lastFloatFromPreviousLine) - updateSegmentsForShapes(m_block, m_lastFloatFromPreviousLine, wordMeasurements, m_width, m_lineInfo.isFirstLine()); -#endif - applyWordSpacing = wordSpacing && m_currentCharacterIsSpace; - - if (!m_width.committedWidth() && m_autoWrap && !m_width.fitsOnLine()) - m_width.fitBelowFloats(); - - if (m_autoWrap || breakWords) { - // If we break only after white-space, consider the current character - // as candidate width for this line. - bool lineWasTooWide = false; - if (m_width.fitsOnLine() && m_currentCharacterIsWS && m_currentStyle->breakOnlyAfterWhiteSpace() && !midWordBreak) { - float charWidth = textWidth(renderText, m_current.offset(), 1, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) + (applyWordSpacing ? wordSpacing : 0); - // Check if line is too big even without the extra space - // at the end of the line. If it is not, do nothing. - // If the line needs the extra whitespace to be too long, - // then move the line break to the space and skip all - // additional whitespace. - if (!m_width.fitsOnLineIncludingExtraWidth(charWidth)) { - lineWasTooWide = true; - m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition()); - m_lineBreaker.skipTrailingWhitespace(m_lineBreak, m_lineInfo); - } - } - if (lineWasTooWide || !m_width.fitsOnLine()) { - if (canHyphenate && !m_width.fitsOnLine()) { - tryHyphenating(renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, m_lineBreak, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated); - if (m_lineBreaker.m_hyphenated) { - m_atEnd = true; - return false; - } - } - if (m_lineBreak.atTextParagraphSeparator()) { - if (!stoppedIgnoringSpaces && m_current.offset() > 0) - ensureCharacterGetsLineBox(m_lineMidpointState, m_current); - m_lineBreak.increment(); - m_lineInfo.setPreviousLineBrokeCleanly(true); - wordMeasurement.endOffset = m_lineBreak.offset(); - } - if (m_lineBreak.renderer() && m_lineBreak.offset() && m_lineBreak.renderer()->isText() && toRenderText(m_lineBreak.renderer())->textLength() && toRenderText(m_lineBreak.renderer())->characterAt(m_lineBreak.offset() - 1) == softHyphen && style.hyphens() != HyphensNone) - hyphenated = true; - if (m_lineBreak.offset() && m_lineBreak.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) { - if (charWidth) { - wordMeasurement.endOffset = m_lineBreak.offset(); - wordMeasurement.width = charWidth; - } - } - // Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace. - if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) { - m_atEnd = true; - return false; - } - } else { - if (!betweenWords || (midWordBreak && !m_autoWrap)) - m_width.addUncommittedWidth(-additionalTempWidth); - if (hyphenWidth) { - // Subtract the width of the soft hyphen out since we fit on a line. - m_width.addUncommittedWidth(-hyphenWidth); - hyphenWidth = 0; - } - } - } - - if (c == '\n' && m_preservesNewline) { - if (!stoppedIgnoringSpaces && m_current.offset()) - ensureCharacterGetsLineBox(m_lineMidpointState, m_current); - m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition()); - m_lineBreak.increment(); - m_lineInfo.setPreviousLineBrokeCleanly(true); - return true; - } - - if (m_autoWrap && betweenWords) { - m_width.commit(); - wrapW = 0; - m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition()); - // Auto-wrapping text should not wrap in the middle of a word once it has had an - // opportunity to break after a word. - breakWords = false; - } - - if (midWordBreak && !U16_IS_TRAIL(c) && !(U_GET_GC_MASK(c) & U_GC_M_MASK)) { - // Remember this as a breakable position in case - // adding the end width forces a break. - m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition()); - midWordBreak &= (breakWords || breakAll); - } - - if (betweenWords) { - lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; - wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0; - lastSpace = m_current.offset(); - } - - if (!m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) { - // If we encounter a newline, or if we encounter a - // second space, we need to go ahead and break up this - // run and enter a mode where we start collapsing spaces. - if (m_currentCharacterIsSpace && previousCharacterIsSpace) { - m_ignoringSpaces = true; - - // We just entered a mode where we are ignoring - // spaces. Create a midpoint to terminate the run - // before the second space. - m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces); - m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, InlineIterator(), TrailingObjects::DoNotCollapseFirstSpace); - } - } - } else if (m_ignoringSpaces) { - // Stop ignoring spaces and begin at this - // new point. - m_ignoringSpaces = false; - lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; - wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurements.last().width) ? wordSpacing : 0; - lastSpace = m_current.offset(); // e.g., "Foo goo", don't add in any of the ignored spaces. - m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset())); - } -#if ENABLE(SVG) - if (isSVGText && m_current.offset()) { - // Force creation of new InlineBoxes for each absolute positioned character (those that start new text chunks). - if (toRenderSVGInlineText(renderText)->characterStartsNewTextChunk(m_current.offset())) - ensureCharacterGetsLineBox(m_lineMidpointState, m_current); - } -#endif - - if (m_currentCharacterIsSpace && !previousCharacterIsSpace) { - m_startOfIgnoredSpaces.setRenderer(m_current.renderer()); - m_startOfIgnoredSpaces.setOffset(m_current.offset()); - // Spaces after right-aligned text and before a line-break get collapsed away completely so that the trailing - // space doesn't seem to push the text out from the right-hand edge. - // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results. - if (m_nextObject && m_nextObject->isBR() && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) { - m_startOfIgnoredSpaces.setOffset(m_startOfIgnoredSpaces.offset() - 1); - // If there's just a single trailing space start ignoring it now so it collapses away. - if (m_current.offset() == renderText->textLength() - 1) - m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces); - } - } - - if (!m_currentCharacterIsWS && previousCharacterIsWS) { - if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace()) - m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition()); - } - - if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces) - m_trailingObjects.setTrailingWhitespace(toRenderText(m_current.renderer())); - else if (!m_currentStyle->collapseWhiteSpace() || !m_currentCharacterIsSpace) - m_trailingObjects.clear(); - - m_atStart = false; - nextCharacter(c, lastCharacter, secondToLastCharacter); - } - - m_renderTextInfo.m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter); - - wordMeasurements.grow(wordMeasurements.size() + 1); - WordMeasurement& wordMeasurement = wordMeasurements.last(); - wordMeasurement.renderer = renderText; - - // IMPORTANT: current.m_pos is > length here! - float additionalTempWidth = m_ignoringSpaces ? 0 : textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout); - wordMeasurement.startOffset = lastSpace; - wordMeasurement.endOffset = m_current.offset(); - wordMeasurement.width = m_ignoringSpaces ? 0 : additionalTempWidth + wordSpacingForWordMeasurement; - additionalTempWidth += lastSpaceWordSpacing; - - float inlineLogicalTempWidth = inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, m_includeEndWidth); - m_width.addUncommittedWidth(additionalTempWidth + inlineLogicalTempWidth); - - if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty()) - wordMeasurement.fallbackFonts.swap(fallbackFonts); - fallbackFonts.clear(); - - if (m_collapseWhiteSpace && m_currentCharacterIsSpace && additionalTempWidth) - m_width.setTrailingWhitespaceWidth(additionalTempWidth, inlineLogicalTempWidth); - - m_includeEndWidth = false; - - if (!m_width.fitsOnLine()) { - if (canHyphenate) - tryHyphenating(renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, m_lineBreak, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated); - - if (!hyphenated && m_lineBreak.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) { - hyphenated = true; - m_atEnd = true; - } - } - return false; -} - -inline bool textBeginsWithBreakablePosition(RenderObject* next) -{ - ASSERT(next->isText()); - RenderText* nextText = toRenderText(next); - if (!nextText->textLength()) - return false; - UChar c = nextText->characterAt(0); - return c == ' ' || c == '\t' || (c == '\n' && !nextText->preservesNewline()); -} - -inline bool BreakingContext::canBreakAtThisPosition() -{ - // If we are no-wrap and have found a line-breaking opportunity already then we should take it. - if (m_width.committedWidth() && !m_width.fitsOnLine(m_currentCharacterIsSpace) && m_currWS == NOWRAP) - return true; - - // Avoid breaking before empty inlines. - if (m_nextObject && m_nextObject->isRenderInline() && isEmptyInline(toRenderInline(*m_nextObject))) - return false; - - // Return early if we autowrap and the current character is a space as we will always want to break at such a position. - if (m_autoWrap && m_currentCharacterIsSpace) - return true; - - if (m_nextObject && m_nextObject->isLineBreakOpportunity()) - return m_autoWrap; - - bool nextIsAutoWrappingText = (m_nextObject && m_nextObject->isText() && (m_autoWrap || m_nextObject->style().autoWrap())); - if (!nextIsAutoWrappingText) - return m_autoWrap; - bool currentIsTextOrEmptyInline = m_current.renderer()->isText() || (m_current.renderer()->isRenderInline() && isEmptyInline(toRenderInline(*m_current.renderer()))); - if (!currentIsTextOrEmptyInline) - return m_autoWrap; - - bool canBreakHere = !m_currentCharacterIsSpace && textBeginsWithBreakablePosition(m_nextObject); - - // See if attempting to fit below floats creates more available width on the line. - if (!m_width.fitsOnLine() && !m_width.committedWidth()) - m_width.fitBelowFloats(); - - bool canPlaceOnLine = m_width.fitsOnLine() || !m_autoWrapWasEverTrueOnLine; - - if (canPlaceOnLine && canBreakHere) - commitLineBreakAtCurrentWidth(m_nextObject); - - return canBreakHere; -} - -inline void BreakingContext::commitAndUpdateLineBreakIfNeeded() -{ - bool checkForBreak = canBreakAtThisPosition(); - - if (checkForBreak && !m_width.fitsOnLine(m_ignoringSpaces)) { - // if we have floats, try to get below them. - if (m_currentCharacterIsSpace && !m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) - m_trailingObjects.clear(); - - if (m_width.committedWidth()) { - m_atEnd = true; - return; - } - - m_width.fitBelowFloats(); - - // |width| may have been adjusted because we got shoved down past a float (thus - // giving us more room), so we need to retest, and only jump to - // the end label if we still don't fit on the line. -dwh - if (!m_width.fitsOnLine(m_ignoringSpaces)) { - m_atEnd = true; - return; - } - } else if (m_blockStyle.autoWrap() && !m_width.fitsOnLine() && !m_width.committedWidth()) { - // If the container autowraps but the current child does not then we still need to ensure that it - // wraps and moves below any floats. - m_width.fitBelowFloats(); - } - - if (!m_current.renderer()->isFloatingOrOutOfFlowPositioned()) { - m_lastObject = m_current.renderer(); - if (m_lastObject->isReplaced() && m_autoWrap && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!m_lastObject->isListMarker() || toRenderListMarker(*m_lastObject).isInside())) { - m_width.commit(); - m_lineBreak.moveToStartOf(m_nextObject); - } - } -} - -inline void checkMidpoints(LineMidpointState& lineMidpointState, InlineIterator& lBreak) -{ - // Check to see if our last midpoint is a start point beyond the line break. If so, - // shave it off the list, and shave off a trailing space if the previous end point doesn't - // preserve whitespace. - if (lBreak.renderer() && lineMidpointState.numMidpoints() && !(lineMidpointState.numMidpoints() % 2)) { - InlineIterator* midpoints = lineMidpointState.midpoints().data(); - InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints() - 2]; - const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints() - 1]; - InlineIterator currpoint = endpoint; - while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) - currpoint.increment(); - if (currpoint == lBreak) { - // We hit the line break before the start point. Shave off the start point. - lineMidpointState.decreaseNumMidpoints(); - if (endpoint.renderer()->style().collapseWhiteSpace() && endpoint.renderer()->isText()) - endpoint.setOffset(endpoint.offset() - 1); - } - } -} - -inline InlineIterator BreakingContext::handleEndOfLine() -{ -#if ENABLE(CSS_SHAPES) - ShapeInsideInfo* shapeInfo = m_block.layoutShapeInsideInfo(); - bool segmentAllowsOverflow = !shapeInfo || !shapeInfo->hasSegments(); -#else - bool segmentAllowsOverflow = true; -#endif - if (segmentAllowsOverflow) { - if (m_lineBreak == m_resolver.position()) { - if (!m_lineBreak.renderer() || !m_lineBreak.renderer()->isBR()) { - // we just add as much as possible - if (m_blockStyle.whiteSpace() == PRE && !m_current.offset()) { - m_lineBreak.moveTo(m_lastObject, m_lastObject->isText() ? m_lastObject->length() : 0); - } else if (m_lineBreak.renderer()) { - // Don't ever break in the middle of a word if we can help it. - // There's no room at all. We just have to be on this line, - // even though we'll spill out. - m_lineBreak.moveTo(m_current.renderer(), m_current.offset()); - } - } - // make sure we consume at least one char/object. - if (m_lineBreak == m_resolver.position()) - m_lineBreak.increment(); - } else if (!m_current.offset() && !m_width.committedWidth() && m_width.uncommittedWidth() && !m_hadUncommittedWidthBeforeCurrent) { - // Do not push the current object to the next line, when this line has some content, but it is still considered empty. - // Empty inline elements like <span></span> can produce such lines and now we just ignore these break opportunities - // at the start of a line, if no width has been committed yet. - // Behave as if it was actually empty and consume at least one object. - m_lineBreak.increment(); - } - } - - // Sanity check our midpoints. - checkMidpoints(m_lineMidpointState, m_lineBreak); - - m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, m_lineBreak, TrailingObjects::CollapseFirstSpace); - - // We might have made lineBreak an iterator that points past the end - // of the object. Do this adjustment to make it point to the start - // of the next object instead to avoid confusing the rest of the - // code. - if (m_lineBreak.offset()) { - m_lineBreak.setOffset(m_lineBreak.offset() - 1); - m_lineBreak.increment(); - } - - return m_lineBreak; -} - -} - -#endif // BreakingContextInlineHeaders_h diff --git a/Source/WebCore/rendering/line/LineBreaker.cpp b/Source/WebCore/rendering/line/LineBreaker.cpp index d8296a669..af873bec5 100644 --- a/Source/WebCore/rendering/line/LineBreaker.cpp +++ b/Source/WebCore/rendering/line/LineBreaker.cpp @@ -25,9 +25,8 @@ #include "config.h" #include "LineBreaker.h" -#include "BreakingContextInlineHeaders.h" +#include "BreakingContext.h" #include "RenderCombineText.h" -#include "ShapeInsideInfo.h" namespace WebCore { @@ -49,9 +48,9 @@ void LineBreaker::skipTrailingWhitespace(InlineIterator& iterator, const LineInf while (!iterator.atEnd() && !requiresLineBox(iterator, lineInfo, TrailingWhitespace)) { RenderObject& object = *iterator.renderer(); if (object.isOutOfFlowPositioned()) - setStaticPositions(m_block, toRenderBox(object)); + setStaticPositions(m_block, downcast<RenderBox>(object), DoNotIndentText); else if (object.isFloating()) - m_block.insertFloatingObject(toRenderBox(object)); + m_block.insertFloatingObject(downcast<RenderBox>(object)); iterator.increment(); } } @@ -61,16 +60,16 @@ void LineBreaker::skipLeadingWhitespace(InlineBidiResolver& resolver, LineInfo& while (!resolver.position().atEnd() && !requiresLineBox(resolver.position(), lineInfo, LeadingWhitespace)) { RenderObject& object = *resolver.position().renderer(); if (object.isOutOfFlowPositioned()) { - setStaticPositions(m_block, toRenderBox(object)); + setStaticPositions(m_block, downcast<RenderBox>(object), width.shouldIndentText()); if (object.style().isOriginalDisplayInlineType()) { - resolver.runs().addRun(new BidiRun(0, 1, object, resolver.context(), resolver.dir())); + resolver.runs().appendRun(std::make_unique<BidiRun>(0, 1, object, resolver.context(), resolver.dir())); lineInfo.incrementRunsFromLeadingWhitespace(); } } else if (object.isFloating()) - m_block.positionNewFloatOnLine(m_block.insertFloatingObject(toRenderBox(object)), lastFloatFromPreviousLine, lineInfo, width); - else if (object.isText() && object.style().hasTextCombine() && object.isCombineText() && !toRenderCombineText(object).isCombined()) { - toRenderCombineText(object).combineText(); - if (toRenderCombineText(object).isCombined()) + m_block.positionNewFloatOnLine(*m_block.insertFloatingObject(downcast<RenderBox>(object)), lastFloatFromPreviousLine, lineInfo, width); + else if (object.style().hasTextCombine() && is<RenderCombineText>(object)) { + downcast<RenderCombineText>(object).combineText(); + if (downcast<RenderCombineText>(object).isCombined()) continue; } resolver.increment(); @@ -78,61 +77,7 @@ void LineBreaker::skipLeadingWhitespace(InlineBidiResolver& resolver, LineInfo& resolver.commitExplicitEmbedding(); } -InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo& lineInfo, RenderTextInfo& renderTextInfo, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements& wordMeasurements) -{ -#if !ENABLE(CSS_SHAPES) - return nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); -#else - ShapeInsideInfo* shapeInsideInfo = m_block.layoutShapeInsideInfo(); - - if (!shapeInsideInfo || !shapeInsideInfo->lineOverlapsShapeBounds()) - return nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); - - InlineIterator end = resolver.position(); - InlineIterator oldEnd = end; - - if (!shapeInsideInfo->hasSegments()) { - end = nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); - resolver.setPositionIgnoringNestedIsolates(oldEnd); - return oldEnd; - } - - const SegmentList& segments = shapeInsideInfo->segments(); - SegmentRangeList& segmentRanges = shapeInsideInfo->segmentRanges(); - - for (unsigned i = 0; i < segments.size() && !end.atEnd(); i++) { - InlineIterator segmentStart = resolver.position(); - end = nextSegmentBreak(resolver, lineInfo, renderTextInfo, lastFloatFromPreviousLine, consecutiveHyphenatedLines, wordMeasurements); - - ASSERT(segmentRanges.size() == i); - if (resolver.position().atEnd()) { - segmentRanges.append(LineSegmentRange(segmentStart, end)); - break; - } - if (resolver.position() == end) { - // Nothing fit this segment - end = segmentStart; - segmentRanges.append(LineSegmentRange(segmentStart, segmentStart)); - resolver.setPositionIgnoringNestedIsolates(segmentStart); - } else { - // Note that resolver.position is already skipping some of the white space at the beginning of the line, - // so that's why segmentStart might be different than resolver.position(). - LineSegmentRange range(resolver.position(), end); - segmentRanges.append(range); - resolver.setPosition(end, numberOfIsolateAncestors(end)); - - if (lineInfo.previousLineBrokeCleanly()) { - // If we hit a new line break, just stop adding anything to this line. - break; - } - } - } - resolver.setPositionIgnoringNestedIsolates(oldEnd); - return end; -#endif -} - -InlineIterator LineBreaker::nextSegmentBreak(InlineBidiResolver& resolver, LineInfo& lineInfo, RenderTextInfo& renderTextInfo, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements& wordMeasurements) +InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo& lineInfo, LineLayoutState& layoutState, RenderTextInfo& renderTextInfo, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements& wordMeasurements) { reset(); @@ -147,7 +92,7 @@ InlineIterator LineBreaker::nextSegmentBreak(InlineBidiResolver& resolver, LineI if (resolver.position().atEnd()) return resolver.position(); - BreakingContext context(*this, resolver, lineInfo, width, renderTextInfo, lastFloatFromPreviousLine, appliedStartWidth, m_block); + BreakingContext context(*this, resolver, lineInfo, layoutState, width, renderTextInfo, lastFloatFromPreviousLine, appliedStartWidth, m_block); while (context.currentObject()) { context.initializeForCurrentObject(); @@ -163,11 +108,11 @@ InlineIterator LineBreaker::nextSegmentBreak(InlineBidiResolver& resolver, LineI context.handleReplaced(); } else if (context.currentObject()->isText()) { if (context.handleText(wordMeasurements, m_hyphenated, consecutiveHyphenatedLines)) { - // We've hit a hard text line break. Our line break iterator is updated, so go ahead and early return. + // We've hit a hard text line break. Our line break iterator is updated, so early return. return context.lineBreak(); } } else if (context.currentObject()->isLineBreakOpportunity()) - context.commitLineBreakAtCurrentWidth(context.currentObject()); + context.commitLineBreakAtCurrentWidth(*context.currentObject()); else ASSERT_NOT_REACHED(); diff --git a/Source/WebCore/rendering/line/LineBreaker.h b/Source/WebCore/rendering/line/LineBreaker.h index 70c404960..0c9d9f8c7 100644 --- a/Source/WebCore/rendering/line/LineBreaker.h +++ b/Source/WebCore/rendering/line/LineBreaker.h @@ -22,13 +22,11 @@ * */ -#ifndef LineBreaker_h -#define LineBreaker_h +#pragma once #include "InlineIterator.h" #include "LineInfo.h" #include "LineInlineHeaders.h" -#include "TextBreakIterator.h" #include <wtf/Vector.h> namespace WebCore { @@ -36,26 +34,23 @@ namespace WebCore { class RenderText; struct RenderTextInfo { - // Destruction of m_layout requires TextLayout to be a complete type, so the constructor and destructor are made non-inline to avoid compilation errors. - RenderTextInfo(); - ~RenderTextInfo(); - - RenderText* m_text; - OwnPtr<TextLayout> m_layout; - LazyLineBreakIterator m_lineBreakIterator; - const Font* m_font; + RenderText* text { nullptr }; + std::unique_ptr<TextLayout, TextLayoutDeleter> layout; + LazyLineBreakIterator lineBreakIterator; + const FontCascade* font { nullptr }; }; class LineBreaker { public: friend class BreakingContext; - LineBreaker(RenderBlockFlow& block) + + explicit LineBreaker(RenderBlockFlow& block) : m_block(block) { reset(); } - InlineIterator nextLineBreak(InlineBidiResolver&, LineInfo&, RenderTextInfo&, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements&); + InlineIterator nextLineBreak(InlineBidiResolver&, LineInfo&, LineLayoutState&, RenderTextInfo&, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements&); bool lineWasHyphenated() { return m_hyphenated; } const Vector<RenderBox*>& positionedObjects() { return m_positionedObjects; } @@ -64,12 +59,11 @@ public: private: void reset(); - InlineIterator nextSegmentBreak(InlineBidiResolver&, LineInfo&, RenderTextInfo&, FloatingObject* lastFloatFromPreviousLine, unsigned consecutiveHyphenatedLines, WordMeasurements&); void skipTrailingWhitespace(InlineIterator&, const LineInfo&); void skipLeadingWhitespace(InlineBidiResolver&, LineInfo&, FloatingObject* lastFloatFromPreviousLine, LineWidth&); FloatingObject* insertFloatingObject(RenderBox& floatBox) { return m_block.insertFloatingObject(floatBox); } - bool positionNewFloatOnLine(FloatingObject* newFloat, FloatingObject* lastFloatFromPreviousLine, LineInfo& lineInfo, LineWidth& width) + bool positionNewFloatOnLine(const FloatingObject& newFloat, FloatingObject* lastFloatFromPreviousLine, LineInfo& lineInfo, LineWidth& width) { return m_block.positionNewFloatOnLine(newFloat, lastFloatFromPreviousLine, lineInfo, width); } @@ -80,6 +74,4 @@ private: Vector<RenderBox*> m_positionedObjects; }; -} - -#endif // LineBreaker_h +} // namespace WebCore diff --git a/Source/WebCore/rendering/line/LineInfo.h b/Source/WebCore/rendering/line/LineInfo.h index 66366c7b3..185981b6e 100644 --- a/Source/WebCore/rendering/line/LineInfo.h +++ b/Source/WebCore/rendering/line/LineInfo.h @@ -27,8 +27,7 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef LineInfo_h -#define LineInfo_h +#pragma once #include "LineWidth.h" @@ -71,6 +70,4 @@ private: unsigned m_runsFromLeadingWhitespace; }; -} - -#endif // LineInfo_h +} // namespace WebCore diff --git a/Source/WebCore/rendering/line/LineInlineHeaders.h b/Source/WebCore/rendering/line/LineInlineHeaders.h index 0b0c44e6e..db7ad2e83 100644 --- a/Source/WebCore/rendering/line/LineInlineHeaders.h +++ b/Source/WebCore/rendering/line/LineInlineHeaders.h @@ -22,8 +22,7 @@ * */ -#ifndef LineInlineHeaders_h -#define LineInlineHeaders_h +#pragma once #include "RenderLayer.h" @@ -43,7 +42,7 @@ inline bool hasInlineDirectionBordersPaddingOrMargin(const RenderInline& flow) return shouldApplyEndBorderPaddingOrMargin && (flow.borderEnd() || flow.marginEnd() || flow.paddingEnd()); } -inline const RenderStyle& lineStyle(const RenderElement& renderer, const LineInfo& lineInfo) +inline const RenderStyle& lineStyle(const RenderObject& renderer, const LineInfo& lineInfo) { return lineInfo.isFirstLine() ? renderer.firstLineStyle() : renderer.style(); } @@ -56,7 +55,7 @@ inline bool requiresLineBoxForContent(const RenderInline& flow, const LineInfo& const RenderStyle& parentStyle = lineStyle(*parent, lineInfo); if (flowStyle.lineHeight() != parentStyle.lineHeight() || flowStyle.verticalAlign() != parentStyle.verticalAlign() - || !parentStyle.font().fontMetrics().hasIdenticalAscentDescentAndLineGap(flowStyle.font().fontMetrics())) + || !parentStyle.fontCascade().fontMetrics().hasIdenticalAscentDescentAndLineGap(flowStyle.fontCascade().fontMetrics())) return true; } return false; @@ -105,8 +104,8 @@ inline bool requiresLineBox(const InlineIterator& it, const LineInfo& lineInfo = return true; bool rendererIsEmptyInline = false; - if (it.renderer()->isRenderInline()) { - const RenderInline& inlineRenderer = toRenderInline(*it.renderer()); + if (is<RenderInline>(*it.renderer())) { + const auto& inlineRenderer = downcast<RenderInline>(*it.renderer()); if (!alwaysRequiresLineBox(inlineRenderer) && !requiresLineBoxForContent(inlineRenderer, lineInfo)) return false; rendererIsEmptyInline = isEmptyInline(inlineRenderer); @@ -120,23 +119,21 @@ inline bool requiresLineBox(const InlineIterator& it, const LineInfo& lineInfo = return notJustWhitespace || rendererIsEmptyInline; } -inline void setStaticPositions(RenderBlockFlow& block, RenderBox& child) +inline void setStaticPositions(RenderBlockFlow& block, RenderBox& child, IndentTextOrNot shouldIndentText) { // FIXME: The math here is actually not really right. It's a best-guess approximation that // will work for the common cases RenderElement* containerBlock = child.container(); LayoutUnit blockHeight = block.logicalHeight(); - if (containerBlock->isRenderInline()) { + if (is<RenderInline>(*containerBlock)) { // A relative positioned inline encloses us. In this case, we also have to determine our // position as though we were an inline. Set |staticInlinePosition| and |staticBlockPosition| on the relative positioned // inline so that we can obtain the value later. - toRenderInline(containerBlock)->layer()->setStaticInlinePosition(block.startAlignedOffsetForLine(blockHeight, false)); - toRenderInline(containerBlock)->layer()->setStaticBlockPosition(blockHeight); + downcast<RenderInline>(*containerBlock).layer()->setStaticInlinePosition(block.startAlignedOffsetForLine(blockHeight, DoNotIndentText)); + downcast<RenderInline>(*containerBlock).layer()->setStaticBlockPosition(blockHeight); } - block.updateStaticInlinePositionForChild(child, blockHeight); + block.updateStaticInlinePositionForChild(child, blockHeight, shouldIndentText); child.layer()->setStaticBlockPosition(blockHeight); } -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/rendering/line/LineLayoutState.h b/Source/WebCore/rendering/line/LineLayoutState.h index 4cf7296aa..67882184b 100644 --- a/Source/WebCore/rendering/line/LineLayoutState.h +++ b/Source/WebCore/rendering/line/LineLayoutState.h @@ -31,40 +31,80 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef LineLayoutState_h -#define LineLayoutState_h +#pragma once #include "LayoutRect.h" -#include "RenderBox.h" +#include "RenderBlockFlow.h" +#include <wtf/RefCounted.h> namespace WebCore { -struct FloatWithRect { - FloatWithRect(RenderBox& f) - : object(f) - , rect(LayoutRect(f.x() - f.marginLeft(), f.y() - f.marginTop(), f.width() + f.marginWidth(), f.height() + f.marginHeight())) - , everHadLayout(f.everHadLayout()) +class FloatWithRect : public RefCounted<FloatWithRect> { + WTF_MAKE_FAST_ALLOCATED; +public: + static Ref<FloatWithRect> create(RenderBox& renderer) { + return adoptRef(*new FloatWithRect(renderer)); } + + RenderBox& renderer() const { return m_renderer; } + LayoutRect rect() const { return m_rect; } + bool everHadLayout() const { return m_everHadLayout; } + + void adjustRect(const LayoutRect& rect) { m_rect = rect; } - RenderBox& object; - LayoutRect rect; - bool everHadLayout; +private: + FloatWithRect() = default; + + FloatWithRect(RenderBox& renderer) + : m_renderer(renderer) + , m_rect(LayoutRect(renderer.x() - renderer.marginLeft(), renderer.y() - renderer.marginTop(), renderer.width() + renderer.horizontalMarginExtent(), renderer.height() + renderer.verticalMarginExtent())) + , m_everHadLayout(renderer.everHadLayout()) + { + } + + RenderBox& m_renderer; + LayoutRect m_rect; + bool m_everHadLayout { false }; }; // Like LayoutState for layout(), LineLayoutState keeps track of global information // during an entire linebox tree layout pass (aka layoutInlineChildren). class LineLayoutState { public: - LineLayoutState(bool fullLayout, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom, RenderFlowThread* flowThread) - : m_endLineLogicalTop(0) - , m_endLine(0) - , m_lastFloat(0) - , m_floatIndex(0) - , m_adjustedLogicalLineTop(0) - , m_flowThread(flowThread) + class FloatList { + public: + void append(Ref<FloatWithRect>&& floatWithRect) + { + m_floats.add(floatWithRect.copyRef()); + m_floatWithRectMap.add(&floatWithRect->renderer(), WTFMove(floatWithRect)); + } + void setLastFloat(FloatingObject* lastFloat) { m_lastFloat = lastFloat; } + FloatingObject* lastFloat() const { return m_lastFloat; } + + void setLastCleanFloat(RenderBox& floatBox) { m_lastCleanFloat = &floatBox; } + RenderBox* lastCleanFloat() const { return m_lastCleanFloat; } + + FloatWithRect* floatWithRect(RenderBox& floatBox) const { return m_floatWithRectMap.get(&floatBox); } + + using Iterator = ListHashSet<Ref<FloatWithRect>>::iterator; + Iterator begin() { return m_floats.begin(); } + Iterator end() { return m_floats.end(); } + Iterator find(FloatWithRect& floatBoxWithRect) { return m_floats.find(floatBoxWithRect); } + bool isEmpty() const { return m_floats.isEmpty(); } + + private: + ListHashSet<Ref<FloatWithRect>> m_floats; + HashMap<RenderBox*, Ref<FloatWithRect>> m_floatWithRectMap; + FloatingObject* m_lastFloat { nullptr }; + RenderBox* m_lastCleanFloat { nullptr }; + }; + + LineLayoutState(const RenderBlockFlow& blockFlow, bool fullLayout, LayoutUnit& repaintLogicalTop, LayoutUnit& repaintLogicalBottom, RenderFlowThread* flowThread) + : m_flowThread(flowThread) , m_repaintLogicalTop(repaintLogicalTop) , m_repaintLogicalBottom(repaintLogicalBottom) + , m_marginInfo(blockFlow, blockFlow.borderAndPaddingBefore(), blockFlow.borderAndPaddingAfter() + blockFlow.scrollbarLogicalHeight()) , m_endLineMatched(false) , m_checkForFloatsFromLastLine(false) , m_isFullLayout(fullLayout) @@ -81,14 +121,6 @@ public: RootInlineBox* endLine() const { return m_endLine; } void setEndLine(RootInlineBox* line) { m_endLine = line; } - FloatingObject* lastFloat() const { return m_lastFloat; } - void setLastFloat(FloatingObject* lastFloat) { m_lastFloat = lastFloat; } - - Vector<FloatWithRect>& floats() { return m_floats; } - - unsigned floatIndex() const { return m_floatIndex; } - void setFloatIndex(unsigned floatIndex) { m_floatIndex = floatIndex; } - LayoutUnit adjustedLogicalLineTop() const { return m_adjustedLogicalLineTop; } void setAdjustedLogicalLineTop(LayoutUnit value) { m_adjustedLogicalLineTop = value; } @@ -119,29 +151,34 @@ public: m_repaintLogicalBottom = std::max(m_repaintLogicalBottom, box->logicalBottomVisualOverflow() + std::max<LayoutUnit>(paginationDelta, 0)); } + RenderBlockFlow::MarginInfo& marginInfo() { return m_marginInfo; } + LayoutUnit& prevFloatBottomFromAnonymousInlineBlock() { return m_prevFloatBottomFromAnonymousInlineBlock; } + LayoutUnit& maxFloatBottomFromAnonymousInlineBlock() { return m_maxFloatBottomFromAnonymousInlineBlock; } + + FloatList& floatList() { return m_floatList; } + private: LineInfo m_lineInfo; LayoutUnit m_endLineLogicalTop; - RootInlineBox* m_endLine; - - FloatingObject* m_lastFloat; - Vector<FloatWithRect> m_floats; - unsigned m_floatIndex; + RootInlineBox* m_endLine { nullptr }; LayoutUnit m_adjustedLogicalLineTop; - RenderFlowThread* m_flowThread; + RenderFlowThread* m_flowThread { nullptr }; + FloatList m_floatList; // FIXME: Should this be a range object instead of two ints? LayoutUnit& m_repaintLogicalTop; LayoutUnit& m_repaintLogicalBottom; + RenderBlockFlow::MarginInfo m_marginInfo; + LayoutUnit m_prevFloatBottomFromAnonymousInlineBlock; + LayoutUnit m_maxFloatBottomFromAnonymousInlineBlock; + bool m_endLineMatched : 1; bool m_checkForFloatsFromLastLine : 1; bool m_isFullLayout : 1; bool m_usesRepaintBounds : 1; }; -} - -#endif // LineLayoutState_h +} // namespace WebCore diff --git a/Source/WebCore/rendering/line/LineWidth.cpp b/Source/WebCore/rendering/line/LineWidth.cpp index 7ffa20715..7ce9edb0e 100644 --- a/Source/WebCore/rendering/line/LineWidth.cpp +++ b/Source/WebCore/rendering/line/LineWidth.cpp @@ -33,31 +33,13 @@ #include "RenderBlockFlow.h" #include "RenderRubyRun.h" -#if ENABLE(CSS_SHAPES) -#include "ShapeInsideInfo.h" -#endif - namespace WebCore { LineWidth::LineWidth(RenderBlockFlow& block, bool isFirstLine, IndentTextOrNot shouldIndentText) : m_block(block) - , m_uncommittedWidth(0) - , m_committedWidth(0) - , m_overhangWidth(0) - , m_trailingWhitespaceWidth(0) - , m_trailingCollapsedWhitespaceWidth(0) - , m_left(0) - , m_right(0) - , m_availableWidth(0) -#if ENABLE(CSS_SHAPES) - , m_segment(0) -#endif , m_isFirstLine(isFirstLine) , m_shouldIndentText(shouldIndentText) { -#if ENABLE(CSS_SHAPES) - updateCurrentShapeSegment(); -#endif updateAvailableWidth(); } @@ -83,55 +65,53 @@ void LineWidth::updateAvailableWidth(LayoutUnit replacedHeight) m_left = m_block.logicalLeftOffsetForLine(height, shouldIndentText(), logicalHeight); m_right = m_block.logicalRightOffsetForLine(height, shouldIndentText(), logicalHeight); -#if ENABLE(CSS_SHAPES) - if (m_segment) { - m_left = std::max<float>(m_segment->logicalLeft, m_left); - m_right = std::min<float>(m_segment->logicalRight, m_right); - } -#endif - computeAvailableWidthFromLeftAndRight(); } -void LineWidth::shrinkAvailableWidthForNewFloatIfNeeded(FloatingObject* newFloat) +static bool newFloatShrinksLine(const FloatingObject& newFloat, const RenderBlockFlow& block, bool isFirstLine) { - LayoutUnit height = m_block.logicalHeight(); - if (height < m_block.logicalTopForFloat(newFloat) || height >= m_block.logicalBottomForFloat(newFloat)) - return; + LayoutUnit blockOffset = block.logicalHeight(); + if (blockOffset >= block.logicalTopForFloat(newFloat) && blockOffset < block.logicalBottomForFloat(newFloat)) + return true; + + // initial-letter float always shrinks the first line. + const auto& style = newFloat.renderer().style(); + if (isFirstLine && style.styleType() == FIRST_LETTER && !style.initialLetter().isEmpty()) + return true; + return false; +} -#if ENABLE(CSS_SHAPES) - ShapeOutsideInfo* shapeOutsideInfo = newFloat->renderer().shapeOutsideInfo(); - if (shapeOutsideInfo) { +void LineWidth::shrinkAvailableWidthForNewFloatIfNeeded(const FloatingObject& newFloat) +{ + if (!newFloatShrinksLine(newFloat, m_block, m_isFirstLine)) + return; + ShapeOutsideDeltas shapeDeltas; + if (ShapeOutsideInfo* shapeOutsideInfo = newFloat.renderer().shapeOutsideInfo()) { LayoutUnit lineHeight = m_block.lineHeight(m_isFirstLine, m_block.isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); - shapeOutsideInfo->updateDeltasForContainingBlockLine(m_block, *newFloat, m_block.logicalHeight(), lineHeight); + shapeDeltas = shapeOutsideInfo->computeDeltasForContainingBlockLine(m_block, newFloat, m_block.logicalHeight(), lineHeight); } -#endif - if (newFloat->type() == FloatingObject::FloatLeft) { + if (newFloat.type() == FloatingObject::FloatLeft) { float newLeft = m_block.logicalRightForFloat(newFloat); - if (shouldIndentText() && m_block.style().isLeftToRightDirection()) + if (shouldIndentText() == IndentText && m_block.style().isLeftToRightDirection()) newLeft += floorToInt(m_block.textIndentOffset()); -#if ENABLE(CSS_SHAPES) - if (shapeOutsideInfo) { - if (shapeOutsideInfo->lineOverlapsShape()) - newLeft += shapeOutsideInfo->rightMarginBoxDelta(); + if (shapeDeltas.isValid()) { + if (shapeDeltas.lineOverlapsShape()) + newLeft += shapeDeltas.rightMarginBoxDelta(); else // If the line doesn't overlap the shape, then we need to act as if this float didn't exist. newLeft = m_left; } -#endif m_left = std::max<float>(m_left, newLeft); } else { float newRight = m_block.logicalLeftForFloat(newFloat); - if (shouldIndentText() && !m_block.style().isLeftToRightDirection()) + if (shouldIndentText() == IndentText && !m_block.style().isLeftToRightDirection()) newRight -= floorToInt(m_block.textIndentOffset()); -#if ENABLE(CSS_SHAPES) - if (shapeOutsideInfo) { - if (shapeOutsideInfo->lineOverlapsShape()) - newRight += shapeOutsideInfo->leftMarginBoxDelta(); + if (shapeDeltas.isValid()) { + if (shapeDeltas.lineOverlapsShape()) + newRight += shapeDeltas.leftMarginBoxDelta(); else // If the line doesn't overlap the shape, then we need to act as if this float didn't exist. newRight = m_right; } -#endif m_right = std::min<float>(m_right, newRight); } @@ -142,23 +122,70 @@ void LineWidth::commit() { m_committedWidth += m_uncommittedWidth; m_uncommittedWidth = 0; + if (m_hasUncommittedReplaced) { + m_hasCommittedReplaced = true; + m_hasUncommittedReplaced = false; + } + m_hasCommitted = true; } void LineWidth::applyOverhang(RenderRubyRun* rubyRun, RenderObject* startRenderer, RenderObject* endRenderer) { - int startOverhang; - int endOverhang; + float startOverhang; + float endOverhang; rubyRun->getOverhang(m_isFirstLine, startRenderer, endRenderer, startOverhang, endOverhang); - startOverhang = std::min<int>(startOverhang, m_committedWidth); + startOverhang = std::min(startOverhang, m_committedWidth); m_availableWidth += startOverhang; - endOverhang = std::max(std::min<int>(endOverhang, m_availableWidth - currentWidth()), 0); + endOverhang = std::max(std::min(endOverhang, m_availableWidth - currentWidth()), 0.0f); m_availableWidth += endOverhang; m_overhangWidth += startOverhang + endOverhang; } -void LineWidth::fitBelowFloats() +inline static float availableWidthAtOffset(const RenderBlockFlow& block, const LayoutUnit& offset, IndentTextOrNot shouldIndentText, + float& newLineLeft, float& newLineRight, const LayoutUnit& lineHeight = 0) +{ + newLineLeft = block.logicalLeftOffsetForLine(offset, shouldIndentText, lineHeight); + newLineRight = block.logicalRightOffsetForLine(offset, shouldIndentText, lineHeight); + return std::max(0.0f, newLineRight - newLineLeft); +} + +void LineWidth::updateLineDimension(LayoutUnit newLineTop, LayoutUnit newLineWidth, float newLineLeft, float newLineRight) +{ + if (newLineWidth <= m_availableWidth) + return; + + m_block.setLogicalHeight(newLineTop); + m_availableWidth = newLineWidth + m_overhangWidth; + m_left = newLineLeft; + m_right = newLineRight; +} + +void LineWidth::wrapNextToShapeOutside(bool isFirstLine) +{ + LayoutUnit lineHeight = m_block.lineHeight(isFirstLine, m_block.isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); + LayoutUnit lineLogicalTop = m_block.logicalHeight(); + LayoutUnit newLineTop = lineLogicalTop; + LayoutUnit floatLogicalBottom = m_block.nextFloatLogicalBottomBelow(lineLogicalTop); + + float newLineWidth; + float newLineLeft = m_left; + float newLineRight = m_right; + while (true) { + newLineWidth = availableWidthAtOffset(m_block, newLineTop, shouldIndentText(), newLineLeft, newLineRight, lineHeight); + if (newLineWidth >= m_uncommittedWidth) + break; + + if (newLineTop >= floatLogicalBottom) + break; + + ++newLineTop; + } + updateLineDimension(newLineTop, newLineWidth, newLineLeft, newLineRight); +} + +void LineWidth::fitBelowFloats(bool isFirstLine) { ASSERT(!m_committedWidth); ASSERT(!fitsOnLine()); @@ -168,37 +195,24 @@ void LineWidth::fitBelowFloats() float newLineWidth = m_availableWidth; float newLineLeft = m_left; float newLineRight = m_right; + + FloatingObject* lastFloatFromPreviousLine = (m_block.containsFloats() ? m_block.m_floatingObjects->set().last().get() : 0); + if (lastFloatFromPreviousLine && lastFloatFromPreviousLine->renderer().shapeOutsideInfo()) + return wrapNextToShapeOutside(isFirstLine); + while (true) { floatLogicalBottom = m_block.nextFloatLogicalBottomBelow(lastFloatLogicalBottom); if (floatLogicalBottom <= lastFloatLogicalBottom) break; - newLineLeft = m_block.logicalLeftOffsetForLine(floatLogicalBottom, shouldIndentText()); - newLineRight = m_block.logicalRightOffsetForLine(floatLogicalBottom, shouldIndentText()); - newLineWidth = std::max(0.0f, newLineRight - newLineLeft); + newLineWidth = availableWidthAtOffset(m_block, floatLogicalBottom, shouldIndentText(), newLineLeft, newLineRight); lastFloatLogicalBottom = floatLogicalBottom; -#if ENABLE(CSS_SHAPES) - // FIXME: This code should be refactored to incorporate with the code above. - ShapeInsideInfo* shapeInsideInfo = m_block.layoutShapeInsideInfo(); - if (shapeInsideInfo) { - LayoutUnit logicalOffsetFromShapeContainer = m_block.logicalOffsetFromShapeAncestorContainer(&shapeInsideInfo->owner()).height(); - LayoutUnit lineHeight = m_block.lineHeight(false, m_block.isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes); - shapeInsideInfo->updateSegmentsForLine(lastFloatLogicalBottom + logicalOffsetFromShapeContainer, lineHeight); - updateCurrentShapeSegment(); - updateAvailableWidth(); - } -#endif if (newLineWidth >= m_uncommittedWidth) break; } - if (newLineWidth > m_availableWidth) { - m_block.setLogicalHeight(lastFloatLogicalBottom); - m_availableWidth = newLineWidth + m_overhangWidth; - m_left = newLineLeft; - m_right = newLineRight; - } + updateLineDimension(lastFloatLogicalBottom, newLineWidth, newLineLeft, newLineRight); } void LineWidth::setTrailingWhitespaceWidth(float collapsedWhitespace, float borderPaddingMargin) @@ -207,14 +221,6 @@ void LineWidth::setTrailingWhitespaceWidth(float collapsedWhitespace, float bord m_trailingWhitespaceWidth = collapsedWhitespace + borderPaddingMargin; } -#if ENABLE(CSS_SHAPES) -void LineWidth::updateCurrentShapeSegment() -{ - if (ShapeInsideInfo* shapeInsideInfo = m_block.layoutShapeInsideInfo()) - m_segment = shapeInsideInfo->currentSegment(); -} -#endif - void LineWidth::computeAvailableWidthFromLeftAndRight() { m_availableWidth = std::max<float>(0, m_right - m_left) + m_overhangWidth; diff --git a/Source/WebCore/rendering/line/LineWidth.h b/Source/WebCore/rendering/line/LineWidth.h index 11d7ae499..93e4d355d 100644 --- a/Source/WebCore/rendering/line/LineWidth.h +++ b/Source/WebCore/rendering/line/LineWidth.h @@ -27,8 +27,7 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef LineWidth_h -#define LineWidth_h +#pragma once #include "LayoutUnit.h" @@ -58,43 +57,51 @@ public: float committedWidth() const { return m_committedWidth; } float availableWidth() const { return m_availableWidth; } float logicalLeftOffset() const { return m_left; } + + bool hasCommitted() const { return m_hasCommitted; } + bool hasCommittedReplaced() const { return m_hasCommittedReplaced; } void updateAvailableWidth(LayoutUnit minimumHeight = 0); - void shrinkAvailableWidthForNewFloatIfNeeded(FloatingObject*); - void addUncommittedWidth(float delta) { m_uncommittedWidth += delta; } + void shrinkAvailableWidthForNewFloatIfNeeded(const FloatingObject&); + void addUncommittedWidth(float delta) + { + m_uncommittedWidth += delta; + } + void addUncommittedReplacedWidth(float delta) + { + addUncommittedWidth(delta); + m_hasUncommittedReplaced = true; + } void commit(); void applyOverhang(RenderRubyRun*, RenderObject* startRenderer, RenderObject* endRenderer); - void fitBelowFloats(); + void fitBelowFloats(bool isFirstLine = false); void setTrailingWhitespaceWidth(float collapsedWhitespace, float borderPaddingMargin = 0); - -#if ENABLE(CSS_SHAPES) - void updateCurrentShapeSegment(); -#endif - - bool shouldIndentText() const { return m_shouldIndentText == IndentText; } + IndentTextOrNot shouldIndentText() const { return m_shouldIndentText; } + + bool isFirstLine() const { return m_isFirstLine; } private: void computeAvailableWidthFromLeftAndRight(); bool fitsOnLineExcludingTrailingCollapsedWhitespace() const; + void updateLineDimension(LayoutUnit newLineTop, LayoutUnit newLineWidth, float newLineLeft, float newLineRight); + void wrapNextToShapeOutside(bool isFirstLine); RenderBlockFlow& m_block; - float m_uncommittedWidth; - float m_committedWidth; - float m_overhangWidth; // The amount by which |m_availableWidth| has been inflated to account for possible contraction due to ruby overhang. - float m_trailingWhitespaceWidth; - float m_trailingCollapsedWhitespaceWidth; - float m_left; - float m_right; - float m_availableWidth; -#if ENABLE(CSS_SHAPES) - const LineSegment* m_segment; -#endif - bool m_isFirstLine; + float m_uncommittedWidth { 0 }; + float m_committedWidth { 0 }; + float m_overhangWidth { 0 }; // The amount by which |m_availableWidth| has been inflated to account for possible contraction due to ruby overhang. + float m_trailingWhitespaceWidth { 0 }; + float m_trailingCollapsedWhitespaceWidth { 0 }; + float m_left { 0 }; + float m_right { 0 }; + float m_availableWidth { 0 }; + bool m_isFirstLine { true }; + bool m_hasCommitted { false }; + bool m_hasCommittedReplaced { false }; + bool m_hasUncommittedReplaced { false }; IndentTextOrNot m_shouldIndentText; }; IndentTextOrNot requiresIndent(bool isFirstLine, bool isAfterHardLineBreak, const RenderStyle&); -} - -#endif // LineWidth_h +} // namespace WebCore diff --git a/Source/WebCore/rendering/line/TrailingObjects.cpp b/Source/WebCore/rendering/line/TrailingObjects.cpp index 96490cd5b..11ce2b6f6 100644 --- a/Source/WebCore/rendering/line/TrailingObjects.cpp +++ b/Source/WebCore/rendering/line/TrailingObjects.cpp @@ -29,44 +29,44 @@ namespace WebCore { -void TrailingObjects::updateMidpointsForTrailingBoxes(LineMidpointState& lineMidpointState, const InlineIterator& lBreak, CollapseFirstSpaceOrNot collapseFirstSpace) +void TrailingObjects::updateWhitespaceCollapsingTransitionsForTrailingBoxes(LineWhitespaceCollapsingState& lineWhitespaceCollapsingState, const InlineIterator& lBreak, CollapseFirstSpaceOrNot collapseFirstSpace) { if (!m_whitespace) return; - // This object is either going to be part of the last midpoint, or it is going to be the actual endpoint. + // This object is either going to be part of the last transition, or it is going to be the actual endpoint. // In both cases we just decrease our pos by 1 level to exclude the space, allowing it to - in effect - collapse into the newline. - if (lineMidpointState.numMidpoints() % 2) { - // Find the trailing space object's midpoint. - int trailingSpaceMidpoint = lineMidpointState.numMidpoints() - 1; - for ( ; trailingSpaceMidpoint > 0 && lineMidpointState.midpoints()[trailingSpaceMidpoint].renderer() != m_whitespace; --trailingSpaceMidpoint) { } - ASSERT(trailingSpaceMidpoint >= 0); + if (lineWhitespaceCollapsingState.numTransitions() % 2) { + // Find the trailing space object's transition. + int trailingSpaceTransition = lineWhitespaceCollapsingState.numTransitions() - 1; + for ( ; trailingSpaceTransition > 0 && lineWhitespaceCollapsingState.transitions()[trailingSpaceTransition].renderer() != m_whitespace; --trailingSpaceTransition) { } + ASSERT(trailingSpaceTransition >= 0); if (collapseFirstSpace == CollapseFirstSpace) - lineMidpointState.midpoints()[trailingSpaceMidpoint].setOffset(lineMidpointState.midpoints()[trailingSpaceMidpoint].offset() -1); + lineWhitespaceCollapsingState.decrementTransitionAt(trailingSpaceTransition); - // Now make sure every single trailingPositionedBox following the trailingSpaceMidpoint properly stops and starts + // Now make sure every single trailingPositionedBox following the trailingSpaceTransition properly stops and starts // ignoring spaces. - size_t currentMidpoint = trailingSpaceMidpoint + 1; + size_t currentTransition = trailingSpaceTransition + 1; for (size_t i = 0; i < m_boxes.size(); ++i) { - if (currentMidpoint >= lineMidpointState.numMidpoints()) { - // We don't have a midpoint for this box yet. - lineMidpointState.ensureLineBoxInsideIgnoredSpaces(m_boxes[i]); + if (currentTransition >= lineWhitespaceCollapsingState.numTransitions()) { + // We don't have a transition for this box yet. + lineWhitespaceCollapsingState.ensureLineBoxInsideIgnoredSpaces(m_boxes[i]); } else { - ASSERT(lineMidpointState.midpoints()[currentMidpoint].renderer() == m_boxes[i]); - ASSERT(lineMidpointState.midpoints()[currentMidpoint + 1].renderer() == m_boxes[i]); + ASSERT(lineWhitespaceCollapsingState.transitions()[currentTransition].renderer() == &(m_boxes[i].get())); + ASSERT(lineWhitespaceCollapsingState.transitions()[currentTransition + 1].renderer() == &(m_boxes[i].get())); } - currentMidpoint += 2; + currentTransition += 2; } } else if (!lBreak.renderer()) { ASSERT(m_whitespace->isText()); ASSERT(collapseFirstSpace == CollapseFirstSpace); - // Add a new end midpoint that stops right at the very end. + // Add a new end transition that stops right at the very end. unsigned length = m_whitespace->textLength(); unsigned pos = length >= 2 ? length - 2 : UINT_MAX; InlineIterator endMid(0, m_whitespace, pos); - lineMidpointState.startIgnoringSpaces(endMid); + lineWhitespaceCollapsingState.startIgnoringSpaces(endMid); for (size_t i = 0; i < m_boxes.size(); ++i) - lineMidpointState.ensureLineBoxInsideIgnoredSpaces(m_boxes[i]); + lineWhitespaceCollapsingState.ensureLineBoxInsideIgnoredSpaces(m_boxes[i]); } } diff --git a/Source/WebCore/rendering/line/TrailingObjects.h b/Source/WebCore/rendering/line/TrailingObjects.h index 5aedea60e..dbd2b0d2b 100644 --- a/Source/WebCore/rendering/line/TrailingObjects.h +++ b/Source/WebCore/rendering/line/TrailingObjects.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2000 Lars Knoll (knoll@kde.org) - * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All right reserved. + * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2016 Apple Inc. All right reserved. * Copyright (C) 2010 Google Inc. All rights reserved. * Copyright (C) 2013 ChangSeok Oh <shivamidow@gmail.com> * Copyright (C) 2013 Adobe Systems Inc. All right reserved. @@ -22,8 +22,7 @@ * */ -#ifndef TrailingObjects_h -#define TrailingObjects_h +#pragma once #include <wtf/Vector.h> @@ -34,11 +33,13 @@ class RenderBoxModelObject; class RenderText; struct BidiRun; +struct BidiIsolatedRun; template <class Iterator, class Run> class BidiResolver; -template <class Iterator> class MidpointState; -typedef BidiResolver<InlineIterator, BidiRun> InlineBidiResolver; -typedef MidpointState<InlineIterator> LineMidpointState; +template <class Iterator, class Run, class IsolateRun> class BidiResolverWithIsolate; +template <class Iterator> class WhitespaceCollapsingState; +typedef BidiResolverWithIsolate<InlineIterator, BidiRun, BidiIsolatedRun> InlineBidiResolver; +typedef WhitespaceCollapsingState<InlineIterator> LineWhitespaceCollapsingState; class TrailingObjects { public: @@ -58,7 +59,7 @@ public: m_boxes.shrink(0); // Use shrink(0) instead of clear() to retain our capacity. } - void appendBoxIfNeeded(RenderBoxModelObject* box) + void appendBoxIfNeeded(RenderBoxModelObject& box) { if (m_whitespace) m_boxes.append(box); @@ -66,13 +67,11 @@ public: enum CollapseFirstSpaceOrNot { DoNotCollapseFirstSpace, CollapseFirstSpace }; - void updateMidpointsForTrailingBoxes(LineMidpointState&, const InlineIterator& lBreak, CollapseFirstSpaceOrNot); + void updateWhitespaceCollapsingTransitionsForTrailingBoxes(LineWhitespaceCollapsingState&, const InlineIterator& lBreak, CollapseFirstSpaceOrNot); private: RenderText* m_whitespace; - Vector<RenderBoxModelObject*, 4> m_boxes; + Vector<std::reference_wrapper<RenderBoxModelObject>, 4> m_boxes; }; -} - -#endif // TrailingObjects_h +} // namespace WebCore |