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.

1542 lines
45KB

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