// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qquickpath_p.h"
#include "qquickpath_p_p.h"
#include "qquicksvgparser_p.h"

#include <QSet>
#include <QTime>

#include <private/qbezier_p.h>
#include <QtCore/qmath.h>
#include <QtCore/private/qnumeric_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype PathElement
    \instantiates QQuickPathElement
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief PathElement is the base path type.

    This type is the base for all path types.  It cannot
    be instantiated.

    \sa Path, PathAttribute, PathPercent, PathLine, PathPolyline, PathQuad, PathCubic, PathArc,
        PathAngleArc, PathCurve, PathSvg
*/

/*!
    \qmltype Path
    \instantiates QQuickPath
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a path for use by \l PathView and \l Shape.

    A Path is composed of one or more path segments - PathLine, PathPolyline, PathQuad,
    PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg.

    The spacing of the items along the Path can be adjusted via a
    PathPercent object.

    PathAttribute allows named attributes with values to be defined
    along the path.

    Path and the other types for specifying path elements are shared between
    \l PathView and \l Shape. The following table provides an overview of the
    applicability of the various path elements:

    \table
    \header
        \li Element
        \li PathView
        \li Shape
        \li Shape, software
    \row
        \li PathMove
        \li N/A
        \li Yes
        \li Yes
    \row
        \li PathLine
        \li Yes
        \li Yes
        \li Yes
    \row
        \li PathPolyline
        \li Yes
        \li Yes
        \li Yes
    \row
    \li PathMultiLine
        \li Yes
        \li Yes
        \li Yes
    \row
        \li PathQuad
        \li Yes
        \li Yes
        \li Yes
    \row
        \li PathCubic
        \li Yes
        \li Yes
        \li Yes
    \row
        \li PathArc
        \li Yes
        \li Yes
        \li Yes
    \row
        \li PathAngleArc
        \li Yes
        \li Yes
        \li Yes
    \row
        \li PathSvg
        \li Yes
        \li Yes
        \li Yes
    \row
        \li PathAttribute
        \li Yes
        \li N/A
        \li N/A
    \row
        \li PathPercent
        \li Yes
        \li N/A
        \li N/A
    \row
        \li PathCurve
        \li Yes
        \li No
        \li No
    \endtable

    \note Path is a non-visual type; it does not display anything on its own.
    To draw a path, use \l Shape.

    \sa PathView, Shape, PathAttribute, PathPercent, PathLine, PathPolyline, PathMove, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg
*/
QQuickPath::QQuickPath(QObject *parent)
 : QObject(*(new QQuickPathPrivate), parent)
{
}

QQuickPath::QQuickPath(QQuickPathPrivate &dd, QObject *parent)
    : QObject(dd, parent)
{
}

QQuickPath::~QQuickPath()
{
}

/*!
    \qmlproperty real QtQuick::Path::startX
    \qmlproperty real QtQuick::Path::startY
    These properties hold the starting position of the path.
*/
qreal QQuickPath::startX() const
{
    Q_D(const QQuickPath);
    return d->startX.isNull ? 0 : d->startX.value;
}

void QQuickPath::setStartX(qreal x)
{
    Q_D(QQuickPath);
    if (d->startX.isValid() && qFuzzyCompare(x, d->startX))
        return;
    d->startX = x;
    emit startXChanged();
    processPath();
}

bool QQuickPath::hasStartX() const
{
    Q_D(const QQuickPath);
    return d->startX.isValid();
}

qreal QQuickPath::startY() const
{
    Q_D(const QQuickPath);
    return d->startY.isNull ? 0 : d->startY.value;
}

void QQuickPath::setStartY(qreal y)
{
    Q_D(QQuickPath);
    if (d->startY.isValid() && qFuzzyCompare(y, d->startY))
        return;
    d->startY = y;
    emit startYChanged();
    processPath();
}

bool QQuickPath::hasStartY() const
{
    Q_D(const QQuickPath);
    return d->startY.isValid();
}

/*!
    \qmlproperty bool QtQuick::Path::closed
    This property holds whether the start and end of the path are identical.
*/
bool QQuickPath::isClosed() const
{
    Q_D(const QQuickPath);
    return d->closed;
}

/*!
    \qmlproperty list<PathElement> QtQuick::Path::pathElements
    This property holds the objects composing the path.

    \qmldefault

    A path can contain the following path objects:
    \list
        \li \l PathLine - a straight line to a given position.
        \li \l PathPolyline - a polyline specified as a list of coordinates.
        \li \l PathMultiline - a list of polylines specified as a list of lists of coordinates.
        \li \l PathQuad - a quadratic Bezier curve to a given position with a control point.
        \li \l PathCubic - a cubic Bezier curve to a given position with two control points.
        \li \l PathArc - an arc to a given position with a radius.
        \li \l PathAngleArc - an arc specified by center point, radii, and angles.
        \li \l PathSvg - a path specified as an SVG path data string.
        \li \l PathCurve - a point on a Catmull-Rom curve.
        \li \l PathAttribute - an attribute at a given position in the path.
        \li \l PathPercent - a way to spread out items along various segments of the path.
    \endlist

    \snippet qml/pathview/pathattributes.qml 2
*/

QQmlListProperty<QQuickPathElement> QQuickPath::pathElements()
{
    return QQmlListProperty<QQuickPathElement>(this,
                                               nullptr,
                                               pathElements_append,
                                               pathElements_count,
                                               pathElements_at,
                                               pathElements_clear);
}

static QQuickPathPrivate *privatePath(QObject *object)
{
    QQuickPath *path = static_cast<QQuickPath*>(object);

    return QQuickPathPrivate::get(path);
}

QQuickPathElement *QQuickPath::pathElements_at(QQmlListProperty<QQuickPathElement> *property, qsizetype index)
{
    QQuickPathPrivate *d = privatePath(property->object);

    return d->_pathElements.at(index);
}

void QQuickPath::pathElements_append(QQmlListProperty<QQuickPathElement> *property, QQuickPathElement *pathElement)
{
    QQuickPathPrivate *d = privatePath(property->object);
    QQuickPath *path = static_cast<QQuickPath*>(property->object);

    d->_pathElements.append(pathElement);

    if (d->componentComplete) {
        QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement);
        if (curve)
            d->_pathCurves.append(curve);
        else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement))
            d->_pathTexts.append(text);
        else {
            QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement);
            if (attribute && !d->_attributes.contains(attribute->name()))
                d->_attributes.append(attribute->name());
        }

        path->processPath();

        connect(pathElement, SIGNAL(changed()), path, SLOT(processPath()));
    }
}

qsizetype QQuickPath::pathElements_count(QQmlListProperty<QQuickPathElement> *property)
{
    QQuickPathPrivate *d = privatePath(property->object);

    return d->_pathElements.size();
}

void QQuickPath::pathElements_clear(QQmlListProperty<QQuickPathElement> *property)
{
    QQuickPathPrivate *d = privatePath(property->object);
    QQuickPath *path = static_cast<QQuickPath*>(property->object);

    path->disconnectPathElements();
    d->_pathElements.clear();
    d->_pathCurves.clear();
    d->_pointCache.clear();
    d->_pathTexts.clear();
}

void QQuickPath::interpolate(int idx, const QString &name, qreal value)
{
    Q_D(QQuickPath);
    interpolate(d->_attributePoints, idx, name, value);
}

void QQuickPath::interpolate(QList<AttributePoint> &attributePoints, int idx, const QString &name, qreal value)
{
    if (!idx)
        return;

    qreal lastValue = 0;
    qreal lastPercent = 0;
    int search = idx - 1;
    while(search >= 0) {
        const AttributePoint &point = attributePoints.at(search);
        if (point.values.contains(name)) {
            lastValue = point.values.value(name);
            lastPercent = point.origpercent;
            break;
        }
        --search;
    }

    ++search;

    const AttributePoint &curPoint = attributePoints.at(idx);

    for (int ii = search; ii < idx; ++ii) {
        AttributePoint &point = attributePoints[ii];

        qreal val = lastValue + (value - lastValue) * (point.origpercent - lastPercent) / (curPoint.origpercent - lastPercent);
        point.values.insert(name, val);
    }
}

void QQuickPath::endpoint(const QString &name)
{
    Q_D(QQuickPath);
    const AttributePoint &first = d->_attributePoints.first();
    qreal val = first.values.value(name);
    for (int ii = d->_attributePoints.size() - 1; ii >= 0; ii--) {
        const AttributePoint &point = d->_attributePoints.at(ii);
        if (point.values.contains(name)) {
            for (int jj = ii + 1; jj < d->_attributePoints.size(); ++jj) {
                AttributePoint &setPoint = d->_attributePoints[jj];
                setPoint.values.insert(name, val);
            }
            return;
        }
    }
}

void QQuickPath::endpoint(QList<AttributePoint> &attributePoints, const QString &name)
{
    const AttributePoint &first = attributePoints.first();
    qreal val = first.values.value(name);
    for (int ii = attributePoints.size() - 1; ii >= 0; ii--) {
        const AttributePoint &point = attributePoints.at(ii);
        if (point.values.contains(name)) {
            for (int jj = ii + 1; jj < attributePoints.size(); ++jj) {
                AttributePoint &setPoint = attributePoints[jj];
                setPoint.values.insert(name, val);
            }
            return;
        }
    }
}

