text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

// tests that some coordinates aren't NaNs
#define JUCE_CHECK_COORDS_ARE_VALID(x, y) \
    jassert (x == x && y == y);

//==============================================================================
namespace PathHelpers
{
    const float ellipseAngularIncrement = 0.05f;

    static String nextToken (String::CharPointerType& t)
    {
        t = t.findEndOfWhitespace();

        auto start = t;
        size_t numChars = 0;

        while (! (t.isEmpty() || t.isWhitespace()))
        {
            ++t;
            ++numChars;
        }

        return { start, numChars };
    }

    inline double lengthOf (float x1, float y1, float x2, float y2) noexcept
    {
        return juce_hypot ((double) (x1 - x2), (double) (y1 - y2));
    }
}

//==============================================================================
const float Path::lineMarker           = 100001.0f;
const float Path::moveMarker           = 100002.0f;
const float Path::quadMarker           = 100003.0f;
const float Path::cubicMarker          = 100004.0f;
const float Path::closeSubPathMarker   = 100005.0f;

const float Path::defaultToleranceForTesting = 1.0f;
const float Path::defaultToleranceForMeasurement = 0.6f;

static inline bool isMarker (float value, float marker) noexcept
{
    return value == marker;
}

//==============================================================================
Path::PathBounds::PathBounds() noexcept
{
}

Rectangle<float> Path::PathBounds::getRectangle() const noexcept
{
    return { pathXMin, pathYMin, pathXMax - pathXMin, pathYMax - pathYMin };
}

void Path::PathBounds::reset() noexcept
{
    pathXMin = pathYMin = pathYMax = pathXMax = 0;
}

void Path::PathBounds::reset (const float x, const float y) noexcept
{
    pathXMin = pathXMax = x;
    pathYMin = pathYMax = y;
}

void Path::PathBounds::extend (const float x, const float y) noexcept
{
    pathXMin = jmin (pathXMin, x);
    pathXMax = jmax (pathXMax, x);
    pathYMin = jmin (pathYMin, y);
    pathYMax = jmax (pathYMax, y);
}

void Path::PathBounds::extend (const float x1, const float y1, const float x2, const float y2) noexcept
{
    if (x1 < x2)
    {
        pathXMin = jmin (pathXMin, x1);
        pathXMax = jmax (pathXMax, x2);
    }
    else
    {
        pathXMin = jmin (pathXMin, x2);
        pathXMax = jmax (pathXMax, x1);
    }

    if (y1 < y2)
    {
        pathYMin = jmin (pathYMin, y1);
        pathYMax = jmax (pathYMax, y2);
    }
    else
    {
        pathYMin = jmin (pathYMin, y2);
        pathYMax = jmax (pathYMax, y1);
    }
}

//==============================================================================
Path::Path()
{
}

Path::~Path()
{
}

Path::Path (const Path& other)
    : numElements (other.numElements),
      bounds (other.bounds),
      useNonZeroWinding (other.useNonZeroWinding)
{
    if (numElements > 0)
    {
        data.setAllocatedSize ((int) numElements);
        memcpy (data.elements, other.data.elements, numElements * sizeof (float));
    }
}

Path& Path::operator= (const Path& other)
{
    if (this != &other)
    {
        data.ensureAllocatedSize ((int) other.numElements);

        numElements = other.numElements;
        bounds = other.bounds;
        useNonZeroWinding = other.useNonZeroWinding;

        if (numElements > 0)
            memcpy (data.elements, other.data.elements, numElements * sizeof (float));
    }

    return *this;
}

Path::Path (Path&& other) noexcept
    : data (static_cast<ArrayAllocationBase <float, DummyCriticalSection>&&> (other.data)),
      numElements (other.numElements),
      bounds (other.bounds),
      useNonZeroWinding (other.useNonZeroWinding)
{
}

Path& Path::operator= (Path&& other) noexcept
{
    data = static_cast<ArrayAllocationBase <float, DummyCriticalSection>&&> (other.data);
    numElements = other.numElements;
    bounds = other.bounds;
    useNonZeroWinding = other.useNonZeroWinding;
    return *this;
}

bool Path::operator== (const Path& other) const noexcept
{
    return ! operator!= (other);
}

bool Path::operator!= (const Path& other) const noexcept
{
    if (numElements != other.numElements || useNonZeroWinding != other.useNonZeroWinding)
        return true;

    for (size_t i = 0; i < numElements; ++i)
        if (data.elements[i] != other.data.elements[i])
            return true;

    return false;
}

void Path::clear() noexcept
{
    numElements = 0;
    bounds.reset();
}

void Path::swapWithPath (Path& other) noexcept
{
    data.swapWith (other.data);
    std::swap (numElements, other.numElements);
    std::swap (bounds.pathXMin, other.bounds.pathXMin);
    std::swap (bounds.pathXMax, other.bounds.pathXMax);
    std::swap (bounds.pathYMin, other.bounds.pathYMin);
    std::swap (bounds.pathYMax, other.bounds.pathYMax);
    std::swap (useNonZeroWinding, other.useNonZeroWinding);
}

//==============================================================================
void Path::setUsingNonZeroWinding (const bool isNonZero) noexcept
{
    useNonZeroWinding = isNonZero;
}

void Path::scaleToFit (float x, float y, float w, float h, bool preserveProportions) noexcept
{
    applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions));
}

//==============================================================================
bool Path::isEmpty() const noexcept
{
    size_t i = 0;

    while (i < numElements)
    {
        auto type = data.elements[i++];

        if (isMarker (type, moveMarker))
        {
            i += 2;
        }
        else if (isMarker (type, lineMarker)
                 || isMarker (type, quadMarker)
                 || isMarker (type, cubicMarker))
        {
            return false;
        }
    }

    return true;
}

Rectangle<float> Path::getBounds() const noexcept
{
    return bounds.getRectangle();
}

Rectangle<float> Path::getBoundsTransformed (const AffineTransform& transform) const noexcept
{
    return getBounds().transformedBy (transform);
}

//==============================================================================
void Path::preallocateSpace (int numExtraCoordsToMakeSpaceFor)
{
    data.ensureAllocatedSize ((int) numElements + numExtraCoordsToMakeSpaceFor);
}

void Path::startNewSubPath (const float x, const float y)
{
    JUCE_CHECK_COORDS_ARE_VALID (x, y);

    if (numElements == 0)
        bounds.reset (x, y);
    else
        bounds.extend (x, y);

    preallocateSpace (3);

    data.elements[numElements++] = moveMarker;
    data.elements[numElements++] = x;
    data.elements[numElements++] = y;
}

void Path::startNewSubPath (Point<float> start)
{
    startNewSubPath (start.x, start.y);
}

void Path::lineTo (const float x, const float y)
{
    JUCE_CHECK_COORDS_ARE_VALID (x, y);

    if (numElements == 0)
        startNewSubPath (0, 0);

    preallocateSpace (3);

    data.elements[numElements++] = lineMarker;
    data.elements[numElements++] = x;
    data.elements[numElements++] = y;

    bounds.extend (x, y);
}

void Path::lineTo (Point<float> end)
{
    lineTo (end.x, end.y);
}

void Path::quadraticTo (const float x1, const float y1,
                        const float x2, const float y2)
{
    JUCE_CHECK_COORDS_ARE_VALID (x1, y1);
    JUCE_CHECK_COORDS_ARE_VALID (x2, y2);

    if (numElements == 0)
        startNewSubPath (0, 0);

    preallocateSpace (5);

    data.elements[numElements++] = quadMarker;
    data.elements[numElements++] = x1;
    data.elements[numElements++] = y1;
    data.elements[numElements++] = x2;
    data.elements[numElements++] = y2;

    bounds.extend (x1, y1, x2, y2);
}

void Path::quadraticTo (Point<float> controlPoint, Point<float> endPoint)
{
    quadraticTo (controlPoint.x, controlPoint.y,
                 endPoint.x, endPoint.y);
}

void Path::cubicTo (const float x1, const float y1,
                    const float x2, const float y2,
                    const float x3, const float y3)
{
    JUCE_CHECK_COORDS_ARE_VALID (x1, y1);
    JUCE_CHECK_COORDS_ARE_VALID (x2, y2);
    JUCE_CHECK_COORDS_ARE_VALID (x3, y3);

    if (numElements == 0)
        startNewSubPath (0, 0);

    preallocateSpace (7);

    data.elements[numElements++] = cubicMarker;
    data.elements[numElements++] = x1;
    data.elements[numElements++] = y1;
    data.elements[numElements++] = x2;
    data.elements[numElements++] = y2;
    data.elements[numElements++] = x3;
    data.elements[numElements++] = y3;

    bounds.extend (x1, y1, x2, y2);
    bounds.extend (x3, y3);
}

void Path::cubicTo (Point<float> controlPoint1,
                    Point<float> controlPoint2,
                    Point<float> endPoint)
{
    cubicTo (controlPoint1.x, controlPoint1.y,
             controlPoint2.x, controlPoint2.y,
             endPoint.x, endPoint.y);
}

void Path::closeSubPath()
{
    if (numElements > 0 && ! isMarker (data.elements[numElements - 1], closeSubPathMarker))
    {
        preallocateSpace (1);
        data.elements[numElements++] = closeSubPathMarker;
    }
}

