/* * Copyright (C) 2006 Apple Computer, Inc. * Copyright (C) 2006 Alexander Kellett * Copyright (C) 2006 Oliver Hunt * Copyright (C) 2007 Nikolas Zimmermann * Copyright (C) 2008 Rob Buis * Copyright (C) 2009 Dirk Schulze * Copyright (C) Research In Motion Limited 2010-2012. All rights reserved. * Copyright (C) 2012 Google 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" #if ENABLE(SVG) #include "RenderSVGText.h" #include "FloatConversion.h" #include "FloatQuad.h" #include "FontCache.h" #include "GraphicsContext.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "LayoutRepainter.h" #include "PointerEventsHitRules.h" #include "RenderSVGInlineText.h" #include "RenderSVGResource.h" #include "RenderSVGRoot.h" #include "SVGLengthList.h" #include "SVGRenderSupport.h" #include "SVGResourcesCache.h" #include "SVGRootInlineBox.h" #include "SVGTextElement.h" #include "SVGTextLayoutAttributesBuilder.h" #include "SVGTextRunRenderingContext.h" #include "SVGTransformList.h" #include "SVGURIReference.h" #include "SimpleFontData.h" #include "TransformState.h" #include "VisiblePosition.h" #include namespace WebCore { RenderSVGText::RenderSVGText(SVGTextElement* node) : RenderSVGBlock(node) , m_needsReordering(false) , m_needsPositioningValuesUpdate(false) , m_needsTransformUpdate(true) , m_needsTextMetricsUpdate(false) { } RenderSVGText::~RenderSVGText() { ASSERT(m_layoutAttributes.isEmpty()); } bool RenderSVGText::isChildAllowed(RenderObject* child, RenderStyle*) const { return child->isInline(); } RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(RenderObject* start) { ASSERT(start); while (start && !start->isSVGText()) start = start->parent(); if (!start || !start->isSVGText()) return 0; return toRenderSVGText(start); } const RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(const RenderObject* start) { ASSERT(start); while (start && !start->isSVGText()) start = start->parent(); if (!start || !start->isSVGText()) return 0; return toRenderSVGText(start); } LayoutRect RenderSVGText::clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const { return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); } void RenderSVGText::computeRectForRepaint(const RenderLayerModelObject* repaintContainer, LayoutRect& rect, bool fixed) const { FloatRect repaintRect = rect; computeFloatRectForRepaint(repaintContainer, repaintRect, fixed); rect = enclosingLayoutRect(repaintRect); } void RenderSVGText::computeFloatRectForRepaint(const RenderLayerModelObject* repaintContainer, FloatRect& repaintRect, bool fixed) const { SVGRenderSupport::computeFloatRectForRepaint(this, repaintContainer, repaintRect, fixed); } void RenderSVGText::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags, bool* wasFixed) const { SVGRenderSupport::mapLocalToContainer(this, repaintContainer, transformState, wasFixed); } const RenderObject* RenderSVGText::pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) const { return SVGRenderSupport::pushMappingToContainer(this, ancestorToStopAt, geometryMap); } static inline void collectLayoutAttributes(RenderObject* text, Vector& attributes) { for (RenderObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { if (descendant->isSVGInlineText()) attributes.append(toRenderSVGInlineText(descendant)->layoutAttributes()); } } static inline bool findPreviousAndNextAttributes(RenderObject* start, RenderSVGInlineText* locateElement, bool& stopAfterNext, SVGTextLayoutAttributes*& previous, SVGTextLayoutAttributes*& next) { ASSERT(start); ASSERT(locateElement); // FIXME: Make this iterative. for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { if (child->isSVGInlineText()) { RenderSVGInlineText* text = toRenderSVGInlineText(child); if (locateElement != text) { if (stopAfterNext) { next = text->layoutAttributes(); return true; } previous = text->layoutAttributes(); continue; } stopAfterNext = true; continue; } if (!child->isSVGInline()) continue; if (findPreviousAndNextAttributes(child, locateElement, stopAfterNext, previous, next)) return true; } return false; } inline bool RenderSVGText::shouldHandleSubtreeMutations() const { if (beingDestroyed() || !everHadLayout()) { ASSERT(m_layoutAttributes.isEmpty()); ASSERT(!m_layoutAttributesBuilder.numberOfTextPositioningElements()); return false; } return true; } void RenderSVGText::subtreeChildWasAdded(RenderObject* child) { ASSERT(child); if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) return; // Always protect the cache before clearing text positioning elements when the cache will subsequently be rebuilt. FontCachePurgePreventer fontCachePurgePreventer; // The positioning elements cache doesn't include the new 'child' yet. Clear the // cache, as the next buildLayoutAttributesForTextRenderer() call rebuilds it. m_layoutAttributesBuilder.clearTextPositioningElements(); if (!child->isSVGInlineText() && !child->isSVGInline()) return; // Detect changes in layout attributes and only measure those text parts that have changed! Vector newLayoutAttributes; collectLayoutAttributes(this, newLayoutAttributes); if (newLayoutAttributes.isEmpty()) { ASSERT(m_layoutAttributes.isEmpty()); return; } // Compare m_layoutAttributes with newLayoutAttributes to figure out which attribute got added. size_t size = newLayoutAttributes.size(); SVGTextLayoutAttributes* attributes = 0; for (size_t i = 0; i < size; ++i) { attributes = newLayoutAttributes[i]; if (m_layoutAttributes.find(attributes) == notFound) { // Every time this is invoked, there's only a single new entry in the newLayoutAttributes list, compared to the old in m_layoutAttributes. bool stopAfterNext = false; SVGTextLayoutAttributes* previous = 0; SVGTextLayoutAttributes* next = 0; ASSERT_UNUSED(child, attributes->context() == child); findPreviousAndNextAttributes(this, attributes->context(), stopAfterNext, previous, next); if (previous) m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(previous->context()); m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(attributes->context()); if (next) m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(next->context()); break; } } #ifndef NDEBUG // Verify that m_layoutAttributes only differs by a maximum of one entry. for (size_t i = 0; i < size; ++i) ASSERT(m_layoutAttributes.find(newLayoutAttributes[i]) != notFound || newLayoutAttributes[i] == attributes); #endif m_layoutAttributes = newLayoutAttributes; } static inline void checkLayoutAttributesConsistency(RenderSVGText* text, Vector& expectedLayoutAttributes) { #ifndef NDEBUG Vector newLayoutAttributes; collectLayoutAttributes(text, newLayoutAttributes); ASSERT(newLayoutAttributes == expectedLayoutAttributes); #else UNUSED_PARAM(text); UNUSED_PARAM(expectedLayoutAttributes); #endif } void RenderSVGText::willBeDestroyed() { m_layoutAttributes.clear(); m_layoutAttributesBuilder.clearTextPositioningElements(); RenderSVGBlock::willBeDestroyed(); } void RenderSVGText::subtreeChildWillBeRemoved(RenderObject* child, Vector& affectedAttributes) { ASSERT(child); if (!shouldHandleSubtreeMutations()) return; checkLayoutAttributesConsistency(this, m_layoutAttributes); // The positioning elements cache depends on the size of each text renderer in the // subtree. If this changes, clear the cache. It's going to be rebuilt below. m_layoutAttributesBuilder.clearTextPositioningElements(); if (m_layoutAttributes.isEmpty() || !child->isSVGInlineText()) return; // This logic requires that the 'text' child is still inserted in the tree. RenderSVGInlineText* text = toRenderSVGInlineText(child); bool stopAfterNext = false; SVGTextLayoutAttributes* previous = 0; SVGTextLayoutAttributes* next = 0; if (!documentBeingDestroyed()) findPreviousAndNextAttributes(this, text, stopAfterNext, previous, next); if (previous) affectedAttributes.append(previous); if (next) affectedAttributes.append(next); size_t position = m_layoutAttributes.find(text->layoutAttributes()); ASSERT(position != notFound); m_layoutAttributes.remove(position); } void RenderSVGText::subtreeChildWasRemoved(const Vector& affectedAttributes) { if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) { ASSERT(affectedAttributes.isEmpty()); return; } // This is called immediately after subtreeChildWillBeDestroyed, once the RenderSVGInlineText::willBeDestroyed() method // passes on to the base class, which removes us from the render tree. At this point we can update the layout attributes. unsigned size = affectedAttributes.size(); for (unsigned i = 0; i < size; ++i) m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(affectedAttributes[i]->context()); } void RenderSVGText::subtreeStyleDidChange(RenderSVGInlineText* text) { ASSERT(text); if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) return; checkLayoutAttributesConsistency(this, m_layoutAttributes); // Only update the metrics cache, but not the text positioning element cache // nor the layout attributes cached in the leaf #text renderers. FontCachePurgePreventer fontCachePurgePreventer; for (RenderObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { if (descendant->isSVGInlineText()) m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(toRenderSVGInlineText(descendant)); } } void RenderSVGText::subtreeTextDidChange(RenderSVGInlineText* text) { ASSERT(text); ASSERT(!beingDestroyed()); if (!everHadLayout()) { ASSERT(m_layoutAttributes.isEmpty()); ASSERT(!m_layoutAttributesBuilder.numberOfTextPositioningElements()); return; } // Always protect the cache before clearing text positioning elements when the cache will subsequently be rebuilt. FontCachePurgePreventer fontCachePurgePreventer; // The positioning elements cache depends on the size of each text renderer in the // subtree. If this changes, clear the cache. It's going to be rebuilt below. m_layoutAttributesBuilder.clearTextPositioningElements(); checkLayoutAttributesConsistency(this, m_layoutAttributes); for (RenderObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { if (descendant->isSVGInlineText()) m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(toRenderSVGInlineText(descendant)); } } static inline void updateFontInAllDescendants(RenderObject* start, SVGTextLayoutAttributesBuilder* builder = 0) { for (RenderObject* descendant = start; descendant; descendant = descendant->nextInPreOrder(start)) { if (!descendant->isSVGInlineText()) continue; RenderSVGInlineText* text = toRenderSVGInlineText(descendant); text->updateScaledFont(); if (builder) builder->rebuildMetricsForTextRenderer(text); } } void RenderSVGText::layout() { StackStats::LayoutCheckPoint layoutCheckPoint; ASSERT(needsLayout()); LayoutRepainter repainter(*this, SVGRenderSupport::checkForSVGRepaintDuringLayout(this)); bool updateCachedBoundariesInParents = false; if (m_needsTransformUpdate) { SVGTextElement* text = static_cast(node()); m_localTransform = text->animatedLocalTransform(); m_needsTransformUpdate = false; updateCachedBoundariesInParents = true; } if (!everHadLayout()) { // When laying out initially, collect all layout attributes, build the character data map, // and propogate resulting SVGLayoutAttributes to all RenderSVGInlineText children in the subtree. ASSERT(m_layoutAttributes.isEmpty()); collectLayoutAttributes(this, m_layoutAttributes); updateFontInAllDescendants(this); m_layoutAttributesBuilder.buildLayoutAttributesForForSubtree(this); m_needsReordering = true; m_needsTextMetricsUpdate = false; m_needsPositioningValuesUpdate = false; updateCachedBoundariesInParents = true; } else if (m_needsPositioningValuesUpdate) { // When the x/y/dx/dy/rotate lists change, recompute the layout attributes, and eventually // update the on-screen font objects as well in all descendants. if (m_needsTextMetricsUpdate) { updateFontInAllDescendants(this); m_needsTextMetricsUpdate = false; } m_layoutAttributesBuilder.buildLayoutAttributesForForSubtree(this); m_needsReordering = true; m_needsPositioningValuesUpdate = false; updateCachedBoundariesInParents = true; } else if (m_needsTextMetricsUpdate || SVGRenderSupport::findTreeRootObject(this)->isLayoutSizeChanged()) { // If the root layout size changed (eg. window size changes) or the transform to the root // context has changed then recompute the on-screen font size. updateFontInAllDescendants(this, &m_layoutAttributesBuilder); ASSERT(!m_needsReordering); ASSERT(!m_needsPositioningValuesUpdate); m_needsTextMetricsUpdate = false; updateCachedBoundariesInParents = true; } checkLayoutAttributesConsistency(this, m_layoutAttributes); // Reduced version of RenderBlock::layoutBlock(), which only takes care of SVG text. // All if branches that could cause early exit in RenderBlocks layoutBlock() method are turned into assertions. ASSERT(!isInline()); ASSERT(!simplifiedLayout()); ASSERT(!scrollsOverflow()); ASSERT(!hasControlClip()); ASSERT(!hasColumns()); ASSERT(!positionedObjects()); ASSERT(!m_overflow); ASSERT(!isAnonymousBlock()); if (!firstChild()) setChildrenInline(true); // FIXME: We need to find a way to only layout the child boxes, if needed. FloatRect oldBoundaries = objectBoundingBox(); ASSERT(childrenInline()); forceLayoutInlineChildren(); if (m_needsReordering) m_needsReordering = false; if (!updateCachedBoundariesInParents) updateCachedBoundariesInParents = oldBoundaries != objectBoundingBox(); // Invalidate all resources of this client if our layout changed. if (everHadLayout() && selfNeedsLayout()) SVGResourcesCache::clientLayoutChanged(this); // If our bounds changed, notify the parents. if (updateCachedBoundariesInParents) RenderSVGBlock::setNeedsBoundariesUpdate(); repainter.repaintAfterLayout(); setNeedsLayout(false); } RootInlineBox* RenderSVGText::createRootInlineBox() { RootInlineBox* box = new (renderArena()) SVGRootInlineBox(this); box->setHasVirtualLogicalHeight(); return box; } bool RenderSVGText::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) { PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, style()->pointerEvents()); bool isVisible = (style()->visibility() == VISIBLE); if (isVisible || !hitRules.requireVisible) { if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill))) { FloatPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent); if (!SVGRenderSupport::pointInClippingArea(this, localPoint)) return false; HitTestLocation hitTestLocation(LayoutPoint(flooredIntPoint(localPoint))); return RenderBlock::nodeAtPoint(request, result, hitTestLocation, LayoutPoint(), hitTestAction); } } return false; } bool RenderSVGText::nodeAtPoint(const HitTestRequest&, HitTestResult&, const HitTestLocation&, const LayoutPoint&, HitTestAction) { ASSERT_NOT_REACHED(); return false; } VisiblePosition RenderSVGText::positionForPoint(const LayoutPoint& pointInContents) { RootInlineBox* rootBox = firstRootBox(); if (!rootBox) return createVisiblePosition(0, DOWNSTREAM); ASSERT_WITH_SECURITY_IMPLICATION(rootBox->isSVGRootInlineBox()); ASSERT(!rootBox->nextRootBox()); ASSERT(childrenInline()); InlineBox* closestBox = static_cast(rootBox)->closestLeafChildForPosition(pointInContents); if (!closestBox) return createVisiblePosition(0, DOWNSTREAM); return closestBox->renderer()->positionForPoint(LayoutPoint(pointInContents.x(), closestBox->y())); } void RenderSVGText::absoluteQuads(Vector& quads, bool* wasFixed) const { quads.append(localToAbsoluteQuad(strokeBoundingBox(), 0 /* mode */, wasFixed)); } void RenderSVGText::paint(PaintInfo& paintInfo, const LayoutPoint&) { if (paintInfo.context->paintingDisabled()) return; if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseSelection) return; PaintInfo blockInfo(paintInfo); GraphicsContextStateSaver stateSaver(*blockInfo.context); blockInfo.applyTransform(localToParentTransform()); RenderBlock::paint(blockInfo, LayoutPoint()); } FloatRect RenderSVGText::strokeBoundingBox() const { FloatRect strokeBoundaries = objectBoundingBox(); const SVGRenderStyle* svgStyle = style()->svgStyle(); if (!svgStyle->hasStroke()) return strokeBoundaries; ASSERT(node()); ASSERT(node()->isSVGElement()); SVGLengthContext lengthContext(toSVGElement(node())); strokeBoundaries.inflate(svgStyle->strokeWidth().value(lengthContext)); return strokeBoundaries; } FloatRect RenderSVGText::repaintRectInLocalCoordinates() const { FloatRect repaintRect = strokeBoundingBox(); SVGRenderSupport::intersectRepaintRectWithResources(this, repaintRect); if (const ShadowData* textShadow = style()->textShadow()) textShadow->adjustRectForShadow(repaintRect); return repaintRect; } void RenderSVGText::addChild(RenderObject* child, RenderObject* beforeChild) { RenderSVGBlock::addChild(child, beforeChild); SVGResourcesCache::clientWasAddedToTree(child, child->style()); subtreeChildWasAdded(child); } void RenderSVGText::removeChild(RenderObject* child) { SVGResourcesCache::clientWillBeRemovedFromTree(child); Vector affectedAttributes; FontCachePurgePreventer fontCachePurgePreventer; subtreeChildWillBeRemoved(child, affectedAttributes); RenderSVGBlock::removeChild(child); subtreeChildWasRemoved(affectedAttributes); } // Fix for . We should not render :first-line CSS Style // in a SVG text element context. RenderBlock* RenderSVGText::firstLineBlock() const { return 0; } // Fix for . We should not render :first-letter CSS Style // in a SVG text element context. void RenderSVGText::updateFirstLetter() { } } #endif // ENABLE(SVG)