void QQuickPath::processPath()
{
    Q_D(QQuickPath);

    if (!d->componentComplete)
        return;

    d->_pointCache.clear();
    d->prevBez.isValid = false;

    if (d->isShapePath) {
        // This path is a ShapePath, so avoid extra overhead
        d->_path = createShapePath(QPointF(), QPointF(), d->pathLength, &d->closed);
    } else {
        d->_path = createPath(QPointF(), QPointF(), d->_attributes, d->pathLength, d->_attributePoints, &d->closed);
    }

    emit changed();
}

inline static void scalePath(QPainterPath &path, const QSizeF &scale)
{
    const qreal xscale = scale.width();
    const qreal yscale = scale.height();
    if (xscale == 1 && yscale ==  1)
        return;

    for (int i = 0; i < path.elementCount(); ++i) {
        const QPainterPath::Element &element = path.elementAt(i);
        path.setElementPositionAt(i, element.x * xscale, element.y * yscale);
    }
}

QPainterPath QQuickPath::createPath(const QPointF &startPoint, const QPointF &endPoint, const QStringList &attributes, qreal &pathLength, QList<AttributePoint> &attributePoints, bool *closed)
{
    Q_D(QQuickPath);

    pathLength = 0;
    attributePoints.clear();

    if (!d->componentComplete)
        return QPainterPath();

    QPainterPath path;

    AttributePoint first;
    for (int ii = 0; ii < attributes.size(); ++ii)
        first.values[attributes.at(ii)] = 0;
    attributePoints << first;

    qreal startX = d->startX.isValid() ? d->startX.value : startPoint.x();
    qreal startY = d->startY.isValid() ? d->startY.value : startPoint.y();
    path.moveTo(startX, startY);

    const QString percentString = QStringLiteral("_qfx_percent");

    bool usesPercent = false;
    int index = 0;
    for (QQuickPathElement *pathElement : std::as_const(d->_pathElements)) {
        if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement)) {
            QQuickPathData data;
            data.index = index;
            data.endPoint = endPoint;
            data.curves = d->_pathCurves;
            curve->addToPath(path, data);
            AttributePoint p;
            p.origpercent = path.length();
            attributePoints << p;
            ++index;
        } else if (QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement)) {
            AttributePoint &point = attributePoints.last();
            point.values[attribute->name()] = attribute->value();
            interpolate(attributePoints, attributePoints.size() - 1, attribute->name(), attribute->value());
        } else if (QQuickPathPercent *percent = qobject_cast<QQuickPathPercent *>(pathElement)) {
            AttributePoint &point = attributePoints.last();
            point.values[percentString] = percent->value();
            interpolate(attributePoints, attributePoints.size() - 1, percentString, percent->value());
            usesPercent = true;
        } else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement)) {
            text->addToPath(path);
        }
    }

    // Fixup end points
    const AttributePoint &last = attributePoints.constLast();
    for (int ii = 0; ii < attributes.size(); ++ii) {
        if (!last.values.contains(attributes.at(ii)))
            endpoint(attributePoints, attributes.at(ii));
    }
    if (usesPercent && !last.values.contains(percentString)) {
        d->_attributePoints.last().values[percentString] = 1;
        interpolate(d->_attributePoints.size() - 1, percentString, 1);
    }
    scalePath(path, d->scale);

    // Adjust percent
    qreal length = path.length();
    qreal prevpercent = 0;
    qreal prevorigpercent = 0;
    for (int ii = 0; ii < attributePoints.size(); ++ii) {
        const AttributePoint &point = attributePoints.at(ii);
        if (point.values.contains(percentString)) { //special string for QQuickPathPercent
            if ( ii > 0) {
                qreal scale = (attributePoints[ii].origpercent/length - prevorigpercent) /
                            (point.values.value(percentString)-prevpercent);
                attributePoints[ii].scale = scale;
            }
            attributePoints[ii].origpercent /= length;
            attributePoints[ii].percent = point.values.value(percentString);
            prevorigpercent = attributePoints.at(ii).origpercent;
            prevpercent = attributePoints.at(ii).percent;
        } else {
            attributePoints[ii].origpercent /= length;
            attributePoints[ii].percent = attributePoints.at(ii).origpercent;
        }
    }

    if (closed) {
        QPointF end = path.currentPosition();
        *closed = length > 0 && startX * d->scale.width() == end.x() && startY * d->scale.height() == end.y();
    }
    pathLength = length;

    return path;
}

QPainterPath QQuickPath::createShapePath(const QPointF &startPoint, const QPointF &endPoint, qreal &pathLength, bool *closed)
{
    Q_D(QQuickPath);

    if (!d->componentComplete)
        return QPainterPath();

    QPainterPath path;

    qreal startX = d->startX.isValid() ? d->startX.value : startPoint.x();
    qreal startY = d->startY.isValid() ? d->startY.value : startPoint.y();
    path.moveTo(startX, startY);

    int index = 0;
    for (QQuickCurve *curve : std::as_const(d->_pathCurves)) {
        QQuickPathData data;
        data.index = index;
        data.endPoint = endPoint;
        data.curves = d->_pathCurves;
        curve->addToPath(path, data);
        ++index;
    }

    for (QQuickPathText *text : std::as_const(d->_pathTexts))
        text->addToPath(path);

    if (closed) {
        QPointF end = path.currentPosition();
        *closed = startX == end.x() && startY == end.y();
    }
    scalePath(path, d->scale);

    // Note: Length of paths inside ShapePath is not used, so currently
    // length is always 0. This avoids potentially heavy path.length()
    //pathLength = path.length();
    pathLength = 0;

    return path;
}

void QQuickPath::classBegin()
{
    Q_D(QQuickPath);
    d->componentComplete = false;
}

void QQuickPath::disconnectPathElements()
{
    Q_D(const QQuickPath);

    for (QQuickPathElement *pathElement : d->_pathElements)
        disconnect(pathElement, SIGNAL(changed()), this, SLOT(processPath()));
}

void QQuickPath::connectPathElements()
{
    Q_D(const QQuickPath);

    for (QQuickPathElement *pathElement : d->_pathElements)
        connect(pathElement, SIGNAL(changed()), this, SLOT(processPath()));
}

void QQuickPath::gatherAttributes()
{
    Q_D(QQuickPath);

    QSet<QString> attributes;

    // First gather up all the attributes
    for (QQuickPathElement *pathElement : std::as_const(d->_pathElements)) {
        if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement))
            d->_pathCurves.append(curve);
        else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement))
            d->_pathTexts.append(text);
        else if (QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement))
            attributes.insert(attribute->name());
    }

    d->_attributes = attributes.values();
}

void QQuickPath::componentComplete()
{
    Q_D(QQuickPath);
    d->componentComplete = true;

    gatherAttributes();

    processPath();

    connectPathElements();
}

QPainterPath QQuickPath::path() const
{
    Q_D(const QQuickPath);
    return d->_path;
}

QStringList QQuickPath::attributes() const
{
    Q_D(const QQuickPath);
    if (!d->componentComplete) {
        QSet<QString> attrs;

        // First gather up all the attributes
        for (QQuickPathElement *pathElement : d->_pathElements) {
            if (QQuickPathAttribute *attribute =
                qobject_cast<QQuickPathAttribute *>(pathElement))
                attrs.insert(attribute->name());
        }
        return attrs.values();
    }
    return d->_attributes;
}

static inline QBezier nextBezier(const QPainterPath &path, int *current, qreal *bezLength, bool reverse = false)
{
    const int lastElement = reverse ? 0 : path.elementCount() - 1;
    const int start = reverse ? *current - 1 : *current + 1;
    for (int i=start; reverse ? i >= lastElement : i <= lastElement; reverse ? --i : ++i) {
        const QPainterPath::Element &e = path.elementAt(i);

        switch (e.type) {
        case QPainterPath::MoveToElement:
            break;
        case QPainterPath::LineToElement:
        {
            QLineF line(path.elementAt(i-1), e);
            *bezLength = line.length();
            QPointF a = path.elementAt(i-1);
            QPointF delta = e - a;
            *current = i;
            return QBezier::fromPoints(a, a + delta / 3, a + 2 * delta / 3, e);
        }
        case QPainterPath::CurveToElement:
        {
            QBezier b = QBezier::fromPoints(path.elementAt(i-1),
                                            e,
                                            path.elementAt(i+1),
                                            path.elementAt(i+2));
            *bezLength = b.length();
            *current = i;
            return b;
        }
        default:
            break;
        }
    }
    *current = lastElement;
    *bezLength = 0;
    return QBezier();
}

static inline int segmentCount(const QPainterPath &path, qreal pathLength)
{
    // In the really simple case of a single straight line we can interpolate without jitter
    // between just two points.
    if (path.elementCount() == 2
            && path.elementAt(0).type == QPainterPath::MoveToElement
            && path.elementAt(1).type == QPainterPath::LineToElement) {
        return 1;
    }
    // more points means less jitter between items as they move along the
    // path, but takes longer to generate
    return qCeil(pathLength*5);
}

//derivative of the equation
static inline qreal slopeAt(qreal t, qreal a, qreal b, qreal c, qreal d)
{
    return 3*t*t*(d - 3*c + 3*b - a) + 6*t*(c - 2*b + a) + 3*(b - a);
}

