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