1/*
2 * Copyright (C) 2000 Lars Knoll (knoll@kde.org)
3 * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All right reserved.
4 * Copyright (C) 2010 Google Inc. All rights reserved.
5 * Copyright (C) 2013 ChangSeok Oh <shivamidow@gmail.com>
6 * Copyright (C) 2013 Adobe Systems Inc. All right reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB.  If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 */
24
25#ifndef BreakingContextInlineHeaders_h
26#define BreakingContextInlineHeaders_h
27
28#include "Hyphenation.h"
29#include "LineBreaker.h"
30#include "LineInfo.h"
31#include "LineWidth.h"
32#include "RenderCombineText.h"
33#include "RenderCounter.h"
34#include "RenderInline.h"
35#include "RenderListMarker.h"
36#include "RenderRubyRun.h"
37#include "RenderSVGInlineText.h"
38#include "TrailingObjects.h"
39#include "break_lines.h"
40#include <wtf/text/StringView.h>
41#include <wtf/unicode/CharacterNames.h>
42
43namespace WebCore {
44
45// We don't let our line box tree for a single line get any deeper than this.
46const unsigned cMaxLineDepth = 200;
47
48struct WordMeasurement {
49    WordMeasurement()
50        : renderer(0)
51        , width(0)
52        , startOffset(0)
53        , endOffset(0)
54    {
55    }
56
57    RenderText* renderer;
58    float width;
59    int startOffset;
60    int endOffset;
61    HashSet<const SimpleFontData*> fallbackFonts;
62};
63
64class BreakingContext {
65public:
66    BreakingContext(LineBreaker& lineBreaker, InlineBidiResolver& resolver, LineInfo& inLineInfo, LineWidth& lineWidth, RenderTextInfo& inRenderTextInfo, FloatingObject* inLastFloatFromPreviousLine, bool appliedStartWidth, RenderBlockFlow& block)
67        : m_lineBreaker(lineBreaker)
68        , m_resolver(resolver)
69        , m_current(resolver.position())
70        , m_lineBreak(resolver.position())
71        , m_block(block)
72        , m_lastObject(m_current.renderer())
73        , m_nextObject(0)
74        , m_currentStyle(0)
75        , m_blockStyle(block.style())
76        , m_lineInfo(inLineInfo)
77        , m_renderTextInfo(inRenderTextInfo)
78        , m_lastFloatFromPreviousLine(inLastFloatFromPreviousLine)
79        , m_width(lineWidth)
80        , m_currWS(NORMAL)
81        , m_lastWS(NORMAL)
82        , m_preservesNewline(false)
83        , m_atStart(true)
84        , m_ignoringSpaces(false)
85        , m_currentCharacterIsSpace(false)
86        , m_currentCharacterIsWS(false)
87        , m_appliedStartWidth(appliedStartWidth)
88        , m_includeEndWidth(true)
89        , m_autoWrap(false)
90        , m_autoWrapWasEverTrueOnLine(false)
91        , m_floatsFitOnLine(true)
92        , m_collapseWhiteSpace(false)
93        , m_startingNewParagraph(m_lineInfo.previousLineBrokeCleanly())
94        , m_allowImagesToBreak(!block.document().inQuirksMode() || !block.isTableCell() || !m_blockStyle.logicalWidth().isIntrinsicOrAuto())
95        , m_atEnd(false)
96        , m_hadUncommittedWidthBeforeCurrent(false)
97        , m_lineMidpointState(resolver.midpointState())
98    {
99        m_lineInfo.setPreviousLineBrokeCleanly(false);
100    }
101
102    RenderObject* currentObject() { return m_current.renderer(); }
103    InlineIterator lineBreak() { return m_lineBreak; }
104    InlineIterator& lineBreakRef() {return m_lineBreak; }
105    LineWidth& lineWidth() { return m_width; }
106    bool atEnd() { return m_atEnd; }
107
108    void initializeForCurrentObject();
109
110    void increment();
111
112    void handleBR(EClear&);
113    void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects);
114    void handleFloat();
115    void handleEmptyInline();
116    void handleReplaced();
117    bool handleText(WordMeasurements&, bool& hyphenated, unsigned& consecutiveHyphenatedLines);
118    bool canBreakAtThisPosition();
119    void commitAndUpdateLineBreakIfNeeded();
120    InlineIterator handleEndOfLine();
121
122    void clearLineBreakIfFitsOnLine(bool ignoringTrailingSpace = false)
123    {
124        if (m_width.fitsOnLine(ignoringTrailingSpace) || m_lastWS == NOWRAP)
125            m_lineBreak.clear();
126    }
127
128    void commitLineBreakAtCurrentWidth(RenderObject* object, unsigned offset = 0, int nextBreak = -1)
129    {
130        m_width.commit();
131        m_lineBreak.moveTo(object, offset, nextBreak);
132    }
133
134private:
135    LineBreaker& m_lineBreaker;
136    InlineBidiResolver& m_resolver;
137
138    InlineIterator m_current;
139    InlineIterator m_lineBreak;
140    InlineIterator m_startOfIgnoredSpaces;
141
142    RenderBlockFlow& m_block;
143    RenderObject* m_lastObject;
144    RenderObject* m_nextObject;
145
146    RenderStyle* m_currentStyle;
147
148    // Firefox and Opera will allow a table cell to grow to fit an image inside it under
149    // very specific circumstances (in order to match common WinIE renderings).
150    // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.)
151    RenderStyle& m_blockStyle;
152
153    LineInfo& m_lineInfo;
154
155    RenderTextInfo& m_renderTextInfo;
156
157    FloatingObject* m_lastFloatFromPreviousLine;
158
159    LineWidth m_width;
160
161    EWhiteSpace m_currWS;
162    EWhiteSpace m_lastWS;
163
164    bool m_preservesNewline;
165    bool m_atStart;
166
167    // This variable is used only if whitespace isn't set to PRE, and it tells us whether
168    // or not we are currently ignoring whitespace.
169    bool m_ignoringSpaces;
170
171    // This variable tracks whether the very last character we saw was a space. We use
172    // this to detect when we encounter a second space so we know we have to terminate
173    // a run.
174    bool m_currentCharacterIsSpace;
175    bool m_currentCharacterIsWS;
176    bool m_appliedStartWidth;
177    bool m_includeEndWidth;
178    bool m_autoWrap;
179    bool m_autoWrapWasEverTrueOnLine;
180    bool m_floatsFitOnLine;
181    bool m_collapseWhiteSpace;
182    bool m_startingNewParagraph;
183    bool m_allowImagesToBreak;
184    bool m_atEnd;
185    bool m_hadUncommittedWidthBeforeCurrent;
186
187    LineMidpointState& m_lineMidpointState;
188
189    TrailingObjects m_trailingObjects;
190};
191
192inline void BreakingContext::initializeForCurrentObject()
193{
194    m_hadUncommittedWidthBeforeCurrent = !!m_width.uncommittedWidth();
195
196    m_currentStyle = &m_current.renderer()->style();
197
198    ASSERT(m_currentStyle);
199
200    m_nextObject = bidiNextSkippingEmptyInlines(m_block, m_current.renderer());
201    if (m_nextObject && m_nextObject->parent() && !m_nextObject->parent()->isDescendantOf(m_current.renderer()->parent()))
202        m_includeEndWidth = true;
203
204    m_currWS = m_current.renderer()->isReplaced() ? m_current.renderer()->parent()->style().whiteSpace() : m_currentStyle->whiteSpace();
205    m_lastWS = m_lastObject->isReplaced() ? m_lastObject->parent()->style().whiteSpace() : m_lastObject->style().whiteSpace();
206
207    m_autoWrap = RenderStyle::autoWrap(m_currWS);
208    m_autoWrapWasEverTrueOnLine = m_autoWrapWasEverTrueOnLine || m_autoWrap;
209
210    m_preservesNewline = m_current.renderer()->isSVGInlineText() ? false : RenderStyle::preserveNewline(m_currWS);
211
212    m_collapseWhiteSpace = RenderStyle::collapseWhiteSpace(m_currWS);
213}
214
215inline void BreakingContext::increment()
216{
217    // Clear out our character space bool, since inline <pre>s don't collapse whitespace
218    // with adjacent inline normal/nowrap spans.
219    if (!m_collapseWhiteSpace)
220        m_currentCharacterIsSpace = false;
221
222    m_current.moveToStartOf(m_nextObject);
223    m_atStart = false;
224}
225
226inline void BreakingContext::handleBR(EClear& clear)
227{
228    if (m_width.fitsOnLine()) {
229        RenderObject* br = m_current.renderer();
230        m_lineBreak.moveToStartOf(br);
231        m_lineBreak.increment();
232
233        // A <br> always breaks a line, so don't let the line be collapsed
234        // away. Also, the space at the end of a line with a <br> does not
235        // get collapsed away. It only does this if the previous line broke
236        // cleanly. Otherwise the <br> has no effect on whether the line is
237        // empty or not.
238        if (m_startingNewParagraph)
239            m_lineInfo.setEmpty(false, &m_block, &m_width);
240        m_trailingObjects.clear();
241        m_lineInfo.setPreviousLineBrokeCleanly(true);
242
243        // A <br> with clearance always needs a linebox in case the lines below it get dirtied later and
244        // need to check for floats to clear - so if we're ignoring spaces, stop ignoring them and add a
245        // run for this object.
246        if (m_ignoringSpaces && m_currentStyle->clear() != CNONE)
247            m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(br);
248        // If we were preceded by collapsing space and are in a right-aligned container we need to ensure the space gets
249        // collapsed away so that it doesn't push the text out from the container's right-hand edge.
250        // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results.
251        else if (m_ignoringSpaces && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT))
252            m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
253
254        if (!m_lineInfo.isEmpty())
255            clear = m_currentStyle->clear();
256    }
257    m_atEnd = true;
258}
259
260inline LayoutUnit borderPaddingMarginStart(const RenderInline& child)
261{
262    return child.marginStart() + child.paddingStart() + child.borderStart();
263}
264
265inline LayoutUnit borderPaddingMarginEnd(const RenderInline& child)
266{
267    return child.marginEnd() + child.paddingEnd() + child.borderEnd();
268}
269
270inline bool shouldAddBorderPaddingMargin(RenderObject* child)
271{
272    // When deciding whether we're at the edge of an inline, adjacent collapsed whitespace is the same as no sibling at all.
273    return !child || (child->isText() && !toRenderText(child)->textLength());
274}
275
276inline RenderObject* previousInFlowSibling(RenderObject* child)
277{
278    child = child->previousSibling();
279    while (child && child->isOutOfFlowPositioned())
280        child = child->previousSibling();
281    return child;
282}
283
284inline LayoutUnit inlineLogicalWidth(RenderObject* child, bool checkStartEdge = true, bool checkEndEdge = true)
285{
286    unsigned lineDepth = 1;
287    LayoutUnit extraWidth = 0;
288    RenderElement* parent = child->parent();
289    while (parent->isRenderInline() && lineDepth++ < cMaxLineDepth) {
290        const RenderInline& parentAsRenderInline = toRenderInline(*parent);
291        if (!isEmptyInline(parentAsRenderInline)) {
292            checkStartEdge = checkStartEdge && shouldAddBorderPaddingMargin(previousInFlowSibling(child));
293            if (checkStartEdge)
294                extraWidth += borderPaddingMarginStart(parentAsRenderInline);
295            checkEndEdge = checkEndEdge && shouldAddBorderPaddingMargin(child->nextSibling());
296            if (checkEndEdge)
297                extraWidth += borderPaddingMarginEnd(parentAsRenderInline);
298            if (!checkStartEdge && !checkEndEdge)
299                return extraWidth;
300        }
301        child = parent;
302        parent = child->parent();
303    }
304    return extraWidth;
305}
306
307inline void BreakingContext::handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects)
308{
309    // If our original display wasn't an inline type, then we can
310    // go ahead and determine our static inline position now.
311    RenderBox* box = toRenderBox(m_current.renderer());
312    bool isInlineType = box->style().isOriginalDisplayInlineType();
313    if (!isInlineType)
314        m_block.setStaticInlinePositionForChild(*box, m_block.logicalHeight(), m_block.startOffsetForContent(m_block.logicalHeight()));
315    else {
316        // If our original display was an INLINE type, then we can go ahead
317        // and determine our static y position now.
318        box->layer()->setStaticBlockPosition(m_block.logicalHeight());
319    }
320
321    // If we're ignoring spaces, we have to stop and include this object and
322    // then start ignoring spaces again.
323    if (isInlineType || box->container()->isRenderInline()) {
324        if (m_ignoringSpaces)
325            m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(box);
326        m_trailingObjects.appendBoxIfNeeded(box);
327    } else
328        positionedObjects.append(box);
329
330    m_width.addUncommittedWidth(inlineLogicalWidth(box));
331    // Reset prior line break context characters.
332    m_renderTextInfo.m_lineBreakIterator.resetPriorContext();
333}
334
335inline void BreakingContext::handleFloat()
336{
337    RenderBox& floatBox = toRenderBox(*m_current.renderer());
338    FloatingObject* floatingObject = m_lineBreaker.insertFloatingObject(floatBox);
339    // check if it fits in the current line.
340    // If it does, position it now, otherwise, position
341    // it after moving to next line (in clearFloats() func)
342    if (m_floatsFitOnLine && m_width.fitsOnLineExcludingTrailingWhitespace(m_block.logicalWidthForFloat(floatingObject))) {
343        m_lineBreaker.positionNewFloatOnLine(floatingObject, m_lastFloatFromPreviousLine, m_lineInfo, m_width);
344        if (m_lineBreak.renderer() == m_current.renderer()) {
345            ASSERT(!m_lineBreak.offset());
346            m_lineBreak.increment();
347        }
348    } else
349        m_floatsFitOnLine = false;
350    // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for floating element.
351    m_renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter);
352}
353
354// This is currently just used for list markers and inline flows that have line boxes. Neither should
355// have an effect on whitespace at the start of the line.
356inline bool shouldSkipWhitespaceAfterStartObject(RenderBlockFlow& block, RenderObject* o, LineMidpointState& lineMidpointState)
357{
358    RenderObject* next = bidiNextSkippingEmptyInlines(block, o);
359    while (next && next->isFloatingOrOutOfFlowPositioned())
360        next = bidiNextSkippingEmptyInlines(block, next);
361
362    if (next && next->isText() && toRenderText(next)->textLength() > 0) {
363        RenderText* nextText = toRenderText(next);
364        UChar nextChar = nextText->characterAt(0);
365        if (nextText->style().isCollapsibleWhiteSpace(nextChar)) {
366            lineMidpointState.startIgnoringSpaces(InlineIterator(0, o, 0));
367            return true;
368        }
369    }
370
371    return false;
372}
373
374inline void BreakingContext::handleEmptyInline()
375{
376    RenderInline& flowBox = toRenderInline(*m_current.renderer());
377
378    // This should only end up being called on empty inlines
379    ASSERT(isEmptyInline(flowBox));
380
381    // Now that some inline flows have line boxes, if we are already ignoring spaces, we need
382    // to make sure that we stop to include this object and then start ignoring spaces again.
383    // If this object is at the start of the line, we need to behave like list markers and
384    // start ignoring spaces.
385    bool requiresLineBox = alwaysRequiresLineBox(flowBox);
386    if (requiresLineBox || requiresLineBoxForContent(flowBox, m_lineInfo)) {
387        // An empty inline that only has line-height, vertical-align or font-metrics will only get a
388        // line box to affect the height of the line if the rest of the line is not empty.
389        if (requiresLineBox)
390            m_lineInfo.setEmpty(false, &m_block, &m_width);
391        if (m_ignoringSpaces) {
392            m_trailingObjects.clear();
393            m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(m_current.renderer());
394        } else if (m_blockStyle.collapseWhiteSpace() && m_resolver.position().renderer() == m_current.renderer()
395            && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
396            // Like with list markers, we start ignoring spaces to make sure that any
397            // additional spaces we see will be discarded.
398            m_currentCharacterIsSpace = true;
399            m_currentCharacterIsWS = true;
400            m_ignoringSpaces = true;
401        } else
402            m_trailingObjects.appendBoxIfNeeded(&flowBox);
403    }
404
405    m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer()) + borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox));
406}
407
408inline void BreakingContext::handleReplaced()
409{
410    RenderBox& replacedBox = toRenderBox(*m_current.renderer());
411
412    if (m_atStart)
413        m_width.updateAvailableWidth(replacedBox.logicalHeight());
414
415    // Break on replaced elements if either has normal white-space.
416    if ((m_autoWrap || RenderStyle::autoWrap(m_lastWS)) && (!m_current.renderer()->isImage() || m_allowImagesToBreak)) {
417        m_width.commit();
418        m_lineBreak.moveToStartOf(m_current.renderer());
419    }
420
421    if (m_ignoringSpaces)
422        m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), 0));
423
424    m_lineInfo.setEmpty(false, &m_block, &m_width);
425    m_ignoringSpaces = false;
426    m_currentCharacterIsSpace = false;
427    m_currentCharacterIsWS = false;
428    m_trailingObjects.clear();
429
430    // Optimize for a common case. If we can't find whitespace after the list
431    // item, then this is all moot.
432    LayoutUnit replacedLogicalWidth = m_block.logicalWidthForChild(replacedBox) + m_block.marginStartForChild(replacedBox) + m_block.marginEndForChild(replacedBox) + inlineLogicalWidth(m_current.renderer());
433    if (m_current.renderer()->isListMarker()) {
434        if (m_blockStyle.collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
435            // Like with inline flows, we start ignoring spaces to make sure that any
436            // additional spaces we see will be discarded.
437            m_currentCharacterIsSpace = true;
438            m_currentCharacterIsWS = false;
439            m_ignoringSpaces = true;
440        }
441        if (toRenderListMarker(*m_current.renderer()).isInside())
442            m_width.addUncommittedWidth(replacedLogicalWidth);
443    } else
444        m_width.addUncommittedWidth(replacedLogicalWidth);
445    if (m_current.renderer()->isRubyRun())
446        m_width.applyOverhang(toRenderRubyRun(m_current.renderer()), m_lastObject, m_nextObject);
447    // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for replaced element.
448    m_renderTextInfo.m_lineBreakIterator.updatePriorContext(replacementCharacter);
449}
450
451inline float firstPositiveWidth(const WordMeasurements& wordMeasurements)
452{
453    for (size_t i = 0; i < wordMeasurements.size(); ++i) {
454        if (wordMeasurements[i].width > 0)
455            return wordMeasurements[i].width;
456    }
457    return 0;
458}
459
460inline bool iteratorIsBeyondEndOfRenderCombineText(const InlineIterator& iter, RenderCombineText& renderer)
461{
462    return iter.renderer() == &renderer && iter.offset() >= renderer.textLength();
463}
464
465inline void nextCharacter(UChar& currentCharacter, UChar& lastCharacter, UChar& secondToLastCharacter)
466{
467    secondToLastCharacter = lastCharacter;
468    lastCharacter = currentCharacter;
469}
470
471// FIXME: Don't let counters mark themselves as needing pref width recalcs during layout
472// so we don't need this hack.
473inline void updateCounterIfNeeded(RenderText& renderText)
474{
475    if (!renderText.preferredLogicalWidthsDirty() || !renderText.isCounter())
476        return;
477    toRenderCounter(renderText).updateCounter();
478}
479
480inline float measureHyphenWidth(RenderText* renderer, const Font& font, HashSet<const SimpleFontData*>* fallbackFonts = 0)
481{
482    const RenderStyle& style = renderer->style();
483    return font.width(RenderBlock::constructTextRun(renderer, font, style.hyphenString().string(), style), fallbackFonts);
484}
485
486ALWAYS_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)
487{
488    const RenderStyle& style = text->style();
489
490    GlyphOverflow glyphOverflow;
491    if (isFixedPitch || (!from && len == text->textLength()) || style.hasTextCombine())
492        return text->width(from, len, font, xPos, &fallbackFonts, &glyphOverflow);
493
494    if (layout)
495        return Font::width(*layout, from, len, &fallbackFonts);
496
497    TextRun run = RenderBlock::constructTextRun(text, font, text, from, len, style);
498    run.setCharactersLength(text->textLength() - from);
499    ASSERT(run.charactersLength() >= run.length());
500
501    run.setCharacterScanForCodePath(!text->canUseSimpleFontCodePath());
502    run.setTabSize(!collapseWhiteSpace, style.tabSize());
503    run.setXPos(xPos);
504    return font.width(run, &fallbackFonts, &glyphOverflow);
505}
506
507// Adding a pair of midpoints before a character will split it out into a new line box.
508inline void ensureCharacterGetsLineBox(LineMidpointState& lineMidpointState, InlineIterator& textParagraphSeparator)
509{
510    InlineIterator midpoint(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset());
511    lineMidpointState.startIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset() - 1));
512    lineMidpointState.stopIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset()));
513}
514
515inline 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)
516{
517    // Map 'hyphenate-limit-{before,after}: auto;' to 2.
518    unsigned minimumPrefixLength;
519    unsigned minimumSuffixLength;
520
521    if (minimumPrefixLimit < 0)
522        minimumPrefixLength = 2;
523    else
524        minimumPrefixLength = static_cast<unsigned>(minimumPrefixLimit);
525
526    if (minimumSuffixLimit < 0)
527        minimumSuffixLength = 2;
528    else
529        minimumSuffixLength = static_cast<unsigned>(minimumSuffixLimit);
530
531    if (pos - lastSpace <= minimumSuffixLength)
532        return;
533
534    if (consecutiveHyphenatedLinesLimit >= 0 && consecutiveHyphenatedLines >= static_cast<unsigned>(consecutiveHyphenatedLinesLimit))
535        return;
536
537    int hyphenWidth = measureHyphenWidth(text, font);
538
539    float maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing;
540    // If the maximum width available for the prefix before the hyphen is small, then it is very unlikely
541    // that an hyphenation opportunity exists, so do not bother to look for it.
542    if (maxPrefixWidth <= font.pixelSize() * 5 / 4)
543        return;
544
545    const RenderStyle& style = text->style();
546    TextRun run = RenderBlock::constructTextRun(text, font, text, lastSpace, pos - lastSpace, style);
547    run.setCharactersLength(text->textLength() - lastSpace);
548    ASSERT(run.charactersLength() >= run.length());
549
550    run.setTabSize(!collapseWhiteSpace, style.tabSize());
551    run.setXPos(xPos + lastSpaceWordSpacing);
552
553    unsigned prefixLength = font.offsetForPosition(run, maxPrefixWidth, false);
554    if (prefixLength < minimumPrefixLength)
555        return;
556
557    prefixLength = lastHyphenLocation(StringView(text->text()).substring(lastSpace, pos - lastSpace), std::min(prefixLength, pos - lastSpace - minimumSuffixLength) + 1, localeIdentifier);
558    if (!prefixLength || prefixLength < minimumPrefixLength)
559        return;
560
561    // When lastSpace is a space, which it always is except sometimes at the beginning of a line or after collapsed
562    // space, it should not count towards hyphenate-limit-before.
563    if (prefixLength == minimumPrefixLength) {
564        UChar characterAtLastSpace = text->characterAt(lastSpace);
565        if (characterAtLastSpace == ' ' || characterAtLastSpace == '\n' || characterAtLastSpace == '\t' || characterAtLastSpace == noBreakSpace)
566            return;
567    }
568
569    ASSERT(pos - lastSpace - prefixLength >= minimumSuffixLength);
570
571#if !ASSERT_DISABLED
572    HashSet<const SimpleFontData*> fallbackFonts;
573    float prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace, fallbackFonts) + lastSpaceWordSpacing;
574    ASSERT(xPos + prefixWidth <= availableWidth);
575#else
576    UNUSED_PARAM(isFixedPitch);
577#endif
578
579    lineBreak.moveTo(text, lastSpace + prefixLength, nextBreakable);
580    hyphenated = true;
581}
582
583inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated,  unsigned& consecutiveHyphenatedLines)
584{
585    if (!m_current.offset())
586        m_appliedStartWidth = false;
587
588    RenderText* renderText = toRenderText(m_current.renderer());
589
590    bool isSVGText = renderText->isSVGInlineText();
591
592    // If we have left a no-wrap inline and entered an autowrap inline while ignoring spaces
593    // then we need to mark the start of the autowrap inline as a potential linebreak now.
594    if (m_autoWrap && !RenderStyle::autoWrap(m_lastWS) && m_ignoringSpaces)
595        commitLineBreakAtCurrentWidth(m_current.renderer());
596
597    if (renderText->style().hasTextCombine() && m_current.renderer()->isCombineText() && !toRenderCombineText(*m_current.renderer()).isCombined()) {
598        RenderCombineText& combineRenderer = toRenderCombineText(*m_current.renderer());
599        combineRenderer.combineText();
600        // The length of the renderer's text may have changed. Increment stale iterator positions
601        if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreak, combineRenderer)) {
602            ASSERT(iteratorIsBeyondEndOfRenderCombineText(m_resolver.position(), combineRenderer));
603            m_lineBreak.increment();
604            m_resolver.increment();
605        }
606    }
607
608    const RenderStyle& style = lineStyle(*renderText->parent(), m_lineInfo);
609    const Font& font = style.font();
610    bool isFixedPitch = font.isFixedPitch();
611    bool canHyphenate = style.hyphens() == HyphensAuto && WebCore::canHyphenate(style.locale());
612
613    unsigned lastSpace = m_current.offset();
614    float wordSpacing = m_currentStyle->font().wordSpacing();
615    float lastSpaceWordSpacing = 0;
616    float wordSpacingForWordMeasurement = 0;
617
618    float wrapW = m_width.uncommittedWidth() + inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, true);
619    float charWidth = 0;
620    bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE;
621    // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word,
622    // which is only possible if the word is the first thing on the line, that is, if |w| is zero.
623    bool breakWords = m_currentStyle->breakWords() && ((m_autoWrap && !m_width.committedWidth()) || m_currWS == PRE);
624    bool midWordBreak = false;
625    bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap;
626    float hyphenWidth = 0;
627
628    if (isSVGText) {
629        breakWords = false;
630        breakAll = false;
631    }
632
633    if (m_renderTextInfo.m_text != renderText) {
634        updateCounterIfNeeded(*renderText);
635        m_renderTextInfo.m_text = renderText;
636        m_renderTextInfo.m_font = &font;
637        m_renderTextInfo.m_layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
638        m_renderTextInfo.m_lineBreakIterator.resetStringAndReleaseIterator(renderText->text(), style.locale());
639    } else if (m_renderTextInfo.m_layout && m_renderTextInfo.m_font != &font) {
640        m_renderTextInfo.m_font = &font;
641        m_renderTextInfo.m_layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
642    }
643
644    TextLayout* textLayout = m_renderTextInfo.m_layout.get();
645
646    // Non-zero only when kerning is enabled and TextLayout isn't used, in which case we measure
647    // words with their trailing space, then subtract its width.
648    HashSet<const SimpleFontData*> fallbackFonts;
649    float wordTrailingSpaceWidth = (font.typesettingFeatures() & Kerning) && !textLayout ? font.width(RenderBlock::constructTextRun(renderText, font, &space, 1, style), &fallbackFonts) + wordSpacing : 0;
650
651    UChar lastCharacter = m_renderTextInfo.m_lineBreakIterator.lastCharacter();
652    UChar secondToLastCharacter = m_renderTextInfo.m_lineBreakIterator.secondToLastCharacter();
653    for (; m_current.offset() < renderText->textLength(); m_current.fastIncrementInTextNode()) {
654        bool previousCharacterIsSpace = m_currentCharacterIsSpace;
655        bool previousCharacterIsWS = m_currentCharacterIsWS;
656        UChar c = m_current.current();
657        m_currentCharacterIsSpace = c == ' ' || c == '\t' || (!m_preservesNewline && (c == '\n'));
658
659        if (!m_collapseWhiteSpace || !m_currentCharacterIsSpace)
660            m_lineInfo.setEmpty(false, &m_block, &m_width);
661
662        if (c == softHyphen && m_autoWrap && !hyphenWidth && style.hyphens() != HyphensNone) {
663            hyphenWidth = measureHyphenWidth(renderText, font, &fallbackFonts);
664            m_width.addUncommittedWidth(hyphenWidth);
665        }
666
667        bool applyWordSpacing = false;
668
669        m_currentCharacterIsWS = m_currentCharacterIsSpace || (breakNBSP && c == noBreakSpace);
670
671        if ((breakAll || breakWords) && !midWordBreak && (!m_currentCharacterIsSpace || style.whiteSpace() != PRE_WRAP)) {
672            wrapW += charWidth;
673            bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText->textLength() && U16_IS_TRAIL((*renderText)[m_current.offset() + 1]);
674            charWidth = textWidth(renderText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, font, m_width.committedWidth() + wrapW, isFixedPitch, m_collapseWhiteSpace, fallbackFonts, textLayout);
675            midWordBreak = m_width.committedWidth() + wrapW + charWidth > m_width.availableWidth();
676        }
677
678        int nextBreakablePosition = m_current.nextBreakablePosition();
679        bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.m_lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP)
680            && (style.hyphens() != HyphensNone || (m_current.previousInSameNode() != softHyphen)));
681        m_current.setNextBreakablePosition(nextBreakablePosition);
682
683        if (betweenWords || midWordBreak) {
684            bool stoppedIgnoringSpaces = false;
685            if (m_ignoringSpaces) {
686                lastSpaceWordSpacing = 0;
687                if (!m_currentCharacterIsSpace) {
688                    // Stop ignoring spaces and begin at this
689                    // new point.
690                    m_ignoringSpaces = false;
691                    wordSpacingForWordMeasurement = 0;
692                    lastSpace = m_current.offset(); // e.g., "Foo    goo", don't add in any of the ignored spaces.
693                    m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
694                    stoppedIgnoringSpaces = true;
695                } else {
696                    // Just keep ignoring these spaces.
697                    nextCharacter(c, lastCharacter, secondToLastCharacter);
698                    continue;
699                }
700            }
701
702            wordMeasurements.grow(wordMeasurements.size() + 1);
703            WordMeasurement& wordMeasurement = wordMeasurements.last();
704
705            wordMeasurement.renderer = renderText;
706            wordMeasurement.endOffset = m_current.offset();
707            wordMeasurement.startOffset = lastSpace;
708
709            float additionalTempWidth;
710            if (wordTrailingSpaceWidth && c == ' ')
711                additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() + 1 - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) - wordTrailingSpaceWidth;
712            else
713                additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
714
715            if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
716                wordMeasurement.fallbackFonts.swap(fallbackFonts);
717            fallbackFonts.clear();
718
719            wordMeasurement.width = additionalTempWidth + wordSpacingForWordMeasurement;
720            additionalTempWidth += lastSpaceWordSpacing;
721            m_width.addUncommittedWidth(additionalTempWidth);
722
723            if (m_collapseWhiteSpace && previousCharacterIsSpace && m_currentCharacterIsSpace && additionalTempWidth)
724                m_width.setTrailingWhitespaceWidth(additionalTempWidth);
725
726            if (!m_appliedStartWidth) {
727                m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer(), true, false));
728                m_appliedStartWidth = true;
729            }
730
731            applyWordSpacing = wordSpacing && m_currentCharacterIsSpace;
732
733            if (!m_width.committedWidth() && m_autoWrap && !m_width.fitsOnLine())
734                m_width.fitBelowFloats(m_lineInfo.isFirstLine());
735
736            if (m_autoWrap || breakWords) {
737                // If we break only after white-space, consider the current character
738                // as candidate width for this line.
739                bool lineWasTooWide = false;
740                if (m_width.fitsOnLine() && m_currentCharacterIsWS && m_currentStyle->breakOnlyAfterWhiteSpace() && !midWordBreak) {
741                    float charWidth = textWidth(renderText, m_current.offset(), 1, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) + (applyWordSpacing ? wordSpacing : 0);
742                    // Check if line is too big even without the extra space
743                    // at the end of the line. If it is not, do nothing.
744                    // If the line needs the extra whitespace to be too long,
745                    // then move the line break to the space and skip all
746                    // additional whitespace.
747                    if (!m_width.fitsOnLineIncludingExtraWidth(charWidth)) {
748                        lineWasTooWide = true;
749                        m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
750                        m_lineBreaker.skipTrailingWhitespace(m_lineBreak, m_lineInfo);
751                    }
752                }
753                if (lineWasTooWide || !m_width.fitsOnLine()) {
754                    if (canHyphenate && !m_width.fitsOnLine()) {
755                        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);
756                        if (m_lineBreaker.m_hyphenated) {
757                            m_atEnd = true;
758                            return false;
759                        }
760                    }
761                    if (m_lineBreak.atTextParagraphSeparator()) {
762                        if (!stoppedIgnoringSpaces && m_current.offset() > 0)
763                            ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
764                        m_lineBreak.increment();
765                        m_lineInfo.setPreviousLineBrokeCleanly(true);
766                        wordMeasurement.endOffset = m_lineBreak.offset();
767                    }
768                    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)
769                        hyphenated = true;
770                    if (m_lineBreak.offset() && m_lineBreak.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) {
771                        if (charWidth) {
772                            wordMeasurement.endOffset = m_lineBreak.offset();
773                            wordMeasurement.width = charWidth;
774                        }
775                    }
776                    // Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace.
777                    if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) {
778                        m_atEnd = true;
779                        return false;
780                    }
781                } else {
782                    if (!betweenWords || (midWordBreak && !m_autoWrap))
783                        m_width.addUncommittedWidth(-additionalTempWidth);
784                    if (hyphenWidth) {
785                        // Subtract the width of the soft hyphen out since we fit on a line.
786                        m_width.addUncommittedWidth(-hyphenWidth);
787                        hyphenWidth = 0;
788                    }
789                }
790            }
791
792            if (c == '\n' && m_preservesNewline) {
793                if (!stoppedIgnoringSpaces && m_current.offset())
794                    ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
795                m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
796                m_lineBreak.increment();
797                m_lineInfo.setPreviousLineBrokeCleanly(true);
798                return true;
799            }
800
801            if (m_autoWrap && betweenWords) {
802                m_width.commit();
803                wrapW = 0;
804                m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
805                // Auto-wrapping text should not wrap in the middle of a word once it has had an
806                // opportunity to break after a word.
807                breakWords = false;
808            }
809
810            if (midWordBreak && !U16_IS_TRAIL(c) && !(U_GET_GC_MASK(c) & U_GC_M_MASK)) {
811                // Remember this as a breakable position in case
812                // adding the end width forces a break.
813                m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
814                midWordBreak &= (breakWords || breakAll);
815            }
816
817            if (betweenWords) {
818                lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
819                wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0;
820                lastSpace = m_current.offset();
821            }
822
823            if (!m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) {
824                // If we encounter a newline, or if we encounter a
825                // second space, we need to go ahead and break up this
826                // run and enter a mode where we start collapsing spaces.
827                if (m_currentCharacterIsSpace && previousCharacterIsSpace) {
828                    m_ignoringSpaces = true;
829
830                    // We just entered a mode where we are ignoring
831                    // spaces. Create a midpoint to terminate the run
832                    // before the second space.
833                    m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
834                    m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, InlineIterator(), TrailingObjects::DoNotCollapseFirstSpace);
835                }
836            }
837        } else if (m_ignoringSpaces) {
838            // Stop ignoring spaces and begin at this
839            // new point.
840            m_ignoringSpaces = false;
841            lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
842            wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurements.last().width) ? wordSpacing : 0;
843            lastSpace = m_current.offset(); // e.g., "Foo    goo", don't add in any of the ignored spaces.
844            m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
845        }
846
847        if (isSVGText && m_current.offset()) {
848            // Force creation of new InlineBoxes for each absolute positioned character (those that start new text chunks).
849            if (toRenderSVGInlineText(renderText)->characterStartsNewTextChunk(m_current.offset()))
850                ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
851        }
852
853        if (m_currentCharacterIsSpace && !previousCharacterIsSpace) {
854            m_startOfIgnoredSpaces.setRenderer(m_current.renderer());
855            m_startOfIgnoredSpaces.setOffset(m_current.offset());
856            // Spaces after right-aligned text and before a line-break get collapsed away completely so that the trailing
857            // space doesn't seem to push the text out from the right-hand edge.
858            // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results.
859            if (m_nextObject && m_startOfIgnoredSpaces.offset() && m_nextObject->isBR() && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) {
860                m_startOfIgnoredSpaces.setOffset(m_startOfIgnoredSpaces.offset() - 1);
861                // If there's just a single trailing space start ignoring it now so it collapses away.
862                if (m_current.offset() == renderText->textLength() - 1)
863                    m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
864            }
865        }
866
867        if (!m_currentCharacterIsWS && previousCharacterIsWS) {
868            if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace())
869                m_lineBreak.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
870        }
871
872        if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces)
873            m_trailingObjects.setTrailingWhitespace(toRenderText(m_current.renderer()));
874        else if (!m_currentStyle->collapseWhiteSpace() || !m_currentCharacterIsSpace)
875            m_trailingObjects.clear();
876
877        m_atStart = false;
878        nextCharacter(c, lastCharacter, secondToLastCharacter);
879    }
880
881    m_renderTextInfo.m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
882
883    wordMeasurements.grow(wordMeasurements.size() + 1);
884    WordMeasurement& wordMeasurement = wordMeasurements.last();
885    wordMeasurement.renderer = renderText;
886
887    // IMPORTANT: current.m_pos is > length here!
888    float additionalTempWidth = m_ignoringSpaces ? 0 : textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
889    wordMeasurement.startOffset = lastSpace;
890    wordMeasurement.endOffset = m_current.offset();
891    wordMeasurement.width = m_ignoringSpaces ? 0 : additionalTempWidth + wordSpacingForWordMeasurement;
892    additionalTempWidth += lastSpaceWordSpacing;
893
894    float inlineLogicalTempWidth = inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, m_includeEndWidth);
895    m_width.addUncommittedWidth(additionalTempWidth + inlineLogicalTempWidth);
896
897    if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
898        wordMeasurement.fallbackFonts.swap(fallbackFonts);
899    fallbackFonts.clear();
900
901    if (m_collapseWhiteSpace && m_currentCharacterIsSpace && additionalTempWidth)
902        m_width.setTrailingWhitespaceWidth(additionalTempWidth, inlineLogicalTempWidth);
903
904    m_includeEndWidth = false;
905
906    if (!m_width.fitsOnLine()) {
907        if (canHyphenate)
908            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);
909
910        if (!hyphenated && m_lineBreak.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) {
911            hyphenated = true;
912            m_atEnd = true;
913        }
914    }
915    return false;
916}
917
918inline bool textBeginsWithBreakablePosition(RenderObject* next)
919{
920    ASSERT(next->isText());
921    RenderText* nextText = toRenderText(next);
922    if (!nextText->textLength())
923        return false;
924    UChar c = nextText->characterAt(0);
925    return c == ' ' || c == '\t' || (c == '\n' && !nextText->preservesNewline());
926}
927
928inline bool BreakingContext::canBreakAtThisPosition()
929{
930    // If we are no-wrap and have found a line-breaking opportunity already then we should take it.
931    if (m_width.committedWidth() && !m_width.fitsOnLine(m_currentCharacterIsSpace) && m_currWS == NOWRAP)
932        return true;
933
934    // Avoid breaking on empty inlines.
935    if (m_current.renderer()->isRenderInline() && isEmptyInline(toRenderInline(*m_current.renderer())))
936        return false;
937
938    // Avoid breaking before empty inlines.
939    if (m_nextObject && m_nextObject->isRenderInline() && isEmptyInline(toRenderInline(*m_nextObject)))
940        return false;
941
942    // Return early if we autowrap and the current character is a space as we will always want to break at such a position.
943    if (m_autoWrap && m_currentCharacterIsSpace)
944        return true;
945
946    if (m_nextObject && m_nextObject->isLineBreakOpportunity())
947        return m_autoWrap;
948
949    bool nextIsAutoWrappingText = (m_nextObject && m_nextObject->isText() && (m_autoWrap || m_nextObject->style().autoWrap()));
950    if (!nextIsAutoWrappingText)
951        return m_autoWrap;
952    bool currentIsTextOrEmptyInline = m_current.renderer()->isText() || (m_current.renderer()->isRenderInline() && isEmptyInline(toRenderInline(*m_current.renderer())));
953    if (!currentIsTextOrEmptyInline)
954        return m_autoWrap;
955
956    bool canBreakHere = !m_currentCharacterIsSpace && textBeginsWithBreakablePosition(m_nextObject);
957
958    // See if attempting to fit below floats creates more available width on the line.
959    if (!m_width.fitsOnLine() && !m_width.committedWidth())
960        m_width.fitBelowFloats(m_lineInfo.isFirstLine());
961
962    bool canPlaceOnLine = m_width.fitsOnLine() || !m_autoWrapWasEverTrueOnLine;
963
964    if (canPlaceOnLine && canBreakHere)
965        commitLineBreakAtCurrentWidth(m_nextObject);
966
967    return canBreakHere;
968}
969
970inline void BreakingContext::commitAndUpdateLineBreakIfNeeded()
971{
972    bool checkForBreak = canBreakAtThisPosition();
973
974    if (checkForBreak && !m_width.fitsOnLine(m_ignoringSpaces)) {
975        // if we have floats, try to get below them.
976        if (m_currentCharacterIsSpace && !m_ignoringSpaces && m_currentStyle->collapseWhiteSpace())
977            m_trailingObjects.clear();
978
979        if (m_width.committedWidth()) {
980            m_atEnd = true;
981            return;
982        }
983
984        m_width.fitBelowFloats(m_lineInfo.isFirstLine());
985
986        // |width| may have been adjusted because we got shoved down past a float (thus
987        // giving us more room), so we need to retest, and only jump to
988        // the end label if we still don't fit on the line. -dwh
989        if (!m_width.fitsOnLine(m_ignoringSpaces)) {
990            m_atEnd = true;
991            return;
992        }
993    } else if (m_blockStyle.autoWrap() && !m_width.fitsOnLine() && !m_width.committedWidth()) {
994        // If the container autowraps but the current child does not then we still need to ensure that it
995        // wraps and moves below any floats.
996        m_width.fitBelowFloats(m_lineInfo.isFirstLine());
997    }
998
999    if (!m_current.renderer()->isFloatingOrOutOfFlowPositioned()) {
1000        m_lastObject = m_current.renderer();
1001        if (m_lastObject->isReplaced() && m_autoWrap && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!m_lastObject->isListMarker() || toRenderListMarker(*m_lastObject).isInside())) {
1002            m_width.commit();
1003            m_lineBreak.moveToStartOf(m_nextObject);
1004        }
1005    }
1006}
1007
1008inline TrailingObjects::CollapseFirstSpaceOrNot checkMidpoints(LineMidpointState& lineMidpointState, InlineIterator& lBreak)
1009{
1010    // Check to see if our last midpoint is a start point beyond the line break. If so,
1011    // shave it off the list, and shave off a trailing space if the previous end point doesn't
1012    // preserve whitespace.
1013    if (lBreak.renderer() && lineMidpointState.numMidpoints() && !(lineMidpointState.numMidpoints() % 2)) {
1014        InlineIterator* midpoints = lineMidpointState.midpoints().data();
1015        InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints() - 2];
1016        const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints() - 1];
1017        InlineIterator currpoint = endpoint;
1018        while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak)
1019            currpoint.increment();
1020        if (currpoint == lBreak) {
1021            // We hit the line break before the start point. Shave off the start point.
1022            lineMidpointState.decreaseNumMidpoints();
1023            if (endpoint.renderer()->style().collapseWhiteSpace() && endpoint.renderer()->isText()) {
1024                endpoint.fastDecrement();
1025                return TrailingObjects::DoNotCollapseFirstSpace;
1026            }
1027        }
1028    }
1029    return TrailingObjects::CollapseFirstSpace;
1030}
1031
1032inline InlineIterator BreakingContext::handleEndOfLine()
1033{
1034    if (m_lineBreak == m_resolver.position()) {
1035        if (!m_lineBreak.renderer() || !m_lineBreak.renderer()->isBR()) {
1036            // we just add as much as possible
1037            if (m_blockStyle.whiteSpace() == PRE && !m_current.offset()) {
1038                m_lineBreak.moveTo(m_lastObject, m_lastObject->isText() ? m_lastObject->length() : 0);
1039            } else if (m_lineBreak.renderer()) {
1040                // Don't ever break in the middle of a word if we can help it.
1041                // There's no room at all. We just have to be on this line,
1042                // even though we'll spill out.
1043                m_lineBreak.moveTo(m_current.renderer(), m_current.offset());
1044            }
1045        }
1046        // make sure we consume at least one char/object.
1047        if (m_lineBreak == m_resolver.position())
1048            m_lineBreak.increment();
1049    } else if (!m_current.offset() && !m_width.committedWidth() && m_width.uncommittedWidth() && !m_hadUncommittedWidthBeforeCurrent) {
1050        // Do not push the current object to the next line, when this line has some content, but it is still considered empty.
1051        // Empty inline elements like <span></span> can produce such lines and now we just ignore these break opportunities
1052        // at the start of a line, if no width has been committed yet.
1053        // Behave as if it was actually empty and consume at least one object.
1054        m_lineBreak.increment();
1055    }
1056
1057    // Sanity check our midpoints.
1058    TrailingObjects::CollapseFirstSpaceOrNot collapsed = checkMidpoints(m_lineMidpointState, m_lineBreak);
1059
1060    m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, m_lineBreak, collapsed);
1061
1062    // We might have made lineBreak an iterator that points past the end
1063    // of the object. Do this adjustment to make it point to the start
1064    // of the next object instead to avoid confusing the rest of the
1065    // code.
1066    if (m_lineBreak.offset()) {
1067        m_lineBreak.setOffset(m_lineBreak.offset() - 1);
1068        m_lineBreak.increment();
1069    }
1070
1071    return m_lineBreak;
1072}
1073
1074}
1075
1076#endif // BreakingContextInlineHeaders_h
1077