void QQuickPath::createPointCache() const
{
    Q_D(const QQuickPath);
    qreal pathLength = d->pathLength;
    if (pathLength <= 0 || qt_is_nan(pathLength))
        return;

    const int segments = segmentCount(d->_path, pathLength);
    const int lastElement = d->_path.elementCount() - 1;
    d->_pointCache.resize(segments+1);

    int currElement = -1;
    qreal bezLength = 0;
    QBezier currBez = nextBezier(d->_path, &currElement, &bezLength);
    qreal currLength = bezLength;
    qreal epc = currLength / pathLength;

    for (int i = 0; i < d->_pointCache.size(); i++) {
        //find which set we are in
        qreal prevPercent = 0;
        qreal prevOrigPercent = 0;
        for (int ii = 0; ii < d->_attributePoints.size(); ++ii) {
            qreal percent = qreal(i)/segments;
            const AttributePoint &point = d->_attributePoints.at(ii);
            if (percent < point.percent || ii == d->_attributePoints.size() - 1) { //### || is special case for very last item
                qreal elementPercent = (percent - prevPercent);

                qreal spc = prevOrigPercent + elementPercent * point.scale;

                while (spc > epc) {
                    if (currElement > lastElement)
                        break;
                    currBez = nextBezier(d->_path, &currElement, &bezLength);
                    if (bezLength == 0.0) {
                        currLength = pathLength;
                        epc = 1.0;
                        break;
                    }
                    currLength += bezLength;
                    epc = currLength / pathLength;
                }
                qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength;
                d->_pointCache[i] = currBez.pointAt(qBound(qreal(0), realT, qreal(1)));
                break;
            }
            prevOrigPercent = point.origpercent;
            prevPercent = point.percent;
        }
    }
}

void QQuickPath::invalidateSequentialHistory() const
{
    Q_D(const QQuickPath);
    d->prevBez.isValid = false;
}

/*!
    \qmlproperty size QtQuick::Path::scale

    This property holds the scale factor for the path.
    The width and height of \a scale can be different, to
    achieve anisotropic scaling.

    \note Setting this property will not affect the border width.

    \since QtQuick 2.14
*/
QSizeF QQuickPath::scale() const
{
    Q_D(const QQuickPath);
    return d->scale;
}

void QQuickPath::setScale(const QSizeF &scale)
{
    Q_D(QQuickPath);
    if (scale == d->scale)
        return;
    d->scale = scale;
    emit scaleChanged();
    processPath();
}

QPointF QQuickPath::sequentialPointAt(qreal p, qreal *angle) const
{
    Q_D(const QQuickPath);
    return sequentialPointAt(d->_path, d->pathLength, d->_attributePoints, d->prevBez, p, angle);
}

QPointF QQuickPath::sequentialPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle)
{
    Q_ASSERT(p >= 0.0 && p <= 1.0);

    if (!prevBez.isValid)
        return p > .5 ? backwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle) :
                        forwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle);

    return p < prevBez.p ? backwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle) :
                           forwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle);
}

QPointF QQuickPath::forwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle)
{
    if (pathLength <= 0 || qt_is_nan(pathLength))
        return path.pointAtPercent(0);  //expensive?

    const int lastElement = path.elementCount() - 1;
    bool haveCachedBez = prevBez.isValid;
    int currElement = haveCachedBez ? prevBez.element : -1;
    qreal bezLength = haveCachedBez ? prevBez.bezLength : 0;
    QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, &currElement, &bezLength);
    qreal currLength = haveCachedBez ? prevBez.currLength : bezLength;
    qreal epc = currLength / pathLength;

    //find which set we are in
    qreal prevPercent = 0;
    qreal prevOrigPercent = 0;
    for (int ii = 0; ii < attributePoints.size(); ++ii) {
        qreal percent = p;
        const AttributePoint &point = attributePoints.at(ii);
        if (percent < point.percent || ii == attributePoints.size() - 1) {
            qreal elementPercent = (percent - prevPercent);

            qreal spc = prevOrigPercent + elementPercent * point.scale;

            while (spc > epc) {
                Q_ASSERT(!(currElement > lastElement));
                Q_UNUSED(lastElement);
                currBez = nextBezier(path, &currElement, &bezLength);
                currLength += bezLength;
                epc = currLength / pathLength;
            }
            prevBez.element = currElement;
            prevBez.bezLength = bezLength;
            prevBez.currLength = currLength;
            prevBez.bezier = currBez;
            prevBez.p = p;
            prevBez.isValid = true;

            qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength;

            if (angle) {
                qreal m1 = slopeAt(realT, currBez.x1, currBez.x2, currBez.x3, currBez.x4);
                qreal m2 = slopeAt(realT, currBez.y1, currBez.y2, currBez.y3, currBez.y4);
                *angle = QLineF(0, 0, m1, m2).angle();
            }

            return currBez.pointAt(qBound(qreal(0), realT, qreal(1)));
        }
        prevOrigPercent = point.origpercent;
        prevPercent = point.percent;
    }

    return QPointF(0,0);
}

//ideally this should be merged with forwardsPointAt
QPointF QQuickPath::backwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle)
{
    if (pathLength <= 0 || qt_is_nan(pathLength))
        return path.pointAtPercent(0);

    const int firstElement = 1; //element 0 is always a MoveTo, which we ignore
    bool haveCachedBez = prevBez.isValid;
    int currElement = haveCachedBez ? prevBez.element : path.elementCount();
    qreal bezLength = haveCachedBez ? prevBez.bezLength : 0;
    QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, &currElement, &bezLength, true /*reverse*/);
    qreal currLength = haveCachedBez ? prevBez.currLength : pathLength;
    qreal prevLength = currLength - bezLength;
    qreal epc = prevLength / pathLength;

    for (int ii = attributePoints.size() - 1; ii > 0; --ii) {
        qreal percent = p;
        const AttributePoint &point = attributePoints.at(ii);
        const AttributePoint &prevPoint = attributePoints.at(ii-1);
        if (percent > prevPoint.percent || ii == 1) {
            qreal elementPercent = (percent - prevPoint.percent);

            qreal spc = prevPoint.origpercent + elementPercent * point.scale;

            while (spc < epc) {
                Q_ASSERT(!(currElement < firstElement));
                Q_UNUSED(firstElement);
                currBez = nextBezier(path, &currElement, &bezLength, true /*reverse*/);
                //special case for first element is to avoid floating point math
                //causing an epc that never hits 0.
                currLength = (currElement == firstElement) ? bezLength : prevLength;
                prevLength = currLength - bezLength;
                epc = prevLength / pathLength;
            }
            prevBez.element = currElement;
            prevBez.bezLength = bezLength;
            prevBez.currLength = currLength;
            prevBez.bezier = currBez;
            prevBez.p = p;
            prevBez.isValid = true;

            qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength;

            if (angle) {
                qreal m1 = slopeAt(realT, currBez.x1, currBez.x2, currBez.x3, currBez.x4);
                qreal m2 = slopeAt(realT, currBez.y1, currBez.y2, currBez.y3, currBez.y4);
                *angle = QLineF(0, 0, m1, m2).angle();
            }

            return currBez.pointAt(qBound(qreal(0), realT, qreal(1)));
        }
    }

    return QPointF(0,0);
}

/*!
   \qmlmethod point Path::pointAtPercent(real t)

    Returns the point at the percentage \a t of the current path.
    The argument \a t has to be between 0 and 1.

    \note Similarly to other percent methods in \l QPainterPath,
    the percentage measurement is not linear with regards to the length,
    if curves are present in the path.
    When curves are present, the percentage argument is mapped to the \c t
    parameter of the Bezier equations.

   \sa QPainterPath::pointAtPercent()

   \since QtQuick 2.14
*/
QPointF QQuickPath::pointAtPercent(qreal t) const
{
    Q_D(const QQuickPath);
    if (d->isShapePath)                     // this since ShapePath does not calculate the length at all,
        return d->_path.pointAtPercent(t);  // in order to be faster.

    if (d->_pointCache.isEmpty()) {
        createPointCache();
        if (d->_pointCache.isEmpty())
            return QPointF();
    }

    const int segmentCount = d->_pointCache.size() - 1;
    qreal idxf = t*segmentCount;
    int idx1 = qFloor(idxf);
    qreal delta = idxf - idx1;
    if (idx1 > segmentCount)
        idx1 = segmentCount;
    else if (idx1 < 0)
        idx1 = 0;

    if (delta == 0.0)
        return d->_pointCache.at(idx1);

    // interpolate between the two points.
    int idx2 = qCeil(idxf);
    if (idx2 > segmentCount)
        idx2 = segmentCount;
    else if (idx2 < 0)
        idx2 = 0;

    QPointF p1 = d->_pointCache.at(idx1);
    QPointF p2 = d->_pointCache.at(idx2);
    QPointF pos = p1 * (1.0-delta) + p2 * delta;

    return pos;
}

qreal QQuickPath::attributeAt(const QString &name, qreal percent) const
{
    Q_D(const QQuickPath);
    if (percent < 0 || percent > 1)
        return 0;

    for (int ii = 0; ii < d->_attributePoints.size(); ++ii) {
        const AttributePoint &point = d->_attributePoints.at(ii);

        if (point.percent == percent) {
            return point.values.value(name);
        } else if (point.percent > percent) {
            qreal lastValue =
                ii?(d->_attributePoints.at(ii - 1).values.value(name)):0;
            qreal lastPercent =
                ii?(d->_attributePoints.at(ii - 1).percent):0;
            qreal curValue = point.values.value(name);
            qreal curPercent = point.percent;

            return lastValue + (curValue - lastValue) * (percent - lastPercent) / (curPercent - lastPercent);
        }
    }

    return 0;
}

