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