| /* |
| * 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 |