/****************************************************************************/

qreal QQuickCurve::x() const
{
    return _x.isNull ? 0 : _x.value;
}

void QQuickCurve::setX(qreal x)
{
    if (_x.isNull || _x != x) {
        _x = x;
        emit xChanged();
        emit changed();
    }
}

bool QQuickCurve::hasX()
{
    return _x.isValid();
}

qreal QQuickCurve::y() const
{
    return _y.isNull ? 0 : _y.value;
}

void QQuickCurve::setY(qreal y)
{
    if (_y.isNull || _y != y) {
        _y = y;
        emit yChanged();
        emit changed();
    }
}

bool QQuickCurve::hasY()
{
    return _y.isValid();
}

qreal QQuickCurve::relativeX() const
{
    return _relativeX;
}

void QQuickCurve::setRelativeX(qreal x)
{
    if (_relativeX.isNull || _relativeX != x) {
        _relativeX = x;
        emit relativeXChanged();
        emit changed();
    }
}

bool QQuickCurve::hasRelativeX()
{
    return _relativeX.isValid();
}

qreal QQuickCurve::relativeY() const
{
    return _relativeY;
}

void QQuickCurve::setRelativeY(qreal y)
{
    if (_relativeY.isNull || _relativeY != y) {
        _relativeY = y;
        emit relativeYChanged();
        emit changed();
    }
}

bool QQuickCurve::hasRelativeY()
{
    return _relativeY.isValid();
}

/****************************************************************************/

/*!
    \qmltype PathAttribute
    \instantiates QQuickPathAttribute
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Specifies how to set an attribute at a given position in a Path.

    The PathAttribute object allows attributes consisting of a name and
    a value to be specified for various points along a path.  The
    attributes are exposed to the delegate as
    \l{Attached Properties and Attached Signal Handlers} {Attached Properties}.
    The value of an attribute at any particular point along the path is interpolated
    from the PathAttributes bounding that point.

    The example below shows a path with the items scaled to 30% with
    opacity 50% at the top of the path and scaled 100% with opacity
    100% at the bottom.  Note the use of the PathView.iconScale and
    PathView.iconOpacity attached properties to set the scale and opacity
    of the delegate.

    \table
    \row
    \li \image declarative-pathattribute.png
    \li
    \snippet qml/pathview/pathattributes.qml 0
    (see the PathView documentation for the specification of ContactModel.qml
     used for ContactModel above.)
    \endtable


    \sa Path
*/

/*!
    \qmlproperty string QtQuick::PathAttribute::name
    This property holds the name of the attribute to change.

    This attribute will be available to the delegate as PathView.<name>

    Note that using an existing Item property name such as "opacity" as an
    attribute is allowed.  This is because path attributes add a new
    \l{Attached Properties and Attached Signal Handlers} {Attached Property}
    which in no way clashes with existing properties.
*/

/*!
    the name of the attribute to change.
*/

QString QQuickPathAttribute::name() const
{
    return _name;
}

void QQuickPathAttribute::setName(const QString &name)
{
    if (_name == name)
        return;
     _name = name;
    emit nameChanged();
}

/*!
   \qmlproperty real QtQuick::PathAttribute::value
   This property holds the value for the attribute.

   The value specified can be used to influence the visual appearance
   of an item along the path. For example, the following Path specifies
   an attribute named \e itemRotation, which has the value \e 0 at the
   beginning of the path, and the value 90 at the end of the path.

   \qml
   Path {
       startX: 0
       startY: 0
       PathAttribute { name: "itemRotation"; value: 0 }
       PathLine { x: 100; y: 100 }
       PathAttribute { name: "itemRotation"; value: 90 }
   }
   \endqml

   In our delegate, we can then bind the \e rotation property to the
   \l{Attached Properties and Attached Signal Handlers} {Attached Property}
   \e PathView.itemRotation created for this attribute.

   \qml
   Rectangle {
       width: 10; height: 10
       rotation: PathView.itemRotation
   }
   \endqml

   As each item is positioned along the path, it will be rotated accordingly:
   an item at the beginning of the path with be not be rotated, an item at
   the end of the path will be rotated 90 degrees, and an item mid-way along
   the path will be rotated 45 degrees.
*/

/*!
    the new value of the attribute.
*/
qreal QQuickPathAttribute::value() const
{
    return _value;
}

void QQuickPathAttribute::setValue(qreal value)
{
    if (_value != value) {
        _value = value;
        emit valueChanged();
        emit changed();
    }
}

/****************************************************************************/

/*!
    \qmltype PathLine
    \instantiates QQuickPathLine
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a straight line.

    The example below creates a path consisting of a straight line from
    0,100 to 200,100:

    \qml
    Path {
        startX: 0; startY: 100
        PathLine { x: 200; y: 100 }
    }
    \endqml

    \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline
*/

/*!
    \qmlproperty real QtQuick::PathLine::x
    \qmlproperty real QtQuick::PathLine::y

    Defines the end point of the line.

    \sa relativeX, relativeY
*/

/*!
    \qmlproperty real QtQuick::PathLine::relativeX
    \qmlproperty real QtQuick::PathLine::relativeY

    Defines the end point of the line relative to its start.

    If both a relative and absolute end position are specified for a single axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative x
    and an absolute y.

    \sa x, y
*/

inline QPointF positionForCurve(const QQuickPathData &data, const QPointF &prevPoint)
{
    QQuickCurve *curve = data.curves.at(data.index);
    bool isEnd = data.index == data.curves.size() - 1;
    return QPointF(curve->hasRelativeX() ? prevPoint.x() + curve->relativeX() : !isEnd || curve->hasX() ? curve->x() : data.endPoint.x(),
                   curve->hasRelativeY() ? prevPoint.y() + curve->relativeY() : !isEnd || curve->hasY() ? curve->y() : data.endPoint.y());
}

void QQuickPathLine::addToPath(QPainterPath &path, const QQuickPathData &data)
{
    path.lineTo(positionForCurve(data, path.currentPosition()));
}

/****************************************************************************/

/*!
    \qmltype PathMove
    \instantiates QQuickPathMove
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Moves the Path's position.

    The example below creates a path consisting of two horizontal lines with
    some empty space between them. All three segments have a width of 100:

    \qml
    Path {
        startX: 0; startY: 100
        PathLine { relativeX: 100; y: 100 }
        PathMove { relativeX: 100; y: 100 }
        PathLine { relativeX: 100; y: 100 }
    }
    \endqml

    \note PathMove should not be used in a Path associated with a PathView. Use
    PathLine instead. For ShapePath however it is important to distinguish
    between the operations of drawing a straight line and moving the path
    position without drawing anything.

    \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathLine
*/

/*!
    \qmlproperty real QtQuick::PathMove::x
    \qmlproperty real QtQuick::PathMove::y

    Defines the position to move to.

    \sa relativeX, relativeY
*/

/*!
    \qmlproperty real QtQuick::PathMove::relativeX
    \qmlproperty real QtQuick::PathMove::relativeY

    Defines the position to move to relative to its start.

    If both a relative and absolute end position are specified for a single axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative x
    and an absolute y.

    \sa x, y
*/

void QQuickPathMove::addToPath(QPainterPath &path, const QQuickPathData &data)
{
    path.moveTo(positionForCurve(data, path.currentPosition()));
}

/****************************************************************************/

/*!
    \qmltype PathQuad
    \instantiates QQuickPathQuad
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a quadratic Bezier curve with a control point.

    The following QML produces the path shown below:
    \table
    \row
    \li \image declarative-pathquad.png
    \li
    \qml
    Path {
        startX: 0; startY: 0
        PathQuad { x: 200; y: 0; controlX: 100; controlY: 150 }
    }
    \endqml
    \endtable

    \sa Path, PathCubic, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg
*/

/*!
    \qmlproperty real QtQuick::PathQuad::x
    \qmlproperty real QtQuick::PathQuad::y

    Defines the end point of the curve.

    \sa relativeX, relativeY
*/

/*!
    \qmlproperty real QtQuick::PathQuad::relativeX
    \qmlproperty real QtQuick::PathQuad::relativeY

    Defines the end point of the curve relative to its start.

    If both a relative and absolute end position are specified for a single axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative x
    and an absolute y.

    \sa x, y
*/

/*!
   \qmlproperty real QtQuick::PathQuad::controlX
   \qmlproperty real QtQuick::PathQuad::controlY

   Defines the position of the control point.
*/

/*!
    the x position of the control point.
*/
qreal QQuickPathQuad::controlX() const
{
    return _controlX;
}

void QQuickPathQuad::setControlX(qreal x)
{
    if (_controlX != x) {
        _controlX = x;
        emit controlXChanged();
        emit changed();
    }
}


/*!
    the y position of the control point.
*/
qreal QQuickPathQuad::controlY() const
{
    return _controlY;
}

void QQuickPathQuad::setControlY(qreal y)
{
    if (_controlY != y) {
        _controlY = y;
        emit controlYChanged();
        emit changed();
    }
}

/*!
   \qmlproperty real QtQuick::PathQuad::relativeControlX
   \qmlproperty real QtQuick::PathQuad::relativeControlY

    Defines the position of the control point relative to the curve's start.

    If both a relative and absolute control position are specified for a single axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative control x
    and an absolute control y.

    \sa controlX, controlY
*/

qreal QQuickPathQuad::relativeControlX() const
{
    return _relativeControlX;
}

