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.

1635 lines
48KB

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