Point<float> Path::getCurrentPosition() const
{
    int i = (int) numElements - 1;

    if (i > 0 && isMarker (data.elements[i], closeSubPathMarker))
    {
        while (i >= 0)
        {
            if (isMarker (data.elements[i], moveMarker))
            {
                i += 2;
                break;
            }

            --i;
        }
    }

    if (i > 0)
        return { data.elements[i - 1], data.elements[i] };

    return {};
}

void Path::addRectangle (const float x, const float y,
                         const float w, const float h)
{
    float x1 = x, y1 = y, x2 = x + w, y2 = y + h;

    if (w < 0) std::swap (x1, x2);
    if (h < 0) std::swap (y1, y2);

    preallocateSpace (13);

    if (numElements == 0)
    {
        bounds.pathXMin = x1;
        bounds.pathXMax = x2;
        bounds.pathYMin = y1;
        bounds.pathYMax = y2;
    }
    else
    {
        bounds.pathXMin = jmin (bounds.pathXMin, x1);
        bounds.pathXMax = jmax (bounds.pathXMax, x2);
        bounds.pathYMin = jmin (bounds.pathYMin, y1);
        bounds.pathYMax = jmax (bounds.pathYMax, y2);
    }

    data.elements[numElements++] = moveMarker;
    data.elements[numElements++] = x1;
    data.elements[numElements++] = y2;
    data.elements[numElements++] = lineMarker;
    data.elements[numElements++] = x1;
    data.elements[numElements++] = y1;
    data.elements[numElements++] = lineMarker;
    data.elements[numElements++] = x2;
    data.elements[numElements++] = y1;
    data.elements[numElements++] = lineMarker;
    data.elements[numElements++] = x2;
    data.elements[numElements++] = y2;
    data.elements[numElements++] = closeSubPathMarker;
}

void Path::addRoundedRectangle (float x, float y, float w, float h, float csx, float csy)
{
    addRoundedRectangle (x, y, w, h, csx, csy, true, true, true, true);
}

void Path::addRoundedRectangle (const float x, const float y, const float w, const float h,
                                float csx, float csy,
                                const bool curveTopLeft, const bool curveTopRight,
                                const bool curveBottomLeft, const bool curveBottomRight)
{
    csx = jmin (csx, w * 0.5f);
    csy = jmin (csy, h * 0.5f);
    auto cs45x = csx * 0.45f;
    auto cs45y = csy * 0.45f;
    auto x2 = x + w;
    auto y2 = y + h;

    if (curveTopLeft)
    {
        startNewSubPath (x, y + csy);
        cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y);
    }
    else
    {
        startNewSubPath (x, y);
    }

    if (curveTopRight)
    {
        lineTo (x2 - csx, y);
        cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy);
    }
    else
    {
        lineTo (x2, y);
    }

    if (curveBottomRight)
    {
        lineTo (x2, y2 - csy);
        cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2);
    }
    else
    {
        lineTo (x2, y2);
    }

    if (curveBottomLeft)
    {
        lineTo (x + csx, y2);
        cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy);
    }
    else
    {
        lineTo (x, y2);
    }

    closeSubPath();
}

void Path::addRoundedRectangle (float x, float y, float w, float h, float cs)
{
    addRoundedRectangle (x, y, w, h, cs, cs);
}

void Path::addTriangle (float x1, float y1,
                        float x2, float y2,
                        float x3, float y3)
{
    addTriangle (Point<float> (x1, y1),
                 Point<float> (x2, y2),
                 Point<float> (x3, y3));
}

void Path::addTriangle (Point<float> p1, Point<float> p2, Point<float> p3)
{
    startNewSubPath (p1);
    lineTo (p2);
    lineTo (p3);
    closeSubPath();
}

void Path::addQuadrilateral (const float x1, const float y1,
                             const float x2, const float y2,
                             const float x3, const float y3,
                             const float x4, const float y4)
{
    startNewSubPath (x1, y1);
    lineTo (x2, y2);
    lineTo (x3, y3);
    lineTo (x4, y4);
    closeSubPath();
}

void Path::addEllipse (float x, float y, float w, float h)
{
    addEllipse (Rectangle<float> (x, y, w, h));
}

void Path::addEllipse (Rectangle<float> area)
{
    auto hw = area.getWidth() * 0.5f;
    auto hw55 = hw * 0.55f;
    auto hh = area.getHeight() * 0.5f;
    auto hh55 = hh * 0.55f;
    auto cx = area.getX() + hw;
    auto cy = area.getY() + hh;

    startNewSubPath (cx, cy - hh);
    cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy);
    cubicTo (cx + hw, cy + hh55, cx + hw55, cy + hh, cx, cy + hh);
    cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh55, cx - hw, cy);
    cubicTo (cx - hw, cy - hh55, cx - hw55, cy - hh, cx, cy - hh);
    closeSubPath();
}

void Path::addArc (const float x, const float y,
                   const float w, const float h,
                   const float fromRadians,
                   const float toRadians,
                   const bool startAsNewSubPath)
{
    auto radiusX = w / 2.0f;
    auto radiusY = h / 2.0f;

    addCentredArc (x + radiusX,
                   y + radiusY,
                   radiusX, radiusY,
                   0.0f,
                   fromRadians, toRadians,
                   startAsNewSubPath);
}

void Path::addCentredArc (const float centreX, const float centreY,
                          const float radiusX, const float radiusY,
                          const float rotationOfEllipse,
                          const float fromRadians,
                          float toRadians,
                          const bool startAsNewSubPath)
{
    if (radiusX > 0.0f && radiusY > 0.0f)
    {
        const Point<float> centre (centreX, centreY);
        auto rotation = AffineTransform::rotation (rotationOfEllipse, centreX, centreY);
        auto angle = fromRadians;

        if (startAsNewSubPath)
            startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation));

        if (fromRadians < toRadians)
        {
            if (startAsNewSubPath)
                angle += PathHelpers::ellipseAngularIncrement;

            while (angle < toRadians)
            {
                lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation));
                angle += PathHelpers::ellipseAngularIncrement;
            }
        }
        else
        {
            if (startAsNewSubPath)
                angle -= PathHelpers::ellipseAngularIncrement;

            while (angle > toRadians)
            {
                lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation));
                angle -= PathHelpers::ellipseAngularIncrement;
            }
        }

        lineTo (centre.getPointOnCircumference (radiusX, radiusY, toRadians).transformedBy (rotation));
    }
}

void Path::addPieSegment (const float x, const float y,
                          const float width, const float height,
                          const float fromRadians,
                          const float toRadians,
                          const float innerCircleProportionalSize)
{
    float radiusX = width * 0.5f;
    float radiusY = height * 0.5f;
    const Point<float> centre (x + radiusX, y + radiusY);

    startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, fromRadians));
    addArc (x, y, width, height, fromRadians, toRadians);

    if (std::abs (fromRadians - toRadians) > float_Pi * 1.999f)
    {
        closeSubPath();

        if (innerCircleProportionalSize > 0)
        {
            radiusX *= innerCircleProportionalSize;
            radiusY *= innerCircleProportionalSize;

            startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, toRadians));
            addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians);
        }
    }
    else
    {
        if (innerCircleProportionalSize > 0)
        {
            radiusX *= innerCircleProportionalSize;
            radiusY *= innerCircleProportionalSize;

            addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians);
        }
        else
        {
            lineTo (centre);
        }
    }

    closeSubPath();
}

void Path::addPieSegment (Rectangle<float> segmentBounds,
                          const float fromRadians,
                          const float toRadians,
                          const float innerCircleProportionalSize)
{
    addPieSegment (segmentBounds.getX(),
                   segmentBounds.getY(),
                   segmentBounds.getWidth(),
                   segmentBounds.getHeight(),
                   fromRadians,
                   toRadians,
                   innerCircleProportionalSize);
}

//==============================================================================
void Path::addLineSegment (Line<float> line, float lineThickness)
{
    auto reversed = line.reversed();
    lineThickness *= 0.5f;

    startNewSubPath (line.getPointAlongLine (0, lineThickness));
    lineTo (line.getPointAlongLine (0, -lineThickness));
    lineTo (reversed.getPointAlongLine (0, lineThickness));
    lineTo (reversed.getPointAlongLine (0, -lineThickness));
    closeSubPath();
}

void Path::addArrow (Line<float> line, float lineThickness,
                     float arrowheadWidth, float arrowheadLength)
{
    auto reversed = line.reversed();
    lineThickness *= 0.5f;
    arrowheadWidth *= 0.5f;
    arrowheadLength = jmin (arrowheadLength, 0.8f * line.getLength());

    startNewSubPath (line.getPointAlongLine (0, lineThickness));
    lineTo (line.getPointAlongLine (0, -lineThickness));
    lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness));
    lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth));
    lineTo (line.getEnd());
    lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth));
    lineTo (reversed.getPointAlongLine (arrowheadLength, -lineThickness));
    closeSubPath();
}

void Path::addPolygon (Point<float> centre, int numberOfSides,
                       float radius, float startAngle)
{
    jassert (numberOfSides > 1); // this would be silly.

    if (numberOfSides > 1)
    {
        auto angleBetweenPoints = float_Pi * 2.0f / numberOfSides;

        for (int i = 0; i < numberOfSides; ++i)
        {
            auto angle = startAngle + i * angleBetweenPoints;
            auto p = centre.getPointOnCircumference (radius, angle);

            if (i == 0)
                startNewSubPath (p);
            else
                lineTo (p);
        }

        closeSubPath();
    }
}

