blob: a55be0e8030d43d97670db6de63345e3b5b5852b [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "sky/engine/config.h"
#include "sky/engine/core/rendering/RenderBlockFlow.h"
#include "sky/engine/core/frame/FrameView.h"
#include "sky/engine/core/frame/LocalFrame.h"
#include "sky/engine/core/frame/Settings.h"
#include "sky/engine/core/rendering/BidiRun.h"
#include "sky/engine/core/rendering/HitTestLocation.h"
#include "sky/engine/core/rendering/RenderLayer.h"
#include "sky/engine/core/rendering/RenderText.h"
#include "sky/engine/core/rendering/RenderView.h"
#include "sky/engine/core/rendering/line/LineWidth.h"
#include "sky/engine/platform/text/BidiTextRun.h"
namespace blink {
RenderBlockFlow::RenderBlockFlow(ContainerNode* node)
: RenderBlock(node)
{
}
RenderBlockFlow::~RenderBlockFlow()
{
}
RenderBlockFlow* RenderBlockFlow::createAnonymous(Document* document)
{
RenderBlockFlow* renderer = new RenderBlockFlow(0);
renderer->setDocumentForAnonymous(document);
return renderer;
}
bool RenderBlockFlow::updateLogicalWidthAndColumnWidth()
{
return RenderBlock::updateLogicalWidthAndColumnWidth();
}
void RenderBlockFlow::layout()
{
ASSERT(needsLayout());
ASSERT(isInlineBlock() || !isInline());
if (simplifiedLayout())
return;
SubtreeLayoutScope layoutScope(*this);
layoutBlockFlow(layoutScope);
updateLayerTransformAfterLayout();
clearNeedsLayout();
}
inline void RenderBlockFlow::layoutBlockFlow(SubtreeLayoutScope& layoutScope)
{
LayoutUnit oldLeft = logicalLeft();
bool logicalWidthChanged = updateLogicalWidthAndColumnWidth();
bool relayoutChildren = logicalWidthChanged;
LayoutState state(*this, locationOffset(), 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 || isDocumentElement(), 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);
}
void RenderBlockFlow::determineLogicalLeftPositionForChild(RenderBox* child)
{
LayoutUnit startPosition = borderStart() + paddingStart();
LayoutUnit totalAvailableLogicalWidth = borderAndPaddingLogicalWidth() + availableLogicalWidth();
LayoutUnit childMarginStart = marginStartForChild(child);
LayoutUnit newPosition = startPosition + childMarginStart;
// If the child has an offset from the content edge to avoid floats then use that, otherwise let any negative
// margin pull it back over the content edge or any positive margin push it out.
if (child->style()->marginStartUsing(style()).isAuto())
newPosition = std::max(newPosition, childMarginStart);
child->setX(style()->isLeftToRightDirection() ? newPosition : totalAvailableLogicalWidth - newPosition - logicalWidthForChild(child));
}
void RenderBlockFlow::layoutBlockChild(RenderBox* child)
{
child->computeAndSetBlockDirectionMargins(this);
LayoutUnit marginBefore = marginBeforeForChild(child);
child->setY(logicalHeight() + marginBefore);
child->layoutIfNeeded();
determineLogicalLeftPositionForChild(child);
setLogicalHeight(logicalHeight() + marginBefore + logicalHeightForChild(child) + marginAfterForChild(child));
}
void RenderBlockFlow::layoutChildren(bool relayoutChildren, SubtreeLayoutScope& layoutScope, LayoutUnit beforeEdge, LayoutUnit afterEdge)
{
dirtyForLayoutFromPercentageHeightDescendants(layoutScope);
RenderBox* next = firstChildBox();
RenderBox* lastNormalFlowChild = 0;
while (next) {
RenderBox* child = next;
next = child->nextSiblingBox();
updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, child);
if (child->isOutOfFlowPositioned()) {
child->containingBlock()->insertPositionedObject(child);
adjustPositionedBlock(child);
continue;
}
// Lay out the child.
layoutBlockChild(child);
lastNormalFlowChild = child;
}
// 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.
setLogicalHeight(std::max(logicalHeight() + afterEdge, beforeEdge + afterEdge));
}
void RenderBlockFlow::adjustPositionedBlock(RenderBox* child)
{
bool hasStaticBlockPosition = child->style()->hasStaticBlockPosition();
LayoutUnit logicalTop = logicalHeight();
updateStaticInlinePositionForChild(child);
RenderLayer* childLayer = child->layer();
if (childLayer->staticBlockPosition() != logicalTop) {
childLayer->setStaticBlockPosition(logicalTop);
if (hasStaticBlockPosition)
child->setChildNeedsLayout(MarkOnlyThis);
}
}
RootInlineBox* RenderBlockFlow::createAndAppendRootInlineBox()
{
RootInlineBox* rootBox = createRootInlineBox();
m_lineBoxes.appendLineBox(rootBox);
return rootBox;
}
void RenderBlockFlow::deleteLineBoxTree()
{
m_lineBoxes.deleteLineBoxTree();
}
void RenderBlockFlow::updateStaticInlinePositionForChild(RenderBox* child)
{
if (child->style()->isOriginalDisplayInlineType())
setStaticInlinePositionForChild(child, startAlignedOffsetForLine(false));
else
setStaticInlinePositionForChild(child, startOffsetForContent());
}
void RenderBlockFlow::setStaticInlinePositionForChild(RenderBox* child, LayoutUnit inlinePosition)
{
child->layer()->setStaticInlinePosition(inlinePosition);
}
void RenderBlockFlow::addChild(RenderObject* newChild, RenderObject* beforeChild)
{
RenderBlock::addChild(newChild, beforeChild);
}
LayoutUnit RenderBlockFlow::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 RenderBlockFlow::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* RenderBlockFlow::createRootInlineBox()
{
return new RootInlineBox(*this);
}
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 RenderBlockFlow::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;
}
}
LayoutUnit RenderBlockFlow::startAlignedOffsetForLine(bool firstLine)
{
ETextAlign textAlign = style()->textAlign();
if (textAlign == TASTART) // FIXME: Handle TAEND here
return startOffsetForLine(firstLine);
// updateLogicalWidthForAlignment() handles the direction of the block so no need to consider it here
float totalLogicalWidth = 0;
float logicalLeft = logicalLeftOffsetForLine(false).toFloat();
float availableLogicalWidth = logicalRightOffsetForLine(false) - logicalLeft;
updateLogicalWidthForAlignment(textAlign, 0, 0, logicalLeft, totalLogicalWidth, availableLogicalWidth, 0);
if (!style()->isLeftToRightDirection())
return logicalWidth() - logicalLeft;
return logicalLeft;
}
} // namespace blink