51bd707eabd7fea3876692b2eb4f2ae857195e7b
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / PathCG.cpp
1 /*
2  * Copyright (C) 2003, 2006 Apple Inc.  All rights reserved.
3  *                     2006, 2008 Rob Buis <buis@kde.org>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "Path.h"
29
30 #if USE(CG)
31
32 #include "AffineTransform.h"
33 #include "FloatRect.h"
34 #include "GraphicsContext.h"
35 #include "IntRect.h"
36 #include "StrokeStyleApplier.h"
37 #include <CoreGraphics/CoreGraphics.h>
38 #include <wtf/MathExtras.h>
39 #include <wtf/RetainPtr.h>
40 #include <wtf/text/WTFString.h>
41
42 #if PLATFORM(COCOA)
43 #include "WebCoreSystemInterface.h"
44 #endif
45
46 #if PLATFORM(WIN)
47 #include <WebKitSystemInterface/WebKitSystemInterface.h>
48 #endif
49
50 namespace WebCore {
51
52 static size_t putBytesNowhere(void*, const void*, size_t count)
53 {
54     return count;
55 }
56
57 static CGContextRef createScratchContext()
58 {
59     CGDataConsumerCallbacks callbacks = { putBytesNowhere, 0 };
60     RetainPtr<CGDataConsumerRef> consumer = adoptCF(CGDataConsumerCreate(0, &callbacks));
61     CGContextRef context = CGPDFContextCreate(consumer.get(), 0, 0);
62
63     CGFloat black[4] = { 0, 0, 0, 1 };
64     CGContextSetFillColor(context, black);
65     CGContextSetStrokeColor(context, black);
66
67     return context;
68 }
69
70 static inline CGContextRef scratchContext()
71 {
72     static CGContextRef context = createScratchContext();
73     return context;
74 }
75
76 Path Path::polygonPathFromPoints(const Vector<FloatPoint>& points)
77 {
78     Path path;
79     if (points.size() < 2)
80         return path;
81
82     Vector<CGPoint, 32> cgPoints;
83     cgPoints.reserveInitialCapacity(points.size());
84     for (size_t i = 0; i < points.size(); ++i)
85         cgPoints.uncheckedAppend(points[i]);
86
87     CGPathAddLines(path.ensurePlatformPath(), nullptr, cgPoints.data(), cgPoints.size());
88     path.closeSubpath();
89     return path;
90 }
91
92 Path::Path()
93     : m_path(nullptr)
94 {
95 }
96
97 Path::Path(RetainPtr<CGMutablePathRef> p)
98     : m_path(p.leakRef())
99 {
100 }
101
102 Path::~Path()
103 {
104     if (m_path)
105         CGPathRelease(m_path);
106 }
107
108 PlatformPathPtr Path::ensurePlatformPath()
109 {
110     if (!m_path)
111         m_path = CGPathCreateMutable();
112     return m_path;
113 }
114
115 Path::Path(const Path& other)
116 {
117     m_path = other.m_path ? CGPathCreateMutableCopy(other.m_path) : 0;
118 }
119
120 Path& Path::operator=(const Path& other)
121 {
122     CGMutablePathRef path = other.m_path ? CGPathCreateMutableCopy(other.m_path) : 0;
123     if (m_path)
124         CGPathRelease(m_path);
125     m_path = path;
126     return *this;
127 }
128
129 static void copyClosingSubpathsApplierFunction(void* info, const CGPathElement* element)
130 {
131     CGMutablePathRef path = static_cast<CGMutablePathRef>(info);
132     CGPoint* points = element->points;
133     
134     switch (element->type) {
135     case kCGPathElementMoveToPoint:
136         if (!CGPathIsEmpty(path)) // to silence a warning when trying to close an empty path
137             CGPathCloseSubpath(path); // This is the only change from CGPathCreateMutableCopy
138         CGPathMoveToPoint(path, 0, points[0].x, points[0].y);
139         break;
140     case kCGPathElementAddLineToPoint:
141         CGPathAddLineToPoint(path, 0, points[0].x, points[0].y);
142         break;
143     case kCGPathElementAddQuadCurveToPoint:
144         CGPathAddQuadCurveToPoint(path, 0, points[0].x, points[0].y, points[1].x, points[1].y);
145         break;
146     case kCGPathElementAddCurveToPoint:
147         CGPathAddCurveToPoint(path, 0, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y);
148         break;
149     case kCGPathElementCloseSubpath:
150         CGPathCloseSubpath(path);
151         break;
152     }
153 }
154
155 static CGMutablePathRef copyCGPathClosingSubpaths(CGPathRef originalPath)
156 {
157     CGMutablePathRef path = CGPathCreateMutable();
158     CGPathApply(originalPath, path, copyClosingSubpathsApplierFunction);
159     CGPathCloseSubpath(path);
160     return path;
161 }
162
163 bool Path::contains(const FloatPoint &point, WindRule rule) const
164 {
165     if (isNull())
166         return false;
167
168     if (!fastBoundingRect().contains(point))
169         return false;
170
171     // CGPathContainsPoint returns false for non-closed paths, as a work-around, we copy and close the path first.  Radar 4758998 asks for a better CG API to use
172     RetainPtr<CGMutablePathRef> path = adoptCF(copyCGPathClosingSubpaths(m_path));
173     bool ret = CGPathContainsPoint(path.get(), 0, point, rule == RULE_EVENODD ? true : false);
174     return ret;
175 }
176
177 bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const
178 {
179     if (isNull())
180         return false;
181
182     ASSERT(applier);
183
184     CGContextRef context = scratchContext();
185
186     CGContextSaveGState(context);
187     CGContextBeginPath(context);
188     CGContextAddPath(context, platformPath());
189
190     GraphicsContext gc(context);
191     applier->strokeStyle(&gc);
192
193     bool hitSuccess = CGContextPathContainsPoint(context, point, kCGPathStroke);
194     CGContextRestoreGState(context);
195     
196     return hitSuccess;
197 }
198
199 void Path::translate(const FloatSize& size)
200 {
201     transform(AffineTransform(1, 0, 0, 1, size.width(), size.height()));
202 }
203
204 void Path::transform(const AffineTransform& transform)
205 {
206     if (transform.isIdentity() || isEmpty())
207         return;
208
209     CGAffineTransform transformCG = transform;
210 #if PLATFORM(WIN)
211     CGMutablePathRef path = CGPathCreateMutable();
212     CGPathAddPath(path, &transformCG, m_path);
213 #else
214     CGMutablePathRef path = CGPathCreateMutableCopyByTransformingPath(m_path, &transformCG);
215 #endif
216     CGPathRelease(m_path);
217     m_path = path;
218 }
219
220 FloatRect Path::boundingRect() const
221 {
222     if (isNull())
223         return CGRectZero;
224
225     // CGPathGetBoundingBox includes the path's control points, CGPathGetPathBoundingBox does not.
226
227     CGRect bound = CGPathGetPathBoundingBox(m_path);
228     return CGRectIsNull(bound) ? CGRectZero : bound;
229 }
230
231 FloatRect Path::fastBoundingRect() const
232 {
233     if (isNull())
234         return CGRectZero;
235     CGRect bound = CGPathGetBoundingBox(m_path);
236     return CGRectIsNull(bound) ? CGRectZero : bound;
237 }
238
239 FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const
240 {
241     if (isNull())
242         return CGRectZero;
243
244     CGContextRef context = scratchContext();
245
246     CGContextSaveGState(context);
247     CGContextBeginPath(context);
248     CGContextAddPath(context, platformPath());
249
250     if (applier) {
251         GraphicsContext graphicsContext(context);
252         applier->strokeStyle(&graphicsContext);
253     }
254
255     CGContextReplacePathWithStrokedPath(context);
256     CGRect box = CGContextIsPathEmpty(context) ? CGRectZero : CGContextGetPathBoundingBox(context);
257     CGContextRestoreGState(context);
258
259     return CGRectIsNull(box) ? CGRectZero : box;
260 }
261
262 void Path::moveTo(const FloatPoint& point)
263 {
264     CGPathMoveToPoint(ensurePlatformPath(), nullptr, point.x(), point.y());
265 }
266
267 void Path::addLineTo(const FloatPoint& p)
268 {
269     CGPathAddLineToPoint(ensurePlatformPath(), nullptr, p.x(), p.y());
270 }
271
272 void Path::addQuadCurveTo(const FloatPoint& cp, const FloatPoint& p)
273 {
274     CGPathAddQuadCurveToPoint(ensurePlatformPath(), nullptr, cp.x(), cp.y(), p.x(), p.y());
275 }
276
277 void Path::addBezierCurveTo(const FloatPoint& cp1, const FloatPoint& cp2, const FloatPoint& p)
278 {
279     CGPathAddCurveToPoint(ensurePlatformPath(), nullptr, cp1.x(), cp1.y(), cp2.x(), cp2.y(), p.x(), p.y());
280 }
281
282 void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius)
283 {
284     CGPathAddArcToPoint(ensurePlatformPath(), nullptr, p1.x(), p1.y(), p2.x(), p2.y(), radius);
285 }
286
287 void Path::platformAddPathForRoundedRect(const FloatRect& rect, const FloatSize& topLeftRadius, const FloatSize& topRightRadius, const FloatSize& bottomLeftRadius, const FloatSize& bottomRightRadius)
288 {
289 #if PLATFORM(COCOA)
290     bool equalWidths = (topLeftRadius.width() == topRightRadius.width() && topRightRadius.width() == bottomLeftRadius.width() && bottomLeftRadius.width() == bottomRightRadius.width());
291     bool equalHeights = (topLeftRadius.height() == bottomLeftRadius.height() && bottomLeftRadius.height() == topRightRadius.height() && topRightRadius.height() == bottomRightRadius.height());
292
293     if (equalWidths && equalHeights) {
294         // Ensure that CG can render the rounded rect.
295         CGFloat radiusWidth = topLeftRadius.width();
296         CGFloat radiusHeight = topLeftRadius.height();
297         CGRect rectToDraw = rect;
298         CGFloat rectWidth = CGRectGetWidth(rectToDraw);
299         CGFloat rectHeight = CGRectGetHeight(rectToDraw);
300         if (rectWidth < 2 * radiusWidth)
301             radiusWidth = rectWidth / 2 - std::numeric_limits<CGFloat>::epsilon();
302         if (rectHeight < 2 * radiusHeight)
303             radiusHeight = rectHeight / 2 - std::numeric_limits<CGFloat>::epsilon();
304         CGPathAddRoundedRect(ensurePlatformPath(), nullptr, rectToDraw, radiusWidth, radiusHeight);
305         return;
306     }
307 #endif
308
309     addBeziersForRoundedRect(rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
310 }
311
312 void Path::closeSubpath()
313 {
314     // FIXME: Unclear if close commands should have meaning for a null path.
315     if (isNull())
316         return;
317
318     CGPathCloseSubpath(m_path);
319 }
320
321 void Path::addArc(const FloatPoint& p, float radius, float startAngle, float endAngle, bool clockwise)
322 {
323     // Workaround for <rdar://problem/5189233> CGPathAddArc hangs or crashes when passed inf as start or end angle
324     if (std::isfinite(startAngle) && std::isfinite(endAngle))
325         CGPathAddArc(ensurePlatformPath(), nullptr, p.x(), p.y(), radius, startAngle, endAngle, clockwise);
326 }
327
328 void Path::addRect(const FloatRect& r)
329 {
330     CGPathAddRect(ensurePlatformPath(), 0, r);
331 }
332
333 void Path::addEllipse(FloatPoint p, float radiusX, float radiusY, float rotation, float startAngle, float endAngle, bool anticlockwise)
334 {
335     AffineTransform transform;
336     transform.translate(p.x(), p.y()).rotate(rad2deg(rotation)).scale(radiusX, radiusY);
337
338     CGAffineTransform cgTransform = transform;
339     CGPathAddArc(ensurePlatformPath(), &cgTransform, 0, 0, 1, startAngle, endAngle, anticlockwise);
340 }
341
342 void Path::addEllipse(const FloatRect& r)
343 {
344     CGPathAddEllipseInRect(ensurePlatformPath(), 0, r);
345 }
346
347 void Path::addPath(const Path& path, const AffineTransform& transform)
348 {
349     if (!path.platformPath())
350         return;
351
352     if (!transform.isInvertible())
353         return;
354
355     CGAffineTransform transformCG = transform;
356     // CG doesn't allow adding a path to itself. Optimize for the common case
357     // and copy the path for the self referencing case.
358     if (ensurePlatformPath() != path.platformPath()) {
359         CGPathAddPath(ensurePlatformPath(), &transformCG, path.platformPath());
360         return;
361     }
362     CGPathRef pathCopy = CGPathCreateCopy(path.platformPath());
363     CGPathAddPath(ensurePlatformPath(), &transformCG, pathCopy);
364     CGPathRelease(pathCopy);
365 }
366
367
368 void Path::clear()
369 {
370     if (isNull())
371         return;
372
373     CGPathRelease(m_path);
374     m_path = CGPathCreateMutable();
375 }
376
377 bool Path::isEmpty() const
378 {
379     return isNull() || CGPathIsEmpty(m_path);
380 }
381
382 bool Path::hasCurrentPoint() const
383 {
384     return !isEmpty();
385 }
386     
387 FloatPoint Path::currentPoint() const 
388 {
389     if (isNull())
390         return FloatPoint();
391     return CGPathGetCurrentPoint(m_path);
392 }
393
394 static void CGPathApplierToPathApplier(void* info, const CGPathElement* element)
395 {
396     const PathApplierFunction& function = *(PathApplierFunction*)info;
397     FloatPoint points[3];
398     PathElement pelement;
399     pelement.type = (PathElementType)element->type;
400     pelement.points = points;
401     CGPoint* cgPoints = element->points;
402     switch (element->type) {
403     case kCGPathElementMoveToPoint:
404     case kCGPathElementAddLineToPoint:
405         points[0] = cgPoints[0];
406         break;
407     case kCGPathElementAddQuadCurveToPoint:
408         points[0] = cgPoints[0];
409         points[1] = cgPoints[1];
410         break;
411     case kCGPathElementAddCurveToPoint:
412         points[0] = cgPoints[0];
413         points[1] = cgPoints[1];
414         points[2] = cgPoints[2];
415         break;
416     case kCGPathElementCloseSubpath:
417         break;
418     }
419     function(pelement);
420 }
421
422 void Path::apply(const PathApplierFunction& function) const
423 {
424     if (isNull())
425         return;
426
427     CGPathApply(m_path, (void*)&function, CGPathApplierToPathApplier);
428 }
429
430 }
431
432 #endif // USE(CG)