diff --git a/modules/juce_gui_basics/drawables/juce_Drawable.cpp b/modules/juce_gui_basics/drawables/juce_Drawable.cpp index 4075340eb3..fcec458993 100644 --- a/modules/juce_gui_basics/drawables/juce_Drawable.cpp +++ b/modules/juce_gui_basics/drawables/juce_Drawable.cpp @@ -44,6 +44,17 @@ Drawable::~Drawable() { } +void Drawable::applyDrawableClipPath (Graphics& g) +{ + if (drawableClipPath != nullptr) + { + auto clipPath = drawableClipPath->getOutlineAsPath(); + + if (! clipPath.isEmpty()) + g.getInternalContext().clipToPath (clipPath, {}); + } +} + //============================================================================== void Drawable::draw (Graphics& g, float opacity, const AffineTransform& transform) const { @@ -59,6 +70,8 @@ void Drawable::nonConstDraw (Graphics& g, float opacity, const AffineTransform& .followedBy (getTransform()) .followedBy (transform)); + applyDrawableClipPath (g); + if (! g.isClipEmpty()) { if (opacity < 1.0f) @@ -91,6 +104,15 @@ DrawableComposite* Drawable::getParent() const return dynamic_cast (getParentComponent()); } +void Drawable::setClipPath (Drawable* clipPath) +{ + if (drawableClipPath != clipPath) + { + drawableClipPath = clipPath; + repaint(); + } +} + void Drawable::transformContextToCorrectOrigin (Graphics& g) { g.setOrigin (originRelativeToComponent); diff --git a/modules/juce_gui_basics/drawables/juce_Drawable.h b/modules/juce_gui_basics/drawables/juce_Drawable.h index 64921cb4ac..810970b2c3 100644 --- a/modules/juce_gui_basics/drawables/juce_Drawable.h +++ b/modules/juce_gui_basics/drawables/juce_Drawable.h @@ -54,6 +54,9 @@ public: */ virtual Drawable* createCopy() const = 0; + /** Creates a path that describes the outline of this drawable. */ + virtual Path getOutlineAsPath() const = 0; + //============================================================================== /** Renders this Drawable object. @@ -63,7 +66,8 @@ public: @see drawWithin */ - void draw (Graphics& g, float opacity, const AffineTransform& transform = {}) const; + void draw (Graphics& g, float opacity, + const AffineTransform& transform = AffineTransform()) const; /** Renders the Drawable at a given offset within the Graphics context. @@ -116,6 +120,11 @@ public: /** Returns the DrawableComposite that contains this object, if there is one. */ DrawableComposite* getParent() const; + /** Sets a the clipping region of this drawable using another drawable. + The drawbale passed in ill be deleted when no longer needed. + */ + void setClipPath (Drawable* drawableClipPath); + //============================================================================== /** Tries to turn some kind of image file into a drawable. @@ -149,6 +158,20 @@ public: */ static Drawable* createFromSVG (const XmlElement& svgDocument); + /** Attempts to parse an SVG (Scalable Vector Graphics) document from a file, + and to turn this into a Drawable tree. + + The object returned must be deleted by the caller. If something goes wrong + while parsing, it may return nullptr. + + SVG is a pretty large and complex spec, and this doesn't aim to be a full + implementation, but it can return the basic vector objects. + + Any references to references to external image files will be relative to + the parent directory of the file passed. + */ + static Drawable* createFromSVGFile (const File& svgFile); + /** Parses an SVG path string and returns it. */ static Path parseSVGPath (const String& svgPath); @@ -213,8 +236,11 @@ protected: void parentHierarchyChanged() override; /** @internal */ void setBoundsToEnclose (Rectangle); + /** @internal */ + void applyDrawableClipPath (Graphics&); Point originRelativeToComponent; + ScopedPointer drawableClipPath; #ifndef DOXYGEN /** Internal utility class used by Drawables. */ diff --git a/modules/juce_gui_basics/drawables/juce_DrawableComposite.cpp b/modules/juce_gui_basics/drawables/juce_DrawableComposite.cpp index eeabaf9db3..2dfa0fec22 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableComposite.cpp +++ b/modules/juce_gui_basics/drawables/juce_DrawableComposite.cpp @@ -319,3 +319,16 @@ ValueTree DrawableComposite::createValueTree (ComponentBuilder::ImageProvider* i return tree; } + +Path DrawableComposite::getOutlineAsPath() const +{ + Path p; + + for (int i = 0; i < getNumChildComponents(); ++i) + if (auto* childDrawable = dynamic_cast (getChildComponent (i))) + p.addPath (childDrawable->getOutlineAsPath()); + + p.applyTransform (getTransform()); + + return p; +} diff --git a/modules/juce_gui_basics/drawables/juce_DrawableComposite.h b/modules/juce_gui_basics/drawables/juce_DrawableComposite.h index deeb46dab1..a11ae87777 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableComposite.h +++ b/modules/juce_gui_basics/drawables/juce_DrawableComposite.h @@ -115,6 +115,8 @@ public: void parentHierarchyChanged() override; /** @internal */ MarkerList* getMarkers (bool xAxis) override; + /** @internal */ + Path getOutlineAsPath() const override; //============================================================================== /** Internally-used class for wrapping a DrawableComposite's state into a ValueTree. */ diff --git a/modules/juce_gui_basics/drawables/juce_DrawableImage.cpp b/modules/juce_gui_basics/drawables/juce_DrawableImage.cpp index 3940f2235e..c8f1770955 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableImage.cpp +++ b/modules/juce_gui_basics/drawables/juce_DrawableImage.cpp @@ -290,3 +290,8 @@ ValueTree DrawableImage::createValueTree (ComponentBuilder::ImageProvider* image return tree; } + +Path DrawableImage::getOutlineAsPath() const +{ + return {}; // not applicable for images +} diff --git a/modules/juce_gui_basics/drawables/juce_DrawableImage.h b/modules/juce_gui_basics/drawables/juce_DrawableImage.h index a4ea339a6b..818012e444 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableImage.h +++ b/modules/juce_gui_basics/drawables/juce_DrawableImage.h @@ -94,6 +94,8 @@ public: ValueTree createValueTree (ComponentBuilder::ImageProvider*) const override; /** @internal */ static const Identifier valueTreeType; + /** @internal */ + Path getOutlineAsPath() const override; //============================================================================== /** Internally-used class for wrapping a DrawableImage's state into a ValueTree. */ diff --git a/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp b/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp index f1ed39532e..9c23c44a7c 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp +++ b/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp @@ -171,6 +171,7 @@ void DrawableShape::writeTo (FillAndStrokeState& state, ComponentBuilder::ImageP void DrawableShape::paint (Graphics& g) { transformContextToCorrectOrigin (g); + applyDrawableClipPath (g); g.setFillType (mainFill.fill); g.fillPath (path); @@ -488,3 +489,10 @@ bool DrawableShape::replaceColour (Colour original, Colour replacement) bool changed2 = replaceColourInFill (strokeFill, original, replacement); return changed1 || changed2; } + +Path DrawableShape::getOutlineAsPath() const +{ + Path outline (isStrokeVisible() ? strokePath : path); + outline.applyTransform (getTransform()); + return outline; +} diff --git a/modules/juce_gui_basics/drawables/juce_DrawableShape.h b/modules/juce_gui_basics/drawables/juce_DrawableShape.h index a9f60ae256..2cb7e3712a 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableShape.h +++ b/modules/juce_gui_basics/drawables/juce_DrawableShape.h @@ -156,6 +156,8 @@ public: bool hitTest (int x, int y) override; /** @internal */ bool replaceColour (Colour originalColour, Colour replacementColour) override; + /** @internal */ + Path getOutlineAsPath() const override; protected: //============================================================================== diff --git a/modules/juce_gui_basics/drawables/juce_DrawableText.cpp b/modules/juce_gui_basics/drawables/juce_DrawableText.cpp index af15206aae..3bfddce9d7 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableText.cpp +++ b/modules/juce_gui_basics/drawables/juce_DrawableText.cpp @@ -162,6 +162,18 @@ void DrawableText::recalculateCoordinates (Expression::Scope* scope) } //============================================================================== +Rectangle DrawableText::getTextArea (float w, float h) const +{ + return Rectangle (w, h).getSmallestIntegerContainer(); +} + +AffineTransform DrawableText::getTextTransform (float w, float h) const +{ + return AffineTransform::fromTargetPoints (0, 0, resolvedPoints[0].x, resolvedPoints[0].y, + w, 0, resolvedPoints[1].x, resolvedPoints[1].y, + 0, h, resolvedPoints[2].x, resolvedPoints[2].y); +} + void DrawableText::paint (Graphics& g) { transformContextToCorrectOrigin (g); @@ -169,13 +181,11 @@ void DrawableText::paint (Graphics& g) const float w = Line (resolvedPoints[0], resolvedPoints[1]).getLength(); const float h = Line (resolvedPoints[0], resolvedPoints[2]).getLength(); - g.addTransform (AffineTransform::fromTargetPoints (0, 0, resolvedPoints[0].x, resolvedPoints[0].y, - w, 0, resolvedPoints[1].x, resolvedPoints[1].y, - 0, h, resolvedPoints[2].x, resolvedPoints[2].y)); + g.addTransform (getTextTransform (w, h)); g.setFont (scaledFont); g.setColour (colour); - g.drawFittedText (text, Rectangle (w, h).getSmallestIntegerContainer(), justification, 0x100000); + g.drawFittedText (text, getTextArea (w, h), justification, 0x100000); } Rectangle DrawableText::getDrawableBounds() const @@ -334,3 +344,31 @@ ValueTree DrawableText::createValueTree (ComponentBuilder::ImageProvider*) const return tree; } + +Path DrawableText::getOutlineAsPath() const +{ + auto w = Line (resolvedPoints[0], resolvedPoints[1]).getLength(); + auto h = Line (resolvedPoints[0], resolvedPoints[2]).getLength(); + const auto area = getTextArea (w, h).toFloat(); + + GlyphArrangement arr; + arr.addFittedText (scaledFont, text, + area.getX(), area.getY(), + area.getWidth(), area.getHeight(), + justification, + 0x100000); + + Path pathOfAllGlyphs; + + for (int i = 0; i < arr.getNumGlyphs(); ++i) + { + Path gylphPath; + arr.getGlyph (i).createPath (gylphPath); + pathOfAllGlyphs.addPath (gylphPath); + } + + pathOfAllGlyphs.applyTransform (getTextTransform (w, h) + .followedBy (getTransform())); + + return pathOfAllGlyphs; +} diff --git a/modules/juce_gui_basics/drawables/juce_DrawableText.h b/modules/juce_gui_basics/drawables/juce_DrawableText.h index d7536b3d70..1033b0b665 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableText.h +++ b/modules/juce_gui_basics/drawables/juce_DrawableText.h @@ -99,6 +99,8 @@ public: static const Identifier valueTreeType; /** @internal */ Rectangle getDrawableBounds() const override; + /** @internal */ + Path getOutlineAsPath() const override; //============================================================================== /** Internally-used class for wrapping a DrawableText's state into a ValueTree. */ @@ -147,6 +149,8 @@ private: bool registerCoordinates (RelativeCoordinatePositionerBase&); void recalculateCoordinates (Expression::Scope*); void refreshBounds(); + Rectangle getTextArea (float width, float height) const; + AffineTransform getTextTransform (float width, float height) const; DrawableText& operator= (const DrawableText&); JUCE_LEAK_DETECTOR (DrawableText) diff --git a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index 88e456a873..bb1e76f306 100644 --- a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -28,7 +28,8 @@ class SVGState { public: //============================================================================== - explicit SVGState (const XmlElement* topLevel) : topLevelXml (topLevel, nullptr) + explicit SVGState (const XmlElement* topLevel, const File& svgFile = {}) + : originalFile (svgFile), topLevelXml (topLevel, nullptr) { } @@ -47,11 +48,9 @@ public: { XmlPath child (e, this); - if (e->compareAttribute ("id", id)) - { - op (child); - return true; - } + if (e->compareAttribute ("id", id) + && ! child->hasTagName ("defs")) + return op (child); if (child.applyOperationToChildWithID (id, op)) return true; @@ -70,20 +69,60 @@ public: const SVGState* state; Path* targetPath; - void operator() (const XmlPath& xmlPath) const + bool operator() (const XmlPath& xmlPath) const { - state->parsePathElement (xmlPath, *targetPath); + return state->parsePathElement (xmlPath, *targetPath); } }; - struct GetClipPathOp + struct UseShapeOp + { + const SVGState* state; + Path* sourcePath; + AffineTransform* transform; + Drawable* target; + + bool operator() (const XmlPath& xmlPath) + { + target = state->parseShape (xmlPath, *sourcePath, true, transform); + return target != nullptr; + } + }; + + struct UseTextOp + { + const SVGState* state; + AffineTransform* transform; + Drawable* target; + + bool operator() (const XmlPath& xmlPath) + { + target = state->parseText (xmlPath, true, transform); + return target != nullptr; + } + }; + + struct UseImageOp { const SVGState* state; + AffineTransform* transform; Drawable* target; - void operator() (const XmlPath& xmlPath) const + bool operator() (const XmlPath& xmlPath) { - state->applyClipPath (*target, xmlPath); + target = state->parseImage (xmlPath, true, transform); + return target != nullptr; + } + }; + + struct GetClipPathOp + { + SVGState* state; + Drawable* target; + + bool operator() (const XmlPath& xmlPath) + { + return state->applyClipPath (*target, xmlPath); } }; @@ -92,9 +131,9 @@ public: const SVGState* state; ColourGradient* gradient; - void operator() (const XmlPath& xml) const + bool operator() (const XmlPath& xml) const { - state->addGradientStopsIn (*gradient, xml); + return state->addGradientStopsIn (*gradient, xml); } }; @@ -105,11 +144,16 @@ public: float opacity; FillType fillType; - void operator() (const XmlPath& xml) + bool operator() (const XmlPath& xml) { if (xml->hasTagNameIgnoringNamespace ("linearGradient") || xml->hasTagNameIgnoringNamespace ("radialGradient")) + { fillType = state->getGradientFillType (xml, *path, opacity); + return true; + } + + return false; } }; @@ -402,6 +446,7 @@ public: private: //============================================================================== + const File originalFile; const XmlPath topLevelXml; float width = 512, height = 512, viewBoxW = 0, viewBoxH = 0; AffineTransform transform; @@ -423,7 +468,7 @@ private: } //============================================================================== - void parseSubElements (const XmlPath& xml, DrawableComposite& parentDrawable) + void parseSubElements (const XmlPath& xml, DrawableComposite& parentDrawable, const bool shouldParseClip = true) { forEachXmlChildElement (*xml, e) { @@ -435,6 +480,9 @@ private: if (! isNone (getStyleAttribute (child, "display"))) drawable->setVisible (true); + + if (shouldParseClip) + parseClipPath (child, *drawable); } } } @@ -449,11 +497,13 @@ private: auto tag = xml->getTagNameWithoutNamespace(); - if (tag == "g") return parseGroupElement (xml); + if (tag == "g") return parseGroupElement (xml, true); if (tag == "svg") return parseSVGElement (xml); if (tag == "text") return parseText (xml, true); + if (tag == "image") return parseImage (xml, true); if (tag == "switch") return parseSwitch (xml); if (tag == "a") return parseLinkElement (xml); + if (tag == "use") return parseUseOther (xml); if (tag == "style") parseCSSStyle (xml); if (tag == "defs") parseDefs (xml); @@ -462,7 +512,7 @@ private: bool parsePathElement (const XmlPath& xml, Path& path) const { - const String tag (xml->getTagNameWithoutNamespace()); + auto tag = xml->getTagNameWithoutNamespace(); if (tag == "path") { parsePath (xml, path); return true; } if (tag == "rect") { parseRect (xml, path); return true; } @@ -471,7 +521,7 @@ private: if (tag == "line") { parseLine (xml, path); return true; } if (tag == "polyline") { parsePolygon (xml, true, path); return true; } if (tag == "polygon") { parsePolygon (xml, false, path); return true; } - if (tag == "use") { parseUse (xml, path); return true; } + if (tag == "use") { return parseUsePath (xml, path); } return false; } @@ -484,9 +534,17 @@ private: return nullptr; } - DrawableComposite* parseGroupElement (const XmlPath& xml) + DrawableComposite* parseGroupElement (const XmlPath& xml, bool shouldParseTransform = true) { - auto drawable = new DrawableComposite(); + if (shouldParseTransform && xml->hasAttribute ("transform")) + { + SVGState newState (*this); + newState.addTransform (xml); + + return newState.parseGroupElement (xml, false); + } + + auto* drawable = new DrawableComposite(); setCommonAttributes (*drawable, xml); @@ -602,17 +660,35 @@ private: } } - void parseUse (const XmlPath& xml, Path& path) const + static String getLinkedID (const XmlPath& xml) { auto link = xml->getStringAttribute ("xlink:href"); if (link.startsWithChar ('#')) - { - auto linkedID = link.substring (1); + return link.substring (1); + + return {}; + } + + bool parseUsePath (const XmlPath& xml, Path& path) const + { + auto linkedID = getLinkedID (xml); + if (linkedID.isNotEmpty()) + { UsePathOp op = { this, &path }; - topLevelXml.applyOperationToChildWithID (linkedID, op); + return topLevelXml.applyOperationToChildWithID (linkedID, op); } + + return false; + } + + Drawable* parseUseOther (const XmlPath& xml) const + { + if (auto* drawableText = parseText (xml, false)) return drawableText; + if (auto* drawableImage = parseImage (xml, false)) return drawableImage; + + return nullptr; } static String parseURL (const String& str) @@ -625,22 +701,46 @@ private: } //============================================================================== + + Drawable* useShape (const XmlPath& xml, Path& path) const + { + auto translation = AffineTransform::translation ((float) xml->getDoubleAttribute ("x", 0.0), + (float) xml->getDoubleAttribute ("y", 0.0)); + + UseShapeOp op = { this, &path, &translation, nullptr }; + + auto linkedID = getLinkedID (xml); + + if (linkedID.isNotEmpty()) + topLevelXml.applyOperationToChildWithID (linkedID, op); + + return op.target; + } + Drawable* parseShape (const XmlPath& xml, Path& path, - const bool shouldParseTransform = true) const + const bool shouldParseTransform = true, + AffineTransform* additonalTransform = nullptr) const { if (shouldParseTransform && xml->hasAttribute ("transform")) { SVGState newState (*this); newState.addTransform (xml); - return newState.parseShape (xml, path, false); + return newState.parseShape (xml, path, false, additonalTransform); } + if (xml->hasTagName ("use")) + return useShape (xml, path); + auto dp = new DrawablePath(); setCommonAttributes (*dp, xml); dp->setFill (Colours::transparentBlack); path.applyTransform (transform); + + if (additonalTransform != nullptr) + path.applyTransform (*additonalTransform); + dp->setPath (path); dp->setFill (getPathFillType (path, xml, "fill", @@ -666,7 +766,6 @@ private: if (strokeDashArray.isNotEmpty()) parseDashArray (strokeDashArray, *dp); - parseClipPath (xml, *dp); return dp; } @@ -726,7 +825,7 @@ private: } } - void parseClipPath (const XmlPath& xml, Drawable& d) const + bool parseClipPath (const XmlPath& xml, Drawable& d) { const String clipPath (getStyleAttribute (xml, "clip-path")); @@ -737,22 +836,36 @@ private: if (urlID.isNotEmpty()) { GetClipPathOp op = { this, &d }; - topLevelXml.applyOperationToChildWithID (urlID, op); + return topLevelXml.applyOperationToChildWithID (urlID, op); } } + + return false; } - void applyClipPath (Drawable& target, const XmlPath& xmlPath) const + bool applyClipPath (Drawable& target, const XmlPath& xmlPath) { if (xmlPath->hasTagNameIgnoringNamespace ("clipPath")) { - // TODO: implement clipping.. - ignoreUnused (target); + ScopedPointer drawableClipPath (new DrawableComposite()); + + parseSubElements (xmlPath, *drawableClipPath, false); + + if (drawableClipPath->getNumChildComponents() > 0) + { + setCommonAttributes (*drawableClipPath, xmlPath); + target.setClipPath (drawableClipPath.release()); + return true; + } } + + return false; } - void addGradientStopsIn (ColourGradient& cg, const XmlPath& fillXml) const + bool addGradientStopsIn (ColourGradient& cg, const XmlPath& fillXml) const { + bool result = false; + if (fillXml.xml != nullptr) { forEachXmlChildElementWithTagName (*fillXml, e, "stop") @@ -768,8 +881,11 @@ private: offset *= 0.01; cg.addColour (jlimit (0.0, 1.0, offset), col); + result = true; } } + + return result; } FillType getGradientFillType (const XmlPath& fillXml, @@ -779,12 +895,12 @@ private: ColourGradient gradient; { - auto link = fillXml->getStringAttribute ("xlink:href"); + auto linkedID = getLinkedID (fillXml); - if (link.startsWithChar ('#')) + if (linkedID.isNotEmpty()) { SetGradientStopsOp op = { this, &gradient, }; - topLevelXml.applyOperationToChildWithID (link.substring (1), op); + topLevelXml.applyOperationToChildWithID (linkedID, op); } } @@ -957,16 +1073,39 @@ private: } //============================================================================== - Drawable* parseText (const XmlPath& xml, bool shouldParseTransform) + + Drawable* useText (const XmlPath& xml) const + { + auto translation = AffineTransform::translation ((float) xml->getDoubleAttribute ("x", 0.0), + (float) xml->getDoubleAttribute ("y", 0.0)); + + UseTextOp op = { this, &translation, nullptr }; + + auto linkedID = getLinkedID (xml); + + if (linkedID.isNotEmpty()) + topLevelXml.applyOperationToChildWithID (linkedID, op); + + return op.target; + } + + Drawable* parseText (const XmlPath& xml, bool shouldParseTransform, + AffineTransform* additonalTransform = nullptr) const { if (shouldParseTransform && xml->hasAttribute ("transform")) { SVGState newState (*this); newState.addTransform (xml); - return newState.parseText (xml, false); + return newState.parseText (xml, false, additonalTransform); } + if (xml->hasTagName ("use")) + return useText (xml); + + if (! xml->hasTagName ("text")) + return nullptr; + Array xCoords, yCoords, dxCoords, dyCoords; getCoordList (xCoords, getInheritedAttribute (xml, "x"), true, true); @@ -975,7 +1114,7 @@ private: getCoordList (dyCoords, getInheritedAttribute (xml, "dy"), true, false); auto font = getFont (xml); - auto anchorStr = getStyleAttribute(xml, "text-anchor"); + auto anchorStr = getStyleAttribute (xml, "text-anchor"); auto dc = new DrawableComposite(); setCommonAttributes (*dc, xml); @@ -991,7 +1130,11 @@ private: dt->setText (text); dt->setFont (font, true); - dt->setTransform (transform); + + if (additonalTransform != nullptr) + dt->setTransform (transform.followedBy (*additonalTransform)); + else + dt->setTransform (transform); dt->setColour (parseColour (xml, "fill", Colours::black) .withMultipliedAlpha (getStyleAttribute (xml, "fill-opacity", "1").getFloatValue())); @@ -1030,6 +1173,95 @@ private: return f.withPointHeight (getCoordLength (getStyleAttribute (xml, "font-size"), 1.0f)); } + //============================================================================== + Drawable* useImage (const XmlPath& xml) const + { + auto translation = AffineTransform::translation ((float) xml->getDoubleAttribute ("x", 0.0), + (float) xml->getDoubleAttribute ("y", 0.0)); + + UseImageOp op = { this, &translation, nullptr }; + + auto linkedID = getLinkedID (xml); + + if (linkedID.isNotEmpty()) + topLevelXml.applyOperationToChildWithID (linkedID, op); + + return op.target; + } + + Drawable* parseImage (const XmlPath& xml, bool shouldParseTransform, + AffineTransform* additionalTransform = nullptr) const + { + if (shouldParseTransform && xml->hasAttribute ("transform")) + { + SVGState newState (*this); + newState.addTransform (xml); + + return newState.parseImage (xml, false, additionalTransform); + } + + if (xml->hasTagName ("use")) + return useImage (xml); + + if (! xml->hasTagName ("image")) + return nullptr; + + auto link = xml->getStringAttribute ("xlink:href"); + + ScopedPointer inputStream; + MemoryOutputStream imageStream; + + if (link.startsWith ("data:")) + { + const auto indexOfComma = link.indexOf (","); + auto format = link.substring (5, indexOfComma).trim(); + + const auto indexOfSemi = format.indexOf (";"); + + if (format.substring (indexOfSemi + 1).trim().equalsIgnoreCase ("base64")) + { + auto mime = format.substring (0, indexOfSemi).trim(); + + if (mime.equalsIgnoreCase ("image/png") || mime.equalsIgnoreCase ("image/jpeg")) + { + const String base64text = link.substring (indexOfComma + 1).removeCharacters ("\t\n\r "); + + if (Base64::convertFromBase64 (imageStream, base64text)) + inputStream = new MemoryInputStream (imageStream.getData(), imageStream.getDataSize(), false); + } + } + } + else + { + auto linkedFile = originalFile.getParentDirectory().getChildFile (link); + + if (linkedFile.existsAsFile()) + inputStream = linkedFile.createInputStream(); + } + + if (inputStream != nullptr) + { + auto image = ImageFileFormat::loadFrom (*inputStream); + + if (image.isValid()) + { + auto* di = new DrawableImage(); + + setCommonAttributes (*di, xml); + di->setImage (image); + + if (additionalTransform != nullptr) + di->setTransform (transform.followedBy (*additionalTransform)); + else + di->setTransform (transform); + + return di; + } + } + + return nullptr; + } + //============================================================================== void addTransform (const XmlPath& xml) { @@ -1514,6 +1746,25 @@ Drawable* Drawable::createFromSVG (const XmlElement& svgDocument) return state.parseSVGElement (SVGState::XmlPath (&svgDocument, nullptr)); } +Drawable* Drawable::createFromSVGFile (const File& svgFile) +{ + XmlDocument doc (svgFile); + ScopedPointer outer (doc.getDocumentElement (true)); + + if (outer != nullptr && outer->hasTagName ("svg")) + { + ScopedPointer svgDocument (doc.getDocumentElement()); + + if (svgDocument != nullptr) + { + SVGState state (svgDocument, svgFile); + return state.parseSVGElement (SVGState::XmlPath (svgDocument, nullptr)); + } + } + + return nullptr; +} + Path Drawable::parseSVGPath (const String& svgPath) { SVGState state (nullptr);