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