juce_Path.cpp 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636
  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. String::CharPointerType start (t);
  28. size_t numChars = 0;
  29. while (! (t.isEmpty() || t.isWhitespace()))
  30. {
  31. ++t;
  32. ++numChars;
  33. }
  34. return String (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. //==============================================================================
  50. Path::PathBounds::PathBounds() noexcept
  51. : pathXMin (0), pathXMax (0), pathYMin (0), pathYMax (0)
  52. {
  53. }
  54. Rectangle<float> Path::PathBounds::getRectangle() const noexcept
  55. {
  56. return Rectangle<float> (pathXMin, pathYMin, pathXMax - pathXMin, pathYMax - pathYMin);
  57. }
  58. void Path::PathBounds::reset() noexcept
  59. {
  60. pathXMin = pathYMin = pathYMax = pathXMax = 0;
  61. }
  62. void Path::PathBounds::reset (const float x, const float y) noexcept
  63. {
  64. pathXMin = pathXMax = x;
  65. pathYMin = pathYMax = y;
  66. }
  67. void Path::PathBounds::extend (const float x, const float y) noexcept
  68. {
  69. pathXMin = jmin (pathXMin, x);
  70. pathXMax = jmax (pathXMax, x);
  71. pathYMin = jmin (pathYMin, y);
  72. pathYMax = jmax (pathYMax, y);
  73. }
  74. void Path::PathBounds::extend (const float x1, const float y1, const float x2, const float y2) noexcept
  75. {
  76. if (x1 < x2)
  77. {
  78. pathXMin = jmin (pathXMin, x1);
  79. pathXMax = jmax (pathXMax, x2);
  80. }
  81. else
  82. {
  83. pathXMin = jmin (pathXMin, x2);
  84. pathXMax = jmax (pathXMax, x1);
  85. }
  86. if (y1 < y2)
  87. {
  88. pathYMin = jmin (pathYMin, y1);
  89. pathYMax = jmax (pathYMax, y2);
  90. }
  91. else
  92. {
  93. pathYMin = jmin (pathYMin, y2);
  94. pathYMax = jmax (pathYMax, y1);
  95. }
  96. }
  97. //==============================================================================
  98. Path::Path()
  99. : numElements (0), useNonZeroWinding (true)
  100. {
  101. }
  102. Path::~Path()
  103. {
  104. }
  105. Path::Path (const Path& other)
  106. : numElements (other.numElements),
  107. bounds (other.bounds),
  108. useNonZeroWinding (other.useNonZeroWinding)
  109. {
  110. if (numElements > 0)
  111. {
  112. data.setAllocatedSize ((int) numElements);
  113. memcpy (data.elements, other.data.elements, numElements * sizeof (float));
  114. }
  115. }
  116. Path& Path::operator= (const Path& other)
  117. {
  118. if (this != &other)
  119. {
  120. data.ensureAllocatedSize ((int) other.numElements);
  121. numElements = other.numElements;
  122. bounds = other.bounds;
  123. useNonZeroWinding = other.useNonZeroWinding;
  124. if (numElements > 0)
  125. memcpy (data.elements, other.data.elements, numElements * sizeof (float));
  126. }
  127. return *this;
  128. }
  129. #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
  130. Path::Path (Path&& other) noexcept
  131. : data (static_cast<ArrayAllocationBase <float, DummyCriticalSection>&&> (other.data)),
  132. numElements (other.numElements),
  133. bounds (other.bounds),
  134. useNonZeroWinding (other.useNonZeroWinding)
  135. {
  136. }
  137. Path& Path::operator= (Path&& other) noexcept
  138. {
  139. data = static_cast<ArrayAllocationBase <float, DummyCriticalSection>&&> (other.data);
  140. numElements = other.numElements;
  141. bounds = other.bounds;
  142. useNonZeroWinding = other.useNonZeroWinding;
  143. return *this;
  144. }
  145. #endif
  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 (const float x, const float y, const float w, const float h,
  180. const bool preserveProportions) noexcept
  181. {
  182. applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions));
  183. }
  184. //==============================================================================
  185. bool Path::isEmpty() const noexcept
  186. {
  187. size_t i = 0;
  188. while (i < numElements)
  189. {
  190. const float type = data.elements [i++];
  191. if (type == moveMarker)
  192. {
  193. i += 2;
  194. }
  195. else if (type == lineMarker
  196. || type == quadMarker
  197. || type == cubicMarker)
  198. {
  199. return false;
  200. }
  201. }
  202. return true;
  203. }
  204. Rectangle<float> Path::getBounds() const noexcept
  205. {
  206. return bounds.getRectangle();
  207. }
  208. Rectangle<float> Path::getBoundsTransformed (const AffineTransform& transform) const noexcept
  209. {
  210. return getBounds().transformedBy (transform);
  211. }
  212. //==============================================================================
  213. void Path::preallocateSpace (int numExtraCoordsToMakeSpaceFor)
  214. {
  215. data.ensureAllocatedSize ((int) numElements + numExtraCoordsToMakeSpaceFor);
  216. }
  217. void Path::startNewSubPath (const float x, const float y)
  218. {
  219. JUCE_CHECK_COORDS_ARE_VALID (x, y);
  220. if (numElements == 0)
  221. bounds.reset (x, y);
  222. else
  223. bounds.extend (x, y);
  224. preallocateSpace (3);
  225. data.elements [numElements++] = moveMarker;
  226. data.elements [numElements++] = x;
  227. data.elements [numElements++] = y;
  228. }
  229. void Path::startNewSubPath (const Point<float> start)
  230. {
  231. startNewSubPath (start.x, start.y);
  232. }
  233. void Path::lineTo (const float x, const float y)
  234. {
  235. JUCE_CHECK_COORDS_ARE_VALID (x, y);
  236. if (numElements == 0)
  237. startNewSubPath (0, 0);
  238. preallocateSpace (3);
  239. data.elements [numElements++] = lineMarker;
  240. data.elements [numElements++] = x;
  241. data.elements [numElements++] = y;
  242. bounds.extend (x, y);
  243. }
  244. void Path::lineTo (const Point<float> end)
  245. {
  246. lineTo (end.x, end.y);
  247. }
  248. void Path::quadraticTo (const float x1, const float y1,
  249. const float x2, const float y2)
  250. {
  251. JUCE_CHECK_COORDS_ARE_VALID (x1, y1);
  252. JUCE_CHECK_COORDS_ARE_VALID (x2, y2);
  253. if (numElements == 0)
  254. startNewSubPath (0, 0);
  255. preallocateSpace (5);
  256. data.elements [numElements++] = quadMarker;
  257. data.elements [numElements++] = x1;
  258. data.elements [numElements++] = y1;
  259. data.elements [numElements++] = x2;
  260. data.elements [numElements++] = y2;
  261. bounds.extend (x1, y1, x2, y2);
  262. }
  263. void Path::quadraticTo (const Point<float> controlPoint,
  264. const Point<float> endPoint)
  265. {
  266. quadraticTo (controlPoint.x, controlPoint.y,
  267. endPoint.x, endPoint.y);
  268. }
  269. void Path::cubicTo (const float x1, const float y1,
  270. const float x2, const float y2,
  271. const float x3, const float y3)
  272. {
  273. JUCE_CHECK_COORDS_ARE_VALID (x1, y1);
  274. JUCE_CHECK_COORDS_ARE_VALID (x2, y2);
  275. JUCE_CHECK_COORDS_ARE_VALID (x3, y3);
  276. if (numElements == 0)
  277. startNewSubPath (0, 0);
  278. preallocateSpace (7);
  279. data.elements [numElements++] = cubicMarker;
  280. data.elements [numElements++] = x1;
  281. data.elements [numElements++] = y1;
  282. data.elements [numElements++] = x2;
  283. data.elements [numElements++] = y2;
  284. data.elements [numElements++] = x3;
  285. data.elements [numElements++] = y3;
  286. bounds.extend (x1, y1, x2, y2);
  287. bounds.extend (x3, y3);
  288. }
  289. void Path::cubicTo (const Point<float> controlPoint1,
  290. const Point<float> controlPoint2,
  291. const Point<float> endPoint)
  292. {
  293. cubicTo (controlPoint1.x, controlPoint1.y,
  294. controlPoint2.x, controlPoint2.y,
  295. endPoint.x, endPoint.y);
  296. }
  297. void Path::closeSubPath()
  298. {
  299. if (numElements > 0
  300. && 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 && data.elements[i] == closeSubPathMarker)
  310. {
  311. while (i >= 0)
  312. {
  313. if (data.elements[i] == moveMarker)
  314. {
  315. i += 2;
  316. break;
  317. }
  318. --i;
  319. }
  320. }
  321. if (i > 0)
  322. return Point<float> (data.elements [i - 1], data.elements [i]);
  323. return Point<float>();
  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. const float cs45x = csx * 0.45f;
  372. const float cs45y = csy * 0.45f;
  373. const float x2 = x + w;
  374. const float 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. const float hw = area.getWidth() * 0.5f;
  450. const float hw55 = hw * 0.55f;
  451. const float hh = area.getHeight() * 0.5f;
  452. const float hh55 = hh * 0.55f;
  453. const float cx = area.getX() + hw;
  454. const float 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. const float radiusX = w / 2.0f;
  469. const float 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. const AffineTransform rotation (AffineTransform::rotation (rotationOfEllipse, centreX, centreY));
  488. float 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. const Line<float> 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. const Line<float> 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. const float angleBetweenPoints = float_Pi * 2.0f / numberOfSides;
  598. for (int i = 0; i < numberOfSides; ++i)
  599. {
  600. const float angle = startAngle + i * angleBetweenPoints;
  601. const Point<float> 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. const float angleBetweenPoints = float_Pi * 2.0f / numberOfPoints;
  617. for (int i = 0; i < numberOfPoints; ++i)
  618. {
  619. const float angle = startAngle + i * angleBetweenPoints;
  620. const Point<float> 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. const float halfW = bodyArea.getWidth() / 2.0f;
  637. const float halfH = bodyArea.getHeight() / 2.0f;
  638. const float cornerSizeW = jmin (cornerSize, halfW);
  639. const float cornerSizeH = jmin (cornerSize, halfH);
  640. const float cornerSizeW2 = 2.0f * cornerSizeW;
  641. const float 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* const d = other.data.elements;
  687. while (i < other.numElements)
  688. {
  689. const float type = d[i++];
  690. if (type == moveMarker)
  691. {
  692. startNewSubPath (d[i], d[i + 1]);
  693. i += 2;
  694. }
  695. else if (type == lineMarker)
  696. {
  697. lineTo (d[i], d[i + 1]);
  698. i += 2;
  699. }
  700. else if (type == quadMarker)
  701. {
  702. quadraticTo (d[i], d[i + 1], d[i + 2], d[i + 3]);
  703. i += 4;
  704. }
  705. else if (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 (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* const d = other.data.elements;
  726. while (i < other.numElements)
  727. {
  728. const float type = d [i++];
  729. if (type == closeSubPathMarker)
  730. {
  731. closeSubPath();
  732. }
  733. else
  734. {
  735. float x = d[i++];
  736. float y = d[i++];
  737. transformToApply.transformPoint (x, y);
  738. if (type == moveMarker)
  739. {
  740. startNewSubPath (x, y);
  741. }
  742. else if (type == lineMarker)
  743. {
  744. lineTo (x, y);
  745. }
  746. else if (type == quadMarker)
  747. {
  748. float x2 = d [i++];
  749. float y2 = d [i++];
  750. transformToApply.transformPoint (x2, y2);
  751. quadraticTo (x, y, x2, y2);
  752. }
  753. else if (type == cubicMarker)
  754. {
  755. float x2 = d [i++];
  756. float y2 = d [i++];
  757. float x3 = d [i++];
  758. float 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. float* const end = d + numElements;
  777. while (d < end)
  778. {
  779. const float type = *d++;
  780. if (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 (type == lineMarker)
  795. {
  796. transform.transformPoint (d[0], d[1]);
  797. bounds.extend (d[0], d[1]);
  798. d += 2;
  799. }
  800. else if (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 (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. Rectangle<float> boundsRect (getBounds());
  828. if (preserveProportions)
  829. {
  830. if (w <= 0 || h <= 0 || boundsRect.isEmpty())
  831. return AffineTransform();
  832. float newW, newH;
  833. const float 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. float newXCentre = x;
  845. float 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. const float 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 (Line<float> (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. const float lineLength = line.getLength();
  949. if (distanceFromStart <= lineLength)
  950. return line.getPointAlongLine (distanceFromStart);
  951. distanceFromStart -= lineLength;
  952. }
  953. return Point<float> (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. const float 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. size_t indexOfPathStart = 0, indexOfPathStartThis = 0;
  983. size_t n = 0;
  984. bool lastWasLine = false, firstWasLine = false;
  985. Path p;
  986. while (n < numElements)
  987. {
  988. const float type = data.elements [n++];
  989. if (type == moveMarker)
  990. {
  991. indexOfPathStart = p.numElements;
  992. indexOfPathStartThis = n - 1;
  993. const float x = data.elements [n++];
  994. const float y = data.elements [n++];
  995. p.startNewSubPath (x, y);
  996. lastWasLine = false;
  997. firstWasLine = (data.elements [n] == lineMarker);
  998. }
  999. else if (type == lineMarker || type == closeSubPathMarker)
  1000. {
  1001. float startX = 0, startY = 0, joinX = 0, joinY = 0, endX, endY;
  1002. if (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. const double len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY);
  1029. if (len1 > 0)
  1030. {
  1031. const double 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. const double len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY);
  1036. if (len2 > 0)
  1037. {
  1038. const double 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 (type == lineMarker)
  1046. {
  1047. p.lineTo (endX, endY);
  1048. lastWasLine = true;
  1049. }
  1050. if (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. const double len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY);
  1061. if (len1 > 0)
  1062. {
  1063. const double 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. const double len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY);
  1068. if (len2 > 0)
  1069. {
  1070. const double 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 (type == quadMarker)
  1082. {
  1083. lastWasLine = false;
  1084. const float x1 = data.elements [n++];
  1085. const float y1 = data.elements [n++];
  1086. const float x2 = data.elements [n++];
  1087. const float y2 = data.elements [n++];
  1088. p.quadraticTo (x1, y1, x2, y2);
  1089. }
  1090. else if (type == cubicMarker)
  1091. {
  1092. lastWasLine = false;
  1093. const float x1 = data.elements [n++];
  1094. const float y1 = data.elements [n++];
  1095. const float x2 = data.elements [n++];
  1096. const float y2 = data.elements [n++];
  1097. const float x3 = data.elements [n++];
  1098. const float 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. const float x = source.readFloat();
  1114. const float y = source.readFloat();
  1115. startNewSubPath (x, y);
  1116. break;
  1117. }
  1118. case 'l':
  1119. {
  1120. const float x = source.readFloat();
  1121. const float y = source.readFloat();
  1122. lineTo (x, y);
  1123. break;
  1124. }
  1125. case 'q':
  1126. {
  1127. const float x1 = source.readFloat();
  1128. const float y1 = source.readFloat();
  1129. const float x2 = source.readFloat();
  1130. const float y2 = source.readFloat();
  1131. quadraticTo (x1, y1, x2, y2);
  1132. break;
  1133. }
  1134. case 'b':
  1135. {
  1136. const float x1 = source.readFloat();
  1137. const float y1 = source.readFloat();
  1138. const float x2 = source.readFloat();
  1139. const float y2 = source.readFloat();
  1140. const float x3 = source.readFloat();
  1141. const float 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. size_t i = 0;
  1171. while (i < numElements)
  1172. {
  1173. const float type = data.elements [i++];
  1174. if (type == moveMarker)
  1175. {
  1176. dest.writeByte ('m');
  1177. dest.writeFloat (data.elements [i++]);
  1178. dest.writeFloat (data.elements [i++]);
  1179. }
  1180. else if (type == lineMarker)
  1181. {
  1182. dest.writeByte ('l');
  1183. dest.writeFloat (data.elements [i++]);
  1184. dest.writeFloat (data.elements [i++]);
  1185. }
  1186. else if (type == quadMarker)
  1187. {
  1188. dest.writeByte ('q');
  1189. dest.writeFloat (data.elements [i++]);
  1190. dest.writeFloat (data.elements [i++]);
  1191. dest.writeFloat (data.elements [i++]);
  1192. dest.writeFloat (data.elements [i++]);
  1193. }
  1194. else if (type == cubicMarker)
  1195. {
  1196. dest.writeByte ('b');
  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. dest.writeFloat (data.elements [i++]);
  1203. }
  1204. else if (type == closeSubPathMarker)
  1205. {
  1206. dest.writeByte ('c');
  1207. }
  1208. }
  1209. dest.writeByte ('e'); // marks the end-of-path
  1210. }
  1211. String Path::toString() const
  1212. {
  1213. MemoryOutputStream s (2048);
  1214. if (! useNonZeroWinding)
  1215. s << 'a';
  1216. size_t i = 0;
  1217. float lastMarker = 0.0f;
  1218. while (i < numElements)
  1219. {
  1220. const float marker = data.elements [i++];
  1221. char markerChar = 0;
  1222. int numCoords = 0;
  1223. if (marker == moveMarker)
  1224. {
  1225. markerChar = 'm';
  1226. numCoords = 2;
  1227. }
  1228. else if (marker == lineMarker)
  1229. {
  1230. markerChar = 'l';
  1231. numCoords = 2;
  1232. }
  1233. else if (marker == quadMarker)
  1234. {
  1235. markerChar = 'q';
  1236. numCoords = 4;
  1237. }
  1238. else if (marker == cubicMarker)
  1239. {
  1240. markerChar = 'c';
  1241. numCoords = 6;
  1242. }
  1243. else
  1244. {
  1245. jassert (marker == closeSubPathMarker);
  1246. markerChar = 'z';
  1247. }
  1248. if (marker != lastMarker)
  1249. {
  1250. if (s.getDataSize() != 0)
  1251. s << ' ';
  1252. s << markerChar;
  1253. lastMarker = marker;
  1254. }
  1255. while (--numCoords >= 0 && i < numElements)
  1256. {
  1257. String coord (data.elements [i++], 3);
  1258. while (coord.endsWithChar ('0') && coord != "0")
  1259. coord = coord.dropLastCharacters (1);
  1260. if (coord.endsWithChar ('.'))
  1261. coord = coord.dropLastCharacters (1);
  1262. if (s.getDataSize() != 0)
  1263. s << ' ';
  1264. s << coord;
  1265. }
  1266. }
  1267. return s.toUTF8();
  1268. }
  1269. void Path::restoreFromString (StringRef stringVersion)
  1270. {
  1271. clear();
  1272. setUsingNonZeroWinding (true);
  1273. String::CharPointerType t (stringVersion.text);
  1274. juce_wchar marker = 'm';
  1275. int numValues = 2;
  1276. float values [6];
  1277. for (;;)
  1278. {
  1279. const String token (PathHelpers::nextToken (t));
  1280. const juce_wchar firstChar = token[0];
  1281. int startNum = 0;
  1282. if (firstChar == 0)
  1283. break;
  1284. if (firstChar == 'm' || firstChar == 'l')
  1285. {
  1286. marker = firstChar;
  1287. numValues = 2;
  1288. }
  1289. else if (firstChar == 'q')
  1290. {
  1291. marker = firstChar;
  1292. numValues = 4;
  1293. }
  1294. else if (firstChar == 'c')
  1295. {
  1296. marker = firstChar;
  1297. numValues = 6;
  1298. }
  1299. else if (firstChar == 'z')
  1300. {
  1301. marker = firstChar;
  1302. numValues = 0;
  1303. }
  1304. else if (firstChar == 'a')
  1305. {
  1306. setUsingNonZeroWinding (false);
  1307. continue;
  1308. }
  1309. else
  1310. {
  1311. ++startNum;
  1312. values [0] = token.getFloatValue();
  1313. }
  1314. for (int i = startNum; i < numValues; ++i)
  1315. values [i] = PathHelpers::nextToken (t).getFloatValue();
  1316. switch (marker)
  1317. {
  1318. case 'm': startNewSubPath (values[0], values[1]); break;
  1319. case 'l': lineTo (values[0], values[1]); break;
  1320. case 'q': quadraticTo (values[0], values[1], values[2], values[3]); break;
  1321. case 'c': cubicTo (values[0], values[1], values[2], values[3], values[4], values[5]); break;
  1322. case 'z': closeSubPath(); break;
  1323. default: jassertfalse; break; // illegal string format?
  1324. }
  1325. }
  1326. }
  1327. //==============================================================================
  1328. Path::Iterator::Iterator (const Path& p) noexcept
  1329. : x1 (0), y1 (0), x2 (0), y2 (0), x3 (0), y3 (0),
  1330. path (p), index (0)
  1331. {
  1332. }
  1333. Path::Iterator::~Iterator() noexcept
  1334. {
  1335. }
  1336. bool Path::Iterator::next() noexcept
  1337. {
  1338. const float* const elements = path.data.elements;
  1339. if (index < path.numElements)
  1340. {
  1341. const float type = elements [index++];
  1342. if (type == moveMarker)
  1343. {
  1344. elementType = startNewSubPath;
  1345. x1 = elements [index++];
  1346. y1 = elements [index++];
  1347. }
  1348. else if (type == lineMarker)
  1349. {
  1350. elementType = lineTo;
  1351. x1 = elements [index++];
  1352. y1 = elements [index++];
  1353. }
  1354. else if (type == quadMarker)
  1355. {
  1356. elementType = quadraticTo;
  1357. x1 = elements [index++];
  1358. y1 = elements [index++];
  1359. x2 = elements [index++];
  1360. y2 = elements [index++];
  1361. }
  1362. else if (type == cubicMarker)
  1363. {
  1364. elementType = cubicTo;
  1365. x1 = elements [index++];
  1366. y1 = elements [index++];
  1367. x2 = elements [index++];
  1368. y2 = elements [index++];
  1369. x3 = elements [index++];
  1370. y3 = elements [index++];
  1371. }
  1372. else if (type == closeSubPathMarker)
  1373. {
  1374. elementType = closePath;
  1375. }
  1376. return true;
  1377. }
  1378. return false;
  1379. }
  1380. #undef JUCE_CHECK_COORDS_ARE_VALID