// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sky/engine/core/rendering/RenderParagraph.h" #include "sky/engine/core/rendering/BidiRunForLine.h" #include "sky/engine/core/rendering/InlineIterator.h" #include "sky/engine/core/rendering/RenderLayer.h" #include "sky/engine/core/rendering/RenderObjectInlines.h" #include "sky/engine/core/rendering/RenderView.h" #include "sky/engine/core/rendering/TextRunConstructor.h" #include "sky/engine/core/rendering/VerticalPositionCache.h" #include "sky/engine/core/rendering/line/BreakingContextInlineHeaders.h" #include "sky/engine/core/rendering/line/LineLayoutState.h" #include "sky/engine/core/rendering/line/LineWidth.h" #include "sky/engine/core/rendering/line/RenderTextInfo.h" #include "sky/engine/core/rendering/line/WordMeasurement.h" #include "sky/engine/platform/fonts/Character.h" #include "sky/engine/platform/text/BidiResolver.h" #include "sky/engine/wtf/RefCountedLeakCounter.h" #include "sky/engine/wtf/StdLibExtras.h" #include "sky/engine/wtf/Vector.h" #include "sky/engine/wtf/unicode/CharacterNames.h" namespace blink { using namespace WTF::Unicode; RenderParagraph::RenderParagraph() { } RenderParagraph::~RenderParagraph() { } const char* RenderParagraph::renderName() const { return "RenderParagraph"; } LayoutUnit RenderParagraph::logicalLeftSelectionOffset(RenderBlock* rootBlock, LayoutUnit position) { LayoutUnit logicalLeft = logicalLeftOffsetForLine(false); if (logicalLeft == logicalLeftOffsetForContent()) return RenderBlock::logicalLeftSelectionOffset(rootBlock, position); RenderBlock* cb = this; while (cb != rootBlock) { logicalLeft += cb->logicalLeft(); cb = cb->containingBlock(); } return logicalLeft; } LayoutUnit RenderParagraph::logicalRightSelectionOffset(RenderBlock* rootBlock, LayoutUnit position) { LayoutUnit logicalRight = logicalRightOffsetForLine(false); if (logicalRight == logicalRightOffsetForContent()) return RenderBlock::logicalRightSelectionOffset(rootBlock, position); RenderBlock* cb = this; while (cb != rootBlock) { logicalRight += cb->logicalLeft(); cb = cb->containingBlock(); } return logicalRight; } RootInlineBox* RenderParagraph::lineAtIndex(int i) const { ASSERT(i >= 0); for (RootInlineBox* box = firstRootBox(); box; box = box->nextRootBox()) { if (!i--) return box; } return 0; } int RenderParagraph::lineCount(const RootInlineBox* stopRootInlineBox, bool* found) const { int count = 0; for (RootInlineBox* box = firstRootBox(); box; box = box->nextRootBox()) { count++; if (box == stopRootInlineBox) { if (found) *found = true; break; } } return count; } void RenderParagraph::deleteLineBoxTree() { m_lineBoxes.deleteLineBoxTree(); } GapRects RenderParagraph::inlineSelectionGaps(RenderBlock* rootBlock, const LayoutPoint& rootBlockPhysicalPosition, const LayoutSize& offsetFromRootBlock, LayoutUnit& lastLogicalTop, LayoutUnit& lastLogicalLeft, LayoutUnit& lastLogicalRight, const PaintInfo* paintInfo) { GapRects result; bool containsStart = selectionState() == SelectionStart || selectionState() == SelectionBoth; if (!firstLineBox()) { if (containsStart) { // Go ahead and update our lastLogicalTop to be the bottom of the block.
s or empty blocks with height can trip this // case. lastLogicalTop = rootBlock->blockDirectionOffset(offsetFromRootBlock) + logicalHeight(); lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, logicalHeight()); lastLogicalRight = logicalRightSelectionOffset(rootBlock, logicalHeight()); } return result; } RootInlineBox* lastSelectedLine = 0; RootInlineBox* curr; for (curr = firstRootBox(); curr && !curr->hasSelectedChildren(); curr = curr->nextRootBox()) { } // Now paint the gaps for the lines. for (; curr && curr->hasSelectedChildren(); curr = curr->nextRootBox()) { LayoutUnit selTop = curr->selectionTopAdjustedForPrecedingBlock(); LayoutUnit selHeight = curr->selectionHeightAdjustedForPrecedingBlock(); if (!containsStart && !lastSelectedLine && selectionState() != SelectionStart && selectionState() != SelectionBoth) { result.uniteCenter(blockSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, selTop, paintInfo)); } LayoutRect logicalRect(curr->logicalLeft(), selTop, curr->logicalWidth(), selTop + selHeight); logicalRect.move(offsetFromRootBlock); LayoutRect physicalRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, logicalRect); if (!paintInfo || (physicalRect.y() < paintInfo->rect.maxY() && physicalRect.maxY() > paintInfo->rect.y())) result.unite(curr->lineSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, selTop, selHeight, paintInfo)); lastSelectedLine = curr; } if (containsStart && !lastSelectedLine) { // VisibleSelection must start just after our last line. lastSelectedLine = lastRootBox(); } if (lastSelectedLine && selectionState() != SelectionEnd && selectionState() != SelectionBoth) { // Go ahead and update our lastY to be the bottom of the last selected line. lastLogicalTop = rootBlock->blockDirectionOffset(offsetFromRootBlock) + lastSelectedLine->selectionBottom(); lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, lastSelectedLine->selectionBottom()); lastLogicalRight = logicalRightSelectionOffset(rootBlock, lastSelectedLine->selectionBottom()); } return result; } void RenderParagraph::addOverflowFromChildren() { LayoutUnit endPadding = hasOverflowClip() ? paddingEnd() : LayoutUnit(); for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { addLayoutOverflow(curr->paddedLayoutOverflowRect(endPadding)); LayoutRect visualOverflow = curr->visualOverflowRect(curr->lineTop(), curr->lineBottom()); addContentsVisualOverflow(visualOverflow); } } void RenderParagraph::simplifiedNormalFlowLayout() { ListHashSet lineBoxes; for (InlineWalker walker(this); !walker.atEnd(); walker.advance()) { RenderObject* o = walker.current(); if (!o->isOutOfFlowPositioned() && o->isReplaced()) { o->layoutIfNeeded(); if (toRenderBox(o)->inlineBoxWrapper()) { RootInlineBox& box = toRenderBox(o)->inlineBoxWrapper()->root(); lineBoxes.add(&box); } } else if (o->isText() || (o->isRenderInline() && !walker.atEndOfInline())) { o->clearNeedsLayout(); } } // FIXME: Glyph overflow will get lost in this case, but not really a big deal. GlyphOverflowAndFallbackFontsMap textBoxDataMap; for (ListHashSet::const_iterator it = lineBoxes.begin(); it != lineBoxes.end(); ++it) { RootInlineBox* box = *it; box->computeOverflow(box->lineTop(), box->lineBottom(), textBoxDataMap); } } void RenderParagraph::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, Vector& layers) { m_lineBoxes.paint(this, paintInfo, paintOffset, layers); for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { // TODO(ojan): This is wrong at the moment. Inlines can have self painting // layers as well. Either make inlines with self-painting layers work or // don't allow inlines to be self painting. if (child->isBox()) { RenderBox* box = toRenderBox(child); if (box->hasSelfPaintingLayer()) layers.append(box); } } } bool RenderParagraph::hitTestContents(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) { return m_lineBoxes.hitTest(this, request, result, locationInContainer, accumulatedOffset); } void RenderParagraph::markLinesDirtyInBlockRange(LayoutUnit logicalTop, LayoutUnit logicalBottom, RootInlineBox* highest) { if (logicalTop >= logicalBottom) return; RootInlineBox* lowestDirtyLine = lastRootBox(); RootInlineBox* afterLowest = lowestDirtyLine; while (lowestDirtyLine && lowestDirtyLine->lineBottomWithLeading() >= logicalBottom && logicalBottom < LayoutUnit::max()) { afterLowest = lowestDirtyLine; lowestDirtyLine = lowestDirtyLine->prevRootBox(); } while (afterLowest && afterLowest != highest && (afterLowest->lineBottomWithLeading() >= logicalTop || afterLowest->lineBottomWithLeading() < 0)) { afterLowest->markDirty(); afterLowest = afterLowest->prevRootBox(); } } static void updateLogicalWidthForLeftAlignedBlock(bool isLeftToRightDirection, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float availableLogicalWidth) { // The direction of the block should determine what happens with wide lines. // In particular with RTL blocks, wide lines should still spill out to the left. if (isLeftToRightDirection) { if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) trailingSpaceRun->m_box->setLogicalWidth(std::max(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); return; } if (trailingSpaceRun) trailingSpaceRun->m_box->setLogicalWidth(0); else if (totalLogicalWidth > availableLogicalWidth) logicalLeft -= (totalLogicalWidth - availableLogicalWidth); } static void updateLogicalWidthForRightAlignedBlock(bool isLeftToRightDirection, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float availableLogicalWidth) { // Wide lines spill out of the block based off direction. // So even if text-align is right, if direction is LTR, wide lines should overflow out of the right // side of the block. if (isLeftToRightDirection) { if (trailingSpaceRun) { totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); trailingSpaceRun->m_box->setLogicalWidth(0); } if (totalLogicalWidth < availableLogicalWidth) logicalLeft += availableLogicalWidth - totalLogicalWidth; return; } if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) { trailingSpaceRun->m_box->setLogicalWidth(std::max(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); } else logicalLeft += availableLogicalWidth - totalLogicalWidth; } static void updateLogicalWidthForCenterAlignedBlock(bool isLeftToRightDirection, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float availableLogicalWidth) { float trailingSpaceWidth = 0; if (trailingSpaceRun) { totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); trailingSpaceWidth = std::min(trailingSpaceRun->m_box->logicalWidth(), (availableLogicalWidth - totalLogicalWidth + 1) / 2); trailingSpaceRun->m_box->setLogicalWidth(std::max(0, trailingSpaceWidth)); } if (isLeftToRightDirection) logicalLeft += std::max((availableLogicalWidth - totalLogicalWidth) / 2, 0); else logicalLeft += totalLogicalWidth > availableLogicalWidth ? (availableLogicalWidth - totalLogicalWidth) : (availableLogicalWidth - totalLogicalWidth) / 2 - trailingSpaceWidth; } void RenderParagraph::updateLogicalWidthForAlignment(const ETextAlign& textAlign, const RootInlineBox* rootInlineBox, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float& availableLogicalWidth, unsigned expansionOpportunityCount) { TextDirection direction; if (rootInlineBox && rootInlineBox->renderer().style()->unicodeBidi() == Plaintext) direction = rootInlineBox->direction(); else direction = style()->direction(); // Armed with the total width of the line (without justification), // we now examine our text-align property in order to determine where to position the // objects horizontally. The total width of the line can be increased if we end up // justifying text. switch (textAlign) { case LEFT: updateLogicalWidthForLeftAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case RIGHT: updateLogicalWidthForRightAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case CENTER: updateLogicalWidthForCenterAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case JUSTIFY: adjustInlineDirectionLineBounds(expansionOpportunityCount, logicalLeft, availableLogicalWidth); if (expansionOpportunityCount) { if (trailingSpaceRun) { totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); trailingSpaceRun->m_box->setLogicalWidth(0); } break; } // Fall through case TASTART: if (direction == LTR) updateLogicalWidthForLeftAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); else updateLogicalWidthForRightAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; case TAEND: if (direction == LTR) updateLogicalWidthForRightAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); else updateLogicalWidthForLeftAlignedBlock(style()->isLeftToRightDirection(), trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth); break; } } RootInlineBox* RenderParagraph::createAndAppendRootInlineBox() { RootInlineBox* rootBox = createRootInlineBox(); m_lineBoxes.appendLineBox(rootBox); return rootBox; } RootInlineBox* RenderParagraph::createRootInlineBox() { return new RootInlineBox(*this); } InlineBox* RenderParagraph::createInlineBoxForRenderer(RenderObject* obj, bool isRootLineBox, bool isOnlyRun) { if (isRootLineBox) return toRenderParagraph(obj)->createAndAppendRootInlineBox(); if (obj->isText()) { InlineTextBox* textBox = toRenderText(obj)->createInlineTextBox(); // We only treat a box as text for a
if we are on a line by ourself or in strict mode // (Note the use of strict mode. In "almost strict" mode, we don't treat the box for
as text.) return textBox; } if (obj->isBox()) return toRenderBox(obj)->createInlineBox(); return toRenderInline(obj)->createAndAppendInlineFlowBox(); } static inline void dirtyLineBoxesForRenderer(RenderObject* o, bool fullLayout) { if (o->isText()) { RenderText* renderText = toRenderText(o); renderText->dirtyLineBoxes(fullLayout); } else toRenderInline(o)->dirtyLineBoxes(fullLayout); } static bool parentIsConstructedOrHaveNext(InlineFlowBox* parentBox) { do { if (parentBox->isConstructed() || parentBox->nextOnLine()) return true; parentBox = parentBox->parent(); } while (parentBox); return false; } InlineFlowBox* RenderParagraph::createLineBoxes(RenderObject* obj, const LineInfo& lineInfo, InlineBox* childBox) { // See if we have an unconstructed line box for this object that is also // the last item on the line. unsigned lineDepth = 1; InlineFlowBox* parentBox = 0; InlineFlowBox* result = 0; bool hasDefaultLineBoxContain = style()->lineBoxContain() == RenderStyle::initialLineBoxContain(); do { ASSERT_WITH_SECURITY_IMPLICATION(obj->isRenderInline() || obj == this); RenderInline* inlineFlow = (obj != this) ? toRenderInline(obj) : 0; // Get the last box we made for this render object. parentBox = inlineFlow ? inlineFlow->lastLineBox() : toRenderBlock(obj)->lastLineBox(); // If this box or its ancestor is constructed then it is from a previous line, and we need // to make a new box for our line. If this box or its ancestor is unconstructed but it has // something following it on the line, then we know we have to make a new box // as well. In this situation our inline has actually been split in two on // the same line (this can happen with very fancy language mixtures). bool constructedNewBox = false; bool allowedToConstructNewBox = !hasDefaultLineBoxContain || !inlineFlow || inlineFlow->alwaysCreateLineBoxes(); bool canUseExistingParentBox = parentBox && !parentIsConstructedOrHaveNext(parentBox); if (allowedToConstructNewBox && !canUseExistingParentBox) { // We need to make a new box for this render object. Once // made, we need to place it at the end of the current line. InlineBox* newBox = createInlineBoxForRenderer(obj, obj == this); ASSERT_WITH_SECURITY_IMPLICATION(newBox->isInlineFlowBox()); parentBox = toInlineFlowBox(newBox); parentBox->setFirstLineStyleBit(lineInfo.isFirstLine()); if (!hasDefaultLineBoxContain) parentBox->clearDescendantsHaveSameLineHeightAndBaseline(); constructedNewBox = true; } if (constructedNewBox || canUseExistingParentBox) { if (!result) result = parentBox; // If we have hit the block itself, then |box| represents the root // inline box for the line, and it doesn't have to be appended to any parent // inline. if (childBox) parentBox->addToLine(childBox); if (!constructedNewBox || obj == this) break; childBox = parentBox; } // If we've exceeded our line depth, then jump straight to the root and skip all the remaining // intermediate inline flows. obj = (++lineDepth >= cMaxLineDepth) ? this : obj->parent(); } while (true); return result; } template static inline bool endsWithASCIISpaces(const CharacterType* characters, unsigned pos, unsigned end) { while (isASCIISpace(characters[pos])) { pos++; if (pos >= end) return true; } return false; } static bool reachedEndOfTextRenderer(const BidiRunList& bidiRuns) { BidiRun* run = bidiRuns.logicallyLastRun(); if (!run) return true; unsigned pos = run->stop(); RenderObject* r = run->m_object; if (!r->isText()) return false; RenderText* renderText = toRenderText(r); unsigned length = renderText->textLength(); if (pos >= length) return true; if (renderText->is8Bit()) return endsWithASCIISpaces(renderText->characters8(), pos, length); return endsWithASCIISpaces(renderText->characters16(), pos, length); } RootInlineBox* RenderParagraph::constructLine(BidiRunList& bidiRuns, const LineInfo& lineInfo) { ASSERT(bidiRuns.firstRun()); bool rootHasSelectedChildren = false; InlineFlowBox* parentBox = 0; int runCount = bidiRuns.runCount() - lineInfo.runsFromLeadingWhitespace(); for (BidiRun* r = bidiRuns.firstRun(); r; r = r->next()) { // Create a box for our object. bool isOnlyRun = (runCount == 1); if (runCount == 2) isOnlyRun = false; if (lineInfo.isEmpty()) continue; InlineBox* box = createInlineBoxForRenderer(r->m_object, false, isOnlyRun); r->m_box = box; ASSERT(box); if (!box) continue; if (!rootHasSelectedChildren && box->renderer().selectionState() != RenderObject::SelectionNone) rootHasSelectedChildren = true; // If we have no parent box yet, or if the run is not simply a sibling, // then we need to construct inline boxes as necessary to properly enclose the // run's inline box. Segments can only be siblings at the root level, as // they are positioned separately. if (!parentBox || parentBox->renderer() != r->m_object->parent()) { // Create new inline boxes all the way back to the appropriate insertion point. parentBox = createLineBoxes(r->m_object->parent(), lineInfo, box); } else { // Append the inline box to this line. parentBox->addToLine(box); } box->setBidiLevel(r->level()); if (box->isInlineTextBox()) { InlineTextBox* text = toInlineTextBox(box); text->setStart(r->m_start); text->setLen(r->m_stop - r->m_start); text->setDirOverride(r->dirOverride()); if (r->m_hasHyphen) text->setHasHyphen(true); } } ASSERT(lastLineBox() && !lastLineBox()->isConstructed()); // Set the m_selectedChildren flag on the root inline box if one of the leaf inline box // from the bidi runs walk above has a selection state. if (rootHasSelectedChildren) lastLineBox()->root().setHasSelectedChildren(true); // Set bits on our inline flow boxes that indicate which sides should // paint borders/margins/padding. This knowledge will ultimately be used when // we determine the horizontal positions and widths of all the inline boxes on // the line. bool isLogicallyLastRunWrapped = bidiRuns.logicallyLastRun()->m_object && bidiRuns.logicallyLastRun()->m_object->isText() ? !reachedEndOfTextRenderer(bidiRuns) : true; lastLineBox()->determineSpacingForFlowBoxes(lineInfo.isLastLine(), isLogicallyLastRunWrapped, bidiRuns.logicallyLastRun()->m_object); // Now mark the line boxes as being constructed. lastLineBox()->setConstructed(); // Return the last line. return lastRootBox(); } ETextAlign RenderParagraph::textAlignmentForLine(bool endsWithSoftBreak) const { ETextAlign alignment = style()->textAlign(); if (endsWithSoftBreak) return alignment; return (alignment == JUSTIFY) ? TASTART : alignment; } static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* run, RenderText* renderer, float xPos, const LineInfo& lineInfo, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements) { HashSet fallbackFonts; GlyphOverflow glyphOverflow; const Font& font = renderer->style(lineInfo.isFirstLine())->font(); // Always compute glyph overflow if the block's line-box-contain value is "glyphs". if (lineBox->fitsToGlyphs()) { // If we don't stick out of the root line's font box, then don't bother computing our glyph overflow. This optimization // will keep us from computing glyph bounds in nearly all cases. bool includeRootLine = lineBox->includesRootLineBoxFontOrLeading(); int baselineShift = lineBox->verticalPositionForBox(run->m_box, verticalPositionCache); int rootDescent = includeRootLine ? font.fontMetrics().descent() : 0; int rootAscent = includeRootLine ? font.fontMetrics().ascent() : 0; int boxAscent = font.fontMetrics().ascent() - baselineShift; int boxDescent = font.fontMetrics().descent() + baselineShift; if (boxAscent > rootDescent || boxDescent > rootAscent) glyphOverflow.computeBounds = true; } LayoutUnit hyphenWidth = 0; if (toInlineTextBox(run->m_box)->hasHyphen()) { const Font& font = renderer->style(lineInfo.isFirstLine())->font(); hyphenWidth = measureHyphenWidth(renderer, font, run->direction()); } float measuredWidth = 0; bool kerningIsEnabled = font.fontDescription().typesettingFeatures() & Kerning; bool canUseSimpleFontCodePath = renderer->canUseSimpleFontCodePath(); // Since we don't cache glyph overflows, we need to re-measure the run if // the style is linebox-contain: glyph. if (!lineBox->fitsToGlyphs() && canUseSimpleFontCodePath) { int lastEndOffset = run->m_start; for (size_t i = 0, size = wordMeasurements.size(); i < size && lastEndOffset < run->m_stop; ++i) { const WordMeasurement& wordMeasurement = wordMeasurements[i]; if (wordMeasurement.width <=0 || wordMeasurement.startOffset == wordMeasurement.endOffset) continue; if (wordMeasurement.renderer != renderer || wordMeasurement.startOffset != lastEndOffset || wordMeasurement.endOffset > run->m_stop) continue; lastEndOffset = wordMeasurement.endOffset; if (kerningIsEnabled && lastEndOffset == run->m_stop) { int wordLength = lastEndOffset - wordMeasurement.startOffset; measuredWidth += renderer->width(wordMeasurement.startOffset, wordLength, xPos, run->direction(), lineInfo.isFirstLine()); if (i > 0 && wordLength == 1 && renderer->characterAt(wordMeasurement.startOffset) == ' ') measuredWidth += renderer->style()->wordSpacing(); } else measuredWidth += wordMeasurement.width; if (!wordMeasurement.fallbackFonts.isEmpty()) { HashSet::const_iterator end = wordMeasurement.fallbackFonts.end(); for (HashSet::const_iterator it = wordMeasurement.fallbackFonts.begin(); it != end; ++it) fallbackFonts.add(*it); } } if (measuredWidth && lastEndOffset != run->m_stop) { // If we don't have enough cached data, we'll measure the run again. measuredWidth = 0; fallbackFonts.clear(); } } if (!measuredWidth) measuredWidth = renderer->width(run->m_start, run->m_stop - run->m_start, xPos, run->direction(), lineInfo.isFirstLine(), &fallbackFonts, &glyphOverflow); run->m_box->setLogicalWidth(measuredWidth + hyphenWidth); if (!fallbackFonts.isEmpty()) { ASSERT(run->m_box->isText()); GlyphOverflowAndFallbackFontsMap::ValueType* it = textBoxDataMap.add(toInlineTextBox(run->m_box), std::make_pair(Vector(), GlyphOverflow())).storedValue; ASSERT(it->value.first.isEmpty()); copyToVector(fallbackFonts, it->value.first); run->m_box->parent()->clearDescendantsHaveSameLineHeightAndBaseline(); } if (!glyphOverflow.isZero()) { ASSERT(run->m_box->isText()); GlyphOverflowAndFallbackFontsMap::ValueType* it = textBoxDataMap.add(toInlineTextBox(run->m_box), std::make_pair(Vector(), GlyphOverflow())).storedValue; it->value.second = glyphOverflow; run->m_box->clearKnownToHaveNoOverflow(); } } static inline void computeExpansionForJustifiedText(BidiRun* firstRun, BidiRun* trailingSpaceRun, Vector& expansionOpportunities, unsigned expansionOpportunityCount, float& totalLogicalWidth, float availableLogicalWidth) { if (!expansionOpportunityCount || availableLogicalWidth <= totalLogicalWidth) return; size_t i = 0; for (BidiRun* r = firstRun; r; r = r->next()) { if (!r->m_box || r == trailingSpaceRun) continue; if (r->m_object->isText()) { unsigned opportunitiesInRun = expansionOpportunities[i++]; ASSERT(opportunitiesInRun <= expansionOpportunityCount); // Don't justify for white-space: pre. if (r->m_object->style()->whiteSpace() != PRE) { InlineTextBox* textBox = toInlineTextBox(r->m_box); int expansion = (availableLogicalWidth - totalLogicalWidth) * opportunitiesInRun / expansionOpportunityCount; textBox->setExpansion(expansion); totalLogicalWidth += expansion; } expansionOpportunityCount -= opportunitiesInRun; if (!expansionOpportunityCount) break; } } } static void updateLogicalInlinePositions(RenderParagraph* block, float& lineLogicalLeft, float& lineLogicalRight, float& availableLogicalWidth, IndentTextOrNot shouldIndentText) { lineLogicalLeft = block->logicalLeftOffsetForLine(shouldIndentText == IndentText).toFloat(); lineLogicalRight = block->logicalRightOffsetForLine(shouldIndentText == IndentText).toFloat(); availableLogicalWidth = lineLogicalRight - lineLogicalLeft; } void RenderParagraph::computeInlineDirectionPositionsForLine(RootInlineBox* lineBox, const LineInfo& lineInfo, BidiRun* firstRun, BidiRun* trailingSpaceRun, bool reachedEnd, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements) { ETextAlign textAlign = textAlignmentForLine(!reachedEnd && !lineBox->endsWithBreak()); // 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 // box is only affected if it is the first child of its parent element." // CSS3 "text-indent", "each-line" affects the first line of the block container as well as each line after a forced line break, // but does not affect lines after a soft wrap break. bool isFirstLine = lineInfo.isFirstLine(); bool isAfterHardLineBreak = lineBox->prevRootBox() && lineBox->prevRootBox()->endsWithBreak(); IndentTextOrNot shouldIndentText = requiresIndent(isFirstLine, isAfterHardLineBreak, style()); float lineLogicalLeft; float lineLogicalRight; float availableLogicalWidth; updateLogicalInlinePositions(this, lineLogicalLeft, lineLogicalRight, availableLogicalWidth, shouldIndentText); bool needsWordSpacing; if (firstRun && firstRun->m_object->isReplaced()) updateLogicalInlinePositions(this, lineLogicalLeft, lineLogicalRight, availableLogicalWidth, shouldIndentText); computeInlineDirectionPositionsForSegment(lineBox, lineInfo, textAlign, lineLogicalLeft, availableLogicalWidth, firstRun, trailingSpaceRun, textBoxDataMap, verticalPositionCache, wordMeasurements); // The widths of all runs are now known. We can now place every inline box (and // compute accurate widths for the inline flow boxes). needsWordSpacing = false; lineBox->placeBoxesInInlineDirection(lineLogicalLeft, needsWordSpacing); } BidiRun* RenderParagraph::computeInlineDirectionPositionsForSegment(RootInlineBox* lineBox, const LineInfo& lineInfo, ETextAlign textAlign, float& logicalLeft, float& availableLogicalWidth, BidiRun* firstRun, BidiRun* trailingSpaceRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements) { bool needsWordSpacing = true; float totalLogicalWidth = lineBox->getFlowSpacingLogicalWidth().toFloat(); unsigned expansionOpportunityCount = 0; bool isAfterExpansion = true; Vector expansionOpportunities; RenderObject* previousObject = 0; TextJustify textJustify = style()->textJustify(); BidiRun* r = firstRun; for (; r; r = r->next()) { if (!r->m_box || r->m_object->isOutOfFlowPositioned() || r->m_box->isLineBreak()) continue; // Positioned objects are only participating to figure out their // correct static x position. They have no effect on the width. // Similarly, line break boxes have no effect on the width. if (r->m_object->isText()) { RenderText* rt = toRenderText(r->m_object); if (textAlign == JUSTIFY && r != trailingSpaceRun && textJustify != TextJustifyNone) { if (!isAfterExpansion) toInlineTextBox(r->m_box)->setCanHaveLeadingExpansion(true); unsigned opportunitiesInRun; if (rt->is8Bit()) opportunitiesInRun = Character::expansionOpportunityCount(rt->characters8() + r->m_start, r->m_stop - r->m_start, r->m_box->direction(), isAfterExpansion); else opportunitiesInRun = Character::expansionOpportunityCount(rt->characters16() + r->m_start, r->m_stop - r->m_start, r->m_box->direction(), isAfterExpansion); expansionOpportunities.append(opportunitiesInRun); expansionOpportunityCount += opportunitiesInRun; } if (rt->textLength()) { if (!r->m_start && needsWordSpacing && isSpaceOrNewline(rt->characterAt(r->m_start))) totalLogicalWidth += rt->style(lineInfo.isFirstLine())->font().fontDescription().wordSpacing(); needsWordSpacing = !isSpaceOrNewline(rt->characterAt(r->m_stop - 1)); } setLogicalWidthForTextRun(lineBox, r, rt, totalLogicalWidth, lineInfo, textBoxDataMap, verticalPositionCache, wordMeasurements); } else { isAfterExpansion = false; if (!r->m_object->isRenderInline()) { RenderBox* renderBox = toRenderBox(r->m_object); r->m_box->setLogicalWidth(logicalWidthForChild(renderBox).toFloat()); totalLogicalWidth += marginStartForChild(renderBox) + marginEndForChild(renderBox); } } totalLogicalWidth += r->m_box->logicalWidth(); previousObject = r->m_object; } if (isAfterExpansion && !expansionOpportunities.isEmpty()) { expansionOpportunities.last()--; expansionOpportunityCount--; } updateLogicalWidthForAlignment(textAlign, lineBox, trailingSpaceRun, logicalLeft, totalLogicalWidth, availableLogicalWidth, expansionOpportunityCount); computeExpansionForJustifiedText(firstRun, trailingSpaceRun, expansionOpportunities, expansionOpportunityCount, totalLogicalWidth, availableLogicalWidth); return r; } void RenderParagraph::computeBlockDirectionPositionsForLine(RootInlineBox* lineBox, BidiRun* firstRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache) { setLogicalHeight(lineBox->alignBoxesInBlockDirection(logicalHeight(), textBoxDataMap, verticalPositionCache)); // Now make sure we place replaced render objects correctly. for (BidiRun* r = firstRun; r; r = r->next()) { ASSERT(r->m_box); if (!r->m_box) continue; // Skip runs with no line boxes. // Align positioned boxes with the top of the line box. This is // a reasonable approximation of an appropriate y position. if (r->m_object->isOutOfFlowPositioned()) r->m_box->setLogicalTop(logicalHeight().toFloat()); // Position is used to properly position both replaced elements and // to update the static normal flow x/y of positioned elements. if (r->m_object->isText()) toRenderText(r->m_object)->positionLineBox(r->m_box); else if (r->m_object->isBox()) toRenderBox(r->m_object)->positionLineBox(r->m_box); } } // This function constructs line boxes for all of the text runs in the resolver and computes their position. RootInlineBox* RenderParagraph::createLineBoxesFromBidiRuns(unsigned bidiLevel, BidiRunList& bidiRuns, const InlineIterator& end, LineInfo& lineInfo, VerticalPositionCache& verticalPositionCache, BidiRun* trailingSpaceRun, WordMeasurements& wordMeasurements) { if (!bidiRuns.runCount()) return 0; // FIXME: Why is this only done when we had runs? lineInfo.setLastLine(!end.object()); RootInlineBox* lineBox = constructLine(bidiRuns, lineInfo); if (!lineBox) return 0; lineBox->setBidiLevel(bidiLevel); lineBox->setEndsWithBreak(lineInfo.previousLineBrokeCleanly()); GlyphOverflowAndFallbackFontsMap textBoxDataMap; // Now we position all of our text runs horizontally. computeInlineDirectionPositionsForLine(lineBox, lineInfo, bidiRuns.firstRun(), trailingSpaceRun, end.atEnd(), textBoxDataMap, verticalPositionCache, wordMeasurements); // Now position our text runs vertically. computeBlockDirectionPositionsForLine(lineBox, bidiRuns.firstRun(), textBoxDataMap, verticalPositionCache); // Compute our overflow now. lineBox->computeOverflow(lineBox->lineTop(), lineBox->lineBottom(), textBoxDataMap); return lineBox; } static void deleteLineRange(LineLayoutState& layoutState, RootInlineBox* startLine, RootInlineBox* stopLine = 0) { RootInlineBox* boxToDelete = startLine; while (boxToDelete && boxToDelete != stopLine) { // Note: deleteLineRange(firstRootBox()) is not identical to deleteLineBoxTree(). // deleteLineBoxTree uses nextLineBox() instead of nextRootBox() when traversing. RootInlineBox* next = boxToDelete->nextRootBox(); boxToDelete->deleteLine(); boxToDelete = next; } } void RenderParagraph::layoutRunsAndFloats(LineLayoutState& layoutState) { // We want to skip ahead to the first dirty line InlineBidiResolver resolver; RootInlineBox* startLine = determineStartPosition(layoutState, resolver); // We also find the first clean line and extract these lines. We will add them back // if we determine that we're able to synchronize after handling all our dirty lines. InlineIterator cleanLineStart; BidiStatus cleanLineBidiStatus; if (!layoutState.isFullLayout() && startLine) determineEndPosition(layoutState, startLine, cleanLineStart, cleanLineBidiStatus); if (startLine) deleteLineRange(layoutState, startLine); layoutRunsAndFloatsInRange(layoutState, resolver, cleanLineStart, cleanLineBidiStatus); linkToEndLineIfNeeded(layoutState); } void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, InlineBidiResolver& resolver, const InlineIterator& cleanLineStart, const BidiStatus& cleanLineBidiStatus) { RenderStyle* styleToUse = style(); LineMidpointState& lineMidpointState = resolver.midpointState(); InlineIterator endOfLine = resolver.position(); bool checkForEndLineMatch = layoutState.endLine(); RenderTextInfo renderTextInfo; VerticalPositionCache verticalPositionCache; LineBreaker lineBreaker(this); while (!endOfLine.atEnd()) { // FIXME: Is this check necessary before the first iteration or can it be moved to the end? if (checkForEndLineMatch) { layoutState.setEndLineMatched(matchedEndLine(layoutState, resolver, cleanLineStart, cleanLineBidiStatus)); if (layoutState.endLineMatched()) { resolver.setPosition(InlineIterator(resolver.position().root(), 0, 0), 0); break; } } lineMidpointState.reset(); layoutState.lineInfo().setEmpty(true); layoutState.lineInfo().resetRunsFromLeadingWhitespace(); bool isNewUBAParagraph = layoutState.lineInfo().previousLineBrokeCleanly(); FloatingObject* lastFloatFromPreviousLine = 0; WordMeasurements wordMeasurements; endOfLine = lineBreaker.nextLineBreak(resolver, layoutState.lineInfo(), renderTextInfo, lastFloatFromPreviousLine, wordMeasurements); renderTextInfo.m_lineBreakIterator.resetPriorContext(); if (resolver.position().atEnd()) { // FIXME: We shouldn't be creating any runs in nextLineBreak to begin with! // Once BidiRunList is separated from BidiResolver this will not be needed. resolver.runs().deleteRuns(); resolver.markCurrentRunEmpty(); // FIXME: This can probably be replaced by an ASSERT (or just removed). resolver.setPosition(InlineIterator(resolver.position().root(), 0, 0), 0); break; } ASSERT(endOfLine != resolver.position()); // This is a short-cut for empty lines. if (layoutState.lineInfo().isEmpty()) { if (lastRootBox()) lastRootBox()->setLineBreakInfo(endOfLine.object(), endOfLine.offset(), resolver.status()); } else { VisualDirectionOverride override = (styleToUse->rtlOrdering() == VisualOrder ? (styleToUse->direction() == LTR ? VisualLeftToRightOverride : VisualRightToLeftOverride) : NoVisualOverride); if (isNewUBAParagraph && styleToUse->unicodeBidi() == Plaintext && !resolver.context()->parent()) { TextDirection direction = determinePlaintextDirectionality(resolver.position().root(), resolver.position().object(), resolver.position().offset()); resolver.setStatus(BidiStatus(direction, isOverride(styleToUse->unicodeBidi()))); } // FIXME: This ownership is reversed. We should own the BidiRunList and pass it to createBidiRunsForLine. BidiRunList& bidiRuns = resolver.runs(); constructBidiRunsForLine(resolver, bidiRuns, endOfLine, override, layoutState.lineInfo().previousLineBrokeCleanly(), isNewUBAParagraph); ASSERT(resolver.position() == endOfLine); BidiRun* trailingSpaceRun = resolver.trailingSpaceRun(); if (bidiRuns.runCount() && lineBreaker.lineWasHyphenated()) bidiRuns.logicallyLastRun()->m_hasHyphen = true; // Now that the runs have been ordered, we create the line boxes. // At the same time we figure out where border/padding/margin should be applied for // inline flow boxes. RootInlineBox* lineBox = createLineBoxesFromBidiRuns(resolver.status().context->level(), bidiRuns, endOfLine, layoutState.lineInfo(), verticalPositionCache, trailingSpaceRun, wordMeasurements); bidiRuns.deleteRuns(); resolver.markCurrentRunEmpty(); // FIXME: This can probably be replaced by an ASSERT (or just removed). if (lineBox) lineBox->setLineBreakInfo(endOfLine.object(), endOfLine.offset(), resolver.status()); } if (!layoutState.lineInfo().isEmpty()) layoutState.lineInfo().setFirstLine(false); lineMidpointState.reset(); resolver.setPosition(endOfLine, numberOfIsolateAncestors(endOfLine)); } } void RenderParagraph::linkToEndLineIfNeeded(LineLayoutState& layoutState) { if (layoutState.endLine()) { if (layoutState.endLineMatched()) { // Attach all the remaining lines, and then adjust their y-positions as needed. LayoutUnit delta = logicalHeight() - layoutState.endLineLogicalTop(); for (RootInlineBox* line = layoutState.endLine(); line; line = line->nextRootBox()) { line->attachLine(); if (delta) line->adjustBlockDirectionPosition(delta.toFloat()); } setLogicalHeight(lastRootBox()->lineBottomWithLeading()); } else { // Delete all the remaining lines. deleteLineRange(layoutState, layoutState.endLine()); } } } struct InlineMinMaxIterator { /* InlineMinMaxIterator is a class that will iterate over all render objects that contribute to inline min/max width calculations. Note the following about the way it walks: (1) Positioned content is skipped (since it does not contribute to min/max width of a block) (2) We do not drill into the children of floats or replaced elements, since you can't break in the middle of such an element. (3) Inline flows (e.g., , , ) are walked twice, since each side can have distinct borders/margin/padding that contribute to the min/max width. */ RenderObject* parent; RenderObject* current; bool endOfInline; InlineMinMaxIterator(RenderObject* p) : parent(p), current(p), endOfInline(false) { } RenderObject* next(); }; RenderObject* InlineMinMaxIterator::next() { RenderObject* result = 0; bool oldEndOfInline = endOfInline; endOfInline = false; while (current || current == parent) { if (!oldEndOfInline && (current == parent || (!current->isReplaced() && !current->isOutOfFlowPositioned()))) result = current->slowFirstChild(); if (!result) { // We hit the end of our inline. (It was empty, e.g., .) if (!oldEndOfInline && current->isRenderInline()) { result = current; endOfInline = true; break; } while (current && current != parent) { result = current->nextSibling(); if (result) break; current = current->parent(); if (current && current != parent && current->isRenderInline()) { result = current; endOfInline = true; break; } } } if (!result) break; if (!result->isOutOfFlowPositioned() && (result->isText() || result->isReplaced() || result->isRenderInline())) break; current = result; result = 0; } // Update our position. current = result; return current; } static LayoutUnit getBPMWidth(LayoutUnit childValue, Length cssUnit) { if (cssUnit.type() != Auto) return (cssUnit.isFixed() ? static_cast(cssUnit.value()) : childValue); return 0; } static LayoutUnit getBorderPaddingMargin(RenderBoxModelObject* child, bool endOfInline) { RenderStyle* childStyle = child->style(); if (endOfInline) { return getBPMWidth(child->marginEnd(), childStyle->marginEnd()) + getBPMWidth(child->paddingEnd(), childStyle->paddingEnd()) + child->borderEnd(); } return getBPMWidth(child->marginStart(), childStyle->marginStart()) + getBPMWidth(child->paddingStart(), childStyle->paddingStart()) + child->borderStart(); } static inline void stripTrailingSpace(float& inlineMax, float& inlineMin, RenderObject* trailingSpaceChild) { if (trailingSpaceChild && trailingSpaceChild->isText()) { // Collapse away the trailing space at the end of a block. RenderText* t = toRenderText(trailingSpaceChild); const UChar space = ' '; const Font& font = t->style()->font(); // FIXME: This ignores first-line. float spaceWidth = font.width(constructTextRun(t, font, &space, 1, t->style(), LTR)); inlineMax -= spaceWidth + font.fontDescription().wordSpacing(); if (inlineMin > inlineMax) inlineMin = inlineMax; } } static inline void updatePreferredWidth(LayoutUnit& preferredWidth, float& result) { LayoutUnit snappedResult = LayoutUnit::fromFloatCeil(result); preferredWidth = std::max(snappedResult, preferredWidth); } // When converting between floating point and LayoutUnits we risk losing precision // with each conversion. When this occurs while accumulating our preferred widths, // we can wind up with a line width that's larger than our maxPreferredWidth due to // pure float accumulation. static inline LayoutUnit adjustFloatForSubPixelLayout(float value) { return LayoutUnit::fromFloatCeil(value); } // FIXME: This function should be broken into something less monolithic. // FIXME: The main loop here is very similar to LineBreaker::nextSegmentBreak. They can probably reuse code. void RenderParagraph::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const { float inlineMax = 0; float inlineMin = 0; RenderStyle* styleToUse = style(); RenderBlock* containingBlock = this->containingBlock(); LayoutUnit cw = containingBlock ? containingBlock->contentLogicalWidth() : LayoutUnit(); // If we are at the start of a line, we want to ignore all white-space. // Also strip spaces if we previously had text that ended in a trailing space. bool stripFrontSpaces = true; RenderObject* trailingSpaceChild = 0; bool autoWrap, oldAutoWrap; autoWrap = oldAutoWrap = styleToUse->autoWrap(); InlineMinMaxIterator childIterator(const_cast(this)); // Only gets added to the max preffered width once. bool addedTextIndent = false; // Signals the text indent was more negative than the min preferred width bool hasRemainingNegativeTextIndent = false; LayoutUnit textIndent = minimumValueForLength(styleToUse->textIndent(), cw); bool isPrevChildInlineFlow = false; bool shouldBreakLineAfterText = false; while (RenderObject* child = childIterator.next()) { autoWrap = child->isReplaced() ? child->parent()->style()->autoWrap() : child->style()->autoWrap(); // Step One: determine whether or not we need to go ahead and // terminate our current line. Each discrete chunk can become // the new min-width, if it is the widest chunk seen so far, and // it can also become the max-width. // Children fall into three categories: // (1) An inline flow object. These objects always have a min/max of 0, // and are included in the iteration solely so that their margins can // be added in. // // (2) An inline non-text non-flow object, e.g., an inline replaced element. // These objects can always be on a line by themselves, so in this situation // we need to go ahead and break the current line, and then add in our own // margins and min/max width on its own line, and then terminate the line. // // (3) A text object. Text runs can have breakable characters at the start, // the middle or the end. They may also lose whitespace off the front if // we're already ignoring whitespace. In order to compute accurate min-width // information, we need three pieces of information. // (a) the min-width of the first non-breakable run. Should be 0 if the text string // starts with whitespace. // (b) the min-width of the last non-breakable run. Should be 0 if the text string // ends with whitespace. // (c) the min/max width of the string (trimmed for whitespace). // // If the text string starts with whitespace, then we need to go ahead and // terminate our current line (unless we're already in a whitespace stripping // mode. // // If the text string has a breakable character in the middle, but didn't start // with whitespace, then we add the width of the first non-breakable run and // then end the current line. We then need to use the intermediate min/max width // values (if any of them are larger than our current min/max). We then look at // the width of the last non-breakable run and use that to start a new line // (unless we end in whitespace). RenderStyle* childStyle = child->style(); float childMin = 0; float childMax = 0; if (!child->isText()) { // Case (1) and (2). Inline replaced and inline flow elements. if (child->isRenderInline()) { // Add in padding/border/margin from the appropriate side of // the element. float bpm = getBorderPaddingMargin(toRenderInline(child), childIterator.endOfInline).toFloat(); childMin += bpm; childMax += bpm; inlineMin += childMin; inlineMax += childMax; child->clearPreferredLogicalWidthsDirty(); } else { // Inline replaced elts add in their margins to their min/max values. LayoutUnit margins = 0; Length startMargin = childStyle->marginStart(); Length endMargin = childStyle->marginEnd(); if (startMargin.isFixed()) margins += adjustFloatForSubPixelLayout(startMargin.value()); if (endMargin.isFixed()) margins += adjustFloatForSubPixelLayout(endMargin.value()); childMin += margins.ceilToFloat(); childMax += margins.ceilToFloat(); } } if (!child->isRenderInline() && !child->isText()) { // Case (2). Inline replaced elements and floats. // Go ahead and terminate the current line as far as // minwidth is concerned. LayoutUnit childMinPreferredLogicalWidth = child->minPreferredLogicalWidth(); LayoutUnit childMaxPreferredLogicalWidth = child->maxPreferredLogicalWidth(); childMin += childMinPreferredLogicalWidth.ceilToFloat(); childMax += childMaxPreferredLogicalWidth.ceilToFloat(); bool canBreakReplacedElement = true; if ((canBreakReplacedElement && (autoWrap || oldAutoWrap) && (!isPrevChildInlineFlow || shouldBreakLineAfterText))) { updatePreferredWidth(minLogicalWidth, inlineMin); inlineMin = 0; } // Add in text-indent. This is added in only once. if (!addedTextIndent) { float ceiledTextIndent = textIndent.ceilToFloat(); childMin += ceiledTextIndent; childMax += ceiledTextIndent; if (childMin < 0) textIndent = adjustFloatForSubPixelLayout(childMin); else addedTextIndent = true; } // Add our width to the max. inlineMax += std::max(0, childMax); if (!autoWrap || !canBreakReplacedElement || (isPrevChildInlineFlow && !shouldBreakLineAfterText)) { inlineMin += childMin; } else { // Now check our line. updatePreferredWidth(minLogicalWidth, childMin); // Now start a new line. inlineMin = 0; } if (autoWrap && canBreakReplacedElement && isPrevChildInlineFlow) { updatePreferredWidth(minLogicalWidth, inlineMin); inlineMin = 0; } // We are no longer stripping whitespace at the start of // a line. stripFrontSpaces = false; trailingSpaceChild = 0; } else if (child->isText()) { // Case (3). Text. RenderText* t = toRenderText(child); // Determine if we have a breakable character. Pass in // whether or not we should ignore any spaces at the front // of the string. If those are going to be stripped out, // then they shouldn't be considered in the breakable char // check. bool hasBreakableChar, hasBreak; float firstLineMinWidth, lastLineMinWidth; bool hasBreakableStart, hasBreakableEnd; float firstLineMaxWidth, lastLineMaxWidth; t->trimmedPrefWidths(inlineMax, firstLineMinWidth, hasBreakableStart, lastLineMinWidth, hasBreakableEnd, hasBreakableChar, hasBreak, firstLineMaxWidth, lastLineMaxWidth, childMin, childMax, stripFrontSpaces, styleToUse->direction()); // This text object will not be rendered, but it may still provide a breaking opportunity. if (!hasBreak && !childMax) { if (autoWrap && (hasBreakableStart || hasBreakableEnd)) { updatePreferredWidth(minLogicalWidth, inlineMin); inlineMin = 0; } continue; } if (stripFrontSpaces) trailingSpaceChild = child; else trailingSpaceChild = 0; // Add in text-indent. This is added in only once. float ti = 0; if (!addedTextIndent || hasRemainingNegativeTextIndent) { ti = textIndent.ceilToFloat(); childMin += ti; firstLineMinWidth += ti; // It the text indent negative and larger than the child minimum, we re-use the remainder // in future minimum calculations, but using the negative value again on the maximum // will lead to under-counting the max pref width. if (!addedTextIndent) { childMax += ti; firstLineMaxWidth += ti; addedTextIndent = true; } if (childMin < 0) { textIndent = childMin; hasRemainingNegativeTextIndent = true; } } // If we have no breakable characters at all, // then this is the easy case. We add ourselves to the current // min and max and continue. if (!hasBreakableChar) { inlineMin += childMin; } else { if (hasBreakableStart) { updatePreferredWidth(minLogicalWidth, inlineMin); } else { inlineMin += firstLineMinWidth; updatePreferredWidth(minLogicalWidth, inlineMin); childMin -= ti; } inlineMin = childMin; if (hasBreakableEnd) { updatePreferredWidth(minLogicalWidth, inlineMin); inlineMin = 0; shouldBreakLineAfterText = false; } else { updatePreferredWidth(minLogicalWidth, inlineMin); inlineMin = lastLineMinWidth; shouldBreakLineAfterText = true; } } if (hasBreak) { inlineMax += firstLineMaxWidth; updatePreferredWidth(maxLogicalWidth, inlineMax); updatePreferredWidth(maxLogicalWidth, childMax); inlineMax = lastLineMaxWidth; addedTextIndent = true; } else { inlineMax += std::max(0, childMax); } } if (!child->isText() && child->isRenderInline()) isPrevChildInlineFlow = true; else isPrevChildInlineFlow = false; oldAutoWrap = autoWrap; } if (styleToUse->collapseWhiteSpace()) stripTrailingSpace(inlineMax, inlineMin, trailingSpaceChild); updatePreferredWidth(minLogicalWidth, inlineMin); updatePreferredWidth(maxLogicalWidth, inlineMax); maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth); } int RenderParagraph::firstLineBoxBaseline(FontBaselineOrAuto baselineType) const { if (!firstLineBox()) return -1; FontBaseline baseline; if (baselineType.m_auto) baseline = firstRootBox()->baselineType(); else baseline = baselineType.m_baseline; return firstLineBox()->logicalTop() + style(true)->fontMetrics().ascent(baseline); } int RenderParagraph::lastLineBoxBaseline(LineDirectionMode lineDirection) const { if (!firstLineBox() && hasLineIfEmpty()) { const FontMetrics& fontMetrics = firstLineStyle()->fontMetrics(); return fontMetrics.ascent() + (lineHeight(true, lineDirection, PositionOfInteriorLineBoxes) - fontMetrics.height()) / 2 + (lineDirection == HorizontalLine ? borderTop() + paddingTop() : borderRight() + paddingRight()); } if (lastLineBox()) return lastLineBox()->logicalTop() + style(lastLineBox() == firstLineBox())->fontMetrics().ascent(lastRootBox()->baselineType()); return -1; } void RenderParagraph::layout() { ASSERT(needsLayout()); ASSERT(isInlineBlock() || !isInline()); if (simplifiedLayout()) return; SubtreeLayoutScope layoutScope(*this); LayoutUnit oldLeft = logicalLeft(); bool logicalWidthChanged = updateLogicalWidthAndColumnWidth(); bool relayoutChildren = logicalWidthChanged; LayoutUnit beforeEdge = borderBefore() + paddingBefore(); LayoutUnit afterEdge = borderAfter() + paddingAfter(); LayoutUnit previousHeight = logicalHeight(); setLogicalHeight(beforeEdge); layoutChildren(relayoutChildren, layoutScope, beforeEdge, afterEdge); LayoutUnit oldClientAfterEdge = clientLogicalBottom(); updateLogicalHeight(); if (previousHeight != logicalHeight()) relayoutChildren = true; layoutPositionedObjects(relayoutChildren, oldLeft != logicalLeft() ? ForcedLayoutAfterContainingBlockMoved : DefaultLayout); // Add overflow from children (unless we're multi-column, since in that case all our child overflow is clipped anyway). computeOverflow(oldClientAfterEdge); updateLayerTransformAfterLayout(); clearNeedsLayout(); } void RenderParagraph::layoutChildren(bool relayoutChildren, SubtreeLayoutScope& layoutScope, LayoutUnit beforeEdge, LayoutUnit afterEdge) { // Figure out if we should clear out our line boxes. // FIXME: Handle resize eventually! bool isFullLayout = !firstLineBox() || selfNeedsLayout() || relayoutChildren; LineLayoutState layoutState(isFullLayout); if (isFullLayout) lineBoxes()->deleteLineBoxes(); if (firstChild()) { // In full layout mode, clear the line boxes of children upfront. Otherwise, // siblings can run into stale root lineboxes during layout. Then layout // the replaced elements later. In partial layout mode, line boxes are not // deleted and only dirtied. In that case, we can layout the replaced // elements at the same time. Vector replacedChildren; for (InlineWalker walker(this); !walker.atEnd(); walker.advance()) { RenderObject* o = walker.current(); if (!layoutState.hasInlineChild() && o->isInline()) layoutState.setHasInlineChild(true); if (o->isReplaced() || o->isOutOfFlowPositioned()) { RenderBox* box = toRenderBox(o); updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, box); if (o->isOutOfFlowPositioned()) { o->containingBlock()->insertPositionedObject(box); } else if (isFullLayout || o->needsLayout()) { // Replaced element. box->dirtyLineBoxes(isFullLayout); if (isFullLayout) replacedChildren.append(box); else o->layoutIfNeeded(); } } else if (o->isText() || (o->isRenderInline() && !walker.atEndOfInline())) { if (!o->isText()) toRenderInline(o)->updateAlwaysCreateLineBoxes(layoutState.isFullLayout()); if (layoutState.isFullLayout() || o->selfNeedsLayout()) dirtyLineBoxesForRenderer(o, layoutState.isFullLayout()); o->clearNeedsLayout(); } } for (size_t i = 0; i < replacedChildren.size(); i++) replacedChildren[i]->layoutIfNeeded(); layoutRunsAndFloats(layoutState); } // Expand the last line to accommodate Ruby and emphasis marks. int lastLineAnnotationsAdjustment = 0; if (lastRootBox()) { LayoutUnit lowestAllowedPosition = std::max(lastRootBox()->lineBottom(), logicalHeight() + paddingAfter()); lastLineAnnotationsAdjustment = lastRootBox()->computeUnderAnnotationAdjustment(lowestAllowedPosition); } // Now add in the bottom border/padding. setLogicalHeight(logicalHeight() + lastLineAnnotationsAdjustment + afterEdge); if (!firstLineBox() && hasLineIfEmpty()) setLogicalHeight(logicalHeight() + lineHeight(true, HorizontalLine, PositionOfInteriorLineBoxes)); } RootInlineBox* RenderParagraph::determineStartPosition(LineLayoutState& layoutState, InlineBidiResolver& resolver) { RootInlineBox* curr = 0; RootInlineBox* last = 0; if (layoutState.isFullLayout()) { // If we encountered a new float and have inline children, mark ourself to force us to issue paint invalidations. if (layoutState.hasInlineChild() && !selfNeedsLayout()) { setNeedsLayout(MarkOnlyThis); } // FIXME: This should just call deleteLineBoxTree, but that causes // crashes for fast/repaint tests. curr = firstRootBox(); while (curr) { // Note: This uses nextRootBox() insted of nextLineBox() like deleteLineBoxTree does. RootInlineBox* next = curr->nextRootBox(); curr->deleteLine(); curr = next; } ASSERT(!firstLineBox() && !lastLineBox()); } else { if (curr) { // We have a dirty line. if (RootInlineBox* prevRootBox = curr->prevRootBox()) { // We have a previous line. if (!prevRootBox->endsWithBreak() || !prevRootBox->lineBreakObj() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= toRenderText(prevRootBox->lineBreakObj())->textLength())) // The previous line didn't break cleanly or broke at a newline // that has been deleted, so treat it as dirty too. curr = prevRootBox; } } else { // No dirty lines were found. // If the last line didn't break cleanly, treat it as dirty. if (lastRootBox() && !lastRootBox()->endsWithBreak()) curr = lastRootBox(); } // If we have no dirty lines, then last is just the last root box. last = curr ? curr->prevRootBox() : lastRootBox(); } layoutState.lineInfo().setFirstLine(!last); layoutState.lineInfo().setPreviousLineBrokeCleanly(!last || last->endsWithBreak()); if (last) { setLogicalHeight(last->lineBottomWithLeading()); InlineIterator iter = InlineIterator(this, last->lineBreakObj(), last->lineBreakPos()); resolver.setPosition(iter, numberOfIsolateAncestors(iter)); resolver.setStatus(last->lineBreakBidiStatus()); } else { TextDirection direction = style()->direction(); if (style()->unicodeBidi() == Plaintext) direction = determinePlaintextDirectionality(this); resolver.setStatus(BidiStatus(direction, isOverride(style()->unicodeBidi()))); InlineIterator iter = InlineIterator(this, bidiFirstSkippingEmptyInlines(this, resolver.runs(), &resolver), 0); resolver.setPosition(iter, numberOfIsolateAncestors(iter)); } return curr; } void RenderParagraph::determineEndPosition(LineLayoutState& layoutState, RootInlineBox* startLine, InlineIterator& cleanLineStart, BidiStatus& cleanLineBidiStatus) { ASSERT(!layoutState.endLine()); RootInlineBox* last = 0; for (RootInlineBox* curr = startLine->nextRootBox(); curr; curr = curr->nextRootBox()) { if (curr->isDirty()) last = 0; else if (!last) last = curr; } if (!last) return; // At this point, |last| is the first line in a run of clean lines that ends with the last line // in the block. RootInlineBox* prev = last->prevRootBox(); cleanLineStart = InlineIterator(this, prev->lineBreakObj(), prev->lineBreakPos()); cleanLineBidiStatus = prev->lineBreakBidiStatus(); layoutState.setEndLineLogicalTop(prev->lineBottomWithLeading()); for (RootInlineBox* line = last; line; line = line->nextRootBox()) line->extractLine(); // Disconnect all line boxes from their render objects while preserving // their connections to one another. layoutState.setEndLine(last); } bool RenderParagraph::checkPaginationAndFloatsAtEndLine(LineLayoutState& layoutState) { // FIXME(sky): Remove this. return true; } bool RenderParagraph::matchedEndLine(LineLayoutState& layoutState, const InlineBidiResolver& resolver, const InlineIterator& endLineStart, const BidiStatus& endLineStatus) { if (resolver.position() == endLineStart) { if (resolver.status() != endLineStatus) return false; return checkPaginationAndFloatsAtEndLine(layoutState); } // The first clean line doesn't match, but we can check a handful of following lines to try // to match back up. static int numLines = 8; // The # of lines we're willing to match against. RootInlineBox* originalEndLine = layoutState.endLine(); RootInlineBox* line = originalEndLine; for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) { if (line->lineBreakObj() == resolver.position().object() && line->lineBreakPos() == resolver.position().offset()) { // We have a match. if (line->lineBreakBidiStatus() != resolver.status()) return false; // ...but the bidi state doesn't match. bool matched = false; RootInlineBox* result = line->nextRootBox(); layoutState.setEndLine(result); if (result) { layoutState.setEndLineLogicalTop(line->lineBottomWithLeading()); matched = checkPaginationAndFloatsAtEndLine(layoutState); } // Now delete the lines that we failed to sync. deleteLineRange(layoutState, originalEndLine, result); return matched; } } return false; } } // namespace blink