| /* |
| * 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 Adobe Systems Incorporated. |
| * |
| * 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 "core/layout/TextRunConstructor.h" |
| #include "core/layout/api/LineLayoutBox.h" |
| #include "core/layout/api/LineLayoutListMarker.h" |
| #include "core/layout/api/LineLayoutRubyRun.h" |
| #include "core/layout/api/LineLayoutSVGInlineText.h" |
| #include "core/layout/api/LineLayoutText.h" |
| #include "core/layout/api/LineLayoutTextCombine.h" |
| #include "core/layout/line/InlineIterator.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "core/layout/line/LayoutTextInfo.h" |
| #include "core/layout/line/LineBreaker.h" |
| #include "core/layout/line/LineInfo.h" |
| #include "core/layout/line/LineWidth.h" |
| #include "core/layout/line/TrailingObjects.h" |
| #include "core/layout/line/WordMeasurement.h" |
| #include "core/paint/PaintLayer.h" |
| #include "platform/fonts/CharacterRange.h" |
| #include "platform/text/Hyphenation.h" |
| #include "platform/text/TextBreakIterator.h" |
| #include "wtf/Allocator.h" |
| #include "wtf/Vector.h" |
| |
| namespace blink { |
| |
| // We don't let our line box tree for a single line get any deeper than this. |
| const unsigned cMaxLineDepth = 200; |
| |
| class BreakingContext { |
| STACK_ALLOCATED(); |
| |
| public: |
| BreakingContext(InlineBidiResolver& resolver, |
| LineInfo& inLineInfo, |
| LineWidth& lineWidth, |
| LayoutTextInfo& inLayoutTextInfo, |
| bool appliedStartWidth, |
| LineLayoutBlockFlow block) |
| : m_resolver(resolver), |
| m_current(resolver.position()), |
| m_lineBreak(resolver.position()), |
| m_block(block), |
| m_lastObject(m_current.getLineLayoutItem()), |
| m_nextObject(nullptr), |
| m_currentStyle(nullptr), |
| m_blockStyle(block.style()), |
| m_lineInfo(inLineInfo), |
| m_layoutTextInfo(inLayoutTextInfo), |
| m_width(lineWidth), |
| m_currWS(EWhiteSpace::Normal), |
| m_lastWS(EWhiteSpace::Normal), |
| m_preservesNewline(false), |
| m_atStart(true), |
| m_ignoringSpaces(false), |
| m_currentCharacterIsSpace(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_lineMidpointState(resolver.midpointState()) { |
| m_lineInfo.setPreviousLineBrokeCleanly(false); |
| } |
| |
| LineLayoutItem currentItem() { return m_current.getLineLayoutItem(); } |
| InlineIterator lineBreak() { return m_lineBreak; } |
| bool atEnd() { return m_atEnd; } |
| |
| void initializeForCurrentObject(); |
| |
| void increment(); |
| |
| void handleBR(EClear&); |
| void handleOutOfFlowPositioned(Vector<LineLayoutBox>& positionedObjects); |
| void handleFloat(); |
| void handleEmptyInline(); |
| void handleReplaced(); |
| bool handleText(WordMeasurements&, bool& hyphenated); |
| void prepareForNextCharacter(const LineLayoutText&, |
| bool& prohibitBreakInside, |
| bool previousCharacterIsSpace); |
| bool canBreakAtWhitespace(bool breakWords, |
| WordMeasurement&, |
| bool stoppedIgnoringSpaces, |
| float charWidth, |
| bool& hyphenated, |
| bool disableSoftHyphen, |
| float& hyphenWidth, |
| bool betweenWords, |
| bool midWordBreak, |
| bool canBreakMidWord, |
| bool previousCharacterIsSpace, |
| float lastWidthMeasurement, |
| const LineLayoutText&, |
| const Font&, |
| bool applyWordSpacing, |
| float wordSpacing); |
| bool trailingSpaceExceedsAvailableWidth(bool canBreakMidWord, |
| const LineLayoutText&, |
| WordMeasurement&, |
| bool applyWordSpacing, |
| bool wordSpacing, |
| const Font&); |
| WordMeasurement& calculateWordWidth(WordMeasurements&, |
| LineLayoutText&, |
| unsigned lastSpace, |
| float& lastWidthMeasurement, |
| float wordSpacingForWordMeasurement, |
| const Font&, |
| float wordTrailingSpaceWidth, |
| UChar); |
| void stopIgnoringSpaces(unsigned& lastSpace); |
| void commitAndUpdateLineBreakIfNeeded(); |
| InlineIterator handleEndOfLine(); |
| |
| void clearLineBreakIfFitsOnLine() { |
| if (m_width.fitsOnLine() || m_lastWS == EWhiteSpace::Nowrap) |
| m_lineBreak.clear(); |
| } |
| |
| private: |
| void setCurrentCharacterIsSpace(UChar); |
| void skipTrailingWhitespace(InlineIterator&, const LineInfo&); |
| bool shouldMidWordBreak(UChar, |
| LineLayoutText, |
| const Font&, |
| float& charWidth, |
| float& widthFromLastBreakingOpportunity, |
| bool breakAll, |
| int& nextBreakablePositionForBreakAll); |
| bool rewindToMidWordBreak(WordMeasurement&, int end, float width); |
| bool rewindToFirstMidWordBreak(LineLayoutText, |
| const ComputedStyle&, |
| const Font&, |
| bool breakAll, |
| WordMeasurement&); |
| bool rewindToMidWordBreak(LineLayoutText, |
| const ComputedStyle&, |
| const Font&, |
| bool breakAll, |
| WordMeasurement&); |
| bool hyphenate(LineLayoutText, |
| const ComputedStyle&, |
| const Font&, |
| const Hyphenation&, |
| float lastSpaceWordSpacing, |
| WordMeasurement&); |
| bool isBreakAtSoftHyphen() const; |
| |
| InlineBidiResolver& m_resolver; |
| |
| InlineIterator m_current; |
| InlineIterator m_lineBreak; |
| InlineIterator m_startOfIgnoredSpaces; |
| |
| LineLayoutBlockFlow m_block; |
| LineLayoutItem m_lastObject; |
| LineLayoutItem m_nextObject; |
| |
| const ComputedStyle* m_currentStyle; |
| const ComputedStyle* m_blockStyle; |
| |
| LineInfo& m_lineInfo; |
| |
| LayoutTextInfo& m_layoutTextInfo; |
| |
| LineWidth m_width; |
| |
| EWhiteSpace m_currWS; |
| EWhiteSpace m_lastWS; |
| |
| bool m_preservesNewline; |
| bool m_atStart; |
| bool m_ignoringSpaces; |
| bool m_currentCharacterIsSpace; |
| 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; |
| |
| LineMidpointState& m_lineMidpointState; |
| |
| TrailingObjects m_trailingObjects; |
| }; |
| |
| // When ignoring spaces, this needs to be called for objects that need line |
| // boxes such as LayoutInlines or hard line breaks to ensure that they're not |
| // ignored. |
| inline void ensureLineBoxInsideIgnoredSpaces(LineMidpointState* midpointState, |
| LineLayoutItem item) { |
| InlineIterator midpoint(0, item, 0); |
| midpointState->stopIgnoringSpaces(midpoint); |
| midpointState->startIgnoringSpaces(midpoint); |
| } |
| |
| inline bool shouldCollapseWhiteSpace(const ComputedStyle& style, |
| const LineInfo& lineInfo, |
| WhitespacePosition whitespacePosition) { |
| // CSS2 16.6.1 |
| // If a space (U+0020) at the beginning of a line has 'white-space' set to |
| // 'normal', 'nowrap', or 'pre-line', it is removed. |
| // If a space (U+0020) at the end of a line has 'white-space' set to 'normal', |
| // 'nowrap', or 'pre-line', it is also removed. |
| // If spaces (U+0020) or tabs (U+0009) at the end of a line have 'white-space' |
| // set to 'pre-wrap', UAs may visually collapse them. |
| return style.collapseWhiteSpace() || |
| (whitespacePosition == TrailingWhitespace && |
| style.whiteSpace() == EWhiteSpace::PreWrap && |
| (!lineInfo.isEmpty() || !lineInfo.previousLineBrokeCleanly())); |
| } |
| |
| inline bool requiresLineBoxForContent(LineLayoutInline flow, |
| const LineInfo& lineInfo) { |
| LineLayoutItem parent = flow.parent(); |
| if (flow.document().inNoQuirksMode() && |
| (flow.style(lineInfo.isFirstLine())->lineHeight() != |
| parent.style(lineInfo.isFirstLine())->lineHeight() || |
| flow.style()->verticalAlign() != parent.style()->verticalAlign() || |
| !parent.style()->hasIdenticalAscentDescentAndLineGap(flow.styleRef()))) |
| return true; |
| return false; |
| } |
| |
| inline bool alwaysRequiresLineBox(LineLayoutItem flow) { |
| // FIXME: Right now, we only allow line boxes for inlines that are truly |
| // empty. We need to fix this, though, because at the very least, inlines |
| // containing only ignorable whitespace should should also have line boxes. |
| return isEmptyInline(flow) && |
| LineLayoutInline(flow).hasInlineDirectionBordersPaddingOrMargin(); |
| } |
| |
| inline bool requiresLineBox( |
| const InlineIterator& it, |
| const LineInfo& lineInfo = LineInfo(), |
| WhitespacePosition whitespacePosition = LeadingWhitespace) { |
| if (it.getLineLayoutItem().isEmptyText()) |
| return false; |
| |
| if (it.getLineLayoutItem().isFloatingOrOutOfFlowPositioned()) |
| return false; |
| |
| if (it.getLineLayoutItem().isLayoutInline() && |
| !alwaysRequiresLineBox(it.getLineLayoutItem()) && |
| !requiresLineBoxForContent(LineLayoutInline(it.getLineLayoutItem()), |
| lineInfo)) |
| return false; |
| |
| if (!shouldCollapseWhiteSpace(it.getLineLayoutItem().styleRef(), lineInfo, |
| whitespacePosition) || |
| it.getLineLayoutItem().isBR()) |
| return true; |
| |
| UChar current = it.current(); |
| bool notJustWhitespace = current != spaceCharacter && |
| current != tabulationCharacter && |
| current != softHyphenCharacter && |
| (current != newlineCharacter || |
| it.getLineLayoutItem().preservesNewline()); |
| return notJustWhitespace || isEmptyInline(it.getLineLayoutItem()); |
| } |
| |
| inline void setStaticPositions(LineLayoutBlockFlow block, |
| LineLayoutBox child, |
| IndentTextOrNot indentText) { |
| ASSERT(child.isOutOfFlowPositioned()); |
| // FIXME: The math here is actually not really right. It's a best-guess |
| // approximation that will work for the common cases |
| LineLayoutItem containerBlock = child.container(); |
| LayoutUnit blockHeight = block.logicalHeight(); |
| if (containerBlock.isLayoutInline()) { |
| // 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. |
| LineLayoutInline(containerBlock) |
| .layer() |
| ->setStaticInlinePosition( |
| block.startAlignedOffsetForLine(blockHeight, indentText)); |
| LineLayoutInline(containerBlock) |
| .layer() |
| ->setStaticBlockPosition(blockHeight); |
| |
| // If |child| is a leading or trailing positioned object this is its only |
| // opportunity to ensure it moves with an inline container changing width. |
| child.moveWithEdgeOfInlineContainerIfNecessary( |
| child.isHorizontalWritingMode()); |
| } |
| block.updateStaticInlinePositionForChild(child, blockHeight, indentText); |
| child.layer()->setStaticBlockPosition(blockHeight); |
| } |
| |
| // FIXME: The entire concept of the skipTrailingWhitespace function is flawed, |
| // since we really need to be building line boxes even for containers that may |
| // ultimately collapse away. Otherwise we'll never get positioned elements quite |
| // right. In other words, we need to build this function's work into the normal |
| // line object iteration process. NB. this function will insert any floating |
| // elements that would otherwise be skipped but it will not position them. |
| inline void BreakingContext::skipTrailingWhitespace(InlineIterator& iterator, |
| const LineInfo& lineInfo) { |
| while (!iterator.atEnd() && |
| !requiresLineBox(iterator, lineInfo, TrailingWhitespace)) { |
| LineLayoutItem item = iterator.getLineLayoutItem(); |
| if (item.isOutOfFlowPositioned()) |
| setStaticPositions(m_block, LineLayoutBox(item), DoNotIndentText); |
| else if (item.isFloating()) |
| m_block.insertFloatingObject(LineLayoutBox(item)); |
| iterator.increment(); |
| } |
| } |
| |
| inline void BreakingContext::initializeForCurrentObject() { |
| m_currentStyle = m_current.getLineLayoutItem().style(); |
| m_nextObject = |
| bidiNextSkippingEmptyInlines(m_block, m_current.getLineLayoutItem()); |
| if (m_nextObject && m_nextObject.parent() && |
| !m_nextObject.parent().isDescendantOf( |
| m_current.getLineLayoutItem().parent())) |
| m_includeEndWidth = true; |
| |
| m_currWS = m_current.getLineLayoutItem().isLayoutInline() |
| ? m_currentStyle->whiteSpace() |
| : m_current.getLineLayoutItem().parent().style()->whiteSpace(); |
| m_lastWS = m_lastObject.isLayoutInline() |
| ? m_lastObject.style()->whiteSpace() |
| : m_lastObject.parent().style()->whiteSpace(); |
| |
| bool isSVGText = m_current.getLineLayoutItem().isSVGInlineText(); |
| m_autoWrap = !isSVGText && ComputedStyle::autoWrap(m_currWS); |
| m_autoWrapWasEverTrueOnLine = m_autoWrapWasEverTrueOnLine || m_autoWrap; |
| |
| m_preservesNewline = !isSVGText && ComputedStyle::preserveNewline(m_currWS); |
| |
| m_collapseWhiteSpace = ComputedStyle::collapseWhiteSpace(m_currWS); |
| |
| // Ensure the whitespace in constructions like '<span style="white-space: |
| // pre-wrap">text <span><span> text</span>' does not collapse. |
| if (m_collapseWhiteSpace && !ComputedStyle::collapseWhiteSpace(m_lastWS)) |
| m_currentCharacterIsSpace = false; |
| } |
| |
| inline void BreakingContext::increment() { |
| m_current.moveToStartOf(m_nextObject); |
| |
| // When the line box tree is created, this position in the line will be |
| // snapped to LayoutUnit's, and those measurements will be used by the paint |
| // code. Do the equivalent snapping here, to get consistent line measurements. |
| m_width.snapUncommittedWidth(); |
| |
| m_atStart = false; |
| } |
| |
| inline void BreakingContext::handleBR(EClear& clear) { |
| if (m_width.fitsOnLine()) { |
| LineLayoutItem br = m_current.getLineLayoutItem(); |
| 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_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() != ClearNone) |
| ensureLineBoxInsideIgnoredSpaces(&m_lineMidpointState, br); |
| |
| if (!m_lineInfo.isEmpty()) |
| clear = m_currentStyle->clear(); |
| } |
| m_atEnd = true; |
| } |
| |
| inline LayoutUnit borderPaddingMarginStart(LineLayoutInline child) { |
| return child.marginStart() + child.paddingStart() + child.borderStart(); |
| } |
| |
| inline LayoutUnit borderPaddingMarginEnd(LineLayoutInline child) { |
| return child.marginEnd() + child.paddingEnd() + child.borderEnd(); |
| } |
| |
| inline bool shouldAddBorderPaddingMargin(LineLayoutItem child, |
| bool& checkSide) { |
| if (!child || (child.isText() && !LineLayoutText(child).textLength())) |
| return true; |
| checkSide = false; |
| return checkSide; |
| } |
| |
| inline LayoutUnit inlineLogicalWidthFromAncestorsIfNeeded(LineLayoutItem child, |
| bool start = true, |
| bool end = true) { |
| unsigned lineDepth = 1; |
| LayoutUnit extraWidth; |
| LineLayoutItem parent = child.parent(); |
| while (parent.isLayoutInline() && lineDepth++ < cMaxLineDepth) { |
| LineLayoutInline parentAsLayoutInline(parent); |
| if (!isEmptyInline(parentAsLayoutInline)) { |
| if (start && shouldAddBorderPaddingMargin(child.previousSibling(), start)) |
| extraWidth += borderPaddingMarginStart(parentAsLayoutInline); |
| if (end && shouldAddBorderPaddingMargin(child.nextSibling(), end)) |
| extraWidth += borderPaddingMarginEnd(parentAsLayoutInline); |
| if (!start && !end) |
| return extraWidth; |
| } |
| child = parent; |
| parent = child.parent(); |
| } |
| return extraWidth; |
| } |
| |
| inline void BreakingContext::handleOutOfFlowPositioned( |
| Vector<LineLayoutBox>& positionedObjects) { |
| // If our original display wasn't an inline type, then we can |
| // go ahead and determine our static inline position now. |
| LineLayoutBox box(m_current.getLineLayoutItem()); |
| bool isInlineType = box.style()->isOriginalDisplayInlineType(); |
| if (!isInlineType) { |
| m_block.setStaticInlinePositionForChild(box, |
| m_block.startOffsetForContent()); |
| } else { |
| // If our original display was an INLINE type, then we can determine our |
| // static y position now. Note, however, that if we're paginated, we may |
| // have to update this position after the line has been laid out, since the |
| // line may be pushed by a pagination strut. |
| 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. |
| bool containerIsInline = box.container().isLayoutInline(); |
| if (isInlineType || containerIsInline) { |
| if (m_ignoringSpaces) |
| ensureLineBoxInsideIgnoredSpaces(&m_lineMidpointState, box); |
| m_trailingObjects.appendObjectIfNeeded(box); |
| } |
| if (!containerIsInline) |
| positionedObjects.append(box); |
| m_width.addUncommittedWidth( |
| inlineLogicalWidthFromAncestorsIfNeeded(box).toFloat()); |
| // Reset prior line break context characters. |
| m_layoutTextInfo.m_lineBreakIterator.resetPriorContext(); |
| } |
| |
| inline void BreakingContext::handleFloat() { |
| LineLayoutBox floatBox(m_current.getLineLayoutItem()); |
| FloatingObject* floatingObject = m_block.insertFloatingObject(floatBox); |
| // Check if it fits in the current line; if it does, position it now, |
| // otherwise, position it after moving to next line (in newLine() func). |
| // FIXME: Bug 110372: Properly position multiple stacked floats with |
| // non-rectangular shape outside. |
| if (m_floatsFitOnLine && |
| m_width.fitsOnLine( |
| m_block.logicalWidthForFloat(*floatingObject).toFloat(), |
| ExcludeWhitespace)) { |
| m_block.placeNewFloats(m_block.logicalHeight(), &m_width); |
| if (m_lineBreak.getLineLayoutItem() == m_current.getLineLayoutItem()) { |
| 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_layoutTextInfo.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( |
| LineLayoutBlockFlow block, |
| LineLayoutItem o, |
| LineMidpointState& lineMidpointState) { |
| LineLayoutItem next = bidiNextSkippingEmptyInlines(block, o); |
| while (next && next.isFloatingOrOutOfFlowPositioned()) |
| next = bidiNextSkippingEmptyInlines(block, next); |
| |
| while (next && isEmptyInline(next)) { |
| LineLayoutItem child = LineLayoutInline(next).firstChild(); |
| next = child ? child : bidiNextSkippingEmptyInlines(block, next); |
| } |
| |
| if (next && !next.isBR() && next.isText() && |
| LineLayoutText(next).textLength() > 0) { |
| LineLayoutText nextText(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() { |
| // This should only end up being called on empty inlines |
| ASSERT(m_current.getLineLayoutItem()); |
| |
| LineLayoutInline flowBox(m_current.getLineLayoutItem()); |
| |
| bool requiresLineBox = alwaysRequiresLineBox(m_current.getLineLayoutItem()); |
| if (requiresLineBox || requiresLineBoxForContent(flowBox, m_lineInfo)) { |
| // An empty inline that only has line-height, vertical-align or font-metrics |
| // will not force linebox creation (and thus affect the height of the line) |
| // if the rest of the line is empty. |
| if (requiresLineBox) |
| m_lineInfo.setEmpty(false); |
| if (m_ignoringSpaces) { |
| // If we are in a run of ignored spaces then ensure we get a linebox if |
| // lineboxes are eventually created for the line... |
| m_trailingObjects.clear(); |
| ensureLineBoxInsideIgnoredSpaces(&m_lineMidpointState, |
| m_current.getLineLayoutItem()); |
| } else if (m_blockStyle->collapseWhiteSpace() && |
| m_resolver.position().getLineLayoutItem() == |
| m_current.getLineLayoutItem() && |
| shouldSkipWhitespaceAfterStartObject( |
| m_block, m_current.getLineLayoutItem(), |
| m_lineMidpointState)) { |
| // If this object is at the start of the line, we need to behave like list |
| // markers and start ignoring spaces. |
| m_currentCharacterIsSpace = true; |
| m_ignoringSpaces = true; |
| } else { |
| // If we are after a trailing space but aren't ignoring spaces yet then |
| // ensure we get a linebox if we encounter collapsible whitepace. |
| m_trailingObjects.appendObjectIfNeeded(m_current.getLineLayoutItem()); |
| } |
| } |
| |
| m_width.addUncommittedWidth( |
| (inlineLogicalWidthFromAncestorsIfNeeded(m_current.getLineLayoutItem()) + |
| borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox)) |
| .toFloat()); |
| } |
| |
| inline void BreakingContext::handleReplaced() { |
| LineLayoutBox replacedBox(m_current.getLineLayoutItem()); |
| |
| if (m_atStart) |
| m_width.updateAvailableWidth(replacedBox.logicalHeight()); |
| |
| // Break on replaced elements if either has normal white-space, |
| // or if the replaced element is ruby that can break before. |
| if ((m_autoWrap || ComputedStyle::autoWrap(m_lastWS)) && |
| (!m_current.getLineLayoutItem().isImage() || m_allowImagesToBreak) && |
| (!m_current.getLineLayoutItem().isRubyRun() || |
| LineLayoutRubyRun(m_current.getLineLayoutItem()) |
| .canBreakBefore(m_layoutTextInfo.m_lineBreakIterator))) { |
| m_width.commit(); |
| m_lineBreak.moveToStartOf(m_current.getLineLayoutItem()); |
| } |
| |
| if (m_ignoringSpaces) |
| m_lineMidpointState.stopIgnoringSpaces( |
| InlineIterator(0, m_current.getLineLayoutItem(), 0)); |
| |
| m_lineInfo.setEmpty(false); |
| m_ignoringSpaces = false; |
| m_currentCharacterIsSpace = 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) + |
| inlineLogicalWidthFromAncestorsIfNeeded(m_current.getLineLayoutItem()); |
| if (m_current.getLineLayoutItem().isListMarker()) { |
| if (m_blockStyle->collapseWhiteSpace() && |
| shouldSkipWhitespaceAfterStartObject( |
| m_block, m_current.getLineLayoutItem(), 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_ignoringSpaces = true; |
| } |
| if (LineLayoutListMarker(m_current.getLineLayoutItem()).isInside()) |
| m_width.addUncommittedWidth(replacedLogicalWidth.toFloat()); |
| } else { |
| m_width.addUncommittedWidth(replacedLogicalWidth.toFloat()); |
| } |
| if (m_current.getLineLayoutItem().isRubyRun()) |
| m_width.applyOverhang(LineLayoutRubyRun(m_current.getLineLayoutItem()), |
| m_lastObject, m_nextObject); |
| // Update prior line break context characters, using U+FFFD (OBJECT |
| // REPLACEMENT CHARACTER) for replaced element. |
| m_layoutTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter); |
| } |
| |
| inline void nextCharacter(UChar& currentCharacter, |
| UChar& lastCharacter, |
| UChar& secondToLastCharacter) { |
| secondToLastCharacter = lastCharacter; |
| lastCharacter = currentCharacter; |
| } |
| |
| ALWAYS_INLINE void BreakingContext::setCurrentCharacterIsSpace(UChar c) { |
| m_currentCharacterIsSpace = c == spaceCharacter || c == tabulationCharacter || |
| (!m_preservesNewline && (c == newlineCharacter)); |
| } |
| |
| 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; |
| } |
| |
| ALWAYS_INLINE TextDirection |
| textDirectionFromUnicode(WTF::Unicode::CharDirection direction) { |
| return direction == WTF::Unicode::RightToLeft || |
| direction == WTF::Unicode::RightToLeftArabic |
| ? RTL |
| : LTR; |
| } |
| |
| ALWAYS_INLINE float textWidth( |
| LineLayoutText text, |
| unsigned from, |
| unsigned len, |
| const Font& font, |
| float xPos, |
| bool collapseWhiteSpace, |
| HashSet<const SimpleFontData*>* fallbackFonts = nullptr, |
| FloatRect* glyphBounds = nullptr) { |
| if ((!from && len == text.textLength()) || text.style()->hasTextCombine()) |
| return text.width(from, len, font, LayoutUnit(xPos), |
| text.style()->direction(), fallbackFonts, glyphBounds); |
| |
| TextRun run = constructTextRun(font, text, from, len, text.styleRef()); |
| run.setTabSize(!collapseWhiteSpace, text.style()->getTabSize()); |
| run.setXPos(xPos); |
| return font.width(run, fallbackFonts, glyphBounds); |
| } |
| |
| ALWAYS_INLINE bool BreakingContext::shouldMidWordBreak( |
| UChar c, |
| LineLayoutText layoutText, |
| const Font& font, |
| float& charWidth, |
| float& widthFromLastBreakingOpportunity, |
| bool breakAll, |
| int& nextBreakablePositionForBreakAll) { |
| // For breakWords/breakAll, we need to measure up to normal break |
| // opportunity and then rewindToMidWordBreak() because ligatures/kerning can |
| // shorten the width as we add more characters. |
| // However, doing so can hit the performance when a "word" is really long, |
| // such as minimized JS, because the next line will re-shape the rest of the |
| // word in the current architecture. |
| // This function is a heuristic optimization to stop at 2em overflow. |
| float overflowAllowance = 2 * font.getFontDescription().computedSize(); |
| |
| widthFromLastBreakingOpportunity += charWidth; |
| bool midWordBreakIsBeforeSurrogatePair = |
| U16_IS_LEAD(c) && m_current.offset() + 1 < layoutText.textLength() && |
| U16_IS_TRAIL(layoutText.uncheckedCharacterAt(m_current.offset() + 1)); |
| charWidth = textWidth( |
| layoutText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, |
| font, m_width.committedWidth() + widthFromLastBreakingOpportunity, |
| m_collapseWhiteSpace); |
| if (m_width.committedWidth() + widthFromLastBreakingOpportunity + charWidth <= |
| m_width.availableWidth() + overflowAllowance) |
| return false; |
| |
| // breakAll has different break opportunities. Ensure we break only at |
| // breakAll allows to break. |
| if (breakAll && |
| !m_layoutTextInfo.m_lineBreakIterator.isBreakable( |
| m_current.offset(), nextBreakablePositionForBreakAll, |
| LineBreakType::BreakAll)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| ALWAYS_INLINE int lastBreakablePositionForBreakAll(LineLayoutText text, |
| const ComputedStyle& style, |
| int start, |
| int end) { |
| LazyLineBreakIterator lineBreakIterator(text.text(), style.locale()); |
| int lastBreakablePosition = 0, nextBreakablePosition = -1; |
| for (int i = start;; i = nextBreakablePosition + 1) { |
| lineBreakIterator.isBreakable(i, nextBreakablePosition, |
| LineBreakType::BreakAll); |
| if (nextBreakablePosition == end) |
| return end; |
| if (nextBreakablePosition < 0 || nextBreakablePosition > end) |
| return lastBreakablePosition; |
| lastBreakablePosition = nextBreakablePosition; |
| } |
| } |
| |
| ALWAYS_INLINE bool BreakingContext::rewindToMidWordBreak( |
| WordMeasurement& wordMeasurement, |
| int end, |
| float width) { |
| wordMeasurement.endOffset = end; |
| wordMeasurement.width = width; |
| |
| m_current.moveTo(m_current.getLineLayoutItem(), end, |
| m_current.nextBreakablePosition()); |
| setCurrentCharacterIsSpace(m_current.current()); |
| m_lineBreak.moveTo(m_current.getLineLayoutItem(), end, |
| m_current.nextBreakablePosition()); |
| return true; |
| } |
| |
| ALWAYS_INLINE bool BreakingContext::rewindToFirstMidWordBreak( |
| LineLayoutText text, |
| const ComputedStyle& style, |
| const Font& font, |
| bool breakAll, |
| WordMeasurement& wordMeasurement) { |
| int start = wordMeasurement.startOffset; |
| int end; |
| if (breakAll) { |
| LazyLineBreakIterator lineBreakIterator(text.text(), style.locale()); |
| end = -1; |
| lineBreakIterator.isBreakable(start + 1, end, LineBreakType::BreakAll); |
| if (end < 0) |
| return false; |
| } else { |
| end = start + 1; |
| } |
| if (end >= wordMeasurement.endOffset) |
| return false; |
| |
| float width = textWidth(text, start, end - start, font, |
| m_width.currentWidth(), m_collapseWhiteSpace); |
| return rewindToMidWordBreak(wordMeasurement, end, width); |
| } |
| |
| ALWAYS_INLINE bool BreakingContext::rewindToMidWordBreak( |
| LineLayoutText text, |
| const ComputedStyle& style, |
| const Font& font, |
| bool breakAll, |
| WordMeasurement& wordMeasurement) { |
| int start = wordMeasurement.startOffset; |
| int len = wordMeasurement.endOffset - start; |
| if (!len) |
| return false; |
| if (m_width.availableWidth() <= LayoutUnit::epsilon()) |
| return rewindToFirstMidWordBreak(text, style, font, breakAll, |
| wordMeasurement); |
| |
| TextRun run = constructTextRun(font, text, start, len, style); |
| run.setTabSize(!m_collapseWhiteSpace, style.getTabSize()); |
| run.setXPos(m_width.currentWidth()); |
| |
| // TODO(kojii): should be replaced with safe-to-break when hb is ready. |
| float x = |
| m_width.availableWidth() + LayoutUnit::epsilon() - m_width.currentWidth(); |
| if (run.rtl()) |
| x = wordMeasurement.width - x; |
| len = font.offsetForPosition(run, x, false); |
| if (!len && !m_width.currentWidth()) |
| return rewindToFirstMidWordBreak(text, style, font, breakAll, |
| wordMeasurement); |
| |
| int end = start + len; |
| if (breakAll) { |
| end = lastBreakablePositionForBreakAll(text, style, start, end); |
| if (!end) |
| return false; |
| len = end - start; |
| } |
| FloatRect rect = font.selectionRectForText(run, FloatPoint(), 0, 0, len); |
| return rewindToMidWordBreak(wordMeasurement, end, rect.width()); |
| } |
| |
| ALWAYS_INLINE bool BreakingContext::hyphenate( |
| LineLayoutText text, |
| const ComputedStyle& style, |
| const Font& font, |
| const Hyphenation& hyphenation, |
| float lastSpaceWordSpacing, |
| WordMeasurement& wordMeasurement) { |
| unsigned start = wordMeasurement.startOffset; |
| unsigned len = wordMeasurement.endOffset - start; |
| if (len <= Hyphenation::minimumSuffixLength) |
| return false; |
| |
| float hyphenWidth = text.hyphenWidth( |
| font, textDirectionFromUnicode(m_resolver.position().direction())); |
| float maxPrefixWidth = m_width.availableWidth() - m_width.currentWidth() - |
| hyphenWidth - lastSpaceWordSpacing; |
| |
| if (maxPrefixWidth <= Hyphenation::minimumPrefixWidth(font)) |
| return false; |
| |
| TextRun run = constructTextRun(font, text, start, len, style); |
| run.setTabSize(!m_collapseWhiteSpace, style.getTabSize()); |
| run.setXPos(m_width.currentWidth()); |
| unsigned maxPrefixLength = font.offsetForPosition(run, maxPrefixWidth, false); |
| if (maxPrefixLength < Hyphenation::minimumPrefixLength) |
| return false; |
| |
| unsigned prefixLength = hyphenation.lastHyphenLocation( |
| StringView(text.text(), start, len), |
| std::min(maxPrefixLength, len - Hyphenation::minimumSuffixLength) + 1); |
| if (!prefixLength || prefixLength < Hyphenation::minimumPrefixLength) |
| return false; |
| |
| // TODO(kojii): getCharacterRange() measures as if the word were not broken |
| // as defined in the spec, and is faster than measuring each fragment, but |
| // ignores the kerning between the last letter and the hyphen. |
| return rewindToMidWordBreak( |
| wordMeasurement, start + prefixLength, |
| font.getCharacterRange(run, 0, prefixLength).width() + hyphenWidth); |
| } |
| |
| ALWAYS_INLINE bool BreakingContext::isBreakAtSoftHyphen() const { |
| return m_lineBreak != m_resolver.position() |
| ? m_lineBreak.previousInSameNode() == softHyphenCharacter |
| : m_current.previousInSameNode() == softHyphenCharacter; |
| } |
| |
| inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, |
| bool& hyphenated) { |
| if (!m_current.offset()) |
| m_appliedStartWidth = false; |
| |
| LineLayoutText layoutText(m_current.getLineLayoutItem()); |
| |
| // 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 && !ComputedStyle::autoWrap(m_lastWS) && m_ignoringSpaces) { |
| m_width.commit(); |
| m_lineBreak.moveToStartOf(m_current.getLineLayoutItem()); |
| } |
| |
| const ComputedStyle& style = layoutText.styleRef(m_lineInfo.isFirstLine()); |
| const Font& font = style.font(); |
| |
| unsigned lastSpace = m_current.offset(); |
| float wordSpacing = m_currentStyle->wordSpacing(); |
| float lastSpaceWordSpacing = 0; |
| float wordSpacingForWordMeasurement = 0; |
| |
| float widthFromLastBreakingOpportunity = m_width.uncommittedWidth(); |
| float charWidth = 0; |
| // 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 == EWhiteSpace::Pre); |
| bool midWordBreak = false; |
| bool breakAll = |
| m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap; |
| bool keepAll = m_currentStyle->wordBreak() == KeepAllWordBreak && m_autoWrap; |
| bool prohibitBreakInside = m_currentStyle->hasTextCombine() && |
| layoutText.isCombineText() && |
| LineLayoutTextCombine(layoutText).isCombined(); |
| |
| // This is currently only used for word-break: break-all, specifically for the |
| // case where we have a break opportunity within a word, then a string of non- |
| // breakable content that ends up making our word wider than the current line. |
| // See: fast/css3-text/css3-word-break/word-break-all-wrap-with-floats.html |
| float widthMeasurementAtLastBreakOpportunity = 0; |
| |
| Hyphenation* hyphenation = style.getHyphenation(); |
| bool disableSoftHyphen = style.getHyphens() == HyphensNone; |
| float hyphenWidth = 0; |
| |
| if (layoutText.isSVGInlineText()) { |
| breakWords = false; |
| breakAll = false; |
| keepAll = false; |
| } |
| |
| // Use LineBreakType::Normal for break-all. When a word does not fit, |
| // rewindToMidWordBreak() finds the mid-word break point. |
| LineBreakType lineBreakType = |
| keepAll ? LineBreakType::KeepAll : LineBreakType::Normal; |
| bool canBreakMidWord = breakAll || breakWords; |
| int nextBreakablePositionForBreakAll = -1; |
| |
| if (layoutText.isWordBreak()) { |
| m_width.commit(); |
| m_lineBreak.moveToStartOf(m_current.getLineLayoutItem()); |
| ASSERT(m_current.offset() == layoutText.textLength()); |
| } |
| |
| if (m_layoutTextInfo.m_text != layoutText) { |
| m_layoutTextInfo.m_text = layoutText; |
| m_layoutTextInfo.m_font = &font; |
| m_layoutTextInfo.m_lineBreakIterator.resetStringAndReleaseIterator( |
| layoutText.text(), style.locale()); |
| } else if (m_layoutTextInfo.m_font != &font) { |
| m_layoutTextInfo.m_font = &font; |
| } |
| |
| // Non-zero only when kerning is enabled, in which case we measure |
| // words with their trailing space, then subtract its width. |
| float wordTrailingSpaceWidth = |
| (font.getFontDescription().getTypesettingFeatures() & Kerning) |
| ? font.width(constructTextRun(font, &spaceCharacter, 1, style, |
| style.direction())) + |
| wordSpacing |
| : 0; |
| |
| UChar lastCharacter = m_layoutTextInfo.m_lineBreakIterator.lastCharacter(); |
| UChar secondToLastCharacter = |
| m_layoutTextInfo.m_lineBreakIterator.secondToLastCharacter(); |
| for (; m_current.offset() < layoutText.textLength(); |
| m_current.fastIncrementInTextNode()) { |
| bool previousCharacterIsSpace = m_currentCharacterIsSpace; |
| UChar c = m_current.current(); |
| setCurrentCharacterIsSpace(c); |
| |
| if (!m_collapseWhiteSpace || !m_currentCharacterIsSpace) { |
| m_lineInfo.setEmpty(false); |
| m_width.setTrailingWhitespaceWidth(0); |
| } |
| |
| if (c == softHyphenCharacter && m_autoWrap && !hyphenWidth && |
| !disableSoftHyphen) { |
| hyphenWidth = layoutText.hyphenWidth( |
| font, textDirectionFromUnicode(m_resolver.position().direction())); |
| m_width.addUncommittedWidth(hyphenWidth); |
| } |
| |
| bool applyWordSpacing = false; |
| |
| // Determine if we should try breaking in the middle of a word. |
| if (canBreakMidWord && !midWordBreak && !U16_IS_TRAIL(c)) |
| midWordBreak = shouldMidWordBreak( |
| c, layoutText, font, charWidth, widthFromLastBreakingOpportunity, |
| breakAll, nextBreakablePositionForBreakAll); |
| |
| // Determine if we are in the whitespace between words. |
| int nextBreakablePosition = m_current.nextBreakablePosition(); |
| bool betweenWords = |
| c == newlineCharacter || |
| (m_currWS != EWhiteSpace::Pre && !m_atStart && |
| m_layoutTextInfo.m_lineBreakIterator.isBreakable( |
| m_current.offset(), nextBreakablePosition, lineBreakType) && |
| (!disableSoftHyphen || |
| m_current.previousInSameNode() != softHyphenCharacter)); |
| m_current.setNextBreakablePosition(nextBreakablePosition); |
| |
| // If we're in the middle of a word or at the start of a new one and can't |
| // break there, then continue to the next character. |
| if (!betweenWords && !midWordBreak) { |
| if (m_ignoringSpaces) { |
| // Stop ignoring spaces and begin at this |
| // new point. |
| lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; |
| wordSpacingForWordMeasurement = |
| (applyWordSpacing && wordMeasurements.back().width) ? wordSpacing |
| : 0; |
| stopIgnoringSpaces(lastSpace); |
| } |
| |
| prepareForNextCharacter(layoutText, prohibitBreakInside, |
| previousCharacterIsSpace); |
| m_atStart = false; |
| nextCharacter(c, lastCharacter, secondToLastCharacter); |
| continue; |
| } |
| |
| // If we're collapsing space and we're at a collapsible space such as a |
| // space or tab, continue to the next character. |
| if (m_ignoringSpaces && m_currentCharacterIsSpace) { |
| lastSpaceWordSpacing = 0; |
| // Just keep ignoring these spaces. |
| nextCharacter(c, lastCharacter, secondToLastCharacter); |
| continue; |
| } |
| |
| // We're in the first whitespace after a word or in whitespace that we don't |
| // collapse, which means we may have a breaking opportunity here. |
| |
| // If we're here and we're collapsing space then the current character isn't |
| // a form of whitespace we can collapse. Stop ignoring spaces. |
| bool stoppedIgnoringSpaces = false; |
| if (m_ignoringSpaces) { |
| lastSpaceWordSpacing = 0; |
| wordSpacingForWordMeasurement = 0; |
| stoppedIgnoringSpaces = true; |
| stopIgnoringSpaces(lastSpace); |
| } |
| |
| // Update our tally of the width since the last breakable position with the |
| // width of the word we're now at the end of. |
| float lastWidthMeasurement; |
| WordMeasurement& wordMeasurement = calculateWordWidth( |
| wordMeasurements, layoutText, lastSpace, lastWidthMeasurement, |
| wordSpacingForWordMeasurement, font, wordTrailingSpaceWidth, c); |
| lastWidthMeasurement += lastSpaceWordSpacing; |
| m_width.addUncommittedWidth(lastWidthMeasurement); |
| |
| // We keep track of the total width contributed by trailing space as we |
| // often want to exclude it when determining |
| // if a run fits on a line. |
| if (m_collapseWhiteSpace && previousCharacterIsSpace && |
| m_currentCharacterIsSpace && lastWidthMeasurement) |
| m_width.setTrailingWhitespaceWidth(lastWidthMeasurement); |
| |
| // If this is the end of the first word in run of text then make sure we |
| // apply the width from any leading inlines. |
| // For example: '<span style="margin-left: 5px;"><span style="margin-left: |
| // 10px;">FirstWord</span></span>' would apply a width of 15px from the two |
| // span ancestors. |
| if (!m_appliedStartWidth) { |
| m_width.addUncommittedWidth( |
| inlineLogicalWidthFromAncestorsIfNeeded(m_current.getLineLayoutItem(), |
| true, false) |
| .toFloat()); |
| m_appliedStartWidth = true; |
| } |
| |
| midWordBreak = false; |
| if (!m_width.fitsOnLine()) { |
| if (canBreakMidWord) { |
| m_width.addUncommittedWidth(-wordMeasurement.width); |
| if (rewindToMidWordBreak(layoutText, style, font, breakAll, |
| wordMeasurement)) { |
| lastWidthMeasurement = wordMeasurement.width + lastSpaceWordSpacing; |
| midWordBreak = true; |
| } |
| m_width.addUncommittedWidth(wordMeasurement.width); |
| } else if (hyphenation) { |
| m_width.addUncommittedWidth(-wordMeasurement.width); |
| DCHECK(lastSpace == static_cast<unsigned>(wordMeasurement.startOffset)); |
| DCHECK(m_current.offset() == |
| static_cast<unsigned>(wordMeasurement.endOffset)); |
| if (hyphenate(layoutText, style, font, *hyphenation, |
| lastSpaceWordSpacing, wordMeasurement)) { |
| m_width.addUncommittedWidth(wordMeasurement.width); |
| hyphenated = true; |
| m_atEnd = true; |
| return false; |
| } |
| m_width.addUncommittedWidth(wordMeasurement.width); |
| } |
| } |
| |
| // If we haven't hit a breakable position yet and already don't fit on the |
| // line try to move below any floats. |
| if (!m_width.committedWidth() && m_autoWrap && !m_width.fitsOnLine() && |
| !widthMeasurementAtLastBreakOpportunity) { |
| float availableWidthBefore = m_width.availableWidth(); |
| m_width.fitBelowFloats(m_lineInfo.isFirstLine()); |
| // If availableWidth changes by moving the line below floats, needs to |
| // measure midWordBreak again. |
| if (midWordBreak && availableWidthBefore != m_width.availableWidth()) |
| midWordBreak = false; |
| } |
| |
| // If there is a soft-break available at this whitespace position then take |
| // it. |
| applyWordSpacing = wordSpacing && m_currentCharacterIsSpace; |
| if (canBreakAtWhitespace(breakWords, wordMeasurement, stoppedIgnoringSpaces, |
| charWidth, hyphenated, disableSoftHyphen, |
| hyphenWidth, betweenWords, midWordBreak, |
| canBreakMidWord, previousCharacterIsSpace, |
| lastWidthMeasurement, layoutText, font, |
| applyWordSpacing, wordSpacing)) |
| return false; |
| |
| // If there is a hard-break available at this whitespace position then take |
| // it. |
| if (c == newlineCharacter && m_preservesNewline) { |
| if (!stoppedIgnoringSpaces && m_current.offset()) |
| m_lineMidpointState.ensureCharacterGetsLineBox(m_current); |
| m_lineBreak.moveTo(m_current.getLineLayoutItem(), m_current.offset(), |
| m_current.nextBreakablePosition()); |
| m_lineBreak.increment(); |
| m_lineInfo.setPreviousLineBrokeCleanly(true); |
| return true; |
| } |
| |
| // Auto-wrapping text should not wrap in the middle of a word once it has |
| // had an opportunity to break after a word. |
| if (m_autoWrap && betweenWords) { |
| m_width.commit(); |
| widthFromLastBreakingOpportunity = 0; |
| m_lineBreak.moveTo(m_current.getLineLayoutItem(), m_current.offset(), |
| m_current.nextBreakablePosition()); |
| breakWords = false; |
| canBreakMidWord = breakAll; |
| widthMeasurementAtLastBreakOpportunity = lastWidthMeasurement; |
| } |
| |
| if (betweenWords) { |
| lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; |
| wordSpacingForWordMeasurement = |
| (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0; |
| lastSpace = m_current.offset(); |
| } |
| |
| // 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_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) { |
| 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.updateMidpointsForTrailingObjects( |
| m_lineMidpointState, InlineIterator(), |
| TrailingObjects::DoNotCollapseFirstSpace); |
| } |
| } |
| |
| prepareForNextCharacter(layoutText, prohibitBreakInside, |
| previousCharacterIsSpace); |
| m_atStart = false; |
| nextCharacter(c, lastCharacter, secondToLastCharacter); |
| } |
| |
| m_layoutTextInfo.m_lineBreakIterator.setPriorContext(lastCharacter, |
| secondToLastCharacter); |
| |
| wordMeasurements.grow(wordMeasurements.size() + 1); |
| WordMeasurement& wordMeasurement = wordMeasurements.back(); |
| wordMeasurement.layoutText = layoutText; |
| |
| // IMPORTANT: current.offset() is > layoutText.textLength() here! |
| float lastWidthMeasurement = 0; |
| wordMeasurement.startOffset = lastSpace; |
| wordMeasurement.endOffset = m_current.offset(); |
| midWordBreak = false; |
| if (!m_ignoringSpaces) { |
| lastWidthMeasurement = |
| textWidth(layoutText, lastSpace, m_current.offset() - lastSpace, font, |
| m_width.currentWidth(), m_collapseWhiteSpace, |
| &wordMeasurement.fallbackFonts, &wordMeasurement.glyphBounds); |
| wordMeasurement.width = |
| lastWidthMeasurement + wordSpacingForWordMeasurement; |
| wordMeasurement.glyphBounds.move(wordSpacingForWordMeasurement, 0); |
| |
| if (canBreakMidWord && !m_width.fitsOnLine(lastWidthMeasurement) && |
| rewindToMidWordBreak(layoutText, style, font, breakAll, |
| wordMeasurement)) { |
| lastWidthMeasurement = wordMeasurement.width; |
| midWordBreak = true; |
| } |
| } |
| lastWidthMeasurement += lastSpaceWordSpacing; |
| |
| LayoutUnit additionalWidthFromAncestors = |
| inlineLogicalWidthFromAncestorsIfNeeded(m_current.getLineLayoutItem(), |
| !m_appliedStartWidth, |
| m_includeEndWidth); |
| m_width.addUncommittedWidth(lastWidthMeasurement + |
| additionalWidthFromAncestors); |
| |
| if (m_collapseWhiteSpace && m_currentCharacterIsSpace && lastWidthMeasurement) |
| m_width.setTrailingWhitespaceWidth(lastWidthMeasurement + |
| additionalWidthFromAncestors); |
| |
| m_includeEndWidth = false; |
| |
| if (midWordBreak) { |
| m_width.commit(); |
| m_atEnd = true; |
| } else if (!m_width.fitsOnLine()) { |
| if (hyphenation && (m_nextObject || m_lineInfo.isEmpty())) { |
| m_width.addUncommittedWidth(-wordMeasurement.width); |
| DCHECK(lastSpace == static_cast<unsigned>(wordMeasurement.startOffset)); |
| DCHECK(m_current.offset() == |
| static_cast<unsigned>(wordMeasurement.endOffset)); |
| if (hyphenate(layoutText, style, font, *hyphenation, lastSpaceWordSpacing, |
| wordMeasurement)) { |
| hyphenated = true; |
| m_atEnd = true; |
| } |
| m_width.addUncommittedWidth(wordMeasurement.width); |
| } |
| if (!hyphenated && isBreakAtSoftHyphen() && !disableSoftHyphen) { |
| hyphenated = true; |
| m_atEnd = true; |
| } else if (!m_ignoringSpaces && canBreakMidWord && |
| m_width.committedWidth()) { |
| m_atEnd = true; |
| } |
| } |
| return false; |
| } |
| |
| inline void BreakingContext::prepareForNextCharacter( |
| const LineLayoutText& layoutText, |
| bool& prohibitBreakInside, |
| bool previousCharacterIsSpace) { |
| if (layoutText.isSVGInlineText() && m_current.offset()) { |
| // Force creation of new InlineBoxes for each absolute positioned character |
| // (those that start new text chunks). |
| if (LineLayoutSVGInlineText(layoutText) |
| .characterStartsNewTextChunk(m_current.offset())) |
| m_lineMidpointState.ensureCharacterGetsLineBox(m_current); |
| } |
| if (prohibitBreakInside) { |
| m_current.setNextBreakablePosition(layoutText.textLength()); |
| prohibitBreakInside = false; |
| } |
| if (m_currentCharacterIsSpace && !previousCharacterIsSpace) { |
| m_startOfIgnoredSpaces.setLineLayoutItem(m_current.getLineLayoutItem()); |
| m_startOfIgnoredSpaces.setOffset(m_current.offset()); |
| } |
| if (!m_currentCharacterIsSpace && previousCharacterIsSpace) { |
| if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace()) |
| m_lineBreak.moveTo(m_current.getLineLayoutItem(), m_current.offset(), |
| m_current.nextBreakablePosition()); |
| } |
| if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces) |
| m_trailingObjects.setTrailingWhitespace( |
| LineLayoutText(m_current.getLineLayoutItem())); |
| else if (!m_currentStyle->collapseWhiteSpace() || !m_currentCharacterIsSpace) |
| m_trailingObjects.clear(); |
| } |
| |
| inline void BreakingContext::stopIgnoringSpaces(unsigned& lastSpace) { |
| m_ignoringSpaces = false; |
| // e.g., "Foo goo", don't add in any of the ignored spaces. |
| lastSpace = m_current.offset(); |
| m_lineMidpointState.stopIgnoringSpaces( |
| InlineIterator(0, m_current.getLineLayoutItem(), m_current.offset())); |
| } |
| |
| inline WordMeasurement& BreakingContext::calculateWordWidth( |
| WordMeasurements& wordMeasurements, |
| LineLayoutText& layoutText, |
| unsigned lastSpace, |
| float& lastWidthMeasurement, |
| float wordSpacingForWordMeasurement, |
| const Font& font, |
| float wordTrailingSpaceWidth, |
| UChar c) { |
| wordMeasurements.grow(wordMeasurements.size() + 1); |
| WordMeasurement& wordMeasurement = wordMeasurements.back(); |
| wordMeasurement.layoutText = layoutText; |
| wordMeasurement.endOffset = m_current.offset(); |
| wordMeasurement.startOffset = lastSpace; |
| |
| if (wordTrailingSpaceWidth && c == spaceCharacter) |
| lastWidthMeasurement = |
| textWidth(layoutText, lastSpace, m_current.offset() + 1 - lastSpace, |
| font, m_width.currentWidth(), m_collapseWhiteSpace, |
| &wordMeasurement.fallbackFonts, |
| &wordMeasurement.glyphBounds) - |
| wordTrailingSpaceWidth; |
| else |
| lastWidthMeasurement = |
| textWidth(layoutText, lastSpace, m_current.offset() - lastSpace, font, |
| m_width.currentWidth(), m_collapseWhiteSpace, |
| &wordMeasurement.fallbackFonts, &wordMeasurement.glyphBounds); |
| |
| wordMeasurement.width = lastWidthMeasurement + wordSpacingForWordMeasurement; |
| wordMeasurement.glyphBounds.move(wordSpacingForWordMeasurement, 0); |
| return wordMeasurement; |
| } |
| |
| inline bool BreakingContext::trailingSpaceExceedsAvailableWidth( |
| bool canBreakMidWord, |
| const LineLayoutText& layoutText, |
| WordMeasurement& wordMeasurement, |
| bool applyWordSpacing, |
| bool wordSpacing, |
| const Font& font) { |
| // If we break only after white-space, consider the current character |
| // as candidate width for this line. |
| if (m_width.fitsOnLine() && m_currentCharacterIsSpace && |
| m_currentStyle->breakOnlyAfterWhiteSpace() && !canBreakMidWord) { |
| float charWidth = textWidth(layoutText, m_current.offset(), 1, font, |
| m_width.currentWidth(), m_collapseWhiteSpace, |
| &wordMeasurement.fallbackFonts, |
| &wordMeasurement.glyphBounds) + |
| (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.fitsOnLine(charWidth)) { |
| m_lineBreak.moveTo(m_current.getLineLayoutItem(), m_current.offset(), |
| m_current.nextBreakablePosition()); |
| skipTrailingWhitespace(m_lineBreak, m_lineInfo); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| inline bool BreakingContext::canBreakAtWhitespace( |
| bool breakWords, |
| WordMeasurement& wordMeasurement, |
| bool stoppedIgnoringSpaces, |
| float charWidth, |
| bool& hyphenated, |
| bool disableSoftHyphen, |
| float& hyphenWidth, |
| bool betweenWords, |
| bool midWordBreak, |
| bool canBreakMidWord, |
| bool previousCharacterIsSpace, |
| float lastWidthMeasurement, |
| const LineLayoutText& layoutText, |
| const Font& font, |
| bool applyWordSpacing, |
| float wordSpacing) { |
| if (!m_autoWrap && !breakWords) |
| return false; |
| |
| // If we break only after white-space, consider the current character |
| // as candidate width for this line. |
| if (midWordBreak || trailingSpaceExceedsAvailableWidth( |
| canBreakMidWord, layoutText, wordMeasurement, |
| applyWordSpacing, wordSpacing, font) || |
| !m_width.fitsOnLine()) { |
| if (m_lineBreak.atTextParagraphSeparator()) { |
| if (!stoppedIgnoringSpaces && m_current.offset() > 0) |
| m_lineMidpointState.ensureCharacterGetsLineBox(m_current); |
| m_lineBreak.increment(); |
| m_lineInfo.setPreviousLineBrokeCleanly(true); |
| wordMeasurement.endOffset = m_lineBreak.offset(); |
| } |
| if (isBreakAtSoftHyphen() && !disableSoftHyphen) |
| 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 true; |
| } |
| } else { |
| if (!betweenWords || (midWordBreak && !m_autoWrap)) |
| m_width.addUncommittedWidth(-lastWidthMeasurement); |
| if (hyphenWidth) { |
| // Subtract the width of the soft hyphen out since we fit on a line. |
| m_width.addUncommittedWidth(-hyphenWidth); |
| hyphenWidth = 0; |
| } |
| } |
| return false; |
| } |
| |
| inline void BreakingContext::commitAndUpdateLineBreakIfNeeded() { |
| bool checkForBreak = m_autoWrap; |
| if (m_width.committedWidth() && !m_width.fitsOnLine() && |
| m_lineBreak.getLineLayoutItem() && m_currWS == EWhiteSpace::Nowrap) { |
| if (m_width.fitsOnLine(0, ExcludeWhitespace)) { |
| m_width.commit(); |
| m_lineBreak.moveToStartOf(m_nextObject); |
| } |
| checkForBreak = true; |
| } else if (m_nextObject && m_current.getLineLayoutItem().isText() && |
| m_nextObject.isText() && !m_nextObject.isBR() && |
| (m_autoWrap || m_nextObject.style()->autoWrap())) { |
| if (m_autoWrap && m_currentCharacterIsSpace) { |
| checkForBreak = true; |
| } else { |
| LineLayoutText nextText(m_nextObject); |
| if (nextText.textLength()) { |
| UChar c = nextText.characterAt(0); |
| // If the next item on the line is text, and if we did not end with |
| // a space, then the next text run continues our word (and so it needs |
| // to keep adding to the uncommitted width. Just update and continue. |
| checkForBreak = |
| !m_currentCharacterIsSpace && |
| (c == spaceCharacter || c == tabulationCharacter || |
| (c == newlineCharacter && !m_nextObject.preservesNewline())); |
| } else if (nextText.isWordBreak()) { |
| checkForBreak = true; |
| } |
| |
| if (!m_width.fitsOnLine() && !m_width.committedWidth()) |
| m_width.fitBelowFloats(m_lineInfo.isFirstLine()); |
| |
| bool canPlaceOnLine = |
| m_width.fitsOnLine() || !m_autoWrapWasEverTrueOnLine; |
| if (canPlaceOnLine && checkForBreak) { |
| m_width.commit(); |
| m_lineBreak.moveToStartOf(m_nextObject); |
| } |
| } |
| } |
| |
| SECURITY_DCHECK(m_currentStyle->refCount() > 0); |
| if (checkForBreak && !m_width.fitsOnLine()) { |
| // 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(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 |
| // the end label if we still don't fit on the line. -dwh |
| if (!m_width.fitsOnLine()) { |
| 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(m_lineInfo.isFirstLine()); |
| } |
| |
| if (!m_current.getLineLayoutItem().isFloatingOrOutOfFlowPositioned()) { |
| m_lastObject = m_current.getLineLayoutItem(); |
| if (m_lastObject.isAtomicInlineLevel() && m_autoWrap && |
| (!m_lastObject.isImage() || m_allowImagesToBreak) && |
| (!m_lastObject.isListMarker() || |
| LineLayoutListMarker(m_lastObject).isInside()) && |
| !m_lastObject.isRubyRun()) { |
| m_width.commit(); |
| m_lineBreak.moveToStartOf(m_nextObject); |
| } |
| } |
| } |
| |
| inline IndentTextOrNot requiresIndent(bool isFirstLine, |
| bool isAfterHardLineBreak, |
| const ComputedStyle& style) { |
| IndentTextOrNot indentText = DoNotIndentText; |
| if (isFirstLine || |
| (isAfterHardLineBreak && style.getTextIndentLine()) == TextIndentEachLine) |
| indentText = IndentText; |
| |
| if (style.getTextIndentType() == TextIndentHanging) |
| indentText = indentText == IndentText ? DoNotIndentText : IndentText; |
| |
| return indentText; |
| } |
| |
| } // namespace blink |
| |
| #endif // BreakingContextInlineHeaders_h |