Skip to content

Commit cb9281f

Browse files
authored
ofxSvg::fixSvgString(): addition to xml preprocessor (#7679)
#changelog #addon
1 parent 8f4993d commit cb9281f

2 files changed

Lines changed: 143 additions & 128 deletions

File tree

addons/ofxSvg/src/ofxSvg.cpp

Lines changed: 108 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
#include "ofxSvg.h"
21
#include "ofConstants.h"
2+
#include "ofxSvg.h"
33
#include <locale>
44

55
using std::string;
66
using std::vector;
77

8-
extern "C"{
9-
#include "svgtiny.h"
8+
extern "C" {
9+
#include "svgtiny.h"
1010
}
11-
ofxSvg::~ofxSvg(){
11+
ofxSvg::~ofxSvg() {
1212
paths.clear();
1313
}
1414

@@ -20,14 +20,14 @@ float ofxSvg::getHeight() const {
2020
return height;
2121
}
2222

23-
int ofxSvg::getNumPath(){
23+
int ofxSvg::getNumPath() {
2424
return paths.size();
2525
}
26-
ofPath & ofxSvg::getPathAt(int n){
26+
ofPath & ofxSvg::getPathAt(int n) {
2727
return paths[n];
2828
}
2929

30-
void ofxSvg::load(of::filesystem::path fileName){
30+
void ofxSvg::load(of::filesystem::path fileName) {
3131
// fileName = ofToDataPath(fileName);
3232
std::string file = ofToDataPath(fileName);
3333

@@ -39,58 +39,57 @@ void ofxSvg::load(of::filesystem::path fileName){
3939
// }
4040

4141
// ofBuffer buffer = ofBufferFromFile(fileName);
42-
42+
4343
// loadFromString(buffer.getText(), fileName);
4444

45-
if(file.compare("") == 0){
45+
if (file.compare("") == 0) {
4646
ofLogError("ofxSVG") << "load(): path does not exist: \"" << file << "\"";
4747
return;
4848
}
4949

5050
ofBuffer buffer = ofBufferFromFile(file);
51-
52-
loadFromString(buffer.getText(), file);
5351

52+
loadFromString(buffer.getText(), file);
5453
}
5554

56-
void ofxSvg::loadFromString(std::string stringdata, std::string urlstring){
57-
55+
void ofxSvg::loadFromString(std::string stringdata, std::string urlstring) {
56+
5857
// goes some way to improving SVG compatibility
5958
fixSvgString(stringdata);
6059

61-
const char* data = stringdata.c_str();
60+
const char * data = stringdata.c_str();
6261
int size = stringdata.size();
63-
const char* url = urlstring.c_str();
62+
const char * url = urlstring.c_str();
6463

6564
struct svgtiny_diagram * diagram = svgtiny_create();
6665
// Switch to "C" locale as svgtiny expect it to parse floating points (issue 6644)
67-
std::locale prev_locale = std::locale::global( std::locale::classic() );
66+
std::locale prev_locale = std::locale::global(std::locale::classic());
6867
svgtiny_code code = svgtiny_parse(diagram, data, size, url, 0, 0);
6968
// Restore locale
70-
std::locale::global( prev_locale );
69+
std::locale::global(prev_locale);
7170

72-
if(code != svgtiny_OK){
71+
if (code != svgtiny_OK) {
7372
string msg;
74-
switch(code){
75-
case svgtiny_OUT_OF_MEMORY:
76-
msg = "svgtiny_OUT_OF_MEMORY";
77-
break;
73+
switch (code) {
74+
case svgtiny_OUT_OF_MEMORY:
75+
msg = "svgtiny_OUT_OF_MEMORY";
76+
break;
7877

79-
/*case svgtiny_LIBXML_ERROR:
78+
/*case svgtiny_LIBXML_ERROR:
8079
msg = "svgtiny_LIBXML_ERROR";
8180
break;*/
8281

83-
case svgtiny_NOT_SVG:
84-
msg = "svgtiny_NOT_SVG";
85-
break;
82+
case svgtiny_NOT_SVG:
83+
msg = "svgtiny_NOT_SVG";
84+
break;
8685

87-
case svgtiny_SVG_ERROR:
88-
msg = "svgtiny_SVG_ERROR: line " + ofToString(diagram->error_line) + ": " + diagram->error_message;
89-
break;
86+
case svgtiny_SVG_ERROR:
87+
msg = "svgtiny_SVG_ERROR: line " + ofToString(diagram->error_line) + ": " + diagram->error_message;
88+
break;
9089

91-
default:
92-
msg = "unknown svgtiny_code " + ofToString(code);
93-
break;
90+
default:
91+
msg = "unknown svgtiny_code " + ofToString(code);
92+
break;
9493
}
9594
ofLogError("ofxSVG") << "load(): couldn't parse \"" << urlstring << "\": " << msg;
9695
}
@@ -100,142 +99,161 @@ void ofxSvg::loadFromString(std::string stringdata, std::string urlstring){
10099
svgtiny_free(diagram);
101100
}
102101

103-
void ofxSvg::fixSvgString(std::string& xmlstring) {
104-
102+
void ofxSvg::fixSvgString(std::string & xmlstring) {
103+
105104
ofXml xml;
106-
105+
107106
xml.parse(xmlstring);
108-
107+
109108
// so it turns out that if the stroke width is <1 it rounds it down to 0,
110109
// and makes it disappear because svgtiny stores strokewidth as an integer!
111110
ofXml::Search strokeWidthElements = xml.find("//*[@stroke-width]");
112-
if(!strokeWidthElements.empty()) {
113-
114-
for(ofXml & element: strokeWidthElements){
111+
if (!strokeWidthElements.empty()) {
112+
for (ofXml & element : strokeWidthElements) {
115113
//cout << element.toString() << endl;
116114
float strokewidth = element.getAttribute("stroke-width").getFloatValue();
117-
strokewidth = MAX(1,round(strokewidth));
115+
strokewidth = MAX(1, round(strokewidth));
118116
element.getAttribute("stroke-width").set(strokewidth);
119-
120117
}
121118
}
122-
119+
120+
// Affinity Designer does not set width/height as pixels but as a percentage
121+
// and relies on the "viewBox" to convey the size of things. this applies the
122+
// viewBox to the width and height.
123+
124+
std::vector<std::string> rect;
125+
for (ofXml & element : xml.find("//*[@viewBox]")) {
126+
rect = ofSplitString(element.getAttribute("viewBox").getValue(), " ");
127+
}
128+
129+
if (rect.size() == 4) {
130+
131+
for (ofXml & element : xml.find("//*[@width]")) {
132+
if (element.getAttribute("width").getValue() == "100%") {
133+
auto w = ofToFloat(rect.at(2));
134+
ofLogWarning("ofxSvg::fixSvgString()") << "the SVG size is provided as percentage, which svgtiny translates to 0. The width is corrected from the viewBox width: " << w;
135+
element.getAttribute("width").set(w);
136+
}
137+
}
138+
139+
for (ofXml & element : xml.find("//*[@height]")) {
140+
if (element.getAttribute("height").getValue() == "100%") {
141+
auto w = ofToFloat(rect.at(3));
142+
ofLogWarning("ofxSvg::fixSvgString()") << "the SVG size is provided as percentage, which svgtiny translates to 0. The height is corrected from the viewBox height: " << w;
143+
element.getAttribute("height").set(w);
144+
}
145+
}
146+
}
147+
123148
//lib svgtiny doesn't remove elements with display = none, so this code fixes that
124-
149+
125150
bool finished = false;
126-
while(!finished) {
127-
128-
ofXml::Search invisibleElements = xml.find("//*[@display=\"none\"]");
129-
130-
if(invisibleElements.empty()) {
151+
while (!finished) {
152+
153+
ofXml::Search invisibleElements = xml.find("//*[@display=\"none\"]");
154+
155+
if (invisibleElements.empty()) {
131156
finished = true;
132157
} else {
133-
const ofXml& element = invisibleElements[0];
158+
const ofXml & element = invisibleElements[0];
134159
ofXml parent = element.getParent();
135-
if(parent && element) parent.removeChild(element);
160+
if (parent && element) parent.removeChild(element);
136161
}
137-
138162
}
139-
163+
140164
// implement the SVG "use" element by expanding out those elements into
141165
// XML that svgtiny will parse correctly.
142166
ofXml::Search useElements = xml.find("//use");
143-
if(!useElements.empty()) {
144-
145-
for(ofXml & element: useElements){
146-
167+
if (!useElements.empty()) {
168+
169+
for (ofXml & element : useElements) {
170+
147171
// get the id attribute
148172
string id = element.getAttribute("xlink:href").getValue();
149173
// remove the leading "#" from the id
150174
id.erase(id.begin());
151-
175+
152176
// find the original definition of that element - TODO add defs into path?
153-
string searchstring ="//*[@id='"+id+"']";
177+
string searchstring = "//*[@id='" + id + "']";
154178
ofXml idelement = xml.findFirst(searchstring);
155-
179+
156180
// if we found one then use it! (find first returns an empty xml on failure)
157-
if(idelement.getAttribute("id").getValue()!="") {
158-
181+
if (idelement.getAttribute("id").getValue() != "") {
182+
159183
// make a copy of that element
160184
element.appendChild(idelement);
161-
185+
162186
// then turn the use element into a g element
163187
element.setName("g");
164-
165188
}
166189
}
167190
}
168-
191+
169192
xmlstring = xml.toString();
170-
171193
}
172194

173-
void ofxSvg::draw(){
174-
for(int i = 0; i < (int)paths.size(); i++){
195+
void ofxSvg::draw() {
196+
for (int i = 0; i < (int)paths.size(); i++) {
175197
paths[i].draw();
176198
}
177199
}
178200

179-
void ofxSvg::setupDiagram(struct svgtiny_diagram * diagram){
201+
void ofxSvg::setupDiagram(struct svgtiny_diagram * diagram) {
180202

181203
width = diagram->width;
182204
height = diagram->height;
183205

184206
paths.clear();
185207

186-
for(int i = 0; i < (int)diagram->shape_count; i++){
187-
if(diagram->shape[i].path){
208+
for (int i = 0; i < (int)diagram->shape_count; i++) {
209+
if (diagram->shape[i].path) {
188210
paths.push_back(ofPath());
189-
setupShape(&diagram->shape[i],paths.back());
190-
}else if(diagram->shape[i].text){
211+
setupShape(&diagram->shape[i], paths.back());
212+
} else if (diagram->shape[i].text) {
191213
ofLogWarning("ofxSVG") << "setupDiagram(): text: not implemented yet";
192214
}
193215
}
194216
}
195217

196-
void ofxSvg::setupShape(struct svgtiny_shape * shape, ofPath & path){
218+
void ofxSvg::setupShape(struct svgtiny_shape * shape, ofPath & path) {
197219
float * p = shape->path;
198220

199221
path.setFilled(false);
200222

201-
if(shape->fill != svgtiny_TRANSPARENT){
223+
if (shape->fill != svgtiny_TRANSPARENT) {
202224
path.setFilled(true);
203225
path.setFillHexColor(shape->fill);
204226
path.setPolyWindingMode(OF_POLY_WINDING_NONZERO);
205-
}
227+
}
206228

207-
if(shape->stroke != svgtiny_TRANSPARENT){
229+
if (shape->stroke != svgtiny_TRANSPARENT) {
208230
path.setStrokeWidth(shape->stroke_width);
209231
path.setStrokeHexColor(shape->stroke);
210232
}
211233

212-
for(int i = 0; i < (int)shape->path_length;){
213-
if(p[i] == svgtiny_PATH_MOVE){
234+
for (int i = 0; i < (int)shape->path_length;) {
235+
if (p[i] == svgtiny_PATH_MOVE) {
214236
path.moveTo(p[i + 1], p[i + 2]);
215237
i += 3;
216-
}
217-
else if(p[i] == svgtiny_PATH_CLOSE){
238+
} else if (p[i] == svgtiny_PATH_CLOSE) {
218239
path.close();
219240

220241
i += 1;
221-
}
222-
else if(p[i] == svgtiny_PATH_LINE){
242+
} else if (p[i] == svgtiny_PATH_LINE) {
223243
path.lineTo(p[i + 1], p[i + 2]);
224244
i += 3;
225-
}
226-
else if(p[i] == svgtiny_PATH_BEZIER){
245+
} else if (p[i] == svgtiny_PATH_BEZIER) {
227246
path.bezierTo(p[i + 1], p[i + 2],
228-
p[i + 3], p[i + 4],
229-
p[i + 5], p[i + 6]);
247+
p[i + 3], p[i + 4],
248+
p[i + 5], p[i + 6]);
230249
i += 7;
231-
}
232-
else{
250+
} else {
233251
ofLogError("ofxSVG") << "setupShape(): SVG parse error";
234252
i += 1;
235253
}
236254
}
237255
}
238256

239-
const vector <ofPath> & ofxSvg::getPaths() const{
240-
return paths;
257+
const vector<ofPath> & ofxSvg::getPaths() const {
258+
return paths;
241259
}

0 commit comments

Comments
 (0)