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