void QQuickPathQuad::setRelativeControlX(qreal x)
{
    if (_relativeControlX.isNull || _relativeControlX != x) {
        _relativeControlX = x;
        emit relativeControlXChanged();
        emit changed();
    }
}

bool QQuickPathQuad::hasRelativeControlX()
{
    return _relativeControlX.isValid();
}

qreal QQuickPathQuad::relativeControlY() const
{
    return _relativeControlY;
}

void QQuickPathQuad::setRelativeControlY(qreal y)
{
    if (_relativeControlY.isNull || _relativeControlY != y) {
        _relativeControlY = y;
        emit relativeControlYChanged();
        emit changed();
    }
}

bool QQuickPathQuad::hasRelativeControlY()
{
    return _relativeControlY.isValid();
}

void QQuickPathQuad::addToPath(QPainterPath &path, const QQuickPathData &data)
{
    const QPointF &prevPoint = path.currentPosition();
    QPointF controlPoint(hasRelativeControlX() ? prevPoint.x() + relativeControlX() : controlX(),
                         hasRelativeControlY() ? prevPoint.y() + relativeControlY() : controlY());
    path.quadTo(controlPoint, positionForCurve(data, path.currentPosition()));
}

/****************************************************************************/

/*!
    \qmltype PathCubic
    \instantiates QQuickPathCubic
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a cubic Bezier curve with two control points.

    The following QML produces the path shown below:
    \table
    \row
    \li \image declarative-pathcubic.png
    \li
    \qml
    Path {
        startX: 20; startY: 0
        PathCubic {
            x: 180; y: 0
            control1X: -10; control1Y: 90
            control2X: 210; control2Y: 90
        }
    }
    \endqml
    \endtable

    \sa Path, PathQuad, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg
*/

/*!
    \qmlproperty real QtQuick::PathCubic::x
    \qmlproperty real QtQuick::PathCubic::y

    Defines the end point of the curve.

    \sa relativeX, relativeY
*/

/*!
    \qmlproperty real QtQuick::PathCubic::relativeX
    \qmlproperty real QtQuick::PathCubic::relativeY

    Defines the end point of the curve relative to its start.

    If both a relative and absolute end position are specified for a single axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative x
    and an absolute y.

    \sa x, y
*/

/*!
   \qmlproperty real QtQuick::PathCubic::control1X
   \qmlproperty real QtQuick::PathCubic::control1Y

    Defines the position of the first control point.
*/
qreal QQuickPathCubic::control1X() const
{
    return _control1X;
}

void QQuickPathCubic::setControl1X(qreal x)
{
    if (_control1X != x) {
        _control1X = x;
        emit control1XChanged();
        emit changed();
    }
}

qreal QQuickPathCubic::control1Y() const
{
    return _control1Y;
}

void QQuickPathCubic::setControl1Y(qreal y)
{
    if (_control1Y != y) {
        _control1Y = y;
        emit control1YChanged();
        emit changed();
    }
}

/*!
   \qmlproperty real QtQuick::PathCubic::control2X
   \qmlproperty real QtQuick::PathCubic::control2Y

    Defines the position of the second control point.
*/
qreal QQuickPathCubic::control2X() const
{
    return _control2X;
}

void QQuickPathCubic::setControl2X(qreal x)
{
    if (_control2X != x) {
        _control2X = x;
        emit control2XChanged();
        emit changed();
    }
}

qreal QQuickPathCubic::control2Y() const
{
    return _control2Y;
}

void QQuickPathCubic::setControl2Y(qreal y)
{
    if (_control2Y != y) {
        _control2Y = y;
        emit control2YChanged();
        emit changed();
    }
}

/*!
   \qmlproperty real QtQuick::PathCubic::relativeControl1X
   \qmlproperty real QtQuick::PathCubic::relativeControl1Y
   \qmlproperty real QtQuick::PathCubic::relativeControl2X
   \qmlproperty real QtQuick::PathCubic::relativeControl2Y

    Defines the positions of the control points relative to the curve's start.

    If both a relative and absolute control position are specified for a control point's axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative control1 x
    and an absolute control1 y.

    \sa control1X, control1Y, control2X, control2Y
*/

qreal QQuickPathCubic::relativeControl1X() const
{
    return _relativeControl1X;
}

void QQuickPathCubic::setRelativeControl1X(qreal x)
{
    if (_relativeControl1X.isNull || _relativeControl1X != x) {
        _relativeControl1X = x;
        emit relativeControl1XChanged();
        emit changed();
    }
}

bool QQuickPathCubic::hasRelativeControl1X()
{
    return _relativeControl1X.isValid();
}

qreal QQuickPathCubic::relativeControl1Y() const
{
    return _relativeControl1Y;
}

void QQuickPathCubic::setRelativeControl1Y(qreal y)
{
    if (_relativeControl1Y.isNull || _relativeControl1Y != y) {
        _relativeControl1Y = y;
        emit relativeControl1YChanged();
        emit changed();
    }
}

bool QQuickPathCubic::hasRelativeControl1Y()
{
    return _relativeControl1Y.isValid();
}

qreal QQuickPathCubic::relativeControl2X() const
{
    return _relativeControl2X;
}

void QQuickPathCubic::setRelativeControl2X(qreal x)
{
    if (_relativeControl2X.isNull || _relativeControl2X != x) {
        _relativeControl2X = x;
        emit relativeControl2XChanged();
        emit changed();
    }
}

bool QQuickPathCubic::hasRelativeControl2X()
{
    return _relativeControl2X.isValid();
}

qreal QQuickPathCubic::relativeControl2Y() const
{
    return _relativeControl2Y;
}

void QQuickPathCubic::setRelativeControl2Y(qreal y)
{
    if (_relativeControl2Y.isNull || _relativeControl2Y != y) {
        _relativeControl2Y = y;
        emit relativeControl2YChanged();
        emit changed();
    }
}

bool QQuickPathCubic::hasRelativeControl2Y()
{
    return _relativeControl2Y.isValid();
}

void QQuickPathCubic::addToPath(QPainterPath &path, const QQuickPathData &data)
{
    const QPointF &prevPoint = path.currentPosition();
    QPointF controlPoint1(hasRelativeControl1X() ? prevPoint.x() + relativeControl1X() : control1X(),
                          hasRelativeControl1Y() ? prevPoint.y() + relativeControl1Y() : control1Y());
    QPointF controlPoint2(hasRelativeControl2X() ? prevPoint.x() + relativeControl2X() : control2X(),
                          hasRelativeControl2Y() ? prevPoint.y() + relativeControl2Y() : control2Y());
    path.cubicTo(controlPoint1, controlPoint2, positionForCurve(data, path.currentPosition()));
}

/****************************************************************************/

/*!
    \qmltype PathCurve
    \instantiates QQuickPathCatmullRomCurve
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a point on a Catmull-Rom curve.

    PathCurve provides an easy way to specify a curve passing directly through a set of points.
    Typically multiple PathCurves are used in a series, as the following example demonstrates:

    \snippet qml/path/basiccurve.qml 0

    This example produces the following path (with the starting point and PathCurve points
    highlighted in red):

    \image declarative-pathcurve.png

    \sa Path, PathLine, PathQuad, PathCubic, PathArc, PathSvg
*/

/*!
    \qmlproperty real QtQuick::PathCurve::x
    \qmlproperty real QtQuick::PathCurve::y

    Defines the end point of the curve.

    \sa relativeX, relativeY
*/

/*!
    \qmlproperty real QtQuick::PathCurve::relativeX
    \qmlproperty real QtQuick::PathCurve::relativeY

    Defines the end point of the curve relative to its start.

    If both a relative and absolute end position are specified for a single axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative x
    and an absolute y.

    \sa x, y
*/

inline QPointF previousPathPosition(const QPainterPath &path)
{
    int count = path.elementCount();
    if (count < 1)
        return QPointF();

    int index = path.elementAt(count-1).type == QPainterPath::CurveToDataElement ? count - 4 : count - 2;
    return index > -1 ? QPointF(path.elementAt(index)) : path.pointAtPercent(0);
}

