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