Skip to content

Commit

Permalink
ofxSvg improved compatibility (#6303)
Browse files Browse the repository at this point in the history
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. 

#changelog #addons #ofxSvg
  • Loading branch information
sebleedelisle authored and arturoc committed Jun 5, 2019
1 parent 67076ab commit 1c1e15a
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 20 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@

CORE
----
### utils
- ofXML - provided access to the underlying pugi::xml_node method "getParent()"

PLATFORM/IDE SPECIFIC
-----------------

Expand All @@ -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/)
-
--------------------------------------

```
Expand Down
106 changes: 101 additions & 5 deletions addons/ofxSvg/src/ofxSvg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand All @@ -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;
Expand All @@ -47,21 +74,90 @@ 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);

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;
Expand Down
47 changes: 33 additions & 14 deletions addons/ofxSvg/src/ofxSvg.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ofPath> & getPaths() const;

private:

void fixSvgString(std::string& xmlstring);

float width, height;

std::vector <ofPath> paths;
Expand Down
3 changes: 3 additions & 0 deletions libs/openFrameworks/utils/ofXml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions libs/openFrameworks/utils/ofXml.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class ofXml{

ofXml getFirstChild() const;
ofXml getLastChild() const;

ofXml getParent() const;


Attribute getAttribute(const std::string & name) const;
Expand Down

0 comments on commit 1c1e15a

Please sign in to comment.