File indexing completed on 2025-01-26 04:04:57

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
0003  *  SPDX-FileCopyrightText: 2020 Sharaf Zaman <sharafzaz121@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 #include "SvgMeshPatch.h"
0008 
0009 #include <array>
0010 #include <math.h>
0011 #include <QDebug>
0012 #include <kis_global.h>
0013 
0014 
0015 inline QPointF lerp(const QPointF& p1, const QPointF& p2, qreal t)
0016 {
0017     return (1 - t) * p1 + t * p2;
0018 }
0019 
0020 void deCasteljau(const std::array<QPointF, 4>& points,
0021                  qreal t, QPointF *p1, QPointF *p2,
0022                  QPointF *p3, QPointF *p4, QPointF *p5)
0023 {
0024     QPointF q[4];
0025 
0026     q[0] = points[0];
0027     q[1] = points[1];
0028     q[2] = points[2];
0029     q[3] = points[3];
0030 
0031     // points of the new segment after the split point
0032     QPointF p[3];
0033 
0034     // the De Casteljau algorithm
0035     for (unsigned short j = 1; j <= 3; ++j) {
0036         for (unsigned short i = 0; i <= 3 - j; ++i) {
0037             q[i] = (1.0 - t) * q[i] + t * q[i + 1];
0038         }
0039         p[j - 1] = q[0];
0040     }
0041 
0042     if (p1)
0043         *p1 = p[0];
0044     if (p2)
0045         *p2 = p[1];
0046     if (p3)
0047         *p3 = p[2];
0048     if (p4)
0049         *p4 = q[1];
0050     if (p5)
0051         *p5 = q[2];
0052 }
0053 
0054 QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitAt(const std::array<QPointF, 4>& points, qreal t)
0055 {
0056     QPointF newCP2, newCP1, splitP, splitCP1, splitCP2;
0057     deCasteljau(points, t, &newCP2, &splitCP1, &splitP, &splitCP2, &newCP1);
0058     return {{points[0], newCP2, splitCP1, splitP},
0059             {splitP, splitCP2, newCP1, points[3]}};
0060 }
0061 
0062 SvgMeshPatch::SvgMeshPatch(QPointF startingPoint)
0063     : m_newPath(true)
0064     , m_startingPoint(startingPoint)
0065     , m_parametricCoords({QPointF(0, 0), {1, 0}, {1, 1}, {0, 1}})
0066 {
0067 }
0068 
0069 SvgMeshPatch::SvgMeshPatch(const SvgMeshPatch& other)
0070     : m_newPath(other.m_newPath)
0071     , m_startingPoint(other.m_startingPoint)
0072     , m_nodes(other.m_nodes)
0073     , controlPoints(other.controlPoints)
0074     , m_parametricCoords({QPointF(0, 0), {1, 0}, {1, 1}, {0, 1}})
0075 {
0076 }
0077 
0078 void SvgMeshPatch::moveTo(const QPointF& p)
0079 {
0080     controlPoints[counter][0] = p;
0081 }
0082 
0083 void SvgMeshPatch::lineTo(const QPointF& p)
0084 {
0085     controlPoints[counter][1] = lerp(controlPoints[counter][0], p, 1.0 / 3);
0086     controlPoints[counter][2] = lerp(controlPoints[counter][0], p, 2.0 / 3);
0087     controlPoints[counter][3] = p;
0088     counter++;
0089     if (counter < Size)
0090         controlPoints[counter][0] = p;
0091 }
0092 
0093 void SvgMeshPatch::curveTo(const QPointF& c1, const QPointF& c2, const QPointF& p)
0094 {
0095     controlPoints[counter][1] = c1;
0096     controlPoints[counter][2] = c2;
0097     controlPoints[counter][3] = p;
0098     counter++;
0099     if (counter < Size)
0100         controlPoints[counter][0] = p;
0101 }
0102 
0103 SvgMeshStop SvgMeshPatch::getStop(SvgMeshPatch::Type type) const
0104 {
0105     return m_nodes[type];
0106 }
0107 
0108 QPointF SvgMeshPatch::segmentPointAt(Type type, qreal t) const
0109 {
0110     QPointF p;
0111     deCasteljau(controlPoints[type], t, 0, 0, &p, 0, 0);
0112     return p;
0113 }
0114 
0115 QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> SvgMeshPatch::segmentSplitAt(Type type, qreal t) const
0116 {
0117     return splitAt(controlPoints[type], t);
0118 }
0119 
0120 std::array<QPointF, 4> SvgMeshPatch::getSegment(Type type) const
0121 {
0122     return controlPoints[type];
0123 }
0124 
0125 QPainterPath SvgMeshPatch::getPath() const
0126 {
0127     QPainterPath path;
0128     path.moveTo(controlPoints[Top][0]);
0129     for (const auto& i: controlPoints) {
0130         path.cubicTo(i[1], i[2], i[3]);
0131     }
0132     return path;
0133 }
0134 
0135 QRectF SvgMeshPatch::boundingRect() const
0136 {
0137     return getPath().boundingRect();
0138 }
0139 
0140 QSizeF SvgMeshPatch::size() const
0141 {
0142     return boundingRect().size();
0143 }
0144 
0145 std::array<QPointF, 4> SvgMeshPatch::getMidCurve(bool isVertical) const
0146 {
0147     std::array<QPointF, 4> p;
0148     std::array<QPointF, 4> curvedBoundary0;
0149     std::array<QPointF, 4> curvedBoundary1;
0150 
0151     QPointF midpointRuled0;
0152     QPointF midpointRuled1;
0153 
0154     if (isVertical) {
0155         curvedBoundary0 = getSegment(Right);
0156         curvedBoundary1 = getSegment(Left);
0157 
0158         midpointRuled0 = segmentPointAt(Top, 0.5);
0159         midpointRuled1 = segmentPointAt(Bottom, 0.5);
0160     } else {
0161         curvedBoundary0 = getSegment(Top);
0162         curvedBoundary1 = getSegment(Bottom);
0163 
0164         midpointRuled0 = segmentPointAt(Left, 0.5);
0165         midpointRuled1 = segmentPointAt(Right, 0.5);
0166     }
0167 
0168     // we have to reverse it, cB1 & cB2 are in opposite direction
0169     std::reverse(curvedBoundary1.begin(), curvedBoundary1.end());
0170 
0171     // Sum of two Bezier curve is a Bezier curve
0172     QVector<QPointF> midCurved = {
0173         (curvedBoundary0[0] + curvedBoundary1[0]) / 2,
0174         (curvedBoundary0[1] + curvedBoundary1[1]) / 2,
0175         (curvedBoundary0[2] + curvedBoundary1[2]) / 2,
0176         (curvedBoundary0[3] + curvedBoundary1[3]) / 2,
0177     };
0178 
0179     // line cutting the bilinear surface in middle
0180     QPointF x_2_1 = lerp(midpointRuled0, midpointRuled1, 1.0 / 3);
0181     QPointF x_2_2 = lerp(midpointRuled0, midpointRuled1, 2.0 / 3);
0182 
0183     // line cutting rulled surface in middle
0184     QPointF x_3_1 = lerp(midCurved[0], midCurved[3], 1.0 / 3);
0185     QPointF x_3_2 = lerp(midCurved[0], midCurved[3], 2.0 / 3);
0186 
0187 
0188     p[0] = midpointRuled0;
0189 
0190     // X_1 = x_1_1 + x_2_1 - x_3_1
0191     p[1] = midCurved[1] + x_2_1 - x_3_1;
0192 
0193     // X_2 = x_1_2 + x_2_2 - x_3_2
0194     p[2] = midCurved[2] + x_2_2 - x_3_2;
0195 
0196     p[3] = midpointRuled1;
0197 
0198     return p;
0199 }
0200 
0201 void SvgMeshPatch::subdivideHorizontally(QVector<SvgMeshPatch*>& subdivided,
0202                                          const QVector<QColor>& colors) const
0203 {
0204     const QPair<SvgMeshPath, SvgMeshPath> splitRight = segmentSplitAt(Right, 0.5);
0205     const QPair<SvgMeshPath, SvgMeshPath> splitLeft  = segmentSplitAt(Left, 0.5);
0206 
0207     SvgMeshPath midHor = getMidCurve(/*isVertical = */ false);
0208     SvgMeshPath rMidHor = midHor;
0209     std::reverse(rMidHor.begin(), rMidHor.end());
0210 
0211     QColor c1 = getStop(Top).color;
0212     QColor c2 = getStop(Right).color;
0213     QColor c3 = getStop(Bottom).color;
0214     QColor c4 = getStop(Left).color;
0215     QColor midc23 = colors[1];
0216     QColor midc41 = colors[3];
0217 
0218     QPointF midRightParametric = getMidpointParametric(Right);
0219     QPointF midLeftParametric = getMidpointParametric(Left);
0220 
0221     SvgMeshPatch *patch = new SvgMeshPatch(getSegment(Top)[0]);
0222     patch->addStop(getSegment(Top), c1, Top);
0223     patch->addStop(splitRight.first, c2, Right);
0224     patch->addStop(rMidHor, midc23, Bottom);
0225     patch->addStop(splitLeft.second, midc41, Left);
0226     patch->m_parametricCoords = {
0227         m_parametricCoords[0],
0228         m_parametricCoords[1],
0229         midRightParametric,
0230         midLeftParametric
0231     };
0232     subdivided.append(patch);
0233 
0234     patch = new SvgMeshPatch(midHor[0]);
0235     patch->addStop(midHor, midc41, Top);
0236     patch->addStop(splitRight.second, midc23, Right);
0237     patch->addStop(getSegment(Bottom), c3, Bottom);
0238     patch->addStop(splitLeft.first,c4, Left);
0239     patch->m_parametricCoords = {
0240         midLeftParametric,
0241         midRightParametric,
0242         m_parametricCoords[2],
0243         m_parametricCoords[3]
0244     };
0245     subdivided.append(patch);
0246 }
0247 
0248 void SvgMeshPatch::subdivideVertically(QVector<SvgMeshPatch*>& subdivided,
0249                                        const QVector<QColor>& colors) const
0250 {
0251     const QPair<SvgMeshPath, SvgMeshPath> splitTop    = segmentSplitAt(Top, 0.5);
0252     const QPair<SvgMeshPath, SvgMeshPath> splitBottom = segmentSplitAt(Bottom, 0.5);
0253 
0254     SvgMeshPath midVer = getMidCurve(/*isVertical = */ true);
0255     SvgMeshPath rMidVer = midVer;
0256     std::reverse(rMidVer.begin(), rMidVer.end());
0257 
0258     QColor c1 = getStop(Top).color;
0259     QColor c2 = getStop(Right).color;
0260     QColor c3 = getStop(Bottom).color;
0261     QColor c4 = getStop(Left).color;
0262     QColor midc12 = colors[0];
0263     QColor midc34 = colors[2];
0264 
0265     QPointF midTopParametric = getMidpointParametric(Top);
0266     QPointF midBottomParametric = getMidpointParametric(Bottom);
0267 
0268     SvgMeshPatch *patch = new SvgMeshPatch(splitTop.first[0]);
0269     patch->addStop(splitTop.first, c1, Top);
0270     patch->addStop(midVer, midc12, Right);
0271     patch->addStop(splitBottom.second, midc34, Bottom);
0272     patch->addStop(getSegment(Left), c4, Left);
0273     patch->m_parametricCoords = {
0274         m_parametricCoords[0],
0275         midTopParametric,
0276         midBottomParametric,
0277         m_parametricCoords[3]
0278     };
0279     subdivided.append(patch);
0280 
0281     patch = new SvgMeshPatch(splitTop.second[0]);
0282     patch->addStop(splitTop.second, midc12, Top);
0283     patch->addStop(getSegment(Right), c2, Right);
0284     patch->addStop(splitBottom.first, c3, Bottom);
0285     patch->addStop(rMidVer, midc34, Left);
0286     patch->m_parametricCoords = {
0287         midTopParametric,
0288         m_parametricCoords[1],
0289         m_parametricCoords[2],
0290         midBottomParametric
0291     };
0292     subdivided.append(patch);
0293 }
0294 
0295 void SvgMeshPatch::subdivide(QVector<SvgMeshPatch*>& subdivided,
0296                              const QVector<QColor>& colors) const
0297 {
0298     KIS_ASSERT(colors.size() == 5);
0299 
0300     // The orientation is left to right and top to bottom, which means
0301     // Eg. the first part of splitTop is TopLeft and the second part is TopRight
0302     // Similarly the first part of splitRight is RightTop, but the first part of
0303     // splitLeft is splitLeft.second (once again, in Top to Bottom  convention)
0304     const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitTop    = segmentSplitAt(Top, 0.5);
0305     const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitRight  = segmentSplitAt(Right, 0.5);
0306     const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitBottom = segmentSplitAt(Bottom, 0.5);
0307     const QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> splitLeft   = segmentSplitAt(Left, 0.5);
0308 
0309     // The way the curve and the colors at the corners are arranged before and after subdivision
0310     //
0311     //              midc12
0312     //       c1       +       c2
0313     //        +---------------+
0314     //        |       |       |
0315     //        |       | midVer|
0316     //        |       | <     |
0317     // midc41 +---------------+ midc23
0318     //        |  ^    |       |
0319     //        | midHor|       |
0320     //        |       |       |
0321     //        +---------------+
0322     //       c4       +       c3
0323     //              midc43
0324     //
0325     //             
0326     //  midHor --> left to right
0327     //  midVer --> top to bottom
0328 
0329 
0330     QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> midHor = splitAt(getMidCurve(/*isVertical = */ false), 0.5);
0331     QPair<std::array<QPointF, 4>, std::array<QPointF, 4>> midVer = splitAt(getMidCurve(/*isVertical = */ true), 0.5);
0332 
0333     // middle curve is shared among the two, so we need both directions
0334     std::array<QPointF, 4> reversedMidHorFirst = midHor.first;
0335     std::reverse(reversedMidHorFirst.begin(), reversedMidHorFirst.end());
0336     std::array<QPointF, 4> reversedMidHorSecond = midHor.second;
0337     std::reverse(reversedMidHorSecond.begin(), reversedMidHorSecond.end());
0338 
0339     std::array<QPointF, 4> reversedMidVerFirst = midVer.first;
0340     std::reverse(reversedMidVerFirst.begin(), reversedMidVerFirst.end());
0341     std::array<QPointF, 4> reversedMidVerSecond = midVer.second;
0342     std::reverse(reversedMidVerSecond.begin(), reversedMidVerSecond.end());
0343 
0344     QColor c1 = getStop(Top).color;
0345     QColor c2 = getStop(Right).color;
0346     QColor c3 = getStop(Bottom).color;
0347     QColor c4 = getStop(Left).color;
0348     QColor midc12 = colors[0];
0349     QColor midc23 = colors[1];
0350     QColor midc34 = colors[2];
0351     QColor midc41 = colors[3];
0352     QColor center = colors[4];
0353 
0354     // mid points in parametric space
0355     QPointF midTopP     = getMidpointParametric(Top);
0356     QPointF midRightP   = getMidpointParametric(Right);
0357     QPointF midBottomP  = getMidpointParametric(Bottom);
0358     QPointF midLeftP    = getMidpointParametric(Left);
0359     QPointF centerP     = 0.5 * (midTopP + midBottomP);
0360 
0361     // patch 1: TopLeft/NorthWest
0362     SvgMeshPatch *patch = new SvgMeshPatch(splitTop.first[0]);
0363     patch->addStop(splitTop.first, c1, Top);
0364     patch->addStop(midVer.first, midc12, Right);
0365     patch->addStop(reversedMidHorFirst, center, Bottom);
0366     patch->addStop(splitLeft.second, midc41, Left);
0367     patch->m_parametricCoords = {
0368         m_parametricCoords[0],
0369         midTopP,
0370         centerP,
0371         midLeftP
0372     };
0373     subdivided.append(patch);
0374 
0375     // patch 2: TopRight/NorthRight
0376     patch = new SvgMeshPatch(splitTop.second[0]);
0377     patch->addStop(splitTop.second, midc12, Top);
0378     patch->addStop(splitRight.first, c2, Right);
0379     patch->addStop(reversedMidHorSecond, midc23, Bottom);
0380     patch->addStop(reversedMidVerFirst, center, Left);
0381     patch->m_parametricCoords = {
0382         midTopP,
0383         m_parametricCoords[1],
0384         midRightP,
0385         centerP
0386     };
0387     subdivided.append(patch);
0388 
0389     // patch 3: BottomLeft/SouthWest
0390     patch = new SvgMeshPatch(midHor.first[0]);
0391     patch->addStop(midHor.first, midc41, Top);
0392     patch->addStop(midVer.second, center, Right);
0393     patch->addStop(splitBottom.second, midc34, Bottom);
0394     patch->addStop(splitLeft.first, c4, Left);
0395     patch->m_parametricCoords = {
0396         midLeftP,
0397         centerP,
0398         midBottomP,
0399         m_parametricCoords[3]
0400     };
0401     subdivided.append(patch);
0402 
0403     // patch 4: BottomRight/SouthEast
0404     patch = new SvgMeshPatch(midHor.second[0]);
0405     patch->addStop(midHor.second, center, Top);
0406     patch->addStop(splitRight.second, midc23, Right);
0407     patch->addStop(splitBottom.first, c3, Bottom);
0408     patch->addStop(reversedMidVerSecond, midc34, Left);
0409     patch->m_parametricCoords = {
0410         centerP,
0411         midRightP,
0412         m_parametricCoords[2],
0413         midBottomP
0414     };
0415     subdivided.append(patch);
0416 }
0417 
0418 static qreal controlrectLen(const SvgMeshPath &path) {
0419     return QLineF(path[0], path[1]).length() +
0420         QLineF(path[1], path[2]).length() +
0421         QLineF(path[2], path[3]).length();
0422 }
0423 
0424 bool SvgMeshPatch::isDivisibleVertically() const
0425 {
0426     // I arrived at this number by the virtue called trial 'n error
0427     const qreal minlength = 1.7;
0428     const qreal line1 = QLineF(controlPoints[Top][0], controlPoints[Top][3]).length();
0429     const qreal control1 = controlrectLen(getSegment(Top));
0430 
0431     // a decent average, thanks to Khronos's forums
0432     if ((line1 + control1 / 2) < minlength) {
0433         return false;
0434     }
0435 
0436     const qreal line2 = QLineF(controlPoints[Bottom][0], controlPoints[Bottom][3]).length();
0437     const qreal control2 = controlrectLen(getSegment(Bottom));
0438     if ((line2 + control2 / 2) < minlength) {
0439         return false;
0440     }
0441 
0442     return true;
0443 }
0444 
0445 bool SvgMeshPatch::isDivisibleHorizontally() const
0446 {
0447     // I arrived at this number by the virtue called trial 'n error
0448     const qreal minlength = 1.7;
0449 
0450     // a decent average, thanks to Khronos's forums
0451     const qreal line1 = QLineF(controlPoints[Right][0], controlPoints[Right][3]).length();
0452     const qreal control1 = controlrectLen(getSegment(Right));
0453     if ((line1 + control1 / 2) < minlength) {
0454         return false;
0455     }
0456 
0457     const qreal line2 = QLineF(controlPoints[Left][0], controlPoints[Left][3]).length();
0458     const qreal control2 = controlrectLen(getSegment(Left));
0459     if ((line2 + control2 / 2) < minlength) {
0460         return false;
0461     }
0462 
0463     return true;
0464 }
0465 
0466 void SvgMeshPatch::addStop(const QString& pathStr,
0467                            QColor color,
0468                            Type edge,
0469                            bool pathIncomplete,
0470                            QPointF lastPoint)
0471 {
0472     SvgMeshStop node(color, m_startingPoint);
0473     m_nodes[edge] = node;
0474 
0475     m_startingPoint = parseMeshPath(pathStr, pathIncomplete, lastPoint);
0476 }
0477 
0478 void SvgMeshPatch::addStop(const std::array<QPointF, 4>& pathPoints, QColor color, Type edge)
0479 {
0480     SvgMeshStop stop(color, pathPoints[0]);
0481     m_nodes[edge] = stop;
0482 
0483     if (edge == SvgMeshPatch::Top) {
0484         moveTo(pathPoints[0]);
0485         m_newPath = false;
0486     }
0487 
0488     curveTo(pathPoints[1], pathPoints[2], pathPoints[3]);
0489     m_startingPoint = pathPoints[3];
0490 }
0491 
0492 
0493 void SvgMeshPatch::addStopLinear(const std::array<QPointF, 2>& pathPoints, QColor color, Type edge)
0494 {
0495     SvgMeshStop stop(color, pathPoints[0]);
0496     m_nodes[edge] = stop;
0497 
0498     if (edge == SvgMeshPatch::Top) {
0499         moveTo(pathPoints[0]);
0500         m_newPath = false;
0501     }
0502 
0503     lineTo(pathPoints[1]);
0504     m_startingPoint = pathPoints[1];
0505 }
0506 
0507 void SvgMeshPatch::modifyPath(SvgMeshPatch::Type type, std::array<QPointF, 4> newPath)
0508 {
0509     controlPoints[type] = newPath;
0510     m_nodes[type].point = newPath[0];
0511 }
0512 
0513 void SvgMeshPatch::modifyCorner(SvgMeshPatch::Type type, const QPointF &delta)
0514 {
0515     controlPoints[type][0] -= delta;
0516     controlPoints[type][1] -= delta;
0517     m_nodes[type].point = controlPoints[type][0];
0518 
0519     controlPoints[(Size + type - 1) % Size][3] -= delta;
0520     controlPoints[(Size + type - 1) % Size][2] -= delta;
0521 }
0522 
0523 void SvgMeshPatch::setStopColor(SvgMeshPatch::Type type, const QColor &color)
0524 {
0525     m_nodes[type].color = color;
0526 }
0527 
0528 void SvgMeshPatch::setTransform(const QTransform& matrix)
0529 {
0530     m_startingPoint = matrix.map(m_startingPoint);
0531     for (int i = 0; i < Size; ++i) {
0532         m_nodes[i].point = matrix.map(m_nodes[i].point);
0533         for (int j = 0; j < 4; ++j) {
0534             controlPoints[i][j] = matrix.map(controlPoints[i][j]);
0535         }
0536     }
0537 }
0538 
0539 int SvgMeshPatch::countPoints() const
0540 {
0541     return m_nodes.size();
0542 }
0543 
0544 
0545 QPointF SvgMeshPatch::parseMeshPath(const QString& s, bool pathIncomplete, const QPointF lastPoint)
0546 {
0547     // bits and pieces from KoPathShapeLoader, see the copyright above
0548     if (!s.isEmpty()) {
0549         QString d = s;
0550         d.replace(',', ' ');
0551         d = d.simplified();
0552 
0553         const QByteArray buffer = d.toLatin1();
0554         const char *ptr = buffer.constData();
0555         qreal curx = m_startingPoint.x();
0556         qreal cury = m_startingPoint.y();
0557         qreal tox, toy, x1, y1, x2, y2;
0558         bool relative = false;
0559         char command = *(ptr++);
0560 
0561         if (m_newPath) {
0562             moveTo(m_startingPoint);
0563             m_newPath = false;
0564         }
0565 
0566        while (*ptr == ' ')
0567            ++ptr;
0568 
0569        switch (command) {
0570        case 'l':
0571            relative = true;
0572            Q_FALLTHROUGH();
0573        case 'L': {
0574            ptr = getCoord(ptr, tox);
0575            ptr = getCoord(ptr, toy);
0576 
0577            if (relative) {
0578                tox = curx + tox;
0579                toy = cury + toy;
0580            }
0581 
0582            if (pathIncomplete) {
0583                tox = lastPoint.x();
0584                toy = lastPoint.y();
0585            }
0586 
0587            // we convert lines to cubic curve
0588            lineTo({tox, toy});
0589            break;
0590        }
0591        case 'c':
0592            relative = true;
0593            Q_FALLTHROUGH();
0594        case 'C': {
0595            ptr = getCoord(ptr, x1);
0596            ptr = getCoord(ptr, y1);
0597            ptr = getCoord(ptr, x2);
0598            ptr = getCoord(ptr, y2);
0599            ptr = getCoord(ptr, tox);
0600            ptr = getCoord(ptr, toy);
0601 
0602            if (relative) {
0603                x1  = curx + x1;
0604                y1  = cury + y1;
0605                x2  = curx + x2;
0606                y2  = cury + y2;
0607                tox = curx + tox;
0608                toy = cury + toy;
0609            }
0610 
0611            if (pathIncomplete) {
0612                tox = lastPoint.x();
0613                toy = lastPoint.y();
0614            }
0615 
0616            curveTo(QPointF(x1, y1), QPointF(x2, y2), QPointF(tox, toy));
0617            break;
0618        }
0619 
0620        default: {
0621            qWarning() << "SvgMeshPatch::parseMeshPath: Bad command \"" << command << "\"";
0622            return QPointF();
0623        }
0624        }
0625        return {tox, toy};
0626     }
0627     return QPointF();
0628 }
0629 
0630 const char* SvgMeshPatch::getCoord(const char* ptr, qreal& number)
0631 {
0632     // copied from KoPathShapeLoader, see the copyright above
0633     int integer, exponent;
0634     qreal decimal, frac;
0635     int sign, expsign;
0636 
0637     exponent = 0;
0638     integer = 0;
0639     frac = 1.0;
0640     decimal = 0;
0641     sign = 1;
0642     expsign = 1;
0643 
0644     // read the sign
0645     if (*ptr == '+')
0646         ++ptr;
0647     else if (*ptr == '-') {
0648         ++ptr;
0649         sign = -1;
0650     }
0651 
0652     // read the integer part
0653     while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
0654         integer = (integer * 10) + *(ptr++) - '0';
0655     if (*ptr == '.') { // read the decimals
0656         ++ptr;
0657         while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
0658             decimal += (*(ptr++) - '0') * (frac *= 0.1);
0659     }
0660 
0661     if (*ptr == 'e' || *ptr == 'E') { // read the exponent part
0662         ++ptr;
0663 
0664         // read the sign of the exponent
0665         if (*ptr == '+')
0666             ++ptr;
0667         else if (*ptr == '-') {
0668             ++ptr;
0669             expsign = -1;
0670         }
0671 
0672         exponent = 0;
0673         while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') {
0674             exponent *= 10;
0675             exponent += *ptr - '0';
0676             ++ptr;
0677         }
0678     }
0679     number = integer + decimal;
0680     number *= sign * pow((qreal)10, qreal(expsign * exponent));
0681 
0682     // skip the following space
0683     if (*ptr == ' ')
0684         ++ptr;
0685 
0686     return ptr;
0687 }