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/page/FrameView.h" 30 #include "core/platform/graphics/GraphicsContextStateSaver.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 37 namespace WebCore { 38 39 RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType; 40 41 RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node) 42 : RenderSVGResourceContainer(node) 43 { 44 } 45 46 RenderSVGResourceClipper::~RenderSVGResourceClipper() 47 { 48 if (m_clipper.isEmpty()) 49 return; 50 51 deleteAllValues(m_clipper); 52 m_clipper.clear(); 53 } 54 55 void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation) 56 { 57 m_clipBoundaries = FloatRect(); 58 if (!m_clipper.isEmpty()) { 59 deleteAllValues(m_clipper); 60 m_clipper.clear(); 61 } 62 63 markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation); 64 } 65 66 void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation) 67 { 68 ASSERT(client); 69 if (m_clipper.contains(client)) 70 delete m_clipper.take(client); 71 72 markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation); 73 } 74 75 bool RenderSVGResourceClipper::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode) 76 { 77 ASSERT(object); 78 ASSERT(context); 79 ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode); 80 81 return applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context); 82 } 83 84 bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) 85 { 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 = node()->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 (static_cast<SVGClipPathElement*>(node())->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* object, const FloatRect& objectBoundingBox, 148 const FloatRect& repaintRect, GraphicsContext* context) 149 { 150 bool missingClipperData = !m_clipper.contains(object); 151 if (missingClipperData) 152 m_clipper.set(object, new ClipperData); 153 154 bool shouldCreateClipData = false; 155 AffineTransform animatedLocalTransform = static_cast<SVGClipPathElement*>(node())->animatedLocalTransform(); 156 ClipperData* clipperData = m_clipper.get(object); 157 if (!clipperData->clipMaskImage) { 158 if (pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox)) 159 return true; 160 shouldCreateClipData = true; 161 } 162 163 AffineTransform absoluteTransform; 164 SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(object, absoluteTransform); 165 166 if (shouldCreateClipData && !repaintRect.isEmpty()) { 167 if (!SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, clipperData->clipMaskImage, Unaccelerated)) 168 return false; 169 170 GraphicsContext* maskContext = clipperData->clipMaskImage->context(); 171 ASSERT(maskContext); 172 173 maskContext->concatCTM(animatedLocalTransform); 174 175 // clipPath can also be clipped by another clipPath. 176 SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); 177 RenderSVGResourceClipper* clipper; 178 bool succeeded; 179 if (resources && (clipper = resources->clipper())) { 180 GraphicsContextStateSaver stateSaver(*maskContext); 181 182 if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext)) 183 return false; 184 185 succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox); 186 // The context restore applies the clipping on non-CG platforms. 187 } else 188 succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox); 189 190 if (!succeeded) 191 clipperData->clipMaskImage.clear(); 192 } 193 194 if (!clipperData->clipMaskImage) 195 return false; 196 197 SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperData->clipMaskImage, missingClipperData); 198 return true; 199 } 200 201 bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox) 202 { 203 ASSERT(frame()); 204 ASSERT(clipperData); 205 ASSERT(clipperData->clipMaskImage); 206 207 GraphicsContext* maskContext = clipperData->clipMaskImage->context(); 208 ASSERT(maskContext); 209 210 AffineTransform maskContentTransformation; 211 SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node()); 212 if (clipPath->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 213 maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); 214 maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); 215 maskContext->concatCTM(maskContentTransformation); 216 } 217 218 // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints: 219 // - fill-opacity/stroke-opacity/opacity set to 1 220 // - masker/filter not applied when rendering the children 221 // - fill is set to the initial fill paint server (solid, black) 222 // - stroke is set to the initial stroke paint server (none) 223 PaintBehavior oldBehavior = frame()->view()->paintBehavior(); 224 frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask); 225 226 // Draw all clipPath children into a global mask. 227 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { 228 RenderObject* renderer = childNode->renderer(); 229 if (!childNode->isSVGElement() || !renderer) 230 continue; 231 if (renderer->needsLayout()) { 232 frame()->view()->setPaintBehavior(oldBehavior); 233 return false; 234 } 235 RenderStyle* style = renderer->style(); 236 if (!style || style->display() == NONE || style->visibility() != VISIBLE) 237 continue; 238 239 WindRule newClipRule = style->svgStyle()->clipRule(); 240 bool isUseElement = childNode->hasTagName(SVGNames::useTag); 241 if (isUseElement) { 242 SVGUseElement* useElement = toSVGUseElement(childNode); 243 renderer = useElement->rendererClipChild(); 244 if (!renderer) 245 continue; 246 if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) 247 newClipRule = renderer->style()->svgStyle()->clipRule(); 248 } 249 250 // Only shapes, paths and texts are allowed for clipping. 251 if (!renderer->isSVGShape() && !renderer->isSVGText()) 252 continue; 253 254 maskContext->setFillRule(newClipRule); 255 256 // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. 257 // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering. 258 // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. 259 SVGRenderingContext::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation); 260 } 261 262 frame()->view()->setPaintBehavior(oldBehavior); 263 return true; 264 } 265 266 void RenderSVGResourceClipper::calculateClipContentRepaintRect() 267 { 268 // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip. 269 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { 270 RenderObject* renderer = childNode->renderer(); 271 if (!childNode->isSVGElement() || !renderer) 272 continue; 273 if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag)) 274 continue; 275 RenderStyle* style = renderer->style(); 276 if (!style || style->display() == NONE || style->visibility() != VISIBLE) 277 continue; 278 m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates())); 279 } 280 m_clipBoundaries = static_cast<SVGClipPathElement*>(node())->animatedLocalTransform().mapRect(m_clipBoundaries); 281 } 282 283 bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint) 284 { 285 FloatPoint point = nodeAtPoint; 286 if (!SVGRenderSupport::pointInClippingArea(this, point)) 287 return false; 288 289 SVGClipPathElement* clipPathElement = static_cast<SVGClipPathElement*>(node()); 290 if (clipPathElement->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 291 AffineTransform transform; 292 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); 293 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); 294 point = transform.inverse().mapPoint(point); 295 } 296 297 point = clipPathElement->animatedLocalTransform().inverse().mapPoint(point); 298 299 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { 300 RenderObject* renderer = childNode->renderer(); 301 if (!childNode->isSVGElement() || !renderer) 302 continue; 303 if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag)) 304 continue; 305 IntPoint hitPoint; 306 HitTestResult result(hitPoint); 307 if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent | HitTestRequest::DisallowShadowContent), result, point, HitTestForeground)) 308 return true; 309 } 310 311 return false; 312 } 313 314 FloatRect RenderSVGResourceClipper::resourceBoundingBox(RenderObject* object) 315 { 316 // Resource was not layouted yet. Give back the boundingBox of the object. 317 if (selfNeedsLayout()) 318 return object->objectBoundingBox(); 319 320 if (m_clipBoundaries.isEmpty()) 321 calculateClipContentRepaintRect(); 322 323 if (static_cast<SVGClipPathElement*>(node())->clipPathUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 324 FloatRect objectBoundingBox = object->objectBoundingBox(); 325 AffineTransform transform; 326 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); 327 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); 328 return transform.mapRect(m_clipBoundaries); 329 } 330 331 return m_clipBoundaries; 332 } 333 334 } 335