1 /*
2 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3 *
4 * See the LICENSE file for terms of use.
5 */
6 #include "Wt/WPainterPath.h"
7
8 #include <limits>
9
10 #include <cmath>
11 #include <cassert>
12
13 #include "Wt/WStringStream.h"
14
15 #include "WebUtils.h"
16
17 #ifndef M_PI
18 #define M_PI 3.14159265358979323846
19 #endif
20
21 namespace {
degreesToRadians(double r)22 double degreesToRadians(double r) {
23 return (r / 180.) * M_PI;
24 }
25 }
26
27 namespace Wt {
28
Segment(double x,double y,SegmentType type)29 WPainterPath::Segment::Segment(double x, double y, SegmentType type)
30 : x_(x), y_(y), type_(type)
31 { }
32
33 bool WPainterPath::Segment::operator== (const Segment& other) const
34 {
35 return type_ == other.type_
36 && x_ == other.x_
37 && y_ == other.y_;
38 }
39
40 bool WPainterPath::Segment::operator!= (const Segment& other) const
41 {
42 return !(*this == other);
43 }
44
WPainterPath()45 WPainterPath::WPainterPath()
46 : isRect_(false),
47 openSubPathsEnabled_(false)
48 { }
49
WPainterPath(const WPointF & startPoint)50 WPainterPath::WPainterPath(const WPointF& startPoint)
51 : isRect_(false),
52 openSubPathsEnabled_(false)
53 {
54 moveTo(startPoint);
55 }
56
WPainterPath(const WPainterPath & path)57 WPainterPath::WPainterPath(const WPainterPath& path)
58 : WJavaScriptExposableObject(path),
59 isRect_(path.isRect_),
60 openSubPathsEnabled_(path.openSubPathsEnabled_)
61 #ifndef WT_TARGET_JAVA
62 ,segments_(path.segments_)
63 #endif
64 {
65 #ifdef WT_TARGET_JAVA
66 segments_ = path.segments_;
67 #endif
68 }
69
70 WPainterPath& WPainterPath::operator= (const WPainterPath& path)
71 {
72 #ifndef WT_TARGET_JAVA
73 WJavaScriptExposableObject::operator=(path);
74 #else
75 if (path.isJavaScriptBound()) assignBinding(path);
76 #endif
77
78 segments_ = path.segments_;
79 isRect_ = path.isRect_;
80
81 return *this;
82 }
83
84 #ifdef WT_TARGET_JAVA
clone()85 WPainterPath WPainterPath::clone() const
86 {
87 return WPainterPath(*this);
88 }
89 #endif
90
getArcPosition(double cx,double cy,double rx,double ry,double angle)91 WPointF WPainterPath::getArcPosition(double cx, double cy,
92 double rx, double ry,
93 double angle)
94 {
95 /*
96 * angles are counter-clockwise, which means against the logic of
97 * the downward X-Y system
98 */
99 double a = -degreesToRadians(angle);
100
101 return WPointF(cx + rx * std::cos(a), cy + ry * std::sin(a));
102 }
103
beginPosition()104 WPointF WPainterPath::beginPosition() const
105 {
106 WPointF result(0, 0);
107
108 for (unsigned int i = 0;
109 i < segments_.size() && segments_[i].type() == MoveTo;
110 ++i)
111 result = WPointF(segments_[i].x(), segments_[i].y());
112
113 return result;
114 }
115
currentPosition()116 WPointF WPainterPath::currentPosition() const
117 {
118 return positionAtSegment(segments_.size());
119 }
120
positionAtSegment(int index)121 WPointF WPainterPath::positionAtSegment(int index) const
122 {
123 if (index > 0) {
124 const Segment& s = segments_[index - 1];
125 switch (s.type()) {
126 case MoveTo:
127 case LineTo:
128 case CubicEnd:
129 case QuadEnd:
130 return WPointF(s.x(), s.y());
131 case ArcAngleSweep: {
132 int i = segments_.size() - 3;
133 double cx = segments_[i].x();
134 double cy = segments_[i].y();
135 double rx = segments_[i+1].x();
136 double ry = segments_[i+1].y();
137 double theta1 = segments_[i+2].x();
138 double deltaTheta = segments_[i+2].y();
139
140 return getArcPosition(cx, cy, rx, ry, theta1 + deltaTheta);
141 }
142 default:
143 assert(false);
144 }
145 }
146
147 return WPointF(0, 0);
148 }
149
getSubPathStart()150 WPointF WPainterPath::getSubPathStart() const
151 {
152 /*
153 * Find start point of last sub path, which is the point of the last
154 * moveTo operation, or either (0, 0).
155 */
156 for (int i = segments_.size() - 1; i >= 0; --i)
157 if (segments_[i].type() == MoveTo)
158 return WPointF(segments_[i].x(), segments_[i].y());
159
160 return WPointF(0, 0);
161 }
162
closeSubPath()163 void WPainterPath::closeSubPath()
164 {
165 checkModifiable();
166 moveTo(0, 0);
167 }
168
isEmpty()169 bool WPainterPath::isEmpty() const
170 {
171 for (unsigned i = 0; i < segments_.size(); ++i)
172 if (segments_[i].type() != MoveTo)
173 return false;
174
175 return true;
176 }
177
178 bool WPainterPath::operator==(const WPainterPath& path) const
179 {
180 if (segments_.size() != path.segments_.size())
181 return false;
182
183 for (unsigned i = 0; i < segments_.size(); ++i)
184 if (segments_[i] != path.segments_[i])
185 return false;
186
187 return true;
188 }
189
190 bool WPainterPath::operator!=(const WPainterPath& path) const
191 {
192 return !(*this == path);
193 }
194
moveTo(const WPointF & point)195 void WPainterPath::moveTo(const WPointF& point)
196 {
197 moveTo(point.x(), point.y());
198 }
199
moveTo(double x,double y)200 void WPainterPath::moveTo(double x, double y)
201 {
202 checkModifiable();
203 /*
204 * first close previous sub path
205 */
206 if (!openSubPathsEnabled_ &&
207 !segments_.empty() &&
208 segments_.back().type() != MoveTo) {
209 WPointF startP = getSubPathStart();
210 WPointF currentP = currentPosition();
211
212 if (startP != currentP)
213 lineTo(startP.x(), startP.y());
214 }
215
216 segments_.push_back(Segment(x, y, MoveTo));
217 }
218
lineTo(const WPointF & point)219 void WPainterPath::lineTo(const WPointF& point)
220 {
221 lineTo(point.x(), point.y());
222 }
223
lineTo(double x,double y)224 void WPainterPath::lineTo(double x, double y)
225 {
226 checkModifiable();
227 segments_.push_back(Segment(x, y, LineTo));
228 }
229
cubicTo(const WPointF & c1,const WPointF & c2,const WPointF & endPoint)230 void WPainterPath::cubicTo(const WPointF& c1, const WPointF& c2,
231 const WPointF& endPoint)
232 {
233 cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), endPoint.x(), endPoint.y());
234 }
235
cubicTo(double c1x,double c1y,double c2x,double c2y,double endPointx,double endPointy)236 void WPainterPath::cubicTo(double c1x, double c1y, double c2x, double c2y,
237 double endPointx, double endPointy)
238 {
239 checkModifiable();
240 segments_.push_back(Segment(c1x, c1y, CubicC1));
241 segments_.push_back(Segment(c2x, c2y, CubicC2));
242 segments_.push_back(Segment(endPointx, endPointy, CubicEnd));
243 }
244
arcTo(double cx,double cy,double radius,double startAngle,double sweepLength)245 void WPainterPath::arcTo(double cx, double cy, double radius,
246 double startAngle, double sweepLength)
247 {
248 arcTo(cx - radius, cy - radius, radius * 2, radius * 2,
249 startAngle, sweepLength);
250 }
251
arcTo(double x,double y,double width,double height,double startAngle,double sweepLength)252 void WPainterPath::arcTo(double x, double y, double width, double height,
253 double startAngle, double sweepLength)
254 {
255 checkModifiable();
256 segments_.push_back(Segment(x + width/2, y + height/2, ArcC));
257 segments_.push_back(Segment(width/2, height/2, ArcR));
258 segments_.push_back(Segment(startAngle, sweepLength,
259 ArcAngleSweep));
260 }
261
arcMoveTo(double cx,double cy,double radius,double angle)262 void WPainterPath::arcMoveTo(double cx, double cy, double radius, double angle)
263 {
264 moveTo(getArcPosition(cx, cy, radius, radius, angle));
265 }
266
arcMoveTo(double x,double y,double width,double height,double angle)267 void WPainterPath::arcMoveTo(double x, double y, double width, double height,
268 double angle)
269 {
270 moveTo(getArcPosition(x + width/2, y + height/2, width/2, height/2, angle));
271 }
272
quadTo(double cx,double cy,double endPointX,double endPointY)273 void WPainterPath::quadTo(double cx, double cy,
274 double endPointX, double endPointY)
275 {
276 checkModifiable();
277 segments_.push_back(Segment(cx, cy, QuadC));
278 segments_.push_back(Segment(endPointX, endPointY, QuadEnd));
279 }
280
quadTo(const WPointF & c,const WPointF & endPoint)281 void WPainterPath::quadTo(const WPointF& c, const WPointF& endPoint)
282 {
283 quadTo(c.x(), c.y(), endPoint.x(), endPoint.y());
284 }
285
addEllipse(double x,double y,double width,double height)286 void WPainterPath::addEllipse(double x, double y, double width, double height)
287 {
288 moveTo(x + width, y + height/2);
289 arcTo(x, y, width, height, 0, 360);
290 }
291
addEllipse(const WRectF & rect)292 void WPainterPath::addEllipse(const WRectF& rect)
293 {
294 addEllipse(rect.x(), rect.y(), rect.width(), rect.height());
295 }
296
addRect(double x,double y,double width,double height)297 void WPainterPath::addRect(double x, double y, double width, double height)
298 {
299 checkModifiable();
300 if (isEmpty())
301 isRect_ = true;
302
303 moveTo(x, y);
304 lineTo(x + width, y);
305 lineTo(x + width, y + height);
306 lineTo(x, y + height);
307 lineTo(x, y);
308 }
309
addRect(const WRectF & rectangle)310 void WPainterPath::addRect(const WRectF& rectangle)
311 {
312 addRect(rectangle.x(), rectangle.y(), rectangle.width(), rectangle.height());
313 }
314
addPolygon(const std::vector<WPointF> & points)315 void WPainterPath::addPolygon(const std::vector<WPointF>& points)
316 {
317 checkModifiable();
318 if (!points.empty()) {
319 unsigned i = 0;
320 if (currentPosition() != points[0])
321 moveTo(points[i++]);
322
323 for (; i < points.size(); ++i)
324 lineTo(points[i]);
325 }
326 }
327
addPath(const WPainterPath & path)328 void WPainterPath::addPath(const WPainterPath& path)
329 {
330 checkModifiable();
331 if (currentPosition() != path.beginPosition())
332 moveTo(path.beginPosition());
333
334 Utils::insert(segments_, path.segments_);
335 }
336
connectPath(const WPainterPath & path)337 void WPainterPath::connectPath(const WPainterPath& path)
338 {
339 checkModifiable();
340 if (currentPosition() != path.beginPosition())
341 lineTo(path.beginPosition());
342
343 addPath(path);
344 }
345
asRect(WRectF & result)346 bool WPainterPath::asRect(WRectF& result) const
347 {
348 if (isRect_) {
349 if (segments_.size() == 4) {
350 result.setX(0);
351 result.setY(0);
352 result.setWidth(segments_[0].x());
353 result.setHeight(segments_[1].y());
354 return true;
355 } else if (segments_.size() == 5
356 && segments_[0].type() == MoveTo) {
357 result.setX(segments_[0].x());
358 result.setY(segments_[0].y());
359 result.setWidth(segments_[1].x() - segments_[0].x());
360 result.setHeight(segments_[2].y() - segments_[0].y());
361 return true;
362 } else
363 return false;
364 } else
365 return false;
366 }
367
controlPointRect(const WTransform & transform)368 WRectF WPainterPath::controlPointRect(const WTransform& transform) const
369 {
370 if (isEmpty())
371 return WRectF();
372 else {
373 bool identity = transform.isIdentity();
374
375 double minX, minY, maxX, maxY;
376 minX = minY = std::numeric_limits<double>::max();
377 maxX = maxY = std::numeric_limits<double>::min();
378
379 for (unsigned i = 0; i < segments_.size(); ++i) {
380 const Segment& s = segments_[i];
381
382 switch (s.type()) {
383 case MoveTo:
384 case LineTo:
385 case CubicC1:
386 case CubicC2:
387 case CubicEnd:
388 case QuadC:
389 case QuadEnd: {
390 if (identity) {
391 minX = std::min(s.x(), minX);
392 minY = std::min(s.y(), minY);
393 maxX = std::max(s.x(), maxX);
394 maxY = std::max(s.y(), maxY);
395 } else {
396 WPointF p = transform.map(WPointF(s.x(), s.y()));
397 minX = std::min(p.x(), minX);
398 minY = std::min(p.y(), minY);
399 maxX = std::max(p.x(), maxX);
400 maxY = std::max(p.y(), maxY);
401 }
402 break;
403 }
404 case ArcC: {
405 const Segment& s2 = segments_[i+1];
406
407 if (identity) {
408 WPointF tl(s.x() - s2.x(), s.y() - s2.y());
409 minX = std::min(tl.x(), minX);
410 minY = std::min(tl.y(), minY);
411
412 WPointF br(s.x() + s2.x(), s.y() + s2.y());
413 maxX = std::max(br.x(), maxX);
414 maxY = std::max(br.y(), maxY);
415 } else {
416 WPointF p1 = transform.map(WPointF(s.x(), s.y()));
417 WPointF p2 = transform.map(WPointF(s2.x(), s2.y()));
418
419 WPointF tl(p1.x() - p2.x(), p1.y() - p2.y());
420 minX = std::min(tl.x(), minX);
421 minY = std::min(tl.y(), minY);
422
423 WPointF br(p1.x() + p2.x(), p1.y() + p2.y());
424 maxX = std::max(br.x(), maxX);
425 maxY = std::max(br.y(), maxY);
426 }
427
428 i += 2;
429 break;
430 }
431 default:
432 assert(false);
433 }
434 }
435
436 return WRectF(minX, minY, maxX - minX, maxY - minY);
437 }
438 }
439
jsValue()440 std::string WPainterPath::jsValue() const
441 {
442 char buf[30];
443 WStringStream ss;
444 ss << '[';
445 for (std::size_t i = 0; i < segments_.size(); ++i) {
446 const Segment &s = segments_[i];
447 if (i != 0) ss << ',';
448 ss << '[';
449 ss << Utils::round_js_str(s.x(), 3, buf) << ',';
450 ss << Utils::round_js_str(s.y(), 3, buf) << ',';
451 ss << (int)s.type()
452 << ']';
453 }
454 ss << ']';
455 return ss.str();
456 }
457
crisp()458 WPainterPath WPainterPath::crisp() const
459 {
460 WPainterPath result;
461
462 if (isJavaScriptBound()) {
463 result.assignBinding(*this,
464 WT_CLASS ".gfxUtils.path_crisp(" + jsRef() + ')');
465 }
466
467 for (std::size_t i = 0; i < segments_.size(); ++i) {
468 const Segment &segment = segments_[i];
469 double hx = std::floor(segment.x()) + 0.5;
470 double hy = std::floor(segment.y()) + 0.5;
471 result.segments_.push_back(Segment(hx, hy, segment.type()));
472 }
473
474 return result;
475 }
476
setOpenSubPathsEnabled(bool enabled)477 void WPainterPath::setOpenSubPathsEnabled(bool enabled)
478 {
479 openSubPathsEnabled_ = enabled;
480 }
481
isPointInPath(const WPointF & p)482 bool WPainterPath::isPointInPath(const WPointF &p) const
483 {
484 bool res = false;
485 double ax = 0.0, ay = 0.0;
486 double px = p.x(), py = p.y();
487 for (std::size_t i = 0; i < segments_.size(); ++i) {
488 double bx = ax;
489 double by = ay;
490 if (segments_[i].type() == ArcC) {
491 WPointF arcPos = getArcPosition(segments_[i].x(), segments_[i].y(),
492 segments_[i+1].x(), segments_[i+1].y(),
493 segments_[i+2].x());
494 bx = arcPos.x();
495 by = arcPos.y();
496 } else if (segments_[i].type() == ArcAngleSweep) {
497 WPointF arcPos = getArcPosition(segments_[i-2].x(), segments_[i-2].y(),
498 segments_[i-1].x(), segments_[i-1].y(),
499 segments_[i].x() + segments_[i].y());
500 bx = arcPos.x();
501 by = arcPos.y();
502 } else if (segments_[i].type() != ArcR) {
503 bx = segments_[i].x();
504 by = segments_[i].y();
505 }
506 if (segments_[i].type() != MoveTo) {
507 if ( (ay > py) != (by > py) &&
508 (px < (bx - ax) * (py - ay) / (by - ay) + ax) ) {
509 res = !res;
510 }
511 }
512 ax = bx;
513 ay = by;
514 }
515 return res;
516 }
517
assignFromJSON(const Json::Value & value)518 void WPainterPath::assignFromJSON(const Json::Value &value) {}
519
520 }
521