mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added method Drawable::getOutlineAsPath(), and used this for parsing SVG clip regions
This commit is contained in:
parent
80229c24a9
commit
cb7ecfd77b
11 changed files with 418 additions and 45 deletions
|
|
@ -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<DrawableComposite*> (getParentComponent());
|
||||
}
|
||||
|
||||
void Drawable::setClipPath (Drawable* clipPath)
|
||||
{
|
||||
if (drawableClipPath != clipPath)
|
||||
{
|
||||
drawableClipPath = clipPath;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Drawable::transformContextToCorrectOrigin (Graphics& g)
|
||||
{
|
||||
g.setOrigin (originRelativeToComponent);
|
||||
|
|
|
|||
|
|
@ -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<float>);
|
||||
/** @internal */
|
||||
void applyDrawableClipPath (Graphics&);
|
||||
|
||||
Point<int> originRelativeToComponent;
|
||||
ScopedPointer<Drawable> drawableClipPath;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/** Internal utility class used by Drawables. */
|
||||
|
|
|
|||
|
|
@ -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<Drawable*> (getChildComponent (i)))
|
||||
p.addPath (childDrawable->getOutlineAsPath());
|
||||
|
||||
p.applyTransform (getTransform());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -290,3 +290,8 @@ ValueTree DrawableImage::createValueTree (ComponentBuilder::ImageProvider* image
|
|||
|
||||
return tree;
|
||||
}
|
||||
|
||||
Path DrawableImage::getOutlineAsPath() const
|
||||
{
|
||||
return {}; // not applicable for images
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -162,6 +162,18 @@ void DrawableText::recalculateCoordinates (Expression::Scope* scope)
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
Rectangle<int> DrawableText::getTextArea (float w, float h) const
|
||||
{
|
||||
return Rectangle<float> (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<float> (resolvedPoints[0], resolvedPoints[1]).getLength();
|
||||
const float h = Line<float> (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<float> (w, h).getSmallestIntegerContainer(), justification, 0x100000);
|
||||
g.drawFittedText (text, getTextArea (w, h), justification, 0x100000);
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableText::getDrawableBounds() const
|
||||
|
|
@ -334,3 +344,31 @@ ValueTree DrawableText::createValueTree (ComponentBuilder::ImageProvider*) const
|
|||
|
||||
return tree;
|
||||
}
|
||||
|
||||
Path DrawableText::getOutlineAsPath() const
|
||||
{
|
||||
auto w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength();
|
||||
auto h = Line<float> (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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ public:
|
|||
static const Identifier valueTreeType;
|
||||
/** @internal */
|
||||
Rectangle<float> 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<int> getTextArea (float width, float height) const;
|
||||
AffineTransform getTextTransform (float width, float height) const;
|
||||
|
||||
DrawableText& operator= (const DrawableText&);
|
||||
JUCE_LEAK_DETECTOR (DrawableText)
|
||||
|
|
|
|||
|
|
@ -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 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;
|
||||
|
||||
bool operator() (const XmlPath& xmlPath)
|
||||
{
|
||||
target = state->parseImage (xmlPath, true, transform);
|
||||
return target != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct GetClipPathOp
|
||||
{
|
||||
const SVGState* state;
|
||||
SVGState* state;
|
||||
Drawable* target;
|
||||
|
||||
void operator() (const XmlPath& xmlPath) const
|
||||
bool operator() (const XmlPath& xmlPath)
|
||||
{
|
||||
state->applyClipPath (*target, 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<DrawableComposite> 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<float> 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> 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<XmlElement> outer (doc.getDocumentElement (true));
|
||||
|
||||
if (outer != nullptr && outer->hasTagName ("svg"))
|
||||
{
|
||||
ScopedPointer<XmlElement> 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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue