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/frame/FrameView.h"
     29 #include "core/frame/LocalFrame.h"
     30 #include "core/rendering/HitTestResult.h"
     31 #include "core/rendering/svg/SVGRenderSupport.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/RuntimeEnabledFeatures.h"
     37 #include "platform/graphics/DisplayList.h"
     38 #include "platform/graphics/GraphicsContextStateSaver.h"
     39 #include "wtf/TemporaryChange.h"
     40 
     41 namespace WebCore {
     42 
     43 const RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType;
     44 
     45 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node)
     46     : RenderSVGResourceContainer(node)
     47     , m_inClipExpansion(false)
     48 {
     49 }
     50 
     51 RenderSVGResourceClipper::~RenderSVGResourceClipper()
     52 {
     53 }
     54 
     55 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation)
     56 {
     57     m_clipContentDisplayList.clear();
     58     m_clipBoundaries = FloatRect();
     59     markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
     60 }
     61 
     62 void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation)
     63 {
     64     ASSERT(client);
     65     markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
     66 }
     67 
     68 bool RenderSVGResourceClipper::applyResource(RenderObject*, RenderStyle*, GraphicsContext*&, unsigned short)
     69 {
     70     // Clippers are always applied using stateful methods.
     71     ASSERT_NOT_REACHED();
     72     return false;
     73 }
     74 
     75 bool RenderSVGResourceClipper::applyStatefulResource(RenderObject* object, GraphicsContext*& context, ClipperContext& clipperContext)
     76 {
     77     ASSERT(object);
     78     ASSERT(context);
     79 
     80     clearInvalidationMask();
     81 
     82     return applyClippingToContext(object, object->objectBoundingBox(), object->paintInvalidationRectInLocalCoordinates(), context, clipperContext);
     83 }
     84 
     85 bool RenderSVGResourceClipper::tryPathOnlyClipping(GraphicsContext* context,
     86     const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) {
     87     // If the current clip-path gets clipped itself, we have to fallback to masking.
     88     if (!style()->svgStyle()->clipperResource().isEmpty())
     89         return false;
     90     WindRule clipRule = RULE_NONZERO;
     91     Path clipPath = Path();
     92 
     93     for (Element* childElement = ElementTraversal::firstWithin(*element()); childElement; childElement = ElementTraversal::nextSibling(*childElement)) {
     94         RenderObject* renderer = childElement->renderer();
     95         if (!renderer)
     96             continue;
     97         // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts.
     98         if (renderer->isSVGText())
     99             return false;
    100         if (!childElement->isSVGElement() || !toSVGElement(childElement)->isSVGGraphicsElement())
    101             continue;
    102         SVGGraphicsElement* styled = toSVGGraphicsElement(childElement);
    103         RenderStyle* style = renderer->style();
    104         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    105              continue;
    106         const SVGRenderStyle* svgStyle = style->svgStyle();
    107         // Current shape in clip-path gets clipped too. Fallback to masking.
    108         if (!svgStyle->clipperResource().isEmpty())
    109             return false;
    110 
    111         if (clipPath.isEmpty()) {
    112             // First clip shape.
    113             styled->toClipPath(clipPath);
    114             clipRule = svgStyle->clipRule();
    115             clipPath.setWindRule(clipRule);
    116             continue;
    117         }
    118 
    119         if (RuntimeEnabledFeatures::pathOpsSVGClippingEnabled()) {
    120             // Attempt to generate a combined clip path, fall back to masking if not possible.
    121             Path subPath;
    122             styled->toClipPath(subPath);
    123             subPath.setWindRule(svgStyle->clipRule());
    124             if (!clipPath.unionPath(subPath))
    125                 return false;
    126         } else {
    127             return false;
    128         }
    129     }
    130     // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary.
    131     if (toSVGClipPathElement(element())->clipPathUnits()->currentValue()->enumValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    132         AffineTransform transform;
    133         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    134         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    135         clipPath.transform(transform);
    136     }
    137 
    138     // Transform path by animatedLocalTransform.
    139     clipPath.transform(animatedLocalTransform);
    140 
    141     // The SVG specification wants us to clip everything, if clip-path doesn't have a child.
    142     if (clipPath.isEmpty())
    143         clipPath.addRect(FloatRect());
    144     context->clipPath(clipPath, clipRule);
    145     return true;
    146 }
    147 
    148 bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* target, const FloatRect& targetBoundingBox,
    149     const FloatRect& repaintRect, GraphicsContext* context, ClipperContext& clipperContext)
    150 {
    151     ASSERT(target);
    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->isSVG()
    165         && toSVGClipPathElement(element())->clipPathUnits()->currentValue()->enumValue() == 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())->clipPathUnits()->currentValue()->enumValue();
    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     // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection
    262     // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and
    263     // userSpaceOnUse units (http://crbug.com/294900).
    264     context->beginRecording(strokeBoundingBox());
    265 
    266     // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints:
    267     // - fill-opacity/stroke-opacity/opacity set to 1
    268     // - masker/filter not applied when rendering the children
    269     // - fill is set to the initial fill paint server (solid, black)
    270     // - stroke is set to the initial stroke paint server (none)
    271     PaintBehavior oldBehavior = frame()->view()->paintBehavior();
    272     frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask);
    273 
    274     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
    275         RenderObject* renderer = childElement->renderer();
    276         if (!renderer)
    277             continue;
    278 
    279         RenderStyle* style = renderer->style();
    280         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    281             continue;
    282 
    283         WindRule newClipRule = style->svgStyle()->clipRule();
    284         bool isUseElement = isSVGUseElement(*childElement);
    285         if (isUseElement) {
    286             SVGUseElement& useElement = toSVGUseElement(*childElement);
    287             renderer = useElement.rendererClipChild();
    288             if (!renderer)
    289                 continue;
    290             if (!useElement.hasAttribute(SVGNames::clip_ruleAttr))
    291                 newClipRule = renderer->style()->svgStyle()->clipRule();
    292         }
    293 
    294         // Only shapes, paths and texts are allowed for clipping.
    295         if (!renderer->isSVGShape() && !renderer->isSVGText())
    296             continue;
    297 
    298         context->setFillRule(newClipRule);
    299 
    300         if (isUseElement)
    301             renderer = childElement->renderer();
    302 
    303         SVGRenderingContext::renderSubtree(context, renderer, contentTransformation);
    304     }
    305 
    306     frame()->view()->setPaintBehavior(oldBehavior);
    307 
    308     return context->endRecording();
    309 }
    310 
    311 void RenderSVGResourceClipper::calculateClipContentRepaintRect()
    312 {
    313     // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip.
    314     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
    315         RenderObject* renderer = childElement->renderer();
    316         if (!renderer)
    317             continue;
    318         if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement))
    319             continue;
    320         RenderStyle* style = renderer->style();
    321         if (!style || style->display() == NONE || style->visibility() != VISIBLE)
    322              continue;
    323         m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->paintInvalidationRectInLocalCoordinates()));
    324     }
    325     m_clipBoundaries = toSVGClipPathElement(element())->animatedLocalTransform().mapRect(m_clipBoundaries);
    326 }
    327 
    328 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint)
    329 {
    330     FloatPoint point = nodeAtPoint;
    331     if (!SVGRenderSupport::pointInClippingArea(this, point))
    332         return false;
    333 
    334     SVGClipPathElement* clipPathElement = toSVGClipPathElement(element());
    335     if (clipPathElement->clipPathUnits()->currentValue()->enumValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    336         AffineTransform transform;
    337         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    338         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    339         point = transform.inverse().mapPoint(point);
    340     }
    341 
    342     point = clipPathElement->animatedLocalTransform().inverse().mapPoint(point);
    343 
    344     for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) {
    345         RenderObject* renderer = childElement->renderer();
    346         if (!renderer)
    347             continue;
    348         if (!renderer->isSVGShape() && !renderer->isSVGText() && !isSVGUseElement(*childElement))
    349             continue;
    350         IntPoint hitPoint;
    351         HitTestResult result(hitPoint);
    352         if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent), result, point, HitTestForeground))
    353             return true;
    354     }
    355 
    356     return false;
    357 }
    358 
    359 FloatRect RenderSVGResourceClipper::resourceBoundingBox(const RenderObject* object)
    360 {
    361     // Resource was not layouted yet. Give back the boundingBox of the object.
    362     if (selfNeedsLayout())
    363         return object->objectBoundingBox();
    364 
    365     if (m_clipBoundaries.isEmpty())
    366         calculateClipContentRepaintRect();
    367 
    368     if (toSVGClipPathElement(element())->clipPathUnits()->currentValue()->enumValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
    369         FloatRect objectBoundingBox = object->objectBoundingBox();
    370         AffineTransform transform;
    371         transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
    372         transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
    373         return transform.mapRect(m_clipBoundaries);
    374     }
    375 
    376     return m_clipBoundaries;
    377 }
    378 
    379 }
    380