/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ // tests that some co-ords aren't NaNs #define JUCE_CHECK_COORDS_ARE_VALID(x, y) \ jassert (x == x && y == y); //============================================================================== namespace PathHelpers { const float ellipseAngularIncrement = 0.05f; String nextToken (String::CharPointerType& t) { t = t.findEndOfWhitespace(); String::CharPointerType start (t); size_t numChars = 0; while (! (t.isEmpty() || t.isWhitespace())) { ++t; ++numChars; } return String (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; //============================================================================== Path::Path() : numElements (0), pathXMin (0), pathXMax (0), pathYMin (0), pathYMax (0), useNonZeroWinding (true) { } Path::~Path() { } Path::Path (const Path& other) : numElements (other.numElements), pathXMin (other.pathXMin), pathXMax (other.pathXMax), pathYMin (other.pathYMin), pathYMax (other.pathYMax), 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; pathXMin = other.pathXMin; pathXMax = other.pathXMax; pathYMin = other.pathYMin; pathYMax = other.pathYMax; useNonZeroWinding = other.useNonZeroWinding; if (numElements > 0) memcpy (data.elements, other.data.elements, numElements * sizeof (float)); } return *this; } #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS Path::Path (Path&& other) noexcept : data (static_cast &&> (other.data)), numElements (other.numElements), pathXMin (other.pathXMin), pathXMax (other.pathXMax), pathYMin (other.pathYMin), pathYMax (other.pathYMax), useNonZeroWinding (other.useNonZeroWinding) { } Path& Path::operator= (Path&& other) noexcept { data = static_cast &&> (other.data); numElements = other.numElements; pathXMin = other.pathXMin; pathXMax = other.pathXMax; pathYMin = other.pathYMin; pathYMax = other.pathYMax; useNonZeroWinding = other.useNonZeroWinding; return *this; } #endif 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; pathXMin = 0; pathYMin = 0; pathYMax = 0; pathXMax = 0; } void Path::swapWithPath (Path& other) noexcept { data.swapWith (other.data); std::swap (numElements, other.numElements); std::swap (pathXMin, other.pathXMin); std::swap (pathXMax, other.pathXMax); std::swap (pathYMin, other.pathYMin); std::swap (pathYMax, other.pathYMax); std::swap (useNonZeroWinding, other.useNonZeroWinding); } //============================================================================== void Path::setUsingNonZeroWinding (const bool isNonZero) noexcept { useNonZeroWinding = isNonZero; } void Path::scaleToFit (const float x, const float y, const float w, const float h, const bool preserveProportions) noexcept { applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions)); } //============================================================================== bool Path::isEmpty() const noexcept { size_t i = 0; while (i < numElements) { const float type = data.elements [i++]; if (type == moveMarker) { i += 2; } else if (type == lineMarker || type == quadMarker || type == cubicMarker) { return false; } } return true; } Rectangle Path::getBounds() const noexcept { return Rectangle (pathXMin, pathYMin, pathXMax - pathXMin, pathYMax - pathYMin); } Rectangle Path::getBoundsTransformed (const AffineTransform& transform) const noexcept { return getBounds().transformed (transform); } //============================================================================== void Path::startNewSubPath (const float x, const float y) { JUCE_CHECK_COORDS_ARE_VALID (x, y); if (numElements == 0) { pathXMin = pathXMax = x; pathYMin = pathYMax = y; } else { pathXMin = jmin (pathXMin, x); pathXMax = jmax (pathXMax, x); pathYMin = jmin (pathYMin, y); pathYMax = jmax (pathYMax, y); } data.ensureAllocatedSize ((int) numElements + 3); data.elements [numElements++] = moveMarker; data.elements [numElements++] = x; data.elements [numElements++] = y; } void Path::startNewSubPath (const Point& 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); data.ensureAllocatedSize ((int) numElements + 3); data.elements [numElements++] = lineMarker; data.elements [numElements++] = x; data.elements [numElements++] = y; pathXMin = jmin (pathXMin, x); pathXMax = jmax (pathXMax, x); pathYMin = jmin (pathYMin, y); pathYMax = jmax (pathYMax, y); } void Path::lineTo (const Point& 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); data.ensureAllocatedSize ((int) numElements + 5); data.elements [numElements++] = quadMarker; data.elements [numElements++] = x1; data.elements [numElements++] = y1; data.elements [numElements++] = x2; data.elements [numElements++] = y2; pathXMin = jmin (pathXMin, x1, x2); pathXMax = jmax (pathXMax, x1, x2); pathYMin = jmin (pathYMin, y1, y2); pathYMax = jmax (pathYMax, y1, y2); } void Path::quadraticTo (const Point& controlPoint, const Point& 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); data.ensureAllocatedSize ((int) numElements + 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; pathXMin = jmin (pathXMin, x1, x2, x3); pathXMax = jmax (pathXMax, x1, x2, x3); pathYMin = jmin (pathYMin, y1, y2, y3); pathYMax = jmax (pathYMax, y1, y2, y3); } void Path::cubicTo (const Point& controlPoint1, const Point& controlPoint2, const Point& endPoint) { cubicTo (controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y); } void Path::closeSubPath() { if (numElements > 0 && data.elements [numElements - 1] != closeSubPathMarker) { data.ensureAllocatedSize ((int) numElements + 1); data.elements [numElements++] = closeSubPathMarker; } } Point Path::getCurrentPosition() const { int i = (int) numElements - 1; if (i > 0 && data.elements[i] == closeSubPathMarker) { while (i >= 0) { if (data.elements[i] == moveMarker) { i += 2; break; } --i; } } if (i > 0) return Point (data.elements [i - 1], data.elements [i]); return Point(); } 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); data.ensureAllocatedSize ((int) numElements + 13); if (numElements == 0) { pathXMin = x1; pathXMax = x2; pathYMin = y1; pathYMax = y2; } else { pathXMin = jmin (pathXMin, x1); pathXMax = jmax (pathXMax, x2); pathYMin = jmin (pathYMin, y1); pathYMax = jmax (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 (const float x, const float y, const float w, const float h, float csx, float csy) { csx = jmin (csx, w * 0.5f); csy = jmin (csy, h * 0.5f); const float cs45x = csx * 0.45f; const float cs45y = csy * 0.45f; const float x2 = x + w; const float y2 = y + h; startNewSubPath (x + csx, y); lineTo (x2 - csx, y); cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); lineTo (x2, y2 - csy); cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); lineTo (x + csx, y2); cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); lineTo (x, y + csy); cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); closeSubPath(); } void Path::addRoundedRectangle (const float x, const float y, const float w, const float h, float cs) { addRoundedRectangle (x, y, w, h, cs, cs); } void Path::addTriangle (const float x1, const float y1, const float x2, const float y2, const float x3, const float y3) { startNewSubPath (x1, y1); lineTo (x2, y2); lineTo (x3, y3); 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 (const float x, const float y, const float w, const float h) { const float hw = w * 0.5f; const float hw55 = hw * 0.55f; const float hh = h * 0.5f; const float hh55 = hh * 0.55f; const float cx = x + hw; const float cy = y + 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) { const float radiusX = w / 2.0f; const float 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 centre (centreX, centreY); const AffineTransform rotation (AffineTransform::rotation (rotationOfEllipse, centreX, centreY)); float 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 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::addLineSegment (const Line& line, float lineThickness) { const Line 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 (const Line& line, float lineThickness, float arrowheadWidth, float arrowheadLength) { const Line 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 (const Point& centre, const int numberOfSides, const float radius, const float startAngle) { jassert (numberOfSides > 1); // this would be silly. if (numberOfSides > 1) { const float angleBetweenPoints = float_Pi * 2.0f / numberOfSides; for (int i = 0; i < numberOfSides; ++i) { const float angle = startAngle + i * angleBetweenPoints; const Point p (centre.getPointOnCircumference (radius, angle)); if (i == 0) startNewSubPath (p); else lineTo (p); } closeSubPath(); } } void Path::addStar (const Point& centre, const int numberOfPoints, const float innerRadius, const float outerRadius, const float startAngle) { jassert (numberOfPoints > 1); // this would be silly. if (numberOfPoints > 1) { const float angleBetweenPoints = float_Pi * 2.0f / numberOfPoints; for (int i = 0; i < numberOfPoints; ++i) { const float angle = startAngle + i * angleBetweenPoints; const Point 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 (float x, float y, float w, float h, float cs, float tipX, float tipY, int whichSide, float arrowPos, float arrowWidth) { if (w > 1.0f && h > 1.0f) { cs = jmin (cs, w * 0.5f, h * 0.5f); const float cs2 = 2.0f * cs; startNewSubPath (x + cs, y); if (whichSide == 0) { const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; const float arrowX1 = x + cs + jmax (0.0f, (w - cs2 - arrowWidth) * arrowPos - halfArrowW); lineTo (arrowX1, y); lineTo (tipX, tipY); lineTo (arrowX1 + halfArrowW * 2.0f, y); } lineTo (x + w - cs, y); if (cs > 0.0f) addArc (x + w - cs2, y, cs2, cs2, 0, float_Pi * 0.5f); if (whichSide == 3) { const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; const float arrowY1 = y + cs + jmax (0.0f, (h - cs2 - arrowWidth) * arrowPos - halfArrowH); lineTo (x + w, arrowY1); lineTo (tipX, tipY); lineTo (x + w, arrowY1 + halfArrowH * 2.0f); } lineTo (x + w, y + h - cs); if (cs > 0.0f) addArc (x + w - cs2, y + h - cs2, cs2, cs2, float_Pi * 0.5f, float_Pi); if (whichSide == 2) { const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; const float arrowX1 = x + cs + jmax (0.0f, (w - cs2 - arrowWidth) * arrowPos - halfArrowW); lineTo (arrowX1 + halfArrowW * 2.0f, y + h); lineTo (tipX, tipY); lineTo (arrowX1, y + h); } lineTo (x + cs, y + h); if (cs > 0.0f) addArc (x, y + h - cs2, cs2, cs2, float_Pi, float_Pi * 1.5f); if (whichSide == 1) { const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; const float arrowY1 = y + cs + jmax (0.0f, (h - cs2 - arrowWidth) * arrowPos - halfArrowH); lineTo (x, arrowY1 + halfArrowH * 2.0f); lineTo (tipX, tipY); lineTo (x, arrowY1); } lineTo (x, y + cs); if (cs > 0.0f) addArc (x, y, cs2, cs2, float_Pi * 1.5f, float_Pi * 2.0f - PathHelpers::ellipseAngularIncrement); closeSubPath(); } } void Path::addPath (const Path& other) { size_t i = 0; while (i < other.numElements) { const float type = other.data.elements [i++]; if (type == moveMarker) { startNewSubPath (other.data.elements [i], other.data.elements [i + 1]); i += 2; } else if (type == lineMarker) { lineTo (other.data.elements [i], other.data.elements [i + 1]); i += 2; } else if (type == quadMarker) { quadraticTo (other.data.elements [i], other.data.elements [i + 1], other.data.elements [i + 2], other.data.elements [i + 3]); i += 4; } else if (type == cubicMarker) { cubicTo (other.data.elements [i], other.data.elements [i + 1], other.data.elements [i + 2], other.data.elements [i + 3], other.data.elements [i + 4], other.data.elements [i + 5]); i += 6; } else if (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; while (i < other.numElements) { const float type = other.data.elements [i++]; if (type == closeSubPathMarker) { closeSubPath(); } else { float x = other.data.elements [i++]; float y = other.data.elements [i++]; transformToApply.transformPoint (x, y); if (type == moveMarker) { startNewSubPath (x, y); } else if (type == lineMarker) { lineTo (x, y); } else if (type == quadMarker) { float x2 = other.data.elements [i++]; float y2 = other.data.elements [i++]; transformToApply.transformPoint (x2, y2); quadraticTo (x, y, x2, y2); } else if (type == cubicMarker) { float x2 = other.data.elements [i++]; float y2 = other.data.elements [i++]; float x3 = other.data.elements [i++]; float y3 = other.data.elements [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 { size_t i = 0; pathYMin = pathXMin = 0; pathYMax = pathXMax = 0; bool setMaxMin = false; while (i < numElements) { const float type = data.elements [i++]; if (type == moveMarker) { transform.transformPoint (data.elements [i], data.elements [i + 1]); if (setMaxMin) { pathXMin = jmin (pathXMin, data.elements [i]); pathXMax = jmax (pathXMax, data.elements [i]); pathYMin = jmin (pathYMin, data.elements [i + 1]); pathYMax = jmax (pathYMax, data.elements [i + 1]); } else { pathXMin = pathXMax = data.elements [i]; pathYMin = pathYMax = data.elements [i + 1]; setMaxMin = true; } i += 2; } else if (type == lineMarker) { transform.transformPoint (data.elements [i], data.elements [i + 1]); pathXMin = jmin (pathXMin, data.elements [i]); pathXMax = jmax (pathXMax, data.elements [i]); pathYMin = jmin (pathYMin, data.elements [i + 1]); pathYMax = jmax (pathYMax, data.elements [i + 1]); i += 2; } else if (type == quadMarker) { transform.transformPoints (data.elements [i], data.elements [i + 1], data.elements [i + 2], data.elements [i + 3]); pathXMin = jmin (pathXMin, data.elements [i], data.elements [i + 2]); pathXMax = jmax (pathXMax, data.elements [i], data.elements [i + 2]); pathYMin = jmin (pathYMin, data.elements [i + 1], data.elements [i + 3]); pathYMax = jmax (pathYMax, data.elements [i + 1], data.elements [i + 3]); i += 4; } else if (type == cubicMarker) { transform.transformPoints (data.elements [i], data.elements [i + 1], data.elements [i + 2], data.elements [i + 3], data.elements [i + 4], data.elements [i + 5]); pathXMin = jmin (pathXMin, data.elements [i], data.elements [i + 2], data.elements [i + 4]); pathXMax = jmax (pathXMax, data.elements [i], data.elements [i + 2], data.elements [i + 4]); pathYMin = jmin (pathYMin, data.elements [i + 1], data.elements [i + 3], data.elements [i + 5]); pathYMax = jmax (pathYMax, data.elements [i + 1], data.elements [i + 3], data.elements [i + 5]); i += 6; } } } //============================================================================== AffineTransform Path::getTransformToScaleToFit (const float x, const float y, const float w, const float h, const bool preserveProportions, const Justification& justification) const { Rectangle bounds (getBounds()); if (preserveProportions) { if (w <= 0 || h <= 0 || bounds.isEmpty()) return AffineTransform::identity; float newW, newH; const float srcRatio = bounds.getHeight() / bounds.getWidth(); if (srcRatio > h / w) { newW = h / srcRatio; newH = h; } else { newW = w; newH = w * srcRatio; } float newXCentre = x; float 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 (bounds.getWidth() * -0.5f - bounds.getX(), bounds.getHeight() * -0.5f - bounds.getY()) .scaled (newW / bounds.getWidth(), newH / bounds.getHeight()) .translated (newXCentre, newYCentre); } else { return AffineTransform::translation (-bounds.getX(), -bounds.getY()) .scaled (w / bounds.getWidth(), h / bounds.getHeight()) .translated (x, y); } } //============================================================================== bool Path::contains (const float x, const float y, const float tolerance) const { if (x <= pathXMin || x >= pathXMax || y <= pathYMin || y >= pathYMax) return false; PathFlatteningIterator i (*this, AffineTransform::identity, tolerance); int positiveCrossings = 0; int negativeCrossings = 0; while (i.next()) { if ((i.y1 <= y && i.y2 > y) || (i.y2 <= y && i.y1 > y)) { const float 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 (const Point& point, const float tolerance) const { return contains (point.x, point.y, tolerance); } bool Path::intersectsLine (const Line& line, const float tolerance) { PathFlatteningIterator i (*this, AffineTransform::identity, tolerance); Point intersection; while (i.next()) if (line.intersects (Line (i.x1, i.y1, i.x2, i.y2), intersection)) return true; return false; } Line Path::getClippedLine (const Line& line, const bool keepSectionOutsidePath) const { Line result (line); const bool startInside = contains (line.getStart()); const bool endInside = contains (line.getEnd()); if (startInside == endInside) { if (keepSectionOutsidePath == startInside) result = Line(); } else { PathFlatteningIterator i (*this, AffineTransform::identity); Point intersection; while (i.next()) { if (line.intersects (Line (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) const { float length = 0; PathFlatteningIterator i (*this, transform); while (i.next()) length += Line (i.x1, i.y1, i.x2, i.y2).getLength(); return length; } Point Path::getPointAlongPath (float distanceFromStart, const AffineTransform& transform) const { PathFlatteningIterator i (*this, transform); while (i.next()) { const Line line (i.x1, i.y1, i.x2, i.y2); const float lineLength = line.getLength(); if (distanceFromStart <= lineLength) return line.getPointAlongLine (distanceFromStart); distanceFromStart -= lineLength; } return Point (i.x2, i.y2); } float Path::getNearestPoint (const Point& targetPoint, Point& pointOnPath, const AffineTransform& transform) const { PathFlatteningIterator i (*this, transform); float bestPosition = 0, bestDistance = std::numeric_limits::max(); float length = 0; Point pointOnLine; while (i.next()) { const Line line (i.x1, i.y1, i.x2, i.y2); const float 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; size_t indexOfPathStart = 0, indexOfPathStartThis = 0; size_t n = 0; bool lastWasLine = false, firstWasLine = false; Path p; while (n < numElements) { const float type = data.elements [n++]; if (type == moveMarker) { indexOfPathStart = p.numElements; indexOfPathStartThis = n - 1; const float x = data.elements [n++]; const float y = data.elements [n++]; p.startNewSubPath (x, y); lastWasLine = false; firstWasLine = (data.elements [n] == lineMarker); } else if (type == lineMarker || type == closeSubPathMarker) { float startX = 0, startY = 0, joinX = 0, joinY = 0, endX, endY; if (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) { const double len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY); if (len1 > 0) { const double 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); } const double len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY); if (len2 > 0) { const double 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 (type == lineMarker) { p.lineTo (endX, endY); lastWasLine = true; } if (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]; const double len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY); if (len1 > 0) { const double 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); } const double len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY); if (len2 > 0) { const double 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 (type == quadMarker) { lastWasLine = false; const float x1 = data.elements [n++]; const float y1 = data.elements [n++]; const float x2 = data.elements [n++]; const float y2 = data.elements [n++]; p.quadraticTo (x1, y1, x2, y2); } else if (type == cubicMarker) { lastWasLine = false; const float x1 = data.elements [n++]; const float y1 = data.elements [n++]; const float x2 = data.elements [n++]; const float y2 = data.elements [n++]; const float x3 = data.elements [n++]; const float 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': { const float x = source.readFloat(); const float y = source.readFloat(); startNewSubPath (x, y); break; } case 'l': { const float x = source.readFloat(); const float y = source.readFloat(); lineTo (x, y); break; } case 'q': { const float x1 = source.readFloat(); const float y1 = source.readFloat(); const float x2 = source.readFloat(); const float y2 = source.readFloat(); quadraticTo (x1, y1, x2, y2); break; } case 'b': { const float x1 = source.readFloat(); const float y1 = source.readFloat(); const float x2 = source.readFloat(); const float y2 = source.readFloat(); const float x3 = source.readFloat(); const float 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'); size_t i = 0; while (i < numElements) { const float type = data.elements [i++]; if (type == moveMarker) { dest.writeByte ('m'); dest.writeFloat (data.elements [i++]); dest.writeFloat (data.elements [i++]); } else if (type == lineMarker) { dest.writeByte ('l'); dest.writeFloat (data.elements [i++]); dest.writeFloat (data.elements [i++]); } else if (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 (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 (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) { const float marker = data.elements [i++]; char markerChar = 0; int numCoords = 0; if (marker == moveMarker) { markerChar = 'm'; numCoords = 2; } else if (marker == lineMarker) { markerChar = 'l'; numCoords = 2; } else if (marker == quadMarker) { markerChar = 'q'; numCoords = 4; } else if (marker == cubicMarker) { markerChar = 'c'; numCoords = 6; } else { jassert (marker == closeSubPathMarker); markerChar = 'z'; } if (marker != lastMarker) { if (s.getDataSize() != 0) s << ' '; s << markerChar; lastMarker = marker; } 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 (const String& stringVersion) { clear(); setUsingNonZeroWinding (true); String::CharPointerType t (stringVersion.getCharPointer()); juce_wchar marker = 'm'; int numValues = 2; float values [6]; for (;;) { const String token (PathHelpers::nextToken (t)); const juce_wchar 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& path_) : path (path_), index (0) { } Path::Iterator::~Iterator() { } bool Path::Iterator::next() { const float* const elements = path.data.elements; if (index < path.numElements) { const float type = elements [index++]; if (type == moveMarker) { elementType = startNewSubPath; x1 = elements [index++]; y1 = elements [index++]; } else if (type == lineMarker) { elementType = lineTo; x1 = elements [index++]; y1 = elements [index++]; } else if (type == quadMarker) { elementType = quadraticTo; x1 = elements [index++]; y1 = elements [index++]; x2 = elements [index++]; y2 = elements [index++]; } else if (type == cubicMarker) { elementType = cubicTo; x1 = elements [index++]; y1 = elements [index++]; x2 = elements [index++]; y2 = elements [index++]; x3 = elements [index++]; y3 = elements [index++]; } else if (type == closeSubPathMarker) { elementType = closePath; } return true; } return false; } #undef JUCE_CHECK_COORDS_ARE_VALID