/* * 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(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); return; } if (trailingSpaceRun) trailingSpaceRun->m_box->setLogicalWidth(0); else if (totalLogicalWidth > availableLogicalWidth) logicalLeft -= (totalLogicalWidth - availableLogicalWidth); } static void updateLogicalWidthForRightAlignedBlock(bool isLeftToRightDirection, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float availableLogicalWidth) { // Wide lines spill out of the block based off direction. // So even if text-align is right, if direction is LTR, wide lines should overflow out of the right // side of the block. if (isLeftToRightDirection) { if (trailingSpaceRun) { totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); trailingSpaceRun->m_box->setLogicalWidth(0); } if (totalLogicalWidth < availableLogicalWidth) logicalLeft += availableLogicalWidth - totalLogicalWidth; return; } if (totalLogicalWidth > availableLogicalWidth && trailingSpaceRun) { trailingSpaceRun->m_box->setLogicalWidth(std::max(0, trailingSpaceRun->m_box->logicalWidth() - totalLogicalWidth + availableLogicalWidth)); totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); } else logicalLeft += availableLogicalWidth - totalLogicalWidth; } static void updateLogicalWidthForCenterAlignedBlock(bool isLeftToRightDirection, BidiRun* trailingSpaceRun, float& logicalLeft, float& totalLogicalWidth, float availableLogicalWidth) { float trailingSpaceWidth = 0; if (trailingSpaceRun) { totalLogicalWidth -= trailingSpaceRun->m_box->logicalWidth(); trailingSpaceWidth = std::min(trailingSpaceRun->m_box->logicalWidth(), (availableLogicalWidth - totalLogicalWidth + 1) / 2); trailingSpaceRun->m_box->setLogicalWidth(std::max(0, trailingSpaceWidth)); } if (isLeftToRightDirection) logicalLeft += std::max((availableLogicalWidth - totalLogicalWidth) / 2, 0); else logicalLeft += totalLogicalWidth > availableLogicalWidth ? (availableLogicalWidth - totalLogicalWidth) : (availableLogicalWidth - totalLogicalWidth) / 2 - trailingSpaceWidth; } void 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