1/* 2 Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> 3 2004, 2005, 2008 Rob Buis <buis@kde.org> 4 2005, 2007 Eric Seidel <eric@webkit.org> 5 2009 Google, Inc. 6 2009 Dirk Schulze <krit@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 45namespace WebCore { 46 47class BoundingRectStrokeStyleApplier : public StrokeStyleApplier { 48public: 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 62private: 63 const RenderObject* m_object; 64 RenderStyle* m_style; 65}; 66 67RenderPath::RenderPath(SVGStyledTransformableElement* node) 68 : RenderSVGModelObject(node) 69{ 70} 71 72const AffineTransform& RenderPath::localToParentTransform() const 73{ 74 return m_localTransform; 75} 76 77AffineTransform RenderPath::localTransform() const 78{ 79 return m_localTransform; 80} 81 82bool 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 93bool 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 105FloatRect 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 116FloatRect 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 133FloatRect 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 144FloatRect 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 176void 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 185void 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 197static 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 214void 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 255void RenderPath::addFocusRingRects(Vector<IntRect>& rects, int, int) 256{ 257 IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates()); 258 if (!rect.isEmpty()) 259 rects.append(rect); 260} 261 262bool 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 284void 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