The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1633 lines
48KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. // tests that some coordinates aren't NaNs
  18. #define JUCE_CHECK_COORDS_ARE_VALID(x, y) \
  19. jassert (x == x && y == y);
  20. //==============================================================================
  21. namespace PathHelpers
  22. {
  23. const float ellipseAngularIncrement = 0.05f;
  24. static String nextToken (String::CharPointerType& t)
  25. {
  26. t = t.findEndOfWhitespace();
  27. auto start = t;
  28. size_t numChars = 0;
  29. while (! (t.isEmpty() || t.isWhitespace()))
  30. {
  31. ++t;
  32. ++numChars;
  33. }
  34. return { start, numChars };
  35. }
  36. inline double lengthOf (float x1, float y1, float x2, float y2) noexcept
  37. {
  38. return juce_hypot ((double) (x1 - x2), (double) (y1 - y2));
  39. }
  40. }
  41. //==============================================================================
  42. const float Path::lineMarker = 100001.0f;
  43. const float Path::moveMarker = 100002.0f;
  44. const float Path::quadMarker = 100003.0f;
  45. const float Path::cubicMarker = 100004.0f;
  46. const float Path::closeSubPathMarker = 100005.0f;
  47. const float Path::defaultToleranceForTesting = 1.0f;
  48. const float Path::defaultToleranceForMeasurement = 0.6f;
  49. static inline bool isMarker (float value, float marker) noexcept
  50. {
  51. return value == marker;
  52. }
  53. //==============================================================================
  54. Path::PathBounds::PathBounds() noexcept
  55. {
  56. }
  57. Rectangle<float> Path::PathBounds::getRectangle() const noexcept
  58. {
  59. return { pathXMin, pathYMin, pathXMax - pathXMin, pathYMax - pathYMin };
  60. }
  61. void Path::PathBounds::reset() noexcept
  62. {
  63. pathXMin = pathYMin = pathYMax = pathXMax = 0;
  64. }
  65. void Path::PathBounds::reset (const float x, const float y) noexcept
  66. {
  67. pathXMin = pathXMax = x;
  68. pathYMin = pathYMax = y;
  69. }
  70. void Path::PathBounds::extend (const float x, const float y) noexcept
  71. {
  72. pathXMin = jmin (pathXMin, x);
  73. pathXMax = jmax (pathXMax, x);
  74. pathYMin = jmin (pathYMin, y);
  75. pathYMax = jmax (pathYMax, y);
  76. }
  77. void Path::PathBounds::extend (const float x1, const float y1, const float x2, const float y2) noexcept
  78. {
  79. if (x1 < x2)
  80. {
  81. pathXMin = jmin (pathXMin, x1);
  82. pathXMax = jmax (pathXMax, x2);
  83. }
  84. else
  85. {
  86. pathXMin = jmin (pathXMin, x2);
  87. pathXMax = jmax (pathXMax, x1);
  88. }
  89. if (y1 < y2)
  90. {
  91. pathYMin = jmin (pathYMin, y1);
  92. pathYMax = jmax (pathYMax, y2);
  93. }
  94. else
  95. {
  96. pathYMin = jmin (pathYMin, y2);
  97. pathYMax = jmax (pathYMax, y1);
  98. }
  99. }
  100. //==============================================================================
  101. Path::Path()
  102. {
  103. }
  104. Path::~Path()
  105. {
  106. }
  107. Path::Path (const Path& other)
  108. : numElements (other.numElements),
  109. bounds (other.bounds),
  110. useNonZeroWinding (other.useNonZeroWinding)
  111. {
  112. if (numElements > 0)
  113. {
  114. data.setAllocatedSize ((int) numElements);
  115. memcpy (data.elements, other.data.elements, numElements * sizeof (float));
  116. }
  117. }
  118. Path& Path::operator= (const Path& other)
  119. {
  120. if (this != &other)
  121. {
  122. data.ensureAllocatedSize ((int) other.numElements);
  123. numElements = other.numElements;
  124. bounds = other.bounds;
  125. useNonZeroWinding = other.useNonZeroWinding;
  126. if (numElements > 0)
  127. memcpy (data.elements, other.data.elements, numElements * sizeof (float));
  128. }
  129. return *this;
  130. }
  131. Path::Path (Path&& other) noexcept
  132. : data (static_cast<ArrayAllocationBase <float, DummyCriticalSection>&&> (other.data)),
  133. numElements (other.numElements),
  134. bounds (other.bounds),
  135. useNonZeroWinding (other.useNonZeroWinding)
  136. {
  137. }
  138. Path& Path::operator= (Path&& other) noexcept
  139. {
  140. data = static_cast<ArrayAllocationBase <float, DummyCriticalSection>&&> (other.data);
  141. numElements = other.numElements;
  142. bounds = other.bounds;
  143. useNonZeroWinding = other.useNonZeroWinding;
  144. return *this;
  145. }
  146. bool Path::operator== (const Path& other) const noexcept
  147. {
  148. return ! operator!= (other);
  149. }
  150. bool Path::operator!= (const Path& other) const noexcept
  151. {
  152. if (numElements != other.numElements || useNonZeroWinding != other.useNonZeroWinding)
  153. return true;
  154. for (size_t i = 0; i < numElements; ++i)
  155. if (data.elements[i] != other.data.elements[i])
  156. return true;
  157. return false;
  158. }
  159. void Path::clear() noexcept
  160. {
  161. numElements = 0;
  162. bounds.reset();
  163. }
  164. void Path::swapWithPath (Path& other) noexcept
  165. {
  166. data.swapWith (other.data);
  167. std::swap (numElements, other.numElements);
  168. std::swap (bounds.pathXMin, other.bounds.pathXMin);
  169. std::swap (bounds.pathXMax, other.bounds.pathXMax);
  170. std::swap (bounds.pathYMin, other.bounds.pathYMin);
  171. std::swap (bounds.pathYMax, other.bounds.pathYMax);
  172. std::swap (useNonZeroWinding, other.useNonZeroWinding);
  173. }
  174. //==============================================================================
  175. void Path::setUsingNonZeroWinding (const bool isNonZero) noexcept
  176. {
  177. useNonZeroWinding = isNonZero;
  178. }
  179. void Path::scaleToFit (float x, float y, float w, float h, bool preserveProportions) noexcept
  180. {
  181. applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions));
  182. }
  183. //==============================================================================
  184. bool Path::isEmpty() const noexcept
  185. {
  186. size_t i = 0;
  187. while (i < numElements)
  188. {
  189. auto type = data.elements[i++];
  190. if (isMarker (type, moveMarker))
  191. {
  192. i += 2;
  193. }
  194. else if (isMarker (type, lineMarker)
  195. || isMarker (type, quadMarker)
  196. || isMarker (type, cubicMarker))
  197. {
  198. return false;
  199. }
  200. }
  201. return true;
  202. }
  203. Rectangle<float> Path::getBounds() const noexcept
  204. {
  205. return bounds.getRectangle();
  206. }
  207. Rectangle<float> Path::getBoundsTransformed (const AffineTransform& transform) const noexcept
  208. {
  209. return getBounds().transformedBy (transform);
  210. }
  211. //==============================================================================
  212. void Path::preallocateSpace (int numExtraCoordsToMakeSpaceFor)
  213. {
  214. data.ensureAllocatedSize ((int) numElements + numExtraCoordsToMakeSpaceFor);
  215. }
  216. void Path::startNewSubPath (const float x, const float y)
  217. {
  218. JUCE_CHECK_COORDS_ARE_VALID (x, y);
  219. if (numElements == 0)
  220. bounds.reset (x, y);
  221. else
  222. bounds.extend (x, y);
  223. preallocateSpace (3);
  224. data.elements[numElements++] = moveMarker;
  225. data.elements[numElements++] = x;
  226. data.elements[numElements++] = y;
  227. }
  228. void Path::startNewSubPath (const Point<float> start)
  229. {
  230. startNewSubPath (start.x, start.y);
  231. }
  232. void Path::lineTo (const float x, const float y)
  233. {
  234. JUCE_CHECK_COORDS_ARE_VALID (x, y);
  235. if (numElements == 0)
  236. startNewSubPath (0, 0);
  237. preallocateSpace (3);
  238. data.elements[numElements++] = lineMarker;
  239. data.elements[numElements++] = x;
  240. data.elements[numElements++] = y;
  241. bounds.extend (x, y);
  242. }
  243. void Path::lineTo (const Point<float> end)
  244. {
  245. lineTo (end.x, end.y);
  246. }
  247. void Path::quadraticTo (const float x1, const float y1,
  248. const float x2, const float y2)
  249. {
  250. JUCE_CHECK_COORDS_ARE_VALID (x1, y1);
  251. JUCE_CHECK_COORDS_ARE_VALID (x2, y2);
  252. if (numElements == 0)
  253. startNewSubPath (0, 0);
  254. preallocateSpace (5);
  255. data.elements[numElements++] = quadMarker;
  256. data.elements[numElements++] = x1;
  257. data.elements[numElements++] = y1;
  258. data.elements[numElements++] = x2;
  259. data.elements[numElements++] = y2;
  260. bounds.extend (x1, y1, x2, y2);
  261. }
  262. void Path::quadraticTo (const Point<float> controlPoint,
  263. const Point<float> endPoint)
  264. {
  265. quadraticTo (controlPoint.x, controlPoint.y,
  266. endPoint.x, endPoint.y);
  267. }
  268. void Path::cubicTo (const float x1, const float y1,
  269. const float x2, const float y2,
  270. const float x3, const float y3)
  271. {
  272. JUCE_CHECK_COORDS_ARE_VALID (x1, y1);
  273. JUCE_CHECK_COORDS_ARE_VALID (x2, y2);
  274. JUCE_CHECK_COORDS_ARE_VALID (x3, y3);
  275. if (numElements == 0)
  276. startNewSubPath (0, 0);
  277. preallocateSpace (7);
  278. data.elements[numElements++] = cubicMarker;
  279. data.elements[numElements++] = x1;
  280. data.elements[numElements++] = y1;
  281. data.elements[numElements++] = x2;
  282. data.elements[numElements++] = y2;
  283. data.elements[numElements++] = x3;
  284. data.elements[numElements++] = y3;
  285. bounds.extend (x1, y1, x2, y2);
  286. bounds.extend (x3, y3);
  287. }
  288. void Path::cubicTo (const Point<float> controlPoint1,
  289. const Point<float> controlPoint2,
  290. const Point<float> endPoint)
  291. {
  292. cubicTo (controlPoint1.x, controlPoint1.y,
  293. controlPoint2.x, controlPoint2.y,
  294. endPoint.x, endPoint.y);
  295. }
  296. void Path::closeSubPath()
  297. {
  298. if (numElements > 0 && ! isMarker (data.elements[numElements - 1], closeSubPathMarker))
  299. {
  300. preallocateSpace (1);
  301. data.elements[numElements++] = closeSubPathMarker;
  302. }
  303. }
  304. Point<float> Path::getCurrentPosition() const
  305. {
  306. int i = (int) numElements - 1;
  307. if (i > 0 && isMarker (data.elements[i], closeSubPathMarker))
  308. {
  309. while (i >= 0)
  310. {
  311. if (isMarker (data.elements[i], moveMarker))
  312. {
  313. i += 2;
  314. break;
  315. }
  316. --i;
  317. }
  318. }
  319. if (i > 0)
  320. return { data.elements[i - 1], data.elements[i] };
  321. return {};
  322. }
  323. void Path::addRectangle (const float x, const float y,
  324. const float w, const float h)
  325. {
  326. float x1 = x, y1 = y, x2 = x + w, y2 = y + h;
  327. if (w < 0) std::swap (x1, x2);
  328. if (h < 0) std::swap (y1, y2);
  329. preallocateSpace (13);
  330. if (numElements == 0)
  331. {
  332. bounds.pathXMin = x1;
  333. bounds.pathXMax = x2;
  334. bounds.pathYMin = y1;
  335. bounds.pathYMax = y2;
  336. }
  337. else
  338. {
  339. bounds.pathXMin = jmin (bounds.pathXMin, x1);
  340. bounds.pathXMax = jmax (bounds.pathXMax, x2);
  341. bounds.pathYMin = jmin (bounds.pathYMin, y1);
  342. bounds.pathYMax = jmax (bounds.pathYMax, y2);
  343. }
  344. data.elements[numElements++] = moveMarker;
  345. data.elements[numElements++] = x1;
  346. data.elements[numElements++] = y2;
  347. data.elements[numElements++] = lineMarker;
  348. data.elements[numElements++] = x1;
  349. data.elements[numElements++] = y1;
  350. data.elements[numElements++] = lineMarker;
  351. data.elements[numElements++] = x2;
  352. data.elements[numElements++] = y1;
  353. data.elements[numElements++] = lineMarker;
  354. data.elements[numElements++] = x2;
  355. data.elements[numElements++] = y2;
  356. data.elements[numElements++] = closeSubPathMarker;
  357. }
  358. void Path::addRoundedRectangle (float x, float y, float w, float h, float csx, float csy)
  359. {
  360. addRoundedRectangle (x, y, w, h, csx, csy, true, true, true, true);
  361. }
  362. void Path::addRoundedRectangle (const float x, const float y, const float w, const float h,
  363. float csx, float csy,
  364. const bool curveTopLeft, const bool curveTopRight,
  365. const bool curveBottomLeft, const bool curveBottomRight)
  366. {
  367. csx = jmin (csx, w * 0.5f);
  368. csy = jmin (csy, h * 0.5f);
  369. auto cs45x = csx * 0.45f;
  370. auto cs45y = csy * 0.45f;
  371. auto x2 = x + w;
  372. auto y2 = y + h;
  373. if (curveTopLeft)
  374. {
  375. startNewSubPath (x, y + csy);
  376. cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y);
  377. }
  378. else
  379. {
  380. startNewSubPath (x, y);
  381. }
  382. if (curveTopRight)
  383. {
  384. lineTo (x2 - csx, y);
  385. cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy);
  386. }
  387. else
  388. {
  389. lineTo (x2, y);
  390. }
  391. if (curveBottomRight)
  392. {
  393. lineTo (x2, y2 - csy);
  394. cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2);
  395. }
  396. else
  397. {
  398. lineTo (x2, y2);
  399. }
  400. if (curveBottomLeft)
  401. {
  402. lineTo (x + csx, y2);
  403. cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy);
  404. }
  405. else
  406. {
  407. lineTo (x, y2);
  408. }
  409. closeSubPath();
  410. }
  411. void Path::addRoundedRectangle (float x, float y, float w, float h, float cs)
  412. {
  413. addRoundedRectangle (x, y, w, h, cs, cs);
  414. }
  415. void Path::addTriangle (float x1, float y1,
  416. float x2, float y2,
  417. float x3, float y3)
  418. {
  419. addTriangle (Point<float> (x1, y1),
  420. Point<float> (x2, y2),
  421. Point<float> (x3, y3));
  422. }
  423. void Path::addTriangle (Point<float> p1, Point<float> p2, Point<float> p3)
  424. {
  425. startNewSubPath (p1);
  426. lineTo (p2);
  427. lineTo (p3);
  428. closeSubPath();
  429. }
  430. void Path::addQuadrilateral (const float x1, const float y1,
  431. const float x2, const float y2,
  432. const float x3, const float y3,
  433. const float x4, const float y4)
  434. {
  435. startNewSubPath (x1, y1);
  436. lineTo (x2, y2);
  437. lineTo (x3, y3);
  438. lineTo (x4, y4);
  439. closeSubPath();
  440. }
  441. void Path::addEllipse (float x, float y, float w, float h)
  442. {
  443. addEllipse (Rectangle<float> (x, y, w, h));
  444. }
  445. void Path::addEllipse (Rectangle<float> area)
  446. {
  447. auto hw = area.getWidth() * 0.5f;
  448. auto hw55 = hw * 0.55f;
  449. auto hh = area.getHeight() * 0.5f;
  450. auto hh55 = hh * 0.55f;
  451. auto cx = area.getX() + hw;
  452. auto cy = area.getY() + hh;
  453. startNewSubPath (cx, cy - hh);
  454. cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy);
  455. cubicTo (cx + hw, cy + hh55, cx + hw55, cy + hh, cx, cy + hh);
  456. cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh55, cx - hw, cy);
  457. cubicTo (cx - hw, cy - hh55, cx - hw55, cy - hh, cx, cy - hh);
  458. closeSubPath();
  459. }
  460. void Path::addArc (const float x, const float y,
  461. const float w, const float h,
  462. const float fromRadians,
  463. const float toRadians,
  464. const bool startAsNewSubPath)
  465. {
  466. auto radiusX = w / 2.0f;
  467. auto radiusY = h / 2.0f;
  468. addCentredArc (x + radiusX,
  469. y + radiusY,
  470. radiusX, radiusY,
  471. 0.0f,
  472. fromRadians, toRadians,
  473. startAsNewSubPath);
  474. }
  475. void Path::addCentredArc (const float centreX, const float centreY,
  476. const float radiusX, const float radiusY,
  477. const float rotationOfEllipse,
  478. const float fromRadians,
  479. float toRadians,
  480. const bool startAsNewSubPath)
  481. {
  482. if (radiusX > 0.0f && radiusY > 0.0f)
  483. {
  484. const Point<float> centre (centreX, centreY);
  485. auto rotation = AffineTransform::rotation (rotationOfEllipse, centreX, centreY);
  486. auto angle = fromRadians;
  487. if (startAsNewSubPath)
  488. startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation));
  489. if (fromRadians < toRadians)
  490. {
  491. if (startAsNewSubPath)
  492. angle += PathHelpers::ellipseAngularIncrement;
  493. while (angle < toRadians)
  494. {
  495. lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation));
  496. angle += PathHelpers::ellipseAngularIncrement;
  497. }
  498. }
  499. else
  500. {
  501. if (startAsNewSubPath)
  502. angle -= PathHelpers::ellipseAngularIncrement;
  503. while (angle > toRadians)
  504. {
  505. lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation));
  506. angle -= PathHelpers::ellipseAngularIncrement;
  507. }
  508. }
  509. lineTo (centre.getPointOnCircumference (radiusX, radiusY, toRadians).transformedBy (rotation));
  510. }
  511. }
  512. void Path::addPieSegment (const float x, const float y,
  513. const float width, const float height,
  514. const float fromRadians,
  515. const float toRadians,
  516. const float innerCircleProportionalSize)
  517. {
  518. float radiusX = width * 0.5f;
  519. float radiusY = height * 0.5f;
  520. const Point<float> centre (x + radiusX, y + radiusY);
  521. startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, fromRadians));
  522. addArc (x, y, width, height, fromRadians, toRadians);
  523. if (std::abs (fromRadians - toRadians) > float_Pi * 1.999f)
  524. {
  525. closeSubPath();
  526. if (innerCircleProportionalSize > 0)
  527. {
  528. radiusX *= innerCircleProportionalSize;
  529. radiusY *= innerCircleProportionalSize;
  530. startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, toRadians));
  531. addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians);
  532. }
  533. }
  534. else
  535. {
  536. if (innerCircleProportionalSize > 0)
  537. {
  538. radiusX *= innerCircleProportionalSize;
  539. radiusY *= innerCircleProportionalSize;
  540. addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians);
  541. }
  542. else
  543. {
  544. lineTo (centre);
  545. }
  546. }
  547. closeSubPath();
  548. }
  549. void Path::addPieSegment (Rectangle<float> segmentBounds,
  550. const float fromRadians,
  551. const float toRadians,
  552. const float innerCircleProportionalSize)
  553. {
  554. addPieSegment (segmentBounds.getX(),
  555. segmentBounds.getY(),
  556. segmentBounds.getWidth(),
  557. segmentBounds.getHeight(),
  558. fromRadians,
  559. toRadians,
  560. innerCircleProportionalSize);
  561. }
  562. //==============================================================================
  563. void Path::addLineSegment (const Line<float>& line, float lineThickness)
  564. {
  565. auto reversed = line.reversed();
  566. lineThickness *= 0.5f;
  567. startNewSubPath (line.getPointAlongLine (0, lineThickness));
  568. lineTo (line.getPointAlongLine (0, -lineThickness));
  569. lineTo (reversed.getPointAlongLine (0, lineThickness));
  570. lineTo (reversed.getPointAlongLine (0, -lineThickness));
  571. closeSubPath();
  572. }
  573. void Path::addArrow (const Line<float>& line, float lineThickness,
  574. float arrowheadWidth, float arrowheadLength)
  575. {
  576. auto reversed = line.reversed();
  577. lineThickness *= 0.5f;
  578. arrowheadWidth *= 0.5f;
  579. arrowheadLength = jmin (arrowheadLength, 0.8f * line.getLength());
  580. startNewSubPath (line.getPointAlongLine (0, lineThickness));
  581. lineTo (line.getPointAlongLine (0, -lineThickness));
  582. lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness));
  583. lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth));
  584. lineTo (line.getEnd());
  585. lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth));
  586. lineTo (reversed.getPointAlongLine (arrowheadLength, -lineThickness));
  587. closeSubPath();
  588. }
  589. void Path::addPolygon (const Point<float> centre, const int numberOfSides,
  590. const float radius, const float startAngle)
  591. {
  592. jassert (numberOfSides > 1); // this would be silly.
  593. if (numberOfSides > 1)
  594. {
  595. auto angleBetweenPoints = float_Pi * 2.0f / numberOfSides;
  596. for (int i = 0; i < numberOfSides; ++i)
  597. {
  598. auto angle = startAngle + i * angleBetweenPoints;
  599. auto p = centre.getPointOnCircumference (radius, angle);
  600. if (i == 0)
  601. startNewSubPath (p);
  602. else
  603. lineTo (p);
  604. }
  605. closeSubPath();
  606. }
  607. }
  608. void Path::addStar (const Point<float> centre, const int numberOfPoints,
  609. const float innerRadius, const float outerRadius, const float startAngle)
  610. {
  611. jassert (numberOfPoints > 1); // this would be silly.
  612. if (numberOfPoints > 1)
  613. {
  614. auto angleBetweenPoints = float_Pi * 2.0f / numberOfPoints;
  615. for (int i = 0; i < numberOfPoints; ++i)
  616. {
  617. auto angle = startAngle + i * angleBetweenPoints;
  618. auto p = centre.getPointOnCircumference (outerRadius, angle);
  619. if (i == 0)
  620. startNewSubPath (p);
  621. else
  622. lineTo (p);
  623. lineTo (centre.getPointOnCircumference (innerRadius, angle + angleBetweenPoints * 0.5f));
  624. }
  625. closeSubPath();
  626. }
  627. }
  628. void Path::addBubble (const Rectangle<float>& bodyArea,
  629. const Rectangle<float>& maximumArea,
  630. const Point<float> arrowTip,
  631. const float cornerSize,
  632. const float arrowBaseWidth)
  633. {
  634. auto halfW = bodyArea.getWidth() / 2.0f;
  635. auto halfH = bodyArea.getHeight() / 2.0f;
  636. auto cornerSizeW = jmin (cornerSize, halfW);
  637. auto cornerSizeH = jmin (cornerSize, halfH);
  638. auto cornerSizeW2 = 2.0f * cornerSizeW;
  639. auto cornerSizeH2 = 2.0f * cornerSizeH;
  640. startNewSubPath (bodyArea.getX() + cornerSizeW, bodyArea.getY());
  641. const Rectangle<float> targetLimit (bodyArea.reduced (jmin (halfW - 1.0f, cornerSizeW + arrowBaseWidth),
  642. jmin (halfH - 1.0f, cornerSizeH + arrowBaseWidth)));
  643. if (Rectangle<float> (targetLimit.getX(), maximumArea.getY(),
  644. targetLimit.getWidth(), bodyArea.getY() - maximumArea.getY()).contains (arrowTip))
  645. {
  646. lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getY());
  647. lineTo (arrowTip.x, arrowTip.y);
  648. lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getY());
  649. }
  650. lineTo (bodyArea.getRight() - cornerSizeW, bodyArea.getY());
  651. addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getY(), cornerSizeW2, cornerSizeH2, 0, float_Pi * 0.5f);
  652. if (Rectangle<float> (bodyArea.getRight(), targetLimit.getY(),
  653. maximumArea.getRight() - bodyArea.getRight(), targetLimit.getHeight()).contains (arrowTip))
  654. {
  655. lineTo (bodyArea.getRight(), arrowTip.y - arrowBaseWidth);
  656. lineTo (arrowTip.x, arrowTip.y);
  657. lineTo (bodyArea.getRight(), arrowTip.y + arrowBaseWidth);
  658. }
  659. lineTo (bodyArea.getRight(), bodyArea.getBottom() - cornerSizeH);
  660. addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi * 0.5f, float_Pi);
  661. if (Rectangle<float> (targetLimit.getX(), bodyArea.getBottom(),
  662. targetLimit.getWidth(), maximumArea.getBottom() - bodyArea.getBottom()).contains (arrowTip))
  663. {
  664. lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getBottom());
  665. lineTo (arrowTip.x, arrowTip.y);
  666. lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getBottom());
  667. }
  668. lineTo (bodyArea.getX() + cornerSizeW, bodyArea.getBottom());
  669. addArc (bodyArea.getX(), bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi, float_Pi * 1.5f);
  670. if (Rectangle<float> (maximumArea.getX(), targetLimit.getY(),
  671. bodyArea.getX() - maximumArea.getX(), targetLimit.getHeight()).contains (arrowTip))
  672. {
  673. lineTo (bodyArea.getX(), arrowTip.y + arrowBaseWidth);
  674. lineTo (arrowTip.x, arrowTip.y);
  675. lineTo (bodyArea.getX(), arrowTip.y - arrowBaseWidth);
  676. }
  677. lineTo (bodyArea.getX(), bodyArea.getY() + cornerSizeH);
  678. addArc (bodyArea.getX(), bodyArea.getY(), cornerSizeW2, cornerSizeH2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f);
  679. closeSubPath();
  680. }
  681. void Path::addPath (const Path& other)
  682. {
  683. size_t i = 0;
  684. const float* d = other.data.elements;
  685. while (i < other.numElements)
  686. {
  687. auto type = d[i++];
  688. if (isMarker (type, moveMarker))
  689. {
  690. startNewSubPath (d[i], d[i + 1]);
  691. i += 2;
  692. }
  693. else if (isMarker (type, lineMarker))
  694. {
  695. lineTo (d[i], d[i + 1]);
  696. i += 2;
  697. }
  698. else if (isMarker (type, quadMarker))
  699. {
  700. quadraticTo (d[i], d[i + 1], d[i + 2], d[i + 3]);
  701. i += 4;
  702. }
  703. else if (isMarker (type, cubicMarker))
  704. {
  705. cubicTo (d[i], d[i + 1], d[i + 2], d[i + 3], d[i + 4], d[i + 5]);
  706. i += 6;
  707. }
  708. else if (isMarker (type, closeSubPathMarker))
  709. {
  710. closeSubPath();
  711. }
  712. else
  713. {
  714. // something's gone wrong with the element list!
  715. jassertfalse;
  716. }
  717. }
  718. }
  719. void Path::addPath (const Path& other,
  720. const AffineTransform& transformToApply)
  721. {
  722. size_t i = 0;
  723. const float* d = other.data.elements;
  724. while (i < other.numElements)
  725. {
  726. auto type = d[i++];
  727. if (isMarker (type, closeSubPathMarker))
  728. {
  729. closeSubPath();
  730. }
  731. else
  732. {
  733. auto x = d[i++];
  734. auto y = d[i++];
  735. transformToApply.transformPoint (x, y);
  736. if (isMarker (type, moveMarker))
  737. {
  738. startNewSubPath (x, y);
  739. }
  740. else if (isMarker (type, lineMarker))
  741. {
  742. lineTo (x, y);
  743. }
  744. else if (isMarker (type, quadMarker))
  745. {
  746. auto x2 = d[i++];
  747. auto y2 = d[i++];
  748. transformToApply.transformPoint (x2, y2);
  749. quadraticTo (x, y, x2, y2);
  750. }
  751. else if (isMarker (type, cubicMarker))
  752. {
  753. auto x2 = d[i++];
  754. auto y2 = d[i++];
  755. auto x3 = d[i++];
  756. auto y3 = d[i++];
  757. transformToApply.transformPoints (x2, y2, x3, y3);
  758. cubicTo (x, y, x2, y2, x3, y3);
  759. }
  760. else
  761. {
  762. // something's gone wrong with the element list!
  763. jassertfalse;
  764. }
  765. }
  766. }
  767. }
  768. //==============================================================================
  769. void Path::applyTransform (const AffineTransform& transform) noexcept
  770. {
  771. bounds.reset();
  772. bool firstPoint = true;
  773. float* d = data.elements;
  774. auto* end = d + numElements;
  775. while (d < end)
  776. {
  777. auto type = *d++;
  778. if (isMarker (type, moveMarker))
  779. {
  780. transform.transformPoint (d[0], d[1]);
  781. if (firstPoint)
  782. {
  783. firstPoint = false;
  784. bounds.reset (d[0], d[1]);
  785. }
  786. else
  787. {
  788. bounds.extend (d[0], d[1]);
  789. }
  790. d += 2;
  791. }
  792. else if (isMarker (type, lineMarker))
  793. {
  794. transform.transformPoint (d[0], d[1]);
  795. bounds.extend (d[0], d[1]);
  796. d += 2;
  797. }
  798. else if (isMarker (type, quadMarker))
  799. {
  800. transform.transformPoints (d[0], d[1], d[2], d[3]);
  801. bounds.extend (d[0], d[1], d[2], d[3]);
  802. d += 4;
  803. }
  804. else if (isMarker (type, cubicMarker))
  805. {
  806. transform.transformPoints (d[0], d[1], d[2], d[3], d[4], d[5]);
  807. bounds.extend (d[0], d[1], d[2], d[3]);
  808. bounds.extend (d[4], d[5]);
  809. d += 6;
  810. }
  811. }
  812. }
  813. //==============================================================================
  814. AffineTransform Path::getTransformToScaleToFit (const Rectangle<float>& area,
  815. bool preserveProportions, Justification justification) const
  816. {
  817. return getTransformToScaleToFit (area.getX(), area.getY(), area.getWidth(), area.getHeight(),
  818. preserveProportions, justification);
  819. }
  820. AffineTransform Path::getTransformToScaleToFit (const float x, const float y,
  821. const float w, const float h,
  822. const bool preserveProportions,
  823. Justification justification) const
  824. {
  825. auto boundsRect = getBounds();
  826. if (preserveProportions)
  827. {
  828. if (w <= 0 || h <= 0 || boundsRect.isEmpty())
  829. return AffineTransform();
  830. float newW, newH;
  831. auto srcRatio = boundsRect.getHeight() / boundsRect.getWidth();
  832. if (srcRatio > h / w)
  833. {
  834. newW = h / srcRatio;
  835. newH = h;
  836. }
  837. else
  838. {
  839. newW = w;
  840. newH = w * srcRatio;
  841. }
  842. auto newXCentre = x;
  843. auto newYCentre = y;
  844. if (justification.testFlags (Justification::left)) newXCentre += newW * 0.5f;
  845. else if (justification.testFlags (Justification::right)) newXCentre += w - newW * 0.5f;
  846. else newXCentre += w * 0.5f;
  847. if (justification.testFlags (Justification::top)) newYCentre += newH * 0.5f;
  848. else if (justification.testFlags (Justification::bottom)) newYCentre += h - newH * 0.5f;
  849. else newYCentre += h * 0.5f;
  850. return AffineTransform::translation (boundsRect.getWidth() * -0.5f - boundsRect.getX(),
  851. boundsRect.getHeight() * -0.5f - boundsRect.getY())
  852. .scaled (newW / boundsRect.getWidth(),
  853. newH / boundsRect.getHeight())
  854. .translated (newXCentre, newYCentre);
  855. }
  856. else
  857. {
  858. return AffineTransform::translation (-boundsRect.getX(), -boundsRect.getY())
  859. .scaled (w / boundsRect.getWidth(),
  860. h / boundsRect.getHeight())
  861. .translated (x, y);
  862. }
  863. }
  864. //==============================================================================
  865. bool Path::contains (const float x, const float y, const float tolerance) const
  866. {
  867. if (x <= bounds.pathXMin || x >= bounds.pathXMax
  868. || y <= bounds.pathYMin || y >= bounds.pathYMax)
  869. return false;
  870. PathFlatteningIterator i (*this, AffineTransform(), tolerance);
  871. int positiveCrossings = 0;
  872. int negativeCrossings = 0;
  873. while (i.next())
  874. {
  875. if ((i.y1 <= y && i.y2 > y) || (i.y2 <= y && i.y1 > y))
  876. {
  877. auto intersectX = i.x1 + (i.x2 - i.x1) * (y - i.y1) / (i.y2 - i.y1);
  878. if (intersectX <= x)
  879. {
  880. if (i.y1 < i.y2)
  881. ++positiveCrossings;
  882. else
  883. ++negativeCrossings;
  884. }
  885. }
  886. }
  887. return useNonZeroWinding ? (negativeCrossings != positiveCrossings)
  888. : ((negativeCrossings + positiveCrossings) & 1) != 0;
  889. }
  890. bool Path::contains (const Point<float> point, const float tolerance) const
  891. {
  892. return contains (point.x, point.y, tolerance);
  893. }
  894. bool Path::intersectsLine (Line<float> line, const float tolerance)
  895. {
  896. PathFlatteningIterator i (*this, AffineTransform(), tolerance);
  897. Point<float> intersection;
  898. while (i.next())
  899. if (line.intersects (Line<float> (i.x1, i.y1, i.x2, i.y2), intersection))
  900. return true;
  901. return false;
  902. }
  903. Line<float> Path::getClippedLine (Line<float> line, const bool keepSectionOutsidePath) const
  904. {
  905. Line<float> result (line);
  906. const bool startInside = contains (line.getStart());
  907. const bool endInside = contains (line.getEnd());
  908. if (startInside == endInside)
  909. {
  910. if (keepSectionOutsidePath == startInside)
  911. result = Line<float>();
  912. }
  913. else
  914. {
  915. PathFlatteningIterator i (*this, AffineTransform());
  916. Point<float> intersection;
  917. while (i.next())
  918. {
  919. if (line.intersects ({ i.x1, i.y1, i.x2, i.y2 }, intersection))
  920. {
  921. if ((startInside && keepSectionOutsidePath) || (endInside && ! keepSectionOutsidePath))
  922. result.setStart (intersection);
  923. else
  924. result.setEnd (intersection);
  925. }
  926. }
  927. }
  928. return result;
  929. }
  930. float Path::getLength (const AffineTransform& transform, float tolerance) const
  931. {
  932. float length = 0;
  933. PathFlatteningIterator i (*this, transform, tolerance);
  934. while (i.next())
  935. length += Line<float> (i.x1, i.y1, i.x2, i.y2).getLength();
  936. return length;
  937. }
  938. Point<float> Path::getPointAlongPath (float distanceFromStart,
  939. const AffineTransform& transform,
  940. float tolerance) const
  941. {
  942. PathFlatteningIterator i (*this, transform, tolerance);
  943. while (i.next())
  944. {
  945. const Line<float> line (i.x1, i.y1, i.x2, i.y2);
  946. auto lineLength = line.getLength();
  947. if (distanceFromStart <= lineLength)
  948. return line.getPointAlongLine (distanceFromStart);
  949. distanceFromStart -= lineLength;
  950. }
  951. return { i.x2, i.y2 };
  952. }
  953. float Path::getNearestPoint (const Point<float> targetPoint, Point<float>& pointOnPath,
  954. const AffineTransform& transform,
  955. float tolerance) const
  956. {
  957. PathFlatteningIterator i (*this, transform, tolerance);
  958. float bestPosition = 0, bestDistance = std::numeric_limits<float>::max();
  959. float length = 0;
  960. Point<float> pointOnLine;
  961. while (i.next())
  962. {
  963. const Line<float> line (i.x1, i.y1, i.x2, i.y2);
  964. auto distance = line.getDistanceFromPoint (targetPoint, pointOnLine);
  965. if (distance < bestDistance)
  966. {
  967. bestDistance = distance;
  968. bestPosition = length + pointOnLine.getDistanceFrom (line.getStart());
  969. pointOnPath = pointOnLine;
  970. }
  971. length += line.getLength();
  972. }
  973. return bestPosition;
  974. }
  975. //==============================================================================
  976. Path Path::createPathWithRoundedCorners (const float cornerRadius) const
  977. {
  978. if (cornerRadius <= 0.01f)
  979. return *this;
  980. Path p;
  981. size_t indexOfPathStart = 0, indexOfPathStartThis = 0;
  982. size_t n = 0;
  983. bool lastWasLine = false, firstWasLine = false;
  984. while (n < numElements)
  985. {
  986. auto type = data.elements[n++];
  987. if (isMarker (type, moveMarker))
  988. {
  989. indexOfPathStart = p.numElements;
  990. indexOfPathStartThis = n - 1;
  991. auto x = data.elements[n++];
  992. auto y = data.elements[n++];
  993. p.startNewSubPath (x, y);
  994. lastWasLine = false;
  995. firstWasLine = (isMarker (data.elements[n], lineMarker));
  996. }
  997. else if (isMarker (type, lineMarker) || isMarker (type, closeSubPathMarker))
  998. {
  999. float startX = 0, startY = 0, joinX = 0, joinY = 0, endX, endY;
  1000. if (isMarker (type, lineMarker))
  1001. {
  1002. endX = data.elements[n++];
  1003. endY = data.elements[n++];
  1004. if (n > 8)
  1005. {
  1006. startX = data.elements[n - 8];
  1007. startY = data.elements[n - 7];
  1008. joinX = data.elements[n - 5];
  1009. joinY = data.elements[n - 4];
  1010. }
  1011. }
  1012. else
  1013. {
  1014. endX = data.elements[indexOfPathStartThis + 1];
  1015. endY = data.elements[indexOfPathStartThis + 2];
  1016. if (n > 6)
  1017. {
  1018. startX = data.elements[n - 6];
  1019. startY = data.elements[n - 5];
  1020. joinX = data.elements[n - 3];
  1021. joinY = data.elements[n - 2];
  1022. }
  1023. }
  1024. if (lastWasLine)
  1025. {
  1026. auto len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY);
  1027. if (len1 > 0)
  1028. {
  1029. auto propNeeded = jmin (0.5, cornerRadius / len1);
  1030. p.data.elements[p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded);
  1031. p.data.elements[p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded);
  1032. }
  1033. auto len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY);
  1034. if (len2 > 0)
  1035. {
  1036. auto propNeeded = jmin (0.5, cornerRadius / len2);
  1037. p.quadraticTo (joinX, joinY,
  1038. (float) (joinX + (endX - joinX) * propNeeded),
  1039. (float) (joinY + (endY - joinY) * propNeeded));
  1040. }
  1041. p.lineTo (endX, endY);
  1042. }
  1043. else if (isMarker (type, lineMarker))
  1044. {
  1045. p.lineTo (endX, endY);
  1046. lastWasLine = true;
  1047. }
  1048. if (isMarker (type, closeSubPathMarker))
  1049. {
  1050. if (firstWasLine)
  1051. {
  1052. startX = data.elements[n - 3];
  1053. startY = data.elements[n - 2];
  1054. joinX = endX;
  1055. joinY = endY;
  1056. endX = data.elements[indexOfPathStartThis + 4];
  1057. endY = data.elements[indexOfPathStartThis + 5];
  1058. auto len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY);
  1059. if (len1 > 0)
  1060. {
  1061. auto propNeeded = jmin (0.5, cornerRadius / len1);
  1062. p.data.elements[p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded);
  1063. p.data.elements[p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded);
  1064. }
  1065. auto len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY);
  1066. if (len2 > 0)
  1067. {
  1068. auto propNeeded = jmin (0.5, cornerRadius / len2);
  1069. endX = (float) (joinX + (endX - joinX) * propNeeded);
  1070. endY = (float) (joinY + (endY - joinY) * propNeeded);
  1071. p.quadraticTo (joinX, joinY, endX, endY);
  1072. p.data.elements[indexOfPathStart + 1] = endX;
  1073. p.data.elements[indexOfPathStart + 2] = endY;
  1074. }
  1075. }
  1076. p.closeSubPath();
  1077. }
  1078. }
  1079. else if (isMarker (type, quadMarker))
  1080. {
  1081. lastWasLine = false;
  1082. auto x1 = data.elements[n++];
  1083. auto y1 = data.elements[n++];
  1084. auto x2 = data.elements[n++];
  1085. auto y2 = data.elements[n++];
  1086. p.quadraticTo (x1, y1, x2, y2);
  1087. }
  1088. else if (isMarker (type, cubicMarker))
  1089. {
  1090. lastWasLine = false;
  1091. auto x1 = data.elements[n++];
  1092. auto y1 = data.elements[n++];
  1093. auto x2 = data.elements[n++];
  1094. auto y2 = data.elements[n++];
  1095. auto x3 = data.elements[n++];
  1096. auto y3 = data.elements[n++];
  1097. p.cubicTo (x1, y1, x2, y2, x3, y3);
  1098. }
  1099. }
  1100. return p;
  1101. }
  1102. //==============================================================================
  1103. void Path::loadPathFromStream (InputStream& source)
  1104. {
  1105. while (! source.isExhausted())
  1106. {
  1107. switch (source.readByte())
  1108. {
  1109. case 'm':
  1110. {
  1111. auto x = source.readFloat();
  1112. auto y = source.readFloat();
  1113. startNewSubPath (x, y);
  1114. break;
  1115. }
  1116. case 'l':
  1117. {
  1118. auto x = source.readFloat();
  1119. auto y = source.readFloat();
  1120. lineTo (x, y);
  1121. break;
  1122. }
  1123. case 'q':
  1124. {
  1125. auto x1 = source.readFloat();
  1126. auto y1 = source.readFloat();
  1127. auto x2 = source.readFloat();
  1128. auto y2 = source.readFloat();
  1129. quadraticTo (x1, y1, x2, y2);
  1130. break;
  1131. }
  1132. case 'b':
  1133. {
  1134. auto x1 = source.readFloat();
  1135. auto y1 = source.readFloat();
  1136. auto x2 = source.readFloat();
  1137. auto y2 = source.readFloat();
  1138. auto x3 = source.readFloat();
  1139. auto y3 = source.readFloat();
  1140. cubicTo (x1, y1, x2, y2, x3, y3);
  1141. break;
  1142. }
  1143. case 'c':
  1144. closeSubPath();
  1145. break;
  1146. case 'n':
  1147. useNonZeroWinding = true;
  1148. break;
  1149. case 'z':
  1150. useNonZeroWinding = false;
  1151. break;
  1152. case 'e':
  1153. return; // end of path marker
  1154. default:
  1155. jassertfalse; // illegal char in the stream
  1156. break;
  1157. }
  1158. }
  1159. }
  1160. void Path::loadPathFromData (const void* const pathData, const size_t numberOfBytes)
  1161. {
  1162. MemoryInputStream in (pathData, numberOfBytes, false);
  1163. loadPathFromStream (in);
  1164. }
  1165. void Path::writePathToStream (OutputStream& dest) const
  1166. {
  1167. dest.writeByte (useNonZeroWinding ? 'n' : 'z');
  1168. for (size_t i = 0; i < numElements;)
  1169. {
  1170. auto type = data.elements[i++];
  1171. if (isMarker (type, moveMarker))
  1172. {
  1173. dest.writeByte ('m');
  1174. dest.writeFloat (data.elements[i++]);
  1175. dest.writeFloat (data.elements[i++]);
  1176. }
  1177. else if (isMarker (type, lineMarker))
  1178. {
  1179. dest.writeByte ('l');
  1180. dest.writeFloat (data.elements[i++]);
  1181. dest.writeFloat (data.elements[i++]);
  1182. }
  1183. else if (isMarker (type, quadMarker))
  1184. {
  1185. dest.writeByte ('q');
  1186. dest.writeFloat (data.elements[i++]);
  1187. dest.writeFloat (data.elements[i++]);
  1188. dest.writeFloat (data.elements[i++]);
  1189. dest.writeFloat (data.elements[i++]);
  1190. }
  1191. else if (isMarker (type, cubicMarker))
  1192. {
  1193. dest.writeByte ('b');
  1194. dest.writeFloat (data.elements[i++]);
  1195. dest.writeFloat (data.elements[i++]);
  1196. dest.writeFloat (data.elements[i++]);
  1197. dest.writeFloat (data.elements[i++]);
  1198. dest.writeFloat (data.elements[i++]);
  1199. dest.writeFloat (data.elements[i++]);
  1200. }
  1201. else if (isMarker (type, closeSubPathMarker))
  1202. {
  1203. dest.writeByte ('c');
  1204. }
  1205. }
  1206. dest.writeByte ('e'); // marks the end-of-path
  1207. }
  1208. String Path::toString() const
  1209. {
  1210. MemoryOutputStream s (2048);
  1211. if (! useNonZeroWinding)
  1212. s << 'a';
  1213. size_t i = 0;
  1214. float lastMarker = 0.0f;
  1215. while (i < numElements)
  1216. {
  1217. auto type = data.elements[i++];
  1218. char markerChar = 0;
  1219. int numCoords = 0;
  1220. if (isMarker (type, moveMarker))
  1221. {
  1222. markerChar = 'm';
  1223. numCoords = 2;
  1224. }
  1225. else if (isMarker (type, lineMarker))
  1226. {
  1227. markerChar = 'l';
  1228. numCoords = 2;
  1229. }
  1230. else if (isMarker (type, quadMarker))
  1231. {
  1232. markerChar = 'q';
  1233. numCoords = 4;
  1234. }
  1235. else if (isMarker (type, cubicMarker))
  1236. {
  1237. markerChar = 'c';
  1238. numCoords = 6;
  1239. }
  1240. else
  1241. {
  1242. jassert (isMarker (type, closeSubPathMarker));
  1243. markerChar = 'z';
  1244. }
  1245. if (! isMarker (type, lastMarker))
  1246. {
  1247. if (s.getDataSize() != 0)
  1248. s << ' ';
  1249. s << markerChar;
  1250. lastMarker = type;
  1251. }
  1252. while (--numCoords >= 0 && i < numElements)
  1253. {
  1254. String coord (data.elements[i++], 3);
  1255. while (coord.endsWithChar ('0') && coord != "0")
  1256. coord = coord.dropLastCharacters (1);
  1257. if (coord.endsWithChar ('.'))
  1258. coord = coord.dropLastCharacters (1);
  1259. if (s.getDataSize() != 0)
  1260. s << ' ';
  1261. s << coord;
  1262. }
  1263. }
  1264. return s.toUTF8();
  1265. }
  1266. void Path::restoreFromString (StringRef stringVersion)
  1267. {
  1268. clear();
  1269. setUsingNonZeroWinding (true);
  1270. auto t = stringVersion.text;
  1271. juce_wchar marker = 'm';
  1272. int numValues = 2;
  1273. float values[6];
  1274. for (;;)
  1275. {
  1276. auto token = PathHelpers::nextToken (t);
  1277. auto firstChar = token[0];
  1278. int startNum = 0;
  1279. if (firstChar == 0)
  1280. break;
  1281. if (firstChar == 'm' || firstChar == 'l')
  1282. {
  1283. marker = firstChar;
  1284. numValues = 2;
  1285. }
  1286. else if (firstChar == 'q')
  1287. {
  1288. marker = firstChar;
  1289. numValues = 4;
  1290. }
  1291. else if (firstChar == 'c')
  1292. {
  1293. marker = firstChar;
  1294. numValues = 6;
  1295. }
  1296. else if (firstChar == 'z')
  1297. {
  1298. marker = firstChar;
  1299. numValues = 0;
  1300. }
  1301. else if (firstChar == 'a')
  1302. {
  1303. setUsingNonZeroWinding (false);
  1304. continue;
  1305. }
  1306. else
  1307. {
  1308. ++startNum;
  1309. values [0] = token.getFloatValue();
  1310. }
  1311. for (int i = startNum; i < numValues; ++i)
  1312. values [i] = PathHelpers::nextToken (t).getFloatValue();
  1313. switch (marker)
  1314. {
  1315. case 'm': startNewSubPath (values[0], values[1]); break;
  1316. case 'l': lineTo (values[0], values[1]); break;
  1317. case 'q': quadraticTo (values[0], values[1], values[2], values[3]); break;
  1318. case 'c': cubicTo (values[0], values[1], values[2], values[3], values[4], values[5]); break;
  1319. case 'z': closeSubPath(); break;
  1320. default: jassertfalse; break; // illegal string format?
  1321. }
  1322. }
  1323. }
  1324. //==============================================================================
  1325. Path::Iterator::Iterator (const Path& p) noexcept
  1326. : elementType (startNewSubPath), path (p)
  1327. {
  1328. }
  1329. Path::Iterator::~Iterator() noexcept
  1330. {
  1331. }
  1332. bool Path::Iterator::next() noexcept
  1333. {
  1334. const float* elements = path.data.elements;
  1335. if (index < path.numElements)
  1336. {
  1337. auto type = elements[index++];
  1338. if (isMarker (type, moveMarker))
  1339. {
  1340. elementType = startNewSubPath;
  1341. x1 = elements[index++];
  1342. y1 = elements[index++];
  1343. }
  1344. else if (isMarker (type, lineMarker))
  1345. {
  1346. elementType = lineTo;
  1347. x1 = elements[index++];
  1348. y1 = elements[index++];
  1349. }
  1350. else if (isMarker (type, quadMarker))
  1351. {
  1352. elementType = quadraticTo;
  1353. x1 = elements[index++];
  1354. y1 = elements[index++];
  1355. x2 = elements[index++];
  1356. y2 = elements[index++];
  1357. }
  1358. else if (isMarker (type, cubicMarker))
  1359. {
  1360. elementType = cubicTo;
  1361. x1 = elements[index++];
  1362. y1 = elements[index++];
  1363. x2 = elements[index++];
  1364. y2 = elements[index++];
  1365. x3 = elements[index++];
  1366. y3 = elements[index++];
  1367. }
  1368. else if (isMarker (type, closeSubPathMarker))
  1369. {
  1370. elementType = closePath;
  1371. }
  1372. return true;
  1373. }
  1374. return false;
  1375. }
  1376. #undef JUCE_CHECK_COORDS_ARE_VALID