WebCore/rendering/RenderSVGResourceClipper.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2  * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
       
     3  *               2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org>
       
     4  * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
       
     5  *
       
     6  * This library is free software; you can redistribute it and/or
       
     7  * modify it under the terms of the GNU Library General Public
       
     8  * License as published by the Free Software Foundation; either
       
     9  * version 2 of the License, or (at your option) any later version.
       
    10  *
       
    11  * This library is distributed in the hope that it will be useful,
       
    12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    14  * Library General Public License for more details.
       
    15  *
       
    16  * You should have received a copy of the GNU Library General Public License
       
    17  * along with this library; see the file COPYING.LIB.  If not, write to
       
    18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
       
    19  * Boston, MA 02110-1301, USA.
       
    20  *
       
    21  */
       
    22 
       
    23 #include "config.h"
       
    24 #if ENABLE(SVG)
       
    25 #include "RenderSVGResourceClipper.h"
       
    26 
       
    27 #include "AffineTransform.h"
       
    28 #include "FloatRect.h"
       
    29 #include "GraphicsContext.h"
       
    30 #include "HitTestRequest.h"
       
    31 #include "HitTestResult.h"
       
    32 #include "ImageBuffer.h"
       
    33 #include "IntRect.h"
       
    34 #include "RenderObject.h"
       
    35 #include "RenderStyle.h"
       
    36 #include "RenderSVGResource.h"
       
    37 #include "SVGClipPathElement.h"
       
    38 #include "SVGElement.h"
       
    39 #include "SVGRenderSupport.h"
       
    40 #include "SVGStyledElement.h"
       
    41 #include "SVGStyledTransformableElement.h"
       
    42 #include "SVGUnitTypes.h"
       
    43 #include "SVGUseElement.h"
       
    44 #include <wtf/UnusedParam.h>
       
    45 
       
    46 namespace WebCore {
       
    47 
       
    48 RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType;
       
    49 
       
    50 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
       
    51     : RenderSVGResourceContainer(node)
       
    52     , m_invalidationBlocked(false)
       
    53 {
       
    54 }
       
    55 
       
    56 RenderSVGResourceClipper::~RenderSVGResourceClipper()
       
    57 {
       
    58     deleteAllValues(m_clipper);
       
    59     m_clipper.clear();
       
    60 }
       
    61 
       
    62 void RenderSVGResourceClipper::invalidateClients()
       
    63 {
       
    64     if (m_invalidationBlocked)
       
    65         return;
       
    66 
       
    67     HashMap<RenderObject*, ClipperData*>::const_iterator end = m_clipper.end();
       
    68     for (HashMap<RenderObject*, ClipperData*>::const_iterator it = m_clipper.begin(); it != end; ++it)
       
    69         markForLayoutAndResourceInvalidation(it->first);
       
    70 
       
    71     deleteAllValues(m_clipper);
       
    72     m_clipper.clear();
       
    73     m_clipBoundaries = FloatRect();
       
    74 }
       
    75 
       
    76 void RenderSVGResourceClipper::invalidateClient(RenderObject* object)
       
    77 {
       
    78     if (m_invalidationBlocked)
       
    79         return;
       
    80 
       
    81     ASSERT(object);
       
    82     if (!m_clipper.contains(object))
       
    83         return;
       
    84 
       
    85     delete m_clipper.take(object);
       
    86     markForLayoutAndResourceInvalidation(object);
       
    87 }
       
    88 
       
    89 bool RenderSVGResourceClipper::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode)
       
    90 {
       
    91     ASSERT(object);
       
    92     ASSERT(context);
       
    93 #ifndef NDEBUG
       
    94     ASSERT(resourceMode == ApplyToDefaultMode);
       
    95 #else
       
    96     UNUSED_PARAM(resourceMode);
       
    97 #endif
       
    98 
       
    99     // Early exit, if this resource contains a child which references ourselves.
       
   100     if (containsCyclicReference(node()))
       
   101         return false;
       
   102 
       
   103     applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context);
       
   104     return true;
       
   105 }
       
   106 
       
   107 bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const FloatRect& objectBoundingBox)
       
   108 {
       
   109     // If the current clip-path gets clipped itself, we have to fallback to masking.
       
   110     if (!style()->svgStyle()->clipperResource().isEmpty())
       
   111         return false;
       
   112     WindRule clipRule = RULE_NONZERO;
       
   113     Path clipPath = Path();
       
   114 
       
   115     // If clip-path only contains one visible shape or path, we can use path-based clipping. Invisible
       
   116     // shapes don't affect the clipping and can be ignored. If clip-path contains more than one
       
   117     // visible shape, the additive clipping may not work, caused by the clipRule. EvenOdd
       
   118     // as well as NonZero can cause self-clipping of the elements.
       
   119     // See also http://www.w3.org/TR/SVG/painting.html#FillRuleProperty
       
   120     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
       
   121         RenderObject* renderer = childNode->renderer();
       
   122         if (!renderer)
       
   123             continue;
       
   124         // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
       
   125         if (renderer->isSVGText())
       
   126             return false;
       
   127         if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyledTransformable())
       
   128             continue;
       
   129         SVGStyledTransformableElement* styled = static_cast<SVGStyledTransformableElement*>(childNode);
       
   130         RenderStyle* style = renderer->style();
       
   131         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
       
   132              continue;
       
   133         const SVGRenderStyle* svgStyle = style->svgStyle();
       
   134         // Current shape in clip-path gets clipped too. Fallback to masking.
       
   135         if (!svgStyle->clipperResource().isEmpty())
       
   136             return false;
       
   137         // Fallback to masking, if there is more than one clipping path.
       
   138         if (clipPath.isEmpty()) {
       
   139             clipPath = styled->toClipPath();
       
   140             clipRule = svgStyle->clipRule();
       
   141         } else
       
   142             return false;
       
   143     }
       
   144     // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary.
       
   145     if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
       
   146         AffineTransform transform;
       
   147         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
       
   148         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
       
   149         clipPath.transform(transform);
       
   150     }
       
   151     // The SVG specification wants us to clip everything, if clip-path doesn't have a child.
       
   152     if (clipPath.isEmpty())
       
   153         clipPath.addRect(FloatRect());
       
   154     context->beginPath();
       
   155     context->addPath(clipPath);
       
   156     context->clipPath(clipRule);
       
   157     return true;
       
   158 }
       
   159 
       
   160 bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* object, const FloatRect& objectBoundingBox,
       
   161                                                       const FloatRect& repaintRect, GraphicsContext* context)
       
   162 {
       
   163     if (!m_clipper.contains(object))
       
   164         m_clipper.set(object, new ClipperData);
       
   165 
       
   166     ClipperData* clipperData = m_clipper.get(object);
       
   167     if (!clipperData->clipMaskImage) {
       
   168         if (pathOnlyClipping(context, objectBoundingBox))
       
   169             return true;
       
   170         createClipData(clipperData, objectBoundingBox, repaintRect);
       
   171     }
       
   172 
       
   173     if (!clipperData->clipMaskImage)
       
   174         return false;
       
   175 
       
   176     context->clipToImageBuffer(repaintRect, clipperData->clipMaskImage.get());
       
   177     return true;
       
   178 }
       
   179 
       
   180 bool RenderSVGResourceClipper::createClipData(ClipperData* clipperData, const FloatRect& objectBoundingBox, const FloatRect& repaintRect)
       
   181 {
       
   182     IntRect clipMaskRect = enclosingIntRect(repaintRect);
       
   183     clipperData->clipMaskImage = ImageBuffer::create(clipMaskRect.size());
       
   184     if (!clipperData->clipMaskImage)
       
   185         return false;
       
   186 
       
   187     GraphicsContext* maskContext = clipperData->clipMaskImage->context();
       
   188     ASSERT(maskContext);
       
   189 
       
   190     maskContext->save();
       
   191     maskContext->translate(-repaintRect.x(), -repaintRect.y());
       
   192 
       
   193     // clipPath can also be clipped by another clipPath.
       
   194     if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(this->document(), style()->svgStyle()->clipperResource())) {
       
   195         if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext)) {
       
   196             maskContext->restore();
       
   197             return false;
       
   198         }            
       
   199     }
       
   200 
       
   201     SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node());
       
   202     if (clipPath->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
       
   203         maskContext->translate(objectBoundingBox.x(), objectBoundingBox.y());
       
   204         maskContext->scale(objectBoundingBox.size());
       
   205     }
       
   206 
       
   207     // Draw all clipPath children into a global mask.
       
   208     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
       
   209         RenderObject* renderer = childNode->renderer();
       
   210         if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer)
       
   211             continue;
       
   212         RenderStyle* style = renderer->style();
       
   213         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
       
   214             continue;
       
   215 
       
   216         WindRule newClipRule = style->svgStyle()->clipRule();
       
   217         bool isUseElement = renderer->isSVGShadowTreeRootContainer();
       
   218         if (isUseElement) {
       
   219             SVGUseElement* useElement = static_cast<SVGUseElement*>(childNode);
       
   220             renderer = useElement->rendererClipChild();
       
   221             if (!renderer)
       
   222                 continue;
       
   223             if (!useElement->hasAttribute(SVGNames::clip_ruleAttr))
       
   224                 newClipRule = renderer->style()->svgStyle()->clipRule();
       
   225         }
       
   226 
       
   227         // Only shapes, paths and texts are allowed for clipping.
       
   228         if (!renderer->isRenderPath() && !renderer->isSVGText())
       
   229             continue;
       
   230 
       
   231         // Save the old RenderStyle of the current object for restoring after drawing
       
   232         // it to the MaskImage. The new intermediate RenderStyle needs to inherit from
       
   233         // the old one.
       
   234         RefPtr<RenderStyle> oldRenderStyle = renderer->style();
       
   235         RefPtr<RenderStyle> newRenderStyle = RenderStyle::clone(oldRenderStyle.get());
       
   236         SVGRenderStyle* svgStyle = newRenderStyle.get()->accessSVGStyle();
       
   237         svgStyle->setFillPaint(SVGPaint::defaultFill());
       
   238         svgStyle->setStrokePaint(SVGPaint::defaultStroke());
       
   239         svgStyle->setFillRule(newClipRule);
       
   240         newRenderStyle.get()->setOpacity(1.0f);
       
   241         svgStyle->setFillOpacity(1.0f);
       
   242         svgStyle->setStrokeOpacity(1.0f);
       
   243         svgStyle->setFilterResource(String());
       
   244         svgStyle->setMaskerResource(String());
       
   245 
       
   246         // The setStyle() call results in a styleDidChange() call, which in turn invalidations the resources.
       
   247         // As we're mutating the resource on purpose, block updates until we've resetted the style again.
       
   248         m_invalidationBlocked = true;
       
   249         renderer->setStyle(newRenderStyle.release());
       
   250 
       
   251         // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule.
       
   252         // We hsve to pass the <use> renderer itself to renderSubtreeToImage() to apply it's x/y/transform/etc. values when rendering.
       
   253         // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above.
       
   254         SVGRenderSupport::renderSubtreeToImage(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer);
       
   255 
       
   256         renderer->setStyle(oldRenderStyle.release());
       
   257         m_invalidationBlocked = false;
       
   258     }
       
   259 
       
   260     maskContext->restore();
       
   261 
       
   262     return true;
       
   263 }
       
   264 
       
   265 void RenderSVGResourceClipper::calculateClipContentRepaintRect()
       
   266 {
       
   267     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
       
   268     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
       
   269         RenderObject* renderer = childNode->renderer();
       
   270         if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer)
       
   271             continue;
       
   272         if (!renderer->isRenderPath() && !renderer->isSVGText() && !renderer->isSVGShadowTreeRootContainer())
       
   273             continue;
       
   274         RenderStyle* style = renderer->style();
       
   275         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
       
   276              continue;
       
   277         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates()));
       
   278     }
       
   279 }
       
   280 
       
   281 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
       
   282 {
       
   283     // FIXME: We should be able to check whether m_clipper.contains(object) - this doesn't work at the moment
       
   284     // as resourceBoundingBox() has already created ClipperData, even if applyResource() returned false.
       
   285     // Early exit, if this resource contains a child which references ourselves.
       
   286     if (containsCyclicReference(node()))
       
   287         return false;
       
   288 
       
   289     FloatPoint point = nodeAtPoint;
       
   290     if (!SVGRenderSupport::pointInClippingArea(this, point))
       
   291         return false;
       
   292 
       
   293     if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
       
   294         AffineTransform transform;
       
   295         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
       
   296         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
       
   297         point = transform.inverse().mapPoint(point);
       
   298     }
       
   299 
       
   300     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
       
   301         RenderObject* renderer = childNode->renderer();
       
   302         if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer)
       
   303             continue;
       
   304         if (!renderer->isRenderPath() && !renderer->isSVGText() && !renderer->isSVGShadowTreeRootContainer())
       
   305             continue;
       
   306         IntPoint hitPoint;
       
   307         HitTestResult result(hitPoint);
       
   308         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent), result, point, HitTestForeground))
       
   309             return true;
       
   310     }
       
   311 
       
   312     return false;
       
   313 }
       
   314 
       
   315 bool RenderSVGResourceClipper::childElementReferencesResource(const SVGRenderStyle* style, const String& referenceId) const
       
   316 {
       
   317     if (!style->hasClipper())
       
   318         return false;
       
   319 
       
   320     return style->clipperResource() == referenceId;
       
   321 }
       
   322 
       
   323 FloatRect RenderSVGResourceClipper::resourceBoundingBox(RenderObject* object)
       
   324 {
       
   325     // Resource was not layouted yet. Give back the boundingBox of the object.
       
   326     if (selfNeedsLayout())
       
   327         return object->objectBoundingBox();
       
   328     
       
   329     if (m_clipBoundaries.isEmpty())
       
   330         calculateClipContentRepaintRect();
       
   331 
       
   332     if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
       
   333         FloatRect objectBoundingBox = object->objectBoundingBox();
       
   334         AffineTransform transform;
       
   335         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
       
   336         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
       
   337         return transform.mapRect(m_clipBoundaries);
       
   338     }
       
   339 
       
   340     return m_clipBoundaries;
       
   341 }
       
   342 
       
   343 }
       
   344 
       
   345 #endif // ENABLE(SVG)