1/* 2 * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org> 3 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org> 4 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. 5 * Copyright (C) 2011 Dirk Schulze <krit@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#if ENABLE(SVG) 26#include "RenderSVGResourceClipper.h" 27 28#include "AffineTransform.h" 29#include "FloatRect.h" 30#include "Frame.h" 31#include "FrameView.h" 32#include "GraphicsContext.h" 33#include "HitTestRequest.h" 34#include "HitTestResult.h" 35#include "ImageBuffer.h" 36#include "IntRect.h" 37#include "RenderObject.h" 38#include "RenderSVGResource.h" 39#include "RenderStyle.h" 40#include "SVGClipPathElement.h" 41#include "SVGElement.h" 42#include "SVGNames.h" 43#include "SVGRenderSupport.h" 44#include "SVGRenderingContext.h" 45#include "SVGResources.h" 46#include "SVGResourcesCache.h" 47#include "SVGStyledElement.h" 48#include "SVGStyledTransformableElement.h" 49#include "SVGUnitTypes.h" 50#include "SVGUseElement.h" 51 52namespace WebCore { 53 54RenderSVGResourceType RenderSVGResourceClipper::s_resourceType = ClipperResourceType; 55 56RenderSVGResourceClipper::RenderSVGResourceClipper(SVGClipPathElement* node) 57 : RenderSVGResourceContainer(node) 58{ 59} 60 61RenderSVGResourceClipper::~RenderSVGResourceClipper() 62{ 63 if (m_clipper.isEmpty()) 64 return; 65 66 deleteAllValues(m_clipper); 67 m_clipper.clear(); 68} 69 70void RenderSVGResourceClipper::removeAllClientsFromCache(bool markForInvalidation) 71{ 72 m_clipBoundaries = FloatRect(); 73 if (!m_clipper.isEmpty()) { 74 deleteAllValues(m_clipper); 75 m_clipper.clear(); 76 } 77 78 markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation); 79} 80 81void RenderSVGResourceClipper::removeClientFromCache(RenderObject* client, bool markForInvalidation) 82{ 83 ASSERT(client); 84 if (m_clipper.contains(client)) 85 delete m_clipper.take(client); 86 87 markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation); 88} 89 90bool RenderSVGResourceClipper::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode) 91{ 92 ASSERT(object); 93 ASSERT(context); 94 ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode); 95 96 return applyClippingToContext(object, object->objectBoundingBox(), object->repaintRectInLocalCoordinates(), context); 97} 98 99bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) 100{ 101 // If the current clip-path gets clipped itself, we have to fallback to masking. 102 if (!style()->svgStyle()->clipperResource().isEmpty()) 103 return false; 104 WindRule clipRule = RULE_NONZERO; 105 Path clipPath = Path(); 106 107 // If clip-path only contains one visible shape or path, we can use path-based clipping. Invisible 108 // shapes don't affect the clipping and can be ignored. If clip-path contains more than one 109 // visible shape, the additive clipping may not work, caused by the clipRule. EvenOdd 110 // as well as NonZero can cause self-clipping of the elements. 111 // See also http://www.w3.org/TR/SVG/painting.html#FillRuleProperty 112 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { 113 RenderObject* renderer = childNode->renderer(); 114 if (!renderer) 115 continue; 116 // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts. 117 if (renderer->isSVGText()) 118 return false; 119 if (!childNode->isSVGElement() || !toSVGElement(childNode)->isStyledTransformable()) 120 continue; 121 SVGStyledTransformableElement* styled = toSVGStyledTransformableElement(childNode); 122 RenderStyle* style = renderer->style(); 123 if (!style || style->display() == NONE || style->visibility() != VISIBLE) 124 continue; 125 const SVGRenderStyle* svgStyle = style->svgStyle(); 126 // Current shape in clip-path gets clipped too. Fallback to masking. 127 if (!svgStyle->clipperResource().isEmpty()) 128 return false; 129 // Fallback to masking, if there is more than one clipping path. 130 if (clipPath.isEmpty()) { 131 styled->toClipPath(clipPath); 132 clipRule = svgStyle->clipRule(); 133 } else 134 return false; 135 } 136 // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary. 137 if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 138 AffineTransform transform; 139 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); 140 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); 141 clipPath.transform(transform); 142 } 143 144 // Transform path by animatedLocalTransform. 145 clipPath.transform(animatedLocalTransform); 146 147 // The SVG specification wants us to clip everything, if clip-path doesn't have a child. 148 if (clipPath.isEmpty()) 149 clipPath.addRect(FloatRect()); 150 context->clipPath(clipPath, clipRule); 151 return true; 152} 153 154bool RenderSVGResourceClipper::applyClippingToContext(RenderObject* object, const FloatRect& objectBoundingBox, 155 const FloatRect& repaintRect, GraphicsContext* context) 156{ 157 bool missingClipperData = !m_clipper.contains(object); 158 if (missingClipperData) 159 m_clipper.set(object, new ClipperData); 160 161 bool shouldCreateClipData = false; 162 AffineTransform animatedLocalTransform = static_cast<SVGClipPathElement*>(node())->animatedLocalTransform(); 163 ClipperData* clipperData = m_clipper.get(object); 164 if (!clipperData->clipMaskImage) { 165 if (pathOnlyClipping(context, animatedLocalTransform, objectBoundingBox)) 166 return true; 167 shouldCreateClipData = true; 168 } 169 170 AffineTransform absoluteTransform; 171 SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(object, absoluteTransform); 172 173 if (shouldCreateClipData && !repaintRect.isEmpty()) { 174 if (!SVGRenderingContext::createImageBuffer(repaintRect, absoluteTransform, clipperData->clipMaskImage, ColorSpaceDeviceRGB, Unaccelerated)) 175 return false; 176 177 GraphicsContext* maskContext = clipperData->clipMaskImage->context(); 178 ASSERT(maskContext); 179 180 maskContext->concatCTM(animatedLocalTransform); 181 182 // clipPath can also be clipped by another clipPath. 183 SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); 184 RenderSVGResourceClipper* clipper; 185 bool succeeded; 186 if (resources && (clipper = resources->clipper())) { 187 GraphicsContextStateSaver stateSaver(*maskContext); 188 189 if (!clipper->applyClippingToContext(this, objectBoundingBox, repaintRect, maskContext)) 190 return false; 191 192 succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox); 193 // The context restore applies the clipping on non-CG platforms. 194 } else 195 succeeded = drawContentIntoMaskImage(clipperData, objectBoundingBox); 196 197 if (!succeeded) 198 clipperData->clipMaskImage.clear(); 199 } 200 201 if (!clipperData->clipMaskImage) 202 return false; 203 204 SVGRenderingContext::clipToImageBuffer(context, absoluteTransform, repaintRect, clipperData->clipMaskImage, missingClipperData); 205 return true; 206} 207 208bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox) 209{ 210 ASSERT(frame()); 211 ASSERT(clipperData); 212 ASSERT(clipperData->clipMaskImage); 213 214 GraphicsContext* maskContext = clipperData->clipMaskImage->context(); 215 ASSERT(maskContext); 216 217 AffineTransform maskContentTransformation; 218 SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node()); 219 if (clipPath->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 220 maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); 221 maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); 222 maskContext->concatCTM(maskContentTransformation); 223 } 224 225 // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints: 226 // - fill-opacity/stroke-opacity/opacity set to 1 227 // - masker/filter not applied when rendering the children 228 // - fill is set to the initial fill paint server (solid, black) 229 // - stroke is set to the initial stroke paint server (none) 230 PaintBehavior oldBehavior = frame()->view()->paintBehavior(); 231 frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask); 232 233 // Draw all clipPath children into a global mask. 234 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { 235 RenderObject* renderer = childNode->renderer(); 236 if (!childNode->isSVGElement() || !toSVGElement(childNode)->isSVGStyledElement() || !renderer) 237 continue; 238 if (renderer->needsLayout()) { 239 frame()->view()->setPaintBehavior(oldBehavior); 240 return false; 241 } 242 RenderStyle* style = renderer->style(); 243 if (!style || style->display() == NONE || style->visibility() != VISIBLE) 244 continue; 245 246 WindRule newClipRule = style->svgStyle()->clipRule(); 247 bool isUseElement = childNode->hasTagName(SVGNames::useTag); 248 if (isUseElement) { 249 SVGUseElement* useElement = static_cast<SVGUseElement*>(childNode); 250 renderer = useElement->rendererClipChild(); 251 if (!renderer) 252 continue; 253 if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) 254 newClipRule = renderer->style()->svgStyle()->clipRule(); 255 } 256 257 // Only shapes, paths and texts are allowed for clipping. 258 if (!renderer->isSVGShape() && !renderer->isSVGText()) 259 continue; 260 261 maskContext->setFillRule(newClipRule); 262 263 // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. 264 // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering. 265 // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. 266 SVGRenderingContext::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation); 267 } 268 269 frame()->view()->setPaintBehavior(oldBehavior); 270 return true; 271} 272 273void RenderSVGResourceClipper::calculateClipContentRepaintRect() 274{ 275 // This is a rough heuristic to appraise the clip size and doesn't consider clip on clip. 276 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { 277 RenderObject* renderer = childNode->renderer(); 278 if (!childNode->isSVGElement() || !toSVGElement(childNode)->isSVGStyledElement() || !renderer) 279 continue; 280 if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag)) 281 continue; 282 RenderStyle* style = renderer->style(); 283 if (!style || style->display() == NONE || style->visibility() != VISIBLE) 284 continue; 285 m_clipBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates())); 286 } 287 m_clipBoundaries = static_cast<SVGClipPathElement*>(node())->animatedLocalTransform().mapRect(m_clipBoundaries); 288} 289 290bool RenderSVGResourceClipper::hitTestClipContent(const FloatRect& objectBoundingBox, const FloatPoint& nodeAtPoint) 291{ 292 FloatPoint point = nodeAtPoint; 293 if (!SVGRenderSupport::pointInClippingArea(this, point)) 294 return false; 295 296 SVGClipPathElement* clipPathElement = static_cast<SVGClipPathElement*>(node()); 297 if (clipPathElement->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 298 AffineTransform transform; 299 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); 300 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); 301 point = transform.inverse().mapPoint(point); 302 } 303 304 point = clipPathElement->animatedLocalTransform().inverse().mapPoint(point); 305 306 for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { 307 RenderObject* renderer = childNode->renderer(); 308 if (!childNode->isSVGElement() || !toSVGElement(childNode)->isSVGStyledElement() || !renderer) 309 continue; 310 if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag)) 311 continue; 312 IntPoint hitPoint; 313 HitTestResult result(hitPoint); 314 if (renderer->nodeAtFloatPoint(HitTestRequest(HitTestRequest::SVGClipContent | HitTestRequest::DisallowShadowContent), result, point, HitTestForeground)) 315 return true; 316 } 317 318 return false; 319} 320 321FloatRect RenderSVGResourceClipper::resourceBoundingBox(RenderObject* object) 322{ 323 // Resource was not layouted yet. Give back the boundingBox of the object. 324 if (selfNeedsLayout()) 325 return object->objectBoundingBox(); 326 327 if (m_clipBoundaries.isEmpty()) 328 calculateClipContentRepaintRect(); 329 330 if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 331 FloatRect objectBoundingBox = object->objectBoundingBox(); 332 AffineTransform transform; 333 transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); 334 transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); 335 return transform.mapRect(m_clipBoundaries); 336 } 337 338 return m_clipBoundaries; 339} 340 341} 342 343#endif // ENABLE(SVG) 344