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.

1297 lines
44KB

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