Audio plugin host https://kx.studio/carla
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.

juce_SVGParser.cpp 47KB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software 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. class SVGState
  18. {
  19. public:
  20. //==============================================================================
  21. explicit SVGState (const XmlElement* const topLevel)
  22. : topLevelXml (topLevel, nullptr),
  23. elementX (0), elementY (0),
  24. width (512), height (512),
  25. viewBoxW (0), viewBoxH (0)
  26. {
  27. }
  28. struct XmlPath
  29. {
  30. XmlPath (const XmlElement* e, const XmlPath* p) noexcept : xml (e), parent (p) {}
  31. const XmlElement& operator*() const noexcept { jassert (xml != nullptr); return *xml; }
  32. const XmlElement* operator->() const noexcept { return xml; }
  33. XmlPath getChild (const XmlElement* e) const noexcept { return XmlPath (e, this); }
  34. const XmlElement* xml;
  35. const XmlPath* parent;
  36. };
  37. //==============================================================================
  38. Drawable* parseSVGElement (const XmlPath& xml)
  39. {
  40. if (! xml->hasTagNameIgnoringNamespace ("svg"))
  41. return nullptr;
  42. DrawableComposite* const drawable = new DrawableComposite();
  43. setDrawableID (*drawable, xml);
  44. SVGState newState (*this);
  45. if (xml->hasAttribute ("transform"))
  46. newState.addTransform (xml);
  47. newState.elementX = getCoordLength (xml->getStringAttribute ("x", String (newState.elementX)), viewBoxW);
  48. newState.elementY = getCoordLength (xml->getStringAttribute ("y", String (newState.elementY)), viewBoxH);
  49. newState.width = getCoordLength (xml->getStringAttribute ("width", String (newState.width)), viewBoxW);
  50. newState.height = getCoordLength (xml->getStringAttribute ("height", String (newState.height)), viewBoxH);
  51. if (newState.width <= 0) newState.width = 100;
  52. if (newState.height <= 0) newState.height = 100;
  53. Point<float> viewboxXY;
  54. if (xml->hasAttribute ("viewBox"))
  55. {
  56. const String viewBoxAtt (xml->getStringAttribute ("viewBox"));
  57. String::CharPointerType viewParams (viewBoxAtt.getCharPointer());
  58. Point<float> vwh;
  59. if (parseCoords (viewParams, viewboxXY, true)
  60. && parseCoords (viewParams, vwh, true)
  61. && vwh.x > 0
  62. && vwh.y > 0)
  63. {
  64. newState.viewBoxW = vwh.x;
  65. newState.viewBoxH = vwh.y;
  66. int placementFlags = 0;
  67. const String aspect (xml->getStringAttribute ("preserveAspectRatio"));
  68. if (aspect.containsIgnoreCase ("none"))
  69. {
  70. placementFlags = RectanglePlacement::stretchToFit;
  71. }
  72. else
  73. {
  74. if (aspect.containsIgnoreCase ("slice")) placementFlags |= RectanglePlacement::fillDestination;
  75. if (aspect.containsIgnoreCase ("xMin")) placementFlags |= RectanglePlacement::xLeft;
  76. else if (aspect.containsIgnoreCase ("xMax")) placementFlags |= RectanglePlacement::xRight;
  77. else placementFlags |= RectanglePlacement::xMid;
  78. if (aspect.containsIgnoreCase ("yMin")) placementFlags |= RectanglePlacement::yTop;
  79. else if (aspect.containsIgnoreCase ("yMax")) placementFlags |= RectanglePlacement::yBottom;
  80. else placementFlags |= RectanglePlacement::yMid;
  81. }
  82. newState.transform = RectanglePlacement (placementFlags)
  83. .getTransformToFit (Rectangle<float> (viewboxXY.x, viewboxXY.y, vwh.x, vwh.y),
  84. Rectangle<float> (newState.width, newState.height))
  85. .followedBy (newState.transform);
  86. }
  87. }
  88. else
  89. {
  90. if (viewBoxW == 0) newState.viewBoxW = newState.width;
  91. if (viewBoxH == 0) newState.viewBoxH = newState.height;
  92. }
  93. newState.parseSubElements (xml, *drawable);
  94. drawable->setContentArea (RelativeRectangle (RelativeCoordinate (viewboxXY.x),
  95. RelativeCoordinate (viewboxXY.x + newState.viewBoxW),
  96. RelativeCoordinate (viewboxXY.y),
  97. RelativeCoordinate (viewboxXY.y + newState.viewBoxH)));
  98. drawable->resetBoundingBoxToContentArea();
  99. return drawable;
  100. }
  101. //==============================================================================
  102. void parsePathString (Path& path, const String& pathString) const
  103. {
  104. String::CharPointerType d (pathString.getCharPointer().findEndOfWhitespace());
  105. Point<float> subpathStart, last, last2, p1, p2, p3;
  106. juce_wchar lastCommandChar = 0;
  107. bool isRelative = true;
  108. bool carryOn = true;
  109. const CharPointer_ASCII validCommandChars ("MmLlHhVvCcSsQqTtAaZz");
  110. while (! d.isEmpty())
  111. {
  112. if (validCommandChars.indexOf (*d) >= 0)
  113. {
  114. lastCommandChar = d.getAndAdvance();
  115. isRelative = (lastCommandChar >= 'a' && lastCommandChar <= 'z');
  116. }
  117. switch (lastCommandChar)
  118. {
  119. case 'M':
  120. case 'm':
  121. case 'L':
  122. case 'l':
  123. if (parseCoordsOrSkip (d, p1, false))
  124. {
  125. if (isRelative)
  126. p1 += last;
  127. if (lastCommandChar == 'M' || lastCommandChar == 'm')
  128. {
  129. subpathStart = p1;
  130. path.startNewSubPath (p1);
  131. lastCommandChar = 'l';
  132. }
  133. else
  134. path.lineTo (p1);
  135. last2 = last;
  136. last = p1;
  137. }
  138. break;
  139. case 'H':
  140. case 'h':
  141. if (parseCoord (d, p1.x, false, true))
  142. {
  143. if (isRelative)
  144. p1.x += last.x;
  145. path.lineTo (p1.x, last.y);
  146. last2.x = last.x;
  147. last.x = p1.x;
  148. }
  149. else
  150. {
  151. ++d;
  152. }
  153. break;
  154. case 'V':
  155. case 'v':
  156. if (parseCoord (d, p1.y, false, false))
  157. {
  158. if (isRelative)
  159. p1.y += last.y;
  160. path.lineTo (last.x, p1.y);
  161. last2.y = last.y;
  162. last.y = p1.y;
  163. }
  164. else
  165. {
  166. ++d;
  167. }
  168. break;
  169. case 'C':
  170. case 'c':
  171. if (parseCoordsOrSkip (d, p1, false)
  172. && parseCoordsOrSkip (d, p2, false)
  173. && parseCoordsOrSkip (d, p3, false))
  174. {
  175. if (isRelative)
  176. {
  177. p1 += last;
  178. p2 += last;
  179. p3 += last;
  180. }
  181. path.cubicTo (p1, p2, p3);
  182. last2 = p2;
  183. last = p3;
  184. }
  185. break;
  186. case 'S':
  187. case 's':
  188. if (parseCoordsOrSkip (d, p1, false)
  189. && parseCoordsOrSkip (d, p3, false))
  190. {
  191. if (isRelative)
  192. {
  193. p1 += last;
  194. p3 += last;
  195. }
  196. p2 = last + (last - last2);
  197. path.cubicTo (p2, p1, p3);
  198. last2 = p1;
  199. last = p3;
  200. }
  201. break;
  202. case 'Q':
  203. case 'q':
  204. if (parseCoordsOrSkip (d, p1, false)
  205. && parseCoordsOrSkip (d, p2, false))
  206. {
  207. if (isRelative)
  208. {
  209. p1 += last;
  210. p2 += last;
  211. }
  212. path.quadraticTo (p1, p2);
  213. last2 = p1;
  214. last = p2;
  215. }
  216. break;
  217. case 'T':
  218. case 't':
  219. if (parseCoordsOrSkip (d, p1, false))
  220. {
  221. if (isRelative)
  222. p1 += last;
  223. p2 = last + (last - last2);
  224. path.quadraticTo (p2, p1);
  225. last2 = p2;
  226. last = p1;
  227. }
  228. break;
  229. case 'A':
  230. case 'a':
  231. if (parseCoordsOrSkip (d, p1, false))
  232. {
  233. String num;
  234. if (parseNextNumber (d, num, false))
  235. {
  236. const float angle = num.getFloatValue() * (180.0f / float_Pi);
  237. if (parseNextNumber (d, num, false))
  238. {
  239. const bool largeArc = num.getIntValue() != 0;
  240. if (parseNextNumber (d, num, false))
  241. {
  242. const bool sweep = num.getIntValue() != 0;
  243. if (parseCoordsOrSkip (d, p2, false))
  244. {
  245. if (isRelative)
  246. p2 += last;
  247. if (last != p2)
  248. {
  249. double centreX, centreY, startAngle, deltaAngle;
  250. double rx = p1.x, ry = p1.y;
  251. endpointToCentreParameters (last.x, last.y, p2.x, p2.y,
  252. angle, largeArc, sweep,
  253. rx, ry, centreX, centreY,
  254. startAngle, deltaAngle);
  255. path.addCentredArc ((float) centreX, (float) centreY,
  256. (float) rx, (float) ry,
  257. angle, (float) startAngle, (float) (startAngle + deltaAngle),
  258. false);
  259. path.lineTo (p2);
  260. }
  261. last2 = last;
  262. last = p2;
  263. }
  264. }
  265. }
  266. }
  267. }
  268. break;
  269. case 'Z':
  270. case 'z':
  271. path.closeSubPath();
  272. last = last2 = subpathStart;
  273. d = d.findEndOfWhitespace();
  274. lastCommandChar = 'M';
  275. break;
  276. default:
  277. carryOn = false;
  278. break;
  279. }
  280. if (! carryOn)
  281. break;
  282. }
  283. // paths that finish back at their start position often seem to be
  284. // left without a 'z', so need to be closed explicitly..
  285. if (path.getCurrentPosition() == subpathStart)
  286. path.closeSubPath();
  287. }
  288. private:
  289. //==============================================================================
  290. const XmlPath topLevelXml;
  291. float elementX, elementY, width, height, viewBoxW, viewBoxH;
  292. AffineTransform transform;
  293. String cssStyleText;
  294. static void setDrawableID (Drawable& d, const XmlPath& xml)
  295. {
  296. String compID (xml->getStringAttribute ("id"));
  297. d.setName (compID);
  298. d.setComponentID (compID);
  299. }
  300. //==============================================================================
  301. void parseSubElements (const XmlPath& xml, DrawableComposite& parentDrawable)
  302. {
  303. forEachXmlChildElement (*xml, e)
  304. parentDrawable.addAndMakeVisible (parseSubElement (xml.getChild (e)));
  305. }
  306. Drawable* parseSubElement (const XmlPath& xml)
  307. {
  308. const String tag (xml->getTagNameWithoutNamespace());
  309. if (tag == "g") return parseGroupElement (xml);
  310. if (tag == "svg") return parseSVGElement (xml);
  311. if (tag == "path") return parsePath (xml);
  312. if (tag == "rect") return parseRect (xml);
  313. if (tag == "circle") return parseCircle (xml);
  314. if (tag == "ellipse") return parseEllipse (xml);
  315. if (tag == "line") return parseLine (xml);
  316. if (tag == "polyline") return parsePolygon (xml, true);
  317. if (tag == "polygon") return parsePolygon (xml, false);
  318. if (tag == "text") return parseText (xml);
  319. if (tag == "switch") return parseSwitch (xml);
  320. if (tag == "style") parseCSSStyle (xml);
  321. return nullptr;
  322. }
  323. DrawableComposite* parseSwitch (const XmlPath& xml)
  324. {
  325. if (const XmlElement* const group = xml->getChildByName ("g"))
  326. return parseGroupElement (xml.getChild (group));
  327. return nullptr;
  328. }
  329. DrawableComposite* parseGroupElement (const XmlPath& xml)
  330. {
  331. DrawableComposite* const drawable = new DrawableComposite();
  332. setDrawableID (*drawable, xml);
  333. if (xml->hasAttribute ("transform"))
  334. {
  335. SVGState newState (*this);
  336. newState.addTransform (xml);
  337. newState.parseSubElements (xml, *drawable);
  338. }
  339. else
  340. {
  341. parseSubElements (xml, *drawable);
  342. }
  343. drawable->resetContentAreaAndBoundingBoxToFitChildren();
  344. return drawable;
  345. }
  346. //==============================================================================
  347. Drawable* parsePath (const XmlPath& xml) const
  348. {
  349. Path path;
  350. parsePathString (path, xml->getStringAttribute ("d"));
  351. if (getStyleAttribute (xml, "fill-rule").trim().equalsIgnoreCase ("evenodd"))
  352. path.setUsingNonZeroWinding (false);
  353. return parseShape (xml, path);
  354. }
  355. Drawable* parseRect (const XmlPath& xml) const
  356. {
  357. Path rect;
  358. const bool hasRX = xml->hasAttribute ("rx");
  359. const bool hasRY = xml->hasAttribute ("ry");
  360. if (hasRX || hasRY)
  361. {
  362. float rx = getCoordLength (xml, "rx", viewBoxW);
  363. float ry = getCoordLength (xml, "ry", viewBoxH);
  364. if (! hasRX)
  365. rx = ry;
  366. else if (! hasRY)
  367. ry = rx;
  368. rect.addRoundedRectangle (getCoordLength (xml, "x", viewBoxW),
  369. getCoordLength (xml, "y", viewBoxH),
  370. getCoordLength (xml, "width", viewBoxW),
  371. getCoordLength (xml, "height", viewBoxH),
  372. rx, ry);
  373. }
  374. else
  375. {
  376. rect.addRectangle (getCoordLength (xml, "x", viewBoxW),
  377. getCoordLength (xml, "y", viewBoxH),
  378. getCoordLength (xml, "width", viewBoxW),
  379. getCoordLength (xml, "height", viewBoxH));
  380. }
  381. return parseShape (xml, rect);
  382. }
  383. Drawable* parseCircle (const XmlPath& xml) const
  384. {
  385. Path circle;
  386. const float cx = getCoordLength (xml, "cx", viewBoxW);
  387. const float cy = getCoordLength (xml, "cy", viewBoxH);
  388. const float radius = getCoordLength (xml, "r", viewBoxW);
  389. circle.addEllipse (cx - radius, cy - radius, radius * 2.0f, radius * 2.0f);
  390. return parseShape (xml, circle);
  391. }
  392. Drawable* parseEllipse (const XmlPath& xml) const
  393. {
  394. Path ellipse;
  395. const float cx = getCoordLength (xml, "cx", viewBoxW);
  396. const float cy = getCoordLength (xml, "cy", viewBoxH);
  397. const float radiusX = getCoordLength (xml, "rx", viewBoxW);
  398. const float radiusY = getCoordLength (xml, "ry", viewBoxH);
  399. ellipse.addEllipse (cx - radiusX, cy - radiusY, radiusX * 2.0f, radiusY * 2.0f);
  400. return parseShape (xml, ellipse);
  401. }
  402. Drawable* parseLine (const XmlPath& xml) const
  403. {
  404. Path line;
  405. const float x1 = getCoordLength (xml, "x1", viewBoxW);
  406. const float y1 = getCoordLength (xml, "y1", viewBoxH);
  407. const float x2 = getCoordLength (xml, "x2", viewBoxW);
  408. const float y2 = getCoordLength (xml, "y2", viewBoxH);
  409. line.startNewSubPath (x1, y1);
  410. line.lineTo (x2, y2);
  411. return parseShape (xml, line);
  412. }
  413. Drawable* parsePolygon (const XmlPath& xml, const bool isPolyline) const
  414. {
  415. const String pointsAtt (xml->getStringAttribute ("points"));
  416. String::CharPointerType points (pointsAtt.getCharPointer());
  417. Path path;
  418. Point<float> p;
  419. if (parseCoords (points, p, true))
  420. {
  421. Point<float> first (p), last;
  422. path.startNewSubPath (first);
  423. while (parseCoords (points, p, true))
  424. {
  425. last = p;
  426. path.lineTo (p);
  427. }
  428. if ((! isPolyline) || first == last)
  429. path.closeSubPath();
  430. }
  431. return parseShape (xml, path);
  432. }
  433. //==============================================================================
  434. Drawable* parseShape (const XmlPath& xml, Path& path,
  435. const bool shouldParseTransform = true) const
  436. {
  437. if (shouldParseTransform && xml->hasAttribute ("transform"))
  438. {
  439. SVGState newState (*this);
  440. newState.addTransform (xml);
  441. return newState.parseShape (xml, path, false);
  442. }
  443. DrawablePath* dp = new DrawablePath();
  444. setDrawableID (*dp, xml);
  445. dp->setFill (Colours::transparentBlack);
  446. path.applyTransform (transform);
  447. dp->setPath (path);
  448. Path::Iterator iter (path);
  449. bool containsClosedSubPath = false;
  450. while (iter.next())
  451. {
  452. if (iter.elementType == Path::Iterator::closePath)
  453. {
  454. containsClosedSubPath = true;
  455. break;
  456. }
  457. }
  458. dp->setFill (getPathFillType (path,
  459. getStyleAttribute (xml, "fill"),
  460. getStyleAttribute (xml, "fill-opacity"),
  461. getStyleAttribute (xml, "opacity"),
  462. containsClosedSubPath ? Colours::black
  463. : Colours::transparentBlack));
  464. const String strokeType (getStyleAttribute (xml, "stroke"));
  465. if (strokeType.isNotEmpty() && ! strokeType.equalsIgnoreCase ("none"))
  466. {
  467. dp->setStrokeFill (getPathFillType (path, strokeType,
  468. getStyleAttribute (xml, "stroke-opacity"),
  469. getStyleAttribute (xml, "opacity"),
  470. Colours::transparentBlack));
  471. dp->setStrokeType (getStrokeFor (xml));
  472. }
  473. return dp;
  474. }
  475. struct SetGradientStopsOp
  476. {
  477. const SVGState* state;
  478. ColourGradient* gradient;
  479. void operator() (const XmlPath& xml)
  480. {
  481. state->addGradientStopsIn (*gradient, xml);
  482. }
  483. };
  484. void addGradientStopsIn (ColourGradient& cg, const XmlPath& fillXml) const
  485. {
  486. if (fillXml.xml != nullptr)
  487. {
  488. forEachXmlChildElementWithTagName (*fillXml, e, "stop")
  489. {
  490. int index = 0;
  491. Colour col (parseColour (getStyleAttribute (fillXml.getChild (e), "stop-color"), index, Colours::black));
  492. const String opacity (getStyleAttribute (fillXml.getChild (e), "stop-opacity", "1"));
  493. col = col.withMultipliedAlpha (jlimit (0.0f, 1.0f, opacity.getFloatValue()));
  494. double offset = e->getDoubleAttribute ("offset");
  495. if (e->getStringAttribute ("offset").containsChar ('%'))
  496. offset *= 0.01;
  497. cg.addColour (jlimit (0.0, 1.0, offset), col);
  498. }
  499. }
  500. }
  501. FillType getGradientFillType (const XmlPath& fillXml,
  502. const Path& path,
  503. const float opacity) const
  504. {
  505. ColourGradient gradient;
  506. {
  507. const String id (fillXml->getStringAttribute ("xlink:href"));
  508. if (id.startsWithChar ('#'))
  509. {
  510. SetGradientStopsOp op = { this, &gradient, };
  511. findElementForId (topLevelXml, id.substring (1), op);
  512. }
  513. }
  514. addGradientStopsIn (gradient, fillXml);
  515. if (gradient.getNumColours() > 0)
  516. {
  517. gradient.addColour (0.0, gradient.getColour (0));
  518. gradient.addColour (1.0, gradient.getColour (gradient.getNumColours() - 1));
  519. }
  520. else
  521. {
  522. gradient.addColour (0.0, Colours::black);
  523. gradient.addColour (1.0, Colours::black);
  524. }
  525. if (opacity < 1.0f)
  526. gradient.multiplyOpacity (opacity);
  527. jassert (gradient.getNumColours() > 0);
  528. gradient.isRadial = fillXml->hasTagNameIgnoringNamespace ("radialGradient");
  529. float gradientWidth = viewBoxW;
  530. float gradientHeight = viewBoxH;
  531. float dx = 0.0f;
  532. float dy = 0.0f;
  533. const bool userSpace = fillXml->getStringAttribute ("gradientUnits").equalsIgnoreCase ("userSpaceOnUse");
  534. if (! userSpace)
  535. {
  536. const Rectangle<float> bounds (path.getBounds());
  537. dx = bounds.getX();
  538. dy = bounds.getY();
  539. gradientWidth = bounds.getWidth();
  540. gradientHeight = bounds.getHeight();
  541. }
  542. if (gradient.isRadial)
  543. {
  544. if (userSpace)
  545. gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("cx", "50%"), gradientWidth),
  546. dy + getCoordLength (fillXml->getStringAttribute ("cy", "50%"), gradientHeight));
  547. else
  548. gradient.point1.setXY (dx + gradientWidth * getCoordLength (fillXml->getStringAttribute ("cx", "50%"), 1.0f),
  549. dy + gradientHeight * getCoordLength (fillXml->getStringAttribute ("cy", "50%"), 1.0f));
  550. const float radius = getCoordLength (fillXml->getStringAttribute ("r", "50%"), gradientWidth);
  551. gradient.point2 = gradient.point1 + Point<float> (radius, 0.0f);
  552. //xxx (the fx, fy focal point isn't handled properly here..)
  553. }
  554. else
  555. {
  556. if (userSpace)
  557. {
  558. gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x1", "0%"), gradientWidth),
  559. dy + getCoordLength (fillXml->getStringAttribute ("y1", "0%"), gradientHeight));
  560. gradient.point2.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x2", "100%"), gradientWidth),
  561. dy + getCoordLength (fillXml->getStringAttribute ("y2", "0%"), gradientHeight));
  562. }
  563. else
  564. {
  565. gradient.point1.setXY (dx + gradientWidth * getCoordLength (fillXml->getStringAttribute ("x1", "0%"), 1.0f),
  566. dy + gradientHeight * getCoordLength (fillXml->getStringAttribute ("y1", "0%"), 1.0f));
  567. gradient.point2.setXY (dx + gradientWidth * getCoordLength (fillXml->getStringAttribute ("x2", "100%"), 1.0f),
  568. dy + gradientHeight * getCoordLength (fillXml->getStringAttribute ("y2", "0%"), 1.0f));
  569. }
  570. if (gradient.point1 == gradient.point2)
  571. return Colour (gradient.getColour (gradient.getNumColours() - 1));
  572. }
  573. FillType type (gradient);
  574. const AffineTransform gradientTransform (parseTransform (fillXml->getStringAttribute ("gradientTransform"))
  575. .followedBy (transform));
  576. if (gradient.isRadial)
  577. {
  578. type.transform = gradientTransform;
  579. }
  580. else
  581. {
  582. // Transform the perpendicular vector into the new coordinate space for the gradient.
  583. // This vector is now the slope of the linear gradient as it should appear in the new coord space
  584. const Point<float> perpendicular (Point<float> (gradient.point2.y - gradient.point1.y,
  585. gradient.point1.x - gradient.point2.x)
  586. .transformedBy (gradientTransform.withAbsoluteTranslation (0, 0)));
  587. const Point<float> newGradPoint1 (gradient.point1.transformedBy (gradientTransform));
  588. const Point<float> newGradPoint2 (gradient.point2.transformedBy (gradientTransform));
  589. // Project the transformed gradient vector onto the transformed slope of the linear
  590. // gradient as it should appear in the new coordinate space
  591. const float scale = perpendicular.getDotProduct (newGradPoint2 - newGradPoint1)
  592. / perpendicular.getDotProduct (perpendicular);
  593. type.gradient->point1 = newGradPoint1;
  594. type.gradient->point2 = newGradPoint2 - perpendicular * scale;
  595. }
  596. return type;
  597. }
  598. struct GetFillTypeOp
  599. {
  600. const SVGState* state;
  601. FillType* dest;
  602. const Path* path;
  603. float opacity;
  604. void operator() (const XmlPath& xml)
  605. {
  606. if (xml->hasTagNameIgnoringNamespace ("linearGradient")
  607. || xml->hasTagNameIgnoringNamespace ("radialGradient"))
  608. *dest = state->getGradientFillType (xml, *path, opacity);
  609. }
  610. };
  611. FillType getPathFillType (const Path& path,
  612. const String& fill,
  613. const String& fillOpacity,
  614. const String& overallOpacity,
  615. const Colour defaultColour) const
  616. {
  617. float opacity = 1.0f;
  618. if (overallOpacity.isNotEmpty())
  619. opacity = jlimit (0.0f, 1.0f, overallOpacity.getFloatValue());
  620. if (fillOpacity.isNotEmpty())
  621. opacity *= (jlimit (0.0f, 1.0f, fillOpacity.getFloatValue()));
  622. if (fill.startsWithIgnoreCase ("url"))
  623. {
  624. const String id (fill.fromFirstOccurrenceOf ("#", false, false)
  625. .upToLastOccurrenceOf (")", false, false).trim());
  626. FillType result;
  627. GetFillTypeOp op = { this, &result, &path, opacity };
  628. if (findElementForId (topLevelXml, id, op))
  629. return result;
  630. }
  631. if (fill.equalsIgnoreCase ("none"))
  632. return Colours::transparentBlack;
  633. int i = 0;
  634. return parseColour (fill, i, defaultColour).withMultipliedAlpha (opacity);
  635. }
  636. PathStrokeType getStrokeFor (const XmlPath& xml) const
  637. {
  638. const String strokeWidth (getStyleAttribute (xml, "stroke-width"));
  639. const String cap (getStyleAttribute (xml, "stroke-linecap"));
  640. const String join (getStyleAttribute (xml, "stroke-linejoin"));
  641. //const String mitreLimit (getStyleAttribute (xml, "stroke-miterlimit"));
  642. //const String dashArray (getStyleAttribute (xml, "stroke-dasharray"));
  643. //const String dashOffset (getStyleAttribute (xml, "stroke-dashoffset"));
  644. PathStrokeType::JointStyle joinStyle = PathStrokeType::mitered;
  645. PathStrokeType::EndCapStyle capStyle = PathStrokeType::butt;
  646. if (join.equalsIgnoreCase ("round"))
  647. joinStyle = PathStrokeType::curved;
  648. else if (join.equalsIgnoreCase ("bevel"))
  649. joinStyle = PathStrokeType::beveled;
  650. if (cap.equalsIgnoreCase ("round"))
  651. capStyle = PathStrokeType::rounded;
  652. else if (cap.equalsIgnoreCase ("square"))
  653. capStyle = PathStrokeType::square;
  654. float ox = 0.0f, oy = 0.0f;
  655. float x = getCoordLength (strokeWidth, viewBoxW), y = 0.0f;
  656. transform.transformPoints (ox, oy, x, y);
  657. return PathStrokeType (strokeWidth.isNotEmpty() ? juce_hypot (x - ox, y - oy) : 1.0f,
  658. joinStyle, capStyle);
  659. }
  660. //==============================================================================
  661. Drawable* parseText (const XmlPath& xml)
  662. {
  663. Array <float> xCoords, yCoords, dxCoords, dyCoords;
  664. getCoordList (xCoords, getInheritedAttribute (xml, "x"), true, true);
  665. getCoordList (yCoords, getInheritedAttribute (xml, "y"), true, false);
  666. getCoordList (dxCoords, getInheritedAttribute (xml, "dx"), true, true);
  667. getCoordList (dyCoords, getInheritedAttribute (xml, "dy"), true, false);
  668. //xxx not done text yet!
  669. forEachXmlChildElement (*xml, e)
  670. {
  671. if (e->isTextElement())
  672. {
  673. const String text (e->getText());
  674. Path path;
  675. Drawable* s = parseShape (xml.getChild (e), path);
  676. delete s; // xxx not finished!
  677. }
  678. else if (e->hasTagNameIgnoringNamespace ("tspan"))
  679. {
  680. Drawable* s = parseText (xml.getChild (e));
  681. delete s; // xxx not finished!
  682. }
  683. }
  684. return nullptr;
  685. }
  686. //==============================================================================
  687. void addTransform (const XmlPath& xml)
  688. {
  689. transform = parseTransform (xml->getStringAttribute ("transform"))
  690. .followedBy (transform);
  691. }
  692. //==============================================================================
  693. bool parseCoord (String::CharPointerType& s, float& value, const bool allowUnits, const bool isX) const
  694. {
  695. String number;
  696. if (! parseNextNumber (s, number, allowUnits))
  697. {
  698. value = 0;
  699. return false;
  700. }
  701. value = getCoordLength (number, isX ? viewBoxW : viewBoxH);
  702. return true;
  703. }
  704. bool parseCoords (String::CharPointerType& s, Point<float>& p, const bool allowUnits) const
  705. {
  706. return parseCoord (s, p.x, allowUnits, true)
  707. && parseCoord (s, p.y, allowUnits, false);
  708. }
  709. bool parseCoordsOrSkip (String::CharPointerType& s, Point<float>& p, const bool allowUnits) const
  710. {
  711. if (parseCoords (s, p, allowUnits))
  712. return true;
  713. if (! s.isEmpty()) ++s;
  714. return false;
  715. }
  716. float getCoordLength (const String& s, const float sizeForProportions) const
  717. {
  718. float n = s.getFloatValue();
  719. const int len = s.length();
  720. if (len > 2)
  721. {
  722. const float dpi = 96.0f;
  723. const juce_wchar n1 = s [len - 2];
  724. const juce_wchar n2 = s [len - 1];
  725. if (n1 == 'i' && n2 == 'n') n *= dpi;
  726. else if (n1 == 'm' && n2 == 'm') n *= dpi / 25.4f;
  727. else if (n1 == 'c' && n2 == 'm') n *= dpi / 2.54f;
  728. else if (n1 == 'p' && n2 == 'c') n *= 15.0f;
  729. else if (n2 == '%') n *= 0.01f * sizeForProportions;
  730. }
  731. return n;
  732. }
  733. float getCoordLength (const XmlPath& xml, const char* attName, const float sizeForProportions) const
  734. {
  735. return getCoordLength (xml->getStringAttribute (attName), sizeForProportions);
  736. }
  737. void getCoordList (Array <float>& coords, const String& list,
  738. const bool allowUnits, const bool isX) const
  739. {
  740. String::CharPointerType text (list.getCharPointer());
  741. float value;
  742. while (parseCoord (text, value, allowUnits, isX))
  743. coords.add (value);
  744. }
  745. //==============================================================================
  746. void parseCSSStyle (const XmlPath& xml)
  747. {
  748. cssStyleText = xml->getAllSubText() + "\n" + cssStyleText;
  749. }
  750. static String::CharPointerType findStyleItem (String::CharPointerType source, String::CharPointerType name)
  751. {
  752. const int nameLength = (int) name.length();
  753. while (! source.isEmpty())
  754. {
  755. if (source.getAndAdvance() == '.'
  756. && CharacterFunctions::compareIgnoreCaseUpTo (source, name, nameLength) == 0)
  757. {
  758. String::CharPointerType endOfName ((source + nameLength).findEndOfWhitespace());
  759. if (*endOfName == '{')
  760. return endOfName;
  761. }
  762. }
  763. return source;
  764. }
  765. String getStyleAttribute (const XmlPath& xml, const String& attributeName,
  766. const String& defaultValue = String()) const
  767. {
  768. if (xml->hasAttribute (attributeName))
  769. return xml->getStringAttribute (attributeName, defaultValue);
  770. const String styleAtt (xml->getStringAttribute ("style"));
  771. if (styleAtt.isNotEmpty())
  772. {
  773. const String value (getAttributeFromStyleList (styleAtt, attributeName, String()));
  774. if (value.isNotEmpty())
  775. return value;
  776. }
  777. else if (xml->hasAttribute ("class"))
  778. {
  779. String::CharPointerType openBrace = findStyleItem (cssStyleText.getCharPointer(),
  780. xml->getStringAttribute ("class").getCharPointer());
  781. if (! openBrace.isEmpty())
  782. {
  783. String::CharPointerType closeBrace = CharacterFunctions::find (openBrace, (juce_wchar) '}');
  784. if (closeBrace != openBrace)
  785. {
  786. const String value (getAttributeFromStyleList (String (openBrace + 1, closeBrace),
  787. attributeName, defaultValue));
  788. if (value.isNotEmpty())
  789. return value;
  790. }
  791. }
  792. }
  793. if (xml.parent != nullptr)
  794. return getStyleAttribute (*xml.parent, attributeName, defaultValue);
  795. return defaultValue;
  796. }
  797. String getInheritedAttribute (const XmlPath& xml, const String& attributeName) const
  798. {
  799. if (xml->hasAttribute (attributeName))
  800. return xml->getStringAttribute (attributeName);
  801. if (xml.parent != nullptr)
  802. return getInheritedAttribute (*xml.parent, attributeName);
  803. return String();
  804. }
  805. //==============================================================================
  806. static bool isIdentifierChar (const juce_wchar c)
  807. {
  808. return CharacterFunctions::isLetter (c) || c == '-';
  809. }
  810. static String getAttributeFromStyleList (const String& list, const String& attributeName, const String& defaultValue)
  811. {
  812. int i = 0;
  813. for (;;)
  814. {
  815. i = list.indexOf (i, attributeName);
  816. if (i < 0)
  817. break;
  818. if ((i == 0 || (i > 0 && ! isIdentifierChar (list [i - 1])))
  819. && ! isIdentifierChar (list [i + attributeName.length()]))
  820. {
  821. i = list.indexOfChar (i, ':');
  822. if (i < 0)
  823. break;
  824. int end = list.indexOfChar (i, ';');
  825. if (end < 0)
  826. end = 0x7ffff;
  827. return list.substring (i + 1, end).trim();
  828. }
  829. ++i;
  830. }
  831. return defaultValue;
  832. }
  833. //==============================================================================
  834. static bool parseNextNumber (String::CharPointerType& text, String& value, const bool allowUnits)
  835. {
  836. String::CharPointerType s (text);
  837. while (s.isWhitespace() || *s == ',')
  838. ++s;
  839. String::CharPointerType start (s);
  840. if (s.isDigit() || *s == '.' || *s == '-')
  841. ++s;
  842. while (s.isDigit() || *s == '.')
  843. ++s;
  844. if ((*s == 'e' || *s == 'E')
  845. && ((s + 1).isDigit() || s[1] == '-' || s[1] == '+'))
  846. {
  847. s += 2;
  848. while (s.isDigit())
  849. ++s;
  850. }
  851. if (allowUnits)
  852. while (s.isLetter())
  853. ++s;
  854. if (s == start)
  855. {
  856. text = s;
  857. return false;
  858. }
  859. value = String (start, s);
  860. while (s.isWhitespace() || *s == ',')
  861. ++s;
  862. text = s;
  863. return true;
  864. }
  865. //==============================================================================
  866. static Colour parseColour (const String& s, int& index, const Colour defaultColour)
  867. {
  868. if (s [index] == '#')
  869. {
  870. uint32 hex[6] = { 0 };
  871. int numChars = 0;
  872. for (int i = 6; --i >= 0;)
  873. {
  874. const int hexValue = CharacterFunctions::getHexDigitValue (s [++index]);
  875. if (hexValue >= 0)
  876. hex [numChars++] = (uint32) hexValue;
  877. else
  878. break;
  879. }
  880. if (numChars <= 3)
  881. return Colour ((uint8) (hex [0] * 0x11),
  882. (uint8) (hex [1] * 0x11),
  883. (uint8) (hex [2] * 0x11));
  884. return Colour ((uint8) ((hex [0] << 4) + hex [1]),
  885. (uint8) ((hex [2] << 4) + hex [3]),
  886. (uint8) ((hex [4] << 4) + hex [5]));
  887. }
  888. if (s [index] == 'r'
  889. && s [index + 1] == 'g'
  890. && s [index + 2] == 'b')
  891. {
  892. const int openBracket = s.indexOfChar (index, '(');
  893. const int closeBracket = s.indexOfChar (openBracket, ')');
  894. if (openBracket >= 3 && closeBracket > openBracket)
  895. {
  896. index = closeBracket;
  897. StringArray tokens;
  898. tokens.addTokens (s.substring (openBracket + 1, closeBracket), ",", "");
  899. tokens.trim();
  900. tokens.removeEmptyStrings();
  901. if (tokens[0].containsChar ('%'))
  902. return Colour ((uint8) roundToInt (2.55 * tokens[0].getDoubleValue()),
  903. (uint8) roundToInt (2.55 * tokens[1].getDoubleValue()),
  904. (uint8) roundToInt (2.55 * tokens[2].getDoubleValue()));
  905. else
  906. return Colour ((uint8) tokens[0].getIntValue(),
  907. (uint8) tokens[1].getIntValue(),
  908. (uint8) tokens[2].getIntValue());
  909. }
  910. }
  911. return Colours::findColourForName (s, defaultColour);
  912. }
  913. static AffineTransform parseTransform (String t)
  914. {
  915. AffineTransform result;
  916. while (t.isNotEmpty())
  917. {
  918. StringArray tokens;
  919. tokens.addTokens (t.fromFirstOccurrenceOf ("(", false, false)
  920. .upToFirstOccurrenceOf (")", false, false),
  921. ", ", "");
  922. tokens.removeEmptyStrings (true);
  923. float numbers [6];
  924. for (int i = 0; i < 6; ++i)
  925. numbers[i] = tokens[i].getFloatValue();
  926. AffineTransform trans;
  927. if (t.startsWithIgnoreCase ("matrix"))
  928. {
  929. trans = AffineTransform (numbers[0], numbers[2], numbers[4],
  930. numbers[1], numbers[3], numbers[5]);
  931. }
  932. else if (t.startsWithIgnoreCase ("translate"))
  933. {
  934. jassert (tokens.size() == 2);
  935. trans = AffineTransform::translation (numbers[0], numbers[1]);
  936. }
  937. else if (t.startsWithIgnoreCase ("scale"))
  938. {
  939. if (tokens.size() == 1)
  940. trans = AffineTransform::scale (numbers[0]);
  941. else
  942. trans = AffineTransform::scale (numbers[0], numbers[1]);
  943. }
  944. else if (t.startsWithIgnoreCase ("rotate"))
  945. {
  946. if (tokens.size() != 3)
  947. trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi));
  948. else
  949. trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi),
  950. numbers[1], numbers[2]);
  951. }
  952. else if (t.startsWithIgnoreCase ("skewX"))
  953. {
  954. trans = AffineTransform (1.0f, std::tan (numbers[0] * (float_Pi / 180.0f)), 0.0f,
  955. 0.0f, 1.0f, 0.0f);
  956. }
  957. else if (t.startsWithIgnoreCase ("skewY"))
  958. {
  959. trans = AffineTransform (1.0f, 0.0f, 0.0f,
  960. std::tan (numbers[0] * (float_Pi / 180.0f)), 1.0f, 0.0f);
  961. }
  962. result = trans.followedBy (result);
  963. t = t.fromFirstOccurrenceOf (")", false, false).trimStart();
  964. }
  965. return result;
  966. }
  967. static void endpointToCentreParameters (const double x1, const double y1,
  968. const double x2, const double y2,
  969. const double angle,
  970. const bool largeArc, const bool sweep,
  971. double& rx, double& ry,
  972. double& centreX, double& centreY,
  973. double& startAngle, double& deltaAngle)
  974. {
  975. const double midX = (x1 - x2) * 0.5;
  976. const double midY = (y1 - y2) * 0.5;
  977. const double cosAngle = cos (angle);
  978. const double sinAngle = sin (angle);
  979. const double xp = cosAngle * midX + sinAngle * midY;
  980. const double yp = cosAngle * midY - sinAngle * midX;
  981. const double xp2 = xp * xp;
  982. const double yp2 = yp * yp;
  983. double rx2 = rx * rx;
  984. double ry2 = ry * ry;
  985. const double s = (xp2 / rx2) + (yp2 / ry2);
  986. double c;
  987. if (s <= 1.0)
  988. {
  989. c = std::sqrt (jmax (0.0, ((rx2 * ry2) - (rx2 * yp2) - (ry2 * xp2))
  990. / (( rx2 * yp2) + (ry2 * xp2))));
  991. if (largeArc == sweep)
  992. c = -c;
  993. }
  994. else
  995. {
  996. const double s2 = std::sqrt (s);
  997. rx *= s2;
  998. ry *= s2;
  999. c = 0;
  1000. }
  1001. const double cpx = ((rx * yp) / ry) * c;
  1002. const double cpy = ((-ry * xp) / rx) * c;
  1003. centreX = ((x1 + x2) * 0.5) + (cosAngle * cpx) - (sinAngle * cpy);
  1004. centreY = ((y1 + y2) * 0.5) + (sinAngle * cpx) + (cosAngle * cpy);
  1005. const double ux = (xp - cpx) / rx;
  1006. const double uy = (yp - cpy) / ry;
  1007. const double vx = (-xp - cpx) / rx;
  1008. const double vy = (-yp - cpy) / ry;
  1009. const double length = juce_hypot (ux, uy);
  1010. startAngle = acos (jlimit (-1.0, 1.0, ux / length));
  1011. if (uy < 0)
  1012. startAngle = -startAngle;
  1013. startAngle += double_Pi * 0.5;
  1014. deltaAngle = acos (jlimit (-1.0, 1.0, ((ux * vx) + (uy * vy))
  1015. / (length * juce_hypot (vx, vy))));
  1016. if ((ux * vy) - (uy * vx) < 0)
  1017. deltaAngle = -deltaAngle;
  1018. if (sweep)
  1019. {
  1020. if (deltaAngle < 0)
  1021. deltaAngle += double_Pi * 2.0;
  1022. }
  1023. else
  1024. {
  1025. if (deltaAngle > 0)
  1026. deltaAngle -= double_Pi * 2.0;
  1027. }
  1028. deltaAngle = fmod (deltaAngle, double_Pi * 2.0);
  1029. }
  1030. template <typename OperationType>
  1031. static bool findElementForId (const XmlPath& parent, const String& id, OperationType& op)
  1032. {
  1033. forEachXmlChildElement (*parent, e)
  1034. {
  1035. if (e->compareAttribute ("id", id))
  1036. {
  1037. op (parent.getChild (e));
  1038. return true;
  1039. }
  1040. if (findElementForId (parent.getChild (e), id, op))
  1041. return true;
  1042. }
  1043. return false;
  1044. }
  1045. SVGState& operator= (const SVGState&) JUCE_DELETED_FUNCTION;
  1046. };
  1047. //==============================================================================
  1048. Drawable* Drawable::createFromSVG (const XmlElement& svgDocument)
  1049. {
  1050. SVGState state (&svgDocument);
  1051. return state.parseSVGElement (SVGState::XmlPath (&svgDocument, nullptr));
  1052. }
  1053. Path Drawable::parseSVGPath (const String& svgPath)
  1054. {
  1055. SVGState state (nullptr);
  1056. Path p;
  1057. state.parsePathString (p, svgPath);
  1058. return p;
  1059. }