diff -r 000000000000 -r 4f2f89ce4247 WebCore/rendering/SVGRenderSupport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/rendering/SVGRenderSupport.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2007, 2008 Rob Buis + * (C) 2007 Nikolas Zimmermann + * (C) 2007 Eric Seidel + * (C) 2009 Google, Inc. All rights reserved. + * (C) 2009 Dirk Schulze + * Copyright (C) Research In Motion Limited 2009-2010. 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 "config.h" + +#if ENABLE(SVG) +#include "SVGRenderSupport.h" + +#include "FrameView.h" +#include "ImageBuffer.h" +#include "NodeRenderStyle.h" +#include "RenderLayer.h" +#include "RenderPath.h" +#include "RenderSVGContainer.h" +#include "RenderSVGResource.h" +#include "RenderSVGResourceClipper.h" +#include "RenderSVGResourceFilter.h" +#include "RenderSVGResourceMarker.h" +#include "RenderSVGResourceMasker.h" +#include "RenderSVGRoot.h" +#include "SVGStyledElement.h" +#include "TransformState.h" +#include + +namespace WebCore { + +IntRect SVGRenderSupport::clippedOverflowRectForRepaint(RenderObject* object, RenderBoxModelObject* repaintContainer) +{ + // Return early for any cases where we don't actually paint + if (object->style()->visibility() != VISIBLE && !object->enclosingLayer()->hasVisibleContent()) + return IntRect(); + + // Pass our local paint rect to computeRectForRepaint() which will + // map to parent coords and recurse up the parent chain. + IntRect repaintRect = enclosingIntRect(object->repaintRectInLocalCoordinates()); + object->computeRectForRepaint(repaintContainer, repaintRect); + return repaintRect; +} + +void SVGRenderSupport::computeRectForRepaint(RenderObject* object, RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + object->style()->svgStyle()->inflateForShadow(repaintRect); + + // Translate to coords in our parent renderer, and then call computeRectForRepaint on our parent + repaintRect = object->localToParentTransform().mapRect(repaintRect); + object->parent()->computeRectForRepaint(repaintContainer, repaintRect, fixed); +} + +void SVGRenderSupport::mapLocalToContainer(const RenderObject* object, RenderBoxModelObject* repaintContainer, bool fixed , bool useTransforms, TransformState& transformState) +{ + ASSERT(!fixed); // We should have no fixed content in the SVG rendering tree. + ASSERT(useTransforms); // Mapping a point through SVG w/o respecting transforms is useless. + transformState.applyTransform(object->localToParentTransform()); + object->parent()->mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState); +} + +bool SVGRenderSupport::prepareToRenderSVGContent(RenderObject* object, PaintInfo& paintInfo) +{ + ASSERT(object); + SVGElement* svgElement = static_cast(object->node()); + ASSERT(svgElement && svgElement->document() && svgElement->isStyled()); + + SVGStyledElement* styledElement = static_cast(svgElement); + RenderStyle* style = object->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + FloatRect repaintRect; + + // Setup transparency layers before setting up SVG resources! + float opacity = style->opacity(); + if (opacity < 1) { + repaintRect = object->repaintRectInLocalCoordinates(); + paintInfo.context->clip(repaintRect); + paintInfo.context->beginTransparencyLayer(opacity); + } + + if (const ShadowData* shadow = svgStyle->shadow()) { + // Eventually compute repaint rect, if not done so far. + if (opacity >= 1) + repaintRect = object->repaintRectInLocalCoordinates(); + + paintInfo.context->clip(repaintRect); + paintInfo.context->setShadow(IntSize(shadow->x(), shadow->y()), shadow->blur(), shadow->color(), style->colorSpace()); + paintInfo.context->beginTransparencyLayer(1); + } + + Document* document = object->document(); + + if (svgStyle->hasMasker()) { + AtomicString maskerId(svgStyle->maskerResource()); + if (RenderSVGResourceMasker* masker = getRenderSVGResourceById(document, maskerId)) { + if (!masker->applyResource(object, style, paintInfo.context, ApplyToDefaultMode)) + return false; + } else + document->accessSVGExtensions()->addPendingResource(maskerId, styledElement); + } + + if (svgStyle->hasClipper()) { + AtomicString clipperId(svgStyle->clipperResource()); + if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById(document, clipperId)) + clipper->applyResource(object, style, paintInfo.context, ApplyToDefaultMode); + else + document->accessSVGExtensions()->addPendingResource(clipperId, styledElement); + } + +#if ENABLE(FILTERS) + if (svgStyle->hasFilter()) { + AtomicString filterId(svgStyle->filterResource()); + if (RenderSVGResourceFilter* filter = getRenderSVGResourceById(document, filterId)) { + if (!filter->applyResource(object, style, paintInfo.context, ApplyToDefaultMode)) + return false; + } else + document->accessSVGExtensions()->addPendingResource(filterId, styledElement); + } +#endif + + return true; +} + +void SVGRenderSupport::finishRenderSVGContent(RenderObject* object, PaintInfo& paintInfo, GraphicsContext* savedContext) +{ +#if !ENABLE(FILTERS) + UNUSED_PARAM(savedContext); +#endif + + ASSERT(object); + + const RenderStyle* style = object->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + +#if ENABLE(FILTERS) + if (svgStyle->hasFilter()) { + AtomicString filterId(svgStyle->filterResource()); + if (RenderSVGResourceFilter* filter = getRenderSVGResourceById(object->document(), filterId)) { + filter->postApplyResource(object, paintInfo.context, ApplyToDefaultMode); + paintInfo.context = savedContext; + } + } +#endif + + float opacity = style->opacity(); + if (opacity < 1) + paintInfo.context->endTransparencyLayer(); + + // This needs to be done separately from opacity, because if both properties are set, + // then the transparency layers are nested. + if (svgStyle->shadow()) + paintInfo.context->endTransparencyLayer(); +} + +void SVGRenderSupport::renderSubtreeToImage(ImageBuffer* image, RenderObject* item) +{ + ASSERT(item); + ASSERT(image); + ASSERT(image->context()); + + // FIXME: This sets the rect to the viewable area of the current frame. This + // is used to support text drawings to the ImageBuffer. See bug 30399. + IntRect rect; + FrameView* frameView = item->document()->view(); + if (frameView) + rect = IntRect(0, 0, frameView->visibleWidth(), frameView->visibleHeight()); + PaintInfo info(image->context(), rect, PaintPhaseForeground, 0, 0, 0); + + // FIXME: isSVGContainer returns true for RenderSVGViewportContainer, so if this is ever + // called with one of those, we will read from the wrong offset in an object due to a bad cast. + RenderSVGContainer* svgContainer = 0; + if (item && item->isSVGContainer()) + svgContainer = toRenderSVGContainer(item); + + bool drawsContents = svgContainer ? svgContainer->drawsContents() : false; + if (svgContainer && !drawsContents) + svgContainer->setDrawsContents(true); + + item->layoutIfNeeded(); + item->paint(info, 0, 0); + + if (svgContainer && !drawsContents) + svgContainer->setDrawsContents(false); +} + +FloatRect SVGRenderSupport::computeContainerBoundingBox(const RenderObject* container, ContainerBoundingBoxMode mode) +{ + FloatRect boundingBox; + + for (RenderObject* current = container->firstChild(); current; current = current->nextSibling()) { + FloatRect childBoundingBox; + + switch (mode) { + case ObjectBoundingBox: + childBoundingBox = current->objectBoundingBox(); + break; + case StrokeBoundingBox: + childBoundingBox = current->strokeBoundingBox(); + break; + case RepaintBoundingBox: + childBoundingBox = current->repaintRectInLocalCoordinates(); + break; + } + + boundingBox.unite(current->localToParentTransform().mapRect(childBoundingBox)); + } + + return boundingBox; +} + +static inline RenderSVGRoot* svgRootTreeObject(RenderObject* start) +{ + while (start && !start->isSVGRoot()) + start = start->parent(); + + ASSERT(start); + ASSERT(start->isSVGRoot()); + return toRenderSVGRoot(start); +} + +void SVGRenderSupport::layoutChildren(RenderObject* start, bool selfNeedsLayout) +{ + bool layoutSizeChanged = svgRootTreeObject(start)->isLayoutSizeChanged(); + + for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { + bool needsLayout = selfNeedsLayout; + + if (layoutSizeChanged) { + // When selfNeedsLayout is false and the layout size changed, we have to check whether this child uses relative lengths + if (SVGElement* element = child->node()->isSVGElement() ? static_cast(child->node()) : 0) { + if (element->isStyled() && static_cast(element)->hasRelativeLengths()) { + // When the layout size changed and when using relative values tell the RenderPath to update its Path object + if (child->isRenderPath()) + toRenderPath(child)->setNeedsPathUpdate(); + + needsLayout = true; + } + } + } + + if (needsLayout) { + child->setNeedsLayout(true, false); + child->layout(); + } else + child->layoutIfNeeded(); + + ASSERT(!child->needsLayout()); + } +} + +bool SVGRenderSupport::isOverflowHidden(const RenderObject* object) +{ + // SVG doesn't support independent x/y overflow + ASSERT(object->style()->overflowX() == object->style()->overflowY()); + + // OSCROLL is never set for SVG - see CSSStyleSelector::adjustRenderStyle + ASSERT(object->style()->overflowX() != OSCROLL); + + // RenderSVGRoot should never query for overflow state - it should always clip itself to the initial viewport size. + ASSERT(!object->isRoot()); + + return object->style()->overflowX() == OHIDDEN; +} + +void SVGRenderSupport::intersectRepaintRectWithResources(const RenderObject* object, FloatRect& repaintRect) +{ + ASSERT(object); + ASSERT(object->style()); + const SVGRenderStyle* svgStyle = object->style()->svgStyle(); + if (!svgStyle) + return; + + RenderObject* renderer = const_cast(object); +#if ENABLE(FILTERS) + if (svgStyle->hasFilter()) { + if (RenderSVGResourceFilter* filter = getRenderSVGResourceById(object->document(), svgStyle->filterResource())) + repaintRect = filter->resourceBoundingBox(renderer); + } +#endif + + if (svgStyle->hasClipper()) { + if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById(object->document(), svgStyle->clipperResource())) + repaintRect.intersect(clipper->resourceBoundingBox(renderer)); + } + + if (svgStyle->hasMasker()) { + if (RenderSVGResourceMasker* masker = getRenderSVGResourceById(object->document(), svgStyle->maskerResource())) + repaintRect.intersect(masker->resourceBoundingBox(renderer)); + } + + svgStyle->inflateForShadow(repaintRect); +} + +bool SVGRenderSupport::pointInClippingArea(const RenderObject* object, const FloatPoint& point) +{ + ASSERT(object); + ASSERT(object->style()); + + Document* document = object->document(); + ASSERT(document); + + const SVGRenderStyle* svgStyle = object->style()->svgStyle(); + ASSERT(svgStyle); + + // We just take clippers into account to determine if a point is on the node. The Specification may + // change later and we also need to check maskers. + if (svgStyle->hasClipper()) { + if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById(document, svgStyle->clipperResource())) + return clipper->hitTestClipContent(object->objectBoundingBox(), point); + } + + return true; +} + +DashArray SVGRenderSupport::dashArrayFromRenderingStyle(const RenderStyle* style, RenderStyle* rootStyle) +{ + DashArray array; + + CSSValueList* dashes = style->svgStyle()->strokeDashArray(); + if (!dashes) + return array; + + CSSPrimitiveValue* dash = 0; + unsigned long len = dashes->length(); + for (unsigned long i = 0; i < len; ++i) { + dash = static_cast(dashes->itemWithoutBoundsCheck(i)); + if (!dash) + continue; + + array.append(dash->computeLengthFloat(const_cast(style), rootStyle)); + } + + return array; +} + +void SVGRenderSupport::applyStrokeStyleToContext(GraphicsContext* context, const RenderStyle* style, const RenderObject* object) +{ + context->setStrokeThickness(SVGRenderStyle::cssPrimitiveToLength(object, style->svgStyle()->strokeWidth(), 1.0f)); + context->setLineCap(style->svgStyle()->capStyle()); + context->setLineJoin(style->svgStyle()->joinStyle()); + if (style->svgStyle()->joinStyle() == MiterJoin) + context->setMiterLimit(style->svgStyle()->strokeMiterLimit()); + + const DashArray& dashes = dashArrayFromRenderingStyle(object->style(), object->document()->documentElement()->renderStyle()); + float dashOffset = SVGRenderStyle::cssPrimitiveToLength(object, style->svgStyle()->strokeDashOffset(), 0.0f); + if (dashes.isEmpty()) + context->setStrokeStyle(SolidStroke); + else + context->setLineDash(dashes, dashOffset); +} + +const RenderObject* SVGRenderSupport::findTextRootObject(const RenderObject* start) +{ + while (start && !start->isSVGText()) + start = start->parent(); + ASSERT(start); + ASSERT(start->isSVGText()); + + return start; +} + +} + +#endif