Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1322 lines
46KB

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