BreakingContextInlineHeaders.h [plain text]
#ifndef BreakingContextInlineHeaders_h
#define BreakingContextInlineHeaders_h
#include "Hyphenation.h"
#include "LineBreaker.h"
#include "LineInfo.h"
#include "LineWidth.h"
#include "RenderCombineText.h"
#include "RenderCounter.h"
#include "RenderInline.h"
#include "RenderListMarker.h"
#include "RenderRubyRun.h"
#include "RenderSVGInlineText.h"
#include "TrailingObjects.h"
#include "break_lines.h"
#include <wtf/text/StringView.h>
#include <wtf/unicode/CharacterNames.h>
namespace WebCore {
const unsigned cMaxLineDepth = 200;
struct WordMeasurement {
WordMeasurement()
: renderer(0)
, width(0)
, startOffset(0)
, endOffset(0)
{
}
RenderText* renderer;
float width;
int startOffset;
int endOffset;
HashSet<const SimpleFontData*> fallbackFonts;
};
class BreakingContext {
public:
BreakingContext(LineBreaker& lineBreaker, InlineBidiResolver& resolver, LineInfo& inLineInfo, LineWidth& lineWidth, RenderTextInfo& inRenderTextInfo, FloatingObject* inLastFloatFromPreviousLine, bool appliedStartWidth, RenderBlockFlow& block)
: m_lineBreaker(lineBreaker)
, m_resolver(resolver)
, m_current(resolver.position())
, m_lineBreak(resolver.position())
, m_block(block)
, m_lastObject(m_current.renderer())
, m_nextObject(0)
, m_currentStyle(0)
, m_blockStyle(block.style())
, m_lineInfo(inLineInfo)
, m_renderTextInfo(inRenderTextInfo)
, m_lastFloatFromPreviousLine(inLastFloatFromPreviousLine)
, m_width(lineWidth)
, m_currWS(NORMAL)
, m_lastWS(NORMAL)
, m_preservesNewline(false)
, m_atStart(true)
, m_ignoringSpaces(false)
, m_currentCharacterIsSpace(false)
, m_currentCharacterIsWS(false)
, m_appliedStartWidth(appliedStartWidth)
, m_includeEndWidth(true)
, m_autoWrap(false)
, m_autoWrapWasEverTrueOnLine(false)
, m_floatsFitOnLine(true)
, m_collapseWhiteSpace(false)
, m_startingNewParagraph(m_lineInfo.previousLineBrokeCleanly())
, m_allowImagesToBreak(!block.document().inQuirksMode() || !block.isTableCell() || !m_blockStyle.logicalWidth().isIntrinsicOrAuto())
, m_atEnd(false)
, m_hadUncommittedWidthBeforeCurrent(false)
, m_lineMidpointState(resolver.midpointState())
{
m_lineInfo.setPreviousLineBrokeCleanly(false);
}
RenderObject* currentObject() { return m_current.renderer(); }
InlineIterator lineBreak() { return m_lineBreak; }
InlineIterator& lineBreakRef() {return m_lineBreak; }
LineWidth& lineWidth() { return m_width; }
bool atEnd() { return m_atEnd; }
void initializeForCurrentObject();
void increment();
void handleBR(EClear&);
void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects);
void handleFloat();
void handleEmptyInline();
void handleReplaced();
bool handleText(WordMeasurements&, bool& hyphenated, unsigned& consecutiveHyphenatedLines);
bool canBreakAtThisPosition();
void commitAndUpdateLineBreakIfNeeded();
InlineIterator handleEndOfLine();
void clearLineBreakIfFitsOnLine(bool ignoringTrailingSpace = false)
{
if (m_width.fitsOnLine(ignoringTrailingSpace) || m_lastWS == NOWRAP)
m_lineBreak.clear();
}
void commitLineBreakAtCurrentWidth(RenderObject* object, unsigned offset = 0, int nextBreak = -1)
{
m_width.commit();
m_lineBreak.moveTo(object, offset, nextBreak);
}
private:
LineBreaker& m_lineBreaker;
InlineBidiResolver& m_resolver;
InlineIterator m_current;
InlineIterator m_lineBreak;
InlineIterator m_startOfIgnoredSpaces;
RenderBlockFlow& m_block;
RenderObject* m_lastObject;
RenderObject* m_nextObject;
RenderStyle* m_currentStyle;
RenderStyle& m_blockStyle;
LineInfo& m_lineInfo;
RenderTextInfo& m_renderTextInfo;
FloatingObject* m_lastFloatFromPreviousLine;
LineWidth m_width;
EWhiteSpace m_currWS;
EWhiteSpace m_lastWS;
bool m_preservesNewline;
bool m_atStart;
bool m_ignoringSpaces;
bool m_currentCharacterIsSpace;
bool m_currentCharacterIsWS;
bool m_appliedStartWidth;
bool m_includeEndWidth;
bool m_autoWrap;
bool m_autoWrapWasEverTrueOnLine;
bool m_floatsFitOnLine;
bool m_collapseWhiteSpace;
bool m_startingNewParagraph;
bool m_allowImagesToBreak;
bool m_atEnd;
bool m_hadUncommittedWidthBeforeCurrent;
LineMidpointState& m_lineMidpointState;
TrailingObjects m_trailingObjects;
};
inline void BreakingContext::initializeForCurrentObject()
{
m_hadUncommittedWidthBeforeCurrent = !!m_width.uncommittedWidth();
m_currentStyle = &m_current.renderer()->style();
ASSERT(m_currentStyle);
m_nextObject = bidiNextSkippingEmptyInlines(m_block, m_current.renderer());
if (m_nextObject && m_nextObject->parent() && !m_nextObject->parent()->isDescendantOf(m_current.renderer()->parent()))
m_includeEndWidth = true;
m_currWS = m_current.renderer()->isReplaced() ? m_current.renderer()->parent()->style().whiteSpace() : m_currentStyle->whiteSpace();
m_lastWS = m_lastObject->isReplaced() ? m_lastObject->parent()->style().whiteSpace() : m_lastObject->style().whiteSpace();
m_autoWrap = RenderStyle::autoWrap(m_currWS);
m_autoWrapWasEverTrueOnLine = m_autoWrapWasEverTrueOnLine || m_autoWrap;
m_preservesNewline = m_current.renderer()->isSVGInlineText() ? false : RenderStyle::preserveNewline(m_currWS);
m_collapseWhiteSpace = RenderStyle::collapseWhiteSpace(m_currWS);
}
inline void BreakingContext::increment()
{
if (!m_collapseWhiteSpace)
m_currentCharacterIsSpace = false;
m_current.moveToStartOf(m_nextObject);
m_atStart = false;
}
inline void BreakingContext::handleBR(EClear& clear)
{
if (m_width.fitsOnLine()) {
RenderObject* br = m_current.renderer();
m_lineBreak.moveToStartOf(br);
m_lineBreak.increment();
if (m_startingNewParagraph)
m_lineInfo.setEmpty(false, &m_block, &m_width);
m_trailingObjects.clear();
m_lineInfo.setPreviousLineBrokeCleanly(true);
if (m_ignoringSpaces && m_currentStyle->clear() != CNONE)
m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(br);
else if (m_ignoringSpaces && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT))
m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
if (!m_lineInfo.isEmpty())
clear = m_currentStyle->clear();
}
m_atEnd = true;
}
inline LayoutUnit borderPaddingMarginStart(const RenderInline& child)
{
return child.marginStart() + child.paddingStart() + child.borderStart();
}
inline LayoutUnit borderPaddingMarginEnd(const RenderInline& child)
{
return child.marginEnd() + child.paddingEnd() + child.borderEnd();
}
inline bool shouldAddBorderPaddingMargin(RenderObject* child)
{
return !child || (child->isText() && !toRenderText(child)->textLength());
}
inline RenderObject* previousInFlowSibling(RenderObject* child)
{
child = child->previousSibling();
while (child && child->isOutOfFlowPositioned())
child = child->previousSibling();
return child;
}
inline LayoutUnit inlineLogicalWidth(RenderObject* child, bool checkStartEdge = true, bool checkEndEdge = true)
{
unsigned lineDepth = 1;
LayoutUnit extraWidth = 0;
RenderElement* parent = child->parent();
while (parent->isRenderInline() && lineDepth++ < cMaxLineDepth) {
const RenderInline& parentAsRenderInline = toRenderInline(*parent);
if (!isEmptyInline(parentAsRenderInline)) {
checkStartEdge = checkStartEdge && shouldAddBorderPaddingMargin(previousInFlowSibling(child));
if (checkStartEdge)
extraWidth += borderPaddingMarginStart(parentAsRenderInline);
checkEndEdge = checkEndEdge && shouldAddBorderPaddingMargin(child->nextSibling());
if (checkEndEdge)
extraWidth += borderPaddingMarginEnd(parentAsRenderInline);
if (!checkStartEdge && !checkEndEdge)
return extraWidth;
}
child = parent;
parent = child->parent();
}
return extraWidth;
}
inline void BreakingContext::handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects)
{
RenderBox* box = toRenderBox(m_current.renderer());
bool isInlineType = box->style().isOriginalDisplayInlineType();
if (!isInlineType)
m_block.setStaticInlinePositionForChild(*box, m_block.logicalHeight(), m_block.startOffsetForContent(m_block.logicalHeight()));
else {
box->layer()->setStaticBlockPosition(m_block.logicalHeight());
}
if (isInlineType || box->container()->isRenderInline()) {
if (m_ignoringSpaces)
m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(box);
m_trailingObjects.appendBoxIfNeeded(box);
} else
positionedObjects.append(box);
m_width.addUncommittedWidth(inlineLogicalWidth(box));
m_renderTextInfo.m_lineBreakIterator.resetPriorContext();
}
inline void BreakingContext::handleFloat()
{
RenderBox& floatBox = toRenderBox(*m_current.renderer());
FloatingObject* floatingObject = m_lineBreaker.insertFloatingObject(floatBox);
if (m_floatsFitOnLine && m_width.fitsOnLineExcludingTrailingWhitespace(m_block.logicalWidthForFloat(floatingObject))) {
m_lineBreaker.positionNewFloatOnLine(floatingObject, m_lastFloatFromPreviousLine, m_lineInfo, m_width);
if (m_lineBreak.renderer() == m_current.renderer()) {
ASSERT(!m_lineBreak.offset());
m_lineBreak.increment();
}
} else
m_floatsFitOnLine = false;
m_renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter);
}
inline bool shouldSkipWhitespaceAfterStartObject(RenderBlockFlow& block, RenderObject* o, LineMidpointState& lineMidpointState)
{
RenderObject* next = bidiNextSkippingEmptyInlines(block, o);
while (next && next->isFloatingOrOutOfFlowPositioned())
next = bidiNextSkippingEmptyInlines(block, next);
if (next && next->isText() && toRenderText(next)->textLength() > 0) {
RenderText* nextText = toRenderText(next);
UChar nextChar = nextText->characterAt(0);
if (nextText->style().isCollapsibleWhiteSpace(nextChar)) {
lineMidpointState.startIgnoringSpaces(InlineIterator(0, o, 0));
return true;
}
}
return false;
}
inline void BreakingContext::handleEmptyInline()
{
RenderInline& flowBox = toRenderInline(*m_current.renderer());
ASSERT(isEmptyInline(flowBox));
bool requiresLineBox = alwaysRequiresLineBox(flowBox);
if (requiresLineBox || requiresLineBoxForContent(flowBox, m_lineInfo)) {
if (requiresLineBox)
m_lineInfo.setEmpty(false, &m_block, &m_width);
if (m_ignoringSpaces) {
m_trailingObjects.clear();
m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(m_current.renderer());
} else if (m_blockStyle.collapseWhiteSpace() && m_resolver.position().renderer() == m_current.renderer()
&& shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
m_currentCharacterIsSpace = true;
m_currentCharacterIsWS = true;
m_ignoringSpaces = true;
} else
m_trailingObjects.appendBoxIfNeeded(&flowBox);
}
m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer()) + borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox));
}
inline void BreakingContext::handleReplaced()
{
RenderBox& replacedBox = toRenderBox(*m_current.renderer());
if (m_atStart)
m_width.updateAvailableWidth(replacedBox.logicalHeight());
if ((m_autoWrap || RenderStyle::autoWrap(m_lastWS)) && (!m_current.renderer()->isImage() || m_allowImagesToBreak)
&& (!m_current.renderer()->isRubyRun() || toRenderRubyRun(m_current.renderer())->canBreakBefore(m_renderTextInfo.m_lineBreakIterator))) {
m_width.commit();
m_lineBreak.moveToStartOf(m_current.renderer());
}
if (m_ignoringSpaces)
m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), 0));
m_lineInfo.setEmpty(false, &m_block, &m_width);
m_ignoringSpaces = false;
m_currentCharacterIsSpace = false;
m_currentCharacterIsWS = false;
m_trailingObjects.clear();
LayoutUnit replacedLogicalWidth = m_block.logicalWidthForChild(replacedBox) + m_block.marginStartForChild(replacedBox) + m_block.marginEndForChild(replacedBox) + inlineLogicalWidth(m_current.renderer());
if (m_current.renderer()->isListMarker()) {
if (m_blockStyle.collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
m_currentCharacterIsSpace = true;
m_currentCharacterIsWS = false;
m_ignoringSpaces = true;
}
if (toRenderListMarker(*m_current.renderer()).isInside())
m_width.addUncommittedWidth(replacedLogicalWidth);
} else
m_width.addUncommittedWidth(replacedLogicalWidth);
if (m_current.renderer()->isRubyRun()) {
m_width.applyOverhang(toRenderRubyRun(m_current.renderer()), m_lastObject, m_nextObject);
toRenderRubyRun(m_current.renderer())->updatePriorContextFromCachedBreakIterator(m_renderTextInfo.m_lineBreakIterator);
} else {
m_renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter);
}
}
inline float firstPositiveWidth(const WordMeasurements& wordMeasurements)
{
for (size_t i = 0; i < wordMeasurements.size(); ++i) {
if (wordMeasurements[i].width > 0)
return wordMeasurements[i].width;
}
return 0;
}
inline bool iteratorIsBeyondEndOfRenderCombineText(const InlineIterator& iter, RenderCombineText& renderer)
{
return iter.renderer() == &renderer && iter.offset() >= renderer.textLength();
}
inline void nextCharacter(UChar& currentCharacter, UChar& lastCharacter, UChar& secondToLastCharacter)
{
secondToLastCharacter = lastCharacter;
lastCharacter = currentCharacter;
}
inline void updateCounterIfNeeded(RenderText& renderText)
{
if (!renderText.preferredLogicalWidthsDirty() || !renderText.isCounter())
return;
toRenderCounter(renderText).updateCounter();
}
inline float measureHyphenWidth(RenderText* renderer, const Font& font, HashSet<const SimpleFontData*>* fallbackFonts = 0)
{
const RenderStyle& style = renderer->style();
return font.width(RenderBlock::constructTextRun(renderer, font, style.hyphenString().string(), style), fallbackFonts);
}
ALWAYS_INLINE float textWidth(RenderText* text, unsigned from, unsigned len, const Font& font, float xPos, bool isFixedPitch, bool collapseWhiteSpace, HashSet<const SimpleFontData*>& fallbackFonts, TextLayout* layout = 0)
{
const RenderStyle& style = text->style();
GlyphOverflow glyphOverflow;
if (isFixedPitch || (!from && len == text->textLength()) || style.hasTextCombine())
return text->width(from, len, font, xPos, &fallbackFonts, &glyphOverflow);
if (layout)
return Font::width(*layout, from, len, &fallbackFonts);
TextRun run = RenderBlock::constructTextRun(text, font, text, from, len, style);
run.setCharactersLength(text->textLength() - from);
ASSERT(run.charactersLength() >= run.length());
run.setCharacterScanForCodePath(!text->canUseSimpleFontCodePath());
run.setTabSize(!collapseWhiteSpace, style.tabSize());
run.setXPos(xPos);
return font.width(run, &fallbackFonts, &glyphOverflow);
}
inline void ensureCharacterGetsLineBox(LineMidpointState& lineMidpointState, InlineIterator& textParagraphSeparator)
{
InlineIterator midpoint(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset());
lineMidpointState.startIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset() - 1));
lineMidpointState.stopIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset()));
}
inline void tryHyphenating(RenderText* text, const Font& font, const AtomicString& localeIdentifier, unsigned consecutiveHyphenatedLines, int consecutiveHyphenatedLinesLimit, int minimumPrefixLimit, int minimumSuffixLimit, unsigned lastSpace, unsigned pos, float xPos, int availableWidth, bool isFixedPitch, bool collapseWhiteSpace, int lastSpaceWordSpacing, InlineIterator& lineBreak, int nextBreakable, bool& hyphenated)
{
unsigned minimumPrefixLength;
unsigned minimumSuffixLength;
if (minimumPrefixLimit < 0)
minimumPrefixLength = 2;
else
minimumPrefixLength = static_cast<unsigned>(minimumPrefixLimit);
if (minimumSuffixLimit < 0)
minimumSuffixLength = 2;
else
minimumSuffixLength = static_cast<unsigned>(minimumSuffixLimit);
if (pos - lastSpace <= minimumSuffixLength)
return;
if (consecutiveHyphenatedLinesLimit >= 0 && consecutiveHyphenatedLines >= static_cast<unsigned>(consecutiveHyphenatedLinesLimit))
return;
int hyphenWidth = measureHyphenWidth(text, font);
float maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing;
if (maxPrefixWidth <= font.pixelSize() * 5 / 4)
return;
const RenderStyle& style = text->style();
TextRun run = RenderBlock::constructTextRun(text, font, text, lastSpace, pos - lastSpace, style);
run.setCharactersLength(text->textLength() - lastSpace);
ASSERT(run.charactersLength() >= run.length());
run.setTabSize(!collapseWhiteSpace, style.tabSize());
run.setXPos(xPos + lastSpaceWordSpacing);
unsigned prefixLength = font.offsetForPosition(run, maxPrefixWidth, false);
if (prefixLength < minimumPrefixLength)
return;
prefixLength = lastHyphenLocation(StringView(text->text()).substring(lastSpace, pos - lastSpace), std::min(prefixLength, pos - lastSpace - minimumSuffixLength) + 1, localeIdentifier);
if (!prefixLength || prefixLength < minimumPrefixLength)
return;
if (prefixLength == minimumPrefixLength) {
UChar characterAtLastSpace = text->characterAt(lastSpace);
if (characterAtLastSpace == ' ' || characterAtLastSpace == '\n' || characterAtLastSpace == '\t' || characterAtLastSpace == noBreakSpace)
return;
}
ASSERT(pos - lastSpace - prefixLength >= minimumSuffixLength);
#if !ASSERT_DISABLED
HashSet<const SimpleFontData*> fallbackFonts;
float prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace, fallbackFonts) + lastSpaceWordSpacing;
ASSERT(xPos + prefixWidth <= availableWidth);
#else
UNUSED_PARAM(isFixedPitch);
#endif
lineBreak.moveTo(text, lastSpace + prefixLength, nextBreakable);
hyphenated = true;
}
inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated, unsigned& consecutiveHyphenatedLines)
{
if (!m_current.offset())
m_appliedStartWidth = false;
RenderText* renderText = toRenderText(m_current.renderer());
bool isSVGText = renderText->isSVGInlineText();
if (m_autoWrap && !RenderStyle::autoWrap(m_lastWS) && m_ignoringSpaces)
commitLineBreakAtCurrentWidth(m_current.renderer());
if (renderText->style().hasTextCombine() && m_current.renderer()->isCombineText() && !toRenderCombineText(*m_current.renderer()).isCombined()) {
RenderCombineText& combineRenderer = toRenderCombineText(*m_current.renderer());
combineRenderer.combineText();
if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreak, combineRenderer)) {
ASSERT(iteratorIsBeyondEndOfRenderCombineText(m_resolver.position(), combineRenderer));
m_lineBreak.increment();
m_resolver.increment();
}
}
const RenderStyle& style = lineStyle(*renderText->parent(), m_lineInfo);
const Font& font = style.font();
bool isFixedPitch = font.isFixedPitch();
bool canHyphenate = style.hyphens() == HyphensAuto && WebCore::canHyphenate(style.locale());
unsigned lastSpace = m_current.offset();
float wordSpacing = m_currentStyle->font().wordSpacing();
float lastSpaceWordSpacing = 0;
float wordSpacingForWordMeasurement = 0;
float wrapW = m_width.uncommittedWidth() + inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, true);
float charWidth = 0;
bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE;
bool breakWords = m_currentStyle->breakWords() && ((m_autoWrap && !m_width.committedWidth()) || m_currWS == PRE);
bool midWordBreak = false;
bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap;
float hyphenWidth = 0;
bool isLooseCJKMode = false;
if (isSVGText) {
breakWords = false;
breakAll = false;
}
if (m_renderTextInfo.m_text != renderText) {
updateCounterIfNeeded(*renderText);
m_renderTextInfo.m_text = renderText;
m_renderTextInfo.m_font = &font;
m_renderTextInfo.m_layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
m_renderTextInfo.m_lineBreakIterator.resetStringAndReleaseIterator(renderText->text(), style.locale(), mapLineBreakToIteratorMode(m_blockStyle.lineBreak()));
isLooseCJKMode = m_renderTextInfo.m_lineBreakIterator.isLooseCJKMode();
} else if (m_renderTextInfo.m_layout && m_renderTextInfo.m_font != &font) {
m_renderTextInfo.m_font = &font;
m_renderTextInfo.m_layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
}
TextLayout* textLayout = m_renderTextInfo.m_layout.get();
HashSet<const SimpleFontData*> fallbackFonts;
float wordTrailingSpaceWidth = (font.typesettingFeatures() & Kerning) && !textLayout ? font.width(RenderBlock::constructTextRun(renderText, font, &space, 1, style), &fallbackFonts) + wordSpacing : 0;
UChar lastCharacter = m_renderTextInfo.m_lineBreakIterator.lastCharacter();
UChar secondToLastCharacter = m_renderTextInfo.m_lineBreakIterator.secondToLastCharacter();
for (; m_current.offset() < renderText->textLength(); m_current.fastIncrementInTextNode()) {
bool previousCharacterIsSpace = m_currentCharacterIsSpace;
bool previousCharacterIsWS = m_currentCharacterIsWS;
UChar c = m_current.current();
m_currentCharacterIsSpace = c == ' ' || c == '\t' || (!m_preservesNewline && (c == '\n'));
if (!m_collapseWhiteSpace || !m_currentCharacterIsSpace)
m_lineInfo.setEmpty(false, &m_block, &m_width);
if (c == softHyphen && m_autoWrap && !hyphenWidth && style.hyphens() != HyphensNone) {
hyphenWidth = measureHyphenWidth(renderText, font, &fallbackFonts);
m_width.addUncommittedWidth(hyphenWidth);
}
bool applyWordSpacing = false;
m_currentCharacterIsWS = m_currentCharacterIsSpace || (breakNBSP && c == noBreakSpace);
if ((breakAll || breakWords) && !midWordBreak && (!m_currentCharacterIsSpace || style.whiteSpace() != PRE_WRAP)) {
wrapW += charWidth;
bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText->textLength() && U16_IS_TRAIL((*renderText)[m_current.offset() + 1]);
charWidth = textWidth(renderText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, font, m_width.committedWidth() + wrapW, isFixedPitch, m_collapseWhiteSpace, fallbackFonts, textLayout);
midWordBreak = m_width.committedWidth() + wrapW + charWidth > m_width.availableWidth();
}
int nextBreakablePosition = m_current.nextBreakablePosition();
bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.m_lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP, isLooseCJKMode)
&& (style.hyphens() != HyphensNone || (m_current.previousInSameNode() != softHyphen)));
m_current.setNextBreakablePosition(nextBreakablePosition);
if (betweenWords || midWordBreak) {
bool stoppedIgnoringSpaces = false;
if (m_ignoringSpaces) {
lastSpaceWordSpacing = 0;
if (!m_currentCharacterIsSpace) {
m_ignoringSpaces = false;
wordSpacingForWordMeasurement = 0;
lastSpace = m_current.offset(); m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
stoppedIgnoringSpaces = true;
} else {
nextCharacter(c, lastCharacter, secondToLastCharacter);
continue;
}
}
wordMeasurements.grow(wordMeasurements.size() + 1);
WordMeasurement& wordMeasurement = wordMeasurements.last();
wordMeasurement.renderer = renderText;
wordMeasurement.endOffset = m_current.offset();
wordMeasurement.startOffset = lastSpace;
float additionalTempWidth;
if (wordTrailingSpaceWidth && c == ' ')
additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() + 1 - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) - wordTrailingSpaceWidth;
else
additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
wordMeasurement.fallbackFonts.swap(fallbackFonts);
fallbackFonts.clear();
wordMeasurement.width = additionalTempWidth + wordSpacingForWordMeasurement;
additionalTempWidth += lastSpaceWordSpacing;
m_width.addUncommittedWidth(additionalTempWidth);
if (m_collapseWhiteSpace && previousCharacterIsSpace && m_currentCharacterIsSpace && additionalTempWidth)
m_width.setTrailingWhitespaceWidth(additionalTempWidth);
if (!m_appliedStartWidth) {
m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer(), true, false));
m_appliedStartWidth = true;
}
applyWordSpacing = wordSpacing && m_currentCharacterIsSpace;
if (!m_width.committedWidth() && m_autoWrap && !m_width.fitsOnLine())
m_width.fitBelowFloats(m_lineInfo.isFirstLine());
if (m_autoWrap || breakWords) {
bool lineWasTooWide = false;
if (m_width.fitsOnLine() && m_currentCharacterIsWS && m_currentStyle->breakOnlyAfterWhiteSpace() && !midWordBreak) {
float charWidth = textWidth(renderText, m_current.offset(), 1, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) + (applyWordSpacing ? wordSpacing : 0);
if (!m_width.fitsOnLineIncludingExtraWidth(charWidth)) {
lineWasTooWide = true;
m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
m_lineBreaker.skipTrailingWhitespace(m_lineBreak, m_lineInfo);
}
}
if (lineWasTooWide || !m_width.fitsOnLine()) {
if (canHyphenate && !m_width.fitsOnLine()) {
tryHyphenating(renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, m_lineBreak, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
if (m_lineBreaker.m_hyphenated) {
m_atEnd = true;
return false;
}
}
if (m_lineBreak.atTextParagraphSeparator()) {
if (!stoppedIgnoringSpaces && m_current.offset() > 0)
ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
m_lineBreak.increment();
m_lineInfo.setPreviousLineBrokeCleanly(true);
wordMeasurement.endOffset = m_lineBreak.offset();
}
if (m_lineBreak.renderer() && m_lineBreak.offset() && m_lineBreak.renderer()->isText() && toRenderText(m_lineBreak.renderer())->textLength() && toRenderText(m_lineBreak.renderer())->characterAt(m_lineBreak.offset() - 1) == softHyphen && style.hyphens() != HyphensNone)
hyphenated = true;
if (m_lineBreak.offset() && m_lineBreak.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) {
if (charWidth) {
wordMeasurement.endOffset = m_lineBreak.offset();
wordMeasurement.width = charWidth;
}
}
if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) {
m_atEnd = true;
return false;
}
} else {
if (!betweenWords || (midWordBreak && !m_autoWrap))
m_width.addUncommittedWidth(-additionalTempWidth);
if (hyphenWidth) {
m_width.addUncommittedWidth(-hyphenWidth);
hyphenWidth = 0;
}
}
}
if (c == '\n' && m_preservesNewline) {
if (!stoppedIgnoringSpaces && m_current.offset())
ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
m_lineBreak.increment();
m_lineInfo.setPreviousLineBrokeCleanly(true);
return true;
}
if (m_autoWrap && betweenWords) {
m_width.commit();
wrapW = 0;
m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
breakWords = false;
}
if (midWordBreak && !U16_IS_TRAIL(c) && !(U_GET_GC_MASK(c) & U_GC_M_MASK)) {
m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
midWordBreak &= (breakWords || breakAll);
}
if (betweenWords) {
lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0;
lastSpace = m_current.offset();
}
if (!m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) {
if (m_currentCharacterIsSpace && previousCharacterIsSpace) {
m_ignoringSpaces = true;
m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, InlineIterator(), TrailingObjects::DoNotCollapseFirstSpace);
}
}
} else if (m_ignoringSpaces) {
m_ignoringSpaces = false;
lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurements.last().width) ? wordSpacing : 0;
lastSpace = m_current.offset(); m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
}
if (isSVGText && m_current.offset()) {
if (toRenderSVGInlineText(renderText)->characterStartsNewTextChunk(m_current.offset()))
ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
}
if (m_currentCharacterIsSpace && !previousCharacterIsSpace) {
m_startOfIgnoredSpaces.setRenderer(m_current.renderer());
m_startOfIgnoredSpaces.setOffset(m_current.offset());
if (m_nextObject && m_startOfIgnoredSpaces.offset() && m_nextObject->isBR() && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) {
m_startOfIgnoredSpaces.setOffset(m_startOfIgnoredSpaces.offset() - 1);
if (m_current.offset() == renderText->textLength() - 1)
m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
}
}
if (!m_currentCharacterIsWS && previousCharacterIsWS) {
if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace())
m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
}
if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces)
m_trailingObjects.setTrailingWhitespace(toRenderText(m_current.renderer()));
else if (!m_currentStyle->collapseWhiteSpace() || !m_currentCharacterIsSpace)
m_trailingObjects.clear();
m_atStart = false;
nextCharacter(c, lastCharacter, secondToLastCharacter);
}
m_renderTextInfo.m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
wordMeasurements.grow(wordMeasurements.size() + 1);
WordMeasurement& wordMeasurement = wordMeasurements.last();
wordMeasurement.renderer = renderText;
float additionalTempWidth = m_ignoringSpaces ? 0 : textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
wordMeasurement.startOffset = lastSpace;
wordMeasurement.endOffset = m_current.offset();
wordMeasurement.width = m_ignoringSpaces ? 0 : additionalTempWidth + wordSpacingForWordMeasurement;
additionalTempWidth += lastSpaceWordSpacing;
float inlineLogicalTempWidth = inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, m_includeEndWidth);
m_width.addUncommittedWidth(additionalTempWidth + inlineLogicalTempWidth);
if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
wordMeasurement.fallbackFonts.swap(fallbackFonts);
fallbackFonts.clear();
if (m_collapseWhiteSpace && m_currentCharacterIsSpace && additionalTempWidth)
m_width.setTrailingWhitespaceWidth(additionalTempWidth, inlineLogicalTempWidth);
m_includeEndWidth = false;
if (!m_width.fitsOnLine()) {
if (canHyphenate)
tryHyphenating(renderText, font, style.locale(), consecutiveHyphenatedLines, m_blockStyle.hyphenationLimitLines(), style.hyphenationLimitBefore(), style.hyphenationLimitAfter(), lastSpace, m_current.offset(), m_width.currentWidth() - additionalTempWidth, m_width.availableWidth(), isFixedPitch, m_collapseWhiteSpace, lastSpaceWordSpacing, m_lineBreak, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
if (!hyphenated && m_lineBreak.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) {
hyphenated = true;
m_atEnd = true;
}
}
return false;
}
inline bool textBeginsWithBreakablePosition(RenderObject* next)
{
ASSERT(next->isText());
RenderText* nextText = toRenderText(next);
if (!nextText->textLength())
return false;
UChar c = nextText->characterAt(0);
return c == ' ' || c == '\t' || (c == '\n' && !nextText->preservesNewline());
}
inline bool BreakingContext::canBreakAtThisPosition()
{
if (m_width.committedWidth() && !m_width.fitsOnLine(m_currentCharacterIsSpace) && m_currWS == NOWRAP)
return true;
if (m_current.renderer()->isRenderInline() && isEmptyInline(toRenderInline(*m_current.renderer())))
return false;
if (m_nextObject && m_nextObject->isRenderInline() && isEmptyInline(toRenderInline(*m_nextObject)))
return false;
if (m_autoWrap && m_currentCharacterIsSpace)
return true;
if (m_nextObject && m_nextObject->isLineBreakOpportunity())
return m_autoWrap;
bool nextIsAutoWrappingText = (m_nextObject && m_nextObject->isText() && (m_autoWrap || m_nextObject->style().autoWrap()));
if (!nextIsAutoWrappingText)
return m_autoWrap;
bool currentIsTextOrEmptyInline = m_current.renderer()->isText() || (m_current.renderer()->isRenderInline() && isEmptyInline(toRenderInline(*m_current.renderer())));
if (!currentIsTextOrEmptyInline)
return m_autoWrap && !m_current.renderer()->isRubyRun();
bool canBreakHere = !m_currentCharacterIsSpace && textBeginsWithBreakablePosition(m_nextObject);
if (!m_width.fitsOnLine() && !m_width.committedWidth())
m_width.fitBelowFloats(m_lineInfo.isFirstLine());
bool canPlaceOnLine = m_width.fitsOnLine() || !m_autoWrapWasEverTrueOnLine;
if (canPlaceOnLine && canBreakHere)
commitLineBreakAtCurrentWidth(m_nextObject);
return canBreakHere;
}
inline void BreakingContext::commitAndUpdateLineBreakIfNeeded()
{
bool checkForBreak = canBreakAtThisPosition();
if (checkForBreak && !m_width.fitsOnLine(m_ignoringSpaces)) {
if (m_currentCharacterIsSpace && !m_ignoringSpaces && m_currentStyle->collapseWhiteSpace())
m_trailingObjects.clear();
if (m_width.committedWidth()) {
m_atEnd = true;
return;
}
m_width.fitBelowFloats(m_lineInfo.isFirstLine());
if (!m_width.fitsOnLine(m_ignoringSpaces)) {
m_atEnd = true;
return;
}
} else if (m_blockStyle.autoWrap() && !m_width.fitsOnLine() && !m_width.committedWidth()) {
m_width.fitBelowFloats(m_lineInfo.isFirstLine());
}
if (!m_current.renderer()->isFloatingOrOutOfFlowPositioned()) {
m_lastObject = m_current.renderer();
if (m_lastObject->isReplaced() && m_autoWrap && !m_lastObject->isRubyRun() && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!m_lastObject->isListMarker() || toRenderListMarker(*m_lastObject).isInside())) {
m_width.commit();
m_lineBreak.moveToStartOf(m_nextObject);
}
}
}
inline TrailingObjects::CollapseFirstSpaceOrNot checkMidpoints(LineMidpointState& lineMidpointState, InlineIterator& lBreak)
{
if (lBreak.renderer() && lineMidpointState.numMidpoints() && !(lineMidpointState.numMidpoints() % 2)) {
InlineIterator* midpoints = lineMidpointState.midpoints().data();
InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints() - 2];
const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints() - 1];
InlineIterator currpoint = endpoint;
while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak)
currpoint.increment();
if (currpoint == lBreak) {
lineMidpointState.decreaseNumMidpoints();
if (endpoint.renderer()->style().collapseWhiteSpace() && endpoint.renderer()->isText()) {
endpoint.fastDecrement();
return TrailingObjects::DoNotCollapseFirstSpace;
}
}
}
return TrailingObjects::CollapseFirstSpace;
}
inline InlineIterator BreakingContext::handleEndOfLine()
{
if (m_lineBreak == m_resolver.position()) {
if (!m_lineBreak.renderer() || !m_lineBreak.renderer()->isBR()) {
if (m_blockStyle.whiteSpace() == PRE && !m_current.offset()) {
m_lineBreak.moveTo(m_lastObject, m_lastObject->isText() ? m_lastObject->length() : 0);
} else if (m_lineBreak.renderer()) {
m_lineBreak.moveTo(m_current.renderer(), m_current.offset());
}
}
if (m_lineBreak == m_resolver.position())
m_lineBreak.increment();
} else if (!m_current.offset() && !m_width.committedWidth() && m_width.uncommittedWidth() && !m_hadUncommittedWidthBeforeCurrent) {
m_lineBreak.increment();
}
TrailingObjects::CollapseFirstSpaceOrNot collapsed = checkMidpoints(m_lineMidpointState, m_lineBreak);
m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, m_lineBreak, collapsed);
if (m_lineBreak.offset()) {
m_lineBreak.setOffset(m_lineBreak.offset() - 1);
m_lineBreak.increment();
}
return m_lineBreak;
}
}
#endif // BreakingContextInlineHeaders_h