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 "core/SVGNames.h"
     28 #include "core/dom/ElementTraversal.h"
     29 #include "core/frame/FrameView.h"
     30 #include "core/frame/LocalFrame.h"
     31 #include "core/rendering/HitTestResult.h"
     32 #include "core/rendering/svg/SVGRenderSupport.h"
     33 #include "core/rendering/svg/SVGRenderingContext.h"
     34 #include "core/rendering/svg/SVGResources.h"
     35 #include "core/rendering/svg/SVGResourcesCache.h"
     36 #include "core/svg/SVGUseElement.h"
     37 #include "platform/RuntimeEnabledFeatures.h"
     38 #include "platform/graphics/DisplayList.h"
     39 #include "platform/graphics/GraphicsContextStateSaver.h"
     40 #include "wtf/TemporaryChange.h"
     41 
     42 namespace blink {
     43 
     44 const RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType;
     45 
     46 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
     47     : RenderSVGResourceContainer(node)
     48     , m_inClipExpansion(false)
     49 {
     50 }
     51 
     52 RenderSVGResourceClipper::~RenderSVGResourceClipper()
     53 {
     54 }
     55 
     56 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
     57 {
     58     m_clipContentDisplayList.clear();
     59     m_clipBoundaries = FloatRect();
     60     markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
     61 }
     62 
     63 void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation)
     64 {
     65     ASSERT(client);
     66     markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
     67 }
     68 
     69 bool RenderSVGResourceClipper::applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short)
     70 {
     71     // Clippers are always applied using stateful methods.
     72     ASSERT_NOT_REACHED();
     73     return false;
     74 }
     75 
     76 bool RenderSVGResourceClipper::applyStatefulResource(RenderObject* object, GraphicsContext*& context, ClipperState& clipperState)
     77 {
     78     ASSERT(object);
     79     ASSERT(context);
     80 
     81     clearInvalidationMask();
     82 
     83     return applyClippingToContext(object, object->objectBoundingBox(), object->paintInvalidationRectInLocalCoordinates(), context, clipperState);
     84 }
     85 
     86 bool RenderSVGResourceClipper::tryPathOnlyClipping(GraphicsContext* context,
     87     const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) {
     88     // If the current clip-path gets clipped itself, we have to fallback to masking.
     89     if (!style()->svgStyle().clipperResource().isEmpty())
     90         return false;
     91     WindRule clipRule = RULE_NONZERO;
     92     Path clipPath = Path();
     93 
     94     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
     95         RenderObject* renderer = childElement->renderer();
     96         if (!renderer)
     97             continue;
     98         // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
     99         if (renderer->isSVGText())
    100             return false;
    101         if (!childElement->isSVGGraphicsElement())
    102             continue;
    103         SVGGraphicsElement* styled = toSVGGraphicsElement(childElement);
    104         RenderStyle* style = renderer->style();
    105         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    106              continue;
    107         const SVGRenderStyle& svgStyle = style->svgStyle();
    108         // Current shape in clip-path gets clipped too. Fallback to masking.
    109         if (!svgStyle.clipperResource().isEmpty())
    110             return false;
    111 
    112         if (clipPath.isEmpty()) {
    113             // First clip shape.
    114             styled->toClipPath(clipPath);
    115             clipRule = svgStyle.clipRule();
    116             clipPath.setWindRule(clipRule);
    117             continue;
    118         }
    119 
    120         if (RuntimeEnabledFeatures::pathOpsSVGClippingEnabled()) {
    121             // Attempt to generate a combined clip path, fall back to masking if not possible.
    122             Path subPath;
    123             styled->toClipPath(subPath);
    124             subPath.setWindRule(svgStyle.clipRule());
    125             if (!clipPath.unionPath(subPath))
    126                 return false;
    127         } else {
    128             return false;
    129         }
    130     }
    131     // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary.
    132     if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    133         AffineTransform transform;
    134         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    135         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    136         clipPath.transform(transform);
    137     }
    138 
    139     // Transform path by animatedLocalTransform.
    140     clipPath.transform(animatedLocalTransform);
    141 
    142     // The SVG specification wants us to clip everything, if clip-path doesn't have a child.
    143     if (clipPath.isEmpty())
    144         clipPath.addRect(FloatRect());
    145     context->clipPath(clipPath, clipRule);
    146     return true;
    147 }
    148 
    149 bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* target, const FloatRect& targetBoundingBox,
    150     const FloatRect& paintInvalidationRect, GraphicsContext* context, ClipperState& clipperState)
    151 {
    152     ASSERT(target);
    153     ASSERT(context);
    154     ASSERT(clipperState == ClipperNotApplied);
    155     ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout());
    156 
    157     if (paintInvalidationRect.isEmpty() || m_inClipExpansion)
    158         return false;
    159     TemporaryChange<bool> inClipExpansionChange(m_inClipExpansion, true);
    160 
    161     AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->animatedLocalTransform();
    162     // When drawing a clip for non-SVG elements, the CTM does not include the zoom factor.
    163     // In this case, we need to apply the zoom scale explicitly - but only for clips with
    164     // userSpaceOnUse units (the zoom is accounted for objectBoundingBox-resolved lengths).
    165     if (!target->isSVG() && clipPathUnits() == 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         clipperState = ClipperAppliedPath;
    173         return true;
    174     }
    175 
    176     // Fall back to masking.
    177     clipperState = ClipperAppliedMask;
    178 
    179     // Mask layer start
    180     context->beginTransparencyLayer(1, &paintInvalidationRect);
    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 = resources ? resources->clipper() : 0;
    188         ClipperState clipPathClipperState = ClipperNotApplied;
    189         if (clipPathClipper && !clipPathClipper->applyClippingToContext(this, targetBoundingBox, paintInvalidationRect, context, clipPathClipperState)) {
    190             // FIXME: Awkward state micro-management. Ideally, GraphicsContextStateSaver should
    191             //   a) pop saveLayers also
    192             //   b) pop multiple states if needed (similarly to SkCanvas::restoreToCount())
    193             // Then we should be able to replace this mess with a single, top-level GCSS.
    194             maskContentSaver.restore();
    195             context->restoreLayer();
    196             return false;
    197         }
    198 
    199         drawClipMaskContent(context, targetBoundingBox);
    200 
    201         if (clipPathClipper)
    202             clipPathClipper->postApplyStatefulResource(this, context, clipPathClipperState);
    203     }
    204 
    205     // Masked content layer start.
    206     context->beginLayer(1, CompositeSourceIn, &paintInvalidationRect);
    207 
    208     return true;
    209 }
    210 
    211 void RenderSVGResourceClipper::postApplyResource(RenderObject*, GraphicsContext*&)
    212 {
    213     // Clippers are always applied using stateful methods.
    214     ASSERT_NOT_REACHED();
    215 }
    216 
    217 void RenderSVGResourceClipper::postApplyStatefulResource(RenderObject*, GraphicsContext*& context, ClipperState& clipperState)
    218 {
    219     switch (clipperState) {
    220     case ClipperAppliedPath:
    221         // Path-only clipping, no layers to restore.
    222         break;
    223     case ClipperAppliedMask:
    224         // Transfer content layer -> mask layer (SrcIn)
    225         context->endLayer();
    226         // Transfer mask layer -> bg layer (SrcOver)
    227         context->endLayer();
    228         break;
    229     default:
    230         ASSERT_NOT_REACHED();
    231     }
    232 }
    233 
    234 void RenderSVGResourceClipper::drawClipMaskContent(GraphicsContext* context, const FloatRect& targetBoundingBox)
    235 {
    236     ASSERT(context);
    237 
    238     AffineTransform contentTransformation;
    239     if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    240         contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y());
    241         contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height());
    242         context->concatCTM(contentTransformation);
    243     }
    244 
    245     if (!m_clipContentDisplayList)
    246         createDisplayList(context, contentTransformation);
    247 
    248     ASSERT(m_clipContentDisplayList);
    249     context->drawDisplayList(m_clipContentDisplayList.get());
    250 }
    251 
    252 void RenderSVGResourceClipper::createDisplayList(GraphicsContext* context,
    253     const AffineTransform& contentTransformation)
    254 {
    255     ASSERT(context);
    256     ASSERT(frame());
    257 
    258     // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection
    259     // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and
    260     // userSpaceOnUse units (http://crbug.com/294900).
    261     FloatRect bounds = strokeBoundingBox();
    262     context->beginRecording(bounds);
    263 
    264     // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
    265     // - fill-opacity/stroke-opacity/opacity set to 1
    266     // - masker/filter not applied when rendering the children
    267     // - fill is set to the initial fill paint server (solid, black)
    268     // - stroke is set to the initial stroke paint server (none)
    269     PaintBehavior oldBehavior = frame()->view()->paintBehavior();
    270     frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
    271 
    272     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
    273         RenderObject* renderer = childElement->renderer();
    274         if (!renderer)
    275             continue;
    276 
    277         RenderStyle* style = renderer->style();
    278         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    279             continue;
    280 
    281         WindRule newClipRule = style->svgStyle().clipRule();
    282         bool isUseElement = isSVGUseElement(*childElement);
    283         if (isUseElement) {
    284             SVGUseElement& useElement = toSVGUseElement(*childElement);
    285             renderer = useElement.rendererClipChild();
    286             if (!renderer)
    287                 continue;
    288             if (!useElement.hasAttribute(SVGNames::clip_ruleAttr))
    289                 newClipRule = renderer->style()->svgStyle().clipRule();
    290         }
    291 
    292         // Only shapes, paths and texts are allowed for clipping.
    293         if (!renderer->isSVGShape() && !renderer->isSVGText())
    294             continue;
    295 
    296         context->setFillRule(newClipRule);
    297 
    298         if (isUseElement)
    299             renderer = childElement->renderer();
    300 
    301         SVGRenderingContext::renderSubtree(context, renderer, contentTransformation);
    302     }
    303 
    304     frame()->view()->setPaintBehavior(oldBehavior);
    305 
    306     m_clipContentDisplayList = context->endRecording();
    307 }
    308 
    309 void RenderSVGResourceClipper::calculateClipContentPaintInvalidationRect()
    310 {
    311     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
    312     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
    313         RenderObject* renderer = childElement->renderer();
    314         if (!renderer)
    315             continue;
    316         if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement))
    317             continue;
    318         RenderStyle* style = renderer->style();
    319         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    320              continue;
    321         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->paintInvalidationRectInLocalCoordinates()));
    322     }
    323     m_clipBoundaries = toSVGClipPathElement(element())->animatedLocalTransform().mapRect(m_clipBoundaries);
    324 }
    325 
    326 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
    327 {
    328     FloatPoint point = nodeAtPoint;
    329     if (!SVGRenderSupport::pointInClippingArea(this, point))
    330         return false;
    331 
    332     if (clipPathUnits() == 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     AffineTransform animatedLocalTransform = toSVGClipPathElement(element())->animatedLocalTransform();
    340     if (!animatedLocalTransform.isInvertible())
    341         return false;
    342 
    343     point = animatedLocalTransform.inverse().mapPoint(point);
    344 
    345     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
    346         RenderObject* renderer = childElement->renderer();
    347         if (!renderer)
    348             continue;
    349         if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement))
    350             continue;
    351         IntPoint hitPoint;
    352         HitTestResult result(hitPoint);
    353         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent), result, point, HitTestForeground))
    354             return true;
    355     }
    356 
    357     return false;
    358 }
    359 
    360 FloatRect RenderSVGResourceClipper::resourceBoundingBox(const RenderObject* object)
    361 {
    362     // Resource was not layouted yet. Give back the boundingBox of the object.
    363     if (selfNeedsLayout())
    364         return object->objectBoundingBox();
    365 
    366     if (m_clipBoundaries.isEmpty())
    367         calculateClipContentPaintInvalidationRect();
    368 
    369     if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    370         FloatRect objectBoundingBox = object->objectBoundingBox();
    371         AffineTransform transform;
    372         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    373         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    374         return transform.mapRect(m_clipBoundaries);
    375     }
    376 
    377     return m_clipBoundaries;
    378 }
    379 
    380 }
    381