diff --git a/CHANGELOG.md b/CHANGELOG.md index 609ab87f075..d026f9e272b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ +CORE +---- +### utils +- ofXML - provided access to the underlying pugi::xml_node method "getParent()" + PLATFORM/IDE SPECIFIC ----------------- @@ -9,7 +14,10 @@ ADDONS ------ ### ofxOpenCv - added support for OpenCV4 : deprecated C functions replaced by their C++ counterpart. Also fix issue due to incorrect pkg-config package [commit](https://github.com/openframeworks/openFrameworks/commit/) - + +### ofxSVG +- added support for the SVG "use" command, a common feature that allows multiple instances of the same graphic elements. Also fixed an issue where line weights less that 1 unit would not render. This increased compatibility can be disabled if required by using the ofxSVG::setImprovedCompatibilityMode(bool mode) method. [commit](https://github.com/openframeworks/openFrameworks/commit/) +- -------------------------------------- ``` diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 8faffe313d7..c696a588bee 100644 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -10,7 +10,22 @@ ofxSVG::~ofxSVG(){ paths.clear(); } -void ofxSVG::load(string path){ +float ofxSVG::getWidth() const { + return width; +} + +float ofxSVG::getHeight() const { + return height; +} + +int ofxSVG::getNumPath(){ + return paths.size(); +} +ofPath & ofxSVG::getPathAt(int n){ + return paths[n]; +} + +void ofxSVG::load(std::string path){ path = ofToDataPath(path); if(path.compare("") == 0){ @@ -19,10 +34,22 @@ void ofxSVG::load(string path){ } ofBuffer buffer = ofBufferFromFile(path); - size_t size = buffer.size(); + + loadFromString(buffer.getText(), path); + +} + +void ofxSVG::loadFromString(std::string stringdata, std::string urlstring){ + + // goes some way to improving SVG compatibility + fixSvgString(stringdata); + + const char* data = stringdata.c_str(); + int size = stringdata.size(); + const char* url = urlstring.c_str(); struct svgtiny_diagram * diagram = svgtiny_create(); - svgtiny_code code = svgtiny_parse(diagram, buffer.getText().c_str(), size, path.c_str(), 0, 0); + svgtiny_code code = svgtiny_parse(diagram, data, size, url, 0, 0); if(code != svgtiny_OK){ string msg; @@ -47,7 +74,7 @@ void ofxSVG::load(string path){ msg = "unknown svgtiny_code " + ofToString(code); break; } - ofLogError("ofxSVG") << "load(): couldn't parse \"" << path << "\": " << msg; + ofLogError("ofxSVG") << "load(): couldn't parse \"" << urlstring << "\": " << msg; } setupDiagram(diagram); @@ -55,13 +82,82 @@ void ofxSVG::load(string path){ svgtiny_free(diagram); } +void ofxSVG::fixSvgString(std::string& xmlstring) { + + ofXml xml; + + xml.parse(xmlstring); + + // so it turns out that if the stroke width is <1 it rounds it down to 0, + // and makes it disappear because svgtiny stores strokewidth as an integer! + ofXml::Search strokeWidthElements = xml.find("//*[@stroke-width]"); + if(!strokeWidthElements.empty()) { + + for(ofXml & element: strokeWidthElements){ + //cout << element.toString() << endl; + float strokewidth = element.getAttribute("stroke-width").getFloatValue(); + strokewidth = MAX(1,round(strokewidth)); + element.getAttribute("stroke-width").set(strokewidth); + + } + } + + //lib svgtiny doesn't remove elements with display = none, so this code fixes that + + bool finished = false; + while(!finished) { + + ofXml::Search invisibleElements = xml.find("//*[@display=\"none\"]"); + + if(invisibleElements.empty()) { + finished = true; + } else { + const ofXml& element = invisibleElements[0]; + ofXml parent = element.getParent(); + if(parent && element) parent.removeChild(element); + } + + } + + // implement the SVG "use" element by expanding out those elements into + // XML that svgtiny will parse correctly. + ofXml::Search useElements = xml.find("//use"); + if(!useElements.empty()) { + + for(ofXml & element: useElements){ + + // get the id attribute + string id = element.getAttribute("xlink:href").getValue(); + // remove the leading "#" from the id + id.erase(id.begin()); + + // find the original definition of that element - TODO add defs into path? + string searchstring ="//*[@id='"+id+"']"; + ofXml idelement = xml.findFirst(searchstring); + + // if we found one then use it! (find first returns an empty xml on failure) + if(idelement.getAttribute("id").getValue()!="") { + + // make a copy of that element + element.appendChild(idelement); + + // then turn the use element into a g element + element.setName("g"); + + } + } + } + + xmlstring = xml.toString(); + +} + void ofxSVG::draw(){ for(int i = 0; i < (int)paths.size(); i++){ paths[i].draw(); } } - void ofxSVG::setupDiagram(struct svgtiny_diagram * diagram){ width = diagram->width; diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index 3ba0d5b3812..008a4a32b45 100644 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -3,31 +3,50 @@ //#include "ofMain.h" #include "ofPath.h" #include "ofTypes.h" +#include "ofXml.h" + + +/// \file +/// ofxSVG is used for loading and rendering SVG files. It's a wrapper +/// for the open source C library [Libsvgtiny](https://www.netsurf-browser.org/projects/libsvgtiny/ "Libsvgtiny website"), +/// and it supports files in the [SVG Tiny format](https://www.w3.org/TR/SVGMobile/ "SVG Tiny 1.2 +/// format specification at the W3C"). +/// +/// Libsvgtiny supports a subset of SVG elements, (for a full list, see the Libsvgtiny readme file) +/// but we have gone some way to improving this by manually implementing some extra features (such as the +/// SVG "use" element). class ofxSVG { public: ~ofxSVG(); - - float getWidth() const { - return width; - } - float getHeight() const { - return height; - } + float getWidth() const ; + float getHeight() const; + + /// \brief Loads an SVG file from the provided filename. + /// + /// ~~~~ void load(std::string path); + + /// \brief Loads an SVG from a text string. + /// + /// Useful for parsing SVG text from sources other than a file. As the + /// underlying SVG parsing library requires a url, this method gives + /// you the option of providing one. + /// + /// ~~~~ + void loadFromString(std::string data, std::string url="local"); + void draw(); - int getNumPath(){ - return paths.size(); - } - ofPath & getPathAt(int n){ - return paths[n]; - } - + int getNumPath(); + ofPath & getPathAt(int n); + const std::vector & getPaths() const; private: + void fixSvgString(std::string& xmlstring); + float width, height; std::vector paths; diff --git a/libs/openFrameworks/utils/ofXml.cpp b/libs/openFrameworks/utils/ofXml.cpp index 33b07daafed..e6c006341c8 100644 --- a/libs/openFrameworks/utils/ofXml.cpp +++ b/libs/openFrameworks/utils/ofXml.cpp @@ -150,6 +150,9 @@ ofXml ofXml::getLastChild() const{ return ofXml(doc, this->xml.last_child()); } +ofXml ofXml::getParent() const { + return ofXml(doc, this->xml.parent()); +} ofXml::Attribute ofXml::getAttribute(const std::string & name) const{ return this->xml.attribute(name.c_str()); diff --git a/libs/openFrameworks/utils/ofXml.h b/libs/openFrameworks/utils/ofXml.h index 67a132c40d6..5fb5b438a0c 100644 --- a/libs/openFrameworks/utils/ofXml.h +++ b/libs/openFrameworks/utils/ofXml.h @@ -135,6 +135,8 @@ class ofXml{ ofXml getFirstChild() const; ofXml getLastChild() const; + + ofXml getParent() const; Attribute getAttribute(const std::string & name) const;