blob: 7489edea96220c16c21d6e6e7097006e589e356b [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(NORMAL)
, m_lastWS(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 == NOWRAP)
m_lineBreak.clear();
}
private:
void skipTrailingWhitespace(InlineIterator&, const LineInfo&);
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&);
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() == PRE_WRAP && (!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()->font().getFontMetrics().hasIdenticalAscentDescentAndLineGap(flow.style()->font().getFontMetrics())))
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().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 go ahead
// and determine our static y position now.
box.layer()->setStaticBlockPosition(m_block.logicalHeight());
}
// If we're ignoring spaces, we have to stop and include this object and
// then start ignoring spaces again.
if (isInlineType || box.container().isLayoutInline()) {
if (m_ignoringSpaces)
ensureLineBoxInsideIgnoredSpaces(&m_lineMidpointState, box);
m_trailingObjects.appendObjectIfNeeded(box);
} else {
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.positionNewFloats(&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);
if (next && isEmptyInline(next))
next = LineLayoutInline(next).firstChild();
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;
}
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 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());
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);
DCHECK(m_width.fitsOnLine(rect.width() - 1)); // avoid failure when rect is rounded up.
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(
text.text().createView(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);
}
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 == 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.getHyphens() == HyphensAuto
? Hyphenation::get(font.getFontDescription().locale()) : nullptr;
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;
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();
m_currentCharacterIsSpace = c == spaceCharacter || c == tabulationCharacter || (!m_preservesNewline && (c == newlineCharacter));
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 (breakWords && !midWordBreak && !U16_IS_TRAIL(c)) {
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);
// Measure up to 2em overflow since ligatures/kerning can shorten
// the width as we add more characters. rewindToMidWordBreak() can
// measure the accurate mid-word break point then.
midWordBreak = m_width.committedWidth() + widthFromLastBreakingOpportunity + charWidth > m_width.availableWidth()
+ 2 * font.getFontDescription().computedSize();
}
// Determine if we are in the whitespace between words.
int nextBreakablePosition = m_current.nextBreakablePosition();
bool betweenWords = c == newlineCharacter || (m_currWS != 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.last().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;
}
// 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)
m_width.fitBelowFloats(m_lineInfo.isFirstLine());
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 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.last();
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
&& m_lineBreak.previousInSameNode() == softHyphenCharacter
&& !disableSoftHyphen) {
hyphenated = true;
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;
lastSpace = m_current.offset(); // e.g., "Foo goo", don't add in any of the ignored spaces.
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.last();
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 (m_lineBreak.getLineLayoutItem() && m_lineBreak.offset() && m_lineBreak.getLineLayoutItem().isText() && LineLayoutText(m_lineBreak.getLineLayoutItem()).textLength() && LineLayoutText(m_lineBreak.getLineLayoutItem()).characterAt(m_lineBreak.offset() - 1) == softHyphenCharacter && !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 == 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);
}
}
}
ASSERT_WITH_SECURITY_IMPLICATION(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 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