Reviewed by eseidel.
[WebKit-https.git] / WebCore / kcanvas / RenderPath.cpp
1 /*
2     Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3                   2004, 2005 Rob Buis <buis@kde.org>
4                   2005 Eric Seidel <eric.seidel@kdemail.net>
5
6     This file is part of the KDE project
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., 59 Temple Place - Suite 330,
21     Boston, MA 02111-1307, USA.
22 */
23
24 #include "config.h"
25 #ifdef SVG_SUPPORT
26 #include "RenderPath.h"
27
28 #include <math.h>
29
30 #include "GraphicsContext.h"
31 #include "RenderSVGContainer.h"
32 #include "KCanvasClipper.h"
33 #include "KCanvasMasker.h"
34 #include "KCanvasMarker.h"
35 #include "KRenderingDevice.h"
36 #include "KRenderingFillPainter.h"
37 #include "KRenderingStrokePainter.h"
38 #include "SVGStyledElement.h"
39 #include <wtf/OwnPtr.h>
40
41 namespace WebCore {
42
43 // RenderPath
44 RenderPath::RenderPath(RenderStyle* style, SVGStyledElement* node)
45     : RenderObject(node)
46 {
47     ASSERT(style != 0);
48 }
49
50 RenderPath::~RenderPath()
51 {
52 }
53
54 AffineTransform RenderPath::localTransform() const
55 {
56     return m_matrix;
57 }
58
59 void RenderPath::setLocalTransform(const AffineTransform &matrix)
60 {
61     m_matrix = matrix;
62 }
63
64
65 FloatPoint RenderPath::mapAbsolutePointToLocal(const FloatPoint& point) const
66 {
67     // FIXME: does it make sense to map incoming points with the inverse of the
68     // absolute transform? 
69     double localX;
70     double localY;
71     absoluteTransform().invert().map(point.x(), point.y(), &localX, &localY);
72     return FloatPoint(localX, localY);
73 }
74
75 bool RenderPath::fillContains(const FloatPoint& point, bool requiresFill) const
76 {
77     if (m_path.isEmpty())
78         return false;
79
80     if (requiresFill && !KSVGPainterFactory::fillPaintServer(style(), this))
81         return false;
82
83     return path().contains(mapAbsolutePointToLocal(point),
84                            KSVGPainterFactory::fillPainter(style(), this).fillRule());
85 }
86
87 FloatRect RenderPath::relativeBBox(bool includeStroke) const
88 {
89     if (m_path.isEmpty())
90         return FloatRect();
91
92     if (includeStroke) {
93         if (m_strokeBbox.isEmpty())
94             m_strokeBbox = strokeBBox();
95         return m_strokeBbox;
96     }
97
98     if (m_fillBBox.isEmpty())
99         m_fillBBox = path().boundingRect();
100     return m_fillBBox;
101 }
102
103 void RenderPath::setPath(const Path& newPath)
104 {
105     m_path = newPath;
106     m_strokeBbox = FloatRect();
107     m_fillBBox = FloatRect();
108 }
109
110 const Path& RenderPath::path() const
111 {
112     return m_path;
113 }
114
115 void RenderPath::layout()
116 {
117     // FIXME: Currently the DOM does all of the % length calculations, so we
118     // pretend that one of the attributes of the element has changed on the DOM
119     // to force the DOM object to update this render object with new aboslute position values.
120
121     IntRect oldBounds;
122     bool checkForRepaint = checkForRepaintDuringLayout();
123     if (selfNeedsLayout() && checkForRepaint)
124         oldBounds = m_absoluteBounds;
125
126     static_cast<SVGStyledElement*>(element())->notifyAttributeChange();
127
128     m_absoluteBounds = getAbsoluteRepaintRect();
129
130     setWidth(m_absoluteBounds.width());
131     setHeight(m_absoluteBounds.height());
132
133     if (selfNeedsLayout() && checkForRepaint)
134         repaintAfterLayoutIfNeeded(oldBounds, oldBounds);
135         
136     setNeedsLayout(false);
137 }
138
139 IntRect RenderPath::getAbsoluteRepaintRect()
140 {
141     FloatRect repaintRect = absoluteTransform().mapRect(relativeBBox(true));
142     
143     // Filters can expand the bounding box
144     KCanvasFilter* filter = getFilterById(document(), style()->svgStyle()->filter().substring(1));
145     if (filter)
146         repaintRect.unite(filter->filterBBoxForItemBBox(repaintRect));
147     
148     if (!repaintRect.isEmpty())
149         repaintRect.inflate(1); // inflate 1 pixel for antialiasing
150     return enclosingIntRect(repaintRect);
151 }
152
153 bool RenderPath::requiresLayer()
154 {
155     return false;
156 }
157
158 short RenderPath::lineHeight(bool b, bool isRootLineBox) const
159 {
160     return static_cast<short>(relativeBBox(true).height());
161 }
162
163 short RenderPath::baselinePosition(bool b, bool isRootLineBox) const
164 {
165     return static_cast<short>(relativeBBox(true).height());
166 }
167
168 void RenderPath::paint(PaintInfo &paintInfo, int parentX, int parentY)
169 {
170     // No one should be transforming us via these.
171     //ASSERT(parentX == 0);
172     //ASSERT(parentY == 0);
173
174     if (paintInfo.p->paintingDisabled() || (paintInfo.phase != PaintPhaseForeground) || style()->visibility() == HIDDEN || path().isEmpty())
175         return;
176     
177     KRenderingDevice* device = renderingDevice();
178     KRenderingDeviceContext* context = device->currentContext();
179     bool shouldPopContext = false;
180     if (context)
181         paintInfo.p->save();
182     else {
183         // Need to set up KCanvas rendering if it hasn't already been done.
184         context = paintInfo.p->createRenderingDeviceContext();
185         device->pushContext(context);
186         shouldPopContext = true;
187     }
188
189     context->concatCTM(localTransform());
190
191     // setup to apply filters
192     KCanvasFilter* filter = getFilterById(document(), style()->svgStyle()->filter().substring(1));
193     if (filter) {
194         filter->prepareFilter(relativeBBox(true));
195         context = device->currentContext();
196     }
197
198     if (KCanvasClipper* clipper = getClipperById(document(), style()->svgStyle()->clipPath().substring(1)))
199         clipper->applyClip(relativeBBox(true));
200
201     if (KCanvasMasker* masker = getMaskerById(document(), style()->svgStyle()->maskElement().substring(1)))
202         masker->applyMask(relativeBBox(true));
203
204     context->clearPath();
205     
206     KRenderingPaintServer *fillPaintServer = KSVGPainterFactory::fillPaintServer(style(), this);
207     if (fillPaintServer) {
208         context->addPath(path());
209         fillPaintServer->setActiveClient(this);
210         fillPaintServer->draw(context, this, APPLY_TO_FILL);
211     }
212     KRenderingPaintServer *strokePaintServer = KSVGPainterFactory::strokePaintServer(style(), this);
213     if (strokePaintServer) {
214         context->addPath(path()); // path is cleared when filled.
215         strokePaintServer->setActiveClient(this);
216         strokePaintServer->draw(context, this, APPLY_TO_STROKE);
217     }
218
219     drawMarkersIfNeeded(paintInfo.p, paintInfo.r, path());
220
221     // actually apply the filter
222     if (filter)
223         filter->applyFilter(relativeBBox(true));
224
225     // restore drawing state
226     if (!shouldPopContext)
227         paintInfo.p->restore();
228     else {
229         device->popContext();
230         delete context;
231     }
232 }
233
234 void RenderPath::absoluteRects(Vector<IntRect>& rects, int _tx, int _ty)
235 {
236     rects.append(getAbsoluteRepaintRect());
237 }
238
239 RenderPath::PointerEventsHitRules RenderPath::pointerEventsHitRules()
240 {
241     PointerEventsHitRules hitRules;
242     
243     switch (style()->svgStyle()->pointerEvents())
244     {
245         case PE_VISIBLE_PAINTED:
246             hitRules.requireVisible = true;
247             hitRules.requireFill = true;
248             hitRules.requireStroke = true;
249             hitRules.canHitFill = true;
250             hitRules.canHitStroke = true;
251             break;
252         case PE_VISIBLE_FILL:
253             hitRules.requireVisible = true;
254             hitRules.canHitFill = true;
255             break;
256         case PE_VISIBLE_STROKE:
257             hitRules.requireVisible = true;
258             hitRules.canHitStroke = true;
259             break;
260         case PE_VISIBLE:
261             hitRules.requireVisible = true;
262             hitRules.canHitFill = true;
263             hitRules.canHitStroke = true;
264             break;
265         case PE_PAINTED:
266             hitRules.requireFill = true;
267             hitRules.requireStroke = true;
268             hitRules.canHitFill = true;
269             hitRules.canHitStroke = true;
270             break;
271         case PE_FILL:
272             hitRules.canHitFill = true;
273             break;
274         case PE_STROKE:
275             hitRules.canHitStroke = true;
276             break;
277         case PE_ALL:
278             hitRules.canHitFill = true;
279             hitRules.canHitStroke = true;
280             break;
281         case PE_NONE:
282             // nothing to do here, defaults are all false.
283             break;
284     }
285     return hitRules;
286 }
287
288 bool RenderPath::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
289 {
290     // We only draw in the forground phase, so we only hit-test then.
291     if (hitTestAction != HitTestForeground)
292         return false;
293     PointerEventsHitRules hitRules = pointerEventsHitRules();
294     
295     bool isVisible = (style()->visibility() == VISIBLE);
296     if (isVisible || !hitRules.requireVisible) {
297         bool hasFill = (style()->svgStyle()->fillPaint() && style()->svgStyle()->fillPaint()->paintType() != SVGPaint::SVG_PAINTTYPE_NONE);
298         bool hasStroke = (style()->svgStyle()->strokePaint() && style()->svgStyle()->strokePaint()->paintType() != SVGPaint::SVG_PAINTTYPE_NONE);
299         FloatPoint hitPoint(_x,_y);
300         if ((hitRules.canHitStroke && (hasStroke || !hitRules.requireStroke) && strokeContains(hitPoint, hitRules.requireStroke))
301             || (hitRules.canHitFill && (hasFill || !hitRules.requireFill) && fillContains(hitPoint, hitRules.requireFill))) {
302             setInnerNode(info);
303             return true;
304         }
305     }
306     return false;
307 }
308
309 enum MarkerType {
310     Start,
311     Mid,
312     End
313 };
314
315 struct MarkerData {
316     FloatPoint origin;
317     FloatPoint subpathStart;
318     double strokeWidth;
319     FloatPoint inslopePoints[2];
320     FloatPoint outslopePoints[2];
321     MarkerType type;
322     KCanvasMarker *marker;
323 };
324
325 struct DrawMarkersData {
326     DrawMarkersData(GraphicsContext*, KCanvasMarker* startMarker, KCanvasMarker* midMarker, double strokeWidth);
327     GraphicsContext* context;
328     int elementIndex;
329     MarkerData previousMarkerData;
330     KCanvasMarker* midMarker;
331 };
332
333 DrawMarkersData::DrawMarkersData(GraphicsContext* c, KCanvasMarker *start, KCanvasMarker *mid, double strokeWidth)
334     : context(c)
335     , elementIndex(0)
336     , midMarker(mid)
337 {
338     previousMarkerData.origin = FloatPoint();
339     previousMarkerData.subpathStart = FloatPoint();
340     previousMarkerData.strokeWidth = strokeWidth;
341     previousMarkerData.marker = start;
342     previousMarkerData.type = Start;
343 }
344
345 static void drawMarkerWithData(GraphicsContext* context, MarkerData &data)
346 {
347     if (!data.marker)
348         return;
349     
350     FloatPoint inslopeChange = data.inslopePoints[1] - FloatSize(data.inslopePoints[0].x(), data.inslopePoints[0].y());
351     FloatPoint outslopeChange = data.outslopePoints[1] - FloatSize(data.outslopePoints[0].x(), data.outslopePoints[0].y());
352     
353     static const double deg2rad = M_PI/180.0;
354     double inslope = atan2(inslopeChange.y(), inslopeChange.x()) / deg2rad;
355     double outslope = atan2(outslopeChange.y(), outslopeChange.x()) / deg2rad;
356     
357     double angle = 0.0;
358     switch (data.type) {
359         case Start:
360             angle = outslope;
361             break;
362         case Mid:
363             angle = (inslope + outslope) / 2;
364             break;
365         case End:
366             angle = inslope;
367     }
368     
369     data.marker->draw(context, FloatRect(), data.origin.x(), data.origin.y(), data.strokeWidth, angle);
370 }
371
372 static inline void updateMarkerDataForElement(MarkerData &previousMarkerData, const PathElement *element)
373 {
374     FloatPoint *points = element->points;
375     
376     switch (element->type) {
377     case PathElementAddQuadCurveToPoint:
378         // TODO
379         previousMarkerData.origin = points[1];
380         break;
381     case PathElementAddCurveToPoint:
382         previousMarkerData.inslopePoints[0] = points[1];
383         previousMarkerData.inslopePoints[1] = points[2];
384         previousMarkerData.origin = points[2];
385         break;
386     case PathElementMoveToPoint:
387         previousMarkerData.subpathStart = points[0];
388     case PathElementAddLineToPoint:
389         previousMarkerData.inslopePoints[0] = previousMarkerData.origin;
390         previousMarkerData.inslopePoints[1] = points[0];
391         previousMarkerData.origin = points[0];
392         break;
393     case PathElementCloseSubpath:
394         previousMarkerData.inslopePoints[0] = previousMarkerData.origin;
395         previousMarkerData.inslopePoints[1] = points[0];
396         previousMarkerData.origin = previousMarkerData.subpathStart;
397         previousMarkerData.subpathStart = FloatPoint();
398     }
399 }
400
401 static void drawStartAndMidMarkers(void *info, const PathElement *element)
402 {
403     DrawMarkersData& data = *reinterpret_cast<DrawMarkersData*>(info);
404
405     int elementIndex = data.elementIndex;
406     MarkerData &previousMarkerData = data.previousMarkerData;
407
408     FloatPoint *points = element->points;
409
410     // First update the outslope for the previous element
411     previousMarkerData.outslopePoints[0] = previousMarkerData.origin;
412     previousMarkerData.outslopePoints[1] = points[0];
413
414     // Draw the marker for the previous element
415     if (elementIndex != 0)
416         drawMarkerWithData(data.context, previousMarkerData);
417
418     // Update our marker data for this element
419     updateMarkerDataForElement(previousMarkerData, element);
420
421     if (elementIndex == 1) {
422         // After drawing the start marker, switch to drawing mid markers
423         previousMarkerData.marker = data.midMarker;
424         previousMarkerData.type = Mid;
425     }
426
427     data.elementIndex++;
428 }
429
430 void RenderPath::drawMarkersIfNeeded(GraphicsContext* context, const FloatRect& rect, const Path& path) const
431 {
432     Document *doc = document();
433     const SVGRenderStyle* svgStyle = style()->svgStyle();
434
435     KCanvasMarker* startMarker = getMarkerById(doc, svgStyle->startMarker().substring(1));
436     KCanvasMarker* midMarker = getMarkerById(doc, svgStyle->midMarker().substring(1));
437     KCanvasMarker* endMarker = getMarkerById(doc, svgStyle->endMarker().substring(1));
438     
439     if (!startMarker && !midMarker && !endMarker)
440         return;
441
442     double strokeWidth = KSVGPainterFactory::cssPrimitiveToLength(this, style()->svgStyle()->strokeWidth(), 1.0);
443
444     DrawMarkersData data(context, startMarker, midMarker, strokeWidth);
445
446     path.apply(&data, drawStartAndMidMarkers);
447
448     data.previousMarkerData.marker = endMarker;
449     data.previousMarkerData.type = End;
450     drawMarkerWithData(context, data.previousMarkerData);
451 }
452
453 bool RenderPath::hasPercentageValues() const
454 {
455     return static_cast<SVGStyledElement*>(element())->hasPercentageValues();
456 }
457
458 }
459
460 #endif // SVG_SUPPORT