flutter_flutter/engine/core/rendering/RenderParagraph.cpp
Hixie 41c3e58e27 Use the baseline information exposed by C++ to pipe baseline data through RenderBox.
This also fixes the C++ side to give the right baseline information.
Previously it was giving the baseline distance for the font, but not
for the actual laid-out text.

I considered also providing a "defaultBaseline" accessor that returns
the distance for the actual dominant baseline, but it turns out right
now we never decide the baseline is ideographic. We always use the
alphabetic baseline. We should probably fix that...

R=eseidel@chromium.org

Review URL: https://codereview.chromium.org/1200233002.
2015-06-24 17:01:14 -07:00

1711 lines
75 KiB
C++

// 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(ContainerNode* node)
: RenderBlock(node)
{
}
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. <hr>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();
// FIXME: Need to find another way to do this, since scrollbars could show when we don't want them to.
if (hasOverflowClip() && !endPadding && node() && node()->isRootEditableElement() && style()->isLeftToRightDirection())
endPadding = 1;
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<RootInlineBox*> 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<RootInlineBox*>::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<RenderBox*>& 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<float>(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<float>(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<float>(0, trailingSpaceWidth));
}
if (isLeftToRightDirection)
logicalLeft += std::max<float>((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 <br> 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 <br> 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 <typename CharacterType>
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<BidiRun>& 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<BidiRun>& 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;
if (!RuntimeEnabledFeatures::css3TextEnabled())
return (alignment == JUSTIFY) ? TASTART : alignment;
if (alignment != JUSTIFY)
return alignment;
TextAlignLast alignmentLast = style()->textAlignLast();
switch (alignmentLast) {
case TextAlignLastStart:
return TASTART;
case TextAlignLastEnd:
return TAEND;
case TextAlignLastLeft:
return LEFT;
case TextAlignLastRight:
return RIGHT;
case TextAlignLastCenter:
return CENTER;
case TextAlignLastJustify:
return JUSTIFY;
case TextAlignLastAuto:
if (style()->textJustify() == TextJustifyDistribute)
return JUSTIFY;
return TASTART;
}
return alignment;
}
static inline void setLogicalWidthForTextRun(RootInlineBox* lineBox, BidiRun* run, RenderText* renderer, float xPos, const LineInfo& lineInfo,
GlyphOverflowAndFallbackFontsMap& textBoxDataMap, VerticalPositionCache& verticalPositionCache, WordMeasurements& wordMeasurements)
{
HashSet<const SimpleFontData*> 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 SimpleFontData*>::const_iterator end = wordMeasurement.fallbackFonts.end();
for (HashSet<const SimpleFontData*>::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<const SimpleFontData*>(), 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<const SimpleFontData*>(), GlyphOverflow())).storedValue;
it->value.second = glyphOverflow;
run->m_box->clearKnownToHaveNoOverflow();
}
}
static inline void computeExpansionForJustifiedText(BidiRun* firstRun, BidiRun* trailingSpaceRun, Vector<unsigned, 16>& 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<unsigned, 16> 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<BidiRun>& 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<BidiRun>& 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., <a>, <span>, <i>) 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., <span></span>.)
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<LayoutUnit>(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<RenderParagraph*>(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<float>(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<float>(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();
// Text truncation kicks in in two cases:
// 1) If your overflow isn't visible and your text-overflow-mode isn't clip.
// 2) If you're an anonymous paragraph with a parent that satisfies #1.
// FIXME: CSS3 says that descendants that are clipped must also know how to truncate. This is insanely
// difficult to figure out in general (especially in the middle of doing layout), so we only handle the
// simple case of an anonymous block truncating when it's parent is clipped.
bool hasTextOverflow = style()->textOverflow() && hasOverflowClip();
// Walk all the lines and delete our ellipsis line boxes if they exist.
if (hasTextOverflow)
deleteEllipsisLineBoxes();
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<RenderBox*> 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));
// See if we have any lines that spill out of our block. If we do, then we will possibly need to
// truncate text.
if (hasTextOverflow)
checkLinesForTextOverflow();
}
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;
}
void RenderParagraph::deleteEllipsisLineBoxes()
{
ETextAlign textAlign = style()->textAlign();
bool ltr = style()->isLeftToRightDirection();
bool firstLine = true;
for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
if (curr->hasEllipsisBox()) {
curr->clearTruncation();
// Shift the line back where it belongs if we cannot accomodate an ellipsis.
float logicalLeft = logicalLeftOffsetForLine(firstLine).toFloat();
float availableLogicalWidth = logicalRightOffsetForLine(false) - logicalLeft;
float totalLogicalWidth = curr->logicalWidth();
updateLogicalWidthForAlignment(textAlign, curr, 0, logicalLeft, totalLogicalWidth, availableLogicalWidth, 0);
if (ltr)
curr->adjustLogicalPosition((logicalLeft - curr->logicalLeft()), 0);
else
curr->adjustLogicalPosition(-(curr->logicalLeft() - logicalLeft), 0);
}
firstLine = false;
}
}
void RenderParagraph::checkLinesForTextOverflow()
{
// Determine the width of the ellipsis using the current font.
// FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if horizontal ellipsis is "not renderable"
const Font& font = style()->font();
DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, (&horizontalEllipsis, 1));
const Font& firstLineFont = firstLineStyle()->font();
// FIXME: We should probably not hard-code the direction here. https://crbug.com/333004
TextDirection ellipsisDirection = LTR;
float firstLineEllipsisWidth = firstLineFont.width(constructTextRun(this, firstLineFont, &horizontalEllipsis, 1, firstLineStyle(), ellipsisDirection));
float ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.width(constructTextRun(this, font, &horizontalEllipsis, 1, style(), ellipsisDirection));
// For LTR text truncation, we want to get the right edge of our padding box, and then we want to see
// if the right edge of a line box exceeds that. For RTL, we use the left edge of the padding box and
// check the left edge of the line box to see if it is less
// Include the scrollbar for overflow blocks, which means we want to use "contentWidth()"
bool ltr = style()->isLeftToRightDirection();
ETextAlign textAlign = style()->textAlign();
bool firstLine = true;
for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
float currLogicalLeft = curr->logicalLeft();
LayoutUnit blockRightEdge = logicalRightOffsetForLine(firstLine);
LayoutUnit blockLeftEdge = logicalLeftOffsetForLine(firstLine);
LayoutUnit lineBoxEdge = ltr ? currLogicalLeft + curr->logicalWidth() : currLogicalLeft;
if ((ltr && lineBoxEdge > blockRightEdge) || (!ltr && lineBoxEdge < blockLeftEdge)) {
// This line spills out of our box in the appropriate direction. Now we need to see if the line
// can be truncated. In order for truncation to be possible, the line must have sufficient space to
// accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis
// space.
LayoutUnit width = firstLine ? firstLineEllipsisWidth : ellipsisWidth;
LayoutUnit blockEdge = ltr ? blockRightEdge : blockLeftEdge;
if (curr->lineCanAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width)) {
float totalLogicalWidth = curr->placeEllipsis(ellipsisStr, ltr, blockLeftEdge.toFloat(), blockRightEdge.toFloat(), width.toFloat());
float logicalLeft = 0; // We are only intersted in the delta from the base position.
float availableLogicalWidth = (blockRightEdge - blockLeftEdge).toFloat();
updateLogicalWidthForAlignment(textAlign, curr, 0, logicalLeft, totalLogicalWidth, availableLogicalWidth, 0);
if (ltr)
curr->adjustLogicalPosition(logicalLeft, 0);
else
curr->adjustLogicalPosition(logicalLeft - (availableLogicalWidth - totalLogicalWidth), 0);
}
}
firstLine = false;
}
}
} // namespace blink