void Path::addStar (Point<float> centre, int numberOfPoints, float innerRadius,
                    float outerRadius, float startAngle)
{
    jassert (numberOfPoints > 1); // this would be silly.

    if (numberOfPoints > 1)
    {
        auto angleBetweenPoints = float_Pi * 2.0f / numberOfPoints;

        for (int i = 0; i < numberOfPoints; ++i)
        {
            auto angle = startAngle + i * angleBetweenPoints;
            auto p = centre.getPointOnCircumference (outerRadius, angle);

            if (i == 0)
                startNewSubPath (p);
            else
                lineTo (p);

            lineTo (centre.getPointOnCircumference (innerRadius, angle + angleBetweenPoints * 0.5f));
        }

        closeSubPath();
    }
}

void Path::addBubble (Rectangle<float> bodyArea,
                      Rectangle<float> maximumArea,
                      Point<float> arrowTip,
                      const float cornerSize,
                      const float arrowBaseWidth)
{
    auto halfW = bodyArea.getWidth() / 2.0f;
    auto halfH = bodyArea.getHeight() / 2.0f;
    auto cornerSizeW = jmin (cornerSize, halfW);
    auto cornerSizeH = jmin (cornerSize, halfH);
    auto cornerSizeW2 = 2.0f * cornerSizeW;
    auto cornerSizeH2 = 2.0f * cornerSizeH;

    startNewSubPath (bodyArea.getX() + cornerSizeW, bodyArea.getY());

    const Rectangle<float> targetLimit (bodyArea.reduced (jmin (halfW - 1.0f, cornerSizeW + arrowBaseWidth),
                                                          jmin (halfH - 1.0f, cornerSizeH + arrowBaseWidth)));

    if (Rectangle<float> (targetLimit.getX(), maximumArea.getY(),
                          targetLimit.getWidth(), bodyArea.getY() - maximumArea.getY()).contains (arrowTip))
    {
        lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getY());
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getY());
    }

    lineTo (bodyArea.getRight() - cornerSizeW, bodyArea.getY());
    addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getY(), cornerSizeW2, cornerSizeH2, 0, float_Pi * 0.5f);

    if (Rectangle<float> (bodyArea.getRight(), targetLimit.getY(),
                          maximumArea.getRight() - bodyArea.getRight(), targetLimit.getHeight()).contains (arrowTip))
    {
        lineTo (bodyArea.getRight(), arrowTip.y - arrowBaseWidth);
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (bodyArea.getRight(), arrowTip.y + arrowBaseWidth);
    }

    lineTo (bodyArea.getRight(), bodyArea.getBottom() - cornerSizeH);
    addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi * 0.5f, float_Pi);

    if (Rectangle<float> (targetLimit.getX(), bodyArea.getBottom(),
                          targetLimit.getWidth(), maximumArea.getBottom() - bodyArea.getBottom()).contains (arrowTip))
    {
        lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getBottom());
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getBottom());
    }

    lineTo (bodyArea.getX() + cornerSizeW, bodyArea.getBottom());
    addArc (bodyArea.getX(), bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi, float_Pi * 1.5f);

    if (Rectangle<float> (maximumArea.getX(), targetLimit.getY(),
                          bodyArea.getX() - maximumArea.getX(), targetLimit.getHeight()).contains (arrowTip))
    {
        lineTo (bodyArea.getX(), arrowTip.y + arrowBaseWidth);
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (bodyArea.getX(), arrowTip.y - arrowBaseWidth);
    }

    lineTo (bodyArea.getX(), bodyArea.getY() + cornerSizeH);
    addArc (bodyArea.getX(), bodyArea.getY(), cornerSizeW2, cornerSizeH2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f);

    closeSubPath();
}

void Path::addPath (const Path& other)
{
    size_t i = 0;
    const float* d = other.data.elements;

    while (i < other.numElements)
    {
        auto type = d[i++];

        if (isMarker (type, moveMarker))
        {
            startNewSubPath (d[i], d[i + 1]);
            i += 2;
        }
        else if (isMarker (type, lineMarker))
        {
            lineTo (d[i], d[i + 1]);
            i += 2;
        }
        else if (isMarker (type, quadMarker))
        {
            quadraticTo (d[i], d[i + 1], d[i + 2], d[i + 3]);
            i += 4;
        }
        else if (isMarker (type, cubicMarker))
        {
            cubicTo (d[i], d[i + 1], d[i + 2], d[i + 3], d[i + 4], d[i + 5]);
            i += 6;
        }
        else if (isMarker (type, closeSubPathMarker))
        {
            closeSubPath();
        }
        else
        {
            // something's gone wrong with the element list!
            jassertfalse;
        }
    }
}

void Path::addPath (const Path& other,
                    const AffineTransform& transformToApply)
{
    size_t i = 0;
    const float* d = other.data.elements;

    while (i < other.numElements)
    {
        auto type = d[i++];

        if (isMarker (type, closeSubPathMarker))
        {
            closeSubPath();
        }
        else
        {
            auto x = d[i++];
            auto y = d[i++];
            transformToApply.transformPoint (x, y);

            if (isMarker (type, moveMarker))
            {
                startNewSubPath (x, y);
            }
            else if (isMarker (type, lineMarker))
            {
                lineTo (x, y);
            }
            else if (isMarker (type, quadMarker))
            {
                auto x2 = d[i++];
                auto y2 = d[i++];
                transformToApply.transformPoint (x2, y2);

                quadraticTo (x, y, x2, y2);
            }
            else if (isMarker (type, cubicMarker))
            {
                auto x2 = d[i++];
                auto y2 = d[i++];
                auto x3 = d[i++];
                auto y3 = d[i++];
                transformToApply.transformPoints (x2, y2, x3, y3);

                cubicTo (x, y, x2, y2, x3, y3);
            }
            else
            {
                // something's gone wrong with the element list!
                jassertfalse;
            }
        }
    }
}

//==============================================================================
void Path::applyTransform (const AffineTransform& transform) noexcept
{
    bounds.reset();
    bool firstPoint = true;
    float* d = data.elements;
    auto* end = d + numElements;

    while (d < end)
    {
        auto type = *d++;

        if (isMarker (type, moveMarker))
        {
            transform.transformPoint (d[0], d[1]);

            if (firstPoint)
            {
                firstPoint = false;
                bounds.reset (d[0], d[1]);
            }
            else
            {
                bounds.extend (d[0], d[1]);
            }

            d += 2;
        }
        else if (isMarker (type, lineMarker))
        {
            transform.transformPoint (d[0], d[1]);
            bounds.extend (d[0], d[1]);
            d += 2;
        }
        else if (isMarker (type, quadMarker))
        {
            transform.transformPoints (d[0], d[1], d[2], d[3]);
            bounds.extend (d[0], d[1], d[2], d[3]);
            d += 4;
        }
        else if (isMarker (type, cubicMarker))
        {
            transform.transformPoints (d[0], d[1], d[2], d[3], d[4], d[5]);
            bounds.extend (d[0], d[1], d[2], d[3]);
            bounds.extend (d[4], d[5]);
            d += 6;
        }
    }
}

//==============================================================================
AffineTransform Path::getTransformToScaleToFit (Rectangle<float> area, bool preserveProportions,
                                                Justification justification) const
{
    return getTransformToScaleToFit (area.getX(), area.getY(), area.getWidth(), area.getHeight(),
                                     preserveProportions, justification);
}

AffineTransform Path::getTransformToScaleToFit (const float x, const float y,
                                                const float w, const float h,
                                                const bool preserveProportions,
                                                Justification justification) const
{
    auto boundsRect = getBounds();

    if (preserveProportions)
    {
        if (w <= 0 || h <= 0 || boundsRect.isEmpty())
            return AffineTransform();

        float newW, newH;
        auto srcRatio = boundsRect.getHeight() / boundsRect.getWidth();

        if (srcRatio > h / w)
        {
            newW = h / srcRatio;
            newH = h;
        }
        else
        {
            newW = w;
            newH = w * srcRatio;
        }

        auto newXCentre = x;
        auto newYCentre = y;

        if (justification.testFlags (Justification::left))          newXCentre += newW * 0.5f;
        else if (justification.testFlags (Justification::right))    newXCentre += w - newW * 0.5f;
        else                                                        newXCentre += w * 0.5f;

        if (justification.testFlags (Justification::top))           newYCentre += newH * 0.5f;
        else if (justification.testFlags (Justification::bottom))   newYCentre += h - newH * 0.5f;
        else                                                        newYCentre += h * 0.5f;

        return AffineTransform::translation (boundsRect.getWidth()  * -0.5f - boundsRect.getX(),
                                             boundsRect.getHeight() * -0.5f - boundsRect.getY())
                    .scaled (newW / boundsRect.getWidth(),
                             newH / boundsRect.getHeight())
                    .translated (newXCentre, newYCentre);
    }
    else
    {
        return AffineTransform::translation (-boundsRect.getX(), -boundsRect.getY())
                    .scaled (w / boundsRect.getWidth(),
                             h / boundsRect.getHeight())
                    .translated (x, y);
    }
}

//==============================================================================
bool Path::contains (const float x, const float y, const float tolerance) const
{
    if (x <= bounds.pathXMin || x >= bounds.pathXMax
         || y <= bounds.pathYMin || y >= bounds.pathYMax)
        return false;

    PathFlatteningIterator i (*this, AffineTransform(), tolerance);

    int positiveCrossings = 0;
    int negativeCrossings = 0;

    while (i.next())
    {
        if ((i.y1 <= y && i.y2 > y) || (i.y2 <= y && i.y1 > y))
        {
            auto intersectX = i.x1 + (i.x2 - i.x1) * (y - i.y1) / (i.y2 - i.y1);

            if (intersectX <= x)
            {
                if (i.y1 < i.y2)
                    ++positiveCrossings;
                else
                    ++negativeCrossings;
            }
        }
    }

    return useNonZeroWinding ? (negativeCrossings != positiveCrossings)
                             : ((negativeCrossings + positiveCrossings) & 1) != 0;
}

bool Path::contains (Point<float> point, const float tolerance) const
{
    return contains (point.x, point.y, tolerance);
}

bool Path::intersectsLine (Line<float> line, const float tolerance)
{
    PathFlatteningIterator i (*this, AffineTransform(), tolerance);
    Point<float> intersection;

    while (i.next())
        if (line.intersects (Line<float> (i.x1, i.y1, i.x2, i.y2), intersection))
            return true;

    return false;
}

Line<float> Path::getClippedLine (Line<float> line, const bool keepSectionOutsidePath) const
{
    Line<float> result (line);
    const bool startInside = contains (line.getStart());
    const bool endInside   = contains (line.getEnd());

    if (startInside == endInside)
    {
        if (keepSectionOutsidePath == startInside)
            result = Line<float>();
    }
    else
    {
        PathFlatteningIterator i (*this, AffineTransform());
        Point<float> intersection;

        while (i.next())
        {
            if (line.intersects ({ i.x1, i.y1, i.x2, i.y2 }, intersection))
            {
                if ((startInside && keepSectionOutsidePath) || (endInside && ! keepSectionOutsidePath))
                    result.setStart (intersection);
                else
                    result.setEnd (intersection);
            }
        }
    }

    return result;
}

float Path::getLength (const AffineTransform& transform, float tolerance) const
{
    float length = 0;
    PathFlatteningIterator i (*this, transform, tolerance);

    while (i.next())
        length += Line<float> (i.x1, i.y1, i.x2, i.y2).getLength();

    return length;
}

Point<float> Path::getPointAlongPath (float distanceFromStart,
                                      const AffineTransform& transform,
                                      float tolerance) const
{
    PathFlatteningIterator i (*this, transform, tolerance);

    while (i.next())
    {
        const Line<float> line (i.x1, i.y1, i.x2, i.y2);
        auto lineLength = line.getLength();

        if (distanceFromStart <= lineLength)
            return line.getPointAlongLine (distanceFromStart);

        distanceFromStart -= lineLength;
    }

    return { i.x2, i.y2 };
}

float Path::getNearestPoint (Point<float> targetPoint, Point<float>& pointOnPath,
                             const AffineTransform& transform,
                             float tolerance) const
{
    PathFlatteningIterator i (*this, transform, tolerance);
    float bestPosition = 0, bestDistance = std::numeric_limits<float>::max();
    float length = 0;
    Point<float> pointOnLine;

    while (i.next())
    {
        const Line<float> line (i.x1, i.y1, i.x2, i.y2);
        auto distance = line.getDistanceFromPoint (targetPoint, pointOnLine);

        if (distance < bestDistance)
        {
            bestDistance = distance;
            bestPosition = length + pointOnLine.getDistanceFrom (line.getStart());
            pointOnPath = pointOnLine;
        }

        length += line.getLength();
    }

    return bestPosition;
}

//==============================================================================
Path Path::createPathWithRoundedCorners (const float cornerRadius) const
{
    if (cornerRadius <= 0.01f)
        return *this;

    Path p;
    size_t indexOfPathStart = 0, indexOfPathStartThis = 0;
    size_t n = 0;
    bool lastWasLine = false, firstWasLine = false;

    while (n < numElements)
    {
        auto type = data.elements[n++];

        if (isMarker (type, moveMarker))
        {
            indexOfPathStart = p.numElements;
            indexOfPathStartThis = n - 1;
            auto x = data.elements[n++];
            auto y = data.elements[n++];
            p.startNewSubPath (x, y);
            lastWasLine = false;
            firstWasLine = (isMarker (data.elements[n], lineMarker));
        }
        else if (isMarker (type, lineMarker) || isMarker (type, closeSubPathMarker))
        {
            float startX = 0, startY = 0, joinX = 0, joinY = 0, endX, endY;

            if (isMarker (type, lineMarker))
            {
                endX = data.elements[n++];
                endY = data.elements[n++];

                if (n > 8)
                {
                    startX = data.elements[n - 8];
                    startY = data.elements[n - 7];
                    joinX  = data.elements[n - 5];
                    joinY  = data.elements[n - 4];
                }
            }
            else
            {
                endX = data.elements[indexOfPathStartThis + 1];
                endY = data.elements[indexOfPathStartThis + 2];

                if (n > 6)
                {
                    startX = data.elements[n - 6];
                    startY = data.elements[n - 5];
                    joinX  = data.elements[n - 3];
                    joinY  = data.elements[n - 2];
                }
            }

            if (lastWasLine)
            {
                auto len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY);

                if (len1 > 0)
                {
                    auto propNeeded = jmin (0.5, cornerRadius / len1);

                    p.data.elements[p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded);
                    p.data.elements[p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded);
                }

                auto len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY);

                if (len2 > 0)
                {
                    auto propNeeded = jmin (0.5, cornerRadius / len2);

                    p.quadraticTo (joinX, joinY,
                                   (float) (joinX + (endX - joinX) * propNeeded),
                                   (float) (joinY + (endY - joinY) * propNeeded));
                }

                p.lineTo (endX, endY);
            }
            else if (isMarker (type, lineMarker))
            {
                p.lineTo (endX, endY);
                lastWasLine = true;
            }

            if (isMarker (type, closeSubPathMarker))
            {
                if (firstWasLine)
                {
                    startX = data.elements[n - 3];
                    startY = data.elements[n - 2];
                    joinX = endX;
                    joinY = endY;
                    endX = data.elements[indexOfPathStartThis + 4];
                    endY = data.elements[indexOfPathStartThis + 5];

                    auto len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY);

                    if (len1 > 0)
                    {
                        auto propNeeded = jmin (0.5, cornerRadius / len1);

                        p.data.elements[p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded);
                        p.data.elements[p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded);
                    }

                    auto len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY);

                    if (len2 > 0)
                    {
                        auto propNeeded = jmin (0.5, cornerRadius / len2);

                        endX = (float) (joinX + (endX - joinX) * propNeeded);
                        endY = (float) (joinY + (endY - joinY) * propNeeded);

                        p.quadraticTo (joinX, joinY, endX, endY);

                        p.data.elements[indexOfPathStart + 1] = endX;
                        p.data.elements[indexOfPathStart + 2] = endY;
                    }
                }

                p.closeSubPath();
            }
        }
        else if (isMarker (type, quadMarker))
        {
            lastWasLine = false;
            auto x1 = data.elements[n++];
            auto y1 = data.elements[n++];
            auto x2 = data.elements[n++];
            auto y2 = data.elements[n++];
            p.quadraticTo (x1, y1, x2, y2);
        }
        else if (isMarker (type, cubicMarker))
        {
            lastWasLine = false;
            auto x1 = data.elements[n++];
            auto y1 = data.elements[n++];
            auto x2 = data.elements[n++];
            auto y2 = data.elements[n++];
            auto x3 = data.elements[n++];
            auto y3 = data.elements[n++];
            p.cubicTo (x1, y1, x2, y2, x3, y3);
        }
    }

    return p;
}

//==============================================================================
void Path::loadPathFromStream (InputStream& source)
{
    while (! source.isExhausted())
    {
        switch (source.readByte())
        {
        case 'm':
        {
            auto x = source.readFloat();
            auto y = source.readFloat();
            startNewSubPath (x, y);
            break;
        }

        case 'l':
        {
            auto x = source.readFloat();
            auto y = source.readFloat();
            lineTo (x, y);
            break;
        }

        case 'q':
        {
            auto x1 = source.readFloat();
            auto y1 = source.readFloat();
            auto x2 = source.readFloat();
            auto y2 = source.readFloat();
            quadraticTo (x1, y1, x2, y2);
            break;
        }

        case 'b':
        {
            auto x1 = source.readFloat();
            auto y1 = source.readFloat();
            auto x2 = source.readFloat();
            auto y2 = source.readFloat();
            auto x3 = source.readFloat();
            auto y3 = source.readFloat();
            cubicTo (x1, y1, x2, y2, x3, y3);
            break;
        }

        case 'c':
            closeSubPath();
            break;

        case 'n':
            useNonZeroWinding = true;
            break;

        case 'z':
            useNonZeroWinding = false;
            break;

        case 'e':
            return; // end of path marker

        default:
            jassertfalse; // illegal char in the stream
            break;
        }
    }
}

void Path::loadPathFromData (const void* const pathData, const size_t numberOfBytes)
{
    MemoryInputStream in (pathData, numberOfBytes, false);
    loadPathFromStream (in);
}

void Path::writePathToStream (OutputStream& dest) const
{
    dest.writeByte (useNonZeroWinding ? 'n' : 'z');

    for (size_t i = 0; i < numElements;)
    {
        auto type = data.elements[i++];

        if (isMarker (type, moveMarker))
        {
            dest.writeByte ('m');
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
        }
        else if (isMarker (type, lineMarker))
        {
            dest.writeByte ('l');
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
        }
        else if (isMarker (type, quadMarker))
        {
            dest.writeByte ('q');
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
        }
        else if (isMarker (type, cubicMarker))
        {
            dest.writeByte ('b');
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
            dest.writeFloat (data.elements[i++]);
        }
        else if (isMarker (type, closeSubPathMarker))
        {
            dest.writeByte ('c');
        }
    }

    dest.writeByte ('e'); // marks the end-of-path
}

String Path::toString() const
{
    MemoryOutputStream s (2048);
    if (! useNonZeroWinding)
        s << 'a';

    size_t i = 0;
    float lastMarker = 0.0f;

    while (i < numElements)
    {
        auto type = data.elements[i++];
        char markerChar = 0;
        int numCoords = 0;

        if (isMarker (type, moveMarker))
        {
            markerChar = 'm';
            numCoords = 2;
        }
        else if (isMarker (type, lineMarker))
        {
            markerChar = 'l';
            numCoords = 2;
        }
        else if (isMarker (type, quadMarker))
        {
            markerChar = 'q';
            numCoords = 4;
        }
        else if (isMarker (type, cubicMarker))
        {
            markerChar = 'c';
            numCoords = 6;
        }
        else
        {
            jassert (isMarker (type, closeSubPathMarker));
            markerChar = 'z';
        }

        if (! isMarker (type, lastMarker))
        {
            if (s.getDataSize() != 0)
                s << ' ';

            s << markerChar;
            lastMarker = type;
        }

        while (--numCoords >= 0 && i < numElements)
        {
            String coord (data.elements[i++], 3);

            while (coord.endsWithChar ('0') && coord != "0")
                coord = coord.dropLastCharacters (1);

            if (coord.endsWithChar ('.'))
                coord = coord.dropLastCharacters (1);

            if (s.getDataSize() != 0)
                s << ' ';

            s << coord;
        }
    }

    return s.toUTF8();
}

void Path::restoreFromString (StringRef stringVersion)
{
    clear();
    setUsingNonZeroWinding (true);

    auto t = stringVersion.text;
    juce_wchar marker = 'm';
    int numValues = 2;
    float values[6];

    for (;;)
    {
        auto token = PathHelpers::nextToken (t);
        auto firstChar = token[0];
        int startNum = 0;

        if (firstChar == 0)
            break;

        if (firstChar == 'm' || firstChar == 'l')
        {
            marker = firstChar;
            numValues = 2;
        }
        else if (firstChar == 'q')
        {
            marker = firstChar;
            numValues = 4;
        }
        else if (firstChar == 'c')
        {
            marker = firstChar;
            numValues = 6;
        }
        else if (firstChar == 'z')
        {
            marker = firstChar;
            numValues = 0;
        }
        else if (firstChar == 'a')
        {
            setUsingNonZeroWinding (false);
            continue;
        }
        else
        {
            ++startNum;
            values [0] = token.getFloatValue();
        }

        for (int i = startNum; i < numValues; ++i)
            values [i] = PathHelpers::nextToken (t).getFloatValue();

        switch (marker)
        {
            case 'm':   startNewSubPath (values[0], values[1]); break;
            case 'l':   lineTo (values[0], values[1]); break;
            case 'q':   quadraticTo (values[0], values[1], values[2], values[3]); break;
            case 'c':   cubicTo (values[0], values[1], values[2], values[3], values[4], values[5]); break;
            case 'z':   closeSubPath(); break;
            default:    jassertfalse; break; // illegal string format?
        }
    }
}

//==============================================================================
Path::Iterator::Iterator (const Path& p) noexcept
    : elementType (startNewSubPath), path (p)
{
}

Path::Iterator::~Iterator() noexcept
{
}

bool Path::Iterator::next() noexcept
{
    const float* elements = path.data.elements;

    if (index < path.numElements)
    {
        auto type = elements[index++];

        if (isMarker (type, moveMarker))
        {
            elementType = startNewSubPath;
            x1 = elements[index++];
            y1 = elements[index++];
        }
        else if (isMarker (type, lineMarker))
        {
            elementType = lineTo;
            x1 = elements[index++];
            y1 = elements[index++];
        }
        else if (isMarker (type, quadMarker))
        {
            elementType = quadraticTo;
            x1 = elements[index++];
            y1 = elements[index++];
            x2 = elements[index++];
            y2 = elements[index++];
        }
        else if (isMarker (type, cubicMarker))
        {
            elementType = cubicTo;
            x1 = elements[index++];
            y1 = elements[index++];
            x2 = elements[index++];
            y2 = elements[index++];
            x3 = elements[index++];
            y3 = elements[index++];
        }
        else if (isMarker (type, closeSubPathMarker))
        {
            elementType = closePath;
        }

        return true;
    }

    return false;
}

#undef JUCE_CHECK_COORDS_ARE_VALID

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    A pair of (x, y) coordinates.

    The ValueType template should be a primitive type such as int, float, double,
    rather than a class.

    @see Line, Path, AffineTransform
*/
template <typename ValueType>
class Point
{
public:
    /** Creates a point at the origin */
    JUCE_CONSTEXPR Point() noexcept : x(), y() {}

    /** Creates a copy of another point. */
    JUCE_CONSTEXPR Point (const Point& other) noexcept : x (other.x), y (other.y)  {}

    /** Creates a point from an (x, y) position. */
    JUCE_CONSTEXPR Point (ValueType initialX, ValueType initialY) noexcept : x (initialX), y (initialY) {}

    //==============================================================================
    /** Copies this point from another one. */
    Point& operator= (const Point& other) noexcept                          { x = other.x; y = other.y; return *this; }

    JUCE_CONSTEXPR inline bool operator== (Point other) const noexcept      { return x == other.x && y == other.y; }
    JUCE_CONSTEXPR inline bool operator!= (Point other) const noexcept      { return x != other.x || y != other.y; }

    /** Returns true if the point is (0, 0). */
    JUCE_CONSTEXPR bool isOrigin() const noexcept                           { return x == ValueType() && y == ValueType(); }

    /** Returns true if the coordinates are finite values. */
    JUCE_CONSTEXPR inline bool isFinite() const noexcept                    { return juce_isfinite(x) && juce_isfinite(y); }

    /** Returns the point's x coordinate. */
    JUCE_CONSTEXPR inline ValueType getX() const noexcept                   { return x; }

    /** Returns the point's y coordinate. */
    JUCE_CONSTEXPR inline ValueType getY() const noexcept                   { return y; }

    /** Sets the point's x coordinate. */
    inline void setX (ValueType newX) noexcept                              { x = newX; }

    /** Sets the point's y coordinate. */
    inline void setY (ValueType newY) noexcept                              { y = newY; }

    /** Returns a point which has the same Y position as this one, but a new X. */
    JUCE_CONSTEXPR Point withX (ValueType newX) const noexcept              { return Point (newX, y); }

    /** Returns a point which has the same X position as this one, but a new Y. */
    JUCE_CONSTEXPR Point withY (ValueType newY) const noexcept              { return Point (x, newY); }

    /** Changes the point's x and y coordinates. */
    void setXY (ValueType newX, ValueType newY) noexcept                    { x = newX; y = newY; }

    /** Adds a pair of coordinates to this value. */
    void addXY (ValueType xToAdd, ValueType yToAdd) noexcept                { x += xToAdd; y += yToAdd; }

    //==============================================================================
    /** Returns a point with a given offset from this one. */
    JUCE_CONSTEXPR Point translated (ValueType deltaX, ValueType deltaY) const noexcept    { return Point (x + deltaX, y + deltaY); }

    /** Adds two points together */
    JUCE_CONSTEXPR Point operator+ (Point other) const noexcept             { return Point (x + other.x, y + other.y); }

    /** Adds another point's coordinates to this one */
    Point& operator+= (Point other) noexcept                                { x += other.x; y += other.y; return *this; }

    /** Subtracts one points from another */
    JUCE_CONSTEXPR Point operator- (Point other) const noexcept             { return Point (x - other.x, y - other.y); }

    /** Subtracts another point's coordinates to this one */
    Point& operator-= (Point other) noexcept                                { x -= other.x; y -= other.y; return *this; }

    /** Multiplies two points together */
    template <typename OtherType>
    JUCE_CONSTEXPR Point operator* (Point<OtherType> other) const noexcept  { return Point ((ValueType) (x * other.x), (ValueType) (y * other.y)); }

    /** Multiplies another point's coordinates to this one */
    template <typename OtherType>
    Point& operator*= (Point<OtherType> other) noexcept                     { *this = *this * other; return *this; }

    /** Divides one point by another */
    template <typename OtherType>
    JUCE_CONSTEXPR Point operator/ (Point<OtherType> other) const noexcept  { return Point ((ValueType) (x / other.x), (ValueType) (y / other.y)); }

    /** Divides this point's coordinates by another */
    template <typename OtherType>
    Point& operator/= (Point<OtherType> other) noexcept                     { *this = *this / other; return *this; }

    /** Returns a point whose coordinates are multiplied by a given scalar value. */
    template <typename FloatType>
    JUCE_CONSTEXPR Point operator* (FloatType multiplier) const noexcept    { return Point ((ValueType) (x * multiplier), (ValueType) (y * multiplier)); }

    /** Returns a point whose coordinates are divided by a given scalar value. */
    template <typename FloatType>
    JUCE_CONSTEXPR Point operator/ (FloatType divisor) const noexcept       { return Point ((ValueType) (x / divisor), (ValueType) (y / divisor)); }

    /** Multiplies the point's coordinates by a scalar value. */
    template <typename FloatType>
    Point& operator*= (FloatType multiplier) noexcept                       { x = (ValueType) (x * multiplier); y = (ValueType) (y * multiplier); return *this; }

    /** Divides the point's coordinates by a scalar value. */
    template <typename FloatType>
    Point& operator/= (FloatType divisor) noexcept                          { x = (ValueType) (x / divisor); y = (ValueType) (y / divisor); return *this; }

    /** Returns the inverse of this point. */
    JUCE_CONSTEXPR Point operator-() const noexcept                         { return Point (-x, -y); }

    //==============================================================================
    /** This type will be double if the Point's type is double, otherwise it will be float. */
    typedef typename TypeHelpers::SmallestFloatType<ValueType>::type FloatType;

    //==============================================================================
    /** Returns the straight-line distance between this point and the origin. */
    ValueType getDistanceFromOrigin() const noexcept                               { return juce_hypot (x, y); }

    /** Returns the straight-line distance between this point and another one. */
    ValueType getDistanceFrom (Point other) const noexcept                         { return juce_hypot (x - other.x, y - other.y); }

    /** Returns the square of the straight-line distance between this point and the origin. */
    JUCE_CONSTEXPR ValueType getDistanceSquaredFromOrigin() const noexcept         { return x * x + y * y; }

    /** Returns the square of the straight-line distance between this point and another one. */
    JUCE_CONSTEXPR ValueType getDistanceSquaredFrom (Point other) const noexcept   { return (*this - other).getDistanceSquaredFromOrigin(); }

    /** Returns the angle from this point to another one.

        Taking this point to be the centre of a circle, and the other point being a position on
        the circumference, the return value is the number of radians clockwise from the 12 o'clock
        direction.
        So 12 o'clock = 0, 3 o'clock = Pi/2, 6 o'clock = Pi, 9 o'clock = -Pi/2
    */
    FloatType getAngleToPoint (Point other) const noexcept
    {
        return static_cast<FloatType> (std::atan2 (static_cast<FloatType> (other.x - x),
                                                   static_cast<FloatType> (y - other.y)));
    }

    /** Returns the point that would be reached by rotating this point clockwise
        about the origin by the specified angle.
    */
    Point rotatedAboutOrigin (ValueType angleRadians) const noexcept
    {
        return Point (x * std::cos (angleRadians) - y * std::sin (angleRadians),
                      x * std::sin (angleRadians) + y * std::cos (angleRadians));
    }

    /** Taking this point to be the centre of a circle, this returns a point on its circumference.
        @param radius   the radius of the circle.
        @param angle    the angle of the point, in radians clockwise from the 12 o'clock position.
    */
    Point<FloatType> getPointOnCircumference (float radius, float angle) const noexcept
    {
        return Point<FloatType> (static_cast<FloatType> (x + radius * std::sin (angle)),
                                 static_cast<FloatType> (y - radius * std::cos (angle)));
    }

    /** Taking this point to be the centre of an ellipse, this returns a point on its circumference.
        @param radiusX  the horizontal radius of the circle.
        @param radiusY  the vertical radius of the circle.
        @param angle    the angle of the point, in radians clockwise from the 12 o'clock position.
    */
    Point<FloatType> getPointOnCircumference (float radiusX, float radiusY, float angle) const noexcept
    {
        return Point<FloatType> (static_cast<FloatType> (x + radiusX * std::sin (angle)),
                                 static_cast<FloatType> (y - radiusY * std::cos (angle)));
    }

    /** Returns the dot-product of two points (x1 * x2 + y1 * y2). */
    JUCE_CONSTEXPR FloatType getDotProduct (Point other) const noexcept          { return x * other.x + y * other.y; }

    //==============================================================================
    /** Uses a transform to change the point's coordinates.
        This will only compile if ValueType = float!

        @see AffineTransform::transformPoint
    */
    void applyTransform (const AffineTransform& transform) noexcept     { transform.transformPoint (x, y); }

    /** Returns the position of this point, if it is transformed by a given AffineTransform. */
    Point transformedBy (const AffineTransform& transform) const noexcept
    {
        return Point (static_cast<ValueType> (transform.mat00 * x + transform.mat01 * y + transform.mat02),
                      static_cast<ValueType> (transform.mat10 * x + transform.mat11 * y + transform.mat12));
    }

    //==============================================================================
    /** Casts this point to a Point<int> object. */
    JUCE_CONSTEXPR Point<int> toInt() const noexcept              { return Point<int> (static_cast<int> (x), static_cast<int> (y)); }

    /** Casts this point to a Point<float> object. */
    JUCE_CONSTEXPR Point<float> toFloat() const noexcept          { return Point<float> (static_cast<float> (x), static_cast<float> (y)); }

    /** Casts this point to a Point<double> object. */
    JUCE_CONSTEXPR Point<double> toDouble() const noexcept        { return Point<double> (static_cast<double> (x), static_cast<double> (y)); }

    /** Casts this point to a Point<int> object using roundToInt() to convert the values. */
    JUCE_CONSTEXPR Point<int> roundToInt() const noexcept         { return Point<int> (juce::roundToInt (x), juce::roundToInt (y)); }

    /** Returns the point as a string in the form "x, y". */
    String toString() const                                       { return String (x) + ", " + String (y); }

    //==============================================================================
    ValueType x; /**< The point's X coordinate. */
    ValueType y; /**< The point's Y coordinate. */
};

/** Multiplies the point's coordinates by a scalar value. */
template <typename ValueType>
Point<ValueType> operator* (ValueType value, Point<ValueType> p) noexcept       { return p * value; }

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Maintains a set of rectangles as a complex region.

    This class allows a set of rectangles to be treated as a solid shape, and can
    add and remove rectangular sections of it, and simplify overlapping or
    adjacent rectangles.

    @see Rectangle
*/
template <typename ValueType>
class RectangleList  final
{
public:
    typedef Rectangle<ValueType> RectangleType;

    //==============================================================================
    /** Creates an empty RectangleList */
    RectangleList() noexcept {}

    /** Creates a copy of another list */
    RectangleList (const RectangleList& other)  : rects (other.rects)
    {
    }

    /** Creates a list containing just one rectangle. */
    RectangleList (const RectangleType& rect)
    {
        addWithoutMerging (rect);
    }

    /** Copies this list from another one. */
    RectangleList& operator= (const RectangleList& other)
    {
        rects = other.rects;
        return *this;
    }

    /** Move constructor */
    RectangleList (RectangleList&& other) noexcept
        : rects (static_cast<Array<RectangleType>&&> (other.rects))
    {
    }

    /** Move assignment operator */
    RectangleList& operator= (RectangleList&& other) noexcept
    {
        rects = static_cast<Array<RectangleType>&&> (other.rects);
        return *this;
    }

    //==============================================================================
    /** Returns true if the region is empty. */
    bool isEmpty() const noexcept                               { return rects.isEmpty(); }

    /** Returns the number of rectangles in the list. */
    int getNumRectangles() const noexcept                       { return rects.size(); }

    /** Returns one of the rectangles at a particular index.
        @returns  the rectangle at the index, or an empty rectangle if the index is out-of-range.
    */
    RectangleType getRectangle (int index) const noexcept       { return rects[index]; }

    //==============================================================================
    /** Removes all rectangles to leave an empty region. */
    void clear()
    {
        rects.clearQuick();
    }

    /** Merges a new rectangle into the list.

        The rectangle being added will first be clipped to remove any parts of it
        that overlap existing rectangles in the list, and adjacent rectangles will be
        merged into it.

        The rectangle can have any size and may be empty, but if it's floating point
        then it's expected to not contain any INF values.
    */
    void add (const RectangleType& rect)
    {
        jassert (rect.isFinite()); // You must provide a valid rectangle to this method!

        if (! rect.isEmpty())
        {
            if (isEmpty())
            {
                rects.add (rect);
            }
            else
            {
                bool anyOverlaps = false;

                for (int j = rects.size(); --j >= 0;)
                {
                    auto& ourRect = rects.getReference (j);

                    if (rect.intersects (ourRect))
                    {
                        if (rect.contains (ourRect))
                            rects.remove (j);
                        else if (! ourRect.reduceIfPartlyContainedIn (rect))
                            anyOverlaps = true;
                    }
                }

                if (anyOverlaps && ! isEmpty())
                {
                    RectangleList r (rect);

                    for (auto& ourRect : rects)
                    {
                        if (rect.intersects (ourRect))
                        {
                            r.subtract (ourRect);

                            if (r.isEmpty())
                                return;
                        }
                    }

                    rects.addArray (r.rects);
                }
                else
                {
                    rects.add (rect);
                }
            }
        }
    }

    /** Merges a new rectangle into the list.

        The rectangle being added will first be clipped to remove any parts of it
        that overlap existing rectangles in the list.
    */
    void add (ValueType x, ValueType y, ValueType width, ValueType height)
    {
        add (RectangleType (x, y, width, height));
    }

    /** Dumbly adds a rectangle to the list without checking for overlaps.

        This simply adds the rectangle to the end, it doesn't merge it or remove
        any overlapping bits.

        The rectangle can have any size and may be empty, but if it's floating point
        then it's expected to not contain any INF values.
    */
    void addWithoutMerging (const RectangleType& rect)
    {
        jassert (rect.isFinite()); // You must provide a valid rectangle to this method!

        if (! rect.isEmpty())
            rects.add (rect);
    }

    /** Merges another rectangle list into this one.

        Any overlaps between the two lists will be clipped, so that the result is
        the union of both lists.
    */
    void add (const RectangleList& other)
    {
        for (auto& r : other)
            add (*r);
    }

    /** Removes a rectangular region from the list.

        Any rectangles in the list which overlap this will be clipped and subdivided
        if necessary.
    */
    void subtract (const RectangleType& rect)
    {
        if (auto numRects = rects.size())
        {
            auto x1 = rect.getX();
            auto y1 = rect.getY();
            auto x2 = x1 + rect.getWidth();
            auto y2 = y1 + rect.getHeight();

            for (int i = numRects; --i >= 0;)
            {
                auto& r = rects.getReference (i);

                auto rx1 = r.getX();
                auto ry1 = r.getY();
                auto rx2 = rx1 + r.getWidth();
                auto ry2 = ry1 + r.getHeight();

                if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2))
                {
                    if (x1 > rx1 && x1 < rx2)
                    {
                        if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2)
                        {
                            r.setWidth (x1 - rx1);
                        }
                        else
                        {
                            r.setX (x1);
                            r.setWidth (rx2 - x1);

                            rects.insert (++i, RectangleType (rx1, ry1, x1 - rx1,  ry2 - ry1));
                            ++i;
                        }
                    }
                    else if (x2 > rx1 && x2 < rx2)
                    {
                        r.setX (x2);
                        r.setWidth (rx2 - x2);

                        if (y1 > ry1 || y2 < ry2 || x1 > rx1)
                        {
                            rects.insert (++i, RectangleType (rx1, ry1, x2 - rx1,  ry2 - ry1));
                            ++i;
                        }
                    }
                    else if (y1 > ry1 && y1 < ry2)
                    {
                        if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2)
                        {
                            r.setHeight (y1 - ry1);
                        }
                        else
                        {
                            r.setY (y1);
                            r.setHeight (ry2 - y1);

                            rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y1 - ry1));
                            ++i;
                        }
                    }
                    else if (y2 > ry1 && y2 < ry2)
                    {
                        r.setY (y2);
                        r.setHeight (ry2 - y2);

                        if (x1 > rx1 || x2 < rx2 || y1 > ry1)
                        {
                            rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y2 - ry1));
                            ++i;
                        }
                    }
                    else
                    {
                        rects.remove (i);
                    }
                }
            }
        }
    }

    /** Removes all areas in another RectangleList from this one.

        Any rectangles in the list which overlap this will be clipped and subdivided
        if necessary.

        @returns true if the resulting list is non-empty.
    */
    bool subtract (const RectangleList& otherList)
    {
        for (auto& r : otherList)
        {
            if (isEmpty())
                return false;

            subtract (r);
        }

        return ! isEmpty();
    }

    /** Removes any areas of the region that lie outside a given rectangle.

        Any rectangles in the list which overlap this will be clipped and subdivided
        if necessary.

        Returns true if the resulting region is not empty, false if it is empty.

        @see getIntersectionWith
    */
    bool clipTo (const RectangleType& rect)
    {
        jassert (rect.isFinite()); // You must provide a valid rectangle to this method!

        bool notEmpty = false;

        if (rect.isEmpty())
        {
            clear();
        }
        else
        {
            for (int i = rects.size(); --i >= 0;)
            {
                auto& r = rects.getReference (i);

                if (! rect.intersectRectangle (r))
                    rects.remove (i);
                else
                    notEmpty = true;
            }
        }

        return notEmpty;
    }

    /** Removes any areas of the region that lie outside a given rectangle list.

        Any rectangles in this object which overlap the specified list will be clipped
        and subdivided if necessary.

        Returns true if the resulting region is not empty, false if it is empty.

        @see getIntersectionWith
    */
    template <typename OtherValueType>
    bool clipTo (const RectangleList<OtherValueType>& other)
    {
        if (isEmpty())
            return false;

        RectangleList result;

        for (auto& rect : rects)
        {
            for (auto& r : other)
            {
                auto clipped = r.template toType<ValueType>();

                if (rect.intersectRectangle (clipped))
                    result.rects.add (clipped);
            }
        }

        swapWith (result);
        return ! isEmpty();
    }

    /** Creates a region which is the result of clipping this one to a given rectangle.

        Unlike the other clipTo method, this one doesn't affect this object - it puts the
        resulting region into the list whose reference is passed-in.

        Returns true if the resulting region is not empty, false if it is empty.

        @see clipTo
    */
    bool getIntersectionWith (const RectangleType& rect, RectangleList& destRegion) const
    {
        jassert (rect.isFinite()); // You must provide a valid rectangle to this method!

        destRegion.clear();

        if (! rect.isEmpty())
            for (auto& r : rects)
                if (rect.intersectRectangle (r))
                    destRegion.rects.add (r);

        return ! destRegion.isEmpty();
    }

    /** Swaps the contents of this and another list.

        This swaps their internal pointers, so is hugely faster than using copy-by-value
        to swap them.
    */
    void swapWith (RectangleList& otherList) noexcept
    {
        rects.swapWith (otherList.rects);
    }

    //==============================================================================
    /** Checks whether the region contains a given point.
        @returns true if the point lies within one of the rectangles in the list
    */
    bool containsPoint (Point<ValueType> point) const noexcept
    {
        for (auto& r : rects)
            if (r.contains (point))
                return true;

        return false;
    }

    /** Checks whether the region contains a given point.
        @returns true if the point lies within one of the rectangles in the list
    */
    bool containsPoint (ValueType x, ValueType y) const noexcept
    {
        return containsPoint (Point<ValueType> (x, y));
    }

    /** Checks whether the region contains the whole of a given rectangle.

        @returns    true all parts of the rectangle passed in lie within the region
                    defined by this object
        @see intersectsRectangle, containsPoint
    */
    bool containsRectangle (const RectangleType& rectangleToCheck) const
    {
        if (rects.size() > 1)
        {
            RectangleList r (rectangleToCheck);

            for (auto& rect : rects)
            {
                r.subtract (rect);

                if (r.isEmpty())
                    return true;
            }
        }
        else if (! isEmpty())
        {
            return rects.getReference (0).contains (rectangleToCheck);
        }

        return false;
    }

    /** Checks whether the region contains any part of a given rectangle.

        @returns    true if any part of the rectangle passed in lies within the region
                    defined by this object
        @see containsRectangle
    */
    bool intersectsRectangle (const RectangleType& rectangleToCheck) const noexcept
    {
        for (auto& r : rects)
            if (r.intersects (rectangleToCheck))
                return true;

        return false;
    }

    /** Checks whether this region intersects any part of another one.

        @see intersectsRectangle
    */
    bool intersects (const RectangleList& other) const noexcept
    {
        for (auto& r : rects)
            if (other.intersectsRectangle (r))
                return true;

        return false;
    }

    //==============================================================================
    /** Returns the smallest rectangle that can enclose the whole of this region. */
    RectangleType getBounds() const noexcept
    {
        if (isEmpty())
            return {};

        auto& r = rects.getReference (0);

        if (rects.size() == 1)
            return r;

        auto minX = r.getX();
        auto minY = r.getY();
        auto maxX = minX + r.getWidth();
        auto maxY = minY + r.getHeight();

        for (int i = rects.size(); --i > 0;)
        {
            auto& r2 = rects.getReference (i);

            minX = jmin (minX, r2.getX());
            minY = jmin (minY, r2.getY());
            maxX = jmax (maxX, r2.getRight());
            maxY = jmax (maxY, r2.getBottom());
        }

        return { minX, minY, maxX - minX, maxY - minY };
    }

    /** Optimises the list into a minimum number of constituent rectangles.

        This will try to combine any adjacent rectangles into larger ones where
        possible, to simplify lists that might have been fragmented by repeated
        add/subtract calls.
    */
    void consolidate()
    {
        for (int i = 0; i < rects.size() - 1; ++i)
        {
            auto& r = rects.getReference (i);
            auto rx1 = r.getX();
            auto ry1 = r.getY();
            auto rx2 = rx1 + r.getWidth();
            auto ry2 = ry1 + r.getHeight();

            for (int j = rects.size(); --j > i;)
            {
                auto& r2 = rects.getReference (j);
                auto jrx1 = r2.getX();
                auto jry1 = r2.getY();
                auto jrx2 = jrx1 + r2.getWidth();
                auto jry2 = jry1 + r2.getHeight();

                // if the vertical edges of any blocks are touching and their horizontals don't
                // line up, split them horizontally..
                if (jrx1 == rx2 || jrx2 == rx1)
                {
                    if (jry1 > ry1 && jry1 < ry2)
                    {
                        r.setHeight (jry1 - ry1);
                        rects.add (RectangleType (rx1, jry1, rx2 - rx1, ry2 - jry1));
                        i = -1;
                        break;
                    }

                    if (jry2 > ry1 && jry2 < ry2)
                    {
                        r.setHeight (jry2 - ry1);
                        rects.add (RectangleType (rx1, jry2, rx2 - rx1, ry2 - jry2));
                        i = -1;
                        break;
                    }
                    else if (ry1 > jry1 && ry1 < jry2)
                    {
                        r2.setHeight (ry1 - jry1);
                        rects.add (RectangleType (jrx1, ry1, jrx2 - jrx1, jry2 - ry1));
                        i = -1;
                        break;
                    }
                    else if (ry2 > jry1 && ry2 < jry2)
                    {
                        r2.setHeight (ry2 - jry1);
                        rects.add (RectangleType (jrx1, ry2, jrx2 - jrx1, jry2 - ry2));
                        i = -1;
                        break;
                    }
                }
            }
        }

        for (int i = 0; i < rects.size() - 1; ++i)
        {
            auto& r = rects.getReference (i);

            for (int j = rects.size(); --j > i;)
            {
                if (r.enlargeIfAdjacent (rects.getReference (j)))
                {
                    rects.remove (j);
                    i = -1;
                    break;
                }
            }
        }
    }

    /** Adds an x and y value to all the coordinates. */
    void offsetAll (Point<ValueType> offset) noexcept
    {
        for (auto& r : rects)
            r += offset;
    }

    /** Adds an x and y value to all the coordinates. */
    void offsetAll (ValueType dx, ValueType dy) noexcept
    {
        offsetAll (Point<ValueType> (dx, dy));
    }

    /** Scales all the coordinates. */
    template <typename ScaleType>
    void scaleAll (ScaleType scaleFactor) noexcept
    {
        for (auto& r : rects)
            r *= scaleFactor;
    }

    /** Applies a transform to all the rectangles.
        Obviously this will create a mess if the transform involves any
        rotation or skewing.
    */
    void transformAll (const AffineTransform& transform) noexcept
    {
        for (auto& r : rects)
            r = r.transformedBy (transform);
    }

    //==============================================================================
    /** Creates a Path object to represent this region. */
    Path toPath() const
    {
        Path p;

        for (auto& r : rects)
            p.addRectangle (r);

        return p;
    }

    //==============================================================================
    /** Standard method for iterating the rectangles in the list. */
    const RectangleType* begin() const noexcept     { return rects.begin(); }
    /** Standard method for iterating the rectangles in the list. */
    const RectangleType* end() const noexcept       { return rects.end(); }

    /** Increases the internal storage to hold a minimum number of rectangles.
        Calling this before adding a large number of rectangles means that
        the array won't have to keep dynamically resizing itself as the elements
        are added, and it'll therefore be more efficient.
        @see Array::ensureStorageAllocated
    */
    void ensureStorageAllocated (int minNumRectangles)
    {
        rects.ensureStorageAllocated (minNumRectangles);
    }

