1 /* 2 Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann (at) kde.org> 3 2004, 2005, 2008 Rob Buis <buis (at) kde.org> 4 2005, 2007 Eric Seidel <eric (at) webkit.org> 5 2009 Google, Inc. 6 2009 Dirk Schulze <krit (at) webkit.org> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Library General Public 10 License as published by the Free Software Foundation; either 11 version 2 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Library General Public License for more details. 17 18 You should have received a copy of the GNU Library General Public License 19 aint with this library; see the file COPYING.LIB. If not, write to 20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 Boston, MA 02110-1301, USA. 22 */ 23 24 #include "config.h" 25 26 #if ENABLE(SVG) 27 #include "RenderPath.h" 28 29 #include "FloatPoint.h" 30 #include "FloatQuad.h" 31 #include "GraphicsContext.h" 32 #include "PointerEventsHitRules.h" 33 #include "RenderSVGContainer.h" 34 #include "StrokeStyleApplier.h" 35 #include "SVGPaintServer.h" 36 #include "SVGRenderSupport.h" 37 #include "SVGResourceFilter.h" 38 #include "SVGResourceMarker.h" 39 #include "SVGResourceMasker.h" 40 #include "SVGStyledTransformableElement.h" 41 #include "SVGTransformList.h" 42 #include "SVGURIReference.h" 43 #include <wtf/MathExtras.h> 44 45 namespace WebCore { 46 47 class BoundingRectStrokeStyleApplier : public StrokeStyleApplier { 48 public: 49 BoundingRectStrokeStyleApplier(const RenderObject* object, RenderStyle* style) 50 : m_object(object) 51 , m_style(style) 52 { 53 ASSERT(style); 54 ASSERT(object); 55 } 56 57 void strokeStyle(GraphicsContext* gc) 58 { 59 applyStrokeStyleToContext(gc, m_style, m_object); 60 } 61 62 private: 63 const RenderObject* m_object; 64 RenderStyle* m_style; 65 }; 66 67 RenderPath::RenderPath(SVGStyledTransformableElement* node) 68 : RenderSVGModelObject(node) 69 { 70 } 71 72 const AffineTransform& RenderPath::localToParentTransform() const 73 { 74 return m_localTransform; 75 } 76 77 AffineTransform RenderPath::localTransform() const 78 { 79 return m_localTransform; 80 } 81 82 bool RenderPath::fillContains(const FloatPoint& point, bool requiresFill) const 83 { 84 if (m_path.isEmpty()) 85 return false; 86 87 if (requiresFill && !SVGPaintServer::fillPaintServer(style(), this)) 88 return false; 89 90 return m_path.contains(point, style()->svgStyle()->fillRule()); 91 } 92 93 bool RenderPath::strokeContains(const FloatPoint& point, bool requiresStroke) const 94 { 95 if (m_path.isEmpty()) 96 return false; 97 98 if (requiresStroke && !SVGPaintServer::strokePaintServer(style(), this)) 99 return false; 100 101 BoundingRectStrokeStyleApplier strokeStyle(this, style()); 102 return m_path.strokeContains(&strokeStyle, point); 103 } 104 105 FloatRect RenderPath::objectBoundingBox() const 106 { 107 if (m_path.isEmpty()) 108 return FloatRect(); 109 110 if (m_cachedLocalFillBBox.isEmpty()) 111 m_cachedLocalFillBBox = m_path.boundingRect(); 112 113 return m_cachedLocalFillBBox; 114 } 115 116 FloatRect RenderPath::strokeBoundingBox() const 117 { 118 if (m_path.isEmpty()) 119 return FloatRect(); 120 121 if (!m_cachedLocalStrokeBBox.isEmpty()) 122 return m_cachedLocalStrokeBBox; 123 124 m_cachedLocalStrokeBBox = objectBoundingBox(); 125 if (style()->svgStyle()->hasStroke()) { 126 BoundingRectStrokeStyleApplier strokeStyle(this, style()); 127 m_cachedLocalStrokeBBox.unite(m_path.strokeBoundingRect(&strokeStyle)); 128 } 129 130 return m_cachedLocalStrokeBBox; 131 } 132 133 FloatRect RenderPath::markerBoundingBox() const 134 { 135 if (m_path.isEmpty()) 136 return FloatRect(); 137 138 if (m_cachedLocalMarkerBBox.isEmpty()) 139 calculateMarkerBoundsIfNeeded(); 140 141 return m_cachedLocalMarkerBBox; 142 } 143 144 FloatRect RenderPath::repaintRectInLocalCoordinates() const 145 { 146 if (m_path.isEmpty()) 147 return FloatRect(); 148 149 // If we already have a cached repaint rect, return that 150 if (!m_cachedLocalRepaintRect.isEmpty()) 151 return m_cachedLocalRepaintRect; 152 153 // FIXME: We need to be careful here. We assume that there is no filter, 154 // clipper, marker or masker if the rects are empty. 155 FloatRect rect = filterBoundingBoxForRenderer(this); 156 if (!rect.isEmpty()) 157 m_cachedLocalRepaintRect = rect; 158 else { 159 m_cachedLocalRepaintRect = strokeBoundingBox(); 160 m_cachedLocalRepaintRect.unite(markerBoundingBox()); 161 } 162 163 rect = clipperBoundingBoxForRenderer(this); 164 if (!rect.isEmpty()) 165 m_cachedLocalRepaintRect.intersect(rect); 166 167 rect = maskerBoundingBoxForRenderer(this); 168 if (!rect.isEmpty()) 169 m_cachedLocalRepaintRect.intersect(rect); 170 171 style()->svgStyle()->inflateForShadow(m_cachedLocalRepaintRect); 172 173 return m_cachedLocalRepaintRect; 174 } 175 176 void RenderPath::setPath(const Path& newPath) 177 { 178 m_path = newPath; 179 m_cachedLocalRepaintRect = FloatRect(); 180 m_cachedLocalStrokeBBox = FloatRect(); 181 m_cachedLocalFillBBox = FloatRect(); 182 m_cachedLocalMarkerBBox = FloatRect(); 183 } 184 185 void RenderPath::layout() 186 { 187 LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout()); 188 189 SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); 190 m_localTransform = element->animatedLocalTransform(); 191 setPath(element->toPathData()); 192 193 repainter.repaintAfterLayout(); 194 setNeedsLayout(false); 195 } 196 197 static inline void fillAndStrokePath(const Path& path, GraphicsContext* context, RenderStyle* style, RenderPath* object) 198 { 199 context->beginPath(); 200 201 SVGPaintServer* fillPaintServer = SVGPaintServer::fillPaintServer(style, object); 202 if (fillPaintServer) { 203 context->addPath(path); 204 fillPaintServer->draw(context, object, ApplyToFillTargetType); 205 } 206 207 SVGPaintServer* strokePaintServer = SVGPaintServer::strokePaintServer(style, object); 208 if (strokePaintServer) { 209 context->addPath(path); // path is cleared when filled. 210 strokePaintServer->draw(context, object, ApplyToStrokeTargetType); 211 } 212 } 213 214 void RenderPath::paint(PaintInfo& paintInfo, int, int) 215 { 216 if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || m_path.isEmpty()) 217 return; 218 219 FloatRect boundingBox = repaintRectInLocalCoordinates(); 220 FloatRect nonLocalBoundingBox = m_localTransform.mapRect(boundingBox); 221 // FIXME: The empty rect check is to deal with incorrect initial clip in renderSubtreeToImage 222 // unfortunately fixing that problem is fairly complex unless we were willing to just futz the 223 // rect to something "close enough" 224 if (!nonLocalBoundingBox.intersects(paintInfo.rect) && !paintInfo.rect.isEmpty()) 225 return; 226 227 PaintInfo childPaintInfo(paintInfo); 228 childPaintInfo.context->save(); 229 applyTransformToPaintInfo(childPaintInfo, m_localTransform); 230 SVGResourceFilter* filter = 0; 231 232 if (childPaintInfo.phase == PaintPhaseForeground) { 233 PaintInfo savedInfo(childPaintInfo); 234 235 if (prepareToRenderSVGContent(this, childPaintInfo, boundingBox, filter)) { 236 if (style()->svgStyle()->shapeRendering() == SR_CRISPEDGES) 237 childPaintInfo.context->setShouldAntialias(false); 238 fillAndStrokePath(m_path, childPaintInfo.context, style(), this); 239 240 if (static_cast<SVGStyledElement*>(node())->supportsMarkers()) 241 m_markerLayoutInfo.drawMarkers(childPaintInfo); 242 } 243 finishRenderSVGContent(this, childPaintInfo, filter, savedInfo.context); 244 } 245 246 if ((childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) 247 paintOutline(childPaintInfo.context, static_cast<int>(boundingBox.x()), static_cast<int>(boundingBox.y()), 248 static_cast<int>(boundingBox.width()), static_cast<int>(boundingBox.height()), style()); 249 250 childPaintInfo.context->restore(); 251 } 252 253 // This method is called from inside paintOutline() since we call paintOutline() 254 // while transformed to our coord system, return local coords 255 void RenderPath::addFocusRingRects(Vector<IntRect>& rects, int, int) 256 { 257 IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates()); 258 if (!rect.isEmpty()) 259 rects.append(rect); 260 } 261 262 bool RenderPath::nodeAtFloatPoint(const HitTestRequest&, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) 263 { 264 // We only draw in the forground phase, so we only hit-test then. 265 if (hitTestAction != HitTestForeground) 266 return false; 267 268 FloatPoint localPoint = m_localTransform.inverse().mapPoint(pointInParent); 269 270 PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, style()->pointerEvents()); 271 272 bool isVisible = (style()->visibility() == VISIBLE); 273 if (isVisible || !hitRules.requireVisible) { 274 if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke)) 275 || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill))) { 276 updateHitTestResult(result, roundedIntPoint(localPoint)); 277 return true; 278 } 279 } 280 281 return false; 282 } 283 284 void RenderPath::calculateMarkerBoundsIfNeeded() const 285 { 286 Document* doc = document(); 287 288 SVGElement* svgElement = static_cast<SVGElement*>(node()); 289 ASSERT(svgElement && svgElement->document()); 290 if (!svgElement->isStyled()) 291 return; 292 293 SVGStyledElement* styledElement = static_cast<SVGStyledElement*>(svgElement); 294 if (!styledElement->supportsMarkers()) 295 return; 296 297 const SVGRenderStyle* svgStyle = style()->svgStyle(); 298 AtomicString startMarkerId(svgStyle->startMarker()); 299 AtomicString midMarkerId(svgStyle->midMarker()); 300 AtomicString endMarkerId(svgStyle->endMarker()); 301 302 SVGResourceMarker* startMarker = getMarkerById(doc, startMarkerId, this); 303 SVGResourceMarker* midMarker = getMarkerById(doc, midMarkerId, this); 304 SVGResourceMarker* endMarker = getMarkerById(doc, endMarkerId, this); 305 306 if (!startMarker && !startMarkerId.isEmpty()) 307 svgElement->document()->accessSVGExtensions()->addPendingResource(startMarkerId, styledElement); 308 else if (startMarker) 309 startMarker->addClient(styledElement); 310 311 if (!midMarker && !midMarkerId.isEmpty()) 312 svgElement->document()->accessSVGExtensions()->addPendingResource(midMarkerId, styledElement); 313 else if (midMarker) 314 midMarker->addClient(styledElement); 315 316 if (!endMarker && !endMarkerId.isEmpty()) 317 svgElement->document()->accessSVGExtensions()->addPendingResource(endMarkerId, styledElement); 318 else if (endMarker) 319 endMarker->addClient(styledElement); 320 321 if (!startMarker && !midMarker && !endMarker) 322 return; 323 324 float strokeWidth = SVGRenderStyle::cssPrimitiveToLength(this, svgStyle->strokeWidth(), 1.0f); 325 m_cachedLocalMarkerBBox = m_markerLayoutInfo.calculateBoundaries(startMarker, midMarker, endMarker, strokeWidth, m_path); 326 } 327 328 } 329 330 #endif // ENABLE(SVG) 331