void QQuickPathCatmullRomCurve::addToPath(QPainterPath &path, const QQuickPathData &data)
{
    //here we convert catmull-rom spline to bezier for use in QPainterPath.
    //basic conversion algorithm:
    //  catmull-rom points * inverse bezier matrix * catmull-rom matrix = bezier points
    //each point in the catmull-rom spline produces a bezier endpoint + 2 control points
    //calculations for each point use a moving window of 4 points
    //  (previous 2 points + current point + next point)
    QPointF prevFar, prev, point, next;

    //get previous points
    int index = data.index - 1;
    QQuickCurve *curve = index == -1 ? 0 : data.curves.at(index);
    if (qobject_cast<QQuickPathCatmullRomCurve*>(curve)) {
        prev = path.currentPosition();
        prevFar = previousPathPosition(path);
    } else {
        prev = path.currentPosition();
        bool prevFarSet = false;
        if (index == -1 && data.curves.size() > 1) {
            if (qobject_cast<QQuickPathCatmullRomCurve*>(data.curves.at(data.curves.size()-1))) {
                //TODO: profile and optimize
                QPointF pos = prev;
                QQuickPathData loopData;
                loopData.endPoint = data.endPoint;
                loopData.curves = data.curves;
                for (int i = data.index; i < data.curves.size(); ++i) {
                    loopData.index = i;
                    pos = positionForCurve(loopData, pos);
                    if (i == data.curves.size()-2)
                        prevFar = pos;
                }
                if (pos == QPointF(path.elementAt(0))) {
                    //this is a closed path starting and ending with catmull-rom segments.
                    //we try to smooth the join point
                    prevFarSet = true;
                }
            }
        }
        if (!prevFarSet)
            prevFar = prev;
    }

    //get current point
    point = positionForCurve(data, path.currentPosition());

    //get next point
    index = data.index + 1;
    if (index < data.curves.size() && qobject_cast<QQuickPathCatmullRomCurve*>(data.curves.at(index))) {
        QQuickPathData nextData;
        nextData.index = index;
        nextData.endPoint = data.endPoint;
        nextData.curves = data.curves;
        next = positionForCurve(nextData, point);
    } else {
        if (point == QPointF(path.elementAt(0)) && qobject_cast<QQuickPathCatmullRomCurve*>(data.curves.at(0)) && path.elementCount() >= 3) {
            //this is a closed path starting and ending with catmull-rom segments.
            //we try to smooth the join point
            next = QPointF(path.elementAt(3));  //the first catmull-rom point
        } else
            next = point;
    }

    /*
        full conversion matrix (inverse bezier * catmull-rom):
        0.000,  1.000,  0.000,  0.000,
        -0.167,  1.000,  0.167,  0.000,
        0.000,  0.167,  1.000, -0.167,
        0.000,  0.000,  1.000,  0.000

        conversion doesn't require full matrix multiplication,
        so below we simplify
    */
    QPointF control1(prevFar.x() * qreal(-0.167) +
                     prev.x() +
                     point.x() * qreal(0.167),
                     prevFar.y() * qreal(-0.167) +
                     prev.y() +
                     point.y() * qreal(0.167));

    QPointF control2(prev.x() * qreal(0.167) +
                     point.x() +
                     next.x() * qreal(-0.167),
                     prev.y() * qreal(0.167) +
                     point.y() +
                     next.y() * qreal(-0.167));

    path.cubicTo(control1, control2, point);
}

/****************************************************************************/

/*!
    \qmltype PathArc
    \instantiates QQuickPathArc
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines an arc with the given radius.

    PathArc provides a simple way of specifying an arc that ends at a given position
    and uses the specified radius. It is modeled after the SVG elliptical arc command.

    The following QML produces the path shown below:
    \table
    \row
    \li \image declarative-patharc.png
    \li \snippet qml/path/basicarc.qml 0
    \endtable

    Note that a single PathArc cannot be used to specify a circle. Instead, you can
    use two PathArc elements, each specifying half of the circle.

    \sa Path, PathLine, PathQuad, PathCubic, PathAngleArc, PathCurve, PathSvg
*/

/*!
    \qmlproperty real QtQuick::PathArc::x
    \qmlproperty real QtQuick::PathArc::y

    Defines the end point of the arc.

    \sa relativeX, relativeY
*/

/*!
    \qmlproperty real QtQuick::PathArc::relativeX
    \qmlproperty real QtQuick::PathArc::relativeY

    Defines the end point of the arc relative to its start.

    If both a relative and absolute end position are specified for a single axis, the relative
    position will be used.

    Relative and absolute positions can be mixed, for example it is valid to set a relative x
    and an absolute y.

    \sa x, y
*/

/*!
    \qmlproperty real QtQuick::PathArc::radiusX
    \qmlproperty real QtQuick::PathArc::radiusY

    Defines the radius of the arc.

    The following QML demonstrates how different radius values can be used to change
    the shape of the arc:
    \table
    \row
    \li \image declarative-arcradius.png
    \li \snippet qml/path/arcradius.qml 0
    \endtable
*/

qreal QQuickPathArc::radiusX() const
{
    return _radiusX;
}

void QQuickPathArc::setRadiusX(qreal radius)
{
    if (_radiusX == radius)
        return;

    _radiusX = radius;
    emit radiusXChanged();
    emit changed();
}

qreal QQuickPathArc::radiusY() const
{
    return _radiusY;
}

void QQuickPathArc::setRadiusY(qreal radius)
{
    if (_radiusY == radius)
        return;

    _radiusY = radius;
    emit radiusYChanged();
    emit changed();
}

/*!
    \qmlproperty bool QtQuick::PathArc::useLargeArc
    Whether to use a large arc as defined by the arc points.

    Given fixed start and end positions, radius, and direction,
    there are two possible arcs that can fit the data. useLargeArc
    is used to distinguish between these. For example, the following
    QML can produce either of the two illustrated arcs below by
    changing the value of useLargeArc.

    \table
    \row
    \li \image declarative-largearc.png
    \li \snippet qml/path/largearc.qml 0
    \endtable

    The default value is false.
*/

bool QQuickPathArc::useLargeArc() const
{
    return _useLargeArc;
}

void QQuickPathArc::setUseLargeArc(bool largeArc)
{
    if (_useLargeArc == largeArc)
        return;

    _useLargeArc = largeArc;
    emit useLargeArcChanged();
    emit changed();
}

/*!
    \qmlproperty enumeration QtQuick::PathArc::direction

    Defines the direction of the arc. Possible values are
    PathArc.Clockwise (default) and PathArc.Counterclockwise.

    The following QML can produce either of the two illustrated arcs below
    by changing the value of direction.
    \table
    \row
    \li \image declarative-arcdirection.png
    \li \snippet qml/path/arcdirection.qml 0
    \endtable

    \sa useLargeArc
*/

QQuickPathArc::ArcDirection QQuickPathArc::direction() const
{
    return _direction;
}

void QQuickPathArc::setDirection(ArcDirection direction)
{
    if (_direction == direction)
        return;

    _direction = direction;
    emit directionChanged();
    emit changed();
}

/*!
    \qmlproperty real QtQuick::PathArc::xAxisRotation

    Defines the rotation of the arc, in degrees. The default value is 0.

    An arc is a section of circles or ellipses. Given the radius and the start
    and end points, there are two ellipses that connect the points. This
    property defines the rotation of the X axis of these ellipses.

    \note The value is only useful when the x and y radius differ, meaning the
    arc is a section of ellipses.

    The following QML demonstrates how different radius values can be used to change
    the shape of the arc:
    \table
    \row
    \li \image declarative-arcrotation.png
    \li \snippet qml/path/arcrotation.qml 0
    \endtable
*/

qreal QQuickPathArc::xAxisRotation() const
{
    return _xAxisRotation;
}

void QQuickPathArc::setXAxisRotation(qreal rotation)
{
    if (_xAxisRotation == rotation)
        return;

    _xAxisRotation = rotation;
    emit xAxisRotationChanged();
    emit changed();
}

void QQuickPathArc::addToPath(QPainterPath &path, const QQuickPathData &data)
{
    const QPointF &startPoint = path.currentPosition();
    const QPointF &endPoint = positionForCurve(data, startPoint);
    QQuickSvgParser::pathArc(path,
            _radiusX,
            _radiusY,
            _xAxisRotation,
            _useLargeArc,
            _direction == Clockwise ? 1 : 0,
            endPoint.x(),
            endPoint.y(),
            startPoint.x(), startPoint.y());
}

/****************************************************************************/

/*!
    \qmltype PathAngleArc
    \instantiates QQuickPathAngleArc
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines an arc with the given radii and center.

    PathAngleArc provides a simple way of specifying an arc. While PathArc is designed
    to work as part of a larger path (specifying start and end), PathAngleArc is designed
    to make a path where the arc is primary (such as a circular progress indicator) more intuitive.

    \sa Path, PathLine, PathQuad, PathCubic, PathCurve, PathSvg, PathArc
*/

/*!
    \qmlproperty real QtQuick::PathAngleArc::centerX
    \qmlproperty real QtQuick::PathAngleArc::centerY

    Defines the center of the arc.
*/

qreal QQuickPathAngleArc::centerX() const
{
    return _centerX;
}

void QQuickPathAngleArc::setCenterX(qreal centerX)
{
    if (_centerX == centerX)
        return;

    _centerX = centerX;
    emit centerXChanged();
    emit changed();
}

qreal QQuickPathAngleArc::centerY() const
{
    return _centerY;
}

void QQuickPathAngleArc::setCenterY(qreal centerY)
{
    if (_centerY == centerY)
        return;

    _centerY = centerY;
    emit centerYChanged();
    emit changed();
}

/*!
    \qmlproperty real QtQuick::PathAngleArc::radiusX
    \qmlproperty real QtQuick::PathAngleArc::radiusY

    Defines the radii of the ellipse of which the arc is part.
*/

qreal QQuickPathAngleArc::radiusX() const
{
    return _radiusX;
}

void QQuickPathAngleArc::setRadiusX(qreal radius)
{
    if (_radiusX == radius)
        return;

    _radiusX = radius;
    emit radiusXChanged();
    emit changed();
}

qreal QQuickPathAngleArc::radiusY() const
{
    return _radiusY;
}

void QQuickPathAngleArc::setRadiusY(qreal radius)
{
    if (_radiusY == radius)
        return;

    _radiusY = radius;
    emit radiusYChanged();
    emit changed();
}

/*!
    \qmlproperty real QtQuick::PathAngleArc::startAngle

    Defines the start angle of the arc.

    The start angle is reported clockwise, with zero degrees at the 3 o'clock position.
*/

qreal QQuickPathAngleArc::startAngle() const
{
    return _startAngle;
}

void QQuickPathAngleArc::setStartAngle(qreal angle)
{
    if (_startAngle == angle)
        return;

    _startAngle = angle;
    emit startAngleChanged();
    emit changed();
}

