The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1307 lines
45KB

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