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.
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.
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.
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.
25 #ifndef BreakingContext_h
26 #define BreakingContext_h
28 #include "Hyphenation.h"
29 #include "LineBreaker.h"
31 #include "LineWidth.h"
32 #include "RenderCombineText.h"
33 #include "RenderCounter.h"
34 #include "RenderInline.h"
35 #include "RenderLineBreak.h"
36 #include "RenderListMarker.h"
37 #include "RenderRubyRun.h"
38 #include "RenderSVGInlineText.h"
39 #include "TrailingObjects.h"
40 #include "break_lines.h"
41 #include <wtf/Optional.h>
42 #include <wtf/text/StringView.h>
43 #include <wtf/unicode/CharacterNames.h>
47 // We don't let our line box tree for a single line get any deeper than this.
48 const unsigned cMaxLineDepth = 200;
50 struct WordMeasurement {
63 HashSet<const Font*> fallbackFonts;
66 struct WordTrailingSpace {
67 WordTrailingSpace(RenderText& renderer, const RenderStyle& style, TextLayout* textLayout = nullptr)
68 : m_renderer(renderer)
70 , m_textLayout(textLayout)
74 WTF::Optional<float> width(HashSet<const Font*>& fallbackFonts)
76 if (m_state == WordTrailingSpaceState::Computed)
79 const FontCascade& font = m_style.fontCascade();
80 if ((font.typesettingFeatures() & Kerning) && !m_textLayout)
81 m_width = font.width(RenderBlock::constructTextRun(&m_renderer, font, &space, 1, m_style), &fallbackFonts) + font.wordSpacing();
82 m_state = WordTrailingSpaceState::Computed;
86 enum class WordTrailingSpaceState { Uninitialized, Computed };
87 WordTrailingSpaceState m_state { WordTrailingSpaceState::Uninitialized };
88 WTF::Optional<float> m_width;
89 RenderText& m_renderer;
90 const RenderStyle& m_style;
91 TextLayout* m_textLayout { nullptr };
94 class BreakingContext {
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 #if ENABLE(CSS_TRAILING_WORD)
101 , m_lineBreakHistory(InlineIterator(resolver.position()), block.style().trailingWord() == TrailingWord::PartiallyBalanced ? 5 : 1)
103 , m_lineBreakHistory(InlineIterator(resolver.position()), 1)
106 , m_lastObject(m_current.renderer())
107 , m_nextObject(nullptr)
108 , m_currentStyle(nullptr)
109 , m_blockStyle(block.style())
110 , m_lineInfo(inLineInfo)
111 , m_renderTextInfo(inRenderTextInfo)
112 , m_lastFloatFromPreviousLine(inLastFloatFromPreviousLine)
116 , m_preservesNewline(false)
118 , m_ignoringSpaces(false)
119 , m_currentCharacterIsSpace(false)
120 , m_currentCharacterIsWS(false)
121 , m_appliedStartWidth(appliedStartWidth)
122 , m_includeEndWidth(true)
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())
130 , m_hadUncommittedWidthBeforeCurrent(false)
131 , m_lineMidpointState(resolver.midpointState())
133 m_lineInfo.setPreviousLineBrokeCleanly(false);
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; }
141 void initializeForCurrentObject();
145 void handleBR(EClear&);
146 void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects);
148 void handleEmptyInline();
149 void handleReplaced();
150 bool handleText(WordMeasurements&, bool& hyphenated, unsigned& consecutiveHyphenatedLines);
151 bool canBreakAtThisPosition();
152 void commitAndUpdateLineBreakIfNeeded();
153 InlineIterator handleEndOfLine();
154 #if ENABLE(CSS_TRAILING_WORD)
155 InlineIterator optimalLineBreakLocationForTrailingWord();
158 void clearLineBreakIfFitsOnLine(bool ignoringTrailingSpace = false)
160 if (m_width.fitsOnLine(ignoringTrailingSpace) || m_lastWS == NOWRAP)
161 m_lineBreakHistory.clear();
164 void commitLineBreakAtCurrentWidth(RenderObject& object, unsigned offset = 0, int nextBreak = -1)
167 m_lineBreakHistory.moveTo(&object, offset, nextBreak);
171 // This class keeps a sliding window of the past n locations for an InlineIterator.
172 class InlineIteratorHistory : private Vector<InlineIterator, 1> {
174 InlineIteratorHistory() = delete;
175 InlineIteratorHistory(const InlineIterator& initial, size_t capacity)
176 : m_capacity(capacity)
178 ASSERT(capacity > 0);
179 this->append(initial);
182 void push(std::function<void(InlineIterator& modifyMe)> updater)
184 ASSERT(!this->isEmpty());
186 this->insert(0, InlineIterator(this->at(0)));
187 updater(this->at(0));
189 this->resize(m_capacity);
192 void update(std::function<void(InlineIterator& modifyMe)> updater)
194 ASSERT(!this->isEmpty());
195 updater(this->at(0));
198 RenderObject* renderer() const { return this->at(0).renderer(); }
199 unsigned offset() const { return this->at(0).offset(); }
200 bool atTextParagraphSeparator() const { return this->at(0).atTextParagraphSeparator(); }
201 UChar previousInSameNode() const { return this->at(0).previousInSameNode(); }
202 const InlineIterator& get(size_t i) const { return this->at(i); };
203 const InlineIterator& current() const { return get(0); }
204 size_t historyLength() const { return this->size(); }
206 void moveTo(RenderObject* object, unsigned offset, int nextBreak = -1)
208 push([&](InlineIterator& modifyMe) {
209 modifyMe.moveTo(object, offset, nextBreak);
215 update([](InlineIterator& modifyMe) {
216 modifyMe.increment();
222 push([](InlineIterator& modifyMe) {
228 const size_t m_capacity;
231 LineBreaker& m_lineBreaker;
232 InlineBidiResolver& m_resolver;
234 InlineIterator m_current;
235 InlineIteratorHistory m_lineBreakHistory;
236 InlineIterator m_startOfIgnoredSpaces;
238 RenderBlockFlow& m_block;
239 RenderObject* m_lastObject;
240 RenderObject* m_nextObject;
242 const RenderStyle* m_currentStyle;
244 // Firefox and Opera will allow a table cell to grow to fit an image inside it under
245 // very specific circumstances (in order to match common WinIE renderings).
246 // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.)
247 RenderStyle& m_blockStyle;
249 LineInfo& m_lineInfo;
251 RenderTextInfo& m_renderTextInfo;
253 FloatingObject* m_lastFloatFromPreviousLine;
257 EWhiteSpace m_currWS;
258 EWhiteSpace m_lastWS;
260 bool m_preservesNewline;
263 // This variable is used only if whitespace isn't set to PRE, and it tells us whether
264 // or not we are currently ignoring whitespace.
265 bool m_ignoringSpaces;
267 // This variable tracks whether the very last character we saw was a space. We use
268 // this to detect when we encounter a second space so we know we have to terminate
270 bool m_currentCharacterIsSpace;
271 bool m_currentCharacterIsWS;
272 bool m_appliedStartWidth;
273 bool m_includeEndWidth;
275 bool m_autoWrapWasEverTrueOnLine;
276 bool m_floatsFitOnLine;
277 bool m_collapseWhiteSpace;
278 bool m_startingNewParagraph;
279 bool m_allowImagesToBreak;
281 bool m_hadUncommittedWidthBeforeCurrent;
283 LineMidpointState& m_lineMidpointState;
285 TrailingObjects m_trailingObjects;
288 inline void BreakingContext::initializeForCurrentObject()
290 m_hadUncommittedWidthBeforeCurrent = !!m_width.uncommittedWidth();
292 m_currentStyle = &m_current.renderer()->style(); // FIXME: Should this be &lineStyle(*m_current.renderer(), m_lineInfo); ?
294 ASSERT(m_currentStyle);
296 m_nextObject = bidiNextSkippingEmptyInlines(m_block, m_current.renderer());
297 if (m_nextObject && m_nextObject->parent() && !m_nextObject->parent()->isDescendantOf(m_current.renderer()->parent()))
298 m_includeEndWidth = true;
300 m_currWS = m_current.renderer()->isReplaced() ? m_current.renderer()->parent()->style().whiteSpace() : m_currentStyle->whiteSpace();
301 m_lastWS = m_lastObject->isReplaced() ? m_lastObject->parent()->style().whiteSpace() : m_lastObject->style().whiteSpace();
303 m_autoWrap = RenderStyle::autoWrap(m_currWS);
304 m_autoWrapWasEverTrueOnLine = m_autoWrapWasEverTrueOnLine || m_autoWrap;
306 m_preservesNewline = m_current.renderer()->isSVGInlineText() ? false : RenderStyle::preserveNewline(m_currWS);
308 m_collapseWhiteSpace = RenderStyle::collapseWhiteSpace(m_currWS);
311 inline void BreakingContext::increment()
313 // Clear out our character space bool, since inline <pre>s don't collapse whitespace
314 // with adjacent inline normal/nowrap spans.
315 if (!m_collapseWhiteSpace)
316 m_currentCharacterIsSpace = false;
318 m_current.moveToStartOf(m_nextObject);
322 inline void BreakingContext::handleBR(EClear& clear)
324 if (m_width.fitsOnLine()) {
325 RenderObject* br = m_current.renderer();
326 m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
327 modifyMe.moveToStartOf(br);
328 modifyMe.increment();
331 // A <br> always breaks a line, so don't let the line be collapsed
332 // away. Also, the space at the end of a line with a <br> does not
333 // get collapsed away. It only does this if the previous line broke
334 // cleanly. Otherwise the <br> has no effect on whether the line is
336 if (m_startingNewParagraph)
337 m_lineInfo.setEmpty(false, &m_block, &m_width);
338 m_trailingObjects.clear();
339 m_lineInfo.setPreviousLineBrokeCleanly(true);
341 // A <br> with clearance always needs a linebox in case the lines below it get dirtied later and
342 // need to check for floats to clear - so if we're ignoring spaces, stop ignoring them and add a
343 // run for this object.
344 if (m_ignoringSpaces && m_currentStyle->clear() != CNONE)
345 m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(br);
346 // If we were preceded by collapsing space and are in a right-aligned container we need to ensure the space gets
347 // collapsed away so that it doesn't push the text out from the container's right-hand edge.
348 // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results.
349 else if (m_ignoringSpaces && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT))
350 m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
352 if (!m_lineInfo.isEmpty())
353 clear = m_currentStyle->clear();
358 inline LayoutUnit borderPaddingMarginStart(const RenderInline& child)
360 return child.marginStart() + child.paddingStart() + child.borderStart();
363 inline LayoutUnit borderPaddingMarginEnd(const RenderInline& child)
365 return child.marginEnd() + child.paddingEnd() + child.borderEnd();
368 inline bool shouldAddBorderPaddingMargin(RenderObject* child)
372 // When deciding whether we're at the edge of an inline, adjacent collapsed whitespace is the same as no sibling at all.
373 if (is<RenderText>(*child) && !downcast<RenderText>(*child).textLength())
375 #if ENABLE(CSS_BOX_DECORATION_BREAK)
376 if (is<RenderLineBreak>(*child) && child->parent()->style().boxDecorationBreak() == DCLONE)
382 inline RenderObject* previousInFlowSibling(RenderObject* child)
385 child = child->previousSibling();
386 } while (child && child->isOutOfFlowPositioned());
390 inline LayoutUnit inlineLogicalWidth(RenderObject* child, bool checkStartEdge = true, bool checkEndEdge = true)
392 unsigned lineDepth = 1;
393 LayoutUnit extraWidth = 0;
394 RenderElement* parent = child->parent();
395 while (is<RenderInline>(*parent) && lineDepth++ < cMaxLineDepth) {
396 const auto& parentAsRenderInline = downcast<RenderInline>(*parent);
397 if (!isEmptyInline(parentAsRenderInline)) {
398 checkStartEdge = checkStartEdge && shouldAddBorderPaddingMargin(previousInFlowSibling(child));
400 extraWidth += borderPaddingMarginStart(parentAsRenderInline);
401 checkEndEdge = checkEndEdge && shouldAddBorderPaddingMargin(child->nextSibling());
403 extraWidth += borderPaddingMarginEnd(parentAsRenderInline);
404 if (!checkStartEdge && !checkEndEdge)
408 parent = child->parent();
413 inline void BreakingContext::handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects)
415 // If our original display wasn't an inline type, then we can determine our static inline position now.
416 auto& box = downcast<RenderBox>(*m_current.renderer());
417 bool isInlineType = box.style().isOriginalDisplayInlineType();
419 m_block.setStaticInlinePositionForChild(box, m_block.logicalHeight(), m_block.startOffsetForContent(m_block.logicalHeight()));
421 // If our original display was an INLINE type, then we can determine our static y position now.
422 box.layer()->setStaticBlockPosition(m_block.logicalHeight());
425 // If we're ignoring spaces, we have to stop and include this object and
426 // then start ignoring spaces again.
427 if (isInlineType || box.container()->isRenderInline()) {
428 if (m_ignoringSpaces)
429 m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(&box);
430 m_trailingObjects.appendBoxIfNeeded(&box);
432 positionedObjects.append(&box);
434 m_width.addUncommittedWidth(inlineLogicalWidth(&box));
435 // Reset prior line break context characters.
436 m_renderTextInfo.lineBreakIterator.resetPriorContext();
439 inline void BreakingContext::handleFloat()
441 auto& floatBox = downcast<RenderBox>(*m_current.renderer());
442 FloatingObject* floatingObject = m_lineBreaker.insertFloatingObject(floatBox);
443 // check if it fits in the current line.
444 // If it does, position it now, otherwise, position
445 // it after moving to next line (in clearFloats() func)
446 if (m_floatsFitOnLine && m_width.fitsOnLineExcludingTrailingWhitespace(m_block.logicalWidthForFloat(floatingObject))) {
447 m_lineBreaker.positionNewFloatOnLine(floatingObject, m_lastFloatFromPreviousLine, m_lineInfo, m_width);
448 if (m_lineBreakHistory.renderer() == m_current.renderer()) {
449 ASSERT(!m_lineBreakHistory.offset());
450 m_lineBreakHistory.increment();
453 m_floatsFitOnLine = false;
454 // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for floating element.
455 m_renderTextInfo.lineBreakIterator.updatePriorContext(replacementCharacter);
458 // This is currently just used for list markers and inline flows that have line boxes. Neither should
459 // have an effect on whitespace at the start of the line.
460 inline bool shouldSkipWhitespaceAfterStartObject(RenderBlockFlow& block, RenderObject* o, LineMidpointState& lineMidpointState)
462 RenderObject* next = bidiNextSkippingEmptyInlines(block, o);
463 while (next && next->isFloatingOrOutOfFlowPositioned())
464 next = bidiNextSkippingEmptyInlines(block, next);
466 if (is<RenderText>(next) && downcast<RenderText>(*next).textLength() > 0) {
467 RenderText& nextText = downcast<RenderText>(*next);
468 UChar nextChar = nextText.characterAt(0);
469 if (nextText.style().isCollapsibleWhiteSpace(nextChar)) {
470 lineMidpointState.startIgnoringSpaces(InlineIterator(nullptr, o, 0));
478 inline void BreakingContext::handleEmptyInline()
480 RenderInline& flowBox = downcast<RenderInline>(*m_current.renderer());
482 // This should only end up being called on empty inlines
483 ASSERT(isEmptyInline(flowBox));
485 // Now that some inline flows have line boxes, if we are already ignoring spaces, we need
486 // to make sure that we stop to include this object and then start ignoring spaces again.
487 // If this object is at the start of the line, we need to behave like list markers and
488 // start ignoring spaces.
489 bool requiresLineBox = alwaysRequiresLineBox(flowBox);
490 if (requiresLineBox || requiresLineBoxForContent(flowBox, m_lineInfo)) {
491 // An empty inline that only has line-height, vertical-align or font-metrics will only get a
492 // line box to affect the height of the line if the rest of the line is not empty.
494 m_lineInfo.setEmpty(false, &m_block, &m_width);
495 if (m_ignoringSpaces) {
496 m_trailingObjects.clear();
497 m_lineMidpointState.ensureLineBoxInsideIgnoredSpaces(m_current.renderer());
498 } else if (m_blockStyle.collapseWhiteSpace() && m_resolver.position().renderer() == m_current.renderer()
499 && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
500 // Like with list markers, we start ignoring spaces to make sure that any
501 // additional spaces we see will be discarded.
502 m_currentCharacterIsSpace = true;
503 m_currentCharacterIsWS = true;
504 m_ignoringSpaces = true;
506 m_trailingObjects.appendBoxIfNeeded(&flowBox);
509 m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer()) + borderPaddingMarginStart(flowBox) + borderPaddingMarginEnd(flowBox));
512 inline void BreakingContext::handleReplaced()
514 auto& replacedBox = downcast<RenderBox>(*m_current.renderer());
517 m_width.updateAvailableWidth(replacedBox.logicalHeight());
519 // Break on replaced elements if either has normal white-space.
520 if (((m_autoWrap || RenderStyle::autoWrap(m_lastWS)) && (!m_current.renderer()->isImage() || m_allowImagesToBreak)
521 && (!m_current.renderer()->isRubyRun() || downcast<RenderRubyRun>(m_current.renderer())->canBreakBefore(m_renderTextInfo.lineBreakIterator))) || replacedBox.isAnonymousInlineBlock()) {
522 commitLineBreakAtCurrentWidth(*m_current.renderer());
523 if (m_width.committedWidth() && replacedBox.isAnonymousInlineBlock()) {
524 // Always force a break before an anonymous inline block if there is content on the line
531 if (replacedBox.isAnonymousInlineBlock())
532 replacedBox.layoutIfNeeded();
534 if (m_ignoringSpaces)
535 m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), 0));
537 m_lineInfo.setEmpty(false, &m_block, &m_width);
538 m_ignoringSpaces = false;
539 m_currentCharacterIsSpace = false;
540 m_currentCharacterIsWS = false;
541 m_trailingObjects.clear();
543 // Optimize for a common case. If we can't find whitespace after the list
544 // item, then this is all moot.
545 LayoutUnit replacedLogicalWidth = m_block.logicalWidthForChild(replacedBox) + m_block.marginStartForChild(replacedBox) + m_block.marginEndForChild(replacedBox) + inlineLogicalWidth(m_current.renderer());
546 if (is<RenderListMarker>(*m_current.renderer())) {
547 if (m_blockStyle.collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(m_block, m_current.renderer(), m_lineMidpointState)) {
548 // Like with inline flows, we start ignoring spaces to make sure that any
549 // additional spaces we see will be discarded.
550 m_currentCharacterIsSpace = true;
551 m_currentCharacterIsWS = false;
552 m_ignoringSpaces = true;
554 if (downcast<RenderListMarker>(*m_current.renderer()).isInside())
555 m_width.addUncommittedWidth(replacedLogicalWidth);
557 m_width.addUncommittedWidth(replacedLogicalWidth);
558 if (is<RenderRubyRun>(*m_current.renderer())) {
559 m_width.applyOverhang(downcast<RenderRubyRun>(m_current.renderer()), m_lastObject, m_nextObject);
560 downcast<RenderRubyRun>(m_current.renderer())->updatePriorContextFromCachedBreakIterator(m_renderTextInfo.lineBreakIterator);
562 // Update prior line break context characters, using U+FFFD (OBJECT REPLACEMENT CHARACTER) for replaced element.
563 m_renderTextInfo.lineBreakIterator.updatePriorContext(replacementCharacter);
566 if (replacedBox.isAnonymousInlineBlock()) {
568 m_lineInfo.setPreviousLineBrokeCleanly(true);
572 inline float firstPositiveWidth(const WordMeasurements& wordMeasurements)
574 for (size_t i = 0; i < wordMeasurements.size(); ++i) {
575 if (wordMeasurements[i].width > 0)
576 return wordMeasurements[i].width;
581 inline bool iteratorIsBeyondEndOfRenderCombineText(const InlineIterator& iter, RenderCombineText& renderer)
583 return iter.renderer() == &renderer && iter.offset() >= renderer.textLength();
586 inline void nextCharacter(UChar& currentCharacter, UChar& lastCharacter, UChar& secondToLastCharacter)
588 secondToLastCharacter = lastCharacter;
589 lastCharacter = currentCharacter;
592 // FIXME: Don't let counters mark themselves as needing pref width recalcs during layout
593 // so we don't need this hack.
594 inline void updateCounterIfNeeded(RenderText& renderText)
596 if (!renderText.preferredLogicalWidthsDirty() || !is<RenderCounter>(renderText))
598 downcast<RenderCounter>(renderText).updateCounter();
601 inline float measureHyphenWidth(RenderText& renderer, const FontCascade& font, HashSet<const Font*>* fallbackFonts = 0)
603 const RenderStyle& style = renderer.style();
604 return font.width(RenderBlock::constructTextRun(&renderer, font, style.hyphenString().string(), style), fallbackFonts);
607 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)
609 const RenderStyle& style = text.style();
611 GlyphOverflow glyphOverflow;
612 if (isFixedPitch || (!from && len == text.textLength()) || style.hasTextCombine())
613 return text.width(from, len, font, xPos, &fallbackFonts, &glyphOverflow);
616 return FontCascade::width(*layout, from, len, &fallbackFonts);
618 TextRun run = RenderBlock::constructTextRun(&text, font, &text, from, len, style);
619 run.setCharactersLength(text.textLength() - from);
620 ASSERT(run.charactersLength() >= run.length());
622 run.setCharacterScanForCodePath(!text.canUseSimpleFontCodePath());
623 run.setTabSize(!collapseWhiteSpace, style.tabSize());
625 return font.width(run, &fallbackFonts, &glyphOverflow);
628 // Adding a pair of midpoints before a character will split it out into a new line box.
629 inline void ensureCharacterGetsLineBox(LineMidpointState& lineMidpointState, InlineIterator& textParagraphSeparator)
631 InlineIterator midpoint(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset());
632 lineMidpointState.startIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset() - 1));
633 lineMidpointState.stopIgnoringSpaces(InlineIterator(0, textParagraphSeparator.renderer(), textParagraphSeparator.offset()));
636 inline void tryHyphenating(RenderText& text, const FontCascade& font, const AtomicString& localeIdentifier, unsigned consecutiveHyphenatedLines, int consecutiveHyphenatedLinesLimit, int minimumPrefixLimit, int minimumSuffixLimit, unsigned lastSpace, unsigned pos, float xPos, int availableWidth, bool isFixedPitch, bool collapseWhiteSpace, int lastSpaceWordSpacing, InlineIterator& lineBreak, int nextBreakable, bool& hyphenated)
638 // Map 'hyphenate-limit-{before,after}: auto;' to 2.
639 unsigned minimumPrefixLength;
640 unsigned minimumSuffixLength;
642 if (minimumPrefixLimit < 0)
643 minimumPrefixLength = 2;
645 minimumPrefixLength = static_cast<unsigned>(minimumPrefixLimit);
647 if (minimumSuffixLimit < 0)
648 minimumSuffixLength = 2;
650 minimumSuffixLength = static_cast<unsigned>(minimumSuffixLimit);
652 if (pos - lastSpace <= minimumSuffixLength)
655 if (consecutiveHyphenatedLinesLimit >= 0 && consecutiveHyphenatedLines >= static_cast<unsigned>(consecutiveHyphenatedLinesLimit))
658 int hyphenWidth = measureHyphenWidth(text, font);
660 float maxPrefixWidth = availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing;
661 // If the maximum width available for the prefix before the hyphen is small, then it is very unlikely
662 // that an hyphenation opportunity exists, so do not bother to look for it.
663 if (maxPrefixWidth <= font.pixelSize() * 5 / 4)
666 const RenderStyle& style = text.style();
667 TextRun run = RenderBlock::constructTextRun(&text, font, &text, lastSpace, pos - lastSpace, style);
668 run.setCharactersLength(text.textLength() - lastSpace);
669 ASSERT(run.charactersLength() >= run.length());
671 run.setTabSize(!collapseWhiteSpace, style.tabSize());
672 run.setXPos(xPos + lastSpaceWordSpacing);
674 unsigned prefixLength = font.offsetForPosition(run, maxPrefixWidth, false);
675 if (prefixLength < minimumPrefixLength)
678 prefixLength = lastHyphenLocation(StringView(text.text()).substring(lastSpace, pos - lastSpace), std::min(prefixLength, pos - lastSpace - minimumSuffixLength) + 1, localeIdentifier);
679 if (!prefixLength || prefixLength < minimumPrefixLength)
682 // When lastSpace is a space, which it always is except sometimes at the beginning of a line or after collapsed
683 // space, it should not count towards hyphenate-limit-before.
684 if (prefixLength == minimumPrefixLength) {
685 UChar characterAtLastSpace = text.characterAt(lastSpace);
686 if (characterAtLastSpace == ' ' || characterAtLastSpace == '\n' || characterAtLastSpace == '\t' || characterAtLastSpace == noBreakSpace)
690 ASSERT(pos - lastSpace - prefixLength >= minimumSuffixLength);
693 HashSet<const Font*> fallbackFonts;
694 float prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace, fallbackFonts) + lastSpaceWordSpacing;
695 ASSERT(xPos + prefixWidth <= availableWidth);
697 UNUSED_PARAM(isFixedPitch);
700 lineBreak.moveTo(&text, lastSpace + prefixLength, nextBreakable);
704 inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated, unsigned& consecutiveHyphenatedLines)
706 if (!m_current.offset())
707 m_appliedStartWidth = false;
709 RenderText& renderText = downcast<RenderText>(*m_current.renderer());
711 bool isSVGText = renderText.isSVGInlineText();
713 // If we have left a no-wrap inline and entered an autowrap inline while ignoring spaces
714 // then we need to mark the start of the autowrap inline as a potential linebreak now.
715 if (m_autoWrap && !RenderStyle::autoWrap(m_lastWS) && m_ignoringSpaces)
716 commitLineBreakAtCurrentWidth(renderText);
718 if (renderText.style().hasTextCombine() && is<RenderCombineText>(*m_current.renderer())) {
719 auto& combineRenderer = downcast<RenderCombineText>(*m_current.renderer());
720 combineRenderer.combineText();
721 // The length of the renderer's text may have changed. Increment stale iterator positions
722 if (iteratorIsBeyondEndOfRenderCombineText(m_lineBreakHistory.current(), combineRenderer)) {
723 ASSERT(iteratorIsBeyondEndOfRenderCombineText(m_resolver.position(), combineRenderer));
724 m_lineBreakHistory.increment();
725 m_resolver.increment();
729 const RenderStyle& style = lineStyle(renderText, m_lineInfo);
730 const FontCascade& font = style.fontCascade();
731 bool isFixedPitch = font.isFixedPitch();
732 bool canHyphenate = style.hyphens() == HyphensAuto && WebCore::canHyphenate(style.locale());
734 unsigned lastSpace = m_current.offset();
735 float wordSpacing = m_currentStyle->fontCascade().wordSpacing();
736 float lastSpaceWordSpacing = 0;
737 float wordSpacingForWordMeasurement = 0;
739 float wrapW = m_width.uncommittedWidth() + inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, true);
741 bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE;
742 // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word,
743 // which is only possible if the word is the first thing on the line, that is, if |w| is zero.
744 bool breakWords = m_currentStyle->breakWords() && ((m_autoWrap && !m_width.committedWidth()) || m_currWS == PRE);
745 bool midWordBreak = false;
746 bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap;
747 bool keepAllWords = m_currentStyle->wordBreak() == KeepAllWordBreak;
748 float hyphenWidth = 0;
749 bool isLooseCJKMode = false;
756 if (m_renderTextInfo.text != &renderText) {
757 updateCounterIfNeeded(renderText);
758 m_renderTextInfo.text = &renderText;
759 m_renderTextInfo.font = &font;
760 m_renderTextInfo.layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
761 m_renderTextInfo.lineBreakIterator.resetStringAndReleaseIterator(renderText.text(), style.locale(), mapLineBreakToIteratorMode(m_blockStyle.lineBreak()));
762 isLooseCJKMode = m_renderTextInfo.lineBreakIterator.isLooseCJKMode();
763 } else if (m_renderTextInfo.layout && m_renderTextInfo.font != &font) {
764 m_renderTextInfo.font = &font;
765 m_renderTextInfo.layout = font.createLayout(renderText, m_width.currentWidth(), m_collapseWhiteSpace);
768 TextLayout* textLayout = m_renderTextInfo.layout.get();
770 // Non-zero only when kerning is enabled and TextLayout isn't used, in which case we measure
771 // words with their trailing space, then subtract its width.
772 HashSet<const Font*> fallbackFonts;
773 UChar lastCharacter = m_renderTextInfo.lineBreakIterator.lastCharacter();
774 UChar secondToLastCharacter = m_renderTextInfo.lineBreakIterator.secondToLastCharacter();
775 WordTrailingSpace wordTrailingSpace(renderText, style, textLayout);
776 for (; m_current.offset() < renderText.textLength(); m_current.fastIncrementInTextNode()) {
777 bool previousCharacterIsSpace = m_currentCharacterIsSpace;
778 bool previousCharacterIsWS = m_currentCharacterIsWS;
779 UChar c = m_current.current();
780 m_currentCharacterIsSpace = c == ' ' || c == '\t' || (!m_preservesNewline && (c == '\n'));
782 if (!m_collapseWhiteSpace || !m_currentCharacterIsSpace)
783 m_lineInfo.setEmpty(false, &m_block, &m_width);
785 if (c == softHyphen && m_autoWrap && !hyphenWidth && style.hyphens() != HyphensNone) {
786 hyphenWidth = measureHyphenWidth(renderText, font, &fallbackFonts);
787 m_width.addUncommittedWidth(hyphenWidth);
790 bool applyWordSpacing = false;
792 m_currentCharacterIsWS = m_currentCharacterIsSpace || (breakNBSP && c == noBreakSpace);
794 if ((breakAll || breakWords) && !midWordBreak && (!m_currentCharacterIsSpace || style.whiteSpace() != PRE_WRAP)) {
796 bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText.textLength() && U16_IS_TRAIL(renderText[m_current.offset() + 1]);
797 charWidth = textWidth(renderText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, font, m_width.committedWidth() + wrapW, isFixedPitch, m_collapseWhiteSpace, fallbackFonts, textLayout);
798 midWordBreak = m_width.committedWidth() + wrapW + charWidth > m_width.availableWidth();
801 int nextBreakablePosition = m_current.nextBreakablePosition();
802 bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP, isLooseCJKMode, keepAllWords)
803 && (style.hyphens() != HyphensNone || (m_current.previousInSameNode() != softHyphen)));
804 m_current.setNextBreakablePosition(nextBreakablePosition);
806 if (betweenWords || midWordBreak) {
807 bool stoppedIgnoringSpaces = false;
808 if (m_ignoringSpaces) {
809 lastSpaceWordSpacing = 0;
810 if (!m_currentCharacterIsSpace) {
811 // Stop ignoring spaces and begin at this
813 m_ignoringSpaces = false;
814 wordSpacingForWordMeasurement = 0;
815 lastSpace = m_current.offset(); // e.g., "Foo goo", don't add in any of the ignored spaces.
816 m_lineMidpointState.stopIgnoringSpaces(InlineIterator(0, m_current.renderer(), m_current.offset()));
817 stoppedIgnoringSpaces = true;
819 // Just keep ignoring these spaces.
820 nextCharacter(c, lastCharacter, secondToLastCharacter);
825 wordMeasurements.grow(wordMeasurements.size() + 1);
826 WordMeasurement& wordMeasurement = wordMeasurements.last();
828 wordMeasurement.renderer = &renderText;
829 wordMeasurement.endOffset = m_current.offset();
830 wordMeasurement.startOffset = lastSpace;
832 float additionalTempWidth;
833 WTF::Optional<float> wordTrailingSpaceWidth;
835 wordTrailingSpaceWidth = wordTrailingSpace.width(fallbackFonts);
836 if (wordTrailingSpaceWidth) {
837 additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() + 1 - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace,
838 wordMeasurement.fallbackFonts, textLayout) - wordTrailingSpaceWidth.value();
841 additionalTempWidth = textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
843 if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
844 wordMeasurement.fallbackFonts.swap(fallbackFonts);
845 fallbackFonts.clear();
847 wordMeasurement.width = additionalTempWidth + wordSpacingForWordMeasurement;
848 additionalTempWidth += lastSpaceWordSpacing;
849 m_width.addUncommittedWidth(additionalTempWidth);
851 if (m_collapseWhiteSpace && previousCharacterIsSpace && m_currentCharacterIsSpace && additionalTempWidth)
852 m_width.setTrailingWhitespaceWidth(additionalTempWidth);
854 if (!m_appliedStartWidth) {
855 m_width.addUncommittedWidth(inlineLogicalWidth(m_current.renderer(), true, false));
856 m_appliedStartWidth = true;
859 applyWordSpacing = wordSpacing && m_currentCharacterIsSpace;
861 if (!m_width.committedWidth() && m_autoWrap && !m_width.fitsOnLine())
862 m_width.fitBelowFloats(m_lineInfo.isFirstLine());
864 if (m_autoWrap || breakWords) {
865 // If we break only after white-space, consider the current character
866 // as candidate width for this line.
867 bool lineWasTooWide = false;
868 if (m_width.fitsOnLine() && m_currentCharacterIsWS && m_currentStyle->breakOnlyAfterWhiteSpace() && !midWordBreak) {
869 float charWidth = textWidth(renderText, m_current.offset(), 1, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout) + (applyWordSpacing ? wordSpacing : 0);
870 // Check if line is too big even without the extra space
871 // at the end of the line. If it is not, do nothing.
872 // If the line needs the extra whitespace to be too long,
873 // then move the line break to the space and skip all
874 // additional whitespace.
875 if (!m_width.fitsOnLineIncludingExtraWidth(charWidth)) {
876 lineWasTooWide = true;
877 m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
878 modifyMe.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
879 m_lineBreaker.skipTrailingWhitespace(modifyMe, m_lineInfo);
883 if (lineWasTooWide || !m_width.fitsOnLine()) {
884 if (canHyphenate && !m_width.fitsOnLine()) {
885 m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
886 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);
888 if (m_lineBreaker.m_hyphenated) {
893 if (m_lineBreakHistory.atTextParagraphSeparator()) {
894 if (!stoppedIgnoringSpaces && m_current.offset() > 0)
895 ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
896 m_lineBreakHistory.increment();
897 m_lineInfo.setPreviousLineBrokeCleanly(true);
898 wordMeasurement.endOffset = m_lineBreakHistory.offset();
900 if (m_lineBreakHistory.offset() && downcast<RenderText>(m_lineBreakHistory.renderer()) && downcast<RenderText>(*m_lineBreakHistory.renderer()).textLength() && downcast<RenderText>(*m_lineBreakHistory.renderer()).characterAt(m_lineBreakHistory.offset() - 1) == softHyphen && style.hyphens() != HyphensNone)
902 if (m_lineBreakHistory.offset() && m_lineBreakHistory.offset() != (unsigned)wordMeasurement.endOffset && !wordMeasurement.width) {
904 wordMeasurement.endOffset = m_lineBreakHistory.offset();
905 wordMeasurement.width = charWidth;
908 // Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace.
909 if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) {
914 if (!betweenWords || (midWordBreak && !m_autoWrap))
915 m_width.addUncommittedWidth(-additionalTempWidth);
917 // Subtract the width of the soft hyphen out since we fit on a line.
918 m_width.addUncommittedWidth(-hyphenWidth);
924 if (c == '\n' && m_preservesNewline) {
925 if (!stoppedIgnoringSpaces && m_current.offset())
926 ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
927 commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
928 m_lineBreakHistory.increment();
929 m_lineInfo.setPreviousLineBrokeCleanly(true);
933 if (m_autoWrap && betweenWords) {
934 commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
936 // Auto-wrapping text should not wrap in the middle of a word once it has had an
937 // opportunity to break after a word.
941 if (midWordBreak && !U16_IS_TRAIL(c) && !(U_GET_GC_MASK(c) & U_GC_M_MASK)) {
942 // Remember this as a breakable position in case
943 // adding the end width forces a break.
944 m_lineBreakHistory.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
945 midWordBreak &= (breakWords || breakAll);
949 lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
950 wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurement.width) ? wordSpacing : 0;
951 lastSpace = m_current.offset();
954 if (!m_ignoringSpaces && m_currentStyle->collapseWhiteSpace()) {
955 // If we encounter a newline, or if we encounter a second space,
956 // we need to break up this run and enter a mode where we start collapsing spaces.
957 if (m_currentCharacterIsSpace && previousCharacterIsSpace) {
958 m_ignoringSpaces = true;
960 // We just entered a mode where we are ignoring
961 // spaces. Create a midpoint to terminate the run
962 // before the second space.
963 m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
964 m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, InlineIterator(), TrailingObjects::DoNotCollapseFirstSpace);
967 } else if (m_ignoringSpaces) {
968 // Stop ignoring spaces and begin at this
970 m_ignoringSpaces = false;
971 lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0;
972 wordSpacingForWordMeasurement = (applyWordSpacing && wordMeasurements.last().width) ? wordSpacing : 0;
973 lastSpace = m_current.offset(); // e.g., "Foo goo", don't add in any of the ignored spaces.
974 m_lineMidpointState.stopIgnoringSpaces(InlineIterator(nullptr, m_current.renderer(), m_current.offset()));
977 if (isSVGText && m_current.offset()) {
978 // Force creation of new InlineBoxes for each absolute positioned character (those that start new text chunks).
979 if (downcast<RenderSVGInlineText>(renderText).characterStartsNewTextChunk(m_current.offset()))
980 ensureCharacterGetsLineBox(m_lineMidpointState, m_current);
983 if (m_currentCharacterIsSpace && !previousCharacterIsSpace) {
984 m_startOfIgnoredSpaces.setRenderer(m_current.renderer());
985 m_startOfIgnoredSpaces.setOffset(m_current.offset());
986 // Spaces after right-aligned text and before a line-break get collapsed away completely so that the trailing
987 // space doesn't seem to push the text out from the right-hand edge.
988 // FIXME: Do this regardless of the container's alignment - will require rebaselining a lot of test results.
989 if (m_nextObject && m_startOfIgnoredSpaces.offset() && m_nextObject->isBR() && (m_blockStyle.textAlign() == RIGHT || m_blockStyle.textAlign() == WEBKIT_RIGHT)) {
990 m_startOfIgnoredSpaces.setOffset(m_startOfIgnoredSpaces.offset() - 1);
991 // If there's just a single trailing space start ignoring it now so it collapses away.
992 if (m_current.offset() == renderText.textLength() - 1)
993 m_lineMidpointState.startIgnoringSpaces(m_startOfIgnoredSpaces);
997 if (!m_currentCharacterIsWS && previousCharacterIsWS) {
998 if (m_autoWrap && m_currentStyle->breakOnlyAfterWhiteSpace())
999 m_lineBreakHistory.moveTo(m_current.renderer(), m_current.offset(), m_current.nextBreakablePosition());
1002 if (m_collapseWhiteSpace && m_currentCharacterIsSpace && !m_ignoringSpaces)
1003 m_trailingObjects.setTrailingWhitespace(downcast<RenderText>(m_current.renderer()));
1004 else if (!m_currentStyle->collapseWhiteSpace() || !m_currentCharacterIsSpace)
1005 m_trailingObjects.clear();
1008 nextCharacter(c, lastCharacter, secondToLastCharacter);
1011 m_renderTextInfo.lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
1013 wordMeasurements.grow(wordMeasurements.size() + 1);
1014 WordMeasurement& wordMeasurement = wordMeasurements.last();
1015 wordMeasurement.renderer = &renderText;
1017 // IMPORTANT: current.m_pos is > length here!
1018 float additionalTempWidth = m_ignoringSpaces ? 0 : textWidth(renderText, lastSpace, m_current.offset() - lastSpace, font, m_width.currentWidth(), isFixedPitch, m_collapseWhiteSpace, wordMeasurement.fallbackFonts, textLayout);
1019 wordMeasurement.startOffset = lastSpace;
1020 wordMeasurement.endOffset = m_current.offset();
1021 wordMeasurement.width = m_ignoringSpaces ? 0 : additionalTempWidth + wordSpacingForWordMeasurement;
1022 additionalTempWidth += lastSpaceWordSpacing;
1024 float inlineLogicalTempWidth = inlineLogicalWidth(m_current.renderer(), !m_appliedStartWidth, m_includeEndWidth);
1025 m_width.addUncommittedWidth(additionalTempWidth + inlineLogicalTempWidth);
1027 if (wordMeasurement.fallbackFonts.isEmpty() && !fallbackFonts.isEmpty())
1028 wordMeasurement.fallbackFonts.swap(fallbackFonts);
1029 fallbackFonts.clear();
1031 if (m_collapseWhiteSpace && m_currentCharacterIsSpace && additionalTempWidth)
1032 m_width.setTrailingWhitespaceWidth(additionalTempWidth, inlineLogicalTempWidth);
1034 m_includeEndWidth = false;
1036 if (!m_width.fitsOnLine()) {
1038 m_lineBreakHistory.push([&](InlineIterator& modifyMe) {
1039 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);
1043 if (!hyphenated && m_lineBreakHistory.previousInSameNode() == softHyphen && style.hyphens() != HyphensNone) {
1051 inline bool textBeginsWithBreakablePosition(RenderText& nextText)
1053 UChar c = nextText.characterAt(0);
1054 return c == ' ' || c == '\t' || (c == '\n' && !nextText.preservesNewline());
1057 inline bool BreakingContext::canBreakAtThisPosition()
1059 // If we are no-wrap and have found a line-breaking opportunity already then we should take it.
1060 if (m_width.committedWidth() && !m_width.fitsOnLine(m_currentCharacterIsSpace) && m_currWS == NOWRAP)
1063 // Avoid breaking on empty inlines.
1064 if (is<RenderInline>(*m_current.renderer()) && isEmptyInline(downcast<RenderInline>(*m_current.renderer())))
1067 // Avoid breaking before empty inlines (as long as the current object isn't replaced).
1068 if (!m_current.renderer()->isReplaced() && is<RenderInline>(m_nextObject) && isEmptyInline(downcast<RenderInline>(*m_nextObject)))
1071 // Return early if we autowrap and the current character is a space as we will always want to break at such a position.
1072 if (m_autoWrap && m_currentCharacterIsSpace)
1075 if (m_nextObject && m_nextObject->isLineBreakOpportunity())
1078 bool nextIsAutoWrappingText = is<RenderText>(m_nextObject) && (m_autoWrap || m_nextObject->style().autoWrap());
1079 if (!nextIsAutoWrappingText)
1081 RenderText& nextRenderText = downcast<RenderText>(*m_nextObject);
1082 bool currentIsTextOrEmptyInline = is<RenderText>(*m_current.renderer()) || (is<RenderInline>(*m_current.renderer()) && isEmptyInline(downcast<RenderInline>(*m_current.renderer())));
1083 if (!currentIsTextOrEmptyInline)
1084 return m_autoWrap && !m_current.renderer()->isRubyRun();
1086 bool canBreakHere = !m_currentCharacterIsSpace && textBeginsWithBreakablePosition(nextRenderText);
1088 // See if attempting to fit below floats creates more available width on the line.
1089 if (!m_width.fitsOnLine() && !m_width.committedWidth())
1090 m_width.fitBelowFloats(m_lineInfo.isFirstLine());
1092 bool canPlaceOnLine = m_width.fitsOnLine() || !m_autoWrapWasEverTrueOnLine;
1094 if (canPlaceOnLine && canBreakHere)
1095 commitLineBreakAtCurrentWidth(nextRenderText);
1097 return canBreakHere;
1100 inline void BreakingContext::commitAndUpdateLineBreakIfNeeded()
1102 bool checkForBreak = canBreakAtThisPosition();
1104 if (checkForBreak && !m_width.fitsOnLine(m_ignoringSpaces)) {
1105 // if we have floats, try to get below them.
1106 if (m_currentCharacterIsSpace && !m_ignoringSpaces && m_currentStyle->collapseWhiteSpace())
1107 m_trailingObjects.clear();
1109 if (m_width.committedWidth()) {
1114 m_width.fitBelowFloats(m_lineInfo.isFirstLine());
1116 // |width| may have been adjusted because we got shoved down past a float (thus
1117 // giving us more room), so we need to retest, and only jump to
1118 // the end label if we still don't fit on the line. -dwh
1119 if (!m_width.fitsOnLine(m_ignoringSpaces)) {
1123 } else if (m_blockStyle.autoWrap() && !m_width.fitsOnLine() && !m_width.committedWidth()) {
1124 // If the container autowraps but the current child does not then we still need to ensure that it
1125 // wraps and moves below any floats.
1126 m_width.fitBelowFloats(m_lineInfo.isFirstLine());
1129 if (!m_current.renderer()->isFloatingOrOutOfFlowPositioned()) {
1130 m_lastObject = m_current.renderer();
1131 if (m_lastObject->isReplaced() && m_autoWrap && !m_lastObject->isRubyRun() && (!m_lastObject->isImage() || m_allowImagesToBreak) && (!is<RenderListMarker>(*m_lastObject) || downcast<RenderListMarker>(*m_lastObject).isInside()))
1132 commitLineBreakAtCurrentWidth(*m_nextObject);
1136 inline TrailingObjects::CollapseFirstSpaceOrNot checkMidpoints(LineMidpointState& lineMidpointState, const InlineIterator& lBreak)
1138 // Check to see if our last midpoint is a start point beyond the line break. If so,
1139 // shave it off the list, and shave off a trailing space if the previous end point doesn't
1140 // preserve whitespace.
1141 if (lBreak.renderer() && lineMidpointState.numMidpoints() && !(lineMidpointState.numMidpoints() % 2)) {
1142 InlineIterator* midpoints = lineMidpointState.midpoints().data();
1143 InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints() - 2];
1144 const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints() - 1];
1145 InlineIterator currpoint = endpoint;
1146 while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak)
1147 currpoint.increment();
1148 if (currpoint == lBreak) {
1149 // We hit the line break before the start point. Shave off the start point.
1150 lineMidpointState.decrementNumMidpoints();
1151 if (endpoint.renderer()->style().collapseWhiteSpace() && endpoint.renderer()->isText()) {
1152 endpoint.fastDecrement();
1153 return TrailingObjects::DoNotCollapseFirstSpace;
1157 return TrailingObjects::CollapseFirstSpace;
1160 inline InlineIterator BreakingContext::handleEndOfLine()
1162 if (m_lineBreakHistory.current() == m_resolver.position()) {
1163 if (!m_lineBreakHistory.renderer() || !m_lineBreakHistory.renderer()->isBR()) {
1164 // we just add as much as possible
1165 if (m_blockStyle.whiteSpace() == PRE && !m_current.offset())
1166 commitLineBreakAtCurrentWidth(*m_lastObject, m_lastObject->isText() ? m_lastObject->length() : 0);
1167 else if (m_lineBreakHistory.renderer()) {
1168 // Don't ever break in the middle of a word if we can help it.
1169 // There's no room at all. We just have to be on this line,
1170 // even though we'll spill out.
1171 commitLineBreakAtCurrentWidth(*m_current.renderer(), m_current.offset());
1174 // make sure we consume at least one char/object.
1175 if (m_lineBreakHistory.current() == m_resolver.position())
1176 m_lineBreakHistory.increment();
1177 } else if (!m_current.offset() && !m_width.committedWidth() && m_width.uncommittedWidth() && !m_hadUncommittedWidthBeforeCurrent) {
1178 // Do not push the current object to the next line, when this line has some content, but it is still considered empty.
1179 // Empty inline elements like <span></span> can produce such lines and now we just ignore these break opportunities
1180 // at the start of a line, if no width has been committed yet.
1181 // Behave as if it was actually empty and consume at least one object.
1182 m_lineBreakHistory.increment();
1185 // Sanity check our midpoints.
1186 TrailingObjects::CollapseFirstSpaceOrNot collapsed = checkMidpoints(m_lineMidpointState, m_lineBreakHistory.current());
1188 m_trailingObjects.updateMidpointsForTrailingBoxes(m_lineMidpointState, m_lineBreakHistory.current(), collapsed);
1190 // We might have made lineBreak an iterator that points past the end
1191 // of the object. Do this adjustment to make it point to the start
1192 // of the next object instead to avoid confusing the rest of the
1194 if (m_lineBreakHistory.offset()) {
1195 m_lineBreakHistory.update([](InlineIterator& modifyMe) {
1196 modifyMe.setOffset(modifyMe.offset() - 1);
1197 modifyMe.increment();
1201 #if ENABLE(CSS_TRAILING_WORD)
1202 if (m_blockStyle.trailingWord() == TrailingWord::PartiallyBalanced)
1203 return optimalLineBreakLocationForTrailingWord();
1205 return m_lineBreakHistory.current();
1208 #if ENABLE(CSS_TRAILING_WORD)
1209 inline InlineIterator BreakingContext::optimalLineBreakLocationForTrailingWord()
1211 const unsigned longTrailingWordLength = 20;
1212 const float optimalTrailingLineRatio = 0.1;
1213 InlineIterator lineBreak = m_lineBreakHistory.current();
1214 if (!lineBreak.renderer() || !m_lineInfo.isFirstLine() || bidiNextSkippingEmptyInlines(*lineBreak.root(), lineBreak.renderer()) || !is<RenderText>(lineBreak.renderer()))
1216 RenderText& renderText = downcast<RenderText>(*lineBreak.renderer());
1217 // Don't even bother measuring if our remaining line has many characters
1218 if (renderText.textLength() == lineBreak.offset() || renderText.textLength() - lineBreak.offset() > longTrailingWordLength)
1220 bool isLooseCJKMode = m_renderTextInfo.text != &renderText && m_renderTextInfo.lineBreakIterator.isLooseCJKMode();
1221 bool breakNBSP = m_autoWrap && m_currentStyle->nbspMode() == SPACE;
1222 int nextBreakablePosition = lineBreak.nextBreakablePosition();
1223 isBreakable(m_renderTextInfo.lineBreakIterator, lineBreak.offset() + 1, nextBreakablePosition, breakNBSP, isLooseCJKMode, m_currentStyle->wordBreak() == KeepAllWordBreak);
1224 if (nextBreakablePosition < 0 || static_cast<unsigned>(nextBreakablePosition) != renderText.textLength())
1226 const RenderStyle& style = lineStyle(renderText, m_lineInfo);
1227 const FontCascade& font = style.fontCascade();
1228 HashSet<const Font*> dummyFonts;
1229 InlineIterator best = lineBreak;
1230 for (size_t i = 1; i < m_lineBreakHistory.historyLength(); ++i) {
1231 const InlineIterator& candidate = m_lineBreakHistory.get(i);
1232 if (candidate.renderer() != lineBreak.renderer())
1234 float width = textWidth(renderText, candidate.offset(), renderText.textLength() - candidate.offset(), font, 0, font.isFixedPitch(), m_collapseWhiteSpace, dummyFonts);
1235 if (width > m_width.availableWidth())
1237 if (width / m_width.availableWidth() > optimalTrailingLineRatio) // Subsequent line is long enough
1247 #endif // BreakingContext_h