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