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.

1303 lines
45KB

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