/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. * All rights reserved. * * 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 "flutter/sky/engine/core/rendering/RenderInline.h" #include "flutter/sky/engine/core/rendering/HitTestResult.h" #include "flutter/sky/engine/core/rendering/InlineTextBox.h" #include "flutter/sky/engine/core/rendering/RenderBlock.h" #include "flutter/sky/engine/core/rendering/RenderGeometryMap.h" #include "flutter/sky/engine/core/rendering/RenderLayer.h" #include "flutter/sky/engine/core/rendering/RenderView.h" #include "flutter/sky/engine/core/rendering/style/StyleInheritedData.h" #include "flutter/sky/engine/platform/geometry/FloatQuad.h" #include "flutter/sky/engine/platform/geometry/Region.h" #include "flutter/sky/engine/platform/geometry/TransformState.h" #include "flutter/sky/engine/platform/graphics/GraphicsContext.h" namespace blink { struct SameSizeAsRenderInline : public RenderBoxModelObject { virtual ~SameSizeAsRenderInline() {} RenderObjectChildList m_children; RenderLineBoxList m_lineBoxes; }; COMPILE_ASSERT(sizeof(RenderInline) == sizeof(SameSizeAsRenderInline), RenderInline_should_stay_small); RenderInline::RenderInline() {} void RenderInline::willBeDestroyed() { // Make sure to destroy anonymous children first while they are still // connected to the rest of the tree, so that they will properly dirty line // boxes that they are removed from. Effects that do :before/:after only on // hover could crash otherwise. children()->destroyLeftoverChildren(); if (!documentBeingDestroyed()) { if (firstLineBox()) { // If line boxes are contained inside a root, that means we're an inline. // In that case, we need to remove all the line boxes so that the parent // lines aren't pointing to deleted children. If the first line box does // not have a parent that means they are either already disconnected or // root lines that can just be destroyed without disconnecting. if (firstLineBox()->parent()) { for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) box->remove(); } } else if (parent()) parent()->dirtyLinesFromChangedChild(this); } m_lineBoxes.deleteLineBoxes(); RenderBoxModelObject::willBeDestroyed(); } void RenderInline::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderBoxModelObject::styleDidChange(diff, oldStyle); if (!alwaysCreateLineBoxes()) { RenderStyle* newStyle = style(); bool alwaysCreateLineBoxesNew = hasBoxDecorationBackground() || newStyle->hasPadding() || newStyle->hasMargin() || newStyle->hasOutline(); if (oldStyle && alwaysCreateLineBoxesNew) { dirtyLineBoxes(false); setNeedsLayout(); } setAlwaysCreateLineBoxes(alwaysCreateLineBoxesNew); } } void RenderInline::updateAlwaysCreateLineBoxes(bool fullLayout) { // Once we have been tainted once, just assume it will happen again. This way // effects like hover highlighting that change the background color will only // cause a layout on the first rollover. if (alwaysCreateLineBoxes()) return; RenderStyle* parentStyle = parent()->style(); RenderInline* parentRenderInline = parent()->isRenderInline() ? toRenderInline(parent()) : 0; bool alwaysCreateLineBoxesNew = (parentRenderInline && parentRenderInline->alwaysCreateLineBoxes()) || (parentRenderInline && parentStyle->verticalAlign() != BASELINE) || style()->verticalAlign() != BASELINE || style()->textEmphasisMark() != TextEmphasisMarkNone || !parentStyle->font().fontMetrics().hasIdenticalAscentDescentAndLineGap( style()->font().fontMetrics()) || parentStyle->lineHeight() != style()->lineHeight(); if (alwaysCreateLineBoxesNew) { if (!fullLayout) dirtyLineBoxes(false); setAlwaysCreateLineBoxes(); } } LayoutRect RenderInline::localCaretRect(InlineBox* inlineBox, int, LayoutUnit* extraWidthToEndOfLine) { if (firstChild()) { // This condition is possible if the RenderInline is at an editing boundary, // i.e. the VisiblePosition is: // | // // FIXME: need to figure out how to make this return a valid rect, note that // there are no line boxes created in the above case. return LayoutRect(); } ASSERT_UNUSED(inlineBox, !inlineBox); if (extraWidthToEndOfLine) *extraWidthToEndOfLine = 0; LayoutRect caretRect = localCaretRectForEmptyElement(borderAndPaddingWidth(), 0); if (InlineBox* firstBox = firstLineBox()) caretRect.moveBy(roundedLayoutPoint(firstBox->topLeft())); return caretRect; } void RenderInline::addChild(RenderObject* newChild, RenderObject* beforeChild) { RenderBoxModelObject::addChild(newChild, beforeChild); newChild->setNeedsLayoutAndPrefWidthsRecalc(); } void RenderInline::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, Vector& layers) { m_lineBoxes.paint(this, paintInfo, paintOffset, layers); } template void RenderInline::generateLineBoxRects(GeneratorContext& yield) const { if (!alwaysCreateLineBoxes()) generateCulledLineBoxRects(yield, this); else if (InlineFlowBox* curr = firstLineBox()) { for (; curr; curr = curr->nextLineBox()) yield(FloatRect(curr->topLeft(), curr->size())); } else yield(FloatRect()); } template void RenderInline::generateCulledLineBoxRects( GeneratorContext& yield, const RenderInline* container) const { if (!culledInlineFirstLineBox()) { yield(FloatRect()); return; } for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { if (curr->isFloatingOrOutOfFlowPositioned()) continue; // We want to get the margin box in the inline direction, and then use our // font ascent/descent in the block direction (aligned to the root box's // baseline). if (curr->isBox()) { RenderBox* currBox = toRenderBox(curr); if (currBox->inlineBoxWrapper()) { RootInlineBox& rootBox = currBox->inlineBoxWrapper()->root(); int logicalTop = rootBox.logicalTop() + (rootBox.renderer() .style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .ascent() - container->style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .ascent()); int logicalHeight = container->style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .height(); yield(FloatRect( currBox->inlineBoxWrapper()->x() - currBox->marginLeft(), logicalTop, (currBox->width() + currBox->marginWidth()).toFloat(), logicalHeight)); } } else if (curr->isRenderInline()) { // If the child doesn't need line boxes either, then we can recur. RenderInline* currInline = toRenderInline(curr); if (!currInline->alwaysCreateLineBoxes()) currInline->generateCulledLineBoxRects(yield, container); else { for (InlineFlowBox* childLine = currInline->firstLineBox(); childLine; childLine = childLine->nextLineBox()) { RootInlineBox& rootBox = childLine->root(); int logicalTop = rootBox.logicalTop() + (rootBox.renderer() .style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .ascent() - container->style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .ascent()); int logicalHeight = container->style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .height(); yield(FloatRect( childLine->x() - childLine->marginLogicalLeft(), logicalTop, childLine->logicalWidth() + childLine->marginLogicalLeft() + childLine->marginLogicalRight(), logicalHeight)); } } } else if (curr->isText()) { RenderText* currText = toRenderText(curr); for (InlineTextBox* childText = currText->firstTextBox(); childText; childText = childText->nextTextBox()) { RootInlineBox& rootBox = childText->root(); int logicalTop = rootBox.logicalTop() + (rootBox.renderer() .style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .ascent() - container->style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .ascent()); int logicalHeight = container->style(rootBox.isFirstLineStyle()) ->font() .fontMetrics() .height(); yield(FloatRect(childText->x(), logicalTop, childText->logicalWidth(), logicalHeight)); } } } } namespace { class AbsoluteRectsGeneratorContext { public: AbsoluteRectsGeneratorContext(Vector& rects, const LayoutPoint& accumulatedOffset) : m_rects(rects), m_accumulatedOffset(accumulatedOffset) {} void operator()(const FloatRect& rect) { IntRect intRect = enclosingIntRect(rect); intRect.move(m_accumulatedOffset.x(), m_accumulatedOffset.y()); m_rects.append(intRect); } private: Vector& m_rects; const LayoutPoint& m_accumulatedOffset; }; class AbsoluteQuadsGeneratorContext { public: AbsoluteQuadsGeneratorContext(const RenderInline* renderer, Vector& quads) : m_quads(quads), m_geometryMap() { m_geometryMap.pushMappingsToAncestor(renderer, 0); } void operator()(const FloatRect& rect) { m_quads.append(m_geometryMap.absoluteRect(rect)); } private: Vector& m_quads; RenderGeometryMap m_geometryMap; }; } // unnamed namespace void RenderInline::absoluteQuads(Vector& quads) const { AbsoluteQuadsGeneratorContext context(this, quads); generateLineBoxRects(context); } LayoutUnit RenderInline::offsetLeft() const { LayoutPoint topLeft; if (InlineBox* firstBox = firstLineBoxIncludingCulling()) topLeft = flooredLayoutPoint(firstBox->topLeft()); return adjustedPositionRelativeToOffsetParent(topLeft).x(); } LayoutUnit RenderInline::offsetTop() const { LayoutPoint topLeft; if (InlineBox* firstBox = firstLineBoxIncludingCulling()) topLeft = flooredLayoutPoint(firstBox->topLeft()); return adjustedPositionRelativeToOffsetParent(topLeft).y(); } static LayoutUnit computeMargin(const RenderInline* renderer, const Length& margin) { if (margin.isAuto()) return 0; if (margin.isFixed()) return margin.value(); if (margin.isPercent()) return minimumValueForLength( margin, std::max( 0, renderer->containingBlock()->availableLogicalWidth())); return 0; } LayoutUnit RenderInline::marginLeft() const { return computeMargin(this, style()->marginLeft()); } LayoutUnit RenderInline::marginRight() const { return computeMargin(this, style()->marginRight()); } LayoutUnit RenderInline::marginTop() const { return computeMargin(this, style()->marginTop()); } LayoutUnit RenderInline::marginBottom() const { return computeMargin(this, style()->marginBottom()); } LayoutUnit RenderInline::marginStart(const RenderStyle* otherStyle) const { return computeMargin( this, style()->marginStartUsing(otherStyle ? otherStyle : style())); } LayoutUnit RenderInline::marginEnd(const RenderStyle* otherStyle) const { return computeMargin( this, style()->marginEndUsing(otherStyle ? otherStyle : style())); } LayoutUnit RenderInline::marginBefore(const RenderStyle* otherStyle) const { return computeMargin( this, style()->marginBeforeUsing(otherStyle ? otherStyle : style())); } LayoutUnit RenderInline::marginAfter(const RenderStyle* otherStyle) const { return computeMargin( this, style()->marginAfterUsing(otherStyle ? otherStyle : style())); } const char* RenderInline::renderName() const { return "RenderInline"; } bool RenderInline::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) { return m_lineBoxes.hitTest(this, request, result, locationInContainer, accumulatedOffset); } namespace { class HitTestCulledInlinesGeneratorContext { public: HitTestCulledInlinesGeneratorContext(Region& region, const HitTestLocation& location) : m_intersected(false), m_region(region), m_location(location) {} void operator()(const FloatRect& rect) { m_intersected = m_intersected || m_location.intersects(rect); m_region.unite(enclosingIntRect(rect)); } bool intersected() const { return m_intersected; } private: bool m_intersected; Region& m_region; const HitTestLocation& m_location; }; } // unnamed namespace bool RenderInline::hitTestCulledInline( const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) { ASSERT(result.isRectBasedTest() && !alwaysCreateLineBoxes()); if (!visibleToHitTestRequest(request)) return false; HitTestLocation tmpLocation(locationInContainer, -toLayoutSize(accumulatedOffset)); Region regionResult; HitTestCulledInlinesGeneratorContext context(regionResult, tmpLocation); generateCulledLineBoxRects(context, this); if (context.intersected()) { updateHitTestResult(result, tmpLocation.point()); return regionResult.contains(tmpLocation.boundingBox()); } return false; } PositionWithAffinity RenderInline::positionForPoint(const LayoutPoint& point) { // FIXME(sky): Now that we don't have continuations, can this whole function // just be the following? return containingBlock()->positionForPoint(point); // FIXME: Does not deal with relative positioned inlines (should it?) RenderBlock* cb = containingBlock(); if (firstLineBox()) { // This inline actually has a line box. We must have clicked in the // border/padding of one of these boxes. We should try to find a result by // asking our containing block. return cb->positionForPoint(point); } // Translate the coords from the pre-anonymous block to the post-anonymous // block. return RenderBoxModelObject::positionForPoint(point); } namespace { class LinesBoundingBoxGeneratorContext { public: LinesBoundingBoxGeneratorContext(FloatRect& rect) : m_rect(rect) {} void operator()(const FloatRect& rect) { m_rect.uniteIfNonZero(rect); } private: FloatRect& m_rect; }; } // unnamed namespace IntRect RenderInline::linesBoundingBox() const { if (!alwaysCreateLineBoxes()) { ASSERT(!firstLineBox()); FloatRect floatResult; LinesBoundingBoxGeneratorContext context(floatResult); generateCulledLineBoxRects(context, this); return enclosingIntRect(floatResult); } IntRect result; // See , for an unknown reason the linked list here is // sometimes inconsistent, first is non-zero and last is zero. We have been // unable to reproduce this at all (and consequently unable to figure ot why // this is happening). The assert will hopefully catch the problem in debug // builds and help us someday figure out why. We also put in a redundant // check of lastLineBox() to avoid the crash for now. ASSERT(!firstLineBox() == !lastLineBox()); // Either both are null or both exist. if (firstLineBox() && lastLineBox()) { // Return the width of the minimal left side and the maximal right side. float logicalLeftSide = 0; float logicalRightSide = 0; for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { if (curr == firstLineBox() || curr->logicalLeft() < logicalLeftSide) logicalLeftSide = curr->logicalLeft(); if (curr == firstLineBox() || curr->logicalRight() > logicalRightSide) logicalRightSide = curr->logicalRight(); } float x = logicalLeftSide; float y = firstLineBox()->y(); float width = logicalRightSide - logicalLeftSide; float height = lastLineBox()->logicalBottom() - y; result = enclosingIntRect(FloatRect(x, y, width, height)); } return result; } InlineBox* RenderInline::culledInlineFirstLineBox() const { for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { if (curr->isFloatingOrOutOfFlowPositioned()) continue; // We want to get the margin box in the inline direction, and then use our // font ascent/descent in the block direction (aligned to the root box's // baseline). if (curr->isBox()) return toRenderBox(curr)->inlineBoxWrapper(); if (curr->isRenderInline()) { RenderInline* currInline = toRenderInline(curr); InlineBox* result = currInline->firstLineBoxIncludingCulling(); if (result) return result; } else if (curr->isText()) { RenderText* currText = toRenderText(curr); if (currText->firstTextBox()) return currText->firstTextBox(); } } return 0; } InlineBox* RenderInline::culledInlineLastLineBox() const { for (RenderObject* curr = lastChild(); curr; curr = curr->previousSibling()) { if (curr->isFloatingOrOutOfFlowPositioned()) continue; // We want to get the margin box in the inline direction, and then use our // font ascent/descent in the block direction (aligned to the root box's // baseline). if (curr->isBox()) return toRenderBox(curr)->inlineBoxWrapper(); if (curr->isRenderInline()) { RenderInline* currInline = toRenderInline(curr); InlineBox* result = currInline->lastLineBoxIncludingCulling(); if (result) return result; } else if (curr->isText()) { RenderText* currText = toRenderText(curr); if (currText->lastTextBox()) return currText->lastTextBox(); } } return 0; } LayoutRect RenderInline::culledInlineVisualOverflowBoundingBox() const { FloatRect floatResult; LinesBoundingBoxGeneratorContext context(floatResult); generateCulledLineBoxRects(context, this); LayoutRect result(enclosingLayoutRect(floatResult)); for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { if (curr->isFloatingOrOutOfFlowPositioned()) continue; // For overflow we just have to propagate by hand and recompute it all. if (curr->isBox()) { RenderBox* currBox = toRenderBox(curr); if (!currBox->hasSelfPaintingLayer() && currBox->inlineBoxWrapper()) { LayoutRect logicalRect = currBox->visualOverflowRect(); logicalRect.moveBy(currBox->location()); result.uniteIfNonZero(logicalRect); } } else if (curr->isRenderInline()) { // If the child doesn't need line boxes either, then we can recur. RenderInline* currInline = toRenderInline(curr); if (!currInline->alwaysCreateLineBoxes()) result.uniteIfNonZero( currInline->culledInlineVisualOverflowBoundingBox()); else result.uniteIfNonZero(currInline->linesVisualOverflowBoundingBox()); } else if (curr->isText()) { // FIXME; Overflow from text boxes is lost. We will need to cache this // information in InlineTextBoxes. RenderText* currText = toRenderText(curr); result.uniteIfNonZero(currText->linesVisualOverflowBoundingBox()); } } return result; } LayoutRect RenderInline::linesVisualOverflowBoundingBox() const { if (!alwaysCreateLineBoxes()) return culledInlineVisualOverflowBoundingBox(); if (!firstLineBox() || !lastLineBox()) return LayoutRect(); // Return the width of the minimal left side and the maximal right side. LayoutUnit logicalLeftSide = LayoutUnit::max(); LayoutUnit logicalRightSide = LayoutUnit::min(); for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { logicalLeftSide = std::min(logicalLeftSide, curr->logicalLeftVisualOverflow()); logicalRightSide = std::max(logicalRightSide, curr->logicalRightVisualOverflow()); } RootInlineBox& firstRootBox = firstLineBox()->root(); RootInlineBox& lastRootBox = lastLineBox()->root(); LayoutUnit logicalTop = firstLineBox()->logicalTopVisualOverflow(firstRootBox.lineTop()); LayoutUnit logicalWidth = logicalRightSide - logicalLeftSide; LayoutUnit logicalHeight = lastLineBox()->logicalBottomVisualOverflow(lastRootBox.lineBottom()) - logicalTop; LayoutRect rect(logicalLeftSide, logicalTop, logicalWidth, logicalHeight); return rect; } void RenderInline::mapLocalToContainer( const RenderBox* paintInvalidationContainer, TransformState& transformState, MapCoordinatesFlags mode) const { bool containerSkipped; RenderObject* o = container(paintInvalidationContainer, &containerSkipped); if (!o) return; if (mode & ApplyContainerFlip && o->isBox()) { mode &= ~ApplyContainerFlip; } LayoutSize containerOffset = offsetFromContainer(o, roundedLayoutPoint(transformState.mappedPoint())); bool preserve3D = mode & UseTransforms && (o->style()->preserves3D() || style()->preserves3D()); if (mode & UseTransforms && shouldUseTransformFromContainer(o)) { TransformationMatrix t; getTransformFromContainer(o, containerOffset, t); transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); } else transformState.move(containerOffset.width(), containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); if (containerSkipped) { // There can't be a transform between paintInvalidationContainer and o, // because transforms create containers, so it should be safe to just // subtract the delta between the paintInvalidationContainer and o. LayoutSize containerOffset = paintInvalidationContainer->offsetFromAncestorContainer(o); transformState.move(-containerOffset.width(), -containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); return; } o->mapLocalToContainer(paintInvalidationContainer, transformState, mode); } void RenderInline::updateHitTestResult(HitTestResult& result, const LayoutPoint& point) {} void RenderInline::dirtyLineBoxes(bool fullLayout) { if (fullLayout) { m_lineBoxes.deleteLineBoxes(); return; } if (!alwaysCreateLineBoxes()) { // We have to grovel into our children in order to dirty the appropriate // lines. for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { if (curr->isFloatingOrOutOfFlowPositioned()) continue; if (curr->isBox() && !curr->needsLayout()) { RenderBox* currBox = toRenderBox(curr); if (currBox->inlineBoxWrapper()) currBox->inlineBoxWrapper()->root().markDirty(); } else if (!curr->selfNeedsLayout()) { if (curr->isRenderInline()) { RenderInline* currInline = toRenderInline(curr); for (InlineFlowBox* childLine = currInline->firstLineBox(); childLine; childLine = childLine->nextLineBox()) childLine->root().markDirty(); } else if (curr->isText()) { RenderText* currText = toRenderText(curr); for (InlineTextBox* childText = currText->firstTextBox(); childText; childText = childText->nextTextBox()) childText->root().markDirty(); } } } } else m_lineBoxes.dirtyLineBoxes(); } void RenderInline::deleteLineBoxTree() { m_lineBoxes.deleteLineBoxTree(); } InlineFlowBox* RenderInline::createInlineFlowBox() { return new InlineFlowBox(*this); } InlineFlowBox* RenderInline::createAndAppendInlineFlowBox() { setAlwaysCreateLineBoxes(); InlineFlowBox* flowBox = createInlineFlowBox(); m_lineBoxes.appendLineBox(flowBox); return flowBox; } LayoutUnit RenderInline::lineHeight( bool firstLine, LineDirectionMode /*direction*/, LinePositionMode /*linePositionMode*/) const { return style()->computedLineHeight(); } int RenderInline::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const { ASSERT(linePositionMode == PositionOnContainingLine); const FontMetrics& fontMetrics = style(firstLine)->fontMetrics(); return fontMetrics.ascent(baselineType) + (lineHeight(firstLine, direction, linePositionMode) - fontMetrics.height()) / 2; } namespace { class AbsoluteRectsIgnoringEmptyRectsGeneratorContext : public AbsoluteRectsGeneratorContext { public: AbsoluteRectsIgnoringEmptyRectsGeneratorContext( Vector& rects, const LayoutPoint& accumulatedOffset) : AbsoluteRectsGeneratorContext(rects, accumulatedOffset) {} void operator()(const FloatRect& rect) { if (!rect.isEmpty()) AbsoluteRectsGeneratorContext::operator()(rect); } }; } // unnamed namespace void RenderInline::addFocusRingRects(Vector& rects, const LayoutPoint& additionalOffset, const RenderBox* paintContainer) const { AbsoluteRectsIgnoringEmptyRectsGeneratorContext context(rects, additionalOffset); generateLineBoxRects(context); addChildFocusRingRects(rects, additionalOffset, paintContainer); } } // namespace blink