19 void copy(VPathData *o);
20 void moveTo(const VPointF &pt);
21 void lineTo(const VPointF &pt);
22 void cubicTo(const VPointF &c1, const VPointF &c2, const VPointF &e);
25 void checkNewSegment();
27 void transform(const VMatrix &m);
29 std::vector<VPointF> m_points;
30 std::vector<VPath::Element> m_elements;
37 void VPathData::transform(const VMatrix &m)
39 for(auto &i : m_points) {
44 void VPathData::checkNewSegment()
51 void VPathData::copy(VPathData *o)
53 m_points = o->m_points;
54 m_elements = o->m_elements;
55 m_segments = o->m_segments;
56 mStartPoint = o->mStartPoint;
59 void VPathData::moveTo(const VPointF &p)
63 m_elements.push_back(VPath::Element::MoveTo);
64 m_points.push_back(p);
67 void VPathData::lineTo(const VPointF &p)
70 m_elements.push_back(VPath::Element::LineTo);
71 m_points.push_back(p);
73 void VPathData::cubicTo(const VPointF &c1, const VPointF &c2, const VPointF &e)
76 m_elements.push_back(VPath::Element::CubicTo);
77 m_points.push_back(c1);
78 m_points.push_back(c2);
79 m_points.push_back(e);
82 void VPathData::close()
84 const VPointF &lastPt = m_points.back();
85 if (!fuzzyCompare(mStartPoint, lastPt)) {
88 m_elements.push_back(VPath::Element::Close);
92 void VPathData::reset()
99 int VPathData::segments() const
105 static const struct VPathData shared_empty;
107 inline void VPath::cleanUp(VPathData *d)
114 if (d->ref.isShared())
118 VPath VPath::copy() const
122 other.d = new VPathData;
123 other.d->m_points = d->m_points;
124 other.d->m_elements = d->m_elements;
125 other.d->m_segments = d->m_segments;
126 other.d->ref.setOwned();
137 : d(const_cast<VPathData*>(&shared_empty))
141 VPath::VPath(const VPath &other)
147 VPath::VPath(VPath &&other): d(other.d)
149 other.d = const_cast<VPathData*>(&shared_empty);
152 VPath &VPath::operator=(const VPath &other)
162 inline VPath &VPath::operator=(VPath &&other)
167 other.d = const_cast<VPathData*>(&shared_empty);
171 bool VPath::isEmpty()const
173 return d->m_elements.empty();
178 if (isEmpty()) return;
185 if (isEmpty()) return;
190 void VPath::moveTo(const VPointF &p)
196 void VPath::lineTo(const VPointF &p)
202 void VPath::cubicTo(const VPointF &c1, const VPointF &c2, const VPointF &e)
205 d->cubicTo(c1, c2, e);
208 void VPath::reserve(int num_elm)
211 d->m_elements.reserve(num_elm);
212 d->m_points.reserve(num_elm);
215 const std::vector<VPath::Element> &VPath::elements() const
217 return d->m_elements;
219 const std::vector<VPointF> &VPath::points() const
223 int VPath::segments() const
225 return d->m_segments + 1;
228 #define PATH_KAPPA 0.5522847498
230 static float tForArcAngle(float angle);
231 void findEllipseCoords(const VRectF &r, float angle, float length,
232 VPointF* startPoint, VPointF *endPoint)
236 *startPoint = VPointF();
238 *endPoint = VPointF();
242 float w2 = r.width() / 2;
243 float h2 = r.height() / 2;
245 float angles[2] = { angle, angle + length };
246 VPointF *points[2] = { startPoint, endPoint };
248 for (int i = 0; i < 2; ++i) {
252 float theta = angles[i] - 360 * floor(angles[i] / 360);
253 float t = theta / 90;
255 int quadrant = int(t);
258 t = tForArcAngle(90 * t);
265 VBezier::coefficients(t, a, b, c, d);
266 VPointF p(a + b + c*PATH_KAPPA, d + c + b*PATH_KAPPA);
269 if (quadrant == 1 || quadrant == 2)
273 if (quadrant == 0 || quadrant == 1)
276 *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y());
281 tForArcAngle(float angle)
283 float radians, cos_angle, sin_angle, tc, ts, t;
285 if (vCompare(angle,0.f)) return 0;
286 if (vCompare(angle, 90.0f)) return 1;
288 radians = (angle/180) * M_PI;
290 cos_angle = cos(radians);
291 sin_angle = sin(radians);
296 // do some iterations of newton's method to approximate cos_angle
297 // finds the zero of the function b.pointAt(tc).x() - cos_angle
298 tc -= ((((2-3*PATH_KAPPA) * tc + 3*(PATH_KAPPA-1)) * tc) * tc + 1 - cos_angle) // value
299 / (((6-9*PATH_KAPPA) * tc + 6*(PATH_KAPPA-1)) * tc); // derivative
300 tc -= ((((2-3*PATH_KAPPA) * tc + 3*(PATH_KAPPA-1)) * tc) * tc + 1 - cos_angle) // value
301 / (((6-9*PATH_KAPPA) * tc + 6*(PATH_KAPPA-1)) * tc); // derivative
305 // do some iterations of newton's method to approximate sin_angle
306 // finds the zero of the function b.pointAt(tc).y() - sin_angle
307 ts -= ((((3*PATH_KAPPA-2) * ts - 6*PATH_KAPPA + 3) * ts + 3*PATH_KAPPA) * ts - sin_angle)
308 / (((9*PATH_KAPPA-6) * ts + 12*PATH_KAPPA - 6) * ts + 3*PATH_KAPPA);
309 ts -= ((((3*PATH_KAPPA-2) * ts - 6*PATH_KAPPA + 3) * ts + 3*PATH_KAPPA) * ts - sin_angle)
310 / (((9*PATH_KAPPA-6) * ts + 12*PATH_KAPPA - 6) * ts + 3*PATH_KAPPA);
312 // use the average of the t that best approximates cos_angle
313 // and the t that best approximates sin_angle
319 // The return value is the starting point of the arc
321 curvesForArc(const VRectF &rect, float startAngle, float sweepLength,
322 VPointF *curves, int *point_count)
331 float w = rect.width();
332 float w2 = rect.width() / 2;
333 float w2k = w2 * PATH_KAPPA;
335 float h = rect.height();
336 float h2 = rect.height() / 2;
337 float h2k = h2 * PATH_KAPPA;
342 VPointF(x + w, y + h2),
345 VPointF(x + w, y + h2 + h2k),
346 VPointF(x + w2 + w2k, y + h),
347 VPointF(x + w2, y + h),
349 // 270 -> 180 degrees
350 VPointF(x + w2 - w2k, y + h),
351 VPointF(x, y + h2 + h2k),
355 VPointF(x, y + h2 - h2k),
356 VPointF(x + w2 - w2k, y),
360 VPointF(x + w2 + w2k, y),
361 VPointF(x + w, y + h2 - h2k),
362 VPointF(x + w, y + h2)
365 if (sweepLength > 360) sweepLength = 360;
366 else if (sweepLength < -360) sweepLength = -360;
368 // Special case fast paths
369 if (startAngle == 0.0) {
370 if (sweepLength == 360.0) {
371 for (int i = 11; i >= 0; --i)
372 curves[(*point_count)++] = points[i];
374 } else if (sweepLength == -360.0) {
375 for (int i = 1; i <= 12; ++i)
376 curves[(*point_count)++] = points[i];
381 int startSegment = int(floor(startAngle / 90));
382 int endSegment = int(floor((startAngle + sweepLength) / 90));
384 float startT = (startAngle - startSegment * 90) / 90;
385 float endT = (startAngle + sweepLength - endSegment * 90) / 90;
387 int delta = sweepLength > 0 ? 1 : -1;
393 // avoid empty start segment
394 if (vIsZero(startT - float(1))) {
396 startSegment += delta;
399 // avoid empty end segment
405 startT = tForArcAngle(startT * 90);
406 endT = tForArcAngle(endT * 90);
408 const bool splitAtStart = !vIsZero(startT);
409 const bool splitAtEnd = !vIsZero(endT - float(1));
411 const int end = endSegment + delta;
414 if (startSegment == end) {
415 const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
416 const int j = 3 * quadrant;
417 return delta > 0 ? points[j + 3] : points[j];
420 VPointF startPoint, endPoint;
421 findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint);
423 for (int i = startSegment; i != end; i += delta) {
424 const int quadrant = 3 - ((i % 4) + 4) % 4;
425 const int j = 3 * quadrant;
429 b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]);
431 b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]);
434 if (startSegment == endSegment && vCompare(startT, endT))
437 if (i == startSegment) {
438 if (i == endSegment && splitAtEnd)
439 b = b.onInterval(startT, endT);
440 else if (splitAtStart)
441 b = b.onInterval(startT, 1);
442 } else if (i == endSegment && splitAtEnd) {
443 b = b.onInterval(0, endT);
446 // push control points
447 curves[(*point_count)++] = b.pt2();
448 curves[(*point_count)++] = b.pt3();
449 curves[(*point_count)++] = b.pt4();
452 curves[*(point_count)-1] = endPoint;
457 void VPath::arcTo(const VRectF &rect, float startAngle, float sweepLength, bool forceMoveTo)
463 VPointF curve_start = curvesForArc(rect, startAngle, sweepLength, pts, &point_count);
465 if (isEmpty() || forceMoveTo) {
466 d->moveTo(curve_start);
468 d->lineTo(curve_start);
470 for (int i=0; i<point_count; i+=3) {
471 d->cubicTo(pts[i], pts[i+1], pts[i+2]);
475 void VPath::addCircle(float cx, float cy, float radius, VPath::Direction dir)
477 addOval(VRectF(cx-radius, cy-radius, 2*radius, 2*radius) , dir);
480 void VPath::addOval(const VRectF &rect, VPath::Direction dir)
482 if (rect.isNull()) return;
489 float w = rect.width();
490 float w2 = rect.width() / 2;
491 float w2k = w2 * PATH_KAPPA;
493 float h = rect.height();
494 float h2 = rect.height() / 2;
495 float h2k = h2 * PATH_KAPPA;
498 if (dir == VPath::Direction::CW) {
499 // moveto 12 o'clock.
500 d->moveTo(VPointF(x+w2, y));
502 d->cubicTo(VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k), VPointF(x + w, y + h2));
504 d->cubicTo(VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h), VPointF(x + w2, y + h));
506 d->cubicTo(VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k), VPointF(x , y + h2));
508 d->cubicTo(VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y));
510 // moveto 12 o'clock.
511 d->moveTo(VPointF(x+w2, y));
513 d->cubicTo(VPointF(x + w2 - w2k, y), VPointF(x, y + h2 - h2k), VPointF(x , y + h2));
515 d->cubicTo(VPointF(x, y + h2 + h2k), VPointF(x + w2 - w2k, y + h), VPointF(x + w2, y + h));
517 d->cubicTo(VPointF(x + w2 + w2k, y + h), VPointF(x + w, y + h2 + h2k), VPointF(x + w, y + h2));
519 d->cubicTo(VPointF(x + w, y + h2 - h2k), VPointF(x + w2 + w2k, y), VPointF(x+w2, y));
523 void VPath::addRect(const VRectF &rect, VPath::Direction dir)
525 if (rect.isNull()) return;
531 float w = rect.width();
532 float h = rect.height();
534 if (dir == VPath::Direction::CW) {
535 moveTo(VPointF(x + w, y));
536 lineTo(VPointF(x + w, y + h));
537 lineTo(VPointF(x , y + h));
538 lineTo(VPointF(x , y));
541 moveTo(VPointF(x + w, y));
542 lineTo(VPointF(x , y));
543 lineTo(VPointF(x , y + h));
544 lineTo(VPointF(x + w, y + h));
549 void VPath::addRoundRect(const VRectF &rect, float rx, float ry, VPath::Direction dir)
551 if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) {
558 float w = rect.width();
559 float h = rect.height();
560 // clamp the rx and ry radius value.
566 if (dir == VPath::Direction::CW) {
567 moveTo(VPointF(x + w, y + ry/2.f));
568 arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 0 , -90, false);
569 arcTo(VRectF(x, y + h - ry, rx, ry), -90 , -90, false);
570 arcTo(VRectF(x, y, rx, ry), -180 , -90, false);
571 arcTo(VRectF(x + w - rx, y, rx, ry), -270 , -90, false);
574 moveTo(VPointF(x + w, y + ry/2.f));
575 arcTo(VRectF(x + w - rx, y , rx, ry), 0 , 90, false);
576 arcTo(VRectF(x, y, rx, ry), 90 , 90, false);
577 arcTo(VRectF(x, y + h - ry, rx, ry), 180 , 90, false);
578 arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 270 , 90, false);
583 void VPath::addPolystarStar(float startAngle, float cx, float cy, float points,
584 float innerRadius, float outerRadius,
585 float innerRoundness, float outerRoundness,
586 VPath::Direction dir)
588 // TODO: Direction feature is missing
589 const static float POLYSTAR_MAGIC_NUMBER = 0.47829 / 0.28;
590 float currentAngle = (startAngle - 90.0) * M_PI / 180.0;
595 float partialPointRadius = 0;
596 float anglePerPoint = (float) (2.0 * M_PI / points);
597 float halfAnglePerPoint = anglePerPoint / 2.0;
598 float partialPointAmount = points - (int) points;
599 bool longSegment = false;
600 int numPoints = (int) ceil(points) * 2.0;
602 innerRoundness /= 100.0;
603 outerRoundness /= 100.0;
605 if (partialPointAmount != 0) {
606 currentAngle += halfAnglePerPoint * (1.0 - partialPointAmount);
609 if (partialPointAmount != 0) {
610 partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius);
611 x = (float) (partialPointRadius * cos(currentAngle));
612 y = (float) (partialPointRadius * sin(currentAngle));
613 currentAngle += anglePerPoint * partialPointAmount / 2.0;
615 x = (float) (outerRadius * cos(currentAngle));
616 y = (float) (outerRadius * sin(currentAngle));
617 currentAngle += halfAnglePerPoint;
620 moveTo(VPointF(x + cx, y + cy));
622 for (int i = 0; i < numPoints; i++) {
623 float radius = longSegment ? outerRadius : innerRadius;
624 float dTheta = halfAnglePerPoint;
625 if (partialPointRadius != 0 && i == numPoints - 2) {
626 dTheta = anglePerPoint * partialPointAmount / 2.0;
628 if (partialPointRadius != 0 && i == numPoints - 1) {
629 radius = partialPointRadius;
633 x = (float) (radius * cos(currentAngle));
634 y = (float) (radius * sin(currentAngle));
636 if (innerRoundness == 0 && outerRoundness == 0) {
637 lineTo(VPointF(x + cx, y + cy));
639 float cp1Theta = (float) (atan2(previousY, previousX) - M_PI / 2.0);
640 float cp1Dx = (float) cos(cp1Theta);
641 float cp1Dy = (float) sin(cp1Theta);
643 float cp2Theta = (float) (atan2(y, x) - M_PI / 2.0);
644 float cp2Dx = (float) cos(cp2Theta);
645 float cp2Dy = (float) sin(cp2Theta);
647 float cp1Roundness = longSegment ? innerRoundness : outerRoundness;
648 float cp2Roundness = longSegment ? outerRoundness : innerRoundness;
649 float cp1Radius = longSegment ? innerRadius : outerRadius;
650 float cp2Radius = longSegment ? outerRadius : innerRadius;
652 float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * cp1Dx / points;
653 float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * cp1Dy / points;
654 float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * cp2Dx / points;
655 float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * cp2Dy / points;
657 if ((partialPointAmount != 0) &&
658 ((i == 0) || (i == numPoints - 1))) {
659 cp1x *= partialPointAmount;
660 cp1y *= partialPointAmount;
661 cp2x *= partialPointAmount;
662 cp2y *= partialPointAmount;
665 cubicTo(VPointF(previousX - cp1x + cx, previousY - cp1y + cy),
666 VPointF(x + cp2x + cx, y + cp2y + cy),
667 VPointF(x + cx, y + cy));
670 currentAngle += dTheta;
671 longSegment = !longSegment;
677 void VPath::addPolystarPolygon(float startAngle, float cx, float cy, float points,
678 float radius, float roundness,
679 VPath::Direction dir)
681 // TODO: Direction feature is missing
682 // TODO: Need to support floating point number for number of points
683 const static float POLYGON_MAGIC_NUMBER = 0.25;
684 float currentAngle = (startAngle - 90.0) * M_PI / 180.0;
689 float anglePerPoint = (float) (2.0 * M_PI / floor(points));
690 int numPoints = (int) floor(points);
694 currentAngle = (currentAngle - 90.0) * M_PI / 180.0;
695 x = (float) (radius * cos(currentAngle));
696 y = (float) (radius * sin(currentAngle));
697 currentAngle += anglePerPoint;
699 moveTo(VPointF(x + cx, y + cy));
701 for (int i = 0; i < numPoints; i++) {
704 x = (float) (radius * cos(currentAngle));
705 y = (float) (radius * sin(currentAngle));
707 if (roundness != 0) {
708 float cp1Theta = (float) (atan2(previousY, previousX) - M_PI / 2.0);
709 float cp1Dx = (float) cos(cp1Theta);
710 float cp1Dy = (float) sin(cp1Theta);
712 float cp2Theta = (float) (atan2(y, x) - M_PI / 2.0);
713 float cp2Dx = (float) cos(cp2Theta);
714 float cp2Dy = (float) sin(cp2Theta);
716 float cp1x = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dx;
717 float cp1y = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dy;
718 float cp2x = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dx;
719 float cp2y = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dy;
721 cubicTo(VPointF(previousX - cp1x + cx, previousY - cp1y + cy),
722 VPointF(x + cp2x + cx, y + cp2y + cy),
725 lineTo(VPointF(x + cx, y + cy));
728 currentAngle += anglePerPoint;
734 void VPath::transform(const VMatrix &m)
736 if (isEmpty()) return;