1 | /* |
2 | Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com> |
3 | Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <wildfox@kde.org> |
4 | 2004, 2005, 2006 Rob Buis <buis@kde.org> |
5 | 2005, 2007 Apple Inc. All Rights reserved. |
6 | 2007 Alp Toker <alp@atoker.com> |
7 | 2008 Dirk Schulze <krit@webkit.org> |
8 | 2011 Igalia S.L. |
9 | |
10 | This library is free software; you can redistribute it and/or |
11 | modify it under the terms of the GNU Library General Public |
12 | License as published by the Free Software Foundation; either |
13 | version 2 of the License, or (at your option) any later version. |
14 | |
15 | This library is distributed in the hope that it will be useful, |
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 | Library General Public License for more details. |
19 | |
20 | You should have received a copy of the GNU Library General Public License |
21 | aint with this library; see the file COPYING.LIB. If not, write to |
22 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
23 | Boston, MA 02110-1301, USA. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "Path.h" |
28 | |
29 | #if USE(CAIRO) |
30 | |
31 | #include "CairoUtilities.h" |
32 | #include "FloatRect.h" |
33 | #include "GraphicsContextImplCairo.h" |
34 | #include "PlatformPathCairo.h" |
35 | #include "StrokeStyleApplier.h" |
36 | #include <math.h> |
37 | #include <wtf/MathExtras.h> |
38 | #include <wtf/text/WTFString.h> |
39 | |
40 | namespace WebCore { |
41 | |
42 | Path::Path() |
43 | : m_path(0) |
44 | { |
45 | } |
46 | |
47 | Path::~Path() |
48 | { |
49 | if (m_path) |
50 | delete m_path; |
51 | } |
52 | |
53 | Path::Path(const Path& other) |
54 | : m_path(0) |
55 | { |
56 | if (other.isNull()) |
57 | return; |
58 | |
59 | cairo_t* cr = ensurePlatformPath()->context(); |
60 | auto pathCopy = cairo_copy_path(other.platformPath()->context()); |
61 | cairo_append_path(cr, pathCopy); |
62 | cairo_path_destroy(pathCopy); |
63 | } |
64 | |
65 | Path::Path(Path&& other) |
66 | { |
67 | m_path = other.m_path; |
68 | other.m_path = nullptr; |
69 | } |
70 | |
71 | PlatformPathPtr Path::ensurePlatformPath() |
72 | { |
73 | if (!m_path) |
74 | m_path = new CairoPath(); |
75 | return m_path; |
76 | } |
77 | |
78 | Path& Path::operator=(const Path& other) |
79 | { |
80 | if (&other == this) |
81 | return *this; |
82 | |
83 | if (other.isNull()) { |
84 | if (m_path) { |
85 | delete m_path; |
86 | m_path = 0; |
87 | } |
88 | } else { |
89 | clear(); |
90 | cairo_t* cr = ensurePlatformPath()->context(); |
91 | auto pathCopy = cairo_copy_path(other.platformPath()->context()); |
92 | cairo_append_path(cr, pathCopy); |
93 | cairo_path_destroy(pathCopy); |
94 | } |
95 | |
96 | return *this; |
97 | } |
98 | |
99 | Path& Path::operator=(Path&& other) |
100 | { |
101 | if (this == &other) |
102 | return *this; |
103 | if (m_path) |
104 | delete m_path; |
105 | m_path = other.m_path; |
106 | other.m_path = nullptr; |
107 | return *this; |
108 | } |
109 | |
110 | void Path::clear() |
111 | { |
112 | if (isNull()) |
113 | return; |
114 | |
115 | cairo_t* cr = platformPath()->context(); |
116 | cairo_identity_matrix(cr); |
117 | cairo_new_path(cr); |
118 | } |
119 | |
120 | bool Path::isEmpty() const |
121 | { |
122 | return isNull() || !cairo_has_current_point(platformPath()->context()); |
123 | } |
124 | |
125 | bool Path::hasCurrentPoint() const |
126 | { |
127 | return !isEmpty(); |
128 | } |
129 | |
130 | FloatPoint Path::currentPoint() const |
131 | { |
132 | if (isNull()) |
133 | return FloatPoint(); |
134 | |
135 | // FIXME: Is this the correct way? |
136 | double x; |
137 | double y; |
138 | cairo_get_current_point(platformPath()->context(), &x, &y); |
139 | return FloatPoint(x, y); |
140 | } |
141 | |
142 | void Path::translate(const FloatSize& p) |
143 | { |
144 | cairo_t* cr = ensurePlatformPath()->context(); |
145 | cairo_translate(cr, -p.width(), -p.height()); |
146 | } |
147 | |
148 | void Path::moveTo(const FloatPoint& p) |
149 | { |
150 | cairo_t* cr = ensurePlatformPath()->context(); |
151 | cairo_move_to(cr, p.x(), p.y()); |
152 | } |
153 | |
154 | void Path::addLineTo(const FloatPoint& p) |
155 | { |
156 | cairo_t* cr = ensurePlatformPath()->context(); |
157 | cairo_line_to(cr, p.x(), p.y()); |
158 | } |
159 | |
160 | void Path::addRect(const FloatRect& rect) |
161 | { |
162 | cairo_t* cr = ensurePlatformPath()->context(); |
163 | cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); |
164 | } |
165 | |
166 | /* |
167 | * inspired by libsvg-cairo |
168 | */ |
169 | void Path::addQuadCurveTo(const FloatPoint& controlPoint, const FloatPoint& point) |
170 | { |
171 | cairo_t* cr = ensurePlatformPath()->context(); |
172 | double x, y; |
173 | double x1 = controlPoint.x(); |
174 | double y1 = controlPoint.y(); |
175 | double x2 = point.x(); |
176 | double y2 = point.y(); |
177 | cairo_get_current_point(cr, &x, &y); |
178 | cairo_curve_to(cr, |
179 | x + 2.0 / 3.0 * (x1 - x), y + 2.0 / 3.0 * (y1 - y), |
180 | x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2), |
181 | x2, y2); |
182 | } |
183 | |
184 | void Path::addBezierCurveTo(const FloatPoint& controlPoint1, const FloatPoint& controlPoint2, const FloatPoint& controlPoint3) |
185 | { |
186 | cairo_t* cr = ensurePlatformPath()->context(); |
187 | cairo_curve_to(cr, controlPoint1.x(), controlPoint1.y(), |
188 | controlPoint2.x(), controlPoint2.y(), |
189 | controlPoint3.x(), controlPoint3.y()); |
190 | } |
191 | |
192 | void Path::addArc(const FloatPoint& p, float r, float startAngle, float endAngle, bool anticlockwise) |
193 | { |
194 | // http://bugs.webkit.org/show_bug.cgi?id=16449 |
195 | // cairo_arc() functions hang or crash when passed inf as radius or start/end angle |
196 | if (!std::isfinite(r) || !std::isfinite(startAngle) || !std::isfinite(endAngle)) |
197 | return; |
198 | |
199 | cairo_t* cr = ensurePlatformPath()->context(); |
200 | float sweep = endAngle - startAngle; |
201 | const float twoPI = 2 * piFloat; |
202 | if ((sweep <= -twoPI || sweep >= twoPI) |
203 | && ((anticlockwise && (endAngle < startAngle)) || (!anticlockwise && (startAngle < endAngle)))) { |
204 | if (anticlockwise) |
205 | cairo_arc_negative(cr, p.x(), p.y(), r, startAngle, startAngle - twoPI); |
206 | else |
207 | cairo_arc(cr, p.x(), p.y(), r, startAngle, startAngle + twoPI); |
208 | cairo_new_sub_path(cr); |
209 | cairo_arc(cr, p.x(), p.y(), r, endAngle, endAngle); |
210 | } else { |
211 | if (anticlockwise) |
212 | cairo_arc_negative(cr, p.x(), p.y(), r, startAngle, endAngle); |
213 | else |
214 | cairo_arc(cr, p.x(), p.y(), r, startAngle, endAngle); |
215 | } |
216 | } |
217 | |
218 | static inline float areaOfTriangleFormedByPoints(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& p3) |
219 | { |
220 | return p1.x() * (p2.y() - p3.y()) + p2.x() * (p3.y() - p1.y()) + p3.x() * (p1.y() - p2.y()); |
221 | } |
222 | |
223 | void Path::addArcTo(const FloatPoint& p1, const FloatPoint& p2, float radius) |
224 | { |
225 | // FIXME: Why do we return if the path is empty? Can't a path start with an arc? |
226 | if (isEmpty()) |
227 | return; |
228 | |
229 | cairo_t* cr = platformPath()->context(); |
230 | |
231 | double x0, y0; |
232 | cairo_get_current_point(cr, &x0, &y0); |
233 | FloatPoint p0(x0, y0); |
234 | |
235 | // Draw only a straight line to p1 if any of the points are equal or the radius is zero |
236 | // or the points are collinear (triangle that the points form has area of zero value). |
237 | if ((p1.x() == p0.x() && p1.y() == p0.y()) || (p1.x() == p2.x() && p1.y() == p2.y()) || !radius |
238 | || !areaOfTriangleFormedByPoints(p0, p1, p2)) { |
239 | cairo_line_to(cr, p1.x(), p1.y()); |
240 | return; |
241 | } |
242 | |
243 | FloatPoint p1p0((p0.x() - p1.x()),(p0.y() - p1.y())); |
244 | FloatPoint p1p2((p2.x() - p1.x()),(p2.y() - p1.y())); |
245 | float p1p0_length = sqrtf(p1p0.x() * p1p0.x() + p1p0.y() * p1p0.y()); |
246 | float p1p2_length = sqrtf(p1p2.x() * p1p2.x() + p1p2.y() * p1p2.y()); |
247 | |
248 | double cos_phi = (p1p0.x() * p1p2.x() + p1p0.y() * p1p2.y()) / (p1p0_length * p1p2_length); |
249 | // all points on a line logic |
250 | if (cos_phi == -1) { |
251 | cairo_line_to(cr, p1.x(), p1.y()); |
252 | return; |
253 | } |
254 | if (cos_phi == 1) { |
255 | // add infinite far away point |
256 | unsigned int max_length = 65535; |
257 | double factor_max = max_length / p1p0_length; |
258 | FloatPoint ep((p0.x() + factor_max * p1p0.x()), (p0.y() + factor_max * p1p0.y())); |
259 | cairo_line_to(cr, ep.x(), ep.y()); |
260 | return; |
261 | } |
262 | |
263 | float tangent = radius / tan(acos(cos_phi) / 2); |
264 | float factor_p1p0 = tangent / p1p0_length; |
265 | FloatPoint t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y())); |
266 | |
267 | FloatPoint orth_p1p0(p1p0.y(), -p1p0.x()); |
268 | float orth_p1p0_length = sqrt(orth_p1p0.x() * orth_p1p0.x() + orth_p1p0.y() * orth_p1p0.y()); |
269 | float factor_ra = radius / orth_p1p0_length; |
270 | |
271 | // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0 |
272 | double cos_alpha = (orth_p1p0.x() * p1p2.x() + orth_p1p0.y() * p1p2.y()) / (orth_p1p0_length * p1p2_length); |
273 | if (cos_alpha < 0.f) |
274 | orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y()); |
275 | |
276 | FloatPoint p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y())); |
277 | |
278 | // calculate angles for addArc |
279 | orth_p1p0 = FloatPoint(-orth_p1p0.x(), -orth_p1p0.y()); |
280 | float sa = acos(orth_p1p0.x() / orth_p1p0_length); |
281 | if (orth_p1p0.y() < 0.f) |
282 | sa = 2 * piDouble - sa; |
283 | |
284 | // anticlockwise logic |
285 | bool anticlockwise = false; |
286 | |
287 | float factor_p1p2 = tangent / p1p2_length; |
288 | FloatPoint t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y())); |
289 | FloatPoint orth_p1p2((t_p1p2.x() - p.x()),(t_p1p2.y() - p.y())); |
290 | float orth_p1p2_length = sqrtf(orth_p1p2.x() * orth_p1p2.x() + orth_p1p2.y() * orth_p1p2.y()); |
291 | float ea = acos(orth_p1p2.x() / orth_p1p2_length); |
292 | if (orth_p1p2.y() < 0) |
293 | ea = 2 * piDouble - ea; |
294 | if ((sa > ea) && ((sa - ea) < piDouble)) |
295 | anticlockwise = true; |
296 | if ((sa < ea) && ((ea - sa) > piDouble)) |
297 | anticlockwise = true; |
298 | |
299 | cairo_line_to(cr, t_p1p0.x(), t_p1p0.y()); |
300 | |
301 | addArc(p, radius, sa, ea, anticlockwise); |
302 | } |
303 | |
304 | void Path::addEllipse(FloatPoint point, float radiusX, float radiusY, float rotation, float startAngle, float endAngle, bool anticlockwise) |
305 | { |
306 | cairo_t* cr = ensurePlatformPath()->context(); |
307 | cairo_save(cr); |
308 | cairo_translate(cr, point.x(), point.y()); |
309 | cairo_rotate(cr, rotation); |
310 | cairo_scale(cr, radiusX, radiusY); |
311 | |
312 | if (anticlockwise) |
313 | cairo_arc_negative(cr, 0, 0, 1, startAngle, endAngle); |
314 | else |
315 | cairo_arc(cr, 0, 0, 1, startAngle, endAngle); |
316 | |
317 | cairo_restore(cr); |
318 | } |
319 | |
320 | void Path::addEllipse(const FloatRect& rect) |
321 | { |
322 | cairo_t* cr = ensurePlatformPath()->context(); |
323 | cairo_save(cr); |
324 | float yRadius = .5 * rect.height(); |
325 | float xRadius = .5 * rect.width(); |
326 | cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius); |
327 | cairo_scale(cr, xRadius, yRadius); |
328 | cairo_arc(cr, 0., 0., 1., 0., 2 * piDouble); |
329 | cairo_restore(cr); |
330 | } |
331 | |
332 | void Path::addPath(const Path& path, const AffineTransform& transform) |
333 | { |
334 | if (path.isNull()) |
335 | return; |
336 | |
337 | cairo_matrix_t matrix = toCairoMatrix(transform); |
338 | if (cairo_matrix_invert(&matrix) != CAIRO_STATUS_SUCCESS) |
339 | return; |
340 | |
341 | cairo_t* cr = path.platformPath()->context(); |
342 | cairo_save(cr); |
343 | cairo_transform(cr, &matrix); |
344 | auto pathCopy = cairo_copy_path(cr); |
345 | cairo_restore(cr); |
346 | cairo_append_path(ensurePlatformPath()->context(), pathCopy); |
347 | cairo_path_destroy(pathCopy); |
348 | } |
349 | |
350 | void Path::closeSubpath() |
351 | { |
352 | cairo_t* cr = ensurePlatformPath()->context(); |
353 | cairo_close_path(cr); |
354 | } |
355 | |
356 | FloatRect Path::boundingRect() const |
357 | { |
358 | // Should this be isEmpty() or can an empty path have a non-zero origin? |
359 | if (isNull()) |
360 | return FloatRect(); |
361 | |
362 | cairo_t* cr = platformPath()->context(); |
363 | double x0, x1, y0, y1; |
364 | cairo_path_extents(cr, &x0, &y0, &x1, &y1); |
365 | return FloatRect(x0, y0, x1 - x0, y1 - y0); |
366 | } |
367 | |
368 | FloatRect Path::strokeBoundingRect(StrokeStyleApplier* applier) const |
369 | { |
370 | // Should this be isEmpty() or can an empty path have a non-zero origin? |
371 | if (isNull()) |
372 | return FloatRect(); |
373 | |
374 | cairo_t* cr = platformPath()->context(); |
375 | if (applier) { |
376 | GraphicsContext gc(GraphicsContextImplCairo::createFactory(cr)); |
377 | applier->strokeStyle(&gc); |
378 | } |
379 | |
380 | double x0, x1, y0, y1; |
381 | cairo_stroke_extents(cr, &x0, &y0, &x1, &y1); |
382 | return FloatRect(x0, y0, x1 - x0, y1 - y0); |
383 | } |
384 | |
385 | bool Path::contains(const FloatPoint& point, WindRule rule) const |
386 | { |
387 | if (isNull() || !std::isfinite(point.x()) || !std::isfinite(point.y())) |
388 | return false; |
389 | cairo_t* cr = platformPath()->context(); |
390 | cairo_fill_rule_t cur = cairo_get_fill_rule(cr); |
391 | cairo_set_fill_rule(cr, rule == WindRule::EvenOdd ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); |
392 | bool contains = cairo_in_fill(cr, point.x(), point.y()); |
393 | cairo_set_fill_rule(cr, cur); |
394 | return contains; |
395 | } |
396 | |
397 | bool Path::strokeContains(StrokeStyleApplier* applier, const FloatPoint& point) const |
398 | { |
399 | if (isNull()) |
400 | return false; |
401 | |
402 | ASSERT(applier); |
403 | cairo_t* cr = platformPath()->context(); |
404 | { |
405 | GraphicsContext gc(GraphicsContextImplCairo::createFactory(cr)); |
406 | applier->strokeStyle(&gc); |
407 | } |
408 | |
409 | return cairo_in_stroke(cr, point.x(), point.y()); |
410 | } |
411 | |
412 | void Path::apply(const PathApplierFunction& function) const |
413 | { |
414 | if (isNull()) |
415 | return; |
416 | |
417 | cairo_t* cr = platformPath()->context(); |
418 | auto pathCopy = cairo_copy_path(cr); |
419 | cairo_path_data_t* data; |
420 | PathElement pelement; |
421 | FloatPoint points[3]; |
422 | pelement.points = points; |
423 | |
424 | for (int i = 0; i < pathCopy->num_data; i += pathCopy->data[i].header.length) { |
425 | data = &pathCopy->data[i]; |
426 | switch (data->header.type) { |
427 | case CAIRO_PATH_MOVE_TO: |
428 | pelement.type = PathElementMoveToPoint; |
429 | pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y); |
430 | function(pelement); |
431 | break; |
432 | case CAIRO_PATH_LINE_TO: |
433 | pelement.type = PathElementAddLineToPoint; |
434 | pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y); |
435 | function(pelement); |
436 | break; |
437 | case CAIRO_PATH_CURVE_TO: |
438 | pelement.type = PathElementAddCurveToPoint; |
439 | pelement.points[0] = FloatPoint(data[1].point.x,data[1].point.y); |
440 | pelement.points[1] = FloatPoint(data[2].point.x,data[2].point.y); |
441 | pelement.points[2] = FloatPoint(data[3].point.x,data[3].point.y); |
442 | function(pelement); |
443 | break; |
444 | case CAIRO_PATH_CLOSE_PATH: |
445 | pelement.type = PathElementCloseSubpath; |
446 | function(pelement); |
447 | break; |
448 | } |
449 | } |
450 | cairo_path_destroy(pathCopy); |
451 | } |
452 | |
453 | void Path::transform(const AffineTransform& transform) |
454 | { |
455 | cairo_t* cr = ensurePlatformPath()->context(); |
456 | cairo_matrix_t matrix = toCairoMatrix(transform); |
457 | cairo_matrix_invert(&matrix); |
458 | cairo_transform(cr, &matrix); |
459 | } |
460 | |
461 | } // namespace WebCore |
462 | |
463 | #endif // USE(CAIRO) |
464 | |