private:
    //==============================================================================
    Array<RectangleType> rects;
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

#if JUCE_MSVC && JUCE_DEBUG
 #pragma optimize ("t", on)
#endif

//==============================================================================
PathFlatteningIterator::PathFlatteningIterator (const Path& path_,
                                                const AffineTransform& transform_,
                                                const float tolerance)
    : x2 (0),
      y2 (0),
      closesSubPath (false),
      subPathIndex (-1),
      path (path_),
      transform (transform_),
      points (path_.data.elements),
      toleranceSquared (tolerance * tolerance),
      isIdentityTransform (transform_.isIdentity())
{
    stackPos = stackBase;
}

PathFlatteningIterator::~PathFlatteningIterator()
{
}

bool PathFlatteningIterator::isLastInSubpath() const noexcept
{
    return stackPos == stackBase.get()
             && (index >= path.numElements || isMarker (points[index], Path::moveMarker));
}

bool PathFlatteningIterator::next()
{
    x1 = x2;
    y1 = y2;

    float x3 = 0;
    float y3 = 0;
    float x4 = 0;
    float y4 = 0;

    for (;;)
    {
        float type;

        if (stackPos == stackBase)
        {
            if (index >= path.numElements)
                return false;

            type = points [index++];

            if (! isMarker (type, Path::closeSubPathMarker))
            {
                x2 = points [index++];
                y2 = points [index++];

                if (isMarker (type, Path::quadMarker))
                {
                    x3 = points [index++];
                    y3 = points [index++];

                    if (! isIdentityTransform)
                        transform.transformPoints (x2, y2, x3, y3);
                }
                else if (isMarker (type, Path::cubicMarker))
                {
                    x3 = points [index++];
                    y3 = points [index++];
                    x4 = points [index++];
                    y4 = points [index++];

                    if (! isIdentityTransform)
                        transform.transformPoints (x2, y2, x3, y3, x4, y4);
                }
                else
                {
                    if (! isIdentityTransform)
                        transform.transformPoint (x2, y2);
                }
            }
        }
        else
        {
            type = *--stackPos;

            if (! isMarker (type, Path::closeSubPathMarker))
            {
                x2 = *--stackPos;
                y2 = *--stackPos;

                if (isMarker (type, Path::quadMarker))
                {
                    x3 = *--stackPos;
                    y3 = *--stackPos;
                }
                else if (isMarker (type, Path::cubicMarker))
                {
                    x3 = *--stackPos;
                    y3 = *--stackPos;
                    x4 = *--stackPos;
                    y4 = *--stackPos;
                }
            }
        }

        if (isMarker (type, Path::lineMarker))
        {
            ++subPathIndex;

            closesSubPath = (stackPos == stackBase)
                             && (index < path.numElements)
                             && (points [index] == Path::closeSubPathMarker)
                             && x2 == subPathCloseX
                             && y2 == subPathCloseY;

            return true;
        }

        if (isMarker (type, Path::quadMarker))
        {
            const size_t offset = (size_t) (stackPos - stackBase);

            if (offset >= stackSize - 10)
            {
                stackSize <<= 1;
                stackBase.realloc (stackSize);
                stackPos = stackBase + offset;
            }

            auto m1x = (x1 + x2) * 0.5f;
            auto m1y = (y1 + y2) * 0.5f;
            auto m2x = (x2 + x3) * 0.5f;
            auto m2y = (y2 + y3) * 0.5f;
            auto m3x = (m1x + m2x) * 0.5f;
            auto m3y = (m1y + m2y) * 0.5f;

            auto errorX = m3x - x2;
            auto errorY = m3y - y2;

            if (errorX * errorX + errorY * errorY > toleranceSquared)
            {
                *stackPos++ = y3;
                *stackPos++ = x3;
                *stackPos++ = m2y;
                *stackPos++ = m2x;
                *stackPos++ = Path::quadMarker;

                *stackPos++ = m3y;
                *stackPos++ = m3x;
                *stackPos++ = m1y;
                *stackPos++ = m1x;
                *stackPos++ = Path::quadMarker;
            }
            else
            {
                *stackPos++ = y3;
                *stackPos++ = x3;
                *stackPos++ = Path::lineMarker;

                *stackPos++ = m3y;
                *stackPos++ = m3x;
                *stackPos++ = Path::lineMarker;
            }

            jassert (stackPos < stackBase + stackSize);
        }
        else if (isMarker (type, Path::cubicMarker))
        {
            const size_t offset = (size_t) (stackPos - stackBase);

            if (offset >= stackSize - 16)
            {
                stackSize <<= 1;
                stackBase.realloc (stackSize);
                stackPos = stackBase + offset;
            }

            auto m1x = (x1 + x2) * 0.5f;
            auto m1y = (y1 + y2) * 0.5f;
            auto m2x = (x3 + x2) * 0.5f;
            auto m2y = (y3 + y2) * 0.5f;
            auto m3x = (x3 + x4) * 0.5f;
            auto m3y = (y3 + y4) * 0.5f;
            auto m4x = (m1x + m2x) * 0.5f;
            auto m4y = (m1y + m2y) * 0.5f;
            auto m5x = (m3x + m2x) * 0.5f;
            auto m5y = (m3y + m2y) * 0.5f;

            auto error1X = m4x - x2;
            auto error1Y = m4y - y2;
            auto error2X = m5x - x3;
            auto error2Y = m5y - y3;

            if (error1X * error1X + error1Y * error1Y > toleranceSquared
                 || error2X * error2X + error2Y * error2Y > toleranceSquared)
            {
                *stackPos++ = y4;
                *stackPos++ = x4;
                *stackPos++ = m3y;
                *stackPos++ = m3x;
                *stackPos++ = m5y;
                *stackPos++ = m5x;
                *stackPos++ = Path::cubicMarker;

                *stackPos++ = (m4y + m5y) * 0.5f;
                *stackPos++ = (m4x + m5x) * 0.5f;
                *stackPos++ = m4y;
                *stackPos++ = m4x;
                *stackPos++ = m1y;
                *stackPos++ = m1x;
                *stackPos++ = Path::cubicMarker;
            }
            else
            {
                *stackPos++ = y4;
                *stackPos++ = x4;
                *stackPos++ = Path::lineMarker;

                *stackPos++ = m5y;
                *stackPos++ = m5x;
                *stackPos++ = Path::lineMarker;

                *stackPos++ = m4y;
                *stackPos++ = m4x;
                *stackPos++ = Path::lineMarker;
            }
        }
        else if (isMarker (type, Path::closeSubPathMarker))
        {
            if (x2 != subPathCloseX || y2 != subPathCloseY)
            {
                x1 = x2;
                y1 = y2;
                x2 = subPathCloseX;
                y2 = subPathCloseY;
                closesSubPath = true;

                return true;
            }
        }
        else
        {
            jassert (isMarker (type, Path::moveMarker));

            subPathIndex = -1;
            subPathCloseX = x1 = x2;
            subPathCloseY = y1 = y2;
        }
    }
}

#if JUCE_MSVC && JUCE_DEBUG
  #pragma optimize ("", on)  // resets optimisations to the project defaults
#endif

} // namespace juce