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 46KB

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