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/frame/Frame.h"
     30 #include "core/frame/FrameView.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 #include "platform/graphics/DisplayList.h"
     37 #include "platform/graphics/GraphicsContextStateSaver.h"
     38 #include "wtf/TemporaryChange.h"
     39 
     40 namespace WebCore {
     41 
     42 const RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType;
     43 
     44 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
     45     : RenderSVGResourceContainer(node)
     46     , m_inClipExpansion(false)
     47 {
     48 }
     49 
     50 RenderSVGResourceClipper::~RenderSVGResourceClipper()
     51 {
     52 }
     53 
     54 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
     55 {
     56     m_clipContentDisplayList.clear();
     57     m_clipBoundaries = FloatRect();
     58     markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
     59 }
     60 
     61 void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation)
     62 {
     63     ASSERT(client);
     64     markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
     65 }
     66 
     67 bool RenderSVGResourceClipper::applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short)
     68 {
     69     // Clippers are always applied using stateful methods.
     70     ASSERT_NOT_REACHED();
     71     return false;
     72 }
     73 
     74 bool RenderSVGResourceClipper::applyStatefulResource(RenderObject* object, GraphicsContext*& context, ClipperContext& clipperContext)
     75 {
     76     ASSERT(object);
     77     ASSERT(context);
     78 
     79     clearInvalidationMask();
     80 
     81     return applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context, clipperContext);
     82 }
     83 
     84 bool RenderSVGResourceClipper::tryPathOnlyClipping(GraphicsContext* context,
     85     const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) {
     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 = element()->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 (toSVGClipPathElement(element())->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* target, const FloatRect& targetBoundingBox,
    148     const FloatRect& repaintRect, GraphicsContext* context, ClipperContext& clipperContext)
    149 {
    150     ASSERT(target);
    151     ASSERT(target->node());
    152     ASSERT(context);
    153     ASSERT(clipperContext.state == ClipperContext::NotAppliedState);
    154     ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout());
    155 
    156     if (repaintRect.isEmpty() || m_inClipExpansion)
    157         return false;
    158     TemporaryChange<bool> inClipExpansionChange(m_inClipExpansion, true);
    159 
    160     AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->animatedLocalTransform();
    161     // When drawing a clip for non-SVG elements, the CTM does not include the zoom factor.
    162     // In this case, we need to apply the zoom scale explicitly - but only for clips with
    163     // userSpaceOnUse units (the zoom is accounted for objectBoundingBox-resolved lengths).
    164     if (!target->node()->isSVGElement()
    165         && toSVGClipPathElement(element())->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) {
    166         ASSERT(style());
    167         animatedLocalTransform.scale(style()->effectiveZoom());
    168     }
    169 
    170     // First, try to apply the clip as a clipPath.
    171     if (tryPathOnlyClipping(context, animatedLocalTransform, targetBoundingBox)) {
    172         clipperContext.state = ClipperContext::AppliedPathState;
    173         return true;
    174     }
    175 
    176     // Fall back to masking.
    177     clipperContext.state = ClipperContext::AppliedMaskState;
    178 
    179     // Mask layer start
    180     context->beginTransparencyLayer(1, &repaintRect);
    181     {
    182         GraphicsContextStateSaver maskContentSaver(*context);
    183         context->concatCTM(animatedLocalTransform);
    184 
    185         // clipPath can also be clipped by another clipPath.
    186         SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this);
    187         RenderSVGResourceClipper* clipPathClipper = 0;
    188         ClipperContext clipPathClipperContext;
    189         if (resources && (clipPathClipper = resources->clipper())) {
    190             if (!clipPathClipper->applyClippingToContext(this, targetBoundingBox, repaintRect, context, clipPathClipperContext)) {
    191                 // FIXME: Awkward state micro-management. Ideally, GraphicsContextStateSaver should
    192                 //   a) pop saveLayers also
    193                 //   b) pop multiple states if needed (similarly to SkCanvas::restoreToCount())
    194                 // Then we should be able to replace this mess with a single, top-level GCSS.
    195                 maskContentSaver.restore();
    196                 context->restoreLayer();
    197                 return false;
    198             }
    199         }
    200 
    201         drawClipMaskContent(context, targetBoundingBox);
    202 
    203         if (clipPathClipper)
    204             clipPathClipper->postApplyStatefulResource(this, context, clipPathClipperContext);
    205     }
    206 
    207     // Masked content layer start.
    208     context->beginLayer(1, CompositeSourceIn, &repaintRect);
    209 
    210     return true;
    211 }
    212 
    213 void RenderSVGResourceClipper::postApplyResource(RenderObject*, GraphicsContext*&, unsigned short,
    214     const Path*, const RenderSVGShape*) {
    215     // Clippers are always applied using stateful methods.
    216     ASSERT_NOT_REACHED();
    217 }
    218 
    219 void RenderSVGResourceClipper::postApplyStatefulResource(RenderObject*, GraphicsContext*& context, ClipperContext& clipperContext)
    220 {
    221     switch (clipperContext.state) {
    222     case ClipperContext::AppliedPathState:
    223         // Path-only clipping, no layers to restore.
    224         break;
    225     case ClipperContext::AppliedMaskState:
    226         // Transfer content layer -> mask layer (SrcIn)
    227         context->endLayer();
    228         // Transfer mask layer -> bg layer (SrcOver)
    229         context->endLayer();
    230         break;
    231     default:
    232         ASSERT_NOT_REACHED();
    233     }
    234 }
    235 
    236 void RenderSVGResourceClipper::drawClipMaskContent(GraphicsContext* context, const FloatRect& targetBoundingBox)
    237 {
    238     ASSERT(context);
    239 
    240     AffineTransform contentTransformation;
    241     SVGUnitTypes::SVGUnitType contentUnits = toSVGClipPathElement(element())->clipPathUnitsCurrentValue();
    242     if (contentUnits == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    243         contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y());
    244         contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height());
    245         context->concatCTM(contentTransformation);
    246     }
    247 
    248     if (!m_clipContentDisplayList)
    249         m_clipContentDisplayList = asDisplayList(context, contentTransformation);
    250 
    251     ASSERT(m_clipContentDisplayList);
    252     context->drawDisplayList(m_clipContentDisplayList.get());
    253 }
    254 
    255 PassRefPtr<DisplayList> RenderSVGResourceClipper::asDisplayList(GraphicsContext* context,
    256     const AffineTransform& contentTransformation)
    257 {
    258     ASSERT(context);
    259     ASSERT(frame());
    260 
    261     context->beginRecording(repaintRectInLocalCoordinates());
    262 
    263     // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
    264     // - fill-opacity/stroke-opacity/opacity set to 1
    265     // - masker/filter not applied when rendering the children
    266     // - fill is set to the initial fill paint server (solid, black)
    267     // - stroke is set to the initial stroke paint server (none)
    268     PaintBehavior oldBehavior = frame()->view()->paintBehavior();
    269     frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
    270 
    271     for (Node* childNode = element()->firstChild(); childNode; childNode = childNode->nextSibling()) {
    272         RenderObject* renderer = childNode->renderer();
    273         if (!childNode->isSVGElement() || !renderer)
    274             continue;
    275 
    276         RenderStyle* style = renderer->style();
    277         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    278             continue;
    279 
    280         WindRule newClipRule = style->svgStyle()->clipRule();
    281         bool isUseElement = childNode->hasTagName(SVGNames::useTag);
    282         if (isUseElement) {
    283             SVGUseElement* useElement = toSVGUseElement(childNode);
    284             renderer = useElement->rendererClipChild();
    285             if (!renderer)
    286                 continue;
    287             if (!useElement->hasAttribute(SVGNames::clip_ruleAttr))
    288                 newClipRule = renderer->style()->svgStyle()->clipRule();
    289         }
    290 
    291         // Only shapes, paths and texts are allowed for clipping.
    292         if (!renderer->isSVGShape() && !renderer->isSVGText())
    293             continue;
    294 
    295         context->setFillRule(newClipRule);
    296 
    297         if (isUseElement)
    298             renderer = childNode->renderer();
    299 
    300         SVGRenderingContext::renderSubtree(context, renderer, contentTransformation);
    301     }
    302 
    303     frame()->view()->setPaintBehavior(oldBehavior);
    304 
    305     return context->endRecording();
    306 }
    307 
    308 void RenderSVGResourceClipper::calculateClipContentRepaintRect()
    309 {
    310     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
    311     for (Node* childNode = element()->firstChild(); childNode; childNode = childNode->nextSibling()) {
    312         RenderObject* renderer = childNode->renderer();
    313         if (!childNode->isSVGElement() || !renderer)
    314             continue;
    315         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
    316             continue;
    317         RenderStyle* style = renderer->style();
    318         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    319              continue;
    320         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates()));
    321     }
    322     m_clipBoundaries = toSVGClipPathElement(element())->animatedLocalTransform().mapRect(m_clipBoundaries);
    323 }
    324 
    325 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
    326 {
    327     FloatPoint point = nodeAtPoint;
    328     if (!SVGRenderSupport::pointInClippingArea(this, point))
    329         return false;
    330 
    331     SVGClipPathElement* clipPathElement = toSVGClipPathElement(element());
    332     if (clipPathElement->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    333         AffineTransform transform;
    334         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    335         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    336         point = transform.inverse().mapPoint(point);
    337     }
    338 
    339     point = clipPathElement->animatedLocalTransform().inverse().mapPoint(point);
    340 
    341     for (Node* childNode = element()->firstChild(); childNode; childNode = childNode->nextSibling()) {
    342         RenderObject* renderer = childNode->renderer();
    343         if (!childNode->isSVGElement() || !renderer)
    344             continue;
    345         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
    346             continue;
    347         IntPoint hitPoint;
    348         HitTestResult result(hitPoint);
    349         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent), result, point, HitTestForeground))
    350             return true;
    351     }
    352 
    353     return false;
    354 }
    355 
    356 FloatRect RenderSVGResourceClipper::resourceBoundingBox(RenderObject* object)
    357 {
    358     // Resource was not layouted yet. Give back the boundingBox of the object.
    359     if (selfNeedsLayout())
    360         return object->objectBoundingBox();
    361 
    362     if (m_clipBoundaries.isEmpty())
    363         calculateClipContentRepaintRect();
    364 
    365     if (toSVGClipPathElement(element())->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    366         FloatRect objectBoundingBox = object->objectBoundingBox();
    367         AffineTransform transform;
    368         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    369         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    370         return transform.mapRect(m_clipBoundaries);
    371     }
    372 
    373     return m_clipBoundaries;
    374 }
    375 
    376 }
    377