diff --git a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index e819d3fc1a..8b17df846d 100644 --- a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -28,7 +28,6 @@ public: //============================================================================== explicit SVGState (const XmlElement* const topLevel) : topLevelXml (topLevel, nullptr), - elementX (0), elementY (0), width (512), height (512), viewBoxW (0), viewBoxH (0) { @@ -66,63 +65,59 @@ public: const XmlPath* parent; }; - //============================================================================== - struct UsePathOp - { - const SVGState* state; - Path* targetPath; - - void operator() (const XmlPath& xmlPath) + //============================================================================== + struct UsePathOp { - state->parsePathElement (xmlPath, *targetPath); - } - }; + const SVGState* state; + Path* targetPath; - struct GetClipPathOp - { - const SVGState* state; - Drawable* target; + void operator() (const XmlPath& xmlPath) + { + state->parsePathElement (xmlPath, *targetPath); + } + }; - void operator() (const XmlPath& xmlPath) + struct GetClipPathOp { - state->applyClipPath (*target, xmlPath); - } - }; + const SVGState* state; + Drawable* target; - struct SetGradientStopsOp - { - const SVGState* state; - ColourGradient* gradient; + void operator() (const XmlPath& xmlPath) + { + state->applyClipPath (*target, xmlPath); + } + }; - void operator() (const XmlPath& xml) + struct SetGradientStopsOp { - state->addGradientStopsIn (*gradient, xml); - } - }; + const SVGState* state; + ColourGradient* gradient; - struct GetFillTypeOp - { - const SVGState* state; - const Path* path; - float opacity; - FillType fillType; + void operator() (const XmlPath& xml) + { + state->addGradientStopsIn (*gradient, xml); + } + }; - void operator() (const XmlPath& xml) + struct GetFillTypeOp { - if (xml->hasTagNameIgnoringNamespace ("linearGradient") - || xml->hasTagNameIgnoringNamespace ("radialGradient")) - fillType = state->getGradientFillType (xml, *path, opacity); - } - }; + const SVGState* state; + const Path* path; + float opacity; + FillType fillType; + + void operator() (const XmlPath& xml) + { + if (xml->hasTagNameIgnoringNamespace ("linearGradient") + || xml->hasTagNameIgnoringNamespace ("radialGradient")) + fillType = state->getGradientFillType (xml, *path, opacity); + } + }; //============================================================================== Drawable* parseSVGElement (const XmlPath& xml) { - if (! xml->hasTagNameIgnoringNamespace ("svg")) - return nullptr; - DrawableComposite* const drawable = new DrawableComposite(); - setCommonAttributes (*drawable, xml); SVGState newState (*this); @@ -130,10 +125,8 @@ public: if (xml->hasAttribute ("transform")) newState.addTransform (xml); - newState.elementX = getCoordLength (xml->getStringAttribute ("x", String (newState.elementX)), viewBoxW); - newState.elementY = getCoordLength (xml->getStringAttribute ("y", String (newState.elementY)), viewBoxH); - newState.width = getCoordLength (xml->getStringAttribute ("width", String (newState.width)), viewBoxW); - newState.height = getCoordLength (xml->getStringAttribute ("height", String (newState.height)), viewBoxH); + newState.width = getCoordLength (xml->getStringAttribute ("width", String (newState.width)), viewBoxW); + newState.height = getCoordLength (xml->getStringAttribute ("height", String (newState.height)), viewBoxH); if (newState.width <= 0) newState.width = 100; if (newState.height <= 0) newState.height = 100; @@ -186,21 +179,19 @@ public: String::CharPointerType d (pathString.getCharPointer().findEndOfWhitespace()); Point subpathStart, last, last2, p1, p2, p3; - juce_wchar lastCommandChar = 0; + juce_wchar currentCommand = 0, previousCommand = 0; bool isRelative = true; bool carryOn = true; - const CharPointer_ASCII validCommandChars ("MmLlHhVvCcSsQqTtAaZz"); - while (! d.isEmpty()) { - if (validCommandChars.indexOf (*d) >= 0) + if (CharPointer_ASCII ("MmLlHhVvCcSsQqTtAaZz").indexOf (*d) >= 0) { - lastCommandChar = d.getAndAdvance(); - isRelative = (lastCommandChar >= 'a' && lastCommandChar <= 'z'); + currentCommand = d.getAndAdvance(); + isRelative = currentCommand >= 'a'; } - switch (lastCommandChar) + switch (currentCommand) { case 'M': case 'm': @@ -211,11 +202,11 @@ public: if (isRelative) p1 += last; - if (lastCommandChar == 'M' || lastCommandChar == 'm') + if (currentCommand == 'M' || currentCommand == 'm') { subpathStart = p1; path.startNewSubPath (p1); - lastCommandChar = 'l'; + currentCommand = 'l'; } else path.lineTo (p1); @@ -325,7 +316,8 @@ public: if (isRelative) p1 += last; - p2 = last + (last - last2); + p2 = CharPointer_ASCII ("QqTt").indexOf (previousCommand) >= 0 ? last + (last - last2) + : p1; path.quadraticTo (p2, p1); last2 = p2; @@ -389,7 +381,7 @@ public: path.closeSubPath(); last = last2 = subpathStart; d = d.findEndOfWhitespace(); - lastCommandChar = 'M'; + currentCommand = 'M'; break; default: @@ -399,6 +391,8 @@ public: if (! carryOn) break; + + previousCommand = currentCommand; } // paths that finish back at their start position often seem to be @@ -410,7 +404,7 @@ public: private: //============================================================================== const XmlPath topLevelXml; - float elementX, elementY, width, height, viewBoxW, viewBoxH; + float width, height, viewBoxW, viewBoxH; AffineTransform transform; String cssStyleText; @@ -485,7 +479,6 @@ private: { SVGState newState (*this); newState.addTransform (xml); - newState.parseSubElements (xml, *drawable); } else @@ -635,8 +628,7 @@ private: path.applyTransform (transform); dp->setPath (path); - dp->setFill (getPathFillType (path, - getStyleAttribute (xml, "fill"), + dp->setFill (getPathFillType (path, xml, "fill", getStyleAttribute (xml, "fill-opacity"), getStyleAttribute (xml, "opacity"), pathContainsClosedSubPath (path) ? Colours::black @@ -646,7 +638,7 @@ private: if (strokeType.isNotEmpty() && ! strokeType.equalsIgnoreCase ("none")) { - dp->setStrokeFill (getPathFillType (path, strokeType, + dp->setStrokeFill (getPathFillType (path, xml, "stroke", getStyleAttribute (xml, "stroke-opacity"), getStyleAttribute (xml, "opacity"), Colours::transparentBlack)); @@ -751,8 +743,7 @@ private: { forEachXmlChildElementWithTagName (*fillXml, e, "stop") { - int index = 0; - Colour col (parseColour (getStyleAttribute (fillXml.getChild (e), "stop-color"), index, Colours::black)); + Colour col (parseColour (fillXml.getChild (e), "stop-color", Colours::black)); const String opacity (getStyleAttribute (fillXml.getChild (e), "stop-opacity", "1")); col = col.withMultipliedAlpha (jlimit (0.0f, 1.0f, opacity.getFloatValue())); @@ -892,7 +883,8 @@ private: } FillType getPathFillType (const Path& path, - const String& fill, + const XmlPath& xml, + StringRef fillAttribute, const String& fillOpacity, const String& overallOpacity, const Colour defaultColour) const @@ -905,6 +897,7 @@ private: if (fillOpacity.isNotEmpty()) opacity *= (jlimit (0.0f, 1.0f, fillOpacity.getFloatValue())); + String fill (getStyleAttribute (xml, fillAttribute)); String urlID = parseURL (fill); if (urlID.isNotEmpty()) @@ -918,8 +911,7 @@ private: if (fill.equalsIgnoreCase ("none")) return Colours::transparentBlack; - int i = 0; - return parseColour (fill, i, defaultColour).withMultipliedAlpha (opacity); + return parseColour (xml, fillAttribute, defaultColour).withMultipliedAlpha (opacity); } static PathStrokeType::JointStyle getJointStyle (const String& join) noexcept @@ -987,8 +979,7 @@ private: dt->setFont (font, true); dt->setTransform (transform); - int i = 0; - dt->setColour (parseColour (getStyleAttribute (xml, "fill"), i, Colours::black) + dt->setColour (parseColour (xml, "fill", Colours::black) .withMultipliedAlpha (getStyleAttribute (xml, "fill-opacity", "1").getFloatValue())); Rectangle bounds (xCoords[0], yCoords[0] - font.getAscent(), @@ -1167,7 +1158,7 @@ private: return xml->getStringAttribute (attributeName); if (xml.parent != nullptr) - return getInheritedAttribute (*xml.parent, attributeName); + return getInheritedAttribute (*xml.parent, attributeName); return String(); } @@ -1285,16 +1276,19 @@ private: } //============================================================================== - static Colour parseColour (const String& s, int& index, const Colour defaultColour) + Colour parseColour (const XmlPath& xml, StringRef attributeName, const Colour defaultColour) const { - if (s [index] == '#') + const String text (getStyleAttribute (xml, attributeName)); + + if (text.startsWithChar ('#')) { uint32 hex[6] = { 0 }; int numChars = 0; + String::CharPointerType s = text.getCharPointer(); - for (int i = 6; --i >= 0;) + while (numChars < 6) { - const int hexValue = CharacterFunctions::getHexDigitValue (s [++index]); + const int hexValue = CharacterFunctions::getHexDigitValue (*++s); if (hexValue >= 0) hex [numChars++] = (uint32) hexValue; @@ -1303,28 +1297,24 @@ private: } if (numChars <= 3) - return Colour ((uint8) (hex [0] * 0x11), - (uint8) (hex [1] * 0x11), - (uint8) (hex [2] * 0x11)); + return Colour ((uint8) (hex[0] * 0x11), + (uint8) (hex[1] * 0x11), + (uint8) (hex[2] * 0x11)); - return Colour ((uint8) ((hex [0] << 4) + hex [1]), - (uint8) ((hex [2] << 4) + hex [3]), - (uint8) ((hex [4] << 4) + hex [5])); + return Colour ((uint8) ((hex[0] << 4) + hex[1]), + (uint8) ((hex[2] << 4) + hex[3]), + (uint8) ((hex[4] << 4) + hex[5])); } - if (s [index] == 'r' - && s [index + 1] == 'g' - && s [index + 2] == 'b') + if (text.startsWith ("rgb")) { - const int openBracket = s.indexOfChar (index, '('); - const int closeBracket = s.indexOfChar (openBracket, ')'); + const int openBracket = text.indexOfChar ('('); + const int closeBracket = text.indexOfChar (openBracket, ')'); if (openBracket >= 3 && closeBracket > openBracket) { - index = closeBracket; - StringArray tokens; - tokens.addTokens (s.substring (openBracket + 1, closeBracket), ",", ""); + tokens.addTokens (text.substring (openBracket + 1, closeBracket), ",", ""); tokens.trim(); tokens.removeEmptyStrings(); @@ -1332,14 +1322,21 @@ private: return Colour ((uint8) roundToInt (2.55 * tokens[0].getDoubleValue()), (uint8) roundToInt (2.55 * tokens[1].getDoubleValue()), (uint8) roundToInt (2.55 * tokens[2].getDoubleValue())); - else - return Colour ((uint8) tokens[0].getIntValue(), - (uint8) tokens[1].getIntValue(), - (uint8) tokens[2].getIntValue()); + + return Colour ((uint8) tokens[0].getIntValue(), + (uint8) tokens[1].getIntValue(), + (uint8) tokens[2].getIntValue()); } } - return Colours::findColourForName (s, defaultColour); + if (text == "inherit") + { + for (const XmlPath* p = xml.parent; p != nullptr; p = p->parent) + if (getStyleAttribute (*p, attributeName).isNotEmpty()) + return parseColour (*p, attributeName, defaultColour); + } + + return Colours::findColourForName (text, defaultColour); } static AffineTransform parseTransform (String t) @@ -1482,6 +1479,9 @@ private: //============================================================================== Drawable* Drawable::createFromSVG (const XmlElement& svgDocument) { + if (! svgDocument.hasTagNameIgnoringNamespace ("svg")) + return nullptr; + SVGState state (&svgDocument); return state.parseSVGElement (SVGState::XmlPath (&svgDocument, nullptr)); }