42 const XmlElement& operator*()
const noexcept {
jassert (xml !=
nullptr);
return *xml; }
43 const XmlElement* operator->()
const noexcept {
return xml; }
46 template <
typename OperationType>
57 if (child.applyOperationToChildWithID (
id, op))
76 return state->parsePathElement (
xmlPath, *targetPath);
88 target = state->parseText (
xmlPath,
true, transform);
89 return target !=
nullptr;
101 target = state->parseImage (
xmlPath,
true, transform);
102 return target !=
nullptr;
113 return state->applyClipPath (*target,
xmlPath);
122 bool operator() (
const XmlPath& xml)
const
124 return state->addGradientStopsIn (*gradient, xml);
135 bool operator() (
const XmlPath& xml)
140 fillType = state->getGradientFillType (xml, *path, opacity);
152 setCommonAttributes (*
drawable, xml);
199 drawable->resetBoundingBoxToContentArea();
205 void parsePathString (Path& path,
const String&
pathString)
const
207 auto d =
pathString.getCharPointer().findEndOfWhitespace();
214 while (! d.isEmpty())
216 if (CharPointer_ASCII (
"MmLlHhVvCcSsQqTtAaZz").indexOf (*d) >= 0)
228 if (parseCoordsOrSkip (d,
p1,
false))
236 path.startNewSubPath (
p1);
248 if (parseCoord (d,
p1.x,
false, Axis::x))
253 path.lineTo (
p1.x, last.y);
266 if (parseCoord (d,
p1.y,
false, Axis::y))
271 path.lineTo (last.x,
p1.y);
284 if (parseCoordsOrSkip (d,
p1,
false)
285 && parseCoordsOrSkip (d,
p2,
false)
286 && parseCoordsOrSkip (d,
p3,
false))
295 path.cubicTo (
p1,
p2,
p3);
304 if (parseCoordsOrSkip (d,
p1,
false)
305 && parseCoordsOrSkip (d,
p3,
false))
318 path.cubicTo (
p2,
p1,
p3);
327 if (parseCoordsOrSkip (d,
p1,
false)
328 && parseCoordsOrSkip (d,
p2,
false))
336 path.quadraticTo (
p1,
p2);
345 if (parseCoordsOrSkip (d,
p1,
false))
355 path.quadraticTo (
p2,
p1);
364 if (parseCoordsOrSkip (d,
p1,
false))
369 if (parseNextNumber (d, num,
false))
381 if (parseCoordsOrSkip (d,
p2,
false))
391 endpointToCentreParameters (last.x, last.y,
p2.x,
p2.y,
397 (
float)
rx, (
float)
ry,
418 d.incrementToEndOfWhitespace();
441 const File originalFile;
442 const XmlPath topLevelXml;
443 float width = 512, height = 512, viewBoxW = 0, viewBoxH = 0;
444 AffineTransform transform;
447 static bool isNone (
const String& s)
noexcept
449 return s.equalsIgnoreCase (
"none");
452 static void setCommonAttributes (Drawable& d,
const XmlPath& xml)
454 auto compID = xml->getStringAttribute (
"id");
456 d.setComponentID (
compID);
458 if (isNone (xml->getStringAttribute (
"display")))
459 d.setVisible (
false);
465 for (
auto* e : xml->getChildIterator())
467 const XmlPath child (xml.getChild (e));
469 if (
auto*
drawable = parseSubElement (child))
473 if (! isNone (getStyleAttribute (child,
"display")))
482 Drawable* parseSubElement (
const XmlPath& xml)
486 if (parsePathElement (xml, path))
487 return parseShape (xml, path);
490 auto tag = xml->getTagNameWithoutNamespace();
492 if (tag ==
"g")
return parseGroupElement (xml,
true);
493 if (tag ==
"svg")
return parseSVGElement (xml);
494 if (tag ==
"text")
return parseText (xml,
true,
nullptr);
495 if (tag ==
"image")
return parseImage (xml,
true);
496 if (tag ==
"switch")
return parseSwitch (xml);
497 if (tag ==
"a")
return parseLinkElement (xml);
498 if (tag ==
"use")
return parseUseOther (xml);
499 if (tag ==
"style") parseCSSStyle (xml);
500 if (tag ==
"defs") parseDefs (xml);
505 bool parsePathElement (
const XmlPath& xml, Path& path)
const
507 auto tag = xml->getTagNameWithoutNamespace();
509 if (tag ==
"path") { parsePath (xml, path);
return true; }
510 if (tag ==
"rect") { parseRect (xml, path);
return true; }
511 if (tag ==
"circle") { parseCircle (xml, path);
return true; }
512 if (tag ==
"ellipse") { parseEllipse (xml, path);
return true; }
513 if (tag ==
"line") { parseLine (xml, path);
return true; }
514 if (tag ==
"polyline") { parsePolygon (xml,
true, path);
return true; }
515 if (tag ==
"polygon") { parsePolygon (xml,
false, path);
return true; }
516 if (tag ==
"use") {
return parseUsePath (xml, path); }
521 DrawableComposite* parseSwitch (
const XmlPath& xml)
523 if (
auto* group = xml->getChildByName (
"g"))
524 return parseGroupElement (xml.getChild (group),
true);
536 return newState.parseGroupElement (xml,
false);
539 auto*
drawable =
new DrawableComposite();
540 setCommonAttributes (*
drawable, xml);
543 drawable->resetContentAreaAndBoundingBoxToFitChildren();
547 DrawableComposite* parseLinkElement (
const XmlPath& xml)
549 return parseGroupElement (xml,
true);
553 void parsePath (
const XmlPath& xml, Path& path)
const
555 parsePathString (path, xml->getStringAttribute (
"d"));
557 if (getStyleAttribute (xml,
"fill-rule").trim().equalsIgnoreCase (
"evenodd"))
558 path.setUsingNonZeroWinding (
false);
561 void parseRect (
const XmlPath& xml, Path& rect)
const
563 const bool hasRX = xml->hasAttribute (
"rx");
564 const bool hasRY = xml->hasAttribute (
"ry");
568 float rx = getCoordLength (xml,
"rx", viewBoxW);
569 float ry = getCoordLength (xml,
"ry", viewBoxH);
576 rect.addRoundedRectangle (getCoordLength (xml,
"x", viewBoxW),
577 getCoordLength (xml,
"y", viewBoxH),
578 getCoordLength (xml,
"width", viewBoxW),
579 getCoordLength (xml,
"height", viewBoxH),
584 rect.addRectangle (getCoordLength (xml,
"x", viewBoxW),
585 getCoordLength (xml,
"y", viewBoxH),
586 getCoordLength (xml,
"width", viewBoxW),
587 getCoordLength (xml,
"height", viewBoxH));
591 void parseCircle (
const XmlPath& xml, Path&
circle)
const
593 auto cx = getCoordLength (xml,
"cx", viewBoxW);
594 auto cy = getCoordLength (xml,
"cy", viewBoxH);
595 auto radius = getCoordLength (xml,
"r", viewBoxW);
597 circle.addEllipse (
cx - radius,
cy - radius, radius * 2.0f, radius * 2.0f);
600 void parseEllipse (
const XmlPath& xml, Path&
ellipse)
const
602 auto cx = getCoordLength (xml,
"cx", viewBoxW);
603 auto cy = getCoordLength (xml,
"cy", viewBoxH);
604 auto radiusX = getCoordLength (xml,
"rx", viewBoxW);
605 auto radiusY = getCoordLength (xml,
"ry", viewBoxH);
610 void parseLine (
const XmlPath& xml, Path& line)
const
612 auto x1 = getCoordLength (xml,
"x1", viewBoxW);
613 auto y1 = getCoordLength (xml,
"y1", viewBoxH);
614 auto x2 = getCoordLength (xml,
"x2", viewBoxW);
615 auto y2 = getCoordLength (xml,
"y2", viewBoxH);
617 line.startNewSubPath (x1, y1);
618 line.lineTo (x2, y2);
621 void parsePolygon (
const XmlPath& xml,
bool isPolyline, Path& path)
const
623 auto pointsAtt = xml->getStringAttribute (
"points");
627 if (parseCoords (
points, p,
true))
629 Point<float> first (p), last;
631 path.startNewSubPath (first);
633 while (parseCoords (
points, p,
true))
644 static String getLinkedID (
const XmlPath& xml)
646 auto link = xml->getStringAttribute (
"xlink:href");
648 if (
link.startsWithChar (
'#'))
649 return link.substring (1);
654 bool parseUsePath (
const XmlPath& xml, Path& path)
const
660 UsePathOp op = {
this, &path };
661 return topLevelXml.applyOperationToChildWithID (
linkedID, op);
667 Drawable* parseUseOther (
const XmlPath& xml)
const
675 static String parseURL (
const String& str)
677 if (str.startsWithIgnoreCase (
"url"))
678 return str.fromFirstOccurrenceOf (
"#",
false,
false)
679 .upToLastOccurrenceOf (
")",
false,
false).trim();
685 Drawable* parseShape (
const XmlPath& xml, Path& path,
697 auto dp =
new DrawablePath();
698 setCommonAttributes (*
dp, xml);
699 dp->setFill (Colours::transparentBlack);
701 path.applyTransform (transform);
708 dp->setFill (getPathFillType (path, xml,
"fill",
709 getStyleAttribute (xml,
"fill-opacity"),
710 getStyleAttribute (xml,
"opacity"),
711 pathContainsClosedSubPath (path) ? Colours::black
712 : Colours::transparentBlack));
714 auto strokeType = getStyleAttribute (xml,
"stroke");
716 if (strokeType.isNotEmpty() && ! isNone (strokeType))
718 dp->setStrokeFill (getPathFillType (path, xml,
"stroke",
719 getStyleAttribute (xml,
"stroke-opacity"),
720 getStyleAttribute (xml,
"opacity"),
721 Colours::transparentBlack));
723 dp->setStrokeType (getStrokeFor (xml));
734 static bool pathContainsClosedSubPath (
const Path& path)
noexcept
736 for (Path::Iterator iter (path); iter.next();)
743 void parseDashArray (
const String&
dashList, DrawablePath&
dp)
const
748 Array<float> dashLengths;
750 for (
auto t =
dashList.getCharPointer();;)
753 if (! parseCoord (t, value,
true, Axis::x))
756 dashLengths.add (value);
758 t.incrementToEndOfWhitespace();
764 if (dashLengths.size() > 0)
766 auto*
dashes = dashLengths.getRawDataPointer();
768 for (
int i = 0; i < dashLengths.size(); ++i)
772 if (dashLengths.size() == 1)
786 dp.setDashLengths (dashLengths);
790 bool parseClipPath (
const XmlPath& xml, Drawable& d)
792 const String
clipPath (getStyleAttribute (xml,
"clip-path"));
798 if (
urlID.isNotEmpty())
800 GetClipPathOp op = {
this, &d };
801 return topLevelXml.applyOperationToChildWithID (
urlID, op);
808 bool applyClipPath (Drawable& target,
const XmlPath&
xmlPath)
810 if (
xmlPath->hasTagNameIgnoringNamespace (
"clipPath"))
814 parseSubElements (
xmlPath, *drawableClipPath,
false);
816 if (drawableClipPath->getNumChildComponents() > 0)
818 setCommonAttributes (*drawableClipPath,
xmlPath);
819 target.setClipPath (std::move (drawableClipPath));
827 bool addGradientStopsIn (ColourGradient&
cg,
const XmlPath&
fillXml)
const
833 for (
auto* e :
fillXml->getChildWithTagNameIterator (
"stop"))
835 auto col = parseColour (
fillXml.getChild (e),
"stop-color", Colours::black);
837 auto opacity = getStyleAttribute (
fillXml.getChild (e),
"stop-opacity",
"1");
838 col =
col.withMultipliedAlpha (
jlimit (0.0f, 1.0f, parseSafeFloat (opacity)));
840 auto offset = parseSafeFloat (e->getStringAttribute (
"offset"));
842 if (e->getStringAttribute (
"offset").containsChar (
'%'))
853 FillType getGradientFillType (
const XmlPath&
fillXml,
855 const float opacity)
const
857 ColourGradient gradient;
864 SetGradientStopsOp op = {
this, &gradient, };
865 topLevelXml.applyOperationToChildWithID (
linkedID, op);
869 addGradientStopsIn (gradient,
fillXml);
871 if (
int numColours = gradient.getNumColours())
873 if (gradient.getColourPosition (0) > 0)
874 gradient.addColour (0.0, gradient.getColour (0));
876 if (gradient.getColourPosition (numColours - 1) < 1.0)
877 gradient.addColour (1.0, gradient.getColour (numColours - 1));
881 gradient.addColour (0.0, Colours::black);
882 gradient.addColour (1.0, Colours::black);
886 gradient.multiplyOpacity (opacity);
888 jassert (gradient.getNumColours() > 0);
890 gradient.isRadial =
fillXml->hasTagNameIgnoringNamespace (
"radialGradient");
897 const bool userSpace =
fillXml->getStringAttribute (
"gradientUnits").equalsIgnoreCase (
"userSpaceOnUse");
901 auto bounds = path.getBounds();
908 if (gradient.isRadial)
914 gradient.point1.setXY (
dx +
gradientWidth * getCoordLength (
fillXml->getStringAttribute (
"cx",
"50%"), 1.0f),
918 gradient.point2 = gradient.point1 + Point<float> (radius, 0.0f);
934 gradient.point1.setXY (
dx +
gradientWidth * getCoordLength (
fillXml->getStringAttribute (
"x1",
"0%"), 1.0f),
937 gradient.point2.setXY (
dx +
gradientWidth * getCoordLength (
fillXml->getStringAttribute (
"x2",
"100%"), 1.0f),
941 if (gradient.point1 == gradient.point2)
942 return Colour (gradient.getColour (gradient.getNumColours() - 1));
945 FillType type (gradient);
949 if (gradient.isRadial)
957 auto perpendicular = Point<float> (gradient.point2.y - gradient.point1.y,
958 gradient.point1.x - gradient.point2.x)
976 FillType getPathFillType (
const Path& path,
983 float opacity = 1.0f;
992 String
urlID = parseURL (fill);
994 if (
urlID.isNotEmpty())
996 GetFillTypeOp op = {
this, &path, opacity, FillType() };
998 if (topLevelXml.applyOperationToChildWithID (
urlID, op))
1003 return Colours::transparentBlack;
1024 float getStrokeWidth (
const String&
strokeWidth)
const noexcept
1030 PathStrokeType getStrokeFor (
const XmlPath& xml)
const
1032 return PathStrokeType (getStrokeWidth (getStyleAttribute (xml,
"stroke-width",
"1")),
1033 getJointStyle (getStyleAttribute (xml,
"stroke-linejoin")),
1034 getEndCapStyle (getStyleAttribute (xml,
"stroke-linecap")));
1038 Drawable* useText (
const XmlPath& xml)
const
1041 parseSafeFloat (xml->getStringAttribute (
"y")));
1043 UseTextOp op = {
this, &translation,
nullptr };
1048 topLevelXml.applyOperationToChildWithID (
linkedID, op);
1065 class StringLayoutState
1068 StringLayoutState (StringLayoutState*
parentIn, Array<float>
xIn, Array<float>
yIn)
1075 Point<float> getNextStartingPos()
const
1077 if (parent !=
nullptr)
1078 return parent->getNextStartingPos();
1080 return nextStartingPos;
1083 void setNextStartingPos (Point<float>
newPos)
1085 nextStartingPos =
newPos;
1087 if (parent !=
nullptr)
1088 parent->setNextStartingPos (
newPos);
1096 if (parent !=
nullptr)
1100 if (! x.has_value())
1103 if (! y.has_value())
1110 bool hasMoreCoords()
const
1112 if (! xCoords.isEmpty() || ! yCoords.isEmpty())
1115 if (parent !=
nullptr)
1116 return parent->hasMoreCoords();
1122 StringLayoutState* parent =
nullptr;
1123 Point<float> nextStartingPos;
1124 Array<float> xCoords, yCoords;
1139 if (xml->hasTagName (
"use"))
1140 return useText (xml);
1142 if (! xml->hasTagName (
"text") && ! xml->hasTagNameIgnoringNamespace (
"tspan"))
1148 getCoordList (*xml, Axis::x),
1149 getCoordList (*xml, Axis::y) };
1151 auto font = getFont (xml);
1152 auto anchorStr = getStyleAttribute (xml,
"text-anchor");
1154 auto dc =
new DrawableComposite();
1155 setCommonAttributes (*dc, xml);
1157 for (
auto* e : xml->getChildIterator())
1159 if (e->isTextElement())
1180 auto dt =
new DrawableText();
1181 dc->addAndMakeVisible (
dt);
1184 dt->setFont (font,
true);
1189 dt->setDrawableTransform (transform);
1191 dt->setColour (parseColour (xml,
"fill", Colours::black)
1192 .withMultipliedAlpha (parseSafeFloat (getStyleAttribute (xml,
"fill-opacity",
"1"))));
1194 const auto x =
optX.value_or (
layoutState.getNextStartingPos().getX());
1195 const auto y =
optY.value_or (
layoutState.getNextStartingPos().getY());
1197 Rectangle<float> bounds (x, y - font.getAscent(),
1198 font.getStringWidthFloat (text), font.getHeight());
1200 if (
anchorStr ==
"middle") bounds.setX (bounds.getX() - bounds.getWidth() / 2.0f);
1201 else if (
anchorStr ==
"end") bounds.setX (bounds.getX() - bounds.getWidth());
1203 dt->setBoundingBox (bounds);
1205 layoutState.setNextStartingPos ({ bounds.getRight(), y });
1208 else if (e->hasTagNameIgnoringNamespace (
"tspan"))
1210 dc->addAndMakeVisible (parseText (xml.getChild (e),
true,
nullptr, &
layoutState));
1217 Font getFont (
const XmlPath& xml)
const
1220 auto family = getStyleAttribute (xml,
"font-family").
unquoted();
1222 if (family.isNotEmpty())
1223 f.setTypefaceName (family);
1225 if (getStyleAttribute (xml,
"font-style").containsIgnoreCase (
"italic"))
1228 if (getStyleAttribute (xml,
"font-weight").containsIgnoreCase (
"bold"))
1231 return f.withPointHeight (getCoordLength (getStyleAttribute (xml,
"font-size",
"15"), 1.0f));
1235 Drawable* useImage (
const XmlPath& xml)
const
1238 parseSafeFloat (xml->getStringAttribute (
"y")));
1240 UseImageOp op = {
this, &translation,
nullptr };
1245 topLevelXml.applyOperationToChildWithID (
linkedID, op);
1261 if (xml->hasTagName (
"use"))
1262 return useImage (xml);
1264 if (! xml->hasTagName (
"image"))
1267 auto link = xml->getStringAttribute (
"xlink:href");
1272 if (
link.startsWith (
"data:"))
1282 if (
mime.equalsIgnoreCase (
"image/png") ||
mime.equalsIgnoreCase (
"image/jpeg"))
1293 auto linkedFile = originalFile.getParentDirectory().getChildFile (link);
1296 inputStream =
linkedFile.createInputStream();
1299 if (inputStream !=
nullptr)
1303 if (image.isValid())
1305 auto*
di =
new DrawableImage();
1307 setCommonAttributes (*
di, xml);
1309 Rectangle<float> imageBounds (parseSafeFloat (xml->getStringAttribute (
"x")),
1310 parseSafeFloat (xml->getStringAttribute (
"y")),
1311 parseSafeFloat (xml->getStringAttribute (
"width", String (image.getWidth()))),
1312 parseSafeFloat (xml->getStringAttribute (
"height", String (image.getHeight()))));
1314 di->setImage (image.rescaled ((
int) imageBounds.getWidth(),
1315 (
int) imageBounds.getHeight()));
1317 di->setTransformToFit (imageBounds, RectanglePlacement (parsePlacementFlags (xml->getStringAttribute (
"preserveAspectRatio").trim())));
1322 di->setTransform (
di->getTransform().followedBy (transform));
1332 void addTransform (
const XmlPath& xml)
1334 transform = parseTransform (xml->getStringAttribute (
"transform"))
1339 enum class Axis { x, y };
1345 if (! parseNextNumber (s, number,
allowUnits))
1351 value = getCoordLength (number,
axis == Axis::x ? viewBoxW : viewBoxH);
1357 return parseCoord (s, p.x,
allowUnits, Axis::x)
1366 if (! s.isEmpty()) ++s;
1370 float getCoordLength (
const String& s,
const float sizeForProportions)
const noexcept
1372 auto n = parseSafeFloat (s);
1373 auto len = s.length();
1379 auto n1 = s[len - 2];
1380 auto n2 = s[len - 1];
1382 if (
n1 ==
'i' &&
n2 ==
'n') n *= dpi;
1383 else if (
n1 ==
'm' &&
n2 ==
'm') n *= dpi / 25.4f;
1384 else if (
n1 ==
'c' &&
n2 ==
'm') n *= dpi / 2.54f;
1385 else if (
n1 ==
'p' &&
n2 ==
'c') n *= 15.0f;
1397 Array<float> getCoordList (
const XmlElement& xml, Axis
axis)
const
1407 Array<float> getCoordList (
const String& list,
bool allowUnits, Axis
axis)
const
1409 auto text = list.getCharPointer();
1419 static float parseSafeFloat (
const String& s)
1421 auto n = s.getFloatValue();
1426 void parseCSSStyle (
const XmlPath& xml)
1428 cssStyleText = xml->getAllSubText() +
"\n" + cssStyleText;
1431 void parseDefs (
const XmlPath& xml)
1433 if (
auto* style = xml->getChildByName (
"style"))
1434 parseCSSStyle (xml.getChild (style));
1441 while (! source.isEmpty())
1443 if (source.getAndAdvance() ==
'.'
1459 String getStyleAttribute (
const XmlPath& xml, StringRef
attributeName,
const String& defaultValue = String())
const
1462 return xml->getStringAttribute (
attributeName, defaultValue);
1464 auto styleAtt = xml->getStringAttribute (
"style");
1470 if (value.isNotEmpty())
1473 else if (xml->hasAttribute (
"class"))
1475 for (
auto i = cssStyleText.getCharPointer();;)
1477 auto openBrace = findStyleItem (i, xml->getStringAttribute (
"class").getCharPointer());
1479 if (openBrace.isEmpty())
1484 if (closeBrace.isEmpty())
1487 auto value = getAttributeFromStyleList (String (openBrace + 1, closeBrace),
1489 if (value.isNotEmpty())
1496 if (xml.parent !=
nullptr)
1497 return getStyleAttribute (*xml.parent,
attributeName, defaultValue);
1499 return defaultValue;
1502 String getInheritedAttribute (
const XmlPath& xml, StringRef
attributeName)
const
1507 if (xml.parent !=
nullptr)
1513 static int parsePlacementFlags (
const String& align)
noexcept
1515 if (
align.isEmpty())
1522 | (
align.containsIgnoreCase (
"xMin") ? RectanglePlacement::xLeft
1523 : (
align.containsIgnoreCase (
"xMax") ? RectanglePlacement::xRight
1524 : RectanglePlacement::xMid))
1525 | (
align.containsIgnoreCase (
"yMin") ? RectanglePlacement::yTop
1526 : (
align.containsIgnoreCase (
"yMax") ? RectanglePlacement::yBottom
1527 : RectanglePlacement::yMid));
1536 static String getAttributeFromStyleList (
const String& list, StringRef
attributeName,
const String& defaultValue)
1547 if ((i == 0 || (i > 0 && ! isIdentifierChar (list [i - 1])))
1550 i = list.indexOfChar (i,
':');
1555 int end = list.indexOfChar (i,
';');
1560 return list.substring (i + 1,
end).trim();
1566 return defaultValue;
1570 static bool isStartOfNumber (
juce_wchar c)
noexcept
1579 while (s.isWhitespace() || *s ==
',')
1584 if (isStartOfNumber (*s))
1598 if ((*s ==
'e' || *s ==
'E') && isStartOfNumber (s[1]))
1607 while (s.isLetter())
1616 value = String (start, s);
1618 while (s.isWhitespace() || *s ==
',')
1627 while (text.isWhitespace() || *text ==
',')
1630 if (*text !=
'0' && *text !=
'1')
1633 value = *(text++) !=
'0';
1635 while (text.isWhitespace() || *text ==
',')
1646 if (text.startsWithChar (
'#'))
1652 auto s = text.getCharPointer();
1654 while (numChars < 8)
1665 return Colour ((
uint8) (hex[0] * 0x11),
1666 (
uint8) (hex[1] * 0x11),
1667 (
uint8) (hex[2] * 0x11));
1669 return Colour ((
uint8) ((hex[0] << 4) + hex[1]),
1670 (
uint8) ((hex[2] << 4) + hex[3]),
1671 (
uint8) ((hex[4] << 4) + hex[5]),
1672 (
uint8) ((hex[6] << 4) + hex[7]));
1675 if (text.startsWith (
"rgb") || text.startsWith (
"hsl"))
1677 auto tokens = [&text]
1679 auto openBracket = text.indexOfChar (
'(');
1680 auto closeBracket = text.indexOfChar (openBracket,
')');
1684 if (openBracket >= 3 && closeBracket > openBracket)
1686 arr.addTokens (text.substring (openBracket + 1, closeBracket),
",",
"");
1688 arr.removeEmptyStrings();
1694 auto alpha = [&tokens, &text]
1696 if ((text.startsWith (
"rgba") || text.startsWith (
"hsla")) && tokens.size() == 4)
1697 return parseSafeFloat (tokens[3]);
1702 if (text.startsWith (
"hsl"))
1704 parseSafeFloat (tokens[1]) / 100.0f,
1705 parseSafeFloat (tokens[2]) / 100.0f,
1708 if (tokens[0].containsChar (
'%'))
1709 return Colour ((
uint8)
roundToInt (2.55f * parseSafeFloat (tokens[0])),
1714 return Colour ((
uint8) tokens[0].getIntValue(),
1715 (
uint8) tokens[1].getIntValue(),
1716 (
uint8) tokens[2].getIntValue(),
1720 if (text ==
"inherit")
1722 for (
const XmlPath* p = xml.parent; p !=
nullptr; p = p->parent)
1730 static AffineTransform parseTransform (String t)
1732 AffineTransform result;
1734 while (t.isNotEmpty())
1737 tokens.addTokens (t.fromFirstOccurrenceOf (
"(",
false,
false)
1738 .upToFirstOccurrenceOf (
")",
false,
false),
1741 tokens.removeEmptyStrings (
true);
1746 numbers[i] = parseSafeFloat (tokens[i]);
1748 AffineTransform
trans;
1750 if (t.startsWithIgnoreCase (
"matrix"))
1755 else if (t.startsWithIgnoreCase (
"translate"))
1759 else if (t.startsWithIgnoreCase (
"scale"))
1763 else if (t.startsWithIgnoreCase (
"rotate"))
1767 else if (t.startsWithIgnoreCase (
"skewX"))
1771 else if (t.startsWithIgnoreCase (
"skewY"))
1776 result =
trans.followedBy (result);
1777 t = t.fromFirstOccurrenceOf (
")",
false,
false).trimStart();
1783 static void endpointToCentreParameters (
double x1,
double y1,
1784 double x2,
double y2,
1787 double&
rx,
double&
ry,
1791 const double midX = (x1 - x2) * 0.5;
1792 const double midY = (
y1 - y2) * 0.5;
1801 double rx2 =
rx *
rx;
1802 double ry2 =
ry *
ry;
1804 const double s = (
xp2 / rx2) + (
yp2 / ry2);
1810 / (( rx2 *
yp2) + (ry2 *
xp2))));
1823 const double cpx = ((
rx *
yp) /
ry) * c;
1824 const double cpy = ((-
ry *
xp) /
rx) * c;
1863 SVGState (
const SVGState&) =
default;
1864 SVGState& operator= (
const SVGState&) =
delete;