Coverage Report

Created: 2026-09-23 07:12

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