blob: 02072ff660e53acd5f7118d9f9de34c1008cb330 [file] [log] [blame]
/*
* 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 canMidWordBreakBefore(LineLayoutText);
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);
if (m_floatsFitOnLine) {
// We need to calculate the logical width of the float before we can tell
// whether it's going to fit on the line. That means that we need to
// position and lay it out. Note that we have to avoid positioning floats
// that have been placed prematurely: Sometimes, floats are inserted too
// early by skipTrailingWhitespace(), and later on they all get placed by
// the first float here in handleFloat(). Their position may then be wrong,
// but it's too late to do anything about that now. See crbug.com/671577
if (!floatingObject->isPlaced())
m_block.positionAndLayoutFloat(*floatingObject, m_block.logicalHeight());
// Check if it fits in the current line; if it does, place it now,
// otherwise, place it after moving to next line (in newLine() func).
// FIXME: Bug 110372: Properly position multiple stacked floats with
// non-rectangular shape outside.
if (m_width.fitsOnLine(
m_block.logicalWidthForFloat(*floatingObject).toFloat(),
ExcludeWhitespace)) {
m_block.placeNewFloats(m_block.logicalHeight(), &m_width);
if (m_lineBreak.getLineLayoutItem() == m_current.getLineLayoutItem()) {
DCHECK(!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
? TextDirection::Rtl
: TextDirection::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::canMidWordBreakBefore(LineLayoutText text) {
// No break opportunities at the beginning of a line.
if (!m_width.currentWidth())
return false;
// If this text node is not the first child of an inline, the parent and the
// preivous have the same style.
if (text.previousSibling())
return true;
// Element boundaries belong to the parent of the element.
// TODO(kojii): Not all cases of element boundaries are supported yet.
// crbug.com/282134
if (LineLayoutItem element = text.parent()) {
if (LineLayoutItem parent = element.parent()) {
const ComputedStyle& parentStyle = parent.styleRef();
return parentStyle.autoWrap() &&
((parentStyle.breakWords() && !m_width.committedWidth()) ||
parentStyle.wordBreak() == BreakAllWordBreak);
}
}
return false;
}
ALWAYS_INLINE bool BreakingContext::rewindToFirstMidWordBreak(
LineLayoutText text,
const ComputedStyle& style,
const Font& font,
bool breakAll,
WordMeasurement& wordMeasurement) {
int start = wordMeasurement.startOffset;
int end = canMidWordBreakBefore(text) ? start : start + 1;
if (breakAll) {
LazyLineBreakIterator lineBreakIterator(text.text(), style.locale());
int nextBreakable = -1;
lineBreakIterator.isBreakable(end, nextBreakable, LineBreakType::BreakAll);
if (nextBreakable < 0)
return false;
end = nextBreakable;
}
if (end >= wordMeasurement.endOffset)
return false;
float width = textWidth(text, start, end - start, font,
m_width.currentWidth(), m_collapseWhiteSpace);
// If the first break opportunity doesn't fit, and if there's a break
// opportunity in previous runs, break at the opportunity.
if (!m_width.fitsOnLine(width) && m_width.committedWidth())
return false;
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;
float xPosToBreak = m_width.availableWidth() - m_width.currentWidth();
if (xPosToBreak <= LayoutUnit::epsilon()) {
// There were no space left. Skip computing how many characters can fit.
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.
xPosToBreak += LayoutUnit::epsilon();
if (run.rtl())
xPosToBreak = wordMeasurement.width - xPosToBreak;
len = font.offsetForPosition(run, xPosToBreak, false);
if (!len) {
// No characters can fit in the available space.
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());
}
ALWAYS_INLINE bool BreakingContext::isBreakAtSoftHyphen() const {
return m_lineBreak != m_resolver.position()
? m_lineBreak.previousInSameNode() == softHyphenCharacter
: m_current.previousInSameNode() == softHyphenCharacter;
}
static ALWAYS_INLINE bool hasVisibleText(LineLayoutText layoutText,
unsigned offset) {
return !layoutText.containsOnlyWhitespace(offset,
layoutText.textLength() - offset);
}
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;
bool isLineEmpty = m_lineInfo.isEmpty();
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 (hyphenation && (m_nextObject || isLineEmpty ||
hasVisibleText(layoutText, m_current.offset()))) {
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 (canBreakMidWord) {
m_width.addUncommittedWidth(-wordMeasurement.width);
if (rewindToMidWordBreak(layoutText, style, font, breakAll,
wordMeasurement)) {
lastWidthMeasurement = wordMeasurement.width + lastSpaceWordSpacing;
midWordBreak = true;
}
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;
isLineEmpty = m_lineInfo.isEmpty();
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();
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);
}
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 (!m_ignoringSpaces && !m_width.fitsOnLine()) {
if (hyphenation && (m_nextObject || isLineEmpty)) {
m_width.addUncommittedWidth(-wordMeasurement.width);
DCHECK_EQ(lastSpace, static_cast<unsigned>(wordMeasurement.startOffset));
DCHECK_EQ(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;
}
if (!hyphenated && canBreakMidWord) {
m_width.addUncommittedWidth(-wordMeasurement.width);
if (rewindToMidWordBreak(layoutText, style, font, breakAll,
wordMeasurement)) {
m_width.addUncommittedWidth(wordMeasurement.width);
m_width.commit();
m_atEnd = true;
} else {
m_width.addUncommittedWidth(wordMeasurement.width);
if (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