diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
commit | 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch) | |
tree | 11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebCore/rendering/RenderFlexibleBox.cpp | |
parent | 648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff) | |
download | external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebCore/rendering/RenderFlexibleBox.cpp')
-rw-r--r-- | WebCore/rendering/RenderFlexibleBox.cpp | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/WebCore/rendering/RenderFlexibleBox.cpp b/WebCore/rendering/RenderFlexibleBox.cpp new file mode 100644 index 0000000..f39bf74 --- /dev/null +++ b/WebCore/rendering/RenderFlexibleBox.cpp @@ -0,0 +1,1179 @@ +/* + * This file is part of the render object implementation for KHTML. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" +#include "RenderFlexibleBox.h" + +#include "CharacterNames.h" +#include "RenderLayer.h" +#include "RenderView.h" + +#ifdef ANDROID_LAYOUT +#include "Document.h" +#include "Settings.h" +#endif + +using namespace std; + +namespace WebCore { + +class FlexBoxIterator { +public: + FlexBoxIterator(RenderFlexibleBox* parent) { + box = parent; + if (box->style()->boxOrient() == HORIZONTAL && box->style()->direction() == RTL) + forward = box->style()->boxDirection() != BNORMAL; + else + forward = box->style()->boxDirection() == BNORMAL; + lastOrdinal = 1; + if (!forward) { + // No choice, since we're going backwards, we have to find out the highest ordinal up front. + RenderObject* child = box->firstChild(); + while (child) { + if (child->style()->boxOrdinalGroup() > lastOrdinal) + lastOrdinal = child->style()->boxOrdinalGroup(); + child = child->nextSibling(); + } + } + + reset(); + } + + void reset() { + current = 0; + currentOrdinal = forward ? 0 : lastOrdinal+1; + } + + RenderObject* first() { + reset(); + return next(); + } + + RenderObject* next() { + + do { + if (!current) { + if (forward) { + currentOrdinal++; + if (currentOrdinal > lastOrdinal) + return 0; + current = box->firstChild(); + } else { + currentOrdinal--; + if (currentOrdinal == 0) + return 0; + current = box->lastChild(); + } + } + else + current = forward ? current->nextSibling() : current->previousSibling(); + if (current && current->style()->boxOrdinalGroup() > lastOrdinal) + lastOrdinal = current->style()->boxOrdinalGroup(); + } while (!current || current->style()->boxOrdinalGroup() != currentOrdinal || + current->style()->visibility() == COLLAPSE); + return current; + } + +private: + RenderFlexibleBox* box; + RenderObject* current; + bool forward; + unsigned int currentOrdinal; + unsigned int lastOrdinal; +}; + +RenderFlexibleBox::RenderFlexibleBox(Node* node) +:RenderBlock(node) +{ + setChildrenInline(false); // All of our children must be block-level + m_flexingChildren = m_stretchingChildren = false; +} + +RenderFlexibleBox::~RenderFlexibleBox() +{ +} + +void RenderFlexibleBox::calcHorizontalPrefWidths() +{ + RenderObject *child = firstChild(); + while (child) { + // positioned children don't affect the minmaxwidth + if (child->isPositioned() || child->style()->visibility() == COLLAPSE) { + child = child->nextSibling(); + continue; + } + + // A margin basically has three types: fixed, percentage, and auto (variable). + // Auto and percentage margins simply become 0 when computing min/max width. + // Fixed margins can be added in as is. + Length ml = child->style()->marginLeft(); + Length mr = child->style()->marginRight(); + int margin = 0, marginLeft = 0, marginRight = 0; + if (ml.isFixed()) + marginLeft += ml.value(); + if (mr.isFixed()) + marginRight += mr.value(); + margin = marginLeft + marginRight; + + m_minPrefWidth += child->minPrefWidth() + margin; + m_maxPrefWidth += child->maxPrefWidth() + margin; + + child = child->nextSibling(); + } +} + +void RenderFlexibleBox::calcVerticalPrefWidths() +{ + RenderObject *child = firstChild(); + while(child != 0) + { + // Positioned children and collapsed children don't affect the min/max width + if (child->isPositioned() || child->style()->visibility() == COLLAPSE) { + child = child->nextSibling(); + continue; + } + + // A margin basically has three types: fixed, percentage, and auto (variable). + // Auto/percentage margins simply become 0 when computing min/max width. + // Fixed margins can be added in as is. + Length ml = child->style()->marginLeft(); + Length mr = child->style()->marginRight(); + int margin = 0; + if (ml.isFixed()) + margin += ml.value(); + if (mr.isFixed()) + margin += mr.value(); + + int w = child->minPrefWidth() + margin; + m_minPrefWidth = max(w, m_minPrefWidth); + + w = child->maxPrefWidth() + margin; + m_maxPrefWidth = max(w, m_maxPrefWidth); + + child = child->nextSibling(); + } +} + +void RenderFlexibleBox::calcPrefWidths() +{ + ASSERT(prefWidthsDirty()); + + if (style()->width().isFixed() && style()->width().value() > 0) + m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value()); + else { + m_minPrefWidth = m_maxPrefWidth = 0; + + if (hasMultipleLines() || isVertical()) + calcVerticalPrefWidths(); + else + calcHorizontalPrefWidths(); + + m_maxPrefWidth = max(m_minPrefWidth, m_maxPrefWidth); + } + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value())); + m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value())); + } + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { + m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value())); + m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value())); + } + + int toAdd = borderLeft() + borderRight() + paddingLeft() + paddingRight(); + m_minPrefWidth += toAdd; + m_maxPrefWidth += toAdd; + + setPrefWidthsDirty(false); +} + +void RenderFlexibleBox::layoutBlock(bool relayoutChildren) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && layoutOnlyPositionedObjects()) + return; + + IntRect oldBounds; + IntRect oldOutlineBox; + bool checkForRepaint = checkForRepaintDuringLayout(); + if (checkForRepaint) { + oldBounds = absoluteClippedOverflowRect(); + oldOutlineBox = absoluteOutlineBox(); + } + + if (!hasReflection()) + view()->pushLayoutState(this, IntSize(m_x, m_y)); + else + view()->disableLayoutState(); + + int previousWidth = m_width; + int previousHeight = m_height; + +#ifdef ANDROID_LAYOUT + int previousVisibleWidth = m_visibleWidth; +#endif + + calcWidth(); + calcHeight(); + m_overflowWidth = m_width; + + if (previousWidth != m_width || previousHeight != m_height || + (parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL && + parent()->style()->boxAlign() == BSTRETCH)) + relayoutChildren = true; + +#ifdef ANDROID_LAYOUT + const Settings* settings = document()->settings(); + ASSERT(settings); + if (previousVisibleWidth != m_visibleWidth + && settings->layoutAlgorithm() == Settings::kLayoutFitColumnToScreen) + relayoutChildren = true; +#endif + + m_height = 0; + m_overflowHeight = 0; + m_flexingChildren = m_stretchingChildren = false; + + initMaxMarginValues(); + + // For overflow:scroll blocks, ensure we have both scrollbars in place always. + if (scrollsOverflow()) { + if (style()->overflowX() == OSCROLL) + m_layer->setHasHorizontalScrollbar(true); + if (style()->overflowY() == OSCROLL) + m_layer->setHasVerticalScrollbar(true); + } + + if (isHorizontal()) + layoutHorizontalBox(relayoutChildren); + else + layoutVerticalBox(relayoutChildren); + + int oldHeight = m_height; + calcHeight(); + if (oldHeight != m_height) { + // If the block got expanded in size, then increase our overflowheight to match. + if (m_overflowHeight > m_height) + m_overflowHeight -= (borderBottom() + paddingBottom() + horizontalScrollbarHeight()); + if (m_overflowHeight < m_height) + m_overflowHeight = m_height; + } + if (previousHeight != m_height) + relayoutChildren = true; + + layoutPositionedObjects(relayoutChildren || isRoot()); + + if (!isFloatingOrPositioned() && m_height == 0) { + // We are a block with no border and padding and a computed height + // of 0. The CSS spec states that zero-height blocks collapse their margins + // together. + // When blocks are self-collapsing, we just use the top margin values and set the + // bottom margin max values to 0. This way we don't factor in the values + // twice when we collapse with our previous vertically adjacent and + // following vertically adjacent blocks. + int pos = maxTopPosMargin(); + int neg = maxTopNegMargin(); + if (maxBottomPosMargin() > pos) + pos = maxBottomPosMargin(); + if (maxBottomNegMargin() > neg) + neg = maxBottomNegMargin(); + setMaxTopMargins(pos, neg); + setMaxBottomMargins(0, 0); + } + + // Always ensure our overflow width is at least as large as our width. + if (m_overflowWidth < m_width) + m_overflowWidth = m_width; + + if (!hasOverflowClip()) { + for (ShadowData* boxShadow = style()->boxShadow(); boxShadow; boxShadow = boxShadow->next) { + m_overflowLeft = min(m_overflowLeft, boxShadow->x - boxShadow->blur); + m_overflowWidth = max(m_overflowWidth, m_width + boxShadow->x + boxShadow->blur); + m_overflowTop = min(m_overflowTop, boxShadow->y - boxShadow->blur); + m_overflowHeight = max(m_overflowHeight, m_height + boxShadow->y + boxShadow->blur); + } + + if (hasReflection()) { + IntRect reflection(reflectionBox()); + m_overflowTop = min(m_overflowTop, reflection.y()); + m_overflowHeight = max(m_overflowHeight, reflection.bottom()); + m_overflowLeft = min(m_overflowLeft, reflection.x()); + m_overflowHeight = max(m_overflowWidth, reflection.right()); + } + } + + if (!hasReflection()) + view()->popLayoutState(); + else + view()->enableLayoutState(); + + // Update our scrollbars if we're overflow:auto/scroll/hidden now that we know if + // we overflow or not. + if (hasOverflowClip()) + m_layer->updateScrollInfoAfterLayout(); + + // Repaint with our new bounds if they are different from our old bounds. + if (checkForRepaint) + repaintAfterLayoutIfNeeded(oldBounds, oldOutlineBox); + + setNeedsLayout(false); +} + +void RenderFlexibleBox::layoutHorizontalBox(bool relayoutChildren) +{ + int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); + int yPos = borderTop() + paddingTop(); + int xPos = borderLeft() + paddingLeft(); + bool heightSpecified = false; + int oldHeight = 0; + + unsigned int highestFlexGroup = 0; + unsigned int lowestFlexGroup = 0; + bool haveFlex = false; + int remainingSpace = 0; + m_overflowHeight = m_height; + + // The first walk over our kids is to find out if we have any flexible children. + FlexBoxIterator iterator(this); + RenderObject* child = iterator.next(); + while (child) { + // Check to see if this child flexes. + if (!child->isPositioned() && child->style()->boxFlex() > 0.0f) { + // We always have to lay out flexible objects again, since the flex distribution + // may have changed, and we need to reallocate space. + child->setOverrideSize(-1); + if (!relayoutChildren) + child->setChildNeedsLayout(true, false); + haveFlex = true; + unsigned int flexGroup = child->style()->boxFlexGroup(); + if (lowestFlexGroup == 0) + lowestFlexGroup = flexGroup; + if (flexGroup < lowestFlexGroup) + lowestFlexGroup = flexGroup; + if (flexGroup > highestFlexGroup) + highestFlexGroup = flexGroup; + } + child = iterator.next(); + } + + // We do 2 passes. The first pass is simply to lay everyone out at + // their preferred widths. The second pass handles flexing the children. + do { + // Reset our height. + m_height = yPos; + m_overflowHeight = m_height; + xPos = borderLeft() + paddingLeft(); + + // Our first pass is done without flexing. We simply lay the children + // out within the box. We have to do a layout first in order to determine + // our box's intrinsic height. + int maxAscent = 0, maxDescent = 0; + child = iterator.first(); + while (child) { + // make sure we relayout children if we need it. + if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent()))) + child->setChildNeedsLayout(true, false); + + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + + // Compute the child's vertical margins. + child->calcVerticalMargins(); + + // Now do the layout. + child->layoutIfNeeded(); + + // Update our height and overflow height. + if (style()->boxAlign() == BBASELINE) { + int ascent = child->marginTop() + child->getBaselineOfFirstLineBox(); + if (ascent == -1) + ascent = child->marginTop() + child->height() + child->marginBottom(); + int descent = (child->marginTop() + child->height() + child->marginBottom()) - ascent; + + // Update our maximum ascent. + maxAscent = max(maxAscent, ascent); + + // Update our maximum descent. + maxDescent = max(maxDescent, descent); + + // Now update our height. + m_height = max(yPos + maxAscent + maxDescent, m_height); + } + else + m_height = max(m_height, yPos + child->marginTop() + child->height() + child->marginBottom()); + + child = iterator.next(); + } + + if (!iterator.first() && hasLineIfEmpty()) + m_height += lineHeight(true, true); + + m_height += toAdd; + + // Always make sure our overflowheight is at least our height. + if (m_overflowHeight < m_height) + m_overflowHeight = m_height; + + oldHeight = m_height; + calcHeight(); + + relayoutChildren = false; + if (oldHeight != m_height) + heightSpecified = true; + + // Now that our height is actually known, we can place our boxes. + m_stretchingChildren = (style()->boxAlign() == BSTRETCH); + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child->containingBlock()->insertPositionedObject(child); + if (child->hasStaticX()) { + if (style()->direction() == LTR) + child->setStaticX(xPos); + else child->setStaticX(width() - xPos); + } + if (child->hasStaticY()) + child->setStaticY(yPos); + child = iterator.next(); + continue; + } + + // We need to see if this child's height has changed, since we make block elements + // fill the height of a containing box by default. + // Now do a layout. + int oldChildHeight = child->height(); + static_cast<RenderBox*>(child)->calcHeight(); + if (oldChildHeight != child->height()) + child->setChildNeedsLayout(true, false); + child->layoutIfNeeded(); + + // We can place the child now, using our value of box-align. + xPos += child->marginLeft(); + int childY = yPos; + switch (style()->boxAlign()) { + case BCENTER: + childY += child->marginTop() + max(0, (contentHeight() - (child->height() + child->marginTop() + child->marginBottom()))/2); + break; + case BBASELINE: { + int ascent = child->marginTop() + child->getBaselineOfFirstLineBox(); + if (ascent == -1) + ascent = child->marginTop() + child->height() + child->marginBottom(); + childY += child->marginTop() + (maxAscent - ascent); + break; + } + case BEND: + childY += contentHeight() - child->marginBottom() - child->height(); + break; + default: // BSTART + childY += child->marginTop(); + break; + } + + placeChild(child, xPos, childY); + + if (child->isRenderBlock()) + static_cast<RenderBlock*>(child)->addVisualOverflow(static_cast<RenderBlock*>(child)->floatRect()); + + m_overflowHeight = max(m_overflowHeight, childY + child->overflowHeight(false)); + m_overflowTop = min(m_overflowTop, child->yPos() + child->overflowTop(false)); + + xPos += child->width() + child->marginRight(); + + child = iterator.next(); + } + + remainingSpace = borderLeft() + paddingLeft() + contentWidth() - xPos; + + m_stretchingChildren = false; + if (m_flexingChildren) + haveFlex = false; // We're done. + else if (haveFlex) { + // We have some flexible objects. See if we need to grow/shrink them at all. + if (!remainingSpace) + break; + + // Allocate the remaining space among the flexible objects. If we are trying to + // grow, then we go from the lowest flex group to the highest flex group. For shrinking, + // we go from the highest flex group to the lowest group. + bool expanding = remainingSpace > 0; + unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; + unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; + for (unsigned int i = start; i <= end && remainingSpace; i++) { + // Always start off by assuming the group can get all the remaining space. + int groupRemainingSpace = remainingSpace; + do { + // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width + // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and + // computing the allowed growth before an object hits its min/max width (and thus + // forces a totalFlex recomputation). + int groupRemainingSpaceAtBeginning = groupRemainingSpace; + float totalFlex = 0.0f; + child = iterator.first(); + while (child) { + if (allowedChildFlex(child, expanding, i)) + totalFlex += child->style()->boxFlex(); + child = iterator.next(); + } + child = iterator.first(); + int spaceAvailableThisPass = groupRemainingSpace; + while (child) { + int allowedFlex = allowedChildFlex(child, expanding, i); + if (allowedFlex) { + int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex())); + spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex); + } + child = iterator.next(); + } + + // The flex groups may not have any flexible objects this time around. + if (!spaceAvailableThisPass || totalFlex == 0.0f) { + // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. + groupRemainingSpace = 0; + continue; + } + + // Now distribute the space to objects. + child = iterator.first(); + while (child && spaceAvailableThisPass && totalFlex) { + if (allowedChildFlex(child, expanding, i)) { + int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex)); + if (spaceAdd) { + child->setOverrideSize(child->overrideWidth() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + } + + spaceAvailableThisPass -= spaceAdd; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + + totalFlex -= child->style()->boxFlex(); + } + child = iterator.next(); + } + if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { + // this is not advancing, avoid getting stuck by distributing the remaining pixels + child = iterator.first(); + int spaceAdd = groupRemainingSpace > 0 ? 1 : -1; + while (child && groupRemainingSpace) { + if (allowedChildFlex(child, expanding, i)) { + child->setOverrideSize(child->overrideWidth() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + } + child = iterator.next(); + } + } + } while (groupRemainingSpace); + } + + // We didn't find any children that could grow. + if (haveFlex && !m_flexingChildren) + haveFlex = false; + } + } while (haveFlex); + + m_flexingChildren = false; + + if (remainingSpace > 0 && ((style()->direction() == LTR && style()->boxPack() != BSTART) || + (style()->direction() == RTL && style()->boxPack() != BEND))) { + // Children must be repositioned. + int offset = 0; + if (style()->boxPack() == BJUSTIFY) { + // Determine the total number of children. + int totalChildren = 0; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + totalChildren++; + child = iterator.next(); + } + + // Iterate over the children and space them out according to the + // justification level. + if (totalChildren > 1) { + totalChildren--; + bool firstChild = true; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + + if (firstChild) { + firstChild = false; + child = iterator.next(); + continue; + } + + offset += remainingSpace/totalChildren; + remainingSpace -= (remainingSpace/totalChildren); + totalChildren--; + + placeChild(child, child->xPos()+offset, child->yPos()); + child = iterator.next(); + } + } + } else { + if (style()->boxPack() == BCENTER) + offset += remainingSpace/2; + else // END for LTR, START for RTL + offset += remainingSpace; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + placeChild(child, child->xPos()+offset, child->yPos()); + child = iterator.next(); + } + } + } + + child = iterator.first(); + while (child && child->isPositioned()) { + child = iterator.next(); + } + + if (child) { + m_overflowLeft = min(child->xPos() + child->overflowLeft(false), m_overflowLeft); + + RenderObject* lastChild = child; + while ((child = iterator.next())) { + if (!child->isPositioned()) + lastChild = child; + } + m_overflowWidth = max(lastChild->xPos() + lastChild->overflowWidth(false), m_overflowWidth); + } + + // So that the calcHeight in layoutBlock() knows to relayout positioned objects because of + // a height change, we revert our height back to the intrinsic height before returning. + if (heightSpecified) + m_height = oldHeight; +} + +void RenderFlexibleBox::layoutVerticalBox(bool relayoutChildren) +{ + int xPos = borderLeft() + paddingLeft(); + int yPos = borderTop() + paddingTop(); + if( style()->direction() == RTL ) + xPos = m_width - paddingRight() - borderRight(); + int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); + bool heightSpecified = false; + int oldHeight = 0; + + unsigned int highestFlexGroup = 0; + unsigned int lowestFlexGroup = 0; + bool haveFlex = false; + int remainingSpace = 0; + + // The first walk over our kids is to find out if we have any flexible children. + FlexBoxIterator iterator(this); + RenderObject *child = iterator.next(); + while (child) { + // Check to see if this child flexes. + if (!child->isPositioned() && child->style()->boxFlex() > 0.0f) { + // We always have to lay out flexible objects again, since the flex distribution + // may have changed, and we need to reallocate space. + child->setOverrideSize(-1); + if (!relayoutChildren) + child->setChildNeedsLayout(true, false); + haveFlex = true; + unsigned int flexGroup = child->style()->boxFlexGroup(); + if (lowestFlexGroup == 0) + lowestFlexGroup = flexGroup; + if (flexGroup < lowestFlexGroup) + lowestFlexGroup = flexGroup; + if (flexGroup > highestFlexGroup) + highestFlexGroup = flexGroup; + } + child = iterator.next(); + } + + // We confine the line clamp ugliness to vertical flexible boxes (thus keeping it out of + // mainstream block layout); this is not really part of the XUL box model. + bool haveLineClamp = style()->lineClamp() >= 0 && style()->lineClamp() <= 100; + if (haveLineClamp) { + int maxLineCount = 0; + child = iterator.first(); + while (child) { + if (!child->isPositioned()) { + if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())) || + (child->style()->height().isAuto() && child->isBlockFlow() && !child->needsLayout())) { + child->setChildNeedsLayout(true, false); + + // Dirty all the positioned objects. + if (child->isRenderBlock()) { + static_cast<RenderBlock*>(child)->markPositionedObjectsForLayout(); + static_cast<RenderBlock*>(child)->clearTruncation(); + } + } + child->layoutIfNeeded(); + if (child->style()->height().isAuto() && child->isBlockFlow()) + maxLineCount = max(maxLineCount, static_cast<RenderBlock*>(child)->lineCount()); + } + child = iterator.next(); + } + + // Get the # of lines and then alter all block flow children with auto height to use the + // specified height. We always try to leave room for at least one line. + int numVisibleLines = max(1, static_cast<int>((maxLineCount + 1) * style()->lineClamp() / 100.0)); + if (numVisibleLines < maxLineCount) { + for (child = iterator.first(); child; child = iterator.next()) { + if (child->isPositioned() || !child->style()->height().isAuto() || !child->isBlockFlow()) + continue; + + RenderBlock* blockChild = static_cast<RenderBlock*>(child); + int lineCount = blockChild->lineCount(); + if (lineCount <= numVisibleLines) + continue; + + int newHeight = blockChild->heightForLineCount(numVisibleLines); + if (newHeight == child->height()) + continue; + + child->setChildNeedsLayout(true, false); + child->setOverrideSize(newHeight); + m_flexingChildren = true; + child->layoutIfNeeded(); + m_flexingChildren = false; + child->setOverrideSize(-1); + + // FIXME: For now don't support RTL. + if (style()->direction() != LTR) + continue; + + // Get the last line + RootInlineBox* lastLine = blockChild->lineAtIndex(lineCount-1); + if (!lastLine) + continue; + + // See if the last item is an anchor + InlineBox* anchorBox = lastLine->lastChild(); + if (!anchorBox) + continue; + if (!anchorBox->object()->element()) + continue; + if (!anchorBox->object()->element()->isLink()) + continue; + + RootInlineBox* lastVisibleLine = blockChild->lineAtIndex(numVisibleLines-1); + if (!lastVisibleLine) + continue; + + const UChar ellipsisAndSpace[2] = { horizontalEllipsis, ' ' }; + static AtomicString ellipsisAndSpaceStr(ellipsisAndSpace, 2); + + const Font& font = style(numVisibleLines == 1)->font(); + int ellipsisAndSpaceWidth = font.width(TextRun(ellipsisAndSpace, 2)); + + // Get ellipsis width + " " + anchor width + int totalWidth = ellipsisAndSpaceWidth + anchorBox->width(); + + // See if this width can be accommodated on the last visible line + RenderBlock* destBlock = static_cast<RenderBlock*>(lastVisibleLine->object()); + RenderBlock* srcBlock = static_cast<RenderBlock*>(lastLine->object()); + + // FIXME: Directions of src/destBlock could be different from our direction and from one another. + if (srcBlock->style()->direction() != LTR) + continue; + if (destBlock->style()->direction() != LTR) + continue; + + int blockEdge = destBlock->rightOffset(lastVisibleLine->yPos()); + if (!lastVisibleLine->canAccommodateEllipsis(true, blockEdge, + lastVisibleLine->xPos() + lastVisibleLine->width(), + totalWidth)) + continue; + + // Let the truncation code kick in. + lastVisibleLine->placeEllipsis(ellipsisAndSpaceStr, true, blockEdge, totalWidth, anchorBox); + destBlock->setHasMarkupTruncation(true); + } + } + } + + // We do 2 passes. The first pass is simply to lay everyone out at + // their preferred widths. The second pass handles flexing the children. + // Our first pass is done without flexing. We simply lay the children + // out within the box. + do { + m_height = borderTop() + paddingTop(); + int minHeight = m_height + toAdd; + m_overflowHeight = m_height; + + child = iterator.first(); + while (child) { + // make sure we relayout children if we need it. + if (!haveLineClamp && (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())))) + child->setChildNeedsLayout(true, false); + + if (child->isPositioned()) + { + child->containingBlock()->insertPositionedObject(child); + if (child->hasStaticX()) { + if (style()->direction() == LTR) + child->setStaticX(borderLeft()+paddingLeft()); + else + child->setStaticX(borderRight()+paddingRight()); + } + if (child->hasStaticY()) + child->setStaticY(m_height); + child = iterator.next(); + continue; + } + + // Compute the child's vertical margins. + child->calcVerticalMargins(); + + // Add in the child's marginTop to our height. + m_height += child->marginTop(); + + // Now do a layout. + child->layoutIfNeeded(); + + // We can place the child now, using our value of box-align. + int childX = borderLeft() + paddingLeft(); + switch (style()->boxAlign()) { + case BCENTER: + case BBASELINE: // Baseline just maps to center for vertical boxes + childX += child->marginLeft() + max(0, (contentWidth() - (child->width() + child->marginLeft() + child->marginRight()))/2); + break; + case BEND: + if (style()->direction() == RTL) + childX += child->marginLeft(); + else + childX += contentWidth() - child->marginRight() - child->width(); + break; + default: // BSTART/BSTRETCH + if (style()->direction() == LTR) + childX += child->marginLeft(); + else + childX += contentWidth() - child->marginRight() - child->width(); + break; + } + + // Place the child. + placeChild(child, childX, m_height); + m_height += child->height() + child->marginBottom(); + + if (child->isRenderBlock()) + static_cast<RenderBlock*>(child)->addVisualOverflow(static_cast<RenderBlock*>(child)->floatRect()); + + // See if this child has made our overflow need to grow. + m_overflowWidth = max(child->xPos() + child->overflowWidth(false), m_overflowWidth); + m_overflowLeft = min(child->xPos() + child->overflowLeft(false), m_overflowLeft); + + child = iterator.next(); + } + + yPos = m_height; + + if (!iterator.first() && hasLineIfEmpty()) + m_height += lineHeight(true, true); + + m_height += toAdd; + + // Negative margins can cause our height to shrink below our minimal height (border/padding). + // If this happens, ensure that the computed height is increased to the minimal height. + if (m_height < minHeight) + m_height = minHeight; + + // Always make sure our overflowheight is at least our height. + if (m_overflowHeight < m_height) + m_overflowHeight = m_height; + + // Now we have to calc our height, so we know how much space we have remaining. + oldHeight = m_height; + calcHeight(); + if (oldHeight != m_height) + heightSpecified = true; + + remainingSpace = borderTop() + paddingTop() + contentHeight() - yPos; + + if (m_flexingChildren) + haveFlex = false; // We're done. + else if (haveFlex) { + // We have some flexible objects. See if we need to grow/shrink them at all. + if (!remainingSpace) + break; + + // Allocate the remaining space among the flexible objects. If we are trying to + // grow, then we go from the lowest flex group to the highest flex group. For shrinking, + // we go from the highest flex group to the lowest group. + bool expanding = remainingSpace > 0; + unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; + unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; + for (unsigned int i = start; i <= end && remainingSpace; i++) { + // Always start off by assuming the group can get all the remaining space. + int groupRemainingSpace = remainingSpace; + do { + // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width + // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and + // computing the allowed growth before an object hits its min/max width (and thus + // forces a totalFlex recomputation). + int groupRemainingSpaceAtBeginning = groupRemainingSpace; + float totalFlex = 0.0f; + child = iterator.first(); + while (child) { + if (allowedChildFlex(child, expanding, i)) + totalFlex += child->style()->boxFlex(); + child = iterator.next(); + } + child = iterator.first(); + int spaceAvailableThisPass = groupRemainingSpace; + while (child) { + int allowedFlex = allowedChildFlex(child, expanding, i); + if (allowedFlex) { + int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex())); + spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex); + } + child = iterator.next(); + } + + // The flex groups may not have any flexible objects this time around. + if (!spaceAvailableThisPass || totalFlex == 0.0f) { + // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. + groupRemainingSpace = 0; + continue; + } + + // Now distribute the space to objects. + child = iterator.first(); + while (child && spaceAvailableThisPass && totalFlex) { + if (allowedChildFlex(child, expanding, i)) { + int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex)); + if (spaceAdd) { + child->setOverrideSize(child->overrideHeight() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + } + + spaceAvailableThisPass -= spaceAdd; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + + totalFlex -= child->style()->boxFlex(); + } + child = iterator.next(); + } + if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { + // this is not advancing, avoid getting stuck by distributing the remaining pixels + child = iterator.first(); + int spaceAdd = groupRemainingSpace > 0 ? 1 : -1; + while (child && groupRemainingSpace) { + if (allowedChildFlex(child, expanding, i)) { + child->setOverrideSize(child->overrideHeight() + spaceAdd); + m_flexingChildren = true; + relayoutChildren = true; + remainingSpace -= spaceAdd; + groupRemainingSpace -= spaceAdd; + } + child = iterator.next(); + } + } + } while (groupRemainingSpace); + } + + // We didn't find any children that could grow. + if (haveFlex && !m_flexingChildren) + haveFlex = false; + } + } while (haveFlex); + + if (style()->boxPack() != BSTART && remainingSpace > 0) { + // Children must be repositioned. + int offset = 0; + if (style()->boxPack() == BJUSTIFY) { + // Determine the total number of children. + int totalChildren = 0; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + totalChildren++; + child = iterator.next(); + } + + // Iterate over the children and space them out according to the + // justification level. + if (totalChildren > 1) { + totalChildren--; + bool firstChild = true; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + + if (firstChild) { + firstChild = false; + child = iterator.next(); + continue; + } + + offset += remainingSpace/totalChildren; + remainingSpace -= (remainingSpace/totalChildren); + totalChildren--; + placeChild(child, child->xPos(), child->yPos()+offset); + child = iterator.next(); + } + } + } else { + if (style()->boxPack() == BCENTER) + offset += remainingSpace/2; + else // END + offset += remainingSpace; + child = iterator.first(); + while (child) { + if (child->isPositioned()) { + child = iterator.next(); + continue; + } + placeChild(child, child->xPos(), child->yPos()+offset); + child = iterator.next(); + } + } + } + + child = iterator.first(); + while (child && child->isPositioned()) { + child = iterator.next(); + } + + if (child) { + m_overflowTop = min(child->yPos() + child->overflowTop(false), m_overflowTop); + + RenderObject* lastChild = child; + while ((child = iterator.next())) { + if (!child->isPositioned()) + lastChild = child; + } + m_overflowHeight = max(lastChild->yPos() + lastChild->overflowHeight(false), m_overflowHeight); + } + + // So that the calcHeight in layoutBlock() knows to relayout positioned objects because of + // a height change, we revert our height back to the intrinsic height before returning. + if (heightSpecified) + m_height = oldHeight; +} + +void RenderFlexibleBox::placeChild(RenderObject* child, int x, int y) +{ + IntRect oldRect(child->xPos(), child->yPos() , child->width(), child->height()); + + // Place the child. + child->setPos(x, y); + + // If the child moved, we have to repaint it as well as any floating/positioned + // descendants. An exception is if we need a layout. In this case, we know we're going to + // repaint ourselves (and the child) anyway. + if (!selfNeedsLayout() && child->checkForRepaintDuringLayout()) + child->repaintDuringLayoutIfMoved(oldRect); +} + +int RenderFlexibleBox::allowedChildFlex(RenderObject* child, bool expanding, unsigned int group) +{ + if (child->isPositioned() || child->style()->boxFlex() == 0.0f || child->style()->boxFlexGroup() != group) + return 0; + + if (expanding) { + if (isHorizontal()) { + // FIXME: For now just handle fixed values. + int maxW = INT_MAX; + int w = child->overrideWidth() - (child->borderLeft() + child->borderRight() + child->paddingLeft() + child->paddingRight()); + if (!child->style()->maxWidth().isUndefined() && + child->style()->maxWidth().isFixed()) + maxW = child->style()->maxWidth().value(); + else if (child->style()->maxWidth().type() == Intrinsic) + maxW = child->maxPrefWidth(); + else if (child->style()->maxWidth().type() == MinIntrinsic) + maxW = child->minPrefWidth(); + if (maxW == INT_MAX) + return maxW; + return max(0, maxW - w); + } else { + // FIXME: For now just handle fixed values. + int maxH = INT_MAX; + int h = child->overrideHeight() - (child->borderTop() + child->borderBottom() + child->paddingTop() + child->paddingBottom()); + if (!child->style()->maxHeight().isUndefined() && + child->style()->maxHeight().isFixed()) + maxH = child->style()->maxHeight().value(); + if (maxH == INT_MAX) + return maxH; + return max(0, maxH - h); + } + } + + // FIXME: For now just handle fixed values. + if (isHorizontal()) { + int minW = child->minPrefWidth(); + int w = child->overrideWidth() - (child->borderLeft() + child->borderRight() + child->paddingLeft() + child->paddingRight()); + if (child->style()->minWidth().isFixed()) + minW = child->style()->minWidth().value(); + else if (child->style()->minWidth().type() == Intrinsic) + minW = child->maxPrefWidth(); + else if (child->style()->minWidth().type() == MinIntrinsic) + minW = child->minPrefWidth(); + + int allowedShrinkage = min(0, minW - w); + return allowedShrinkage; + } else { + if (child->style()->minHeight().isFixed()) { + int minH = child->style()->minHeight().value(); + int h = child->overrideHeight() - (child->borderLeft() + child->borderRight() + child->paddingLeft() + child->paddingRight()); + int allowedShrinkage = min(0, minH - h); + return allowedShrinkage; + } + } + + return 0; +} + +const char *RenderFlexibleBox::renderName() const +{ + if (isFloating()) + return "RenderFlexibleBox (floating)"; + if (isPositioned()) + return "RenderFlexibleBox (positioned)"; + if (isRelPositioned()) + return "RenderFlexibleBox (relative positioned)"; + return "RenderFlexibleBox"; +} + +} // namespace WebCore |