Home | History | Annotate | Download | only in svg
      1 /*
      2  * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann (at) kde.org>
      3  * Copyright (C) 2004, 2005, 2006, 2007, 2008 Rob Buis <buis (at) kde.org>
      4  * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
      5  * Copyright (C) 2011 Dirk Schulze <krit (at) webkit.org>
      6  *
      7  * This library is free software; you can redistribute it and/or
      8  * modify it under the terms of the GNU Library General Public
      9  * License as published by the Free Software Foundation; either
     10  * version 2 of the License, or (at your option) any later version.
     11  *
     12  * This library is distributed in the hope that it will be useful,
     13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15  * Library General Public License for more details.
     16  *
     17  * You should have received a copy of the GNU Library General Public License
     18  * along with this library; see the file COPYING.LIB.  If not, write to
     19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     20  * Boston, MA 02110-1301, USA.
     21  */
     22 
     23 #include "config.h"
     24 
     25 #include "core/rendering/svg/RenderSVGResourceClipper.h"
     26 
     27 #include "RuntimeEnabledFeatures.h"
     28 #include "SVGNames.h"
     29 #include "core/page/FrameView.h"
     30 #include "core/platform/graphics/GraphicsContextStateSaver.h"
     31 #include "core/rendering/HitTestResult.h"
     32 #include "core/rendering/svg/SVGRenderingContext.h"
     33 #include "core/rendering/svg/SVGResources.h"
     34 #include "core/rendering/svg/SVGResourcesCache.h"
     35 #include "core/svg/SVGUseElement.h"
     36 
     37 namespace WebCore {
     38 
     39 RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType;
     40 
     41 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
     42     : RenderSVGResourceContainer(node)
     43 {
     44 }
     45 
     46 RenderSVGResourceClipper::~RenderSVGResourceClipper()
     47 {
     48     if (m_clipper.isEmpty())
     49         return;
     50 
     51     deleteAllValues(m_clipper);
     52     m_clipper.clear();
     53 }
     54 
     55 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
     56 {
     57     m_clipBoundaries = FloatRect();
     58     if (!m_clipper.isEmpty()) {
     59         deleteAllValues(m_clipper);
     60         m_clipper.clear();
     61     }
     62 
     63     markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
     64 }
     65 
     66 void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation)
     67 {
     68     ASSERT(client);
     69     if (m_clipper.contains(client))
     70         delete m_clipper.take(client);
     71 
     72     markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
     73 }
     74 
     75 bool RenderSVGResourceClipper::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode)
     76 {
     77     ASSERT(object);
     78     ASSERT(context);
     79     ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
     80 
     81     return applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context);
     82 }
     83 
     84 bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox)
     85 {
     86     // If the current clip-path gets clipped itself, we have to fallback to masking.
     87     if (!style()->svgStyle()->clipperResource().isEmpty())
     88         return false;
     89     WindRule clipRule = RULE_NONZERO;
     90     Path clipPath = Path();
     91 
     92     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
     93         RenderObject* renderer = childNode->renderer();
     94         if (!renderer)
     95             continue;
     96         // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
     97         if (renderer->isSVGText())
     98             return false;
     99         if (!childNode->isSVGElement() || !toSVGElement(childNode)->isSVGGraphicsElement())
    100             continue;
    101         SVGGraphicsElement* styled = toSVGGraphicsElement(childNode);
    102         RenderStyle* style = renderer->style();
    103         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    104              continue;
    105         const SVGRenderStyle* svgStyle = style->svgStyle();
    106         // Current shape in clip-path gets clipped too. Fallback to masking.
    107         if (!svgStyle->clipperResource().isEmpty())
    108             return false;
    109 
    110         if (clipPath.isEmpty()) {
    111             // First clip shape.
    112             styled->toClipPath(clipPath);
    113             clipRule = svgStyle->clipRule();
    114             clipPath.setWindRule(clipRule);
    115             continue;
    116         }
    117 
    118         if (RuntimeEnabledFeatures::pathOpsSVGClippingEnabled()) {
    119             // Attempt to generate a combined clip path, fall back to masking if not possible.
    120             Path subPath;
    121             styled->toClipPath(subPath);
    122             subPath.setWindRule(svgStyle->clipRule());
    123             if (!clipPath.unionPath(subPath))
    124                 return false;
    125         } else {
    126             return false;
    127         }
    128     }
    129     // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary.
    130     if (static_cast<SVGClipPathElement*>(node())->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    131         AffineTransform transform;
    132         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    133         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    134         clipPath.transform(transform);
    135     }
    136 
    137     // Transform path by animatedLocalTransform.
    138     clipPath.transform(animatedLocalTransform);
    139 
    140     // The SVG specification wants us to clip everything, if clip-path doesn't have a child.
    141     if (clipPath.isEmpty())
    142         clipPath.addRect(FloatRect());
    143     context->clipPath(clipPath, clipRule);
    144     return true;
    145 }
    146 
    147 bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* object, const FloatRect& objectBoundingBox,
    148                                                       const FloatRect& repaintRect, GraphicsContext* context)
    149 {
    150     bool missingClipperData = !m_clipper.contains(object);
    151     if (missingClipperData)
    152         m_clipper.set(object, new ClipperData);
    153 
    154     bool shouldCreateClipData = false;
    155     AffineTransform animatedLocalTransform = static_cast<SVGClipPathElement*>(node())->animatedLocalTransform();
    156     ClipperData* clipperData = m_clipper.get(object);
    157     if (!clipperData->clipMaskImage) {
    158         if (pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox))
    159             return true;
    160         shouldCreateClipData = true;
    161     }
    162 
    163     AffineTransform absoluteTransform;
    164     SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(object, absoluteTransform);
    165 
    166     if (shouldCreateClipData && !repaintRect.isEmpty()) {
    167         if (!SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, clipperData->clipMaskImage, Unaccelerated))
    168             return false;
    169 
    170         GraphicsContext* maskContext = clipperData->clipMaskImage->context();
    171         ASSERT(maskContext);
    172 
    173         maskContext->concatCTM(animatedLocalTransform);
    174 
    175         // clipPath can also be clipped by another clipPath.
    176         SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
    177         RenderSVGResourceClipper* clipper;
    178         bool succeeded;
    179         if (resources && (clipper = resources->clipper())) {
    180             GraphicsContextStateSaver stateSaver(*maskContext);
    181 
    182             if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext))
    183                 return false;
    184 
    185             succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox);
    186             // The context restore applies the clipping on non-CG platforms.
    187         } else
    188             succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox);
    189 
    190         if (!succeeded)
    191             clipperData->clipMaskImage.clear();
    192     }
    193 
    194     if (!clipperData->clipMaskImage)
    195         return false;
    196 
    197     SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperData->clipMaskImage, missingClipperData);
    198     return true;
    199 }
    200 
    201 bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox)
    202 {
    203     ASSERT(frame());
    204     ASSERT(clipperData);
    205     ASSERT(clipperData->clipMaskImage);
    206 
    207     GraphicsContext* maskContext = clipperData->clipMaskImage->context();
    208     ASSERT(maskContext);
    209 
    210     AffineTransform maskContentTransformation;
    211     SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node());
    212     if (clipPath->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    213         maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y());
    214         maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    215         maskContext->concatCTM(maskContentTransformation);
    216     }
    217 
    218     // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
    219     // - fill-opacity/stroke-opacity/opacity set to 1
    220     // - masker/filter not applied when rendering the children
    221     // - fill is set to the initial fill paint server (solid, black)
    222     // - stroke is set to the initial stroke paint server (none)
    223     PaintBehavior oldBehavior = frame()->view()->paintBehavior();
    224     frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
    225 
    226     // Draw all clipPath children into a global mask.
    227     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
    228         RenderObject* renderer = childNode->renderer();
    229         if (!childNode->isSVGElement() || !renderer)
    230             continue;
    231         if (renderer->needsLayout()) {
    232             frame()->view()->setPaintBehavior(oldBehavior);
    233             return false;
    234         }
    235         RenderStyle* style = renderer->style();
    236         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    237             continue;
    238 
    239         WindRule newClipRule = style->svgStyle()->clipRule();
    240         bool isUseElement = childNode->hasTagName(SVGNames::useTag);
    241         if (isUseElement) {
    242             SVGUseElement* useElement = toSVGUseElement(childNode);
    243             renderer = useElement->rendererClipChild();
    244             if (!renderer)
    245                 continue;
    246             if (!useElement->hasAttribute(SVGNames::clip_ruleAttr))
    247                 newClipRule = renderer->style()->svgStyle()->clipRule();
    248         }
    249 
    250         // Only shapes, paths and texts are allowed for clipping.
    251         if (!renderer->isSVGShape() && !renderer->isSVGText())
    252             continue;
    253 
    254         maskContext->setFillRule(newClipRule);
    255 
    256         // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule.
    257         // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering.
    258         // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above.
    259         SVGRenderingContext::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation);
    260     }
    261 
    262     frame()->view()->setPaintBehavior(oldBehavior);
    263     return true;
    264 }
    265 
    266 void RenderSVGResourceClipper::calculateClipContentRepaintRect()
    267 {
    268     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
    269     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
    270         RenderObject* renderer = childNode->renderer();
    271         if (!childNode->isSVGElement() || !renderer)
    272             continue;
    273         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
    274             continue;
    275         RenderStyle* style = renderer->style();
    276         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    277              continue;
    278         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates()));
    279     }
    280     m_clipBoundaries = static_cast<SVGClipPathElement*>(node())->animatedLocalTransform().mapRect(m_clipBoundaries);
    281 }
    282 
    283 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
    284 {
    285     FloatPoint point = nodeAtPoint;
    286     if (!SVGRenderSupport::pointInClippingArea(this, point))
    287         return false;
    288 
    289     SVGClipPathElement* clipPathElement = static_cast<SVGClipPathElement*>(node());
    290     if (clipPathElement->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    291         AffineTransform transform;
    292         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    293         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    294         point = transform.inverse().mapPoint(point);
    295     }
    296 
    297     point = clipPathElement->animatedLocalTransform().inverse().mapPoint(point);
    298 
    299     for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
    300         RenderObject* renderer = childNode->renderer();
    301         if (!childNode->isSVGElement() || !renderer)
    302             continue;
    303         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
    304             continue;
    305         IntPoint hitPoint;
    306         HitTestResult result(hitPoint);
    307         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent | HitTestRequest::DisallowShadowContent), result, point, HitTestForeground))
    308             return true;
    309     }
    310 
    311     return false;
    312 }
    313 
    314 FloatRect RenderSVGResourceClipper::resourceBoundingBox(RenderObject* object)
    315 {
    316     // Resource was not layouted yet. Give back the boundingBox of the object.
    317     if (selfNeedsLayout())
    318         return object->objectBoundingBox();
    319 
    320     if (m_clipBoundaries.isEmpty())
    321         calculateClipContentRepaintRect();
    322 
    323     if (static_cast<SVGClipPathElement*>(node())->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    324         FloatRect objectBoundingBox = object->objectBoundingBox();
    325         AffineTransform transform;
    326         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    327         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    328         return transform.mapRect(m_clipBoundaries);
    329     }
    330 
    331     return m_clipBoundaries;
    332 }
    333 
    334 }
    335