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