/*!
    \qmlproperty real QtQuick::PathAngleArc::sweepAngle

    Defines the sweep angle of the arc.

    The arc will begin at startAngle and continue sweepAngle degrees, with a value of 360
    resulting in a full circle. Positive numbers are clockwise and negative numbers are counterclockwise.
*/

qreal QQuickPathAngleArc::sweepAngle() const
{
    return _sweepAngle;
}

void QQuickPathAngleArc::setSweepAngle(qreal angle)
{
    if (_sweepAngle == angle)
        return;

    _sweepAngle = angle;
    emit sweepAngleChanged();
    emit changed();
}

/*!
    \qmlproperty bool QtQuick::PathAngleArc::moveToStart

    Whether this element should be disconnected from the previous Path element (or startX/Y).

    The default value is true. If set to false, the previous element's end-point
    (or startX/Y if PathAngleArc is the first element) will be connected to the arc's
    start-point with a straight line.
*/

bool QQuickPathAngleArc::moveToStart() const
{
    return _moveToStart;
}

void QQuickPathAngleArc::setMoveToStart(bool move)
{
    if (_moveToStart == move)
        return;

    _moveToStart = move;
    emit moveToStartChanged();
    emit changed();
}

void QQuickPathAngleArc::addToPath(QPainterPath &path, const QQuickPathData &)
{
    qreal x = _centerX - _radiusX;
    qreal y = _centerY - _radiusY;
    qreal width = _radiusX * 2;
    qreal height = _radiusY * 2;
    if (_moveToStart)
        path.arcMoveTo(x, y, width, height, -_startAngle);
    path.arcTo(x, y, width, height, -_startAngle, -_sweepAngle);
}

/****************************************************************************/

/*!
    \qmltype PathSvg
    \instantiates QQuickPathSvg
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a path using an SVG path data string.

    The following QML produces the path shown below:
    \table
    \row
    \li \image declarative-pathsvg.png
    \li
    \qml
    Path {
        startX: 50; startY: 50
        PathSvg { path: "L 150 50 L 100 150 z" }
    }
    \endqml
    \endtable

    \sa Path, PathLine, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve
*/

/*!
    \qmlproperty string QtQuick::PathSvg::path

    The SVG path data string specifying the path.

    See \l {http://www.w3.org/TR/SVG/paths.html#PathData}{W3C SVG Path Data}
    for more details on this format.
*/

QString QQuickPathSvg::path() const
{
    return _path;
}

void QQuickPathSvg::setPath(const QString &path)
{
    if (_path == path)
        return;

    _path = path;
    emit pathChanged();
    emit changed();
}

void QQuickPathSvg::addToPath(QPainterPath &path, const QQuickPathData &)
{
    QQuickSvgParser::parsePathDataFast(_path, path);
}

/****************************************************************************/

/*!
    \qmltype PathPercent
    \instantiates QQuickPathPercent
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Manipulates the way a path is interpreted.

    PathPercent allows you to manipulate the spacing between items on a
    PathView's path. You can use it to bunch together items on part of
    the path, and spread them out on other parts of the path.

    The examples below show the normal distribution of items along a path
    compared to a distribution which places 50% of the items along the
    PathLine section of the path.
    \table
    \row
    \li \image declarative-nopercent.png
    \li
    \qml
    PathView {
        // ...
        Path {
            startX: 20; startY: 0
            PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 }
            PathLine { x: 150; y: 80 }
            PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 }
        }
    }
    \endqml
    \row
    \li \image declarative-percent.png
    \li
    \qml
    PathView {
        // ...
        Path {
            startX: 20; startY: 0
            PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 }
            PathPercent { value: 0.25 }
            PathLine { x: 150; y: 80 }
            PathPercent { value: 0.75 }
            PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 }
            PathPercent { value: 1 }
        }
    }
    \endqml
    \endtable

    \sa Path
*/

/*!
    \qmlproperty real QtQuick::PathPercent::value
    The proportion of items that should be laid out up to this point.

    This value should always be higher than the last value specified
    by a PathPercent at a previous position in the Path.

    In the following example we have a Path made up of three PathLines.
    Normally, the items of the PathView would be laid out equally along
    this path, with an equal number of items per line segment. PathPercent
    allows us to specify that the first and third lines should each hold
    10% of the laid out items, while the second line should hold the remaining
    80%.

    \qml
    PathView {
        // ...
        Path {
            startX: 0; startY: 0
            PathLine { x:100; y: 0; }
            PathPercent { value: 0.1 }
            PathLine { x: 100; y: 100 }
            PathPercent { value: 0.9 }
            PathLine { x: 100; y: 0 }
            PathPercent { value: 1 }
        }
    }
    \endqml
*/

qreal QQuickPathPercent::value() const
{
    return _value;
}

void QQuickPathPercent::setValue(qreal value)
{
    if (_value != value) {
        _value = value;
        emit valueChanged();
        emit changed();
    }
}

/*!
    \qmltype PathPolyline
    \instantiates QQuickPathPolyline
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a polyline through a list of coordinates.
    \since QtQuick 2.14

    The example below creates a triangular path consisting of four vertices
    on the edge of the containing Shape's bounding box.
    Through the containing shape's \l {QtQuick::Path::}{scale} property,
    the path will be rescaled together with its containing shape.

    \qml
    PathPolyline {
        id: ppl
        path: [ Qt.point(0.0, 0.0),
                Qt.point(1.0, 0.0),
                Qt.point(0.5, 1.0),
                Qt.point(0.0, 0.0)
              ]
    }
    \endqml

    \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline
*/

/*!
    \qmlproperty point QtQuick::PathPolyline::start

    This read-only property contains the beginning of the polyline.
*/

/*!
    \qmlproperty list<point> QtQuick::PathPolyline::path

    This property defines the vertices of the polyline.

    It can be a JS array of points constructed with \c Qt.point(),
    a QList or QVector of QPointF, or QPolygonF.
    If you are binding this to a custom property in some C++ object,
    QPolygonF is the most appropriate type to use.
*/

QQuickPathPolyline::QQuickPathPolyline(QObject *parent) : QQuickCurve(parent)
{
}

QVariant QQuickPathPolyline::path() const
{
    return QVariant::fromValue(m_path);
}

void QQuickPathPolyline::setPath(const QVariant &path)
{
    if (path.userType() == QMetaType::QPolygonF) {
        setPath(path.value<QPolygonF>());
    } else if (path.canConvert<QVector<QPointF>>()) {
        setPath(path.value<QVector<QPointF>>());
    } else if (path.canConvert<QVariantList>()) {
        // This handles cases other than QPolygonF or QVector<QPointF>, such as
        // QList<QPointF>, QVector<QPoint>, QVariantList of QPointF, QVariantList of QPoint.
        QVector<QPointF> pathList;
        QVariantList vl = path.value<QVariantList>();
        // If path is a QJSValue, e.g. coming from a JS array of Qt.point() in QML,
        // then path.value<QVariantList>() is inefficient.
        // TODO We should be able to iterate over path.value<QSequentialIterable>() eventually
        for (const QVariant &v : vl)
            pathList.append(v.toPointF());
        setPath(pathList);
    } else {
        qWarning() << "PathPolyline: path of type" << path.userType() << "not supported";
    }
}

void QQuickPathPolyline::setPath(const QVector<QPointF> &path)
{
    if (m_path != path) {
        const QPointF &oldStart = start();
        m_path = path;
        const QPointF &newStart = start();
        emit pathChanged();
        if (oldStart != newStart)
            emit startChanged();
        emit changed();
    }
}

QPointF QQuickPathPolyline::start() const
{
    if (m_path.size()) {
        const QPointF &p = m_path.first();
        return p;
    }
    return QPointF();
}

void QQuickPathPolyline::addToPath(QPainterPath &path, const QQuickPathData &/*data*/)
{
    if (m_path.size() < 2)
        return;

    path.moveTo(m_path.first());
    for (int i = 1; i < m_path.size(); ++i)
        path.lineTo(m_path.at(i));
}


/*!
    \qmltype PathMultiline
    \instantiates QQuickPathMultiline
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a set of polylines through a list of lists of coordinates.
    \since QtQuick 2.14

    This element allows to define a list of polylines at once.
    Each polyline in the list will be preceded by a \l{QPainterPath::moveTo}{moveTo}
    command, effectively making each polyline a separate one.
    The polylines in this list are supposed to be non-intersecting with each other.
    In any case, when used in conjunction with a \l ShapePath, the containing ShapePath's
    \l ShapePath::fillRule applies.
    That is, with the default \c OddEvenFill and non intersecting shapes, the largest shape in the list defines an area to be filled;
    areas where two shapes overlap are holes; areas where three shapes overlap are filled areas inside holes, etc.

    The example below creates a high voltage symbol by adding each path
    of the symbol to the list of paths.
    The coordinates of the vertices are normalized, and through the containing shape's
    \l {QtQuick::Path::}{scale} property, the path will be rescaled together with its containing shape.

    \qml
    PathMultiline {
        paths: [
                [Qt.point(0.5,     0.06698),
                 Qt.point(1,       0.93301),
                 Qt.point(0,       0.93301),
                 Qt.point(0.5,     0.06698)],

                [Qt.point(0.5,     0.12472),
                 Qt.point(0.95,    0.90414),
                 Qt.point(0.05,    0.90414),
                 Qt.point(0.5,     0.12472)],

                [Qt.point(0.47131, 0.32986),
                 Qt.point(0.36229, 0.64789),
                 Qt.point(0.51492, 0.58590),
                 Qt.point(0.47563, 0.76014),
                 Qt.point(0.44950, 0.73590),
                 Qt.point(0.46292, 0.83392),
                 Qt.point(0.52162, 0.75190),
                 Qt.point(0.48531, 0.76230),
                 Qt.point(0.57529, 0.53189),
                 Qt.point(0.41261, 0.59189),
                 Qt.point(0.53001, 0.32786),
                 Qt.point(0.47131, 0.32986)]
               ]
    }
    \endqml

    \sa Path, QPainterPath::setFillRule, PathPolyline, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove
*/

