| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "sky/engine/config.h" | 5 #include "sky/engine/config.h" |
| 6 #include "sky/engine/core/rendering/RenderParagraph.h" | 6 #include "sky/engine/core/rendering/RenderParagraph.h" |
| 7 | 7 |
| 8 #include "sky/engine/core/rendering/BidiRunForLine.h" |
| 8 #include "sky/engine/core/rendering/InlineIterator.h" | 9 #include "sky/engine/core/rendering/InlineIterator.h" |
| 10 #include "sky/engine/core/rendering/RenderLayer.h" |
| 11 #include "sky/engine/core/rendering/RenderObjectInlines.h" |
| 12 #include "sky/engine/core/rendering/RenderView.h" |
| 13 #include "sky/engine/core/rendering/TextRunConstructor.h" |
| 14 #include "sky/engine/core/rendering/TrailingFloatsRootInlineBox.h" |
| 15 #include "sky/engine/core/rendering/VerticalPositionCache.h" |
| 16 #include "sky/engine/core/rendering/line/BreakingContextInlineHeaders.h" |
| 17 #include "sky/engine/core/rendering/line/LineLayoutState.h" |
| 18 #include "sky/engine/core/rendering/line/LineWidth.h" |
| 19 #include "sky/engine/core/rendering/line/RenderTextInfo.h" |
| 20 #include "sky/engine/core/rendering/line/WordMeasurement.h" |
| 21 #include "sky/engine/platform/fonts/Character.h" |
| 22 #include "sky/engine/platform/text/BidiResolver.h" |
| 23 #include "sky/engine/wtf/RefCountedLeakCounter.h" |
| 24 #include "sky/engine/wtf/StdLibExtras.h" |
| 25 #include "sky/engine/wtf/Vector.h" |
| 26 #include "sky/engine/wtf/unicode/CharacterNames.h" |
| 27 |
| 9 | 28 |
| 10 namespace blink { | 29 namespace blink { |
| 11 | 30 |
| 31 using namespace WTF::Unicode; |
| 32 |
| 12 RenderParagraph::RenderParagraph(ContainerNode* node) | 33 RenderParagraph::RenderParagraph(ContainerNode* node) |
| 13 : RenderBlockFlow(node) | 34 : RenderBlockFlow(node) |
| 14 { | 35 { |
| 15 } | 36 } |
| 16 | 37 |
| 17 RenderParagraph::~RenderParagraph() | 38 RenderParagraph::~RenderParagraph() |
| 18 { | 39 { |
| 19 } | 40 } |
| 20 | 41 |
| 21 RenderParagraph* RenderParagraph::createAnonymous(Document& document) | 42 RenderParagraph* RenderParagraph::createAnonymous(Document& document) |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 65 void RenderParagraph::paintContents(PaintInfo& paintInfo, const LayoutPoint& pai
ntOffset) | 86 void RenderParagraph::paintContents(PaintInfo& paintInfo, const LayoutPoint& pai
ntOffset) |
| 66 { | 87 { |
| 67 m_lineBoxes.paint(this, paintInfo, paintOffset); | 88 m_lineBoxes.paint(this, paintInfo, paintOffset); |
| 68 } | 89 } |
| 69 | 90 |
| 70 bool RenderParagraph::hitTestContents(const HitTestRequest& request, HitTestResu
lt& result, const HitTestLocation& locationInContainer, const LayoutPoint& accum
ulatedOffset, HitTestAction hitTestAction) | 91 bool RenderParagraph::hitTestContents(const HitTestRequest& request, HitTestResu
lt& result, const HitTestLocation& locationInContainer, const LayoutPoint& accum
ulatedOffset, HitTestAction hitTestAction) |
| 71 { | 92 { |
| 72 return m_lineBoxes.hitTest(this, request, result, locationInContainer, accum
ulatedOffset, hitTestAction); | 93 return m_lineBoxes.hitTest(this, request, result, locationInContainer, accum
ulatedOffset, hitTestAction); |
| 73 } | 94 } |
| 74 | 95 |
| 96 void RenderParagraph::markLinesDirtyInBlockRange(LayoutUnit logicalTop, LayoutUn
it logicalBottom, RootInlineBox* highest) |
| 97 { |
| 98 if (logicalTop >= logicalBottom) |
| 99 return; |
| 100 |
| 101 RootInlineBox* lowestDirtyLine = lastRootBox(); |
| 102 RootInlineBox* afterLowest = lowestDirtyLine; |
| 103 while (lowestDirtyLine && lowestDirtyLine->lineBottomWithLeading() >= logica
lBottom && logicalBottom < LayoutUnit::max()) { |
| 104 afterLowest = lowestDirtyLine; |
| 105 lowestDirtyLine = lowestDirtyLine->prevRootBox(); |
| 106 } |
| 107 |
| 108 while (afterLowest && afterLowest != highest && (afterLowest->lineBottomWith
Leading() >= logicalTop || afterLowest->lineBottomWithLeading() < 0)) { |
| 109 afterLowest->markDirty(); |
| 110 afterLowest = afterLowest->prevRootBox(); |
| 111 } |
| 112 } |
| 113 |
| 114 static inline InlineBox* createInlineBoxForRenderer(RenderObject* obj, bool isRo
otLineBox, bool isOnlyRun = false) |
| 115 { |
| 116 if (isRootLineBox) |
| 117 return toRenderBlockFlow(obj)->createAndAppendRootInlineBox(); |
| 118 |
| 119 if (obj->isText()) { |
| 120 InlineTextBox* textBox = toRenderText(obj)->createInlineTextBox(); |
| 121 // We only treat a box as text for a <br> if we are on a line by ourself
or in strict mode |
| 122 // (Note the use of strict mode. In "almost strict" mode, we don't trea
t the box for <br> as text.) |
| 123 return textBox; |
| 124 } |
| 125 |
| 126 if (obj->isBox()) |
| 127 return toRenderBox(obj)->createInlineBox(); |
| 128 |
| 129 return toRenderInline(obj)->createAndAppendInlineFlowBox(); |
| 130 } |
| 131 |
| 132 static inline void dirtyLineBoxesForRenderer(RenderObject* o, bool fullLayout) |
| 133 { |
| 134 if (o->isText()) { |
| 135 RenderText* renderText = toRenderText(o); |
| 136 renderText->dirtyLineBoxes(fullLayout); |
| 137 } else |
| 138 toRenderInline(o)->dirtyLineBoxes(fullLayout); |
| 139 } |
| 140 |
| 141 static bool parentIsConstructedOrHaveNext(InlineFlowBox* parentBox) |
| 142 { |
| 143 do { |
| 144 if (parentBox->isConstructed() || parentBox->nextOnLine()) |
| 145 return true; |
| 146 parentBox = parentBox->parent(); |
| 147 } while (parentBox); |
| 148 return false; |
| 149 } |
| 150 |
| 151 InlineFlowBox* RenderParagraph::createLineBoxes(RenderObject* obj, const LineInf
o& lineInfo, InlineBox* childBox) |
| 152 { |
| 153 // See if we have an unconstructed line box for this object that is also |
| 154 // the last item on the line. |
| 155 unsigned lineDepth = 1; |
| 156 InlineFlowBox* parentBox = 0; |
| 157 InlineFlowBox* result = 0; |
| 158 bool hasDefaultLineBoxContain = style()->lineBoxContain() == RenderStyle::in
itialLineBoxContain(); |
| 159 do { |
| 160 ASSERT_WITH_SECURITY_IMPLICATION(obj->isRenderInline() || obj == this); |
| 161 |
| 162 RenderInline* inlineFlow = (obj != this) ? toRenderInline(obj) : 0; |
| 163 |
| 164 // Get the last box we made for this render object. |
| 165 parentBox = inlineFlow ? inlineFlow->lastLineBox() : toRenderBlock(obj)-
>lastLineBox(); |
| 166 |
| 167 // If this box or its ancestor is constructed then it is from a previous
line, and we need |
| 168 // to make a new box for our line. If this box or its ancestor is uncon
structed but it has |
| 169 // something following it on the line, then we know we have to make a ne
w box |
| 170 // as well. In this situation our inline has actually been split in two
on |
| 171 // the same line (this can happen with very fancy language mixtures). |
| 172 bool constructedNewBox = false; |
| 173 bool allowedToConstructNewBox = !hasDefaultLineBoxContain || !inlineFlow
|| inlineFlow->alwaysCreateLineBoxes(); |
| 174 bool canUseExistingParentBox = parentBox && !parentIsConstructedOrHaveNe
xt(parentBox); |
| 175 if (allowedToConstructNewBox && !canUseExistingParentBox) { |
| 176 // We need to make a new box for this render object. Once |
| 177 // made, we need to place it at the end of the current line. |
| 178 InlineBox* newBox = createInlineBoxForRenderer(obj, obj == this); |
| 179 ASSERT_WITH_SECURITY_IMPLICATION(newBox->isInlineFlowBox()); |
| 180 parentBox = toInlineFlowBox(newBox); |
| 181 parentBox->setFirstLineStyleBit(lineInfo.isFirstLine()); |
| 182 if (!hasDefaultLineBoxContain) |
| 183 parentBox->clearDescendantsHaveSameLineHeightAndBaseline(); |
| 184 constructedNewBox = true; |
| 185 } |
| 186 |
| 187 if (constructedNewBox || canUseExistingParentBox) { |
| 188 if (!result) |
| 189 result = parentBox; |
| 190 |
| 191 // If we have hit the block itself, then |box| represents the root |
| 192 // inline box for the line, and it doesn't have to be appended to an
y parent |
| 193 // inline. |
| 194 if (childBox) |
| 195 parentBox->addToLine(childBox); |
| 196 |
| 197 if (!constructedNewBox || obj == this) |
| 198 break; |
| 199 |
| 200 childBox = parentBox; |
| 201 } |
| 202 |
| 203 // If we've exceeded our line depth, then jump straight to the root and
skip all the remaining |
| 204 // intermediate inline flows. |
| 205 obj = (++lineDepth >= cMaxLineDepth) ? this : obj->parent(); |
| 206 |
| 207 } while (true); |
| 208 |
| 209 return result; |
| 210 } |
| 211 |
| 212 template <typename CharacterType> |
| 213 static inline bool endsWithASCIISpaces(const CharacterType* characters, unsigned
pos, unsigned end) |
| 214 { |
| 215 while (isASCIISpace(characters[pos])) { |
| 216 pos++; |
| 217 if (pos >= end) |
| 218 return true; |
| 219 } |
| 220 return false; |
| 221 } |
| 222 |
| 223 static bool reachedEndOfTextRenderer(const BidiRunList<BidiRun>& bidiRuns) |
| 224 { |
| 225 BidiRun* run = bidiRuns.logicallyLastRun(); |
| 226 if (!run) |
| 227 return true; |
| 228 unsigned pos = run->stop(); |
| 229 RenderObject* r = run->m_object; |
| 230 if (!r->isText()) |
| 231 return false; |
| 232 RenderText* renderText = toRenderText(r); |
| 233 unsigned length = renderText->textLength(); |
| 234 if (pos >= length) |
| 235 return true; |
| 236 |
| 237 if (renderText->is8Bit()) |
| 238 return endsWithASCIISpaces(renderText->characters8(), pos, length); |
| 239 return endsWithASCIISpaces(renderText->characters16(), pos, length); |
| 240 } |
| 241 |
| 242 RootInlineBox* RenderParagraph::constructLine(BidiRunList<BidiRun>& bidiRuns, co
nst LineInfo& lineInfo) |
| 243 { |
| 244 ASSERT(bidiRuns.firstRun()); |
| 245 |
| 246 bool rootHasSelectedChildren = false; |
| 247 InlineFlowBox* parentBox = 0; |
| 248 int runCount = bidiRuns.runCount() - lineInfo.runsFromLeadingWhitespace(); |
| 249 for (BidiRun* r = bidiRuns.firstRun(); r; r = r->next()) { |
| 250 // Create a box for our object. |
| 251 bool isOnlyRun = (runCount == 1); |
| 252 if (runCount == 2) |
| 253 isOnlyRun = false; |
| 254 |
| 255 if (lineInfo.isEmpty()) |
| 256 continue; |
| 257 |
| 258 InlineBox* box = createInlineBoxForRenderer(r->m_object, false, isOnlyRu
n); |
| 259 r->m_box = box; |
| 260 |
| 261 ASSERT(box); |
| 262 if (!box) |
| 263 continue; |
| 264 |
| 265 if (!rootHasSelectedChildren && box->renderer().selectionState() != Rend
erObject::SelectionNone) |
| 266 rootHasSelectedChildren = true; |
| 267 |
| 268 // If we have no parent box yet, or if the run is not simply a sibling, |
| 269 // then we need to construct inline boxes as necessary to properly enclo
se the |
| 270 // run's inline box. Segments can only be siblings at the root level, as |
| 271 // they are positioned separately. |
| 272 if (!parentBox || parentBox->renderer() != r->m_object->parent()) { |
| 273 // Create new inline boxes all the way back to the appropriate inser
tion point. |
| 274 parentBox = createLineBoxes(r->m_object->parent(), lineInfo, box); |
| 275 } else { |
| 276 // Append the inline box to this line. |
| 277 parentBox->addToLine(box); |
| 278 } |
| 279 |
| 280 box->setBidiLevel(r->level()); |
| 281 |
| 282 if (box->isInlineTextBox()) { |
| 283 InlineTextBox* text = toInlineTextBox(box); |
| 284 text->setStart(r->m_start); |
| 285 text->setLen(r->m_stop - r->m_start); |
| 286 text->setDirOverride(r->dirOverride()); |
| 287 if (r->m_hasHyphen) |
| 288 text->setHasHyphen(true); |
| 289 } |
| 290 } |
| 291 |
| 292 ASSERT(lastLineBox() && !lastLineBox()->isConstructed()); |
| 293 |
| 294 // Set the m_selectedChildren flag on the root inline box if one of the leaf
inline box |
| 295 // from the bidi runs walk above has a selection state. |
| 296 if (rootHasSelectedChildren) |
| 297 lastLineBox()->root().setHasSelectedChildren(true); |
| 298 |
| 299 // Set bits on our inline flow boxes that indicate which sides should |
| 300 // paint borders/margins/padding. This knowledge will ultimately be used wh
en |
| 301 // we determine the horizontal positions and widths of all the inline boxes
on |
| 302 // the line. |
| 303 bool isLogicallyLastRunWrapped = bidiRuns.logicallyLastRun()->m_object && bi
diRuns.logicallyLastRun()->m_object->isText() ? !reachedEndOfTextRenderer(bidiRu
ns) : true; |
| 304 lastLineBox()->determineSpacingForFlowBoxes(lineInfo.isLastLine(), isLogical
lyLastRunWrapped, bidiRuns.logicallyLastRun()->m_object); |
| 305 |
| 306 // Now mark the line boxes as being constructed. |
| 307 lastLineBox()->setConstructed(); |
| 308 |
| 309 // Return the last line. |
| 310 return lastRootBox(); |
| 311 } |
| 312 |
| 313 ETextAlign RenderParagraph::textAlignmentForLine(bool endsWithSoftBreak) const |
| 314 { |
| 315 ETextAlign alignment = style()->textAlign(); |
| 316 if (endsWithSoftBreak) |
| 317 return alignment; |
| 318 |
| 319 if (!RuntimeEnabledFeatures::css3TextEnabled()) |
| 320 return (alignment == JUSTIFY) ? TASTART : alignment; |
| 321 |
| 322 if (alignment != JUSTIFY) |
| 323 return alignment; |
| 324 |
| 325 TextAlignLast alignmentLast = style()->textAlignLast(); |
| 326 switch (alignmentLast) { |
| 327 case TextAlignLastStart: |
| 328 return TASTART; |
| 329 case TextAlignLastEnd: |
| 330 return TAEND; |
| 331 case TextAlignLastLeft: |
| 332 return LEFT; |
| 333 case TextAlignLastRight: |
| 334 return RIGHT; |
| 335 case TextAlignLastCenter: |
| 336 return CENTER; |
| 337 case TextAlignLastJustify: |
| 338 return JUSTIFY; |
| 339 case TextAlignLastAuto: |
| 340 if (style()->textJustify() == TextJustifyDistribute) |
| 341 return JUSTIFY; |
| 342 return TASTART; |
| 343 } |
| 344 |
| 345 return alignment; |
| 346 } |
| 347 |
| 348 static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* ru
n, RenderText* renderer, float xPos, const LineInfo& lineInfo, |
| 349 GlyphOverflowAndFallbackFontsMap& t
extBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& w
ordMeasurements) |
| 350 { |
| 351 HashSet<const SimpleFontData*> fallbackFonts; |
| 352 GlyphOverflow glyphOverflow; |
| 353 |
| 354 const Font& font = renderer->style(lineInfo.isFirstLine())->font(); |
| 355 // Always compute glyph overflow if the block's line-box-contain value is "g
lyphs". |
| 356 if (lineBox->fitsToGlyphs()) { |
| 357 // If we don't stick out of the root line's font box, then don't bother
computing our glyph overflow. This optimization |
| 358 // will keep us from computing glyph bounds in nearly all cases. |
| 359 bool includeRootLine = lineBox->includesRootLineBoxFontOrLeading(); |
| 360 int baselineShift = lineBox->verticalPositionForBox(run->m_box, vertical
PositionCache); |
| 361 int rootDescent = includeRootLine ? font.fontMetrics().descent() : 0; |
| 362 int rootAscent = includeRootLine ? font.fontMetrics().ascent() : 0; |
| 363 int boxAscent = font.fontMetrics().ascent() - baselineShift; |
| 364 int boxDescent = font.fontMetrics().descent() + baselineShift; |
| 365 if (boxAscent > rootDescent || boxDescent > rootAscent) |
| 366 glyphOverflow.computeBounds = true; |
| 367 } |
| 368 |
| 369 LayoutUnit hyphenWidth = 0; |
| 370 if (toInlineTextBox(run->m_box)->hasHyphen()) { |
| 371 const Font& font = renderer->style(lineInfo.isFirstLine())->font(); |
| 372 hyphenWidth = measureHyphenWidth(renderer, font, run->direction()); |
| 373 } |
| 374 float measuredWidth = 0; |
| 375 |
| 376 bool kerningIsEnabled = font.fontDescription().typesettingFeatures() & Kerni
ng; |
| 377 |
| 378 bool canUseSimpleFontCodePath = renderer->canUseSimpleFontCodePath(); |
| 379 |
| 380 // Since we don't cache glyph overflows, we need to re-measure the run if |
| 381 // the style is linebox-contain: glyph. |
| 382 |
| 383 if (!lineBox->fitsToGlyphs() && canUseSimpleFontCodePath) { |
| 384 int lastEndOffset = run->m_start; |
| 385 for (size_t i = 0, size = wordMeasurements.size(); i < size && lastEndOf
fset < run->m_stop; ++i) { |
| 386 const WordMeasurement& wordMeasurement = wordMeasurements[i]; |
| 387 if (wordMeasurement.width <=0 || wordMeasurement.startOffset == word
Measurement.endOffset) |
| 388 continue; |
| 389 if (wordMeasurement.renderer != renderer || wordMeasurement.startOff
set != lastEndOffset || wordMeasurement.endOffset > run->m_stop) |
| 390 continue; |
| 391 |
| 392 lastEndOffset = wordMeasurement.endOffset; |
| 393 if (kerningIsEnabled && lastEndOffset == run->m_stop) { |
| 394 int wordLength = lastEndOffset - wordMeasurement.startOffset; |
| 395 measuredWidth += renderer->width(wordMeasurement.startOffset, wo
rdLength, xPos, run->direction(), lineInfo.isFirstLine()); |
| 396 if (i > 0 && wordLength == 1 && renderer->characterAt(wordMeasur
ement.startOffset) == ' ') |
| 397 measuredWidth += renderer->style()->wordSpacing(); |
| 398 } else |
| 399 measuredWidth += wordMeasurement.width; |
| 400 if (!wordMeasurement.fallbackFonts.isEmpty()) { |
| 401 HashSet<const SimpleFontData*>::const_iterator end = wordMeasure
ment.fallbackFonts.end(); |
| 402 for (HashSet<const SimpleFontData*>::const_iterator it = wordMea
surement.fallbackFonts.begin(); it != end; ++it) |
| 403 fallbackFonts.add(*it); |
| 404 } |
| 405 } |
| 406 if (measuredWidth && lastEndOffset != run->m_stop) { |
| 407 // If we don't have enough cached data, we'll measure the run again. |
| 408 measuredWidth = 0; |
| 409 fallbackFonts.clear(); |
| 410 } |
| 411 } |
| 412 |
| 413 if (!measuredWidth) |
| 414 measuredWidth = renderer->width(run->m_start, run->m_stop - run->m_start
, xPos, run->direction(), lineInfo.isFirstLine(), &fallbackFonts, &glyphOverflow
); |
| 415 |
| 416 run->m_box->setLogicalWidth(measuredWidth + hyphenWidth); |
| 417 if (!fallbackFonts.isEmpty()) { |
| 418 ASSERT(run->m_box->isText()); |
| 419 GlyphOverflowAndFallbackFontsMap::ValueType* it = textBoxDataMap.add(toI
nlineTextBox(run->m_box), std::make_pair(Vector<const SimpleFontData*>(), GlyphO
verflow())).storedValue; |
| 420 ASSERT(it->value.first.isEmpty()); |
| 421 copyToVector(fallbackFonts, it->value.first); |
| 422 run->m_box->parent()->clearDescendantsHaveSameLineHeightAndBaseline(); |
| 423 } |
| 424 if (!glyphOverflow.isZero()) { |
| 425 ASSERT(run->m_box->isText()); |
| 426 GlyphOverflowAndFallbackFontsMap::ValueType* it = textBoxDataMap.add(toI
nlineTextBox(run->m_box), std::make_pair(Vector<const SimpleFontData*>(), GlyphO
verflow())).storedValue; |
| 427 it->value.second = glyphOverflow; |
| 428 run->m_box->clearKnownToHaveNoOverflow(); |
| 429 } |
| 430 } |
| 431 |
| 432 static inline void computeExpansionForJustifiedText(BidiRun* firstRun, BidiRun*
trailingSpaceRun, Vector<unsigned, 16>& expansionOpportunities, unsigned expansi
onOpportunityCount, float& totalLogicalWidth, float availableLogicalWidth) |
| 433 { |
| 434 if (!expansionOpportunityCount || availableLogicalWidth <= totalLogicalWidth
) |
| 435 return; |
| 436 |
| 437 size_t i = 0; |
| 438 for (BidiRun* r = firstRun; r; r = r->next()) { |
| 439 if (!r->m_box || r == trailingSpaceRun) |
| 440 continue; |
| 441 |
| 442 if (r->m_object->isText()) { |
| 443 unsigned opportunitiesInRun = expansionOpportunities[i++]; |
| 444 |
| 445 ASSERT(opportunitiesInRun <= expansionOpportunityCount); |
| 446 |
| 447 // Don't justify for white-space: pre. |
| 448 if (r->m_object->style()->whiteSpace() != PRE) { |
| 449 InlineTextBox* textBox = toInlineTextBox(r->m_box); |
| 450 int expansion = (availableLogicalWidth - totalLogicalWidth) * op
portunitiesInRun / expansionOpportunityCount; |
| 451 textBox->setExpansion(expansion); |
| 452 totalLogicalWidth += expansion; |
| 453 } |
| 454 expansionOpportunityCount -= opportunitiesInRun; |
| 455 if (!expansionOpportunityCount) |
| 456 break; |
| 457 } |
| 458 } |
| 459 } |
| 460 |
| 461 static void updateLogicalInlinePositions(RenderParagraph* block, float& lineLogi
calLeft, float& lineLogicalRight, float& availableLogicalWidth, IndentTextOrNot
shouldIndentText) |
| 462 { |
| 463 lineLogicalLeft = block->logicalLeftOffsetForLine(shouldIndentText == Indent
Text).toFloat(); |
| 464 lineLogicalRight = block->logicalRightOffsetForLine(shouldIndentText == Inde
ntText).toFloat(); |
| 465 availableLogicalWidth = lineLogicalRight - lineLogicalLeft; |
| 466 } |
| 467 |
| 468 void RenderParagraph::computeInlineDirectionPositionsForLine(RootInlineBox* line
Box, const LineInfo& lineInfo, BidiRun* firstRun, BidiRun* trailingSpaceRun, boo
l reachedEnd, |
| 469 GlyphOverflowAndFallbac
kFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMea
surements& wordMeasurements) |
| 470 { |
| 471 ETextAlign textAlign = textAlignmentForLine(!reachedEnd && !lineBox->endsWit
hBreak()); |
| 472 |
| 473 // CSS 2.1: "'Text-indent' only affects a line if it is the first formatted
line of an element. For example, the first line of an anonymous block |
| 474 // box is only affected if it is the first child of its parent element." |
| 475 // CSS3 "text-indent", "each-line" affects the first line of the block conta
iner as well as each line after a forced line break, |
| 476 // but does not affect lines after a soft wrap break. |
| 477 bool isFirstLine = lineInfo.isFirstLine() && !(isAnonymousBlock() && parent(
)->slowFirstChild() != this); |
| 478 bool isAfterHardLineBreak = lineBox->prevRootBox() && lineBox->prevRootBox()
->endsWithBreak(); |
| 479 IndentTextOrNot shouldIndentText = requiresIndent(isFirstLine, isAfterHardLi
neBreak, style()); |
| 480 float lineLogicalLeft; |
| 481 float lineLogicalRight; |
| 482 float availableLogicalWidth; |
| 483 updateLogicalInlinePositions(this, lineLogicalLeft, lineLogicalRight, availa
bleLogicalWidth, shouldIndentText); |
| 484 bool needsWordSpacing; |
| 485 |
| 486 if (firstRun && firstRun->m_object->isReplaced()) |
| 487 updateLogicalInlinePositions(this, lineLogicalLeft, lineLogicalRight, av
ailableLogicalWidth, shouldIndentText); |
| 488 |
| 489 computeInlineDirectionPositionsForSegment(lineBox, lineInfo, textAlign, line
LogicalLeft, availableLogicalWidth, firstRun, trailingSpaceRun, textBoxDataMap,
verticalPositionCache, wordMeasurements); |
| 490 // The widths of all runs are now known. We can now place every inline box (
and |
| 491 // compute accurate widths for the inline flow boxes). |
| 492 needsWordSpacing = false; |
| 493 lineBox->placeBoxesInInlineDirection(lineLogicalLeft, needsWordSpacing); |
| 494 } |
| 495 |
| 496 BidiRun* RenderParagraph::computeInlineDirectionPositionsForSegment(RootInlineBo
x* lineBox, const LineInfo& lineInfo, ETextAlign textAlign, float& logicalLeft, |
| 497 float& availableLogicalWidth, BidiRun* firstRun, BidiRun* trailingSpaceRun,
GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& vertica
lPositionCache, |
| 498 WordMeasurements& wordMeasurements) |
| 499 { |
| 500 bool needsWordSpacing = true; |
| 501 float totalLogicalWidth = lineBox->getFlowSpacingLogicalWidth().toFloat(); |
| 502 unsigned expansionOpportunityCount = 0; |
| 503 bool isAfterExpansion = true; |
| 504 Vector<unsigned, 16> expansionOpportunities; |
| 505 RenderObject* previousObject = 0; |
| 506 TextJustify textJustify = style()->textJustify(); |
| 507 |
| 508 BidiRun* r = firstRun; |
| 509 for (; r; r = r->next()) { |
| 510 if (!r->m_box || r->m_object->isOutOfFlowPositioned() || r->m_box->isLin
eBreak()) |
| 511 continue; // Positioned objects are only participating to figure out
their |
| 512 // correct static x position. They have no effect on the
width. |
| 513 // Similarly, line break boxes have no effect on the width
. |
| 514 if (r->m_object->isText()) { |
| 515 RenderText* rt = toRenderText(r->m_object); |
| 516 if (textAlign == JUSTIFY && r != trailingSpaceRun && textJustify !=
TextJustifyNone) { |
| 517 if (!isAfterExpansion) |
| 518 toInlineTextBox(r->m_box)->setCanHaveLeadingExpansion(true); |
| 519 unsigned opportunitiesInRun; |
| 520 if (rt->is8Bit()) |
| 521 opportunitiesInRun = Character::expansionOpportunityCount(rt
->characters8() + r->m_start, r->m_stop - r->m_start, r->m_box->direction(), isA
fterExpansion); |
| 522 else |
| 523 opportunitiesInRun = Character::expansionOpportunityCount(rt
->characters16() + r->m_start, r->m_stop - r->m_start, r->m_box->direction(), is
AfterExpansion); |
| 524 expansionOpportunities.append(opportunitiesInRun); |
| 525 expansionOpportunityCount += opportunitiesInRun; |
| 526 } |
| 527 |
| 528 if (rt->textLength()) { |
| 529 if (!r->m_start && needsWordSpacing && isSpaceOrNewline(rt->char
acterAt(r->m_start))) |
| 530 totalLogicalWidth += rt->style(lineInfo.isFirstLine())->font
().fontDescription().wordSpacing(); |
| 531 needsWordSpacing = !isSpaceOrNewline(rt->characterAt(r->m_stop -
1)); |
| 532 } |
| 533 |
| 534 setLogicalWidthForTextRun(lineBox, r, rt, totalLogicalWidth, lineInf
o, textBoxDataMap, verticalPositionCache, wordMeasurements); |
| 535 } else { |
| 536 isAfterExpansion = false; |
| 537 if (!r->m_object->isRenderInline()) { |
| 538 RenderBox* renderBox = toRenderBox(r->m_object); |
| 539 r->m_box->setLogicalWidth(logicalWidthForChild(renderBox).toFloa
t()); |
| 540 totalLogicalWidth += marginStartForChild(renderBox) + marginEndF
orChild(renderBox); |
| 541 } |
| 542 } |
| 543 |
| 544 totalLogicalWidth += r->m_box->logicalWidth(); |
| 545 previousObject = r->m_object; |
| 546 } |
| 547 |
| 548 if (isAfterExpansion && !expansionOpportunities.isEmpty()) { |
| 549 expansionOpportunities.last()--; |
| 550 expansionOpportunityCount--; |
| 551 } |
| 552 |
| 553 updateLogicalWidthForAlignment(textAlign, lineBox, trailingSpaceRun, logical
Left, totalLogicalWidth, availableLogicalWidth, expansionOpportunityCount); |
| 554 |
| 555 computeExpansionForJustifiedText(firstRun, trailingSpaceRun, expansionOpport
unities, expansionOpportunityCount, totalLogicalWidth, availableLogicalWidth); |
| 556 |
| 557 return r; |
| 558 } |
| 559 |
| 560 void RenderParagraph::computeBlockDirectionPositionsForLine(RootInlineBox* lineB
ox, BidiRun* firstRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, |
| 561 VerticalPositionCache& v
erticalPositionCache) |
| 562 { |
| 563 setLogicalHeight(lineBox->alignBoxesInBlockDirection(logicalHeight(), textBo
xDataMap, verticalPositionCache)); |
| 564 |
| 565 // Now make sure we place replaced render objects correctly. |
| 566 for (BidiRun* r = firstRun; r; r = r->next()) { |
| 567 ASSERT(r->m_box); |
| 568 if (!r->m_box) |
| 569 continue; // Skip runs with no line boxes. |
| 570 |
| 571 // Align positioned boxes with the top of the line box. This is |
| 572 // a reasonable approximation of an appropriate y position. |
| 573 if (r->m_object->isOutOfFlowPositioned()) |
| 574 r->m_box->setLogicalTop(logicalHeight().toFloat()); |
| 575 |
| 576 // Position is used to properly position both replaced elements and |
| 577 // to update the static normal flow x/y of positioned elements. |
| 578 if (r->m_object->isText()) |
| 579 toRenderText(r->m_object)->positionLineBox(r->m_box); |
| 580 else if (r->m_object->isBox()) |
| 581 toRenderBox(r->m_object)->positionLineBox(r->m_box); |
| 582 } |
| 583 } |
| 584 |
| 585 // This function constructs line boxes for all of the text runs in the resolver
and computes their position. |
| 586 RootInlineBox* RenderParagraph::createLineBoxesFromBidiRuns(unsigned bidiLevel,
BidiRunList<BidiRun>& bidiRuns, const InlineIterator& end, LineInfo& lineInfo, V
erticalPositionCache& verticalPositionCache, BidiRun* trailingSpaceRun, WordMeas
urements& wordMeasurements) |
| 587 { |
| 588 if (!bidiRuns.runCount()) |
| 589 return 0; |
| 590 |
| 591 // FIXME: Why is this only done when we had runs? |
| 592 lineInfo.setLastLine(!end.object()); |
| 593 |
| 594 RootInlineBox* lineBox = constructLine(bidiRuns, lineInfo); |
| 595 if (!lineBox) |
| 596 return 0; |
| 597 |
| 598 lineBox->setBidiLevel(bidiLevel); |
| 599 lineBox->setEndsWithBreak(lineInfo.previousLineBrokeCleanly()); |
| 600 |
| 601 GlyphOverflowAndFallbackFontsMap textBoxDataMap; |
| 602 |
| 603 // Now we position all of our text runs horizontally. |
| 604 computeInlineDirectionPositionsForLine(lineBox, lineInfo, bidiRuns.firstRun(
), trailingSpaceRun, end.atEnd(), textBoxDataMap, verticalPositionCache, wordMea
surements); |
| 605 |
| 606 // Now position our text runs vertically. |
| 607 computeBlockDirectionPositionsForLine(lineBox, bidiRuns.firstRun(), textBoxD
ataMap, verticalPositionCache); |
| 608 |
| 609 // Compute our overflow now. |
| 610 lineBox->computeOverflow(lineBox->lineTop(), lineBox->lineBottom(), textBoxD
ataMap); |
| 611 |
| 612 return lineBox; |
| 613 } |
| 614 |
| 615 static void deleteLineRange(LineLayoutState& layoutState, RootInlineBox* startLi
ne, RootInlineBox* stopLine = 0) |
| 616 { |
| 617 RootInlineBox* boxToDelete = startLine; |
| 618 while (boxToDelete && boxToDelete != stopLine) { |
| 619 layoutState.updatePaintInvalidationRangeFromBox(boxToDelete); |
| 620 // Note: deleteLineRange(firstRootBox()) is not identical to deleteLineB
oxTree(). |
| 621 // deleteLineBoxTree uses nextLineBox() instead of nextRootBox() when tr
aversing. |
| 622 RootInlineBox* next = boxToDelete->nextRootBox(); |
| 623 boxToDelete->deleteLine(); |
| 624 boxToDelete = next; |
| 625 } |
| 626 } |
| 627 |
| 628 void RenderParagraph::layoutRunsAndFloats(LineLayoutState& layoutState) |
| 629 { |
| 630 // We want to skip ahead to the first dirty line |
| 631 InlineBidiResolver resolver; |
| 632 RootInlineBox* startLine = determineStartPosition(layoutState, resolver); |
| 633 |
| 634 // We also find the first clean line and extract these lines. We will add t
hem back |
| 635 // if we determine that we're able to synchronize after handling all our dir
ty lines. |
| 636 InlineIterator cleanLineStart; |
| 637 BidiStatus cleanLineBidiStatus; |
| 638 if (!layoutState.isFullLayout() && startLine) |
| 639 determineEndPosition(layoutState, startLine, cleanLineStart, cleanLineBi
diStatus); |
| 640 |
| 641 if (startLine) { |
| 642 if (!layoutState.usesPaintInvalidationBounds()) |
| 643 layoutState.setPaintInvalidationRange(logicalHeight()); |
| 644 deleteLineRange(layoutState, startLine); |
| 645 } |
| 646 |
| 647 layoutRunsAndFloatsInRange(layoutState, resolver, cleanLineStart, cleanLineB
idiStatus); |
| 648 linkToEndLineIfNeeded(layoutState); |
| 649 markDirtyFloatsForPaintInvalidation(layoutState.floats()); |
| 650 } |
| 651 |
| 652 void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, |
| 653 InlineBidiResolver& resolver, const InlineIterator& cleanLineStart, |
| 654 const BidiStatus& cleanLineBidiStatus) |
| 655 { |
| 656 RenderStyle* styleToUse = style(); |
| 657 LineMidpointState& lineMidpointState = resolver.midpointState(); |
| 658 InlineIterator endOfLine = resolver.position(); |
| 659 bool checkForEndLineMatch = layoutState.endLine(); |
| 660 RenderTextInfo renderTextInfo; |
| 661 VerticalPositionCache verticalPositionCache; |
| 662 |
| 663 LineBreaker lineBreaker(this); |
| 664 |
| 665 while (!endOfLine.atEnd()) { |
| 666 // FIXME: Is this check necessary before the first iteration or can it b
e moved to the end? |
| 667 if (checkForEndLineMatch) { |
| 668 layoutState.setEndLineMatched(matchedEndLine(layoutState, resolver,
cleanLineStart, cleanLineBidiStatus)); |
| 669 if (layoutState.endLineMatched()) { |
| 670 resolver.setPosition(InlineIterator(resolver.position().root(),
0, 0), 0); |
| 671 break; |
| 672 } |
| 673 } |
| 674 |
| 675 lineMidpointState.reset(); |
| 676 |
| 677 layoutState.lineInfo().setEmpty(true); |
| 678 layoutState.lineInfo().resetRunsFromLeadingWhitespace(); |
| 679 |
| 680 bool isNewUBAParagraph = layoutState.lineInfo().previousLineBrokeCleanly
(); |
| 681 FloatingObject* lastFloatFromPreviousLine = 0; |
| 682 |
| 683 WordMeasurements wordMeasurements; |
| 684 endOfLine = lineBreaker.nextLineBreak(resolver, layoutState.lineInfo(),
renderTextInfo, |
| 685 lastFloatFromPreviousLine, wordMeasurements); |
| 686 renderTextInfo.m_lineBreakIterator.resetPriorContext(); |
| 687 if (resolver.position().atEnd()) { |
| 688 // FIXME: We shouldn't be creating any runs in nextLineBreak to begi
n with! |
| 689 // Once BidiRunList is separated from BidiResolver this will not be
needed. |
| 690 resolver.runs().deleteRuns(); |
| 691 resolver.markCurrentRunEmpty(); // FIXME: This can probably be repla
ced by an ASSERT (or just removed). |
| 692 layoutState.setCheckForFloatsFromLastLine(true); |
| 693 resolver.setPosition(InlineIterator(resolver.position().root(), 0, 0
), 0); |
| 694 break; |
| 695 } |
| 696 |
| 697 ASSERT(endOfLine != resolver.position()); |
| 698 |
| 699 // This is a short-cut for empty lines. |
| 700 if (layoutState.lineInfo().isEmpty()) { |
| 701 if (lastRootBox()) |
| 702 lastRootBox()->setLineBreakInfo(endOfLine.object(), endOfLine.of
fset(), resolver.status()); |
| 703 } else { |
| 704 VisualDirectionOverride override = (styleToUse->rtlOrdering() == Vis
ualOrder ? (styleToUse->direction() == LTR ? VisualLeftToRightOverride : VisualR
ightToLeftOverride) : NoVisualOverride); |
| 705 if (isNewUBAParagraph && styleToUse->unicodeBidi() == Plaintext && !
resolver.context()->parent()) { |
| 706 TextDirection direction = determinePlaintextDirectionality(resol
ver.position().root(), resolver.position().object(), resolver.position().offset(
)); |
| 707 resolver.setStatus(BidiStatus(direction, isOverride(styleToUse->
unicodeBidi()))); |
| 708 } |
| 709 // FIXME: This ownership is reversed. We should own the BidiRunList
and pass it to createBidiRunsForLine. |
| 710 BidiRunList<BidiRun>& bidiRuns = resolver.runs(); |
| 711 constructBidiRunsForLine(resolver, bidiRuns, endOfLine, override, la
youtState.lineInfo().previousLineBrokeCleanly(), isNewUBAParagraph); |
| 712 ASSERT(resolver.position() == endOfLine); |
| 713 |
| 714 BidiRun* trailingSpaceRun = resolver.trailingSpaceRun(); |
| 715 |
| 716 if (bidiRuns.runCount() && lineBreaker.lineWasHyphenated()) |
| 717 bidiRuns.logicallyLastRun()->m_hasHyphen = true; |
| 718 |
| 719 // Now that the runs have been ordered, we create the line boxes. |
| 720 // At the same time we figure out where border/padding/margin should
be applied for |
| 721 // inline flow boxes. |
| 722 |
| 723 RootInlineBox* lineBox = createLineBoxesFromBidiRuns(resolver.status
().context->level(), bidiRuns, endOfLine, layoutState.lineInfo(), verticalPositi
onCache, trailingSpaceRun, wordMeasurements); |
| 724 |
| 725 bidiRuns.deleteRuns(); |
| 726 resolver.markCurrentRunEmpty(); // FIXME: This can probably be repla
ced by an ASSERT (or just removed). |
| 727 |
| 728 if (lineBox) { |
| 729 lineBox->setLineBreakInfo(endOfLine.object(), endOfLine.offset()
, resolver.status()); |
| 730 if (layoutState.usesPaintInvalidationBounds()) |
| 731 layoutState.updatePaintInvalidationRangeFromBox(lineBox); |
| 732 } |
| 733 } |
| 734 |
| 735 for (size_t i = 0; i < lineBreaker.positionedObjects().size(); ++i) |
| 736 setStaticPositions(this, lineBreaker.positionedObjects()[i]); |
| 737 |
| 738 if (!layoutState.lineInfo().isEmpty()) |
| 739 layoutState.lineInfo().setFirstLine(false); |
| 740 |
| 741 lineMidpointState.reset(); |
| 742 resolver.setPosition(endOfLine, numberOfIsolateAncestors(endOfLine)); |
| 743 } |
| 744 } |
| 745 |
| 746 void RenderParagraph::linkToEndLineIfNeeded(LineLayoutState& layoutState) |
| 747 { |
| 748 if (layoutState.endLine()) { |
| 749 if (layoutState.endLineMatched()) { |
| 750 // Attach all the remaining lines, and then adjust their y-positions
as needed. |
| 751 LayoutUnit delta = logicalHeight() - layoutState.endLineLogicalTop()
; |
| 752 for (RootInlineBox* line = layoutState.endLine(); line; line = line-
>nextRootBox()) { |
| 753 line->attachLine(); |
| 754 if (delta) { |
| 755 layoutState.updatePaintInvalidationRangeFromBox(line, delta)
; |
| 756 line->adjustBlockDirectionPosition(delta.toFloat()); |
| 757 } |
| 758 } |
| 759 setLogicalHeight(lastRootBox()->lineBottomWithLeading()); |
| 760 } else { |
| 761 // Delete all the remaining lines. |
| 762 deleteLineRange(layoutState, layoutState.endLine()); |
| 763 } |
| 764 } |
| 765 } |
| 766 |
| 767 void RenderParagraph::markDirtyFloatsForPaintInvalidation(Vector<FloatWithRect>&
floats) |
| 768 { |
| 769 size_t floatCount = floats.size(); |
| 770 // Floats that did not have layout did not paint invalidations when we laid
them out. They would have |
| 771 // painted by now if they had moved, but if they stayed at (0, 0), they stil
l need to be |
| 772 // painted. |
| 773 for (size_t i = 0; i < floatCount; ++i) { |
| 774 if (!floats[i].everHadLayout) { |
| 775 RenderBox* f = floats[i].object; |
| 776 if (!f->x() && !f->y() && f->checkForPaintInvalidation()) { |
| 777 f->setShouldDoFullPaintInvalidation(true); |
| 778 } |
| 779 } |
| 780 } |
| 781 } |
| 782 |
| 783 struct InlineMinMaxIterator { |
| 784 /* InlineMinMaxIterator is a class that will iterate over all render objects tha
t contribute to |
| 785 inline min/max width calculations. Note the following about the way it walks
: |
| 786 (1) Positioned content is skipped (since it does not contribute to min/max wi
dth of a block) |
| 787 (2) We do not drill into the children of floats or replaced elements, since y
ou can't break |
| 788 in the middle of such an element. |
| 789 (3) Inline flows (e.g., <a>, <span>, <i>) are walked twice, since each side c
an have |
| 790 distinct borders/margin/padding that contribute to the min/max width. |
| 791 */ |
| 792 RenderObject* parent; |
| 793 RenderObject* current; |
| 794 bool endOfInline; |
| 795 |
| 796 InlineMinMaxIterator(RenderObject* p) |
| 797 : parent(p), current(p), endOfInline(false) |
| 798 { |
| 799 |
| 800 } |
| 801 |
| 802 RenderObject* next(); |
| 803 }; |
| 804 |
| 805 RenderObject* InlineMinMaxIterator::next() |
| 806 { |
| 807 RenderObject* result = 0; |
| 808 bool oldEndOfInline = endOfInline; |
| 809 endOfInline = false; |
| 810 while (current || current == parent) { |
| 811 if (!oldEndOfInline && (current == parent || (!current->isReplaced() &&
!current->isOutOfFlowPositioned()))) |
| 812 result = current->slowFirstChild(); |
| 813 |
| 814 if (!result) { |
| 815 // We hit the end of our inline. (It was empty, e.g., <span></span>.
) |
| 816 if (!oldEndOfInline && current->isRenderInline()) { |
| 817 result = current; |
| 818 endOfInline = true; |
| 819 break; |
| 820 } |
| 821 |
| 822 while (current && current != parent) { |
| 823 result = current->nextSibling(); |
| 824 if (result) |
| 825 break; |
| 826 current = current->parent(); |
| 827 if (current && current != parent && current->isRenderInline()) { |
| 828 result = current; |
| 829 endOfInline = true; |
| 830 break; |
| 831 } |
| 832 } |
| 833 } |
| 834 |
| 835 if (!result) |
| 836 break; |
| 837 |
| 838 if (!result->isOutOfFlowPositioned() && (result->isText() || result->isR
eplaced() || result->isRenderInline())) |
| 839 break; |
| 840 |
| 841 current = result; |
| 842 result = 0; |
| 843 } |
| 844 |
| 845 // Update our position. |
| 846 current = result; |
| 847 return current; |
| 848 } |
| 849 |
| 850 static LayoutUnit getBPMWidth(LayoutUnit childValue, Length cssUnit) |
| 851 { |
| 852 if (cssUnit.type() != Auto) |
| 853 return (cssUnit.isFixed() ? static_cast<LayoutUnit>(cssUnit.value()) : c
hildValue); |
| 854 return 0; |
| 855 } |
| 856 |
| 857 static LayoutUnit getBorderPaddingMargin(RenderBoxModelObject* child, bool endOf
Inline) |
| 858 { |
| 859 RenderStyle* childStyle = child->style(); |
| 860 if (endOfInline) { |
| 861 return getBPMWidth(child->marginEnd(), childStyle->marginEnd()) + |
| 862 getBPMWidth(child->paddingEnd(), childStyle->paddingEnd()) + |
| 863 child->borderEnd(); |
| 864 } |
| 865 return getBPMWidth(child->marginStart(), childStyle->marginStart()) + |
| 866 getBPMWidth(child->paddingStart(), childStyle->paddingStart()) + |
| 867 child->borderStart(); |
| 868 } |
| 869 |
| 870 static inline void stripTrailingSpace(float& inlineMax, float& inlineMin, Render
Object* trailingSpaceChild) |
| 871 { |
| 872 if (trailingSpaceChild && trailingSpaceChild->isText()) { |
| 873 // Collapse away the trailing space at the end of a block. |
| 874 RenderText* t = toRenderText(trailingSpaceChild); |
| 875 const UChar space = ' '; |
| 876 const Font& font = t->style()->font(); // FIXME: This ignores first-line
. |
| 877 float spaceWidth = font.width(constructTextRun(t, font, &space, 1, t->st
yle(), LTR)); |
| 878 inlineMax -= spaceWidth + font.fontDescription().wordSpacing(); |
| 879 if (inlineMin > inlineMax) |
| 880 inlineMin = inlineMax; |
| 881 } |
| 882 } |
| 883 |
| 884 static inline void updatePreferredWidth(LayoutUnit& preferredWidth, float& resul
t) |
| 885 { |
| 886 LayoutUnit snappedResult = LayoutUnit::fromFloatCeil(result); |
| 887 preferredWidth = std::max(snappedResult, preferredWidth); |
| 888 } |
| 889 |
| 890 // When converting between floating point and LayoutUnits we risk losing precisi
on |
| 891 // with each conversion. When this occurs while accumulating our preferred width
s, |
| 892 // we can wind up with a line width that's larger than our maxPreferredWidth due
to |
| 893 // pure float accumulation. |
| 894 static inline LayoutUnit adjustFloatForSubPixelLayout(float value) |
| 895 { |
| 896 return LayoutUnit::fromFloatCeil(value); |
| 897 } |
| 898 |
| 899 // FIXME: This function should be broken into something less monolithic. |
| 900 // FIXME: The main loop here is very similar to LineBreaker::nextSegmentBreak. T
hey can probably reuse code. |
| 901 void RenderParagraph::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth,
LayoutUnit& maxLogicalWidth) const |
| 902 { |
| 903 float inlineMax = 0; |
| 904 float inlineMin = 0; |
| 905 |
| 906 RenderStyle* styleToUse = style(); |
| 907 RenderBlock* containingBlock = this->containingBlock(); |
| 908 LayoutUnit cw = containingBlock ? containingBlock->contentLogicalWidth() : L
ayoutUnit(); |
| 909 |
| 910 // If we are at the start of a line, we want to ignore all white-space. |
| 911 // Also strip spaces if we previously had text that ended in a trailing spac
e. |
| 912 bool stripFrontSpaces = true; |
| 913 RenderObject* trailingSpaceChild = 0; |
| 914 |
| 915 bool autoWrap, oldAutoWrap; |
| 916 autoWrap = oldAutoWrap = styleToUse->autoWrap(); |
| 917 |
| 918 InlineMinMaxIterator childIterator(const_cast<RenderParagraph*>(this)); |
| 919 |
| 920 // Only gets added to the max preffered width once. |
| 921 bool addedTextIndent = false; |
| 922 // Signals the text indent was more negative than the min preferred width |
| 923 bool hasRemainingNegativeTextIndent = false; |
| 924 |
| 925 LayoutUnit textIndent = minimumValueForLength(styleToUse->textIndent(), cw); |
| 926 bool isPrevChildInlineFlow = false; |
| 927 bool shouldBreakLineAfterText = false; |
| 928 while (RenderObject* child = childIterator.next()) { |
| 929 autoWrap = child->isReplaced() ? child->parent()->style()->autoWrap() : |
| 930 child->style()->autoWrap(); |
| 931 |
| 932 // Step One: determine whether or not we need to go ahead and |
| 933 // terminate our current line. Each discrete chunk can become |
| 934 // the new min-width, if it is the widest chunk seen so far, and |
| 935 // it can also become the max-width. |
| 936 |
| 937 // Children fall into three categories: |
| 938 // (1) An inline flow object. These objects always have a min/max of 0, |
| 939 // and are included in the iteration solely so that their margins can |
| 940 // be added in. |
| 941 // |
| 942 // (2) An inline non-text non-flow object, e.g., an inline replaced elem
ent. |
| 943 // These objects can always be on a line by themselves, so in this situa
tion |
| 944 // we need to go ahead and break the current line, and then add in our o
wn |
| 945 // margins and min/max width on its own line, and then terminate the lin
e. |
| 946 // |
| 947 // (3) A text object. Text runs can have breakable characters at the sta
rt, |
| 948 // the middle or the end. They may also lose whitespace off the front if |
| 949 // we're already ignoring whitespace. In order to compute accurate min-w
idth |
| 950 // information, we need three pieces of information. |
| 951 // (a) the min-width of the first non-breakable run. Should be 0 if the
text string |
| 952 // starts with whitespace. |
| 953 // (b) the min-width of the last non-breakable run. Should be 0 if the t
ext string |
| 954 // ends with whitespace. |
| 955 // (c) the min/max width of the string (trimmed for whitespace). |
| 956 // |
| 957 // If the text string starts with whitespace, then we need to go ahead a
nd |
| 958 // terminate our current line (unless we're already in a whitespace stri
pping |
| 959 // mode. |
| 960 // |
| 961 // If the text string has a breakable character in the middle, but didn'
t start |
| 962 // with whitespace, then we add the width of the first non-breakable run
and |
| 963 // then end the current line. We then need to use the intermediate min/m
ax width |
| 964 // values (if any of them are larger than our current min/max). We then
look at |
| 965 // the width of the last non-breakable run and use that to start a new l
ine |
| 966 // (unless we end in whitespace). |
| 967 RenderStyle* childStyle = child->style(); |
| 968 float childMin = 0; |
| 969 float childMax = 0; |
| 970 |
| 971 if (!child->isText()) { |
| 972 // Case (1) and (2). Inline replaced and inline flow elements. |
| 973 if (child->isRenderInline()) { |
| 974 // Add in padding/border/margin from the appropriate side of |
| 975 // the element. |
| 976 float bpm = getBorderPaddingMargin(toRenderInline(child), childI
terator.endOfInline).toFloat(); |
| 977 childMin += bpm; |
| 978 childMax += bpm; |
| 979 |
| 980 inlineMin += childMin; |
| 981 inlineMax += childMax; |
| 982 |
| 983 child->clearPreferredLogicalWidthsDirty(); |
| 984 } else { |
| 985 // Inline replaced elts add in their margins to their min/max va
lues. |
| 986 LayoutUnit margins = 0; |
| 987 Length startMargin = childStyle->marginStart(); |
| 988 Length endMargin = childStyle->marginEnd(); |
| 989 if (startMargin.isFixed()) |
| 990 margins += adjustFloatForSubPixelLayout(startMargin.value())
; |
| 991 if (endMargin.isFixed()) |
| 992 margins += adjustFloatForSubPixelLayout(endMargin.value()); |
| 993 childMin += margins.ceilToFloat(); |
| 994 childMax += margins.ceilToFloat(); |
| 995 } |
| 996 } |
| 997 |
| 998 if (!child->isRenderInline() && !child->isText()) { |
| 999 // Case (2). Inline replaced elements and floats. |
| 1000 // Go ahead and terminate the current line as far as |
| 1001 // minwidth is concerned. |
| 1002 LayoutUnit childMinPreferredLogicalWidth = child->minPreferredLogica
lWidth(); |
| 1003 LayoutUnit childMaxPreferredLogicalWidth = child->maxPreferredLogica
lWidth(); |
| 1004 childMin += childMinPreferredLogicalWidth.ceilToFloat(); |
| 1005 childMax += childMaxPreferredLogicalWidth.ceilToFloat(); |
| 1006 |
| 1007 bool canBreakReplacedElement = true; |
| 1008 if ((canBreakReplacedElement && (autoWrap || oldAutoWrap) && (!isPre
vChildInlineFlow || shouldBreakLineAfterText))) { |
| 1009 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1010 inlineMin = 0; |
| 1011 } |
| 1012 |
| 1013 // Add in text-indent. This is added in only once. |
| 1014 if (!addedTextIndent) { |
| 1015 float ceiledTextIndent = textIndent.ceilToFloat(); |
| 1016 childMin += ceiledTextIndent; |
| 1017 childMax += ceiledTextIndent; |
| 1018 |
| 1019 if (childMin < 0) |
| 1020 textIndent = adjustFloatForSubPixelLayout(childMin); |
| 1021 else |
| 1022 addedTextIndent = true; |
| 1023 } |
| 1024 |
| 1025 // Add our width to the max. |
| 1026 inlineMax += std::max<float>(0, childMax); |
| 1027 |
| 1028 if (!autoWrap || !canBreakReplacedElement || (isPrevChildInlineFlow
&& !shouldBreakLineAfterText)) { |
| 1029 inlineMin += childMin; |
| 1030 } else { |
| 1031 // Now check our line. |
| 1032 updatePreferredWidth(minLogicalWidth, childMin); |
| 1033 |
| 1034 // Now start a new line. |
| 1035 inlineMin = 0; |
| 1036 } |
| 1037 |
| 1038 if (autoWrap && canBreakReplacedElement && isPrevChildInlineFlow) { |
| 1039 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1040 inlineMin = 0; |
| 1041 } |
| 1042 |
| 1043 // We are no longer stripping whitespace at the start of |
| 1044 // a line. |
| 1045 stripFrontSpaces = false; |
| 1046 trailingSpaceChild = 0; |
| 1047 } else if (child->isText()) { |
| 1048 // Case (3). Text. |
| 1049 RenderText* t = toRenderText(child); |
| 1050 |
| 1051 // Determine if we have a breakable character. Pass in |
| 1052 // whether or not we should ignore any spaces at the front |
| 1053 // of the string. If those are going to be stripped out, |
| 1054 // then they shouldn't be considered in the breakable char |
| 1055 // check. |
| 1056 bool hasBreakableChar, hasBreak; |
| 1057 float firstLineMinWidth, lastLineMinWidth; |
| 1058 bool hasBreakableStart, hasBreakableEnd; |
| 1059 float firstLineMaxWidth, lastLineMaxWidth; |
| 1060 t->trimmedPrefWidths(inlineMax, |
| 1061 firstLineMinWidth, hasBreakableStart, lastLineMinWidth, hasBreak
ableEnd, |
| 1062 hasBreakableChar, hasBreak, firstLineMaxWidth, lastLineMaxWidth, |
| 1063 childMin, childMax, stripFrontSpaces, styleToUse->direction()); |
| 1064 |
| 1065 // This text object will not be rendered, but it may still provide a
breaking opportunity. |
| 1066 if (!hasBreak && !childMax) { |
| 1067 if (autoWrap && (hasBreakableStart || hasBreakableEnd)) { |
| 1068 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1069 inlineMin = 0; |
| 1070 } |
| 1071 continue; |
| 1072 } |
| 1073 |
| 1074 if (stripFrontSpaces) |
| 1075 trailingSpaceChild = child; |
| 1076 else |
| 1077 trailingSpaceChild = 0; |
| 1078 |
| 1079 // Add in text-indent. This is added in only once. |
| 1080 float ti = 0; |
| 1081 if (!addedTextIndent || hasRemainingNegativeTextIndent) { |
| 1082 ti = textIndent.ceilToFloat(); |
| 1083 childMin += ti; |
| 1084 firstLineMinWidth += ti; |
| 1085 |
| 1086 // It the text indent negative and larger than the child minimum
, we re-use the remainder |
| 1087 // in future minimum calculations, but using the negative value
again on the maximum |
| 1088 // will lead to under-counting the max pref width. |
| 1089 if (!addedTextIndent) { |
| 1090 childMax += ti; |
| 1091 firstLineMaxWidth += ti; |
| 1092 addedTextIndent = true; |
| 1093 } |
| 1094 |
| 1095 if (childMin < 0) { |
| 1096 textIndent = childMin; |
| 1097 hasRemainingNegativeTextIndent = true; |
| 1098 } |
| 1099 } |
| 1100 |
| 1101 // If we have no breakable characters at all, |
| 1102 // then this is the easy case. We add ourselves to the current |
| 1103 // min and max and continue. |
| 1104 if (!hasBreakableChar) { |
| 1105 inlineMin += childMin; |
| 1106 } else { |
| 1107 if (hasBreakableStart) { |
| 1108 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1109 } else { |
| 1110 inlineMin += firstLineMinWidth; |
| 1111 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1112 childMin -= ti; |
| 1113 } |
| 1114 |
| 1115 inlineMin = childMin; |
| 1116 |
| 1117 if (hasBreakableEnd) { |
| 1118 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1119 inlineMin = 0; |
| 1120 shouldBreakLineAfterText = false; |
| 1121 } else { |
| 1122 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1123 inlineMin = lastLineMinWidth; |
| 1124 shouldBreakLineAfterText = true; |
| 1125 } |
| 1126 } |
| 1127 |
| 1128 if (hasBreak) { |
| 1129 inlineMax += firstLineMaxWidth; |
| 1130 updatePreferredWidth(maxLogicalWidth, inlineMax); |
| 1131 updatePreferredWidth(maxLogicalWidth, childMax); |
| 1132 inlineMax = lastLineMaxWidth; |
| 1133 addedTextIndent = true; |
| 1134 } else { |
| 1135 inlineMax += std::max<float>(0, childMax); |
| 1136 } |
| 1137 } |
| 1138 |
| 1139 if (!child->isText() && child->isRenderInline()) |
| 1140 isPrevChildInlineFlow = true; |
| 1141 else |
| 1142 isPrevChildInlineFlow = false; |
| 1143 |
| 1144 oldAutoWrap = autoWrap; |
| 1145 } |
| 1146 |
| 1147 if (styleToUse->collapseWhiteSpace()) |
| 1148 stripTrailingSpace(inlineMax, inlineMin, trailingSpaceChild); |
| 1149 |
| 1150 updatePreferredWidth(minLogicalWidth, inlineMin); |
| 1151 updatePreferredWidth(maxLogicalWidth, inlineMax); |
| 1152 |
| 1153 maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth); |
| 1154 } |
| 1155 |
| 1156 void RenderParagraph::layoutChildren(bool relayoutChildren, SubtreeLayoutScope&
layoutScope, LayoutUnit& paintInvalidationLogicalTop, LayoutUnit& paintInvalidat
ionLogicalBottom, LayoutUnit beforeEdge, LayoutUnit afterEdge) |
| 1157 { |
| 1158 // Figure out if we should clear out our line boxes. |
| 1159 // FIXME: Handle resize eventually! |
| 1160 bool isFullLayout = !firstLineBox() || selfNeedsLayout() || relayoutChildren
; |
| 1161 LineLayoutState layoutState(isFullLayout, paintInvalidationLogicalTop, paint
InvalidationLogicalBottom); |
| 1162 |
| 1163 if (isFullLayout) { |
| 1164 // Ensure the old line boxes will be erased. |
| 1165 if (firstLineBox()) |
| 1166 setShouldDoFullPaintInvalidation(true); |
| 1167 lineBoxes()->deleteLineBoxes(); |
| 1168 } |
| 1169 |
| 1170 // Text truncation kicks in in two cases: |
| 1171 // 1) If your overflow isn't visible and your text-overflow-mode isn't c
lip. |
| 1172 // 2) If you're an anonymous paragraph with a parent that satisfies #1. |
| 1173 // FIXME: CSS3 says that descendants that are clipped must also know how to
truncate. This is insanely |
| 1174 // difficult to figure out in general (especially in the middle of doing lay
out), so we only handle the |
| 1175 // simple case of an anonymous block truncating when it's parent is clipped. |
| 1176 bool hasTextOverflow = (style()->textOverflow() && hasOverflowClip()) |
| 1177 || (isAnonymousBlock() && parent() && parent()->style()->textOverflow()
&& parent()->hasOverflowClip()); |
| 1178 |
| 1179 // Walk all the lines and delete our ellipsis line boxes if they exist. |
| 1180 if (hasTextOverflow) |
| 1181 deleteEllipsisLineBoxes(); |
| 1182 |
| 1183 if (firstChild()) { |
| 1184 // In full layout mode, clear the line boxes of children upfront. Otherw
ise, |
| 1185 // siblings can run into stale root lineboxes during layout. Then layout |
| 1186 // the replaced elements later. In partial layout mode, line boxes are n
ot |
| 1187 // deleted and only dirtied. In that case, we can layout the replaced |
| 1188 // elements at the same time. |
| 1189 Vector<RenderBox*> replacedChildren; |
| 1190 for (InlineWalker walker(this); !walker.atEnd(); walker.advance()) { |
| 1191 RenderObject* o = walker.current(); |
| 1192 |
| 1193 if (!layoutState.hasInlineChild() && o->isInline()) |
| 1194 layoutState.setHasInlineChild(true); |
| 1195 |
| 1196 if (o->isReplaced() || o->isOutOfFlowPositioned()) { |
| 1197 RenderBox* box = toRenderBox(o); |
| 1198 |
| 1199 updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, box); |
| 1200 |
| 1201 if (o->isOutOfFlowPositioned()) { |
| 1202 o->containingBlock()->insertPositionedObject(box); |
| 1203 } else if (isFullLayout || o->needsLayout()) { |
| 1204 // Replaced element. |
| 1205 box->dirtyLineBoxes(isFullLayout); |
| 1206 if (isFullLayout) |
| 1207 replacedChildren.append(box); |
| 1208 else |
| 1209 o->layoutIfNeeded(); |
| 1210 } |
| 1211 } else if (o->isText() || (o->isRenderInline() && !walker.atEndOfInl
ine())) { |
| 1212 if (!o->isText()) |
| 1213 toRenderInline(o)->updateAlwaysCreateLineBoxes(layoutState.i
sFullLayout()); |
| 1214 if (layoutState.isFullLayout() || o->selfNeedsLayout()) |
| 1215 dirtyLineBoxesForRenderer(o, layoutState.isFullLayout()); |
| 1216 o->clearNeedsLayout(); |
| 1217 } |
| 1218 } |
| 1219 |
| 1220 for (size_t i = 0; i < replacedChildren.size(); i++) |
| 1221 replacedChildren[i]->layoutIfNeeded(); |
| 1222 |
| 1223 layoutRunsAndFloats(layoutState); |
| 1224 } |
| 1225 |
| 1226 // Expand the last line to accommodate Ruby and emphasis marks. |
| 1227 int lastLineAnnotationsAdjustment = 0; |
| 1228 if (lastRootBox()) { |
| 1229 LayoutUnit lowestAllowedPosition = std::max(lastRootBox()->lineBottom(),
logicalHeight() + paddingAfter()); |
| 1230 lastLineAnnotationsAdjustment = lastRootBox()->computeUnderAnnotationAdj
ustment(lowestAllowedPosition); |
| 1231 } |
| 1232 |
| 1233 // Now add in the bottom border/padding. |
| 1234 setLogicalHeight(logicalHeight() + lastLineAnnotationsAdjustment + afterEdge
); |
| 1235 |
| 1236 if (!firstLineBox() && hasLineIfEmpty()) |
| 1237 setLogicalHeight(logicalHeight() + lineHeight(true, HorizontalLine, Posi
tionOfInteriorLineBoxes)); |
| 1238 |
| 1239 // See if we have any lines that spill out of our block. If we do, then we
will possibly need to |
| 1240 // truncate text. |
| 1241 if (hasTextOverflow) |
| 1242 checkLinesForTextOverflow(); |
| 1243 |
| 1244 // Ensure the new line boxes will be painted. |
| 1245 if (isFullLayout && firstLineBox()) |
| 1246 setShouldDoFullPaintInvalidation(true); |
| 1247 } |
| 1248 |
| 1249 void RenderParagraph::checkFloatsInCleanLine(RootInlineBox* line, Vector<FloatWi
thRect>& floats, size_t& floatIndex, bool& encounteredNewFloat, bool& dirtiedByF
loat) |
| 1250 { |
| 1251 Vector<RenderBox*>* cleanLineFloats = line->floatsPtr(); |
| 1252 if (!cleanLineFloats) |
| 1253 return; |
| 1254 |
| 1255 Vector<RenderBox*>::iterator end = cleanLineFloats->end(); |
| 1256 for (Vector<RenderBox*>::iterator it = cleanLineFloats->begin(); it != end;
++it) { |
| 1257 RenderBox* floatingBox = *it; |
| 1258 floatingBox->layoutIfNeeded(); |
| 1259 LayoutSize newSize(floatingBox->width() + floatingBox->marginWidth(), fl
oatingBox->height() + floatingBox->marginHeight()); |
| 1260 if (floats[floatIndex].object != floatingBox) { |
| 1261 encounteredNewFloat = true; |
| 1262 return; |
| 1263 } |
| 1264 |
| 1265 if (floats[floatIndex].rect.size() != newSize) { |
| 1266 LayoutUnit floatTop = floats[floatIndex].rect.y(); |
| 1267 LayoutUnit floatHeight = std::max(floats[floatIndex].rect.height(),
newSize.height()); |
| 1268 floatHeight = std::min(floatHeight, LayoutUnit::max() - floatTop); |
| 1269 line->markDirty(); |
| 1270 markLinesDirtyInBlockRange(line->lineBottomWithLeading(), floatTop +
floatHeight, line); |
| 1271 floats[floatIndex].rect.setSize(newSize); |
| 1272 dirtiedByFloat = true; |
| 1273 } |
| 1274 floatIndex++; |
| 1275 } |
| 1276 } |
| 1277 |
| 1278 RootInlineBox* RenderParagraph::determineStartPosition(LineLayoutState& layoutSt
ate, InlineBidiResolver& resolver) |
| 1279 { |
| 1280 RootInlineBox* curr = 0; |
| 1281 RootInlineBox* last = 0; |
| 1282 |
| 1283 // FIXME: This entire float-checking block needs to be broken into a new fun
ction. |
| 1284 bool dirtiedByFloat = false; |
| 1285 if (!layoutState.isFullLayout()) { |
| 1286 size_t floatIndex = 0; |
| 1287 for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextR
ootBox()) { |
| 1288 // If a new float has been inserted before this line or before its l
ast known float, just do a full layout. |
| 1289 bool encounteredNewFloat = false; |
| 1290 checkFloatsInCleanLine(curr, layoutState.floats(), floatIndex, encou
nteredNewFloat, dirtiedByFloat); |
| 1291 if (encounteredNewFloat) |
| 1292 layoutState.markForFullLayout(); |
| 1293 |
| 1294 if (dirtiedByFloat || layoutState.isFullLayout()) |
| 1295 break; |
| 1296 } |
| 1297 // Check if a new float has been inserted after the last known float. |
| 1298 if (!curr && floatIndex < layoutState.floats().size()) |
| 1299 layoutState.markForFullLayout(); |
| 1300 } |
| 1301 |
| 1302 if (layoutState.isFullLayout()) { |
| 1303 // If we encountered a new float and have inline children, mark ourself
to force us to issue paint invalidations. |
| 1304 if (layoutState.hasInlineChild() && !selfNeedsLayout()) { |
| 1305 setNeedsLayoutAndFullPaintInvalidation(MarkOnlyThis); |
| 1306 setShouldDoFullPaintInvalidation(true); |
| 1307 } |
| 1308 |
| 1309 // FIXME: This should just call deleteLineBoxTree, but that causes |
| 1310 // crashes for fast/repaint tests. |
| 1311 curr = firstRootBox(); |
| 1312 while (curr) { |
| 1313 // Note: This uses nextRootBox() insted of nextLineBox() like delete
LineBoxTree does. |
| 1314 RootInlineBox* next = curr->nextRootBox(); |
| 1315 curr->deleteLine(); |
| 1316 curr = next; |
| 1317 } |
| 1318 ASSERT(!firstLineBox() && !lastLineBox()); |
| 1319 } else { |
| 1320 if (curr) { |
| 1321 // We have a dirty line. |
| 1322 if (RootInlineBox* prevRootBox = curr->prevRootBox()) { |
| 1323 // We have a previous line. |
| 1324 if (!dirtiedByFloat && (!prevRootBox->endsWithBreak() || !prevRo
otBox->lineBreakObj() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->
lineBreakPos() >= toRenderText(prevRootBox->lineBreakObj())->textLength()))) |
| 1325 // The previous line didn't break cleanly or broke at a newl
ine |
| 1326 // that has been deleted, so treat it as dirty too. |
| 1327 curr = prevRootBox; |
| 1328 } |
| 1329 } else { |
| 1330 // No dirty lines were found. |
| 1331 // If the last line didn't break cleanly, treat it as dirty. |
| 1332 if (lastRootBox() && !lastRootBox()->endsWithBreak()) |
| 1333 curr = lastRootBox(); |
| 1334 } |
| 1335 |
| 1336 // If we have no dirty lines, then last is just the last root box. |
| 1337 last = curr ? curr->prevRootBox() : lastRootBox(); |
| 1338 } |
| 1339 |
| 1340 layoutState.lineInfo().setFirstLine(!last); |
| 1341 layoutState.lineInfo().setPreviousLineBrokeCleanly(!last || last->endsWithBr
eak()); |
| 1342 |
| 1343 if (last) { |
| 1344 setLogicalHeight(last->lineBottomWithLeading()); |
| 1345 InlineIterator iter = InlineIterator(this, last->lineBreakObj(), last->l
ineBreakPos()); |
| 1346 resolver.setPosition(iter, numberOfIsolateAncestors(iter)); |
| 1347 resolver.setStatus(last->lineBreakBidiStatus()); |
| 1348 } else { |
| 1349 TextDirection direction = style()->direction(); |
| 1350 if (style()->unicodeBidi() == Plaintext) |
| 1351 direction = determinePlaintextDirectionality(this); |
| 1352 resolver.setStatus(BidiStatus(direction, isOverride(style()->unicodeBidi
()))); |
| 1353 InlineIterator iter = InlineIterator(this, bidiFirstSkippingEmptyInlines
(this, resolver.runs(), &resolver), 0); |
| 1354 resolver.setPosition(iter, numberOfIsolateAncestors(iter)); |
| 1355 } |
| 1356 return curr; |
| 1357 } |
| 1358 |
| 1359 void RenderParagraph::determineEndPosition(LineLayoutState& layoutState, RootInl
ineBox* startLine, InlineIterator& cleanLineStart, BidiStatus& cleanLineBidiStat
us) |
| 1360 { |
| 1361 ASSERT(!layoutState.endLine()); |
| 1362 size_t floatIndex = layoutState.floatIndex(); |
| 1363 RootInlineBox* last = 0; |
| 1364 for (RootInlineBox* curr = startLine->nextRootBox(); curr; curr = curr->next
RootBox()) { |
| 1365 if (!curr->isDirty()) { |
| 1366 bool encounteredNewFloat = false; |
| 1367 bool dirtiedByFloat = false; |
| 1368 checkFloatsInCleanLine(curr, layoutState.floats(), floatIndex, encou
nteredNewFloat, dirtiedByFloat); |
| 1369 if (encounteredNewFloat) |
| 1370 return; |
| 1371 } |
| 1372 if (curr->isDirty()) |
| 1373 last = 0; |
| 1374 else if (!last) |
| 1375 last = curr; |
| 1376 } |
| 1377 |
| 1378 if (!last) |
| 1379 return; |
| 1380 |
| 1381 // At this point, |last| is the first line in a run of clean lines that ends
with the last line |
| 1382 // in the block. |
| 1383 |
| 1384 RootInlineBox* prev = last->prevRootBox(); |
| 1385 cleanLineStart = InlineIterator(this, prev->lineBreakObj(), prev->lineBreakP
os()); |
| 1386 cleanLineBidiStatus = prev->lineBreakBidiStatus(); |
| 1387 layoutState.setEndLineLogicalTop(prev->lineBottomWithLeading()); |
| 1388 |
| 1389 for (RootInlineBox* line = last; line; line = line->nextRootBox()) |
| 1390 line->extractLine(); // Disconnect all line boxes from their render obje
cts while preserving |
| 1391 // their connections to one another. |
| 1392 |
| 1393 layoutState.setEndLine(last); |
| 1394 } |
| 1395 |
| 1396 bool RenderParagraph::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutS
tate) |
| 1397 { |
| 1398 // FIXME(sky): Remove this. |
| 1399 return true; |
| 1400 } |
| 1401 |
| 1402 bool RenderParagraph::matchedEndLine(LineLayoutState& layoutState, const InlineB
idiResolver& resolver, const InlineIterator& endLineStart, const BidiStatus& end
LineStatus) |
| 1403 { |
| 1404 if (resolver.position() == endLineStart) { |
| 1405 if (resolver.status() != endLineStatus) |
| 1406 return false; |
| 1407 return checkPaginationAndFloatsAtEndLine(layoutState); |
| 1408 } |
| 1409 |
| 1410 // The first clean line doesn't match, but we can check a handful of followi
ng lines to try |
| 1411 // to match back up. |
| 1412 static int numLines = 8; // The # of lines we're willing to match against. |
| 1413 RootInlineBox* originalEndLine = layoutState.endLine(); |
| 1414 RootInlineBox* line = originalEndLine; |
| 1415 for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) { |
| 1416 if (line->lineBreakObj() == resolver.position().object() && line->lineBr
eakPos() == resolver.position().offset()) { |
| 1417 // We have a match. |
| 1418 if (line->lineBreakBidiStatus() != resolver.status()) |
| 1419 return false; // ...but the bidi state doesn't match. |
| 1420 |
| 1421 bool matched = false; |
| 1422 RootInlineBox* result = line->nextRootBox(); |
| 1423 layoutState.setEndLine(result); |
| 1424 if (result) { |
| 1425 layoutState.setEndLineLogicalTop(line->lineBottomWithLeading()); |
| 1426 matched = checkPaginationAndFloatsAtEndLine(layoutState); |
| 1427 } |
| 1428 |
| 1429 // Now delete the lines that we failed to sync. |
| 1430 deleteLineRange(layoutState, originalEndLine, result); |
| 1431 return matched; |
| 1432 } |
| 1433 } |
| 1434 |
| 1435 return false; |
| 1436 } |
| 1437 |
| 1438 void RenderParagraph::deleteEllipsisLineBoxes() |
| 1439 { |
| 1440 ETextAlign textAlign = style()->textAlign(); |
| 1441 bool ltr = style()->isLeftToRightDirection(); |
| 1442 bool firstLine = true; |
| 1443 for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox())
{ |
| 1444 if (curr->hasEllipsisBox()) { |
| 1445 curr->clearTruncation(); |
| 1446 |
| 1447 // Shift the line back where it belongs if we cannot accomodate an e
llipsis. |
| 1448 float logicalLeft = logicalLeftOffsetForLine(firstLine).toFloat(); |
| 1449 float availableLogicalWidth = logicalRightOffsetForLine(false) - log
icalLeft; |
| 1450 float totalLogicalWidth = curr->logicalWidth(); |
| 1451 updateLogicalWidthForAlignment(textAlign, curr, 0, logicalLeft, tota
lLogicalWidth, availableLogicalWidth, 0); |
| 1452 |
| 1453 if (ltr) |
| 1454 curr->adjustLogicalPosition((logicalLeft - curr->logicalLeft()),
0); |
| 1455 else |
| 1456 curr->adjustLogicalPosition(-(curr->logicalLeft() - logicalLeft)
, 0); |
| 1457 } |
| 1458 firstLine = false; |
| 1459 } |
| 1460 } |
| 1461 |
| 1462 void RenderParagraph::checkLinesForTextOverflow() |
| 1463 { |
| 1464 // Determine the width of the ellipsis using the current font. |
| 1465 // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP
) if horizontal ellipsis is "not renderable" |
| 1466 const Font& font = style()->font(); |
| 1467 DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, (&horizontalEllipsis, 1)); |
| 1468 const Font& firstLineFont = firstLineStyle()->font(); |
| 1469 // FIXME: We should probably not hard-code the direction here. https://crbug
.com/333004 |
| 1470 TextDirection ellipsisDirection = LTR; |
| 1471 float firstLineEllipsisWidth = firstLineFont.width(constructTextRun(this, fi
rstLineFont, &horizontalEllipsis, 1, firstLineStyle(), ellipsisDirection)); |
| 1472 float ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : fon
t.width(constructTextRun(this, font, &horizontalEllipsis, 1, style(), ellipsisDi
rection)); |
| 1473 |
| 1474 // For LTR text truncation, we want to get the right edge of our padding box
, and then we want to see |
| 1475 // if the right edge of a line box exceeds that. For RTL, we use the left e
dge of the padding box and |
| 1476 // check the left edge of the line box to see if it is less |
| 1477 // Include the scrollbar for overflow blocks, which means we want to use "co
ntentWidth()" |
| 1478 bool ltr = style()->isLeftToRightDirection(); |
| 1479 ETextAlign textAlign = style()->textAlign(); |
| 1480 bool firstLine = true; |
| 1481 for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox())
{ |
| 1482 float currLogicalLeft = curr->logicalLeft(); |
| 1483 LayoutUnit blockRightEdge = logicalRightOffsetForLine(firstLine); |
| 1484 LayoutUnit blockLeftEdge = logicalLeftOffsetForLine(firstLine); |
| 1485 LayoutUnit lineBoxEdge = ltr ? currLogicalLeft + curr->logicalWidth() :
currLogicalLeft; |
| 1486 if ((ltr && lineBoxEdge > blockRightEdge) || (!ltr && lineBoxEdge < bloc
kLeftEdge)) { |
| 1487 // This line spills out of our box in the appropriate direction. No
w we need to see if the line |
| 1488 // can be truncated. In order for truncation to be possible, the li
ne must have sufficient space to |
| 1489 // accommodate our truncation string, and no replaced elements (imag
es, tables) can overlap the ellipsis |
| 1490 // space. |
| 1491 |
| 1492 LayoutUnit width = firstLine ? firstLineEllipsisWidth : ellipsisWidt
h; |
| 1493 LayoutUnit blockEdge = ltr ? blockRightEdge : blockLeftEdge; |
| 1494 if (curr->lineCanAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, wi
dth)) { |
| 1495 float totalLogicalWidth = curr->placeEllipsis(ellipsisStr, ltr,
blockLeftEdge.toFloat(), blockRightEdge.toFloat(), width.toFloat()); |
| 1496 |
| 1497 float logicalLeft = 0; // We are only intersted in the delta fro
m the base position. |
| 1498 float availableLogicalWidth = (blockRightEdge - blockLeftEdge).t
oFloat(); |
| 1499 updateLogicalWidthForAlignment(textAlign, curr, 0, logicalLeft,
totalLogicalWidth, availableLogicalWidth, 0); |
| 1500 if (ltr) |
| 1501 curr->adjustLogicalPosition(logicalLeft, 0); |
| 1502 else |
| 1503 curr->adjustLogicalPosition(logicalLeft - (availableLogicalW
idth - totalLogicalWidth), 0); |
| 1504 } |
| 1505 } |
| 1506 firstLine = false; |
| 1507 } |
| 1508 } |
| 75 | 1509 |
| 76 } // namespace blink | 1510 } // namespace blink |
| OLD | NEW |