a41a2fc6c2b3d2279b0ccf0dd4314aec53f4300c
[WebKit-https.git] / Source / WebCore / rendering / line / BreakingContext.h
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 BreakingContext_h
26 #define BreakingContext_h
27
28 #include "Hyphenation.h"
29 #include "LineBreaker.h"
30 #include "LineInfo.h"
31 #include "LineLayoutState.h"
32 #include "LineWidth.h"
33 #include "RenderCombineText.h"
34 #include "RenderCounter.h"
35 #include "RenderInline.h"
36 #include "RenderLineBreak.h"
37 #include "RenderListMarker.h"
38 #include "RenderRubyRun.h"
39 #include "RenderSVGInlineText.h"
40 #include "TrailingObjects.h"
41 #include "break_lines.h"
42 #include <wtf/Optional.h>
43 #include <wtf/text/StringView.h>
44 #include <wtf/unicode/CharacterNames.h>
45
46 namespace WebCore {
47
48 // We don't let our line box tree for a single line get any deeper than this.
49 const unsigned cMaxLineDepth = 200;
50
51 struct WordMeasurement {
52     WordMeasurement()
53         : renderer(0)
54         , width(0)
55         , startOffset(0)
56         , endOffset(0)
57     {
58     }
59
60     RenderText* renderer;
61     float width;
62     int startOffset;
63     int endOffset;
64     HashSet<const Font*> fallbackFonts;
65 };
66
67 struct WordTrailingSpace {
68     WordTrailingSpace(const RenderStyle& style, TextLayout* textLayout = nullptr)
69         : m_style(style)
70         , m_textLayout(textLayout)
71     {
72     }
73
74     WTF::Optional<float> width(HashSet<const Font*>& fallbackFonts)
75     {
76         if (m_state == WordTrailingSpaceState::Computed)
77             return m_width;
78
79         const FontCascade& font = m_style.fontCascade();
80         if (font.enableKerning() && !m_textLayout)
81             m_width = font.width(RenderBlock::constructTextRun(&space, 1, m_style), &fallbackFonts) + font.wordSpacing();
82         m_state = WordTrailingSpaceState::Computed;
83         return m_width;
84     }
85
86 private:
87     enum class WordTrailingSpaceState { Uninitialized, Computed };
88     WordTrailingSpaceState m_state { WordTrailingSpaceState::Uninitialized };
89     WTF::Optional<float> m_width;
90     const RenderStyle& m_style;
91     TextLayout* m_textLayout { nullptr };
92 };
93
94 class BreakingContext {
95 public:
96     BreakingContext(LineBreaker& lineBreaker, InlineBidiResolver& resolver, LineInfo& inLineInfo, LineLayoutState& layoutState, LineWidth& lineWidth, RenderTextInfo& inRenderTextInfo, FloatingObject* inLastFloatFromPreviousLine, bool appliedStartWidth, RenderBlockFlow& block)
97         : m_lineBreaker(lineBreaker)
98         , m_resolver(resolver)
99         , m_current(resolver.position())
100 #if ENABLE(CSS_TRAILING_WORD)
101         , m_lineBreakHistory(InlineIterator(resolver.position()), block.style().trailingWord() == TrailingWord::PartiallyBalanced ? 5 : 1)
102 #else
103         , m_lineBreakHistory(InlineIterator(resolver.position()), 1)
104 #endif
105         , m_block(block)
106         , m_lastObject(m_current.renderer())
107         , m_nextObject(nullptr)
108         , m_currentStyle(nullptr)
109         , m_blockStyle(block.style())
110         , m_lineInfo(inLineInfo)
111         , m_renderTextInfo(inRenderTextInfo)
112         , m_lastFloatFromPreviousLine(inLastFloatFromPreviousLine)
113         , m_width(lineWidth)
114         , m_lineLayoutState(layoutState)
115         , m_currWS(NORMAL)
116         , m_lastWS(NORMAL)
117         , m_preservesNewline(false)
118         , m_atStart(true)
119         , m_ignoringSpaces(false)
120         , m_currentCharacterIsSpace(false)
121         , m_currentCharacterIsWS(false)
122         , m_appliedStartWidth(appliedStartWidth)
123         , m_includeEndWidth(true)
124         , m_autoWrap(false)
125         , m_autoWrapWasEverTrueOnLine(false)
126         , m_floatsFitOnLine(true)
127         , m_collapseWhiteSpace(false)
128         , m_startingNewParagraph(m_lineInfo.previousLineBrokeCleanly())
129         , m_allowImagesToBreak(!block.document().inQuirksMode() || !block.isTableCell() || !m_blockStyle.logicalWidth().isIntrinsicOrAuto())
130         , m_atEnd(false)
131         , m_hadUncommittedWidthBeforeCurrent(false)
132         , m_lineMidpointState(resolver.midpointState())
133     {
134         m_lineInfo.setPreviousLineBrokeCleanly(false);
135     }
136
137     RenderObject* currentObject() { return m_current.renderer(); }
138     InlineIterator lineBreak() { return m_lineBreakHistory.current(); }
139     LineWidth& lineWidth() { return m_width; }
140     bool atEnd() { return m_atEnd; }
141     
142     bool fitsOnLineOrHangsAtEnd() const { return m_width.fitsOnLine() || m_hangsAtEnd; }
143
144     void initializeForCurrentObject();
145
146     void increment();
147
148     void handleBR(EClear&);
149     void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects);
150     void handleFloat();
151     void handleEmptyInline();
152     void handleReplaced();
153     bool handleText(WordMeasurements&, bool& hyphenated, unsigned& consecutiveHyphenatedLines);
154     bool canBreakAtThisPosition();
155     void commitAndUpdateLineBreakIfNeeded();
156     InlineIterator handleEndOfLine();
157 #if ENABLE(CSS_TRAILING_WORD)
158     InlineIterator optimalLineBreakLocationForTrailingWord();
159 #endif
160     
161     float computeAdditionalBetweenWordsWidth(RenderText&, TextLayout*, UChar, WordTrailingSpace&, HashSet<const Font*>& fallbackFonts, WordMeasurements&, const FontCascade&, bool isFixedPitch, unsigned lastSpace, float lastSpaceWordSpacing, float wordSpacingForWordMeasurement, unsigned offset);
162
163     void clearLineBreakIfFitsOnLine(bool ignoringTrailingSpace = false)
164     {
165         if (m_width.fitsOnLine(ignoringTrailingSpace) || m_lastWS == NOWRAP || m_hangsAtEnd)
166             m_lineBreakHistory.clear();
167         m_hangsAtEnd = false;
168     }
169
170     void commitLineBreakAtCurrentWidth(RenderObject& object, unsigned offset = 0, int nextBreak = -1)
171     {
172         m_width.commit();
173         m_lineBreakHistory.moveTo(&object, offset, nextBreak);
174         m_hangsAtEnd = false;
175     }
176
177 private:
178     // This class keeps a sliding window of the past n locations for an InlineIterator.
179     class InlineIteratorHistory : private Vector<InlineIterator, 1> {
180     public:
181         InlineIteratorHistory() = delete;
182         InlineIteratorHistory(const InlineIterator& initial, size_t capacity)
183             : m_capacity(capacity)
184         {
185             ASSERT(capacity > 0);
186             this->append(initial);
187         }
188
189         void push(std::function<void(InlineIterator& modifyMe)> updater)
190         {
191             ASSERT(!this->isEmpty());
192             if (m_capacity != 1)
193                 this->insert(0, InlineIterator(this->at(0)));
194             updater(this->at(0));
195             if (m_capacity != 1)
196                 this->resize(m_capacity);
197         }
198
199         void update(std::function<void(InlineIterator& modifyMe)> updater)
200         {
201             ASSERT(!this->isEmpty());
202             updater(this->at(0));
203         }
204
205         RenderObject* renderer() const { return this->at(0).renderer(); }
206         unsigned offset() const { return this->at(0).offset(); }
207         int nextBreakablePosition() const { return this->at(0).nextBreakablePosition(); }
208         bool atTextParagraphSeparator() const { return this->at(0).atTextParagraphSeparator(); }
209         UChar previousInSameNode() const { return this->at(0).previousInSameNode(); }
210         const InlineIterator& get(size_t i) const { return this->at(i); };
211         const InlineIterator& current() const { return get(0); }
212         size_t historyLength() const { return this->size(); }
213
214         void moveTo(RenderObject* object, unsigned offset, int nextBreak = -1)
215         {
216             push([&](InlineIterator& modifyMe) {
217                 modifyMe.moveTo(object, offset, nextBreak);
218             });
219         }
220
221         void increment()
222         {
223             update([](InlineIterator& modifyMe) {
224                 modifyMe.increment();
225             });
226         }
227
228         void clear()
229         {
230             push([](InlineIterator& modifyMe) {
231                 modifyMe.clear();
232             });
233         }
234
235     private:
236         const size_t m_capacity;
237     };
238
239     LineBreaker& m_lineBreaker;
240     InlineBidiResolver& m_resolver;
241
242     InlineIterator m_current;
243     InlineIteratorHistory m_lineBreakHistory;
244     InlineIterator m_startOfIgnoredSpaces;
245
246     RenderBlockFlow& m_block;
247     RenderObject* m_lastObject;
248     RenderObject* m_nextObject;
249
250     const RenderStyle* m_currentStyle;
251
252     // Firefox and Opera will allow a table cell to grow to fit an image inside it under
253     // very specific circumstances (in order to match common WinIE renderings).
254     // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.)
255     RenderStyle& m_blockStyle;
256
257     LineInfo& m_lineInfo;
258
259     RenderTextInfo& m_renderTextInfo;
260
261     FloatingObject* m_lastFloatFromPreviousLine;
262
263     LineWidth m_width;
264     
265     LineLayoutState& m_lineLayoutState;
266
267     EWhiteSpace m_currWS;
268     EWhiteSpace m_lastWS;
269
270     bool m_preservesNewline;
271     bool m_atStart;
272
273     // This variable is used only if whitespace isn't set to PRE, and it tells us whether
274     // or not we are currently ignoring whitespace.
275     bool m_ignoringSpaces;
276
277     // This variable tracks whether the very last character we saw was a space. We use
278     // this to detect when we encounter a second space so we know we have to terminate
279     // a run.
280     bool m_currentCharacterIsSpace;
281     bool m_currentCharacterIsWS;
282     bool m_appliedStartWidth;
283     bool m_includeEndWidth;
284     bool m_autoWrap;
285     bool m_autoWrapWasEverTrueOnLine;
286     bool m_floatsFitOnLine;
287     bool m_collapseWhiteSpace;
288     bool m_startingNewParagraph;
289     bool m_allowImagesToBreak;
290     bool m_atEnd;
291     bool m_hadUncommittedWidthBeforeCurrent;
292     
293     bool m_hangsAtEnd { false };
294
295     LineMidpointState& m_lineMidpointState;
296
297     TrailingObjects m_trailingObjects;
298 };
299
300 inline void BreakingContext::initializeForCurrentObject()
301 {
302     m_hadUncommittedWidthBeforeCurrent = !!m_width.uncommittedWidth();
303
304     m_currentStyle = &m_current.renderer()->style(); // FIXME: Should this be &lineStyle(*m_current.renderer(), m_lineInfo); ?
305
306     ASSERT(m_currentStyle);
307
308     m_nextObject = bidiNextSkippingEmptyInlines(m_block, m_current.renderer());
309     if (m_nextObject && m_nextObject->parent() && !m_nextObject->parent()->isDescendantOf(m_current.renderer()->parent()))
310         m_includeEndWidth = true;
311
312     m_currWS = m_current.renderer()->isReplaced() ? m_current.renderer()->parent()->style().whiteSpace() : m_currentStyle->whiteSpace();
313     m_lastWS = m_lastObject->isReplaced() ? m_lastObject->parent()->style().whiteSpace() : m_lastObject->style().whiteSpace();
314
315     m_autoWrap = RenderStyle::autoWrap(m_currWS);
316     m_autoWrapWasEverTrueOnLine = m_autoWrapWasEverTrueOnLine || m_autoWrap;
317
318     m_preservesNewline = m_current.renderer()->isSVGInlineText() ? false : RenderStyle::preserveNewline(m_currWS);
319
320     m_collapseWhiteSpace = RenderStyle::collapseWhiteSpace(m_currWS);
321 }
322
323 inline void BreakingContext::increment()
324 {
325     // Clear out our character space bool, since inline <pre>s don't collapse whitespace
326     // with adjacent inline normal/nowrap spans.
327     if (!m_collapseWhiteSpace)
328         m_currentCharacterIsSpace = false;
329
330     m_current.moveToStartOf(m_nextObject);
331     m_atStart = false;
332 }
333
334 inline void BreakingContext::handleBR(EClear& clear)
335 {
336     if (fitsOnLineOrHangsAtEnd()) {
337         RenderObject* br = m_current.renderer();
338         m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
339             modifyMe.moveToStartOf(br);
340             modifyMe.increment();
341         });
342
343         // A <br> always breaks a line, so don't let the line be collapsed
344         // away. Also, the space at the end of a line with a <br> does not
345         // get collapsed away. It only does this if the previous line broke
346         // cleanly. Otherwise the <br> has no effect on whether the line is
347         // empty or not.
348         if (m_startingNewParagraph)
349             m_lineInfo.setEmpty(false, &m_block, &m_width);
350         m_trailingObjects.clear();
351         m_lineInfo.setPreviousLineBrokeCleanly(true);
352
353         // A <br> with clearance always needs a linebox in case the lines below it get dirtied later and
354         // need to check for floats to clear - so if we're ignoring spaces, stop ignoring them and add a
355         // run for this object.
356         if (m_ignoringSpaces && m_currentStyle->clear() != CNONE)
357             m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(br);
358         // If we were preceded by collapsing space and are in a right-aligned container we need to ensure the space gets
359         // collapsed away so that it doesn't push the text out from the container's right-hand edge.
360         // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results.
361         else if (m_ignoringSpaces && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT))
362             m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
363
364         if (!m_lineInfo.isEmpty())
365             clear = m_currentStyle->clear();
366     }
367     m_atEnd = true;
368 }
369
370 inline LayoutUnit borderPaddingMarginStart(const RenderInline& child)
371 {
372     return child.marginStart() + child.paddingStart() + child.borderStart();
373 }
374
375 inline LayoutUnit borderPaddingMarginEnd(const RenderInline& child)
376 {
377     return child.marginEnd() + child.paddingEnd() + child.borderEnd();
378 }
379
380 inline bool shouldAddBorderPaddingMargin(RenderObject* child)
381 {
382     if (!child)
383         return true;
384     // When deciding whether we're at the edge of an inline, adjacent collapsed whitespace is the same as no sibling at all.
385     if (is<RenderText>(*child) && !downcast<RenderText>(*child).textLength())
386         return true;
387 #if ENABLE(CSS_BOX_DECORATION_BREAK)
388     if (is<RenderLineBreak>(*child) && child->parent()->style().boxDecorationBreak() == DCLONE)
389         return true;
390 #endif
391     return false;
392 }
393
394 inline RenderObject* previousInFlowSibling(RenderObject* child)
395 {
396     do {
397         child = child->previousSibling();
398     } while (child && child->isOutOfFlowPositioned());
399     return child;
400 }
401
402 inline LayoutUnit inlineLogicalWidth(RenderObject* child, bool checkStartEdge = true, bool checkEndEdge = true)
403 {
404     unsigned lineDepth = 1;
405     LayoutUnit extraWidth = 0;
406     RenderElement* parent = child->parent();
407     while (is<RenderInline>(*parent) && lineDepth++ < cMaxLineDepth) {
408         const auto& parentAsRenderInline = downcast<RenderInline>(*parent);
409         if (!isEmptyInline(parentAsRenderInline)) {
410             checkStartEdge = checkStartEdge && shouldAddBorderPaddingMargin(previousInFlowSibling(child));
411             if (checkStartEdge)
412                 extraWidth += borderPaddingMarginStart(parentAsRenderInline);
413             checkEndEdge = checkEndEdge && shouldAddBorderPaddingMargin(child->nextSibling());
414             if (checkEndEdge)
415                 extraWidth += borderPaddingMarginEnd(parentAsRenderInline);
416             if (!checkStartEdge && !checkEndEdge)
417                 return extraWidth;
418         }
419         child = parent;
420         parent = child->parent();
421     }
422     return extraWidth;
423 }
424
425 inline void BreakingContext::handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects)
426 {
427     // If our original display wasn't an inline type, then we can determine our static inline position now.
428     auto& box = downcast<RenderBox>(*m_current.renderer());
429     bool isInlineType = box.style().isOriginalDisplayInlineType();
430     if (!isInlineType)
431         m_block.setStaticInlinePositionForChild(box, m_block.logicalHeight(), m_block.startOffsetForContent(m_block.logicalHeight()));
432     else {
433         // If our original display was an INLINE type, then we can determine our static y position now.
434         box.layer()->setStaticBlockPosition(m_block.logicalHeight());
435     }
436
437     // If we're ignoring spaces, we have to stop and include this object and
438     // then start ignoring spaces again.
439     if (isInlineType || box.container()->isRenderInline()) {
440         if (m_ignoringSpaces)
441             m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(&box);
442         m_trailingObjects.appendBoxIfNeeded(&box);
443     } else
444         positionedObjects.append(&box);
445
446     m_width.addUncommittedWidth(inlineLogicalWidth(&box));
447     // Reset prior line break context characters.
448     m_renderTextInfo.lineBreakIterator.resetPriorContext();
449 }
450
451 inline void BreakingContext::handleFloat()
452 {
453     auto& floatBox = downcast<RenderBox>(*m_current.renderer());
454     const auto& floatingObject = *m_lineBreaker.insertFloatingObject(floatBox);
455     // check if it fits in the current line.
456     // If it does, position it now, otherwise, position
457     // it after moving to next line (in clearFloats() func)
458     if (m_floatsFitOnLine && m_width.fitsOnLineExcludingTrailingWhitespace(m_block.logicalWidthForFloat(floatingObject))) {
459         m_lineBreaker.positionNewFloatOnLine(floatingObject, m_lastFloatFromPreviousLine, m_lineInfo, m_width);
460         if (m_lineBreakHistory.renderer() == m_current.renderer()) {
461             ASSERT(!m_lineBreakHistory.offset());
462             m_lineBreakHistory.increment();
463         }
464     } else
465         m_floatsFitOnLine = false;
466     // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for floating element.
467     m_renderTextInfo.lineBreakIterator.updatePriorContext(replacementCharacter);
468 }
469
470 // This is currently just used for list markers and inline flows that have line boxes. Neither should
471 // have an effect on whitespace at the start of the line.
472 inline bool shouldSkipWhitespaceAfterStartObject(RenderBlockFlow& block, RenderObject* o, LineMidpointState& lineMidpointState)
473 {
474     RenderObject* next = bidiNextSkippingEmptyInlines(block, o);
475     while (next && next->isFloatingOrOutOfFlowPositioned())
476         next = bidiNextSkippingEmptyInlines(block, next);
477
478     if (is<RenderText>(next) && downcast<RenderText>(*next).textLength() > 0) {
479         RenderText& nextText = downcast<RenderText>(*next);
480         UChar nextChar = nextText.characterAt(0);
481         if (nextText.style().isCollapsibleWhiteSpace(nextChar)) {
482             lineMidpointState.startIgnoringSpaces(InlineIterator(nullptr, o, 0));
483             return true;
484         }
485     }
486
487     return false;
488 }
489
490 inline void BreakingContext::handleEmptyInline()
491 {
492     RenderInline& flowBox = downcast<RenderInline>(*m_current.renderer());
493
494     // This should only end up being called on empty inlines
495     ASSERT(isEmptyInline(flowBox));
496
497     // Now that some inline flows have line boxes, if we are already ignoring spaces, we need
498     // to make sure that we stop to include this object and then start ignoring spaces again.
499     // If this object is at the start of the line, we need to behave like list markers and
500     // start ignoring spaces.
501     bool requiresLineBox = alwaysRequiresLineBox(flowBox);
502     if (requiresLineBox || requiresLineBoxForContent(flowBox, m_lineInfo)) {
503         // An empty inline that only has line-height, vertical-align or font-metrics will only get a
504         // line box to affect the height of the line if the rest of the line is not empty.
505         if (requiresLineBox)
506             m_lineInfo.setEmpty(false, &m_block, &m_width);
507         if (m_ignoringSpaces) {
508             m_trailingObjects.clear();
509             m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(m_current.renderer());
510         } else if (m_blockStyle.collapseWhiteSpace() && m_resolver.position().renderer() == m_current.renderer()
511             && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
512             // Like with list markers, we start ignoring spaces to make sure that any
513             // additional spaces we see will be discarded.
514             m_currentCharacterIsSpace = true;
515             m_currentCharacterIsWS = true;
516             m_ignoringSpaces = true;
517         } else
518             m_trailingObjects.appendBoxIfNeeded(&flowBox);
519     }
520     
521     float inlineWidth = inlineLogicalWidth(m_current.renderer()) + borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox);
522     m_width.addUncommittedWidth(inlineWidth);
523     if (m_hangsAtEnd && inlineWidth)
524         m_hangsAtEnd = false;
525 }
526
527 inline void BreakingContext::handleReplaced()
528 {
529     auto& replacedBox = downcast<RenderBox>(*m_current.renderer());
530
531     if (m_atStart)
532         m_width.updateAvailableWidth(replacedBox.logicalHeight());
533
534     // Break on replaced elements if either has normal white-space.
535     if (((m_autoWrap || RenderStyle::autoWrap(m_lastWS)) && (!m_current.renderer()->isImage() || m_allowImagesToBreak)
536         && (!m_current.renderer()->isRubyRun() || downcast<RenderRubyRun>(m_current.renderer())->canBreakBefore(m_renderTextInfo.lineBreakIterator))) || replacedBox.isAnonymousInlineBlock()) {
537         commitLineBreakAtCurrentWidth(*m_current.renderer());
538         if (m_width.committedWidth() && replacedBox.isAnonymousInlineBlock()) {
539             // Always force a break before an anonymous inline block if there is content on the line
540             // already.
541             m_atEnd = true;
542             return;
543         }
544     } else
545         m_hangsAtEnd = false;
546     
547     if (replacedBox.isAnonymousInlineBlock())
548         m_block.layoutBlockChild(replacedBox, m_lineLayoutState.marginInfo(),
549             m_lineLayoutState.prevFloatBottomFromAnonymousInlineBlock(), m_lineLayoutState.maxFloatBottomFromAnonymousInlineBlock());
550
551     if (m_ignoringSpaces)
552         m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), 0));
553
554     m_lineInfo.setEmpty(false, &m_block, &m_width);
555     m_ignoringSpaces = false;
556     m_currentCharacterIsSpace = false;
557     m_currentCharacterIsWS = false;
558     m_trailingObjects.clear();
559
560     // Optimize for a common case. If we can't find whitespace after the list
561     // item, then this is all moot.
562     LayoutUnit replacedLogicalWidth = m_block.logicalWidthForChild(replacedBox) + m_block.marginStartForChild(replacedBox) + m_block.marginEndForChild(replacedBox) + inlineLogicalWidth(m_current.renderer());
563     if (is<RenderListMarker>(*m_current.renderer())) {
564         if (m_blockStyle.collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
565             // Like with inline flows, we start ignoring spaces to make sure that any
566             // additional spaces we see will be discarded.
567             m_currentCharacterIsSpace = true;
568             m_currentCharacterIsWS = false;
569             m_ignoringSpaces = true;
570         }
571         if (downcast<RenderListMarker>(*m_current.renderer()).isInside())
572             m_width.addUncommittedReplacedWidth(replacedLogicalWidth);
573     } else
574         m_width.addUncommittedReplacedWidth(replacedLogicalWidth);
575     if (is<RenderRubyRun>(*m_current.renderer())) {
576         m_width.applyOverhang(downcast<RenderRubyRun>(m_current.renderer()), m_lastObject, m_nextObject);
577         downcast<RenderRubyRun>(m_current.renderer())->updatePriorContextFromCachedBreakIterator(m_renderTextInfo.lineBreakIterator);
578     } else {
579         // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for replaced element.
580         m_renderTextInfo.lineBreakIterator.updatePriorContext(replacementCharacter);
581     }
582     
583     if (replacedBox.isAnonymousInlineBlock()) {
584         m_atEnd = true;
585         m_lineInfo.setPreviousLineBrokeCleanly(true);
586     }
587 }
588
589 inline float firstPositiveWidth(const WordMeasurements& wordMeasurements)
590 {
591     for (size_t i = 0; i < wordMeasurements.size(); ++i) {
592         if (wordMeasurements[i].width > 0)
593             return wordMeasurements[i].width;
594     }
595     return 0;
596 }
597
598 inline bool iteratorIsBeyondEndOfRenderCombineText(const InlineIterator& iter, RenderCombineText& renderer)
599 {
600     return iter.renderer() == &renderer && iter.offset() >= renderer.textLength();
601 }
602
603 inline void nextCharacter(UChar& currentCharacter, UChar& lastCharacter, UChar& secondToLastCharacter)
604 {
605     secondToLastCharacter = lastCharacter;
606     lastCharacter = currentCharacter;
607 }
608
609 // FIXME: Don't let counters mark themselves as needing pref width recalcs during layout
610 // so we don't need this hack.
611 inline void updateCounterIfNeeded(RenderText& renderText)
612 {
613     if (!renderText.preferredLogicalWidthsDirty() || !is<RenderCounter>(renderText))
614         return;
615     downcast<RenderCounter>(renderText).updateCounter();
616 }
617
618 inline float measureHyphenWidth(RenderText& renderer, const FontCascade& font, HashSet<const Font*>* fallbackFonts = 0)
619 {
620     const RenderStyle& style = renderer.style();
621     return font.width(RenderBlock::constructTextRun(style.hyphenString().string(), style), fallbackFonts);
622 }
623
624 ALWAYS_INLINE float textWidth(RenderText& text, unsigned from, unsigned len, const FontCascade& font, float xPos, bool isFixedPitch, bool collapseWhiteSpace, HashSet<const Font*>& fallbackFonts, TextLayout* layout = nullptr)
625 {
626     const RenderStyle& style = text.style();
627
628     GlyphOverflow glyphOverflow;
629     if (isFixedPitch || (!from && len == text.textLength()) || style.hasTextCombine())
630         return text.width(from, len, font, xPos, &fallbackFonts, &glyphOverflow);
631
632     if (layout)
633         return FontCascade::width(*layout, from, len, &fallbackFonts);
634
635     TextRun run = RenderBlock::constructTextRun(&text, from, len, style);
636     run.setCharactersLength(text.textLength() - from);
637     ASSERT(run.charactersLength() >= run.length());
638
639     run.setCharacterScanForCodePath(!text.canUseSimpleFontCodePath());
640     run.setTabSize(!collapseWhiteSpace, style.tabSize());
641     run.setXPos(xPos);
642     return font.width(run, &fallbackFonts, &glyphOverflow);
643 }
644
645 // Adding a pair of midpoints before a character will split it out into a new line box.
646 inline void ensureCharacterGetsLineBox(LineMidpointState& lineMidpointState, InlineIterator& textParagraphSeparator)
647 {
648     InlineIterator midpoint(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset());
649     lineMidpointState.startIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset() - 1));
650     lineMidpointState.stopIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset()));
651 }
652
653 inline void tryHyphenating(RenderText& text, const FontCascade& 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)
654 {
655     // Map 'hyphenate-limit-{before,after}: auto;' to 2.
656     unsigned minimumPrefixLength;
657     unsigned minimumSuffixLength;
658
659     if (minimumPrefixLimit < 0)
660         minimumPrefixLength = 2;
661     else
662         minimumPrefixLength = static_cast<unsigned>(minimumPrefixLimit);
663
664     if (minimumSuffixLimit < 0)
665         minimumSuffixLength = 2;
666     else
667         minimumSuffixLength = static_cast<unsigned>(minimumSuffixLimit);
668
669     if (pos - lastSpace <= minimumSuffixLength)
670         return;
671
672     if (consecutiveHyphenatedLinesLimit >= 0 && consecutiveHyphenatedLines >= static_cast<unsigned>(consecutiveHyphenatedLinesLimit))
673         return;
674
675     int hyphenWidth = measureHyphenWidth(text, font);
676
677     float maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing;
678     // If the maximum width available for the prefix before the hyphen is small, then it is very unlikely
679     // that an hyphenation opportunity exists, so do not bother to look for it.
680     if (maxPrefixWidth <= font.pixelSize() * 5 / 4)
681         return;
682
683     const RenderStyle& style = text.style();
684     TextRun run = RenderBlock::constructTextRun(&text, lastSpace, pos - lastSpace, style);
685     run.setCharactersLength(text.textLength() - lastSpace);
686     ASSERT(run.charactersLength() >= run.length());
687
688     run.setTabSize(!collapseWhiteSpace, style.tabSize());
689     run.setXPos(xPos + lastSpaceWordSpacing);
690
691     unsigned prefixLength = font.offsetForPosition(run, maxPrefixWidth, false);
692     if (prefixLength < minimumPrefixLength)
693         return;
694
695     prefixLength = lastHyphenLocation(StringView(text.text()).substring(lastSpace, pos - lastSpace), std::min(prefixLength, pos - lastSpace - minimumSuffixLength) + 1, localeIdentifier);
696     if (!prefixLength || prefixLength < minimumPrefixLength)
697         return;
698
699     // When lastSpace is a space, which it always is except sometimes at the beginning of a line or after collapsed
700     // space, it should not count towards hyphenate-limit-before.
701     if (prefixLength == minimumPrefixLength) {
702         UChar characterAtLastSpace = text.characterAt(lastSpace);
703         if (characterAtLastSpace == ' ' || characterAtLastSpace == '\n' || characterAtLastSpace == '\t' || characterAtLastSpace == noBreakSpace)
704             return;
705     }
706
707     ASSERT(pos - lastSpace - prefixLength >= minimumSuffixLength);
708
709 #if !ASSERT_DISABLED
710     HashSet<const Font*> fallbackFonts;
711     float prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace, fallbackFonts) + lastSpaceWordSpacing;
712     ASSERT(xPos + prefixWidth <= availableWidth);
713 #else
714     UNUSED_PARAM(isFixedPitch);
715 #endif
716
717     lineBreak.moveTo(&text, lastSpace + prefixLength, nextBreakable);
718     hyphenated = true;
719 }
720
721 inline float BreakingContext::computeAdditionalBetweenWordsWidth(RenderText& renderText, TextLayout* textLayout, UChar currentCharacter, WordTrailingSpace& wordTrailingSpace, HashSet<const Font*>& fallbackFonts, WordMeasurements& wordMeasurements, const FontCascade& font, bool isFixedPitch, unsigned lastSpace, float lastSpaceWordSpacing, float wordSpacingForWordMeasurement, unsigned offset)
722 {
723     wordMeasurements.grow(wordMeasurements.size() + 1);
724     WordMeasurement& wordMeasurement = wordMeasurements.last();
725     
726     wordMeasurement.renderer = &renderText;
727     wordMeasurement.endOffset = offset;
728     wordMeasurement.startOffset = lastSpace;
729     
730     float additionalTempWidth = 0;
731     WTF::Optional<float> wordTrailingSpaceWidth;
732     if (currentCharacter == ' ')
733         wordTrailingSpaceWidth = wordTrailingSpace.width(fallbackFonts);
734     if (wordTrailingSpaceWidth)
735         additionalTempWidth = textWidth(renderText, lastSpace, offset + 1 - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) - wordTrailingSpaceWidth.value();
736     else
737         additionalTempWidth = textWidth(renderText, lastSpace, offset - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
738     
739     if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
740         wordMeasurement.fallbackFonts.swap(fallbackFonts);
741     fallbackFonts.clear();
742     
743     wordMeasurement.width = additionalTempWidth + wordSpacingForWordMeasurement;
744     additionalTempWidth += lastSpaceWordSpacing;
745     return additionalTempWidth;
746 }
747
748 inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated,  unsigned& consecutiveHyphenatedLines)
749 {
750     if (!m_current.offset())
751         m_appliedStartWidth = false;
752
753     RenderText& renderText = downcast<RenderText>(*m_current.renderer());
754
755     bool isSVGText = renderText.isSVGInlineText();
756
757     // If we have left a no-wrap inline and entered an autowrap inline while ignoring spaces
758     // then we need to mark the start of the autowrap inline as a potential linebreak now.
759     if (m_autoWrap && !RenderStyle::autoWrap(m_lastWS) && m_ignoringSpaces)
760         commitLineBreakAtCurrentWidth(renderText);
761
762     if (renderText.style().hasTextCombine() && is<RenderCombineText>(*m_current.renderer())) {
763         auto& combineRenderer = downcast<RenderCombineText>(*m_current.renderer());
764         combineRenderer.combineText();
765         // The length of the renderer's text may have changed. Increment stale iterator positions
766         if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreakHistory.current(), combineRenderer)) {
767             ASSERT(iteratorIsBeyondEndOfRenderCombineText(m_resolver.position(), combineRenderer));
768             m_lineBreakHistory.increment();
769             m_resolver.increment();
770         }
771     }
772
773     const RenderStyle& style = lineStyle(renderText, m_lineInfo);
774     const FontCascade& font = style.fontCascade();
775     bool isFixedPitch = font.isFixedPitch();
776     bool canHyphenate = style.hyphens() == HyphensAuto && WebCore::canHyphenate(style.locale());
777     bool canHangPunctuationAtStart = style.hangingPunctuation() & FirstHangingPunctuation;
778     bool canHangPunctuationAtEnd = style.hangingPunctuation() & LastHangingPunctuation;
779     bool canHangStopOrCommaAtLineEnd = style.hangingPunctuation() & AllowEndHangingPunctuation;
780     int endPunctuationIndex = canHangPunctuationAtEnd && m_collapseWhiteSpace ? renderText.lastCharacterIndexStrippingSpaces() : renderText.textLength() - 1;
781     unsigned lastSpace = m_current.offset();
782     float wordSpacing = m_currentStyle->fontCascade().wordSpacing();
783     float lastSpaceWordSpacing = 0;
784     float wordSpacingForWordMeasurement = 0;
785
786     float wrapW = m_width.uncommittedWidth() + inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, true);
787     float charWidth = 0;
788     bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE;
789     // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word,
790     // which is only possible if the word is the first thing on the line, that is, if |w| is zero.
791     bool breakWords = m_currentStyle->breakWords() && ((m_autoWrap && !m_width.hasCommitted()) || m_currWS == PRE);
792     bool midWordBreak = false;
793     bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap;
794     bool keepAllWords = m_currentStyle->wordBreak() == KeepAllWordBreak;
795     float hyphenWidth = 0;
796     bool isLooseCJKMode = false;
797
798     if (isSVGText) {
799         breakWords = false;
800         breakAll = false;
801     }
802
803     if (m_renderTextInfo.text != &renderText) {
804         updateCounterIfNeeded(renderText);
805         m_renderTextInfo.text = &renderText;
806         m_renderTextInfo.font = &font;
807         m_renderTextInfo.layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
808         m_renderTextInfo.lineBreakIterator.resetStringAndReleaseIterator(renderText.text(), style.locale(), mapLineBreakToIteratorMode(m_blockStyle.lineBreak()));
809         isLooseCJKMode = m_renderTextInfo.lineBreakIterator.isLooseCJKMode();
810     } else if (m_renderTextInfo.layout && m_renderTextInfo.font != &font) {
811         m_renderTextInfo.font = &font;
812         m_renderTextInfo.layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
813     }
814
815     TextLayout* textLayout = m_renderTextInfo.layout.get();
816
817     // Non-zero only when kerning is enabled and TextLayout isn't used, in which case we measure
818     // words with their trailing space, then subtract its width.
819     HashSet<const Font*> fallbackFonts;
820     UChar lastCharacterFromPreviousRenderText = m_renderTextInfo.lineBreakIterator.lastCharacter();
821     UChar lastCharacter = m_renderTextInfo.lineBreakIterator.lastCharacter();
822     UChar secondToLastCharacter = m_renderTextInfo.lineBreakIterator.secondToLastCharacter();
823     WordTrailingSpace wordTrailingSpace(style, textLayout);
824     for (; m_current.offset() < renderText.textLength(); m_current.fastIncrementInTextNode()) {
825         bool previousCharacterIsSpace = m_currentCharacterIsSpace;
826         bool previousCharacterIsWS = m_currentCharacterIsWS;
827         UChar c = m_current.current();
828         m_currentCharacterIsSpace = c == ' ' || c == '\t' || (!m_preservesNewline && (c == '\n'));
829
830         if (canHangPunctuationAtStart && m_width.isFirstLine() && !m_width.committedWidth() && !wrapW && !inlineLogicalWidth(m_current.renderer(), true, false)) {
831             m_width.addUncommittedWidth(-renderText.hangablePunctuationStartWidth(m_current.offset()));
832             canHangPunctuationAtStart = false;
833         }
834         
835         if (canHangPunctuationAtEnd && !m_nextObject && (int)m_current.offset() == endPunctuationIndex && !inlineLogicalWidth(m_current.renderer(), false, true)) {
836             m_width.addUncommittedWidth(-renderText.hangablePunctuationEndWidth(endPunctuationIndex));
837             canHangPunctuationAtEnd = false;
838         }
839
840         if (!m_collapseWhiteSpace || !m_currentCharacterIsSpace)
841             m_lineInfo.setEmpty(false, &m_block, &m_width);
842
843         if (c == softHyphen && m_autoWrap && !hyphenWidth && style.hyphens() != HyphensNone) {
844             hyphenWidth = measureHyphenWidth(renderText, font, &fallbackFonts);
845             m_width.addUncommittedWidth(hyphenWidth);
846         }
847
848         bool applyWordSpacing = false;
849
850         m_currentCharacterIsWS = m_currentCharacterIsSpace || (breakNBSP && c == noBreakSpace);
851
852         if ((breakAll || breakWords) && !midWordBreak && (!m_currentCharacterIsSpace || style.whiteSpace() != PRE_WRAP)) {
853             wrapW += charWidth;
854             bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText.textLength() && U16_IS_TRAIL(renderText[m_current.offset() + 1]);
855             charWidth = textWidth(renderText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, font, m_width.committedWidth() + wrapW, isFixedPitch, m_collapseWhiteSpace, fallbackFonts, textLayout);
856             midWordBreak = m_width.committedWidth() + wrapW + charWidth > m_width.availableWidth();
857         }
858
859         int nextBreakablePosition = m_current.nextBreakablePosition();
860         bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP, isLooseCJKMode, keepAllWords)
861             && (style.hyphens() != HyphensNone || (m_current.previousInSameNode() != softHyphen)));
862         m_current.setNextBreakablePosition(nextBreakablePosition);
863         
864         if (canHangStopOrCommaAtLineEnd && renderText.isHangableStopOrComma(c) && m_width.fitsOnLine()) {
865             // We need to see if a measurement that excludes the stop would fit. If so, then we should hang
866             // the stop/comma at the end. First measure including the comma.
867             m_hangsAtEnd = false;
868             float inlineStartWidth = !m_appliedStartWidth ? inlineLogicalWidth(m_current.renderer(), true, false) : LayoutUnit();
869             float widthIncludingComma = computeAdditionalBetweenWordsWidth(renderText, textLayout, c, wordTrailingSpace, fallbackFonts, wordMeasurements, font, isFixedPitch, lastSpace, lastSpaceWordSpacing, wordSpacingForWordMeasurement, m_current.offset() + 1) + inlineStartWidth;
870             m_width.addUncommittedWidth(widthIncludingComma);
871             if (!m_width.fitsOnLine()) {
872                 // See if we fit without the comma involved. If we do, then this is a potential hang point.
873                 float widthWithoutStopOrComma = computeAdditionalBetweenWordsWidth(renderText, textLayout, lastCharacter, wordTrailingSpace, fallbackFonts, wordMeasurements, font, isFixedPitch, lastSpace, lastSpaceWordSpacing, wordSpacingForWordMeasurement, m_current.offset()) + inlineStartWidth;
874                 m_width.addUncommittedWidth(widthWithoutStopOrComma - widthIncludingComma);
875                 if (m_width.fitsOnLine())
876                     m_hangsAtEnd = true;
877             } else
878                 m_width.addUncommittedWidth(-widthIncludingComma);
879         }
880         
881         if (betweenWords || midWordBreak) {
882             bool stoppedIgnoringSpaces = false;
883             if (m_ignoringSpaces) {
884                 lastSpaceWordSpacing = 0;
885                 if (!m_currentCharacterIsSpace) {
886                     // Stop ignoring spaces and begin at this new point.
887                     m_ignoringSpaces = false;
888                     wordSpacingForWordMeasurement = 0;
889                     lastSpace = m_current.offset(); // e.g., "Foo    goo", don't add in any of the ignored spaces.
890                     m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
891                     stoppedIgnoringSpaces = true;
892                 } else {
893                     // Just keep ignoring these spaces.
894                     nextCharacter(c, lastCharacter, secondToLastCharacter);
895                     continue;
896                 }
897             }
898             
899             float additionalTempWidth = computeAdditionalBetweenWordsWidth(renderText, textLayout, c, wordTrailingSpace, fallbackFonts, wordMeasurements, font, isFixedPitch, lastSpace, lastSpaceWordSpacing, wordSpacingForWordMeasurement, m_current.offset());
900             m_width.addUncommittedWidth(additionalTempWidth);
901             
902             WordMeasurement& wordMeasurement = wordMeasurements.last();
903
904             if (m_collapseWhiteSpace && previousCharacterIsSpace && m_currentCharacterIsSpace && additionalTempWidth)
905                 m_width.setTrailingWhitespaceWidth(additionalTempWidth);
906
907             if (!m_appliedStartWidth) {
908                 float inlineStartWidth = inlineLogicalWidth(m_current.renderer(), true, false);
909                 m_width.addUncommittedWidth(inlineStartWidth);
910                 m_appliedStartWidth = true;
911                 if (m_hangsAtEnd && inlineStartWidth)
912                     m_hangsAtEnd = false;
913             }
914
915             applyWordSpacing = wordSpacing && m_currentCharacterIsSpace;
916
917             if (!m_width.hasCommitted() && m_autoWrap && !fitsOnLineOrHangsAtEnd())
918                 m_width.fitBelowFloats(m_lineInfo.isFirstLine());
919
920             if (m_autoWrap || breakWords) {
921                 // If we break only after white-space, consider the current character
922                 // as candidate width for this line.
923                 bool lineWasTooWide = false;
924                 if (fitsOnLineOrHangsAtEnd() && m_currentCharacterIsWS && m_currentStyle->breakOnlyAfterWhiteSpace() && !midWordBreak) {
925                     float charWidth = textWidth(renderText, m_current.offset(), 1, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) + (applyWordSpacing ? wordSpacing : 0);
926                     // Check if line is too big even without the extra space
927                     // at the end of the line. If it is not, do nothing.
928                     // If the line needs the extra whitespace to be too long,
929                     // then move the line break to the space and skip all
930                     // additional whitespace.
931                     if (!m_width.fitsOnLineIncludingExtraWidth(charWidth)) {
932                         lineWasTooWide = true;
933                         m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
934                             modifyMe.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
935                             m_lineBreaker.skipTrailingWhitespace(modifyMe, m_lineInfo);
936                         });
937                     }
938                 }
939                 if ((lineWasTooWide || !m_width.fitsOnLine()) && !m_hangsAtEnd) {
940                     if (canHyphenate && !m_width.fitsOnLine()) {
941                         m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
942                             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, modifyMe, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
943                         });
944                         if (m_lineBreaker.m_hyphenated) {
945                             m_atEnd = true;
946                             return false;
947                         }
948                     }
949                     if (m_lineBreakHistory.atTextParagraphSeparator()) {
950                         if (!stoppedIgnoringSpaces && m_current.offset() > 0)
951                             ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
952                         m_lineBreakHistory.increment();
953                         m_lineInfo.setPreviousLineBrokeCleanly(true);
954                         wordMeasurement.endOffset = m_lineBreakHistory.offset();
955                     }
956                     // Check if the last breaking position is a soft-hyphen.
957                     if (!hyphenated && style.hyphens() != HyphensNone) {
958                         Optional<int> lastBreakingPositon;
959                         const RenderObject* rendererAtBreakingPosition = nullptr;
960                         if (m_lineBreakHistory.offset() || m_lineBreakHistory.nextBreakablePosition() > -1) {
961                             lastBreakingPositon = m_lineBreakHistory.offset();
962                             rendererAtBreakingPosition = m_lineBreakHistory.renderer();
963                         } else if (m_current.nextBreakablePosition() > -1 && (unsigned)m_current.nextBreakablePosition() <= m_current.offset()) {
964                             // We might just be right after the soft-hyphen
965                             lastBreakingPositon = m_current.nextBreakablePosition();
966                             rendererAtBreakingPosition = m_current.renderer();
967                         }
968                         if (lastBreakingPositon) {
969                             Optional<UChar> characterBeforeBreakingPosition;
970                             // When last breaking position points to the start of the current context, we need to look at the last character from
971                             // the previous non-empty text renderer.
972                             if (!lastBreakingPositon.value())
973                                 characterBeforeBreakingPosition = lastCharacterFromPreviousRenderText;
974                             else if (is<RenderText>(rendererAtBreakingPosition)) {
975                                 const auto& textRenderer = downcast<RenderText>(*rendererAtBreakingPosition);
976                                 ASSERT(textRenderer.textLength() > (unsigned)(lastBreakingPositon.value() - 1));
977                                 characterBeforeBreakingPosition = textRenderer.characterAt(lastBreakingPositon.value() - 1);
978                             }
979                             if (characterBeforeBreakingPosition)
980                                 hyphenated = characterBeforeBreakingPosition.value() == softHyphen;
981                         }
982                     }
983                     if (m_lineBreakHistory.offset() && m_lineBreakHistory.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) {
984                         if (charWidth) {
985                             wordMeasurement.endOffset = m_lineBreakHistory.offset();
986                             wordMeasurement.width = charWidth;
987                         }
988                     }
989                     // Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace.
990                     if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) {
991                         m_atEnd = true;
992                         return false;
993                     }
994                 } else {
995                     if (!betweenWords || (midWordBreak && !m_autoWrap))
996                         m_width.addUncommittedWidth(-additionalTempWidth);
997                     if (hyphenWidth) {
998                         // Subtract the width of the soft hyphen out since we fit on a line.
999                         m_width.addUncommittedWidth(-hyphenWidth);
1000                         hyphenWidth = 0;
1001                     }
1002                 }
1003             }
1004
1005             if (c == '\n' && m_preservesNewline) {
1006                 if (!stoppedIgnoringSpaces && m_current.offset())
1007                     ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
1008                 commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
1009                 m_lineBreakHistory.increment();
1010                 m_lineInfo.setPreviousLineBrokeCleanly(true);
1011                 return true;
1012             }
1013
1014             if (m_autoWrap && betweenWords) {
1015                 commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
1016                 wrapW = 0;
1017                 // Auto-wrapping text should not wrap in the middle of a word once it has had an
1018                 // opportunity to break after a word.
1019                 breakWords = false;
1020             }
1021
1022             if (midWordBreak && !U16_IS_TRAIL(c) && !(U_GET_GC_MASK(c) & U_GC_M_MASK)) {
1023                 // Remember this as a breakable position in case
1024                 // adding the end width forces a break.
1025                 m_lineBreakHistory.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
1026                 midWordBreak &= (breakWords || breakAll);
1027             }
1028
1029             if (betweenWords) {
1030                 lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
1031                 wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0;
1032                 lastSpace = m_current.offset();
1033             }
1034
1035             if (!m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) {
1036                 // If we encounter a newline, or if we encounter a second space,
1037                 // we need to break up this run and enter a mode where we start collapsing spaces.
1038                 if (m_currentCharacterIsSpace && previousCharacterIsSpace) {
1039                     m_ignoringSpaces = true;
1040
1041                     // We just entered a mode where we are ignoring
1042                     // spaces. Create a midpoint to terminate the run
1043                     // before the second space.
1044                     m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
1045                     m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, InlineIterator(), TrailingObjects::DoNotCollapseFirstSpace);
1046                 }
1047             }
1048         } else {
1049             if (m_ignoringSpaces) {
1050                 // Stop ignoring spaces and begin at this new point.
1051                 m_ignoringSpaces = false;
1052                 lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
1053                 wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurements.last().width) ? wordSpacing : 0;
1054                 lastSpace = m_current.offset(); // e.g., "Foo    goo", don't add in any of the ignored spaces.
1055                 m_lineMidpointState.stopIgnoringSpaces(InlineIterator(nullptr, m_current.renderer(), m_current.offset()));
1056             }
1057             if (m_hangsAtEnd && !renderText.isHangableStopOrComma(c))
1058                 m_hangsAtEnd = false;
1059         }
1060
1061         if (isSVGText && m_current.offset()) {
1062             // Force creation of new InlineBoxes for each absolute positioned character (those that start new text chunks).
1063             if (downcast<RenderSVGInlineText>(renderText).characterStartsNewTextChunk(m_current.offset()))
1064                 ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
1065         }
1066
1067         if (m_currentCharacterIsSpace && !previousCharacterIsSpace) {
1068             m_startOfIgnoredSpaces.setRenderer(m_current.renderer());
1069             m_startOfIgnoredSpaces.setOffset(m_current.offset());
1070             // Spaces after right-aligned text and before a line-break get collapsed away completely so that the trailing
1071             // space doesn't seem to push the text out from the right-hand edge.
1072             // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results.
1073             if (m_nextObject && m_startOfIgnoredSpaces.offset() && m_nextObject->isBR() && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) {
1074                 m_startOfIgnoredSpaces.setOffset(m_startOfIgnoredSpaces.offset() - 1);
1075                 // If there's just a single trailing space start ignoring it now so it collapses away.
1076                 if (m_current.offset() == renderText.textLength() - 1)
1077                     m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
1078             }
1079         }
1080
1081         if (!m_currentCharacterIsWS && previousCharacterIsWS) {
1082             if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace())
1083                 m_lineBreakHistory.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
1084         }
1085
1086         if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces)
1087             m_trailingObjects.setTrailingWhitespace(downcast<RenderText>(m_current.renderer()));
1088         else if (!m_currentStyle->collapseWhiteSpace() || !m_currentCharacterIsSpace)
1089             m_trailingObjects.clear();
1090
1091         m_atStart = false;
1092         nextCharacter(c, lastCharacter, secondToLastCharacter);
1093     }
1094
1095     m_renderTextInfo.lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
1096
1097     wordMeasurements.grow(wordMeasurements.size() + 1);
1098     WordMeasurement& wordMeasurement = wordMeasurements.last();
1099     wordMeasurement.renderer = &renderText;
1100
1101     // IMPORTANT: current.m_pos is > length here!
1102     float additionalTempWidth = m_ignoringSpaces ? 0 : textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
1103     wordMeasurement.startOffset = lastSpace;
1104     wordMeasurement.endOffset = m_current.offset();
1105     wordMeasurement.width = m_ignoringSpaces ? 0 : additionalTempWidth + wordSpacingForWordMeasurement;
1106     additionalTempWidth += lastSpaceWordSpacing;
1107
1108     float inlineLogicalTempWidth = inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, m_includeEndWidth);
1109     m_width.addUncommittedWidth(additionalTempWidth + inlineLogicalTempWidth);
1110     if (m_hangsAtEnd && inlineLogicalTempWidth)
1111         m_hangsAtEnd = false;
1112
1113     if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
1114         wordMeasurement.fallbackFonts.swap(fallbackFonts);
1115     fallbackFonts.clear();
1116
1117     if (m_collapseWhiteSpace && m_currentCharacterIsSpace && additionalTempWidth)
1118         m_width.setTrailingWhitespaceWidth(additionalTempWidth, inlineLogicalTempWidth);
1119
1120     m_includeEndWidth = false;
1121
1122     if (!fitsOnLineOrHangsAtEnd()) {
1123         if (canHyphenate) {
1124             m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
1125                 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, modifyMe, m_current.nextBreakablePosition(), m_lineBreaker.m_hyphenated);
1126             });
1127         }
1128
1129         if (!hyphenated && m_lineBreakHistory.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) {
1130             hyphenated = true;
1131             m_atEnd = true;
1132         }
1133     }
1134     return false;
1135 }
1136
1137 inline bool textBeginsWithBreakablePosition(RenderText& nextText)
1138 {
1139     UChar c = nextText.characterAt(0);
1140     return c == ' ' || c == '\t' || (c == '\n' && !nextText.preservesNewline());
1141 }
1142
1143 inline bool BreakingContext::canBreakAtThisPosition()
1144 {
1145     // If we are no-wrap and have found a line-breaking opportunity already then we should take it.
1146     if (m_width.committedWidth() && !m_width.fitsOnLine(m_currentCharacterIsSpace) && m_currWS == NOWRAP)
1147         return true;
1148
1149     // Avoid breaking on empty inlines.
1150     if (is<RenderInline>(*m_current.renderer()) && isEmptyInline(downcast<RenderInline>(*m_current.renderer())))
1151         return false;
1152
1153     // Avoid breaking before empty inlines (as long as the current object isn't replaced).
1154     if (!m_current.renderer()->isReplaced() && is<RenderInline>(m_nextObject) && isEmptyInline(downcast<RenderInline>(*m_nextObject)))
1155         return false;
1156
1157     // Return early if we autowrap and the current character is a space as we will always want to break at such a position.
1158     if (m_autoWrap && m_currentCharacterIsSpace)
1159         return true;
1160
1161     if (m_nextObject && m_nextObject->isLineBreakOpportunity())
1162         return m_autoWrap;
1163
1164     bool nextIsAutoWrappingText = is<RenderText>(m_nextObject) && (m_autoWrap || m_nextObject->style().autoWrap());
1165     if (!nextIsAutoWrappingText)
1166         return m_autoWrap;
1167     RenderText& nextRenderText = downcast<RenderText>(*m_nextObject);
1168     bool currentIsTextOrEmptyInline = is<RenderText>(*m_current.renderer()) || (is<RenderInline>(*m_current.renderer()) && isEmptyInline(downcast<RenderInline>(*m_current.renderer())));
1169     if (!currentIsTextOrEmptyInline)
1170         return m_autoWrap && !m_current.renderer()->isRubyRun();
1171
1172     bool canBreakHere = !m_currentCharacterIsSpace && textBeginsWithBreakablePosition(nextRenderText);
1173
1174     // See if attempting to fit below floats creates more available width on the line.
1175     if (!m_width.fitsOnLine() && !m_width.hasCommitted())
1176         m_width.fitBelowFloats(m_lineInfo.isFirstLine());
1177
1178     bool canPlaceOnLine = m_width.fitsOnLine() || !m_autoWrapWasEverTrueOnLine;
1179
1180     if (canPlaceOnLine && canBreakHere)
1181         commitLineBreakAtCurrentWidth(nextRenderText);
1182
1183     return canBreakHere;
1184 }
1185
1186 inline void BreakingContext::commitAndUpdateLineBreakIfNeeded()
1187 {
1188     bool checkForBreak = canBreakAtThisPosition();
1189
1190     if (checkForBreak && !m_width.fitsOnLine(m_ignoringSpaces) && !m_hangsAtEnd) {
1191         // if we have floats, try to get below them.
1192         if (m_currentCharacterIsSpace && !m_ignoringSpaces && m_currentStyle->collapseWhiteSpace())
1193             m_trailingObjects.clear();
1194
1195         if (m_width.committedWidth()) {
1196             m_atEnd = true;
1197             return;
1198         }
1199
1200         if (!m_hangsAtEnd)
1201             m_width.fitBelowFloats(m_lineInfo.isFirstLine());
1202
1203         // |width| may have been adjusted because we got shoved down past a float (thus
1204         // giving us more room), so we need to retest, and only jump to
1205         // the end label if we still don't fit on the line. -dwh
1206         if (!m_width.fitsOnLine(m_ignoringSpaces)) {
1207             m_atEnd = true;
1208             return;
1209         }
1210     } else if (m_blockStyle.autoWrap() && !m_width.fitsOnLine() && !m_width.hasCommitted() && !m_hangsAtEnd) {
1211         // If the container autowraps but the current child does not then we still need to ensure that it
1212         // wraps and moves below any floats.
1213         m_width.fitBelowFloats(m_lineInfo.isFirstLine());
1214     }
1215
1216     if (!m_current.renderer()->isFloatingOrOutOfFlowPositioned()) {
1217         m_lastObject = m_current.renderer();
1218         if (m_lastObject->isReplaced() && m_autoWrap && !m_lastObject->isRubyRun() && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!is<RenderListMarker>(*m_lastObject) || downcast<RenderListMarker>(*m_lastObject).isInside()))
1219             commitLineBreakAtCurrentWidth(*m_nextObject);
1220     }
1221 }
1222
1223 inline TrailingObjects::CollapseFirstSpaceOrNot checkMidpoints(LineMidpointState& lineMidpointState, const InlineIterator& lBreak)
1224 {
1225     // Check to see if our last midpoint is a start point beyond the line break. If so,
1226     // shave it off the list, and shave off a trailing space if the previous end point doesn't
1227     // preserve whitespace.
1228     if (lBreak.renderer() && lineMidpointState.numMidpoints() && !(lineMidpointState.numMidpoints() % 2)) {
1229         InlineIterator* midpoints = lineMidpointState.midpoints().data();
1230         InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints() - 2];
1231         const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints() - 1];
1232         InlineIterator currpoint = endpoint;
1233         while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak)
1234             currpoint.increment();
1235         if (currpoint == lBreak) {
1236             // We hit the line break before the start point. Shave off the start point.
1237             lineMidpointState.decrementNumMidpoints();
1238             if (endpoint.renderer()->style().collapseWhiteSpace() && endpoint.renderer()->isText()) {
1239                 endpoint.fastDecrement();
1240                 return TrailingObjects::DoNotCollapseFirstSpace;
1241             }
1242         }
1243     }
1244     return TrailingObjects::CollapseFirstSpace;
1245 }
1246
1247 inline InlineIterator BreakingContext::handleEndOfLine()
1248 {
1249     if (m_lineBreakHistory.current() == m_resolver.position()) {
1250         if (!m_lineBreakHistory.renderer() || !m_lineBreakHistory.renderer()->isBR()) {
1251             // we just add as much as possible
1252             if (m_blockStyle.whiteSpace() == PRE && !m_current.offset())
1253                 commitLineBreakAtCurrentWidth(*m_lastObject, m_lastObject->isText() ? m_lastObject->length() : 0);
1254             else if (m_lineBreakHistory.renderer()) {
1255                 // Don't ever break in the middle of a word if we can help it.
1256                 // There's no room at all. We just have to be on this line,
1257                 // even though we'll spill out.
1258                 commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset());
1259             }
1260         }
1261         // make sure we consume at least one char/object.
1262         if (m_lineBreakHistory.current() == m_resolver.position())
1263             m_lineBreakHistory.increment();
1264     } else if (!m_current.offset() && !m_width.committedWidth() && m_width.uncommittedWidth() && !m_hadUncommittedWidthBeforeCurrent) {
1265         // Do not push the current object to the next line, when this line has some content, but it is still considered empty.
1266         // Empty inline elements like <span></span> can produce such lines and now we just ignore these break opportunities
1267         // at the start of a line, if no width has been committed yet.
1268         // Behave as if it was actually empty and consume at least one object.
1269         m_lineBreakHistory.increment();
1270     }
1271
1272     // Sanity check our midpoints.
1273     TrailingObjects::CollapseFirstSpaceOrNot collapsed = checkMidpoints(m_lineMidpointState, m_lineBreakHistory.current());
1274
1275     m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, m_lineBreakHistory.current(), collapsed);
1276
1277     // We might have made lineBreak an iterator that points past the end
1278     // of the object. Do this adjustment to make it point to the start
1279     // of the next object instead to avoid confusing the rest of the
1280     // code.
1281     if (m_lineBreakHistory.offset()) {
1282         m_lineBreakHistory.update([](InlineIterator& modifyMe) {
1283             modifyMe.setOffset(modifyMe.offset() - 1);
1284             modifyMe.increment();
1285         });
1286     }
1287
1288 #if ENABLE(CSS_TRAILING_WORD)
1289     if (m_blockStyle.trailingWord() == TrailingWord::PartiallyBalanced)
1290         return optimalLineBreakLocationForTrailingWord();
1291 #endif
1292     return m_lineBreakHistory.current();
1293 }
1294
1295 #if ENABLE(CSS_TRAILING_WORD)
1296 inline InlineIterator BreakingContext::optimalLineBreakLocationForTrailingWord()
1297 {
1298     const unsigned longTrailingWordLength = 20;
1299     const float optimalTrailingLineRatio = 0.1;
1300     InlineIterator lineBreak = m_lineBreakHistory.current();
1301     if (!lineBreak.renderer() || !m_lineInfo.isFirstLine() || bidiNextSkippingEmptyInlines(*lineBreak.root(), lineBreak.renderer()) || !is<RenderText>(lineBreak.renderer()))
1302         return lineBreak;
1303     RenderText& renderText = downcast<RenderText>(*lineBreak.renderer());
1304     // Don't even bother measuring if our remaining line has many characters
1305     if (renderText.textLength() == lineBreak.offset() || renderText.textLength() - lineBreak.offset() > longTrailingWordLength)
1306         return lineBreak;
1307     bool isLooseCJKMode = m_renderTextInfo.text != &renderText && m_renderTextInfo.lineBreakIterator.isLooseCJKMode();
1308     bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE;
1309     int nextBreakablePosition = lineBreak.nextBreakablePosition();
1310     isBreakable(m_renderTextInfo.lineBreakIterator, lineBreak.offset() + 1, nextBreakablePosition, breakNBSP, isLooseCJKMode, m_currentStyle->wordBreak() == KeepAllWordBreak);
1311     if (nextBreakablePosition < 0 || static_cast<unsigned>(nextBreakablePosition) != renderText.textLength())
1312         return lineBreak;
1313     const RenderStyle& style = lineStyle(renderText, m_lineInfo);
1314     const FontCascade& font = style.fontCascade();
1315     HashSet<const Font*> dummyFonts;
1316     InlineIterator best = lineBreak;
1317     for (size_t i = 1; i < m_lineBreakHistory.historyLength(); ++i) {
1318         const InlineIterator& candidate = m_lineBreakHistory.get(i);
1319         if (candidate.renderer() != lineBreak.renderer())
1320             return best;
1321         float width = textWidth(renderText, candidate.offset(), renderText.textLength() - candidate.offset(), font, 0, font.isFixedPitch(), m_collapseWhiteSpace, dummyFonts);
1322         if (width > m_width.availableWidth())
1323             return best;
1324         if (width / m_width.availableWidth() > optimalTrailingLineRatio) // Subsequent line is long enough
1325             return candidate;
1326         best = candidate;
1327     }
1328     return best;
1329 }
1330 #endif
1331
1332 }
1333
1334 #endif // BreakingContext_h