/*!
    \qmlproperty point QtQuick::PathMultiline::start

    This read-only property contains the beginning of the polylines.
*/

/*!
    \qmlproperty list<list<point>> QtQuick::PathMultiline::paths

    This property defines the vertices of the polylines.

    It can be a JS array of JS arrays of points constructed with \c Qt.point(),
    a QList or QVector of QPolygonF, or QVector<QVector<QPointF>>.
    If you are binding this to a custom property in some C++ object,
    QVector<QPolygonF> or QVector<QVector<QPointF>> is the most
    appropriate type to use.
*/

QQuickPathMultiline::QQuickPathMultiline(QObject *parent) : QQuickCurve(parent)
{
}

QVariant QQuickPathMultiline::paths() const
{
    return QVariant::fromValue(m_paths);
}

void QQuickPathMultiline::setPaths(const QVariant &paths)
{
    if (paths.canConvert<QVector<QPolygonF>>()) {
        const QVector<QPolygonF> pathPolygons = paths.value<QVector<QPolygonF>>();
        QVector<QVector<QPointF>> pathVectors;
        for (const QPolygonF &p : pathPolygons)
            pathVectors << p;
        setPaths(pathVectors);
    } else if (paths.canConvert<QVector<QVector<QPointF>>>()) {
        setPaths(paths.value<QVector<QVector<QPointF>>>());
    } else if (paths.canConvert<QVariantList>()) {
        // This handles cases other than QVector<QPolygonF> or QVector<QVector<QPointF>>, such as
        // QList<QVector<QPointF>>, QList<QList<QPointF>>, QVariantList of QVector<QPointF>,
        // QVariantList of QVariantList of QPointF, QVector<QList<QPoint>> etc.
        QVector<QVector<QPointF>> pathsList;
        QVariantList vll = paths.value<QVariantList>();
        for (const QVariant &v : vll) {
            // If we bind a QVector<QPolygonF> property directly, rather than via QVariant,
            // it will come through as QJSValue that can be converted to QVariantList of QPolygonF.
            if (v.canConvert<QPolygonF>()) {
                pathsList.append(v.value<QPolygonF>());
            } else {
                QVariantList vl = v.value<QVariantList>();
                QVector<QPointF> l;
                for (const QVariant &point : vl) {
                    if (point.canConvert<QPointF>())
                        l.append(point.toPointF());
                }
                if (l.size() >= 2)
                    pathsList.append(l);
            }
        }
        setPaths(pathsList);
    } else {
        qWarning() << "PathMultiline: paths of type" << paths.userType() << "not supported";
        setPaths(QVector<QVector<QPointF>>());
    }
}

void QQuickPathMultiline::setPaths(const QVector<QVector<QPointF>> &paths)
{
    if (m_paths != paths) {
        const QPointF &oldStart = start();
        m_paths = paths;
        const QPointF &newStart = start();
        emit pathsChanged();
        if (oldStart != newStart)
            emit startChanged();
        emit changed();
    }
}

QPointF QQuickPathMultiline::start() const
{
    if (m_paths.size())
        return m_paths.first().first();
    return QPointF();
}

void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &)
{
    if (!m_paths.size())
        return;
    for (const QVector<QPointF> &p: m_paths) {
        path.moveTo(p.first());
        for (int i = 1; i < p.size(); ++i)
            path.lineTo(p.at(i));
    }
}

/*!
    \qmltype PathText
    \instantiates QQuickPathText
    \inqmlmodule QtQuick
    \ingroup qtquick-animation-paths
    \brief Defines a string in a specified font.
    \since QtQuick 2.15

    This element defines the shape of a specified string in a specified font. The text's
    baseline will be translated to the x and y coordinates, and the outlines from the font
    will be added to the path accordingly.

    \qml
    PathText {
        x: 0
        y: font.pixelSize
        font.family: "Arial"
        font.pixelSize: 100
        text: "Foobar"
    }
    \endqml

    \sa Path, QPainterPath::setFillRule, PathPolyline, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove
*/

/*!
    \qmlproperty real QtQuick::PathText::x

    The horizontal position of the PathText's baseline.
*/

/*!
    \qmlproperty real QtQuick::PathText::y

    The vertical position of the PathText's baseline.

    \note This property refers to the position of the baseline of the text, not the top of its bounding box. This may
    cause some confusion, e.g. when using the PathText with Qt Quick Shapes. See \l FontMetrics for information on how to
    get the ascent of a font, which can be used to translate the text into the expected position.
*/

/*!
    \qmlproperty string QtQuick::PathText::text

    The text for which this PathText should contain the outlines.
*/

/*!
    \qmlproperty string QtQuick::PathText::font.family

    Sets the family name of the font.

    The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
    If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
    If the family isn't available a family will be set using the font matching algorithm.
*/

/*!
    \qmlproperty string QtQuick::PathText::font.styleName

    Sets the style name of the font.

    The style name is case insensitive. If set, the font will be matched against style name instead
    of the font properties \l font.weight, \l font.bold and \l font.italic.
*/

/*!
    \qmlproperty bool QtQuick::PathText::font.bold

    Sets whether the font weight is bold.
*/

/*!
    \qmlproperty int QtQuick::PathText::font.weight

    Sets the font's weight.

    The weight can be one of:

    \value Font.Thin        100
    \value Font.ExtraLight  200
    \value Font.Light       300
    \value Font.Normal      400 (default)
    \value Font.Medium      500
    \value Font.DemiBold    600
    \value Font.Bold        700
    \value Font.ExtraBold   800
    \value Font.Black       900

    \qml
    PathText { text: "Hello"; font.weight: Font.DemiBold }
    \endqml
*/

/*!
    \qmlproperty bool QtQuick::PathText::font.italic

    Sets whether the font has an italic style.
*/

/*!
    \qmlproperty bool QtQuick::PathText::font.underline

    Sets whether the text is underlined.
*/

/*!
    \qmlproperty bool QtQuick::PathText::font.strikeout

    Sets whether the font has a strikeout style.
*/

/*!
    \qmlproperty real QtQuick::PathText::font.pointSize

    Sets the font size in points. The point size must be greater than zero.
*/

/*!
    \qmlproperty int QtQuick::PathText::font.pixelSize

    Sets the font size in pixels.

    Using this function makes the font device dependent.
    Use \c pointSize to set the size of the font in a device independent manner.
*/

/*!
    \qmlproperty real QtQuick::PathText::font.letterSpacing

    Sets the letter spacing for the font.

    Letter spacing changes the default spacing between individual letters in the font.
    A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
*/

/*!
    \qmlproperty real QtQuick::PathText::font.wordSpacing

    Sets the word spacing for the font.

    Word spacing changes the default spacing between individual words.
    A positive value increases the word spacing by a corresponding amount of pixels,
    while a negative value decreases the inter-word spacing accordingly.
*/

/*!
    \qmlproperty enumeration QtQuick::PathText::font.capitalization

    Sets the capitalization for the text.

    \value Font.MixedCase       no capitalization change is applied
    \value Font.AllUppercase    alters the text to be rendered in all uppercase type
    \value Font.AllLowercase    alters the text to be rendered in all lowercase type
    \value Font.SmallCaps       alters the text to be rendered in small-caps type
    \value Font.Capitalize      alters the text to be rendered with the first character of
                                each word as an uppercase character

    \qml
    PathText { text: "Hello"; font.capitalization: Font.AllLowercase }
    \endqml
*/

/*!
    \qmlproperty bool QtQuick::PathText::font.kerning

    Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
    improve performance when creating or changing the text, at the expense of some cosmetic
    features. The default value is true.

    \qml
    PathText { text: "OATS FLAVOUR WAY"; font.kerning: false }
    \endqml
*/

/*!
    \qmlproperty bool QtQuick::PathText::font.preferShaping

    Sometimes, a font will apply complex rules to a set of characters in order to
    display them correctly. In some writing systems, such as Brahmic scripts, this is
    required in order for the text to be legible, but in e.g. Latin script, it is merely
    a cosmetic feature. Setting the \c preferShaping property to false will disable all
    such features when they are not required, which will improve performance in most cases.

    The default value is true.

    \qml
    PathText { text: "Some text"; font.preferShaping: false }
    \endqml
*/

void QQuickPathText::updatePath() const
{
    if (!_path.isEmpty())
        return;

    _path.addText(0.0, 0.0, _font, _text);

    // Account for distance from baseline to top, since addText() takes baseline position
    QRectF brect = _path.boundingRect();
    _path.translate(_x, _y - brect.y());
}

void QQuickPathText::addToPath(QPainterPath &path)
{
    if (_text.isEmpty())
        return;
    updatePath();
    path.addPath(_path);
}

QT_END_NAMESPACE

#include "moc_qquickpath_p.cpp"
