From 61a3ae2223b7589590310a02c8c4873f658ab57f Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Sat, 15 Feb 2025 13:38:21 -0500 Subject: [PATCH 1/8] Update / rewrite of the ofxSvg addon to remove dependency on libTinySvg. --- addons/ofxSvg/addon_config.mk | 31 +- addons/ofxSvg/src/ofxSvg.cpp | 2470 ++++++++++++++++++++++++-- addons/ofxSvg/src/ofxSvg.h | 279 ++- addons/ofxSvg/src/ofxSvgCss.cpp | 518 ++++++ addons/ofxSvg/src/ofxSvgCss.h | 136 ++ addons/ofxSvg/src/ofxSvgElements.cpp | 546 ++++++ addons/ofxSvg/src/ofxSvgElements.h | 258 +++ addons/ofxSvg/src/ofxSvgGroup.cpp | 289 +++ addons/ofxSvg/src/ofxSvgGroup.h | 198 +++ 9 files changed, 4494 insertions(+), 231 deletions(-) mode change 100644 => 100755 addons/ofxSvg/src/ofxSvg.cpp mode change 100644 => 100755 addons/ofxSvg/src/ofxSvg.h create mode 100644 addons/ofxSvg/src/ofxSvgCss.cpp create mode 100644 addons/ofxSvg/src/ofxSvgCss.h create mode 100755 addons/ofxSvg/src/ofxSvgElements.cpp create mode 100755 addons/ofxSvg/src/ofxSvgElements.h create mode 100755 addons/ofxSvg/src/ofxSvgGroup.cpp create mode 100755 addons/ofxSvg/src/ofxSvgGroup.h diff --git a/addons/ofxSvg/addon_config.mk b/addons/ofxSvg/addon_config.mk index bbd68296e55..1774fa56a15 100644 --- a/addons/ofxSvg/addon_config.mk +++ b/addons/ofxSvg/addon_config.mk @@ -16,8 +16,8 @@ meta: ADDON_NAME = ofxSvg - ADDON_DESCRIPTION = Addon for parsing svg files into ofPaths - ADDON_AUTHOR = Joshua Noble, maintained by OF Team + ADDON_DESCRIPTION = Addon for parsing, manipulating and saving svg files. + ADDON_AUTHOR = Nick Hardeman, original by Joshua Noble, maintained by OF Team ADDON_TAGS = "svg" ADDON_URL = http://github.com/openframeworks/openFrameworks @@ -59,30 +59,3 @@ common: # when parsing the file system looking for libraries exclude this for all or # a specific platform # ADDON_LIBS_EXCLUDE = - -osx: - ADDON_LIBS = libs/svgtiny/lib/macos/svgtiny.xcframework/macos-arm64_x86_64/libsvgtiny.a - ADDON_LIBS += libs/libxml2/lib/macos/libxml2.xcframework/macos-arm64_x86_64/libxml2.a - -ios: - ADDON_LIBS = libs/svgtiny/lib/ios/svgtiny.a - ADDON_LIBS += libs/libxml2/lib/ios/xml2.a - -linux64: - ADDON_LIBS = libs/svgtiny/lib/linux64/libsvgtiny.a - ADDON_LIBS += libs/libxml2/lib/linux64/libxml2.a - -linuxarmv6l: - ADDON_LIBS = libs/svgtiny/lib/linuxarmv6l/libsvgtiny.a - ADDON_LIBS += libs/libxml2/lib/linuxarmv6l/libxml2.a - -linuxarmv7l: - ADDON_LIBS = libs/svgtiny/lib/linuxarmv7l/libsvgtiny.a - ADDON_LIBS += libs/libxml2/lib/linuxarmv7l/libxml2.a - -linuxaarch64: - ADDON_LIBS = libs/svgtiny/lib/linuxaarch64/libsvgtiny.a - ADDON_LIBS += libs/libxml2/lib/linuxaarch64/libxml2.a - -msys2: - ADDON_PKG_CONFIG_LIBRARIES = libxml-2.0 diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp old mode 100644 new mode 100755 index bbbf54024f1..00b3ae6dd22 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -1,245 +1,2399 @@ -#include "ofConstants.h" +// +// ofxSvgParser.cpp +// +// Created by Nick Hardeman on 8/31/24. +// + #include "ofxSvg.h" -#include +#include "ofUtils.h" +#include +#include "ofGraphics.h" +using namespace ofx::svg; using std::string; +using std::vector; +using std::shared_ptr; + +ofPath ofxSvg::sDummyPath; + +struct Measurement { + double value; + std::string unit; +}; -extern "C" { -#include "svgtiny.h" +//-------------------------------------------------------------- +Measurement parseMeasurement(const std::string& input) { + + if( input.empty() ) { + Measurement result; + result.value = 0.0; + result.unit = ""; + return result; + } + + size_t i = 0; + + // Extract numeric part + while (i < input.size() && (std::isdigit(input[i]) || input[i] == '.' || input[i] == '-')) { + i++; + } + + Measurement result; + result.value = std::stod(input.substr(0, i)); // Convert number part to double + result.unit = input.substr(i); // Extract the unit part + + return result; } -ofxSvg::ofxSvg(const of::filesystem::path & fileName) { - load(fileName); +//-------------------------------------------------------------- +bool ofxSvg::load( const of::filesystem::path& fileName ) { + ofFile mainXmlFile( fileName, ofFile::ReadOnly ); + ofBuffer tMainXmlBuffer( mainXmlFile ); + + svgPath = fileName; + folderPath = ofFilePath::getEnclosingDirectory( fileName, false ); + + ofXml xml; + if( !xml.load(tMainXmlBuffer )) { + ofLogWarning(moduleName()) << " unable to load svg from " << fileName; + return false; + } + + return loadFromString(tMainXmlBuffer.getText()); } -float ofxSvg::getWidth() const { - return width; +//-------------------------------------------------------------- +bool ofxSvg::loadFromString(const std::string& data, std::string url) { + clear(); + + ofXml xml; + xml.parse(data); + + if( xml ) { + ofXml svgNode = xml.getFirstChild(); + + validateXmlSvgRoot( svgNode ); + ofXml::Attribute viewBoxAttr = svgNode.getAttribute("viewBox"); + if(svgNode) { + + std::vector values = { + parseMeasurement(svgNode.getAttribute("x").getValue()), + parseMeasurement(svgNode.getAttribute("y").getValue()), + parseMeasurement(svgNode.getAttribute("width").getValue()), + parseMeasurement(svgNode.getAttribute("height").getValue()) + }; + + for( auto& tv : values ) { + if( !tv.unit.empty() ) { + mUnitStr = tv.unit; + } + } + if( mUnitStr.empty() ) { + mUnitStr = "px"; + } + + mBounds.x = values[0].value; + mBounds.y = values[1].value; + mBounds.width = values[2].value; + mBounds.height = values[3].value; + +// mBounds.x = ofToFloat( cleanString( svgNode.getAttribute("x").getValue(), "px") ); +// mBounds.y = ofToFloat( cleanString( svgNode.getAttribute("y").getValue(), "px" )); +// mBounds.width = ofToFloat( cleanString( svgNode.getAttribute("width").getValue(), "px" )); +// mBounds.height = ofToFloat( cleanString( svgNode.getAttribute("height").getValue(), "px" )); + mViewbox = mBounds; + } + + if( viewBoxAttr ) { + string tboxstr = viewBoxAttr.getValue(); + vector< string > tvals = ofSplitString( tboxstr, " " ); + if( tvals.size() == 4 ) { + mViewbox.x = ofToFloat(tvals[0] ); + mViewbox.y = ofToFloat( tvals[1] ); + mViewbox.width = ofToFloat( tvals[2] ); + mViewbox.height = ofToFloat( tvals[3] ); + } + } + + if(svgNode) { + ofLogVerbose(moduleName()) << svgNode.findFirst("style").toString() << " bounds: " << mBounds; + } else { + ofLogVerbose( moduleName() ) << __FUNCTION__ << " : NO svgNode: "; + } + + + ofXml styleXmlNode = svgNode.findFirst("//style"); + if( styleXmlNode ) { + ofLogVerbose(moduleName()) << __FUNCTION__ << " : STYLE NODE" << styleXmlNode.getAttribute("type").getValue() << " string: " << styleXmlNode.getValue(); + + mSvgCss.parse(styleXmlNode.getValue()); + + ofLogVerbose(moduleName()) << "-----------------------------"; + ofLogVerbose() << mSvgCss.toString(); + ofLogVerbose(moduleName()) << "-----------------------------"; + } else { + ofLogVerbose(moduleName()) << __FUNCTION__ << " : NO STYLE NODE"; + } + + + // the defs are added in the _parseXmlNode function // + _parseXmlNode( svgNode, mChildren ); + + ofLogVerbose(moduleName()) << " number of defs elements: " << mDefElements.size(); + } + + return true; } -float ofxSvg::getHeight() const { - return height; +//-------------------------------------------------------------- +bool ofxSvg::reload() { + if( svgPath.empty() ) { + ofLogError(moduleName()) << __FUNCTION__ << " : svg path is empty, please call load with file path before calling reload"; + return false; + } + return load( svgPath ); } -int ofxSvg::getNumPath() { - return paths.size(); +//-------------------------------------------------------------- +bool ofxSvg::save( of::filesystem::path apath ) { + // https://www.w3.org/TR/SVG2/struct.html#NewDocument + ofXml svgXml; + ofXml svgXmlNode = svgXml.appendChild("svg"); + if( auto vattr = svgXmlNode.appendAttribute("version")) { + vattr.set("1.1"); + } + if( auto vattr = svgXmlNode.appendAttribute("xmlns")) { + vattr.set("http://www.w3.org/2000/svg"); + } + if( auto vattr = svgXmlNode.appendAttribute("xmlns:xlink")) { + vattr.set("http://www.w3.org/1999/xlink"); + } + // + + if( auto vattr = svgXmlNode.appendAttribute("x")) { + vattr.set(ofToString(mBounds.x,2)+mUnitStr); + } + if( auto vattr = svgXmlNode.appendAttribute("y")) { + vattr.set(ofToString(mBounds.y,2)+mUnitStr); + } + if( auto vattr = svgXmlNode.appendAttribute("width")) { + vattr.set(ofToString(mBounds.getWidth(),2)+mUnitStr); + } + if( auto vattr = svgXmlNode.appendAttribute("height")) { + vattr.set(ofToString(mBounds.getHeight(),2)+mUnitStr); + } + if( auto vattr = svgXmlNode.appendAttribute("viewBox")) { + vattr.set(ofToString(mViewbox.x,0)+" "+ofToString(mViewbox.y,0)+" "+ofToString(mViewbox.getWidth(),0)+" "+ofToString(mViewbox.getHeight(),0)); + } + + ofXml cssNode = svgXmlNode.appendChild("style"); + +// if( mCurrentCss.properties.size() > 0 ) { +// mSvgCss.getAddClass(mCurrentCss); +// } + + // now we need to save out the children // + for( auto& kid : mChildren ) { + _toXml( svgXmlNode, kid ); + } + + if( mSvgCss.classes.size() > 0 && cssNode) { + if( auto vattr = cssNode.appendAttribute("type")) { + vattr.set("text/css"); + } + cssNode.set(mSvgCss.toString(false)); + } else { + svgXmlNode.removeChild(cssNode); + } + +// ofLogNotice("Parser::CSS") << mSvgCss.toString(); + + return svgXml.save(apath); } -ofPath & ofxSvg::getPathAt(int n) { - return paths[n]; + +//-------------------------------------------------------------- +void ofxSvg::clear() { + mChildren.clear(); + mDefElements.clear(); + mCurrentLayer = 0; + mCurrentSvgCss.reset(); + mSvgCss.clear(); + mCPoints.clear(); + mCenterPoints.clear(); + + mCurrentCss.clear(); + + mGroupStack.clear(); + mModelMatrix = glm::mat4(1.f); + mModelMatrixStack = std::stack(); + + mPaths.clear(); } -void ofxSvg::load(const of::filesystem::path & fileName) { - of::filesystem::path file = ofToDataPath(fileName); - if (!of::filesystem::exists(file)) { - ofLogError("ofxSVG") << "load(): path does not exist: " << file ; - return; +//-------------------------------------------------------------- +const int ofxSvg::getTotalLayers(){ + return mCurrentLayer; +} + +//-------------------------------------------------------------- +void ofxSvg::recalculateLayers() { + mCurrentLayer = 0; + auto allKids = getAllChildren(true); + for( auto& kid : allKids ) { + kid->layer = mCurrentLayer += 1.0; } +} - ofBuffer buffer = ofBufferFromFile(fileName); - loadFromString(buffer.getText(), file.string()); +// including these for legacy considerations // +//-------------------------------------------------------------- +int ofxSvg::getNumPath() { + if( mPaths.size() < 1 ) { + getPaths(); + } + return mPaths.size(); } -void ofxSvg::loadFromString(std::string stringdata, std::string urlstring) { +//-------------------------------------------------------------- +ofPath& ofxSvg::getPathAt(int n) { + if( mPaths.size() < 1 ) { + getPaths(); + } + if( n < 0 || n >= mPaths.size() ) { + ofLogWarning(moduleName()) << "getPathAt: " << n << " out of bounds for number of paths: " << mPaths.size(); + return sDummyPath; + } + return mPaths[n]; +} - // goes some way to improving SVG compatibility - fixSvgString(stringdata); +//-------------------------------------------------------------- +const std::vector& ofxSvg::getPaths() const { +// auto spaths = const_cast(this)->getAllElementsForType(); +// std::size_t num = spaths.size(); + if( mPaths.size() < 1 ) { + // previous ofxSvg also included, circles, ellipses, rects, paths, etc. + auto spaths = const_cast(this)->getAllElementsWithPath(); + std::size_t num = spaths.size(); + mPaths.resize(num); + for( std::size_t i = 0; i < num; i++ ) { + mPaths[i] = spaths[i]->path; + } + } + return mPaths; +} - const char * data = stringdata.c_str(); - int size = stringdata.size(); - const char * url = urlstring.c_str(); +//-------------------------------------------------------------- +void ofxSvg::setFontsDirectory( string aDir ) { + fontsDirectory = aDir; + if( fontsDirectory.back() != '/' ) { + fontsDirectory += '/'; + } +} - struct svgtiny_diagram * diagram = svgtiny_create(); - // Switch to "C" locale as svgtiny expect it to parse floating points (issue 6644) - std::locale prev_locale = std::locale::global(std::locale::classic()); - svgtiny_code code = svgtiny_parse(diagram, data, size, url, 0, 0); - // Restore locale - std::locale::global(prev_locale); +//-------------------------------------------------------------- +string ofxSvg::toString(int nlevel) { + string tstr = ""; + if( mChildren.size() ) { + for( std::size_t i = 0; i < mChildren.size(); i++ ) { + tstr += mChildren[i]->toString( nlevel ); + } + } + return tstr; +} - if (code != svgtiny_OK) { - std::string msg; - switch (code) { - case svgtiny_OUT_OF_MEMORY: - msg = "svgtiny_OUT_OF_MEMORY"; - break; +//-------------------------------------------------------------- +void ofxSvg::validateXmlSvgRoot( ofXml& aRootSvgNode ) { + // if there is no width and height set in the svg base node, svg tiny no likey // + if(aRootSvgNode) { + // check for x, y, width and height // + { + auto xattr = aRootSvgNode.getAttribute("x"); + if( !xattr ) { + auto nxattr = aRootSvgNode.appendAttribute("x"); + if(nxattr) nxattr.set("0px"); + } + } + { + auto yattr = aRootSvgNode.getAttribute("y"); + if( !yattr ) { + auto yattr = aRootSvgNode.appendAttribute("y"); + if( yattr ) yattr.set("0px"); + } + } + + ofXml::Attribute viewBoxAttr = aRootSvgNode.getAttribute("viewBox"); + + ofRectangle vrect; + if( viewBoxAttr ) { + string tboxstr = viewBoxAttr.getValue(); + vector< string > tvals = ofSplitString( tboxstr, " " ); + if( tvals.size() == 4 ) { + vrect.x = ofToFloat( cleanString( tvals[0], "px") ); + vrect.y = ofToFloat( cleanString( tvals[1], "px") ); + vrect.width = ofToFloat( cleanString( tvals[2], "px") ); + vrect.height = ofToFloat( cleanString( tvals[3], "px") ); + } + } + + auto wattr = aRootSvgNode.getAttribute("width"); + auto hattr = aRootSvgNode.getAttribute("height"); + + if( !wattr || !hattr ) { +// ofXml::Attribute viewBoxAttr = aRootSvgNode.getAttribute("viewBox"); + if( vrect.getWidth() > 0.0f && vrect.getHeight() > 0.0f ) { +// string tboxstr = viewBoxAttr.getValue(); +// vector< string > tvals = ofSplitString( tboxstr, " " ); +// if( tvals.size() >= 4 ) { + if( !wattr ) { + auto nwattr = aRootSvgNode.appendAttribute("width"); + if(nwattr) nwattr.set( ofToString(vrect.getWidth())+"px" ); + } + + if( !hattr ) { + auto nhattr = aRootSvgNode.appendAttribute("height"); + if(nhattr) nhattr.set( ofToString(vrect.getHeight())+"px" ); + } +// } + } + } + // from previous version of ofxSvg + // Affinity Designer does not set width/height as pixels but as a percentage + // and relies on the "viewBox" to convey the size of things. this applies the + // viewBox to the width and height. + if( vrect.getWidth() > 0.0f ) { + if(wattr) { + if( ofIsStringInString( wattr.getValue(), "%")) { + float wpct = ofToFloat( cleanString(wattr.getValue(), "%" )) / 100.0f; + wattr.set( ofToString(wpct * vrect.getWidth())+"px" ); + } + } + } + if( vrect.getHeight() > 0.0f ) { + if(hattr) { + if( ofIsStringInString( hattr.getValue(), "%")) { + float hpct = ofToFloat( cleanString(hattr.getValue(), "%" )) / 100.0f; + hattr.set( ofToString(hpct * vrect.getHeight())+"px" ); + } + } + } + } +} - /*case svgtiny_LIBXML_ERROR: - msg = "svgtiny_LIBXML_ERROR"; - break;*/ +//-------------------------------------------------------------- +string ofxSvg::cleanString( string aStr, string aReplace ) { + ofStringReplace( aStr, aReplace, ""); + return aStr; +} - case svgtiny_NOT_SVG: - msg = "svgtiny_NOT_SVG"; - break; +//-------------------------------------------------------------- +void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr >& aElements ) { + + auto kids = aParentNode.getChildren(); + for( auto& kid : kids ) { + if( kid.getName() == "g" ) { + auto fkid = kid.getFirstChild(); + if( fkid ) { + mCurrentSvgCss.reset(); + auto tgroup = std::make_shared(); + tgroup->layer = mCurrentLayer += 1.0; + auto idattr = kid.getAttribute("id"); + if( idattr ) { + tgroup->name = idattr.getValue(); + } + + mCurrentSvgCss = std::make_shared( _parseStyle(kid) ); + + aElements.push_back( tgroup ); + _parseXmlNode( kid, tgroup->getChildren() ); + } + } else if( kid.getName() == "defs") { + ofLogVerbose(moduleName()) << __FUNCTION__ << " found a defs node."; + _parseXmlNode(kid, mDefElements ); + } else { + + bool bAddOk = _addElementFromXmlNode( kid, aElements ); +// cout << "----------------------------------" << endl; +// cout << kid.getName() << " kid: " << kid.getAttribute("id").getValue() << " out xml: " << txml.toString() << endl; + } + } +} - case svgtiny_SVG_ERROR: - msg = "svgtiny_SVG_ERROR: line " + ofToString(diagram->error_line) + ": " + diagram->error_message; - break; +//-------------------------------------------------------------- +bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr >& aElements ) { + shared_ptr telement; + + if( tnode.getName() == "use") { + if( auto hrefAtt = tnode.getAttribute("xlink:href")) { + ofLogVerbose(moduleName()) << "found a use node with href " << hrefAtt.getValue(); + std::string href = hrefAtt.getValue(); + if( href.size() > 1 && href[0] == '#' ) { + // try to find by id + href = href.substr(1, std::string::npos); + ofLogVerbose(moduleName()) << "going to look for href " << href; + for( auto & def : mDefElements ) { + if( def->name == href ) { + if( def->getType() == ofx::svg::TYPE_RECTANGLE ) { + auto drect = std::dynamic_pointer_cast(def); + auto nrect = std::make_shared( *drect ); + telement = nrect; + } else if( def->getType() == ofx::svg::TYPE_IMAGE ) { + auto dimg = std::dynamic_pointer_cast(def); + auto nimg = std::make_shared( *dimg ); + ofLogVerbose(moduleName()) << "created an image node with filepath: " << nimg->getFilePath(); + telement = nimg; + } else if( def->getType() == ofx::svg::TYPE_ELLIPSE ) { + auto dell= std::dynamic_pointer_cast(def); + auto nell = std::make_shared( *dell ); + telement = nell; + } else if( def->getType() == ofx::svg::TYPE_CIRCLE ) { + auto dcir= std::dynamic_pointer_cast(def); + auto ncir = std::make_shared( *dcir ); + telement = ncir; + } else if( def->getType() == ofx::svg::TYPE_PATH ) { + auto dpat= std::dynamic_pointer_cast(def); + auto npat = std::make_shared( *dpat ); + telement = npat; + } else if( def->getType() == ofx::svg::TYPE_TEXT ) { + auto dtex = std::dynamic_pointer_cast(def); + auto ntex = std::make_shared( *dtex ); + telement = ntex; + } else { + ofLogWarning("Parser") << "could not find type for def : " << def->name; + } + break; + } + } + } else { + ofLogWarning(moduleName()) << "could not parse use node with href : " << href; + } + } else { + ofLogWarning(moduleName()) << "found a use node but no href!"; + } + } else if( tnode.getName() == "image" ) { + auto image = std::make_shared(); + auto wattr = tnode.getAttribute("width"); + if(wattr) image->width = wattr.getFloatValue(); + auto hattr = tnode.getAttribute("height"); + if(hattr) image->height = hattr.getFloatValue(); + auto xlinkAttr = tnode.getAttribute("xlink:href"); + if( xlinkAttr ) { + image->filepath = folderPath+xlinkAttr.getValue(); + } + telement = image; + + } else if( tnode.getName() == "ellipse" ) { + auto ellipse = std::make_shared(); + auto cxAttr = tnode.getAttribute("cx"); + if(cxAttr) ellipse->pos.x = cxAttr.getFloatValue(); + auto cyAttr = tnode.getAttribute("cy"); + if(cyAttr) ellipse->pos.y = cyAttr.getFloatValue(); + + auto rxAttr = tnode.getAttribute( "rx" ); + if(rxAttr) ellipse->radiusX = rxAttr.getFloatValue(); + auto ryAttr = tnode.getAttribute( "ry" ); + if(ryAttr) ellipse->radiusY = ryAttr.getFloatValue(); + + ellipse->path.setCircleResolution(mCircleResolution); + ellipse->path.setCurveResolution(mCurveResolution); + // make local so we can apply transform later in the function + ellipse->path.ellipse({0.f,0.f}, ellipse->radiusX * 2.0f, ellipse->radiusY * 2.0f ); + + _applyStyleToPath( tnode, ellipse ); + + telement = ellipse; + } else if( tnode.getName() == "circle" ) { + auto circle = std::make_shared(); + auto cxAttr = tnode.getAttribute("cx"); + if(cxAttr) circle->pos.x = cxAttr.getFloatValue(); + auto cyAttr = tnode.getAttribute("cy"); + if(cyAttr) circle->pos.y = cyAttr.getFloatValue(); + + auto rAttr = tnode.getAttribute( "r" ); + if(rAttr) circle->radius = rAttr.getFloatValue(); + + // make local so we can apply transform later in the function + // position is from the top left + circle->path.setCircleResolution(mCircleResolution); + circle->path.setCurveResolution(mCurveResolution); + circle->path.circle({0.f,0.f}, circle->radius ); + + _applyStyleToPath( tnode, circle ); + + telement = circle; + + } else if( tnode.getName() == "line" ) { + auto telePath = std::make_shared(); + + glm::vec3 p1 = {0.f, 0.f, 0.f}; + glm::vec3 p2 = {0.f, 0.f, 0.f}; + auto x1Attr = tnode.getAttribute("x1"); + if(x1Attr) p1.x = x1Attr.getFloatValue(); + auto y1Attr = tnode.getAttribute("y1"); + if(y1Attr) p1.y = y1Attr.getFloatValue(); + + auto x2Attr = tnode.getAttribute("x2"); + if(x2Attr) p2.x = x2Attr.getFloatValue(); + auto y2Attr = tnode.getAttribute("y2"); + if(y2Attr) p2.y = y2Attr.getFloatValue(); + + // set the colors and stroke width, etc. + telePath->path.clear(); + telePath->path.moveTo(p1); + telePath->path.lineTo(p2); + + _applyStyleToPath( tnode, telePath ); + + telement = telePath; + + } else if(tnode.getName() == "polyline" || tnode.getName() == "polygon") { + auto tpath = std::make_shared(); + _parsePolylinePolygon(tnode, tpath); + _applyStyleToPath( tnode, tpath ); + telement = tpath; + } else if( tnode.getName() == "path" ) { + auto tpath = std::make_shared(); + _parsePath( tnode, tpath ); + _applyStyleToPath( tnode, tpath ); + telement = tpath; + } else if( tnode.getName() == "rect" ) { + auto rect = std::make_shared(); + auto xattr = tnode.getAttribute("x"); + if(xattr) rect->rectangle.x = xattr.getFloatValue(); + auto yattr = tnode.getAttribute("y"); + if(yattr) rect->rectangle.y = yattr.getFloatValue(); + auto wattr = tnode.getAttribute("width"); + if(wattr) rect->rectangle.width = wattr.getFloatValue(); + auto hattr = tnode.getAttribute("height"); + if(hattr) rect->rectangle.height = hattr.getFloatValue(); + rect->pos.x = rect->rectangle.x; + rect->pos.y = rect->rectangle.y; + + auto rxAttr = tnode.getAttribute("rx"); + auto ryAttr = tnode.getAttribute("ry"); + + rect->path.setCircleResolution(mCircleResolution); + rect->path.setCurveResolution(mCurveResolution); + + // make local so we can apply transform later in the function + if( !CssClass::sIsNone(rxAttr.getValue()) || !CssClass::sIsNone(ryAttr.getValue())) { + + rect->round = std::max(CssClass::sGetFloat(rxAttr.getValue()), + CssClass::sGetFloat(ryAttr.getValue())); + + rect->path.rectRounded(0.f, 0.f, rect->rectangle.getWidth(), rect->rectangle.getHeight(), + rect->round + ); + + } else { + rect->path.rectangle(0.f, 0.f, rect->getWidth(), rect->getHeight()); + } + + telement = rect; + + _applyStyleToPath( tnode, rect ); + + // this shouldn't be drawn at all, may be a rect that for some reason is generated + // by text blocks // + if( !rect->isFilled() && !rect->hasStroke() ) { + telement->setVisible(false); + } + + } else if( tnode.getName() == "text" ) { + auto text = std::make_shared(); + telement = text; +// std::cout << "has kids: " << tnode.getFirstChild() << " node value: " << tnode.getValue() << std::endl; + if( tnode.getFirstChild() ) { + + auto kids = tnode.getChildren(); + for( auto& kid : kids ) { + if(kid) { + if( kid.getName() == "tspan" ) { + text->textSpans.push_back( _getTextSpanFromXmlNode( kid ) ); + } + } + } + + // this may not be a text block or it may have no text // + if( text->textSpans.size() == 0 ) { + text->textSpans.push_back( _getTextSpanFromXmlNode( tnode ) ); + } + } + + string tempFolderPath = folderPath; + if( tempFolderPath.back() != '/' ) { + tempFolderPath += '/'; + } + if( ofDirectory::doesDirectoryExist( tempFolderPath+"fonts/" )) { + text->setFontDirectory( tempFolderPath+"fonts/" ); + } + if( fontsDirectory != "" ) { + if( ofDirectory::doesDirectoryExist(fontsDirectory)) { + text->setFontDirectory( fontsDirectory ); + } + } + + } else if( tnode.getName() == "g" ) { + + } + + if( !telement ) { + return false; + } + + auto idAttr = tnode.getAttribute("id"); + if( idAttr ) { + telement->name = idAttr.getValue(); + } + + if( telement->getType() == TYPE_RECTANGLE || telement->getType() == TYPE_IMAGE || telement->getType() == TYPE_TEXT || telement->getType() == TYPE_CIRCLE || telement->getType() == TYPE_ELLIPSE ) { + auto transAttr = tnode.getAttribute("transform"); + if( transAttr ) { +// getTransformFromSvgMatrix( transAttr.getValue(), telement->pos, telement->scale.x, telement->scale.y, telement->rotation ); + setTransformFromSvgMatrixString( transAttr.getValue(), telement ); + } + + std::vector typesToApplyTransformToPath = { + TYPE_RECTANGLE, + TYPE_CIRCLE, + TYPE_ELLIPSE + }; + + bool bApplyTransformToPath = false; + for( auto & etype : typesToApplyTransformToPath ) { + if( etype == telement->getType() ) { + bApplyTransformToPath = true; + break; + } + } + + if( bApplyTransformToPath ) { + auto epath = std::dynamic_pointer_cast( telement ); + auto outlines = epath->path.getOutline(); + auto transform = epath->getTransformMatrix(); + for( auto& outline : outlines ) { + for( auto& v : outline ) { + v = transform * glm::vec4(v, 1.0f); + } + } + // now we have new outlines, what do we do? + epath->path.clear(); + bool bFirstOne = true; + for( auto& outline : outlines ) { + for( auto& v : outline ) { + if(bFirstOne) { + bFirstOne = false; + epath->path.moveTo(v); + } else { + epath->path.lineTo(v); + } + } + if( outline.isClosed() ) { + epath->path.close(); + } + } + } + } + + if( telement->getType() == TYPE_TEXT ) { + auto text = std::dynamic_pointer_cast( telement ); + text->ogPos = text->pos; + text->create(); + } + + _applyStyleToElement(tnode, telement); + + telement->layer = mCurrentLayer += 1.0; + aElements.push_back( telement ); + return true; +} - default: - msg = "unknown svgtiny_code " + ofToString(code); - break; +std::vector parsePoints(const std::string& input) { + std::vector points; + std::regex regex("[-]?\\d*\\.?\\d+"); // Matches positive/negative floats + std::sregex_iterator begin(input.begin(), input.end(), regex), end; + + std::vector values; + + // Extract all floating-point values using regex + for (std::sregex_iterator i = begin; i != end; ++i) { + try { + values.push_back(std::stof((*i).str())); + } catch (const std::invalid_argument&) { + std::cerr << "Invalid number found: " << (*i).str() << std::endl; + } + } + + // Create vec2 pairs from the values + for (size_t i = 0; i < values.size(); i += 2) { + if (i + 1 < values.size()) { + glm::vec3 point(values[i], values[i + 1], 0.f); + points.push_back(point); } - ofLogError("ofxSVG") << "load(): couldn't parse \"" << urlstring << "\": " << msg; } + + if( values.size() == 1 && points.size() < 1) { + glm::vec3 point(values[0], values[0], 0.f); + points.push_back(point); + } + + return points; +} - setupDiagram(diagram); - svgtiny_free(diagram); +//---------------------------------------------------- +std::vector _parseSvgArc(const std::string& arcStr) { + std::vector result; + std::regex numberRegex(R"([-+]?[0-9]*\.?[0-9]+)"); // Improved regex for better compatibility + std::sregex_iterator iter(arcStr.begin(), arcStr.end(), numberRegex); + std::sregex_iterator end; + + while (iter != end) { + try { + result.push_back(std::stod(iter->str())); + } catch (const std::exception& e) { + std::cerr << "Error parsing number: " << iter->str() << " - " << e.what() << std::endl; + } + ++iter; + } + + return result; } -void ofxSvg::fixSvgString(std::string & xmlstring) { +//---------------------------------------------------- +int _getWindingOrderOnArc( glm::vec3& aStartPos, glm::vec3& aCenterPos, glm::vec3& aendPos ) { + glm::vec3 sdiff = (aStartPos - aCenterPos); + if( glm::length2(sdiff) > 0.0f ) { + sdiff = glm::normalize(sdiff); + } + glm::vec3 ediff = (aendPos - aCenterPos); + if( glm::length2(ediff) > 0.0f ) { + ediff = glm::normalize(ediff); + } + float tcross = sdiff.x * ediff.y - sdiff.y * ediff.x; +// ofLogNotice("_getWindingOrderOnArc") << "tcross is " << tcross; + if( tcross > 0.0f ) { + // clockwise + return 1; + } else if( tcross < 0.0f ) { + // counter clockwise + return -1; + } + // co-linear + return 0; + +} - ofXml xml; +//---------------------------------------------------- +// Function to find the center of the elliptical arc from SVG arc parameters +glm::vec2 findArcCenter(glm::vec2 start, glm::vec2 end, double rx, double ry, double x_axis_rotation, bool large_arc_flag, bool sweep_flag) { + // Convert the rotation to radians + double phi = glm::radians(x_axis_rotation); + double cos_phi = cos(phi); + double sin_phi = sin(phi); + + // Step 1: Compute (x1', y1') - the coordinates of the start point in the transformed coordinate system + glm::vec2 diff = (start - end) / 2.0f; + glm::vec2 p1_prime(cos_phi * diff.x + sin_phi * diff.y, -sin_phi * diff.x + cos_phi * diff.y); + + // Step 2: Correct radii if necessary + double p1_prime_x_sq = p1_prime.x * p1_prime.x; + double p1_prime_y_sq = p1_prime.y * p1_prime.y; + double rx_sq = rx * rx; + double ry_sq = ry * ry; + double radii_check = p1_prime_x_sq / rx_sq + p1_prime_y_sq / ry_sq; + if (radii_check > 1) { + // Scale radii to ensure the arc can fit between the two points + double scale = std::sqrt(radii_check); + rx *= scale; + ry *= scale; + rx_sq = rx * rx; + ry_sq = ry * ry; + } + + // Step 3: Compute (cx', cy') - the center point in the transformed coordinate system + double factor_numerator = rx_sq * ry_sq - rx_sq * p1_prime_y_sq - ry_sq * p1_prime_x_sq; + double factor_denominator = rx_sq * p1_prime_y_sq + ry_sq * p1_prime_x_sq; + if (factor_numerator < 0) { + factor_numerator = 0; // Precision error correction to avoid sqrt of negative numbers + } + + double factor = std::sqrt(factor_numerator / factor_denominator); + if (large_arc_flag == sweep_flag) { + factor = -factor; + } + + glm::vec2 center_prime(factor * rx * p1_prime.y / ry, factor * -ry * p1_prime.x / rx); + + // Step 4: Compute the center point in the original coordinate system + glm::vec2 center( + cos_phi * center_prime.x - sin_phi * center_prime.y + (start.x + end.x) / 2.0, + sin_phi * center_prime.x + cos_phi * center_prime.y + (start.y + end.y) / 2.0 + ); + + return center; +} - 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 = std::fmax(1.0, std::round(strokewidth)); - element.getAttribute("stroke-width").set(strokewidth); +//-------------------------------------------------------------- +void ofxSvg::_parsePolylinePolygon( ofXml& tnode, std::shared_ptr aSvgPath ) { + auto pointsAttr = tnode.getAttribute("points"); + if( !pointsAttr ) { + ofLogWarning(moduleName()) << __FUNCTION__ << " polyline or polygon does not have a points attriubute."; + return; + } + + if( pointsAttr.getValue().empty() ) { + ofLogWarning(moduleName()) << __FUNCTION__ << " polyline or polygon does not have points."; + return; + } + + auto points = parsePoints(pointsAttr.getValue()); + std::size_t numPoints = points.size(); + for( std::size_t i = 0; i < numPoints; i++ ) { + if( i == 0 ) { + aSvgPath->path.moveTo(points[i]); + } else { + aSvgPath->path.lineTo(points[i]); } } + if( numPoints > 2 ) { + if(tnode.getName() == "polygon" ) { + aSvgPath->path.close(); + } + } +} +// reference: https://www.w3.org/TR/SVG2/paths.html#PathData +//-------------------------------------------------------------- +void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { + aSvgPath->path.clear(); + + auto dattr = tnode.getAttribute("d"); + if( !dattr ) { + ofLogWarning(moduleName()) << __FUNCTION__ << " path node does not have d attriubute."; + return; + } + + aSvgPath->path.setCircleResolution(mCircleResolution); + aSvgPath->path.setCurveResolution(mCurveResolution); + + std::vector splitChars = { + 'M', 'm', // move to + 'V', 'v', // vertical line + 'H', 'h', // horizontal line + 'L','l', // line + 'z','Z', // close path + 'c','C','s','S', // cubic bézier + 'Q', 'q', 'T', 't', // quadratic bézier + 'A', 'a' // elliptical arc + }; + std::string ostring = dattr.getValue(); +// ofLogNotice(moduleName()) << __FUNCTION__ << " dattr: " << ostring; + + if( ostring.empty() ) { + ofLogError(moduleName()) << __FUNCTION__ << " there is no data in the d string."; + return; + } + + std::size_t index = 0; + if( ostring[index] != 'm' && ostring[index] != 'M' ) { + ofLogWarning(moduleName()) << __FUNCTION__ << " first char is not a m or M, ostring[index]: " << ostring[index]; + return; + } + + glm::vec3 currentPos = {0.f, 0.f, 0.f}; + glm::vec3 secondControlPoint = currentPos; + glm::vec3 qControlPoint = currentPos; + + auto convertToAbsolute = [](bool aBRelative, glm::vec3& aCurrentPos, std::vector& aposes) -> glm::vec3 { + for(auto& apos : aposes ) { + if( aBRelative ) { + apos += aCurrentPos; + } + } + if( aposes.size() > 0 ) { + aCurrentPos = aposes.back(); + } + return aCurrentPos; + }; + + auto convertToAbsolute2 = [](bool aBRelative, glm::vec3& aCurrentPos, std::vector& aposes) -> glm::vec3 { + for( std::size_t k = 0; k < aposes.size(); k+= 1 ) { + if( aBRelative ) { + aposes[k] += aCurrentPos; + } + if( k > 0 && k % 2 == 1 ) { + aCurrentPos = aposes[k]; + } + } + return aCurrentPos; + }; + + auto convertToAbsolute3 = [](bool aBRelative, glm::vec3& aCurrentPos, std::vector& aposes) -> glm::vec3 { + for( std::size_t k = 0; k < aposes.size(); k+= 1 ) { + if( aBRelative ) { + aposes[k] += aCurrentPos; + } + + if( k > 0 && k % 3 == 2 ) { + aCurrentPos = aposes[k]; + } + + } + return aCurrentPos; + }; + + + + aSvgPath->path.clear(); + + unsigned int justInCase = 0; +// std::vector commands; + bool breakMe = false; + while( index < ostring.size() && !breakMe && justInCase < 999999) { + // figure out what we have here . + auto cchar = ostring[index]; + // check for valid character // + bool bFoundValidChar = false; + for( auto& sc : splitChars ) { + if( sc == cchar ) { + bFoundValidChar = true; + break; + } + } + if( !bFoundValidChar ) { + ofLogWarning("svgParser") << "Did not find valid character: " << cchar; + breakMe = true; + break; + } + +// ofLogNotice(moduleName()) << " o : ["<< ostring[index] <<"]"; + + // up to next valid character // + std::string currentString; + bool bFoundValidNextChar = false; + auto pos = index+1; + if( pos >= ostring.size() ) { + ofLogVerbose("svgParser") << "pos is greater than string size: " << pos << " / " << ostring.size(); +// break; + breakMe = true; + } + + bFoundValidChar = false; + for( pos = index+1; pos < ostring.size(); pos++ ) { + for( auto& sc : splitChars ) { + if( sc == ostring[pos] ) { + bFoundValidChar = true; + break; + } + } + if( bFoundValidChar ) { + break; + } + currentString.push_back(ostring[pos]); + } + + index += currentString.size()+1; + + + if( currentString.empty() ) { + ofLogVerbose("svgParser") << "currentString is empty: " << cchar; +// break; + } + + + ofLogVerbose(moduleName()) << "["< npositions= {glm::vec3(0.f, 0.f, 0.f)}; + std::optional ctype; + + // check if we are looking for a position + if( cchar == 'm' || cchar == 'M' ) { + if( cchar == 'm' ) { + bRelative = true; + } + npositions = parsePoints(currentString); + ctype = ofPath::Command::moveTo; + } else if( cchar == 'v' || cchar == 'V' ) { + if( cchar == 'v' ) { + bRelative = true; + npositions[0].x = 0.f; + } else { + npositions[0].x = currentPos.x; + } + + npositions[0].y = ofToFloat(currentString); + ctype = ofPath::Command::lineTo; + } else if( cchar == 'H' || cchar == 'h' ) { + if( cchar == 'h' ) { + bRelative = true; + npositions[0].y = 0.f; + } else { + npositions[0].y = currentPos.y; + } + npositions[0].x = ofToFloat(currentString); + + ctype = ofPath::Command::lineTo; + } else if( cchar == 'L' || cchar == 'l' ) { + if( cchar == 'l' ) { + bRelative = true; + } + npositions = parsePoints(currentString); +// for( auto& np : npositions ) { +// ofLogNotice(moduleName()) << cchar << " line to: " << np; +// } + ctype = ofPath::Command::lineTo; + } else if( cchar == 'z' || cchar == 'Z' ) { + ctype = ofPath::Command::close; + npositions.clear(); + } else if( cchar == 'c' || cchar == 'C' || cchar == 'S' || cchar == 's' ) { + if( cchar == 'c' || cchar == 's') { + bRelative = true; + } + ctype = ofPath::Command::bezierTo; + npositions = parsePoints(currentString); +// for( auto& np : npositions ) { +// ofLogNotice(moduleName()) << cchar << " bezier to: " << np; +// } + } else if( cchar == 'Q' || cchar == 'q' || cchar == 'T' || cchar == 't' ) { + if( cchar == 'q' ) { + bRelative = true; + } + + ctype = ofPath::Command::quadBezierTo; + npositions = parsePoints(currentString); + +// for( auto& np : npositions ) { +// ofLogNotice(moduleName()) << " Quad bezier to: " << np; +// } + } else if(cchar == 'a' || cchar == 'A' ) { + if( cchar == 'a' ) { + bRelative = true; + } + ctype = ofPath::Command::arc; +// npositions = _parseStrCoordsFunc(currentString); + auto arcValues = _parseSvgArc(currentString); + if( arcValues.size() == 7 ) { + npositions = { + glm::vec3(arcValues[0], arcValues[1], 0.0f), + glm::vec3(arcValues[2], 0.0f, 0.0f), + glm::vec3(arcValues[3], arcValues[4], 0.0f), + glm::vec3(arcValues[5], arcValues[6], 0.0f) + }; + } else { + ofLogError(moduleName()) << "unable to parse arc command, incorrect number of parameters detected: " << arcValues.size(); + ofLogError(moduleName()) << "-- Arc values ---------------------- "; + for( std::size_t n = 0; n < arcValues.size(); n++ ) { + ofLogError(moduleName()) << n << ": " << arcValues[n]; + } + + } +// for( auto& np : npositions ) { +// ofLogNotice(moduleName()) << " arc parsed positions: " << np; +// } + } + + if( ctype.has_value() ) { + +// for( auto& np : npositions ) { +// ofLogNotice(moduleName()) << cchar << " position: " << np; +// } + + auto prevPos = currentPos; + + auto commandT = ctype.value(); + + if( commandT == ofPath::Command::arc ) { + if( npositions.size() == 4 ) { + std::vector tpositions = {npositions[3]}; + currentPos = convertToAbsolute(bRelative, currentPos, tpositions ); + npositions[3] = tpositions[0]; + } else { + ofLogWarning("ofx::svg::Parser") << "invalid number of arc commands."; + } + } else if( commandT == ofPath::Command::bezierTo ) { + if( cchar == 'S' || cchar == 's' ) { + currentPos = convertToAbsolute2(bRelative, currentPos, npositions ); + } else { + currentPos = convertToAbsolute3(bRelative, currentPos, npositions ); + } +// } else if( commandT == ofPath::Command::quadBezierTo ) { + // TODO: Check quad bezier for poly bezier like cubic bezier + + } else { +// if( commandT == ofPath::Command::moveTo ) { +// ofLogNotice("ofxSvgParser") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; +// } + if( npositions.size() > 0 && commandT != ofPath::Command::close ) { + currentPos = convertToAbsolute(bRelative, currentPos, npositions ); + } + } + +// if( npositions.size() > 0 ) { +// ofLogNotice("ofxSvgParser") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; +// } + + if( commandT != ofPath::Command::bezierTo ) { + secondControlPoint = currentPos; + } + if( commandT != ofPath::Command::quadBezierTo ) { + qControlPoint = currentPos; + } + + if( commandT == ofPath::Command::moveTo ) { + aSvgPath->path.moveTo(currentPos); + } else if( commandT == ofPath::Command::lineTo ) { + aSvgPath->path.lineTo(currentPos); + } else if( commandT == ofPath::Command::close ) { + aSvgPath->path.close(); + } else if( commandT == ofPath::Command::bezierTo ) { + + if( cchar == 'S' || cchar == 's' ) { + // these can come in as multiple sets of points // + std::vector ppositions;// = npositions; + auto tppos = prevPos; + for( std::size_t i = 0; i < npositions.size(); i += 2 ) { + auto cp2 = (secondControlPoint - tppos) * -1.f; + cp2 += tppos; + ppositions.push_back( cp2 ); + ppositions.push_back(npositions[i+0]); + ppositions.push_back(npositions[i+1]); + tppos = npositions[i+1]; + secondControlPoint = npositions[i+0]; + } + + npositions = ppositions; + +// if( npositions.size() == 2 ) { +// auto cp2 = (secondControlPoint - prevPos) * -1.f; +// cp2 += prevPos; +// npositions.insert(npositions.begin(), cp2 ); +// } + } + + auto tcpos = prevPos; + + for( std::size_t k = 0; k < npositions.size(); k +=3 ) { + aSvgPath->path.bezierTo(npositions[k+0], npositions[k+1], npositions[k+2]); + secondControlPoint = npositions[k+1]; + + mCPoints.push_back(prevPos); + mCPoints.push_back(npositions[k+0]); + mCPoints.push_back(npositions[k+1]); + tcpos = npositions[k+2]; + +// mCPoints.push_back(npositions[k+0]); +// mCPoints.push_back(npositions[k+1]); +// mCenterPoints.push_back(npositions[k+2]); + } + +// mCPoints.insert( mCPoints.end(), npositions.begin(), npositions.end() ); + +// if( npositions.size() == 3 ) { +// aSvgPath->path.bezierTo(npositions[0], npositions[1], npositions[2]); +// } +// +// secondControlPoint = npositions[1]; + } else if( commandT == ofPath::Command::quadBezierTo ) { + if( cchar == 'T' || cchar == 't' ) { + if( npositions.size() == 1 ) { + auto cp2 = (qControlPoint - prevPos) * -1.f; + cp2 += prevPos; + npositions.insert(npositions.begin(), cp2 ); + } + } + + if( npositions.size() == 2 ) { + aSvgPath->path.quadBezierTo(prevPos, npositions[0], npositions[1] ); + } + qControlPoint = npositions[0]; + } else if( commandT == ofPath::Command::arc ) { + if( npositions.size() == 4 ) { + // first point is rx, ry + // second point x value is x-axis rotation + // third point x value is large-arc-flag, y value is sweep-flag + // fourth point is x and y: When a relative a command is used, the end point of the arc is (cpx + x, cpy + y). + glm::vec3 radii = npositions[0]; + float xAxisRotation = npositions[1].x; + float largeArcFlag = std::clamp( npositions[2].x, 0.f, 1.f ); + float sweepFlag = std::clamp( npositions[2].y, 0.f, 1.f ); + + glm::vec3 spt = prevPos; + glm::vec3 ept = npositions[3]; + + +// glm::vec3 cpt(spt.x, ept.y, 0.0f); + auto cpt = glm::vec3(findArcCenter(spt, ept, radii.x, radii.y, xAxisRotation, largeArcFlag, sweepFlag ), 0.f); + auto windingOrder = _getWindingOrderOnArc( spt, cpt, ept ); + + if( largeArcFlag < 1 ) { + if( sweepFlag > 0 ) { + if( windingOrder < 0 ) { + windingOrder *= -1.f; + } + } else { + if( windingOrder > 0 ) { + windingOrder *= -1.f; + } + } + } else { + if( sweepFlag > 0 ) { + if( windingOrder < 1 ) { + windingOrder *= -1.f; + } + } else { + if( windingOrder > -1 ) { + windingOrder *= -1.f; + } else { + + } + } + } + + + auto startDiff = (spt - cpt); + if( glm::length2(startDiff) > 0.0f ) { + startDiff = glm::normalize(startDiff); + } else { + startDiff = glm::vec3(1.f, 0.f, 0.f ); + } + auto endDiff = (ept - cpt); + if( glm::length2(endDiff) > 0.0f ) { + endDiff = glm::normalize(endDiff); + } else { + endDiff = glm::vec3(1.f, 0.f, 0.f ); + } + + float startAngle = atan2f( startDiff.y, startDiff.x );// - glm::radians(40.f); + float endAngle = atan2f( endDiff.y, endDiff.x ); + + float xrotRad = glm::radians(xAxisRotation); + + startAngle = ofWrapRadians(startAngle); + endAngle = ofWrapRadians(endAngle); + + + std::string worderS = "co linear"; + if( windingOrder > 0 ) { + worderS = "clockwise"; + } else if( windingOrder < 0 ) { + worderS = "counter clockwise"; + } + + ofLogVerbose("Arc winding order is ") << worderS << " order: " << windingOrder << " startDiff: " << startDiff << " endDiff: " << endDiff << " xAxisRotation: " << xAxisRotation; + + ofPolyline tline; + + if( windingOrder < 0 ) { +// aSvgPath->path.arcNegative(cpt, radii.x, radii.y, glm::degrees(startAngle), glm::degrees(endAngle) ); + tline.arcNegative(cpt, radii.x, radii.y, glm::degrees(startAngle-xrotRad), glm::degrees(endAngle-xrotRad), mCircleResolution ); +// tline.arcNegative(cpt, radii.x, radii.y, glm::degrees(startAngle), glm::degrees(endAngle) ); +// aSvgPath->path.arcNegative(cpt, radii.x, radii.y, glm::degrees(startAngle-xrotRad), glm::degrees(endAngle-xrotRad) ); + } else { + tline.arc(cpt, radii.x, radii.y, glm::degrees(startAngle-xrotRad), glm::degrees(endAngle-xrotRad), mCircleResolution ); +// tline.arc(cpt, radii.x, radii.y, glm::degrees(startAngle), glm::degrees(endAngle) ); +// aSvgPath->path.arc(cpt, radii.x, radii.y, glm::degrees(startAngle-xrotRad), glm::degrees(endAngle-xrotRad) ); +// aSvgPath->path.arc(cpt, radii.x, radii.y, glm::degrees(startAngle), glm::degrees(endAngle) ); + } + + // rotate based on x-axis rotation // + +// aSvgPath->path.rotateRad(xrotRad, glm::vec3(0.0f, 0.0f, 1.f)); + + for( auto& pv : tline.getVertices() ) { + auto nv = pv - cpt; + if( glm::length2(nv) > 0.0f ) { + nv = glm::vec3( glm::rotate(glm::vec2(nv.x, nv.y), xrotRad), 0.f); + } + nv += cpt; + pv.x = nv.x; + pv.y = nv.y; + } +//// +// // I guess we have to copy the line via commands + if( tline.size() > 0 ) { +// aSvgPath->path.moveTo(spt); + for( std::size_t i = 0; i < tline.size(); i++ ) { +// if( i == 0 ) { +// aSvgPath->path.moveTo(tline[0]); +// } else { + aSvgPath->path.lineTo(tline[i]); +// } + } + } + +// auto centers = findEllipseCenter( spt, ept, radii.x, radii.y ); + +// ofLogNotice("centers: ") << std::get<0>(centers) << " and " << std::get<1>(centers) << " spt: " << spt << " ept: " << ept << " radii: " << radii; + + + mCenterPoints.push_back(cpt); +// mCenterPoints.push_back(cpt); + npositions.clear(); + npositions.push_back(ept); + } else { + ofLogWarning("ofx::svg::Parser") << "unable to parse arc segment."; + } + } + +// mCenterPoints.push_back(currentPos); +// mCPoints.insert( mCPoints.end(), npositions.begin(), npositions.end() ); + } + +// ofLogNotice(moduleName()) << "["<properties ) { + css.addProperty(tprop.first, tprop.second); + } + } + + // now apply all of the other via css classes // + // now lets figure out if there is any css applied // + if( auto classAttr = anode.getAttribute("class") ) { + // get a list of classes, is this separated by commas? + auto classList = ofSplitString(classAttr.getValue(), ","); +// ofLogNotice("ofx::svg::Parser") << " going to try and parse style classes string: " << classAttr.getValue(); + for( auto& className : classList ) { + if( mSvgCss.hasClass(className) ) { +// ofLogNotice("ofx::svg::Parser") << " has class " << className; + // now lets try to apply it to the path + auto& tCss = mSvgCss.getClass(className); + for( auto& tprop : tCss.properties ) { + css.addProperty(tprop.first, tprop.second); + } + } + } + } + + // locally set on node overrides the class listing + // are there any properties on the node? + if( auto fillAttr = anode.getAttribute("fill")) { + css.addProperty("fill", fillAttr.getValue()); + } + if( auto strokeAttr = anode.getAttribute("stroke")) { + css.addProperty("stroke", strokeAttr.getValue()); + } + + if( auto strokeWidthAttr = anode.getAttribute("stroke-width")) { + css.addProperty("stroke-width", strokeWidthAttr.getValue()); + } + + if( auto ffattr = anode.getAttribute("font-family") ) { + std::string tFontFam = ffattr.getValue(); + ofStringReplace( tFontFam, "'", "" ); + css.addProperty("font-family", tFontFam); + } + + if( auto fsattr = anode.getAttribute("font-size") ) { + css.addProperty("font-size", fsattr.getValue() ); + } + + // and lastly style + if( auto styleAttr = anode.getAttribute("style") ) { + css.addProperties(styleAttr.getValue()); + } + + // override anything else if set directly on the node + if( auto disAttr = anode.getAttribute("display") ) { + css.addProperties(disAttr.getValue()); + } + + return css; +} + +//-------------------------------------------------------------- +void ofxSvg::_applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ) { + auto css = _parseStyle(tnode); +// ofLogNotice("_applyStyleToElement" ) << " " << aEle->name << " -----"; + if( css.hasAndIsNone("display")) { +// ofLogNotice("parser") << "setting element to invisible: " << aEle->name; + aEle->setVisible(false); + } +} - // Affinity Designer does not set width/height as pixels but as a percentage - // and relies on the "viewBox" to convey the size of things. this applies the - // viewBox to the width and height. +//-------------------------------------------------------------- +void ofxSvg::_applyStyleToPath( ofXml& tnode, std::shared_ptr aSvgPath ) { + auto css = _parseStyle(tnode); + _applyStyleToPath(css, aSvgPath); +} - std::vector rect; - for (ofXml & element : xml.find("//*[@viewBox]")) { - rect = ofSplitString(element.getAttribute("viewBox").getValue(), " "); +//-------------------------------------------------------------- +void ofxSvg::_applyStyleToPath( CssClass& aclass, std::shared_ptr aSvgPath ) { + // now lets figure out if there is any css applied // + + if( aclass.hasProperty("fill")) { + if( !aclass.isNone("fill")) { + aSvgPath->path.setFillColor(aclass.getColor("fill")); + } else { + aSvgPath->path.setFilled(false); + } + } else { +// aSvgPath->path.setFilled(false); + aSvgPath->path.setFillColor(ofColor(0)); + } + + if( !aclass.isNone("stroke") ) { + aSvgPath->path.setStrokeColor(aclass.getColor("stroke")); + } + + if( aclass.hasProperty("stroke-width")) { + if( aclass.isNone("stroke-width")) { + aSvgPath->path.setStrokeWidth(0.f); + } else { + aSvgPath->path.setStrokeWidth( aclass.getFloatValue("stroke-width", 0.f)); + } + } else { + // default with no value is 1.f +// aSvgPath->path.setStrokeWidth(1.f); } + + // if the color is not set and the width is not set, then it should be 0 + if( !aclass.isNone("stroke") ) { + if( !aclass.hasProperty("stroke-width")) { + aSvgPath->path.setStrokeWidth(1.f); + } + } +} - if (rect.size() == 4) { +//-------------------------------------------------------------- +void ofxSvg::_applyStyleToText( ofXml& anode, std::shared_ptr aTextSpan ) { + auto css = _parseStyle(anode); + _applyStyleToText(css, aTextSpan); +} + +//-------------------------------------------------------------- +void ofxSvg::_applyStyleToText( CssClass& aclass, std::shared_ptr aTextSpan ) { + // default font family + aTextSpan->fontFamily = aclass.getValue("font-family", "Arial"); + aTextSpan->fontSize = aclass.getIntValue("font-size", 18 ); + aTextSpan->color = aclass.getColor("fill"); +} + - for (ofXml & element : xml.find("//*[@width]")) { - if (element.getAttribute("width").getValue() == "100%") { - auto w = ofToFloat(rect.at(2)); - ofLogWarning("ofxSvg::fixSvgString()") << "the SVG size is provided as percentage, which svgtiny translates to 0. The width is corrected from the viewBox width: " << w; - element.getAttribute("width").set(w); + +//-------------------------------------------------------------- +glm::vec3 ofxSvg::_parseMatrixString(const std::string& input, const std::string& aprefix, bool abDefaultZero ) { +// std::string prefix = aprefix+"("; +// std::string suffix = ")"; + ofLogVerbose(moduleName()) << __FUNCTION__ << " input: " << input;; + std::string searchStr = aprefix + "("; + size_t startPos = input.find(searchStr); + + if (startPos != std::string::npos) { + startPos += searchStr.size(); + size_t endPos = input.find(")", startPos); + + if (endPos != std::string::npos) { + // Extract the part inside the parentheses + std::string inside = input.substr(startPos, endPos - startPos); + + // Ensure numbers like ".5" are correctly handled by adding a leading zero if needed + if (inside[0] == '.') { + inside = "0" + inside; + } + + float tx = 0.f, ty = 0.f, tz = 0.f; + std::stringstream ss(inside); + if (ss >> tx) { + if (!(ss >> ty)) { + if(abDefaultZero) { + ty = 0.0f; + } else { + ty = tx; // If only one value is provided, duplicate it + } + } + if (!(ss >> tz)) { + if( abDefaultZero) { + tz = 0.0f; + } else { + tz = ty; // If only two values are provided, duplicate the second one + } + } + return glm::vec3(tx, ty, tz); } } + } + return glm::vec3(0.f, 0.f, 0.0f); +} - for (ofXml & element : xml.find("//*[@height]")) { - if (element.getAttribute("height").getValue() == "100%") { - auto w = ofToFloat(rect.at(3)); - ofLogWarning("ofxSvg::fixSvgString()") << "the SVG size is provided as percentage, which svgtiny translates to 0. The height is corrected from the viewBox height: " << w; - element.getAttribute("height").set(w); +//-------------------------------------------------------------- +//bool Parser::getTransformFromSvgMatrix( string aStr, glm::vec2& apos, float& scaleX, float& scaleY, float& arotation ) { +bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr aele ) { + + aele->scale = glm::vec2(1.0f, 1.0f); + aele->rotation = 0.0; + aele->mModelRotationPoint = glm::vec2(0.0f, 0.0f); + //TODO: implement matrix push and pop structure, similar to renderers + ofLogVerbose(moduleName()) << __FUNCTION__ << " going to parse string: " << aStr << " pos: " << aele->pos; + + glm::mat4 mat = glm::mat4(1.f); + + if( ofIsStringInString(aStr, "translate")) { + auto transStr = aStr; + auto tp = _parseMatrixString(transStr, "translate", false ); + ofLogVerbose(moduleName()) << __FUNCTION__ << " translate: " << tp; +// apos += tp; + mat = glm::translate(glm::mat4(1.0f), glm::vec3(tp.x, tp.y, 0.0f)); + } else { + mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.f, 0.f, 0.0f)); + } + + if( ofIsStringInString(aStr, "rotate")) { + auto transStr = aStr; + auto tr = _parseMatrixString(transStr, "rotate", true ); + aele->rotation = tr.x; + if( aele->rotation != 0.f ) { + glm::vec2 rcenter(0.f, 0.f); + if( tr.y != 0.0f || tr.z != 0.0f ) { + rcenter.x = tr.y; + rcenter.y = tr.z; + + aele->mModelRotationPoint = rcenter; + + glm::vec3 pivot(rcenter.x, rcenter.y, 0.f); + // Step 1: Translate to pivot (move pivot to origin) + glm::mat4 toOrigin = glm::translate(glm::mat4(1.0f), -pivot ); + + // Step 2: Apply rotation + glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f) ); + + // Step 3: Translate back to original position + glm::mat4 backToPivot = glm::translate(glm::mat4(1.0f), pivot); + + // Apply transformations in the correct order: T_back * R * T_origin * Original_Transform + mat = backToPivot * rotation * toOrigin * mat; + } else { + mat = mat * glm::toMat4((const glm::quat&)glm::angleAxis(glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f))); } +// ofLogNotice("svg parser") << "rcenter: " << rcenter.x << ", " << rcenter.y; } + ofLogVerbose(moduleName()) << __FUNCTION__ << " arotation: " << aele->rotation; } + + if( ofIsStringInString(aStr, "scale")) { + auto transStr = aStr; + auto ts = _parseMatrixString(transStr, "scale", false ); + aele->scale.x = ts.x; + aele->scale.y = ts.y; + ofLogVerbose(moduleName()) << __FUNCTION__ << " scale: " << ts; + + mat = glm::scale(mat, glm::vec3(aele->scale.x, aele->scale.y, 1.f)); + } + + glm::vec3 pos3 = mat * glm::vec4( aele->pos.x, aele->pos.y, 0.0f, 1.f ); + aele->pos.x = pos3.x; + aele->pos.y = pos3.y; + + + if( ofIsStringInString(aStr, "matrix")) { + auto matrix = aStr; + ofStringReplace(matrix, "matrix(", ""); + ofStringReplace(matrix, ")", ""); + vector matrixNum = ofSplitString(matrix, " ", false, true); + vector matrixF; + for(std::size_t i = 0; i < matrixNum.size(); i++){ + matrixF.push_back(ofToFloat(matrixNum[i])); +// std::cout << " matrix[" << i << "] = " << matrixF[i] << " string version is " << matrixNum[i] << std::endl; + } + + if( matrixNum.size() == 6 ) { + + mat = glm::translate(glm::mat4(1.0f), glm::vec3(matrixF[4], matrixF[5], 0.0f)); + + aele->rotation = glm::degrees( atan2f(matrixF[1],matrixF[0]) ); + if( aele->rotation != 0.f ) { + mat = mat * glm::toMat4((const glm::quat&)glm::angleAxis(glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f))); + } + + aele->scale.x = glm::sqrt(matrixF[0] * matrixF[0] + matrixF[1] * matrixF[1]); + aele->scale.y = glm::sqrt(matrixF[2] * matrixF[2] + matrixF[3] * matrixF[3]); + + mat = glm::scale(mat, glm::vec3(aele->scale.x, aele->scale.y, 1.f)); + + pos3 = mat * glm::vec4( aele->pos.x, aele->pos.y, 0.0f, 1.f ); + + aele->pos.x = pos3.x; + aele->pos.y = pos3.y; + +// apos.x = matrixF[4]; +// apos.y = matrixF[5]; +// +// scaleX = std::sqrtf(matrixF[0] * matrixF[0] + matrixF[1] * matrixF[1]) * (float)ofSign(matrixF[0]); +// scaleY = std::sqrtf(matrixF[2] * matrixF[2] + matrixF[3] * matrixF[3]) * (float)ofSign(matrixF[3]); +// +// arotation = glm::degrees( std::atan2f(matrixF[2],matrixF[3]) ); +// if( scaleX < 0 && scaleY < 0 ){ +// +// }else{ +// arotation *= -1.0f; +// } + // cout << " rotation is " << arotation << endl; +// std::cout << "matrix rotation is " << arotation << " ScaleX: " << scaleX << " scaleY: " << scaleY << " apos: " << apos << std::endl; + +// return true; + } + } + return false; +} - //lib svgtiny doesn't remove elements with display = none, so this code fixes that +//-------------------------------------------------------------- +std::string ofxSvg::getSvgMatrixStringFromElement( std::shared_ptr aele ) { + // matrix(1 0 0 1 352.4516 349.0799)"> - bool finished = false; - while (!finished) { + // there's probably a better way to determine if this should be rotated in a certain way + if( aele->mModelRotationPoint.x != 0.0f || aele->mModelRotationPoint.y != 0.0f ) { + glm::vec2 rcenter(0.f, 0.f); + rcenter.x = aele->mModelRotationPoint.x; + rcenter.y = aele->mModelRotationPoint.y; + + std::ostringstream matrixStream; + matrixStream << std::fixed << std::setprecision(6) << "rotate(" << aele->rotation << " " << rcenter.x << " " << rcenter.y <<")"; + if( aele->scale.x != 1.f || aele->scale.y != 1.f ) { + matrixStream << " scale(" << aele->scale.x << " " << aele->scale.y <<")"; + } + return matrixStream.str(); + + } else { + + // Create the transformation matrix + glm::mat4 transform = glm::mat4(1.0f); // Identity matrix + // transform = glm::translate(transform, glm::vec3(aele->pos, 0.0f) ); + transform = glm::translate(transform, glm::vec3(aele->mModelPos, 0.0f) ); + + transform = glm::rotate(transform, glm::radians(aele->rotation), glm::vec3( 0.0f, 0.0f, 1.f)); + + + transform = glm::scale(transform, glm::vec3(aele->scale, 1.0f) ); + + // Extract the 2D matrix values (first two rows and three columns) + float a = transform[0][0]; // m00 + float b = transform[0][1]; // m01 + float c = transform[1][0]; // m10 + float d = transform[1][1]; // m11 + float e = transform[3][0]; // m20 (translation x) + float f = transform[3][1]; // m21 (translation y) + + // Create the SVG transform matrix string + std::ostringstream matrixStream; + matrixStream << std::fixed << std::setprecision(6) + << "matrix(" << a << " " << b << " " << c << " " << d << " " << e << " " << f << ")"; + return matrixStream.str(); + } + return ""; + +} - ofXml::Search invisibleElements = xml.find("//*[@display=\"none\"]"); +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::_getTextSpanFromXmlNode( ofXml& anode ) { + auto tspan = std::make_shared();; + + string tText = anode.getValue(); + float tx = 0; + auto txattr = anode.getAttribute("x"); + if( txattr) { + tx = txattr.getFloatValue(); + } + float ty = 0; + auto tyattr = anode.getAttribute("y"); + if( tyattr ) { + ty = tyattr.getFloatValue(); + } + + tspan->text = tText; + tspan->rect.x = tx; + tspan->rect.y = ty; + + _applyStyleToText(anode, tspan); + + return tspan; +} - if (invisibleElements.empty()) { - finished = true; - } else { - const ofXml & element = invisibleElements[0]; - ofXml parent = element.getParent(); - if (parent && element) parent.removeChild(element); - } +//-------------------------------------------------------------- +float ofxSvg::getWidth() const { + return mViewbox.getWidth(); +} + +//-------------------------------------------------------------- +float ofxSvg::getHeight() const { + return mViewbox.getHeight(); +} + +//-------------------------------------------------------------- +ofRectangle ofxSvg::getViewbox() const { + return mViewbox; +} + +//-------------------------------------------------------------- +float ofxSvg::getBoundsWidth() const { + return mBounds.getWidth(); +} + +//-------------------------------------------------------------- +float ofxSvg::getBoundsHeight() const { + return mBounds.getHeight(); +} + +//-------------------------------------------------------------- +ofRectangle ofxSvg::getBounds() const { + return mBounds; +} + +//-------------------------------------------------------------- +void ofxSvg::setWidth(float aw) { + mViewbox.width = aw; + if( mBounds.width < 1 ) { + mBounds.width = aw; } +} - // 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()) { +//-------------------------------------------------------------- +void ofxSvg::setHeight(float ah) { + mViewbox.height = ah; + if( mBounds.height < 1 ) { + mBounds.height = ah; + } +} - for (ofXml & element : useElements) { +//-------------------------------------------------------------- +void ofxSvg::setViewBox( const ofRectangle& arect ) { + mViewbox = arect; +} - // get the id attribute - string id = element.getAttribute("xlink:href").getValue(); - // remove the leading "#" from the id - id.erase(id.begin()); +//-------------------------------------------------------------- +void ofxSvg::setBoundsWidth( float aw ) { + mBounds.width = aw; +} - // find the original definition of that element - TODO add defs into path? - string searchstring = "//*[@id='" + id + "']"; - ofXml idelement = xml.findFirst(searchstring); +//-------------------------------------------------------------- +void ofxSvg::setBoundsHeight( float ah ) { + mBounds.height = ah; +} - // if we found one then use it! (find first returns an empty xml on failure) - if (idelement.getAttribute("id").getValue() != "") { +//-------------------------------------------------------------- +void ofxSvg::setBounds( const ofRectangle& arect ) { + mBounds = arect; +} - // make a copy of that element - element.appendChild(idelement); +//-------------------------------------------------------------- +void ofxSvg::pushGroup( const std::string& apath ) { + std::shared_ptr cgroup; + if( mGroupStack.size() > 0 ) { + mGroupStack.back()->get( apath ); + } else { + cgroup = get( apath ); + } + + if( cgroup ) { + pushGroup(cgroup); + } else { + ofLogWarning("ofx::svg::Parser") << "could not find group with path " << apath; + } +} - // then turn the use element into a g element - element.setName("g"); - } - } +//-------------------------------------------------------------- +void ofxSvg::pushGroup( const std::shared_ptr& agroup ) { + if( agroup ) { + mGroupStack.push_back(agroup); } +} + +//-------------------------------------------------------------- +void ofxSvg::popGroup() { + if( mGroupStack.size() > 0 ) { + mGroupStack.pop_back(); + } +} + +//-------------------------------------------------------------- +void ofxSvg::setFillColor(ofColor acolor) { + mFillColor = acolor; + mCurrentCss.setFillColor(acolor); +} + +//-------------------------------------------------------------- +void ofxSvg::setFilled(bool abFilled) { + if( abFilled ) { + mCurrentCss.setFillColor(mFillColor); + } else { + mCurrentCss.setNoFill(); + } +} + +//-------------------------------------------------------------- +void ofxSvg::setStrokeColor(ofColor acolor) { + mStrokeColor = acolor; + mCurrentCss.setStrokeColor(acolor); +} - xmlstring = xml.toString(); +//-------------------------------------------------------------- +void ofxSvg::setStrokeWidth(float aLineWidth) { + mCurrentCss.setStrokeWidth(aLineWidth); } -void ofxSvg::draw() { - for (int i = 0; i < (int)paths.size(); i++) { - paths[i].draw(); +//-------------------------------------------------------------- +void ofxSvg::setHasStroke(bool abStroke) { + if( abStroke ) { + mCurrentCss.setStrokeColor(mStrokeColor); + } else { + mCurrentCss.setNoStroke(); } } -void ofxSvg::setupDiagram(struct svgtiny_diagram * diagram) { +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addGroup(std::string aname) { + auto tgroup = std::make_shared(); + tgroup->name = aname; + _getPushedGroup()->add(tgroup); + recalculateLayers(); + return tgroup; +} - width = diagram->width; - height = diagram->height; +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::add( const ofPath& apath ) { + auto path = std::make_shared(); + path->path = apath; +// _config(path); + _applyModelMatrixToElement( path, glm::vec2(0.f, 0.f) ); + _applyStyleToPath( mCurrentCss, path ); + _getPushedGroup()->add(path); + recalculateLayers(); + mPaths.clear(); + return path; +} - paths.clear(); +//-------------------------------------------------------------- +std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apaths ) { + std::vector< std::shared_ptr > rpaths; + for( auto& path : apaths ) { + rpaths.push_back( add(path) ); + } + return rpaths; +} - for (int i = 0; i < (int)diagram->shape_count; i++) { - if (diagram->shape[i].path) { - paths.push_back(ofPath()); - setupShape(&diagram->shape[i], paths.back()); - } else if (diagram->shape[i].text) { - ofLogWarning("ofxSVG") << "setupDiagram(): text: not implemented yet"; +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::add( const ofPolyline& apoly ) { + if( apoly.size() < 2 ) { + return std::shared_ptr(); + } + + ofPath opath; + const auto& verts = apoly.getVertices(); + for( std::size_t i = 0; i < verts.size(); i++ ) { + if( i == 0 ) { + opath.moveTo(verts[i]); + } else { + opath.lineTo(verts[i]); } } + if( apoly.isClosed() ) { + opath.close(); + } + return add( opath ); +} + +//-------------------------------------------------------------- +std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apolys ) { + std::vector< std::shared_ptr > rpaths; + for( auto& poly : apolys ) { + rpaths.push_back( add(poly) ); + } + return rpaths; +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::add( const ofRectangle& arect ) { + return add( arect, 0.0f); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRadius ) { + auto rect = std::make_shared(); + rect->rectangle = arect; +// rect->pos = arect.getPosition(); + _applyModelMatrixToElement( rect, arect.getPosition() ); + rect->round = aRoundRadius; + rect->path.rectangle(arect); +// _config(rect); + _applyStyleToPath( mCurrentCss, rect ); + _getPushedGroup()->add(rect); + recalculateLayers(); + mPaths.clear(); + return rect; +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addCircle( float aradius ) { + return addCircle(glm::vec2(0.f, 0.f), aradius ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float aradius ) { + auto circle = std::make_shared(); +// circle->pos = apos; + _applyModelMatrixToElement( circle, apos ); + circle->radius = aradius; + circle->path.setCircleResolution(mCircleResolution); + circle->path.circle(apos, aradius); +// _config(circle); + _applyStyleToPath( mCurrentCss, circle ); + _getPushedGroup()->add(circle); + recalculateLayers(); + mPaths.clear(); + return circle; +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addCircle( const glm::vec3& apos, float aradius ) { + return addCircle( glm::vec2(apos.x, apos.y), aradius ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addCircle( const float& ax, const float& ay, float aradius ) { + return addCircle( glm::vec2(ax, ay), aradius ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addEllipse( float aradiusX, float aradiusY ) { + return addEllipse( glm::vec2(0.f, 0.f), aradiusX, aradiusY ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ) { + auto ellipse = std::make_shared(); + _applyModelMatrixToElement( ellipse, apos ); + + ellipse->radiusX = aradiusX; + ellipse->radiusY = aradiusY; + ellipse->path.setCircleResolution(mCircleResolution); + ellipse->path.ellipse(apos, aradiusX, aradiusY); + + _applyStyleToPath( mCurrentCss, ellipse ); + _getPushedGroup()->add(ellipse); + recalculateLayers(); + mPaths.clear(); + return ellipse; +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ) { + return addEllipse( glm::vec2(apos.x, apos.y), aradiusX, aradiusY ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ) { + return addEllipse( glm::vec2(ax, ay), aradiusX, aradiusY ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addImage( const of::filesystem::path& apath, const ofTexture& atex ) { + return addImage(glm::vec2(0.f, 0.f), apath, atex ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ) { + auto img = std::make_shared(); + img->filepath = apath; + img->width = atex.getWidth(); + img->height = atex.getHeight(); + _applyModelMatrixToElement( img, apos ); + _getPushedGroup()->add(img); + recalculateLayers(); + return img; +} + +//---------------------------------------------------------- +void ofxSvg::pushMatrix() { + mModelMatrixStack.push(mModelMatrix); +} + +//---------------------------------------------------------- +bool ofxSvg::popMatrix() { + if( !mModelMatrixStack.empty() ) { + mModelMatrix = mModelMatrixStack.top(); + mModelMatrixStack.pop(); + return true; + } else { + loadIdentityMatrix(); + } + return false; } -void ofxSvg::setupShape(struct svgtiny_shape * shape, ofPath & path) { - float * p = shape->path; +//---------------------------------------------------------- +void ofxSvg::translate(const glm::vec2 & p) { + translate(p.x, p.y); +} - path.setFilled(false); +//---------------------------------------------------------- +void ofxSvg::translate(float x, float y) { + mModelMatrix = glm::translate(mModelMatrix, glm::vec3(x, y, 0.0f)); +} + +//---------------------------------------------------------- +void ofxSvg::scale(float xAmnt, float yAmnt) { + mModelMatrix = glm::scale(mModelMatrix, glm::vec3(xAmnt, yAmnt, 1.f)); +} + +//---------------------------------------------------------- +void ofxSvg::rotateRadians(float aradians) { + mModelMatrix = glm::rotate(mModelMatrix, aradians, glm::vec3(0.f, 0.f, 1.f)); +} - if (shape->fill != svgtiny_TRANSPARENT) { - path.setFilled(true); - path.setFillHexColor(shape->fill); - path.setPolyWindingMode(OF_POLY_WINDING_NONZERO); +//---------------------------------------------------------- +void ofxSvg::rotateDegrees(float adegrees) { + rotateRadians( ofDegToRad(adegrees)); +} + +//---------------------------------------------------------- +void ofxSvg::multMatrix(const glm::mat4 & m) { + mModelMatrix = mModelMatrix * m; +} + +//---------------------------------------------------------- +void ofxSvg::loadMatrix(const glm::mat4 & m) { + mModelMatrix = m; +} + +//---------------------------------------------------------- +void ofxSvg::loadIdentityMatrix() { + mModelMatrix = glm::mat4(1.f); +} + + +//-------------------------------------------------------------- +void ofxSvg::drawDebug() { +// Group::draw(); + ofSetColor( ofColor::limeGreen ); + ofNoFill(); + +// int cindex = 0; +// for( auto& cp : mCPoints ) { +// ofSetColor( (float)(cindex % 2) * 255, 200, 60 ); +//// ofDrawCircle( cp, (cindex+1) * 1.0f ); +// ofDrawCircle( cp, 3. ); +// cindex ++; +// } +// ofFill(); + + for( std::size_t k = 0; k < mCPoints.size(); k += 3 ) { + ofSetColor( ofColor::orange ); + ofDrawCircle( mCPoints[k+0], 6.f ); + ofSetColor( ofColor::white ); + ofDrawCircle( mCPoints[k+1], 3.f ); + ofDrawCircle( mCPoints[k+2], 3.f ); + ofDrawLine( mCPoints[k+0], mCPoints[k+1] ); + ofDrawLine( mCPoints[k+0], mCPoints[k+2] ); + } + + ofFill(); + + ofSetColor( ofColor::orange ); + for( auto& cp : mCenterPoints ) { + ofDrawCircle(cp, 4.f); } + +} - if (shape->stroke != svgtiny_TRANSPARENT) { - path.setStrokeWidth(shape->stroke_width); - path.setStrokeHexColor(shape->stroke); +//-------------------------------------------------------------- +ofx::svg::Group* ofxSvg::_getPushedGroup() { + if( mGroupStack.size() > 0 ) { + return mGroupStack.back().get(); } + return this; +} - for (int i = 0; i < (int)shape->path_length;) { - if (p[i] == svgtiny_PATH_MOVE) { - path.moveTo(p[i + 1], p[i + 2]); - i += 3; - } else if (p[i] == svgtiny_PATH_CLOSE) { - path.close(); +//-------------------------------------------------------------- +bool ofxSvg::_hasPushedMatrix() { + return mModelMatrix != glm::mat4(1.0f); +} - i += 1; - } else if (p[i] == svgtiny_PATH_LINE) { - path.lineTo(p[i + 1], p[i + 2]); - i += 3; - } else if (p[i] == svgtiny_PATH_BEZIER) { - path.bezierTo(p[i + 1], p[i + 2], - p[i + 3], p[i + 4], - p[i + 5], p[i + 6]); - i += 7; - } else { - ofLogError("ofxSVG") << "setupShape(): SVG parse error"; - i += 1; +//-------------------------------------------------------------- +void ofxSvg::_applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ) { + if(_hasPushedMatrix() ) { + aele->pos = aDefaultPos; + aele->mModelPos = _getPos2d(mModelMatrix); + aele->rotation = glm::degrees(_getZRotationRadians(mModelMatrix)); + aele->scale = _getScale2d(mModelMatrix); + + } else { + aele->mModelPos = glm::vec2(0.f, 0.f); + aele->pos = aDefaultPos; + } +} + +//-------------------------------------------------------------- +glm::vec2 ofxSvg::_getPos2d(const glm::mat4& amat) { + // Extract translation (position) + return glm::vec2(amat[3][0], amat[3][1]); +} + +//-------------------------------------------------------------- +glm::vec2 ofxSvg::_getScale2d(const glm::mat4& amat) { + // Extract scale (length of column vectors) + return glm::vec2(glm::length(glm::vec2(amat[0][0], amat[0][1])), // Length of first column + glm::length(glm::vec2(amat[1][0], amat[1][1])) // Length of second column + ); +} + +// Function to extract Z-axis rotation (in degrees) from a glm::mat4 +//-------------------------------------------------------------- +float ofxSvg::_getZRotationRadians(const glm::mat4& amat) { + // Normalize the first column (remove scale effect) + glm::vec2 xAxis = glm::vec2(amat[0][0], amat[0][1]); + if( glm::length2(xAxis) > 0.0f ) { + xAxis = glm::normalize(xAxis); + } else { + return 0.0f; + } + + // Compute rotation angle using atan2 + float angleRadians = std::atan2f(xAxis.y, xAxis.x); + return angleRadians; +} + +//-------------------------------------------------------------- +CssClass& ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath ) { + CssClass tcss; +// tcss.name = aSvgPath->name+"st"; + tcss.name = "st"; + if( aSvgPath->path.isFilled() ) { + tcss.setFillColor(aSvgPath->path.getFillColor()); + } else { + tcss.setNoFill(); + } + + if( aSvgPath->path.hasOutline() ) { + tcss.setStrokeColor(aSvgPath->path.getStrokeColor()); + tcss.setStrokeWidth(aSvgPath->path.getStrokeWidth()); + } else { + tcss.setNoStroke(); + } + if( !aSvgPath->isVisible() ) { + tcss.addProperty("display", "none" ); + } + + return mSvgCss.getAddClass(tcss); +} + +//-------------------------------------------------------------- +void ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ) { + +// ofLogNotice("Parser::_addCssClassFromPath") << mSvgCss.toString(); + + auto& css = _addCssClassFromPath(aSvgPath); + +// ofLogNotice("Parser::_addCssClassFromPath") << "adding: " << css.name << " fill: " << aSvgPath->getFillColor() << std::endl << mSvgCss.toString(); + + if( auto xattr = anode.appendAttribute("class") ) { + xattr.set(css.name); + } +} + +//-------------------------------------------------------------- +void ofxSvg::_addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ) { + + if( !aSvgImage->isVisible() ) { + CssClass tcss; + tcss.name = "st"; + tcss.addProperty("display", "none" ); + + auto& addedClass = mSvgCss.getAddClass(tcss); + + if( auto xattr = anode.appendAttribute("class") ) { + xattr.set(addedClass.name); } } } -const std::vector & ofxSvg::getPaths() const { - return paths; +//-------------------------------------------------------------- +bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { + ofXml txml = aParentNode.appendChild( ofx::svg::Element::sGetSvgXmlName(aele->getType())); + if( !aele->getName().empty() ) { + if( auto iattr = txml.appendAttribute("id")) { + iattr.set(aele->getName()); + } + } + if( aele->getType() == ofx::svg::TYPE_GROUP ) { + auto tgroup = std::dynamic_pointer_cast(aele); + if( tgroup ) { + if( tgroup->getNumChildren() > 0 ) { + for( auto& kid : tgroup->getChildren() ) { + _toXml( txml, kid ); + } + } + } + } else if( aele->getType() == ofx::svg::TYPE_RECTANGLE ) { + auto trect = std::dynamic_pointer_cast(aele); + _addCssClassFromPath( trect, txml ); + + if( auto xattr = txml.appendAttribute("x")) { + xattr.set(trect->pos.x); + } + if( auto xattr = txml.appendAttribute("y")) { + xattr.set(trect->pos.y); + } + if( auto xattr = txml.appendAttribute("width")) { + xattr.set(trect->rectangle.getWidth()); + } + if( auto xattr = txml.appendAttribute("height")) { + xattr.set(trect->rectangle.getHeight()); + } + if( trect->round > 0.0f ) { + if( auto xattr = txml.appendAttribute("rx")) { + xattr.set(trect->round); + } + if( auto xattr = txml.appendAttribute("ry")) { + xattr.set(trect->round); + } + } + + } else if( aele->getType() == ofx::svg::TYPE_IMAGE ) { + auto timage = std::dynamic_pointer_cast(aele); + + _addCssClassFromImage( timage, txml ); + + if( auto xattr = txml.appendAttribute("width")) { + xattr.set(timage->width); + } + if( auto xattr = txml.appendAttribute("height")) { + xattr.set(timage->height); + } + if( !timage->getFilePath().empty() ) { + if( auto xattr = txml.appendAttribute("xlink:href")) { + xattr.set(timage->getFilePath()); + } + } + + + } else if( aele->getType() == ofx::svg::TYPE_ELLIPSE ) { + auto tellipse = std::dynamic_pointer_cast(aele); + _addCssClassFromPath( tellipse, txml ); + + if( auto xattr = txml.appendAttribute("cx")) { + xattr.set(tellipse->pos.x); + } + if( auto xattr = txml.appendAttribute("cy")) { + xattr.set(tellipse->pos.y); + } + if( auto xattr = txml.appendAttribute("rx")) { + xattr.set(tellipse->radiusX); + } + if( auto xattr = txml.appendAttribute("ry")) { + xattr.set(tellipse->radiusY); + } + + } else if( aele->getType() == ofx::svg::TYPE_CIRCLE ) { + auto tcircle = std::dynamic_pointer_cast(aele); + _addCssClassFromPath( tcircle, txml ); + + if( auto xattr = txml.appendAttribute("cx")) { + xattr.set(tcircle->pos.x); + } + if( auto xattr = txml.appendAttribute("cy")) { + xattr.set(tcircle->pos.y); + } + if( auto xattr = txml.appendAttribute("r")) { + xattr.set(tcircle->getRadius()); + } + + } else if( aele->getType() == ofx::svg::TYPE_PATH ) { + auto tpath = std::dynamic_pointer_cast(aele); + + _addCssClassFromPath( tpath, txml ); + + std::stringstream vstr; + + if( tpath->path.getMode() == ofPath::Mode::POLYLINES ) { + + auto outlines = tpath->path.getOutline(); + for( auto& polyline : outlines ) { + const auto& pverts = polyline.getVertices(); + if( pverts.size() > 1 ) { + for( std::size_t i = 0; i < pverts.size(); i++ ) { + if( i == 0 ) { + vstr << "M"<path.getCommands(); + if( commands.size() > 1 ) { +// std::stringstream vstr; + for( auto& command : commands ) { + if( command.type == ofPath::Command::moveTo ) { + vstr << "M" << command.to.x << "," << command.to.y << " "; + } else if( command.type == ofPath::Command::lineTo ) { + vstr << "L" << command.to.x << "," << command.to.y << " "; + } else if( command.type == ofPath::Command::curveTo ) { + // hmm, not sure how to handle this at the moment + } else if( command.type == ofPath::Command::bezierTo ) { + vstr << "C" << command.cp1.x << "," << command.cp1.y << " " << command.cp2.x << "," << command.cp2.y << " " << command.to.x << "," << command.to.y << " "; + } else if( command.type == ofPath::Command::quadBezierTo ) { + vstr << "Q" << command.cp2.x << "," << command.cp2.y << " " << command.to.x << "," << command.to.y << " "; + } else if( command.type == ofPath::Command::arc ) { + // TODO: Not so sure about these + glm::vec2 ept = glm::vec2(command.to.x + cosf( command.radiusX ), command.to.y + sinf(command.radiusY )); + vstr << "A" << command.radiusX << "," << command.radiusY << " 0 " << "0,1 " << ept.x << "," <getType() == ofx::svg::TYPE_TEXT ) { + // TODO: Maybe at some point ;/ + } + + // figure out if we need a transform attribute + if( aele->getType() == TYPE_IMAGE || aele->rotation != 0.0f || aele->scale.x != 1.0f || aele->scale.y != 1.0f ) { + if( auto xattr = txml.appendAttribute("transform")) { + xattr.set( getSvgMatrixStringFromElement(aele) ); + } + } + + + + return txml; } + + diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h old mode 100644 new mode 100755 index c2c678deb45..aeba25d50f9 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -1,61 +1,252 @@ -#pragma once +// +// ofxSvgParser.h +// +// Created by Nick Hardeman on 8/31/24. +// -//#include "ofMain.h" -#include "ofPath.h" -#include "ofTypes.h" +#pragma once +#include "ofxSvgGroup.h" #include "ofXml.h" +#include "ofxSvgCss.h" +#include /// \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() = default; - ~ofxSvg() = default; - ofxSvg(const ofxSvg & a) = default; +/// ofxSVG is used for loading, manipulating, rendering and saving of SVG files. - ofxSvg(const of::filesystem::path & fileName); +//namespace ofx::svg { +class ofxSvg : public ofx::svg::Group { +public: + + virtual ofx::svg::SvgType getType() override {return ofx::svg::TYPE_DOCUMENT;} + /// \brief Loads an SVG file from the provided filename. + /// \return true if the load was successful. + bool load( const of::filesystem::path & fileName ); + /// \brief provided for legacy support. + /// \return true if the load was successful. + bool loadFromString( const std::string& data, std::string url = "local"); + /// \brief Reload from filepath saved from a previouly called load(). + /// \return true if the reload was successful. + bool reload(); + /// \brief Save the svg to the path. + /// Use the add functions to provide data to be saved or load data using the load() function. + /// \return true if the save was successful. + bool save( of::filesystem::path apath ); + /// \brief Remove all of the data from the document. + void clear(); + /// \brief Set the directory to search for fonts if using text elements. + /// Needs to be set before calling load(); + /// \param aDir string representation of the directory. + void setFontsDirectory( std::string aDir ); + + /// \brief A string of the element hierarchy. + /// Helpful for visualizing structure. + std::string toString(int nlevel = 0) override; + /// \brief Get the units, ie. px, cm, in, etc. + /// \return string description of the units used. + std::string getUnitString() { return mUnitStr; } + /// \brief Set the units using a string, ie. px, cm, in, etc. + void setUnitString(std::string astring ) { mUnitStr = astring; } + /// \brief The total layers in the svg, should also be the number of groups + elements. + /// \return int that is the total layers. + const int getTotalLayers(); + /// \brief If an element has been added or removed, the function should be called internally. + /// The function updates the 'layer' property on each element. + /// If the layers appear to be incorrect, call this function. + void recalculateLayers(); + /// \brief Provided for legacy support. + /// Includes circles, ellipses, rectangles and paths as ofPaths. + /// \return int as the number of paths in the document. + int getNumPath(); + /// \brief Provided for legacy support. Use getNumPath() to acquire the total number of paths detected. + /// \param n the index of the ofPath to return. + /// \return An ofPath using the provided index. + ofPath & getPathAt(int n); + /// \brief Provided for legacy support. + /// Includes circles, ellipses, rectangles and paths as ofPaths. + /// \return A vector of ofPaths in the entire document. + const std::vector & getPaths() const; + /// \brief Parse the svg transform string into global position, scale and rotation. + /// The elements pos, scale and rotation is set. + /// \param aStr svg transform string. + /// \param aele ofx::svg::Element to be updated. + bool setTransformFromSvgMatrixString( std::string aStr, std::shared_ptr aele ); + /// \brief Return a string used to represent matrix transforms in svg + /// The matrix can be represented as an array of values like : + /// Or using individual components like tranform(translateX translateY), scale(scaleX scaleY) and rotate(degrees ptx pty ) + /// Skew is currently not supported. + /// Reference: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform + /// \param aele the element to extract the svg matrix from using its pos, scale and rotation properties. + /// \return string that represents the svg matrix for using in a svg file. + std::string getSvgMatrixStringFromElement( std::shared_ptr aele ); + /// \brief The viewbox width. + /// \return float width of the viewbox. float getWidth() const; + /// \brief The viewbox height. + /// \return float height of the viewbox. float getHeight() const; + /// \brief The viewbox rectangle. The viewbox attribute on the svg node. + /// \return ofRectangle with the dimenstions of the viewbox. + ofRectangle getViewbox() const; + /// \brief The bounds width. + /// \return float width of the bounds. + float getBoundsWidth() const; + /// \brief The bounds height. + /// \return float height of the bounds. + float getBoundsHeight() const; + /// \brief The bounds rectangle. The width and height attribute on the svg node. + /// \return ofRectangle with the dimenstions of the bounds. + ofRectangle getBounds() const; + + /// \brief Set the width of the viewbox. + /// If the bounds width is less than 1, set the bounds to the same width. + /// \param aw float width. + void setWidth( float aw ); + /// \brief Set the height of the viewbox. + /// If the bounds height is less than 1, set the bounds to the same height. + /// \param ah float height. + void setHeight( float ah ); + /// \brief Set the dimensions of the viewbox. + /// \param arect an ofRectangle with the dimensions of the viewbox. + void setViewBox( const ofRectangle& arect ); + /// \brief Set the width of the bounds. + /// \param aw float width. + void setBoundsWidth( float aw ); + /// \brief Set the height of the viewbox. + /// \param ah float height. + void setBoundsHeight( float ah ); + /// \brief Set the dimensions of the bounds. + /// \param arect an ofRectangle with the dimensions of the bounds. + void setBounds( const ofRectangle& arect ); + + + void pushGroup( const std::string& aname ); + void pushGroup( const std::shared_ptr& agroup ); + void popGroup(); + + void setFillColor(ofColor acolor); + void setFilled(bool abFilled); - /// \brief Loads an SVG file from the provided filename. - /// - /// ~~~~ - void load(const of::filesystem::path & fileName); + void setStrokeColor(ofColor acolor); + void setStrokeWidth(float aLineWidth); + void setHasStroke(bool abStroke); + + void setCircleResolution( int ac ) { mCircleResolution = ac; } + void setCurveResolution( int ac ) { mCurveResolution = ac; } + int getCircleResolution() { return mCircleResolution; } + int getCurveResolution() { return mCurveResolution; }; + + ofx::svg::CssClass& getCurrentCss() { return mCurrentCss;} + + std::shared_ptr addGroup(std::string aname); + + std::shared_ptr add( const ofPath& apath ); + std::vector< std::shared_ptr > add( const std::vector& apaths ); + + std::shared_ptr add( const ofPolyline& apoly ); + std::vector< std::shared_ptr > add( const std::vector& apolys ); + + std::shared_ptr add( const ofRectangle& arect ); + std::shared_ptr add( const ofRectangle& arect, float aRoundRadius ); + + std::shared_ptr addCircle( float aradius ); + std::shared_ptr addCircle( const glm::vec2& apos, float aradius ); + std::shared_ptr addCircle( const glm::vec3& apos, float aradius ); + std::shared_ptr addCircle( const float& ax, const float& ay, float aradius ); + + std::shared_ptr addEllipse( float aradiusX, float aradiusY ); + std::shared_ptr addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ); + std::shared_ptr addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ); + std::shared_ptr addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ); + + std::shared_ptr addImage( const of::filesystem::path& apath, const ofTexture& atex ); + std::shared_ptr addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ); + + // adapted from ofGLProgrammableRenderer for some sort of conformity + void pushMatrix(); + bool popMatrix(); + void translate(float x, float y); + void translate(const glm::vec2 & p); + void scale(float xAmnt, float yAmnt); + void rotateRadians(float radians); + void rotateDegrees(float adegrees); + void multMatrix(const glm::mat4 & m); + void loadMatrix(const glm::mat4 & m); + void loadIdentityMatrix(); + + virtual void drawDebug(); + +protected: + std::string fontsDirectory = ""; + std::string folderPath, svgPath; + ofRectangle mViewbox; + ofRectangle mBounds; + void validateXmlSvgRoot( ofXml& aRootSvgNode ); + std::string cleanString( std::string aStr, std::string aReplace ); + void _parseXmlNode( ofXml& aParentNode, std::vector< std::shared_ptr >& aElements ); + bool _addElementFromXmlNode( ofXml& tnode, std::vector< std::shared_ptr >& aElements ); + + void _parsePolylinePolygon( ofXml& tnode, std::shared_ptr aSvgPath ); + // reference: https://www.w3.org/TR/SVG/paths.html + void _parsePath( ofXml& tnode, std::shared_ptr aSvgPath ); + + ofx::svg::CssClass _parseStyle( ofXml& tnode ); + void _applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ); + void _applyStyleToPath( ofXml& tnode, std::shared_ptr aSvgPath ); + void _applyStyleToPath( ofx::svg::CssClass& aclass, std::shared_ptr aSvgPath ); + void _applyStyleToText( ofXml& tnode, std::shared_ptr aTextSpan ); + void _applyStyleToText( ofx::svg::CssClass& aclass, std::shared_ptr aTextSpan ); + + glm::vec3 _parseMatrixString(const std::string& input, const std::string& aprefix, bool abDefaultZero ); + + std::shared_ptr _getTextSpanFromXmlNode( ofXml& anode ); + + ofx::svg::Group* _getPushedGroup(); + bool _hasPushedMatrix(); + void _applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ); + glm::vec2 _getPos2d(const glm::mat4& amat); + glm::vec2 _getScale2d(const glm::mat4& amat); + float _getZRotationRadians( const glm::mat4& amat ); + + ofx::svg::CssClass& _addCssClassFromPath( std::shared_ptr aSvgPath ); + void _addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ); + void _addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ); + bool _toXml( ofXml& aParentNode, std::shared_ptr aele ); + + unsigned int mCurrentLayer = 0; + + std::string mUnitStr = "px"; + + ofx::svg::CssStyleSheet mSvgCss; + ofx::svg::CssClass mCurrentCss; + ofColor mFillColor, mStrokeColor; + + std::vector< std::shared_ptr > mGroupStack; + + std::shared_ptr mCurrentSvgCss; + + std::vector< std::shared_ptr > mDefElements; + + // just used for debugging + std::vector mCPoints; + std::vector mCenterPoints; + + glm::mat4 mModelMatrix = glm::mat4(1.f); + std::stack mModelMatrixStack; + + int mCircleResolution = 64; + int mCurveResolution = 24; + + // for legacy purposes // + static ofPath sDummyPath; + mutable std::vector mPaths; +}; - /// \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(); - ofPath & getPathAt(int n); - const std::vector & getPaths() const; - static void fixSvgString(std::string & xmlstring); -private: - float width, height; - std::vector paths; - void setupDiagram(struct svgtiny_diagram * diagram); - void setupShape(struct svgtiny_shape * shape, ofPath & path); -}; -typedef ofxSvg ofxSVG; diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp new file mode 100644 index 00000000000..7d5d6d18f37 --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -0,0 +1,518 @@ +// +// ofxSvg2Css.cp +// Example +// +// Created by Nick Hardeman on 8/22/24. +// + +#include "ofxSvgCss.h" +#include "ofUtils.h" +#include "ofLog.h" +#include +#include +#include + +using namespace ofx::svg; + +std::map sCommonColors = { + {"white", ofColor(255, 255, 255)}, + {"black", ofColor(0, 0, 0)}, + {"red", ofColor(255, 0, 0)}, + {"green", ofColor(0, 255, 0)}, + {"blue", ofColor(0, 0, 255)}, + {"yellow", ofColor(255, 255, 0)}, + {"cyan", ofColor(0, 255, 255)}, + {"magenta", ofColor(255, 0, 255)}, + {"gray", ofColor(128, 128, 128)}, + {"orange", ofColor(255, 165, 0)}, + {"brown", ofColor(165, 42, 42)}, + {"pink", ofColor(255, 192, 203)}, + {"purple", ofColor(128, 0, 128)}, + {"lime", ofColor(0, 255, 0)}, + {"maroon", ofColor(128, 0, 0)}, + {"navy", ofColor(0, 0, 128)}, + {"olive", ofColor(128, 128, 0)}, + {"teal", ofColor(0, 128, 128)}, + {"violet", ofColor(238, 130, 238)}, + {"indigo", ofColor(75, 0, 130)}, + {"gold", ofColor(255, 215, 0)}, + {"silver", ofColor(192, 192, 192)}, + {"beige", ofColor(245, 245, 220)}, + {"lavender", ofColor(230, 230, 250)}, + {"turquoise", ofColor(64, 224, 208)}, + {"sky blue", ofColor(135, 206, 235)}, + {"mint", ofColor(189, 252, 201)}, + {"coral", ofColor(255, 127, 80)}, + {"salmon", ofColor(250, 128, 114)}, + {"khaki", ofColor(240, 230, 140)}, + {"ivory", ofColor(255, 255, 240)}, + {"peach", ofColor(255, 218, 185)}, + {"aquamarine", ofColor(127, 255, 212)}, + {"chartreuse", ofColor(127, 255, 0)}, + {"plum", ofColor(221, 160, 221)}, + {"chocolate", ofColor(210, 105, 30)}, + {"orchid", ofColor(218, 112, 214)}, + {"tan", ofColor(210, 180, 140)}, + {"slate gray", ofColor(112, 128, 144)}, + {"periwinkle", ofColor(204, 204, 255)}, + {"sea green", ofColor(46, 139, 87)}, + {"mauve", ofColor(224, 176, 255)}, + {"rose", ofColor(255, 0, 127)}, + {"rust", ofColor(183, 65, 14)}, + {"amber", ofColor(255, 191, 0)}, + {"crimson", ofColor(220, 20, 60)}, + {"sand", ofColor(194, 178, 128)}, + {"jade", ofColor(0, 168, 107)}, + {"denim", ofColor(21, 96, 189)}, + {"copper", ofColor(184, 115, 51)} +}; + +//-------------------------------------------------------------- +void CssClass::clear() { + properties.clear(); + name = "default"; +} + +//-------------------------------------------------------------- +std::string CssClass::sRgbaToHexString(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::uppercase; + ss << std::setw(2) << static_cast(r); + ss << std::setw(2) << static_cast(g); + ss << std::setw(2) << static_cast(b); + ss << std::setw(2) << static_cast(a); + return "#"+ss.str(); +} + +//-------------------------------------------------------------- +std::string CssClass::sRgbToHexString(unsigned char r, unsigned char g, unsigned char b) { + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::uppercase; + ss << std::setw(2) << static_cast(r); + ss << std::setw(2) << static_cast(g); + ss << std::setw(2) << static_cast(b); + return "#"+ss.str(); +} + +//-------------------------------------------------------------- +bool CssClass::sIsNone( const std::string& astr ) { + if( astr.empty() ) { + return true; + } + if( ofToLower(astr) == "none" ) { + return true; + } + return false; +} + +//-------------------------------------------------------------- +ofColor CssClass::sGetColor(const std::string& astr ) { + bool bHasHash = false; + std::string cstr = astr; + if( ofIsStringInString(cstr, "#")) { + ofStringReplace(cstr, "#", ""); + bHasHash = true; + } + cstr = ofToLower(cstr); + + if( bHasHash ) { + ofColor tcolor(255); + int hint = ofHexToInt(cstr); + tcolor.setHex(hint); + return tcolor; + } else if( !astr.empty() ) { + if( sCommonColors.count(cstr)) { + return sCommonColors[cstr]; + } + } + return ofColor(255); +} + +//-------------------------------------------------------------- +float CssClass::sGetFloat(const std::string& astr) { + if( astr.empty() ) return 0.f; + + bool bHasPix = false; + std::string cstr = astr; + if( ofIsStringInString(cstr, "px")) { + bHasPix = true; + ofStringReplace(cstr, "px", ""); + } + return ofToFloat( cstr ); +} + +//-------------------------------------------------------------- +bool CssClass::addProperties( std::string aPropertiesString ) { + if( aPropertiesString.size() > 0 ) { + auto propertiesStr = ofSplitString(aPropertiesString, ";", true, true); + int pindex = 0; + for( auto& propStr : propertiesStr ) { +// std::cout << " " << pindex << " - property: " << propStr << std::endl; + addProperty(propStr); + pindex++; + } + +// for( auto& prop : properties ) { +// ofLogNotice("ofx::svg2::CssClass") << " prop: " << prop.first << " : " << prop.second.srcString; +// } + } + return properties.size() > 0; +} + +//-------------------------------------------------------------- +bool CssClass::addProperty( std::string aPropString ) { + auto splitProps = ofSplitString(aPropString, ":", true ); + if( splitProps.size() == 2 ) { + return addProperty(splitProps[0], splitProps[1]); + } + return false; +} + +//-------------------------------------------------------------- +bool CssClass::addProperty( std::string aName, std::string avalue ) { + if( !aName.empty() && !avalue.empty() ) { + Property newProp; + newProp.srcString = avalue; + ofStringReplace(newProp.srcString, ";", ""); +// if( ofIsStringInString(newProp.srcString, "px")) { +// newProp.bInPixels = true; +// ofStringReplace(newProp.srcString, "px", ""); +// } +// if( ofIsStringInString(newProp.srcString, "#")) { +// newProp.bHasHash = true; +// ofStringReplace(newProp.srcString, "#", ""); +// } + + ofStringReplace(newProp.srcString, "'", ""); + properties[aName] = newProp; +// if( newProp.bInPixels ) { +// getFloatValue(aName, 1.f); +// } + return true; + } + return false; +} + +//-------------------------------------------------- +bool CssClass::addProperty( const std::string& aName, const Property& aprop ) { + return addProperty(aName, aprop.srcString); +} + +//-------------------------------------------------- +bool CssClass::addProperty( const std::string& aName, const float& avalue ) { + ofx::svg::CssClass::Property prop; + prop.fvalue = avalue; + prop.srcString = ofToString(avalue); + return addProperty(aName, prop ); +} + +//-------------------------------------------------- +bool CssClass::addProperty( const std::string& aName, const ofColor& acolor ) { + ofx::svg::CssClass::Property prop; + prop.cvalue = acolor; + prop.srcString = sRgbToHexString(acolor.r, acolor.g, acolor.b); +// ofLogNotice(" CssClass::addProperty") << prop.srcString << " color: " << acolor; + return addProperty(aName, prop ); +} + +//-------------------------------------------------- +bool CssClass::setFillColor(const ofColor& acolor) { + return addProperty("fill", acolor); +} + +//-------------------------------------------------- +bool CssClass::setNoFill() { + return addProperty("fill", "none" ); +} + +//-------------------------------------------------- +bool CssClass::isFilled() { + return !isNone("fill"); +} + +//-------------------------------------------------- +bool CssClass::setStrokeColor(const ofColor& acolor) { + return addProperty("stroke", acolor); +} + +//-------------------------------------------------- +bool CssClass::setStrokeWidth( const float& awidth ) { + return addProperty("stroke-width", awidth); +} + +//-------------------------------------------------- +bool CssClass::setNoStroke() { + return addProperty("stroke", "none" ); +} + +//-------------------------------------------------- +bool CssClass::hasStroke() { + return !isNone("stroke"); +} + +//-------------------------------------------------- +bool CssClass::hasProperty( const std::string& akey ) { + return (properties.count(akey) > 0); +} + +//-------------------------------------------------- +CssClass::Property& CssClass::getProperty( const std::string& akey ) { + if( properties.count(akey) < 1 ) { + return dummyProp; + } + return properties[akey]; +} + +//-------------------------------------------------- +bool CssClass::isNone(const std::string& akey) { + if( properties.count(akey) < 1 ) { + return true; + } + return sIsNone( properties[akey].srcString ); +} + +//-------------------------------------------------- +bool CssClass::hasAndIsNone(const std::string& akey) { + if( hasProperty(akey)) { + return sIsNone( properties[akey].srcString ); + } + return false; +} + +//-------------------------------------------------- +std::string CssClass::getValue(const std::string& akey, const std::string& adefault) { + if( properties.count(akey) < 1 ) { + return adefault; + } + auto& prop = properties[akey]; + if(!prop.svalue.has_value()) { + prop.svalue = prop.srcString; + } + return prop.svalue.value(); +} + +//-------------------------------------------------- +int CssClass::getIntValue(const std::string& akey, const int& adefault) { + if( properties.count(akey) < 1 ) { + return adefault; + } + auto& prop = properties[akey]; + if(!prop.ivalue.has_value()) { + prop.ivalue = ofToInt(prop.srcString); + } + return prop.ivalue.value(); +} + +//-------------------------------------------------- +float CssClass::getFloatValue(const std::string& akey, const float& adefault) { + if( properties.count(akey) < 1 ) { + return adefault; + } + auto& prop = properties[akey]; + if( !prop.fvalue.has_value() ) { + prop.fvalue = sGetFloat(prop.srcString); +// bool bHasPix = false; +// std::string cstr = prop.srcString; +// if( ofIsStringInString(cstr, "px")) { +// bHasPix = true; +// ofStringReplace(cstr, "px", ""); +// } +// prop.fvalue = ofToFloat( cstr ); + } + return prop.fvalue.value(); +} + +//-------------------------------------------------- +ofColor CssClass::getColor(const std::string& akey) { + if( properties.count(akey) < 1 ) { + return ofColor(255); + } + auto& prop = properties[akey]; + if( !prop.cvalue.has_value() ) { + prop.cvalue = sGetColor(prop.srcString); + } + return prop.cvalue.value(); +} + +//-------------------------------------------------- +std::string CssClass::toString(bool aBPrettyPrint) { + std::stringstream ss; + for( auto& piter : properties ) { + if(aBPrettyPrint) { + ss << std::endl << " "; + } + ss << piter.first << ":" << piter.second.srcString << ";"; + } + return ss.str(); +} + +//-------------------------------------------------- +bool CssStyleSheet::parse( std::string aCssString ) { + if( aCssString.empty() ) { + return false; + } + + classes.clear(); + + ofStringReplace(aCssString, "", ""); + + // Regular expression to match class names (e.g., .cls-1, .cls-2, etc.) + std::regex class_regex(R"((\.[\w\-]+(?:,\s*\.[\w\-]+)*))"); + // Regular expression to match properties within curly braces (e.g., fill: none; stroke-miterlimit: 10;) + std::regex property_regex(R"(\{([^}]+)\})"); + + std::smatch class_match; + std::smatch property_match; + + // Search for each class rule block + auto search_start = aCssString.cbegin(); + while (std::regex_search(search_start, aCssString.cend(), class_match, class_regex)) { + std::string class_list = class_match[1]; // Extract the class list (e.g., .cls-1, .cls-2) + + // Move search start forward to find the corresponding property block + std::string::const_iterator prop_search_start = class_match.suffix().first; + + // Find the corresponding properties block + if (std::regex_search(prop_search_start, aCssString.cend(), property_match, property_regex)) { + std::string properties_block = property_match[1]; // Extract properties + std::map properties; + + // Split properties into key-value pairs + std::regex property_pair_regex(R"(([\w\-]+)\s*:\s*([^;]+);?)"); + std::smatch property_pair_match; + + auto prop_search_start = properties_block.cbegin(); + while (std::regex_search(prop_search_start, properties_block.cend(), property_pair_match, property_pair_regex)) { + std::string key = property_pair_match[1]; // e.g., "fill" + std::string value = property_pair_match[2]; // e.g., "none" + properties[key] = value; // Add the property to the map + prop_search_start = property_pair_match.suffix().first; // Continue searching for more properties + } + + // Process the list of classes (comma-separated classes) + std::regex individual_class_regex(R"(\.[\w\-]+)"); + auto class_search_start = class_list.cbegin(); + std::smatch individual_class_match; + while (std::regex_search(class_search_start, class_list.cend(), individual_class_match, individual_class_regex)) { + std::string class_name = individual_class_match[0]; // Extract the individual class (e.g., .cls-1) + + if( class_name.size() > 0 && class_name[0] == '.' ) { + class_name = class_name.substr(1, std::string::npos); + } + + // Merge properties for the class (with priority for the latest properties) + for (const auto& prop : properties) { + auto& svgCssClass = addClass(class_name); + svgCssClass.addProperty(prop.first, prop.second); +// cssClasses[class_name][prop.first] = prop.second; + } + + class_search_start = individual_class_match.suffix().first; // Move to the next class + } + } + + search_start = property_match.suffix().first; // Move to the next block + } + return classes.size() > 0; +} + +//-------------------------------------------------- +void CssStyleSheet::clear() { + classes.clear(); +} + +//-------------------------------------------------- +CssClass& CssStyleSheet::addClass(std::string aname) { + if( hasClass(aname) ) { +// ofLogWarning("ofx::svg2") << "CssStyleSheet already has class " << aname <<"."; + return classes[aname]; + } + CssClass tclass; + tclass.name = aname; + classes[aname] = tclass; + return classes[aname]; +} + +//-------------------------------------------------- +bool CssStyleSheet::hasClass(const std::string& aname) { + return classes.count(aname) > 0; +} + +//-------------------------------------------------- +CssClass& CssStyleSheet::getClass( const std::string& aname ) { + if( hasClass(aname)) { + return classes[aname]; + } + ofLogWarning("ofx::svg2::CssStyleSheet") << "could not find class " << aname; + return dummyClass; +} + +//-------------------------------------------------- +CssClass& CssStyleSheet::getAddClass( CssClass& aclass ) { + + for( auto& tclass : classes ) { + bool bFoundAll = true; + + // check for the same number of properties + if( tclass.second.properties.size() != aclass.properties.size() ) { + continue; + } + + for( auto& aprop : aclass.properties ) { + bool bFound = false; + for( auto& tprop : tclass.second.properties ) { + if( tprop.first == aprop.first && tprop.second.srcString == aprop.second.srcString) { + bFound = true; + break; + } + } + if( !bFound ) { + bFoundAll = false; + } + } + + if( bFoundAll ) { + // we found another class that has all the things +// ofLogNotice("CssStyleSheet::getAddClass") << "found matching class: " << tclass.second.name; + return classes[tclass.second.name]; + } + } + + // check if name already exists + std::string className = aclass.name; + int ccounter = 0; + bool bKeepGoing = true; + while( bKeepGoing ) { + bool bFound = false; + for( auto& tclass : classes ) { + if( className == tclass.first ) { + bFound = true; + break; + } + } + if( bFound ) { + ccounter ++; + className = aclass.name + ofToString(ccounter); + } else { + bKeepGoing = false; + } + } + + aclass.name = className; + classes[className] = aclass; + return classes[className]; +} + +//-------------------------------------------------- +std::string CssStyleSheet::toString(bool aBPrettyPrint) { + std::stringstream ss; + for( auto& citer : classes ) { + ss << std::endl; + ss << "." << citer.first << " { "; + ss << citer.second.toString(aBPrettyPrint); + ss << "}" << std::endl; + } + + return ss.str(); +} diff --git a/addons/ofxSvg/src/ofxSvgCss.h b/addons/ofxSvg/src/ofxSvgCss.h new file mode 100644 index 00000000000..ff3e1c1367c --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgCss.h @@ -0,0 +1,136 @@ +// +// ofxSvg2Css.h +// Example +// +// Created by Nick Hardeman on 8/22/24. +// + +#pragma once +#include +#include "ofColor.h" +#include "ofLog.h" +#include "ofXml.h" + +namespace ofx::svg { +class CssClass { +public: + + // adding this Optional class since std::optional is not a part of all std:: distributions at the moment, looking at you gcc < 10 + template + class Optional { + public: + Optional() : hasValue(false) {} // Default constructor, no value + Optional(const T& value) : hasValue(true), data(value) {} // Construct with a value + Optional(T&& value) : hasValue(true), data(std::move(value)) {} // Move constructor + + // Copy and move constructors + Optional(const Optional& other) = default; + Optional(Optional&& other) noexcept = default; + + // Assignment operators + Optional& operator=(const Optional& other) = default; + Optional& operator=(Optional&& other) noexcept = default; + + // Destructor + ~Optional() = default; + + // Check if there's a value + bool has_value() const { return hasValue; } + + // Accessors for the value + T& value() { +// if (!hasValue) throw std::runtime_error("No value present"); + if (!hasValue) { + ofLogError("ofx::svg::CssClass") << "No value present"; + } + return data; + } + + const T& value() const { +// if (!hasValue) throw std::runtime_error("No value present"); + if (!hasValue) { + ofLogError("ofx::svg::CssClass") << "No value present"; + } + return data; + } + + // Reset to an empty state + void reset() { hasValue = false; } + + private: + bool hasValue; + T data; + }; + + class Property { + public: + std::string srcString; + Optional fvalue; + Optional ivalue; + Optional svalue; + Optional cvalue; + }; + + std::unordered_map properties; + std::string name = "default"; + + void clear(); + + static std::string sRgbaToHexString(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + static std::string sRgbToHexString(unsigned char r, unsigned char g, unsigned char b); + static bool sIsNone( const std::string& astr ); + static ofColor sGetColor(const std::string& astr); + static float sGetFloat(const std::string& astr); + + bool addProperties( std::string aPropertiesString ); + bool addProperty( std::string aPropString ); + bool addProperty( std::string aName, std::string avalue ); + bool addProperty( const std::string& aName, const Property& aprop ); + bool addProperty( const std::string& aName, const float& avalue ); + bool addProperty( const std::string& aName, const ofColor& acolor ); + + bool setFillColor(const ofColor& acolor); + bool setNoFill(); + bool isFilled(); + + bool setStrokeColor(const ofColor& acolor); + bool setStrokeWidth( const float& awidth ); + bool setNoStroke(); + bool hasStroke(); + + bool hasProperty( const std::string& akey ); + Property& getProperty( const std::string& akey ); + bool isNone(const std::string& akey); + bool hasAndIsNone(const std::string& akey); + + std::string getValue(const std::string& akey, const std::string& adefault); + int getIntValue(const std::string& akey, const int& adefault); + float getFloatValue(const std::string& akey, const float& adefault); + ofColor getColor(const std::string& akey); + + std::string toString(bool aBPrettyPrint=true); + +protected: + Property dummyProp; +}; + +class CssStyleSheet { +public: + + bool parse( std::string aCssString ); + void clear(); + + CssClass& addClass( std::string aname ); + bool hasClass( const std::string& aname ); + CssClass& getClass( const std::string& aname ); + + CssClass& getAddClass( CssClass& aclass ); + + std::unordered_map classes; + + std::string toString(bool aBPrettyPrint=true); + +protected: + CssClass dummyClass; +}; +} diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp new file mode 100755 index 00000000000..ef11faf1258 --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -0,0 +1,546 @@ +// +// ofxSvgElements.cpp +// +// Created by Nick Hardeman on 7/31/15. +// + +#include "ofxSvgElements.h" +#include "ofGraphics.h" + +using std::vector; +using std::string; +using namespace ofx::svg; + +std::map< string, Text::Font > Text::fonts; +ofTrueTypeFont Text::defaultFont; + +//-------------------------------------------------------------- +std::string Element::sGetTypeAsString(SvgType atype) { + switch (atype) { + case TYPE_GROUP: + return "Group"; + break; + case TYPE_RECTANGLE: + return "Rectangle"; + break; + case TYPE_IMAGE: + return "Image"; + break; + case TYPE_ELLIPSE: + return "Ellipse"; + break; + case TYPE_CIRCLE: + return "Circle"; + break; + case TYPE_PATH: + return "Path"; + break; + case TYPE_TEXT: + return "Text"; + break; + case TYPE_DOCUMENT: + return "Document"; + break; + case TYPE_ELEMENT: + return "Element"; + break; + default: + break; + } + return "Unknown"; +} + +//-------------------------------------------------------------- +std::string Element::sGetSvgXmlName(SvgType atype) { + switch (atype) { + case TYPE_GROUP: + return "g"; + break; + case TYPE_RECTANGLE: + return "rect"; + break; + case TYPE_IMAGE: + return "image"; + break; + case TYPE_ELLIPSE: + return "ellipse"; + break; + case TYPE_CIRCLE: + return "circle"; + break; + case TYPE_PATH: + return "path"; + break; + case TYPE_TEXT: + return "text"; + break; + case TYPE_DOCUMENT: + return "svg"; + break; + case TYPE_ELEMENT: + return "Element"; + break; + default: + break; + } + return "Unknown"; +} + +//-------------------------------------------------------------- +string Element::getTypeAsString() { + return sGetTypeAsString(getType()); +} + +//-------------------------------------------------------------- +string Element::toString( int nlevel ) { + + string tstr = ""; + for( int k = 0; k < nlevel; k++ ) { + tstr += " "; + } + tstr += getTypeAsString() + " - " + getName() + "\n"; + + return tstr; +} + +////-------------------------------------------------------------- +//glm::mat4 ofxSvgBase::getTransformMatrix() { +// glm::mat4 rmat = glm::translate(glm::mat4(1.0f), glm::vec3(pos.x, pos.y, 0.0f)); +// return rmat; +//} +// +////-------------------------------------------------------------- +//ofNode ofxSvgBase::getNodeTransform() { +// ofNode tnode; +// tnode.setPosition( pos.x, pos.y, 0.0f ); +// return tnode; +//} + +//-------------------------------------------------------------- +glm::mat4 Element::getTransformMatrix() { + glm::mat4 rmat = glm::translate(glm::mat4(1.0f), glm::vec3(pos.x, pos.y, 0.0f)); + if( rotation != 0.0f ) { + glm::quat rq = glm::angleAxis(ofDegToRad(rotation), glm::vec3(0.f, 0.f, 1.0f )); + rmat = rmat * glm::toMat4((const glm::quat&)rq); + } + if( scale.x != 1.0f || scale.y != 1.0f ) { + rmat = glm::scale(rmat, glm::vec3(scale.x, scale.y, 1.0f)); + } + return rmat; +}; + +//-------------------------------------------------------------- +ofNode Element::getNodeTransform() { + ofNode tnode;// = ofxSvgBase::getNodeTransform(); + tnode.setPosition(pos.x, pos.y, 0.0f); + if( rotation != 0.0f ) { + glm::quat rq = glm::angleAxis(ofDegToRad(rotation), glm::vec3(0.f, 0.f, 1.0f )); + tnode.setOrientation(rq); + } + tnode.setScale(1.0f); + if( scale.x != 1.0f || scale.y != 1.0f ) { + tnode.setScale(scale.x, scale.y, 1.f ); + } + return tnode; +} + +#pragma mark - Image +//-------------------------------------------------------------- +ofRectangle Image::getRectangle() { + return ofRectangle(pos.x, pos.y, getWidth(), getHeight()); +} + +//-------------------------------------------------------------- +void Image::draw() { + if( !bTryLoad ) { + img.load( getFilePath() ); + bTryLoad = true; + } + + if( isVisible() ) { + if( img.isAllocated() ) { + ofPushMatrix(); { + ofTranslate( pos.x, pos.y ); + if( rotation != 0.0 ) ofRotateZDeg( rotation ); + ofScale( scale.x, scale.y ); + if(bUseShapeColor) ofSetColor( getColor() ); + img.draw( 0, 0 ); + } ofPopMatrix(); + } + } +} + +//-------------------------------------------------------------- +glm::vec2 Image::getAnchorPointForPercent( float ax, float ay ) { + glm::vec2 ap = glm::vec2( width * ax * scale.x, height * ay * scale.y ); + ap = glm::rotate(ap, glm::radians(rotation)); + return ap; +} + +#pragma mark - Text + +//-------------------------------------------------------------- +void Text::create() { + meshes.clear(); + + // now lets sort the text based on meshes that we need to create // + vector< std::shared_ptr > tspans = textSpans; + + std::map< string, std::map< int, vector > > > tspanFonts; + for( std::size_t i = 0; i < tspans.size(); i++ ) { + if( tspanFonts.count( tspans[i]->fontFamily ) == 0 ) { + std::map< int, vector< std::shared_ptr> > tmapap; + tspanFonts[ tspans[i]->fontFamily ] = tmapap; + } + std::map< int, vector< std::shared_ptr> >& spanMap = tspanFonts[ tspans[i]->fontFamily ]; + if( spanMap.count(tspans[i]->fontSize) == 0 ) { + vector< std::shared_ptr > tvec; + spanMap[ tspans[i]->fontSize ] = tvec; + } + spanMap[ tspans[i]->fontSize ].push_back( tspans[i] ); + } + + + bool bHasFontDirectory = false; +// cout << "checking directory: " << fdirectory+"/fonts/" << endl; + string fontsDirectory = ofToDataPath("", true); + if( fdirectory != "" ) { + fontsDirectory = fdirectory;//+"/fonts/"; + } + if( ofFile::doesFileExist( fontsDirectory )) { + bHasFontDirectory = true; + } + + std::map< string, std::map< int, vector< std::shared_ptr> > >::iterator mainIt; + for( mainIt = tspanFonts.begin(); mainIt != tspanFonts.end(); ++mainIt ) { + if( fonts.count(mainIt->first) == 0 ) { + Font tafont; + tafont.fontFamily = mainIt->first; + fonts[ mainIt->first ] = tafont; + } + + // now create a mesh for the family // + // map< string, map > meshes; + if( meshes.count(mainIt->first) == 0 ) { + std::map< int, ofMesh > tempMeshMap; + meshes[ mainIt->first ] = tempMeshMap; + } + + Font& tfont = fonts[ mainIt->first ]; + std::map< int, ofMesh >& meshMap = meshes[ mainIt->first ]; + + std::map< int, vector> >::iterator vIt; + for( vIt = mainIt->second.begin(); vIt != mainIt->second.end(); ++vIt ) { + vector>& spanSpans = vIt->second; + bool bFontLoadOk = true; + if (tfont.sizes.count(vIt->first) == 0) { +// string _filename, int _fontSize, bool _bAntiAliased, bool _bFullCharacterSet, bool _makeContours, float _simplifyAmt, int _dpi + // first let's see if the fonts are provided. Some system fonts are .dfont that have several of the faces + // in them, but OF isn't setup to parse them, so we need each bold, regular, italic, etc to be a .ttf font // + string tfontPath = tfont.fontFamily; + if (bHasFontDirectory) { + + ofLogNotice(moduleName()) << __FUNCTION__ << " : " << tfont.fontFamily << " : starting off searching directory : " << fontsDirectory; + string tNewFontPath = ""; + bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, tfont.fontFamily, tNewFontPath); + if (bFoundTheFont) { + tfontPath = tNewFontPath; + } + + /*ofDirectory tfDir; + tfDir.listDir( fontsDirectory ); + for( int ff = 0; ff < tfDir.size(); ff++ ) { + ofFile tfFile = tfDir.getFile(ff); + if( tfFile.getExtension() == "ttf" || tfFile.getExtension() == "otf" ) { + cout << ff << " - font family: " << tfont.fontFamily << " file name: " << tfFile.getBaseName() << endl; + if( ofToLower(tfFile.getBaseName()) == ofToLower(tfont.fontFamily) ) { + ofLogNotice(" >> ofxSvgText found font file for " ) << tfont.fontFamily; + tfontPath = tfFile.getAbsolutePath(); + break; + } + } + }*/ + } + + ofLogNotice(moduleName()) << __FUNCTION__ << " : Trying to load font from: " << tfontPath; + + if (tfontPath == "") { + bFontLoadOk = false; + } + else { + // load(const std::string& _filename, int _fontSize, bool _bAntiAliased, bool _bFullCharacterSet, bool _makeContours, float _simplifyAmt, int _dpi) + bFontLoadOk = tfont.sizes[vIt->first].load(tfontPath, vIt->first, true, true, false, 0.5, 72); + } + if(bFontLoadOk) { +// tfont.sizes[ vIt->first ].setSpaceSize( 0.57 ); +// tfont.sizes[ vIt->first ] = datFontTho; + tfont.textures[ vIt->first ] = tfont.sizes[ vIt->first ].getFontTexture(); + } else { + ofLogError(moduleName()) << __FUNCTION__ << " : error loading font family: " << tfont.fontFamily << " size: " << vIt->first; + tfont.sizes.erase(vIt->first); + } + } + if( !bFontLoadOk ) continue; + + if( meshMap.count(vIt->first) == 0 ) { + meshMap[ vIt->first ] = ofMesh(); + } + ofMesh& tmesh = meshMap[ vIt->first ]; + + if( !tfont.sizes.count( vIt->first ) ) { + ofLogError(moduleName()) << __FUNCTION__ << " : Could not find that font size in the map: " << vIt->first; + continue; + } + + ofTrueTypeFont& ttfont = tfont.sizes[ vIt->first ]; + for( std::size_t i = 0; i < spanSpans.size(); i++ ) { + // create a mesh here // + std::shared_ptr& cspan = spanSpans[i]; + if( cspan->text == "" ) continue; +// cout << "font family: " << cspan.fontFamily << " size: " << cspan.fontSize << " text: " << cspan.text << endl; + +// const ofMesh& stringMesh = ttfont.getStringMesh( "please work", 20, 20 ); + + ofRectangle tempBounds = ttfont.getStringBoundingBox( cspan->text, 0, 0 ); + float tffontx = bCentered ? cspan->rect.x - tempBounds.width/2 : cspan->rect.x; +// const ofMesh& stringMesh = ttfont.getStringMesh( cspan.text, tffontx-ogPos.x, cspan.rect.y-ogPos.y ); + const ofMesh& stringMesh = ttfont.getStringMesh( cspan->text, tffontx, cspan->rect.y ); + int offsetIndex = tmesh.getNumVertices(); + + vector tsIndices = stringMesh.getIndices(); + for( std::size_t k = 0; k < tsIndices.size(); k++ ) { + tsIndices[k] = tsIndices[k] + offsetIndex; + } + + ofFloatColor tcolor = cspan->color; + vector< ofFloatColor > tcolors; + tcolors.assign( stringMesh.getVertices().size(), tcolor ); + + tmesh.addIndices( tsIndices ); + tmesh.addVertices( stringMesh.getVertices() ); + tmesh.addTexCoords( stringMesh.getTexCoords() ); + tmesh.addColors( tcolors ); + } + } + } + + // now loop through and set the width and height of the text spans // + for( std::size_t i = 0; i < textSpans.size(); i++ ) { + auto& tempSpan = textSpans[i]; + ofTrueTypeFont& tfont = tempSpan->getFont(); + if( tfont.isLoaded() ) { + ofRectangle tempBounds = tfont.getStringBoundingBox( tempSpan->text, 0, 0 ); + tempSpan->rect.width = tempBounds.width; + tempSpan->rect.height = tempBounds.height; + tempSpan->lineHeight = tfont.getStringBoundingBox("M", 0, 0).height; +// tempSpan.rect.x = tempSpan.rect.x - ogPos.x; +// tempSpan.rect.y = tempSpan.rect.x - ogPos.x; + //tempSpan.rect.y -= tempSpan.lineHeight; + } + } +} + +//-------------------------------------------------------------- +void Text::draw() { + if( !isVisible() ) return; +// map< string, map > meshes; + if(bUseShapeColor) { + ofSetColor( 255, 255, 255, 255.f * alpha ); + } + std::map< string, std::map >::iterator mainIt; + + ofPushMatrix(); { + ofSetColor( 255, 0, 0 ); + ofDrawCircle(pos, 6); + ofNoFill(); +// ofSetColor( 0, 0, 224 ); +// ofDrawCircle( ogPos, 10); + ofDrawRectangle(getRectangle()); + ofFill(); + +// ofLogNotice("ofx::svg2") << "Text: num text spans: " << textSpans.size() << " meshes size: " << meshes.size(); + + ofTranslate( pos.x, pos.y ); + +// ofSetColor( 255, 255, 255, 255.f * alpha ); + if( rotation > 0 ) ofRotateZDeg( rotation ); + ofTexture* tex = NULL; + for( mainIt = meshes.begin(); mainIt != meshes.end(); ++mainIt ) { + string fontFam = mainIt->first; + std::map< int, ofMesh >::iterator mIt; + for( mIt = meshes[ fontFam ].begin(); mIt != meshes[ fontFam ].end(); ++mIt ) { + int fontSize = mIt->first; + // let's check to make sure that the texture is there, so that we can bind it // + bool bHasTexture = false; + // static map< string, Font > fonts; + if( fonts.count( fontFam ) ) { + if( fonts[ fontFam ].textures.count( fontSize ) ) { + bHasTexture = true; + tex = &fonts[ fontFam ].textures[ fontSize ]; + } + } + + if( bHasTexture ) tex->bind(); + ofMesh& tMeshMesh = mIt->second; + if( bUseShapeColor ) { + vector< ofFloatColor >& tcolors = tMeshMesh.getColors(); + for( auto& tc : tcolors ) { + if( bOverrideColor ) { + tc = _overrideColor; + } else { + tc.a = alpha; + } + } + } else { + tMeshMesh.disableColors(); + } + tMeshMesh.draw(); + if( bHasTexture ) tex->unbind(); + tMeshMesh.enableColors(); + } + } + } ofPopMatrix(); + +} + +//-------------------------------------------------------------- +void Text::draw(const std::string &astring, bool abCentered ) { + if( textSpans.size() > 0 ) { + ofPushMatrix(); { + ofTranslate( pos.x, pos.y ); + if( rotation > 0 ) ofRotateZDeg( rotation ); + textSpans[0]->draw(astring, abCentered ); + } ofPopMatrix(); + } else { + ofLogVerbose(moduleName()) << __FUNCTION__ << " : no text spans to draw with."; + } +} + +//-------------------------------------------------------------- +void Text::draw(const std::string& astring, const ofColor& acolor, bool abCentered ) { + if( textSpans.size() > 0 ) { + ofPushMatrix(); { + ofTranslate( pos.x, pos.y ); + if( rotation > 0 ) ofRotateZDeg( rotation ); + textSpans[0]->draw(astring, acolor, abCentered ); + } ofPopMatrix(); + } else { + ofLogVerbose(moduleName()) << __FUNCTION__ << " : no text spans to draw with."; + } +} + +//-------------------------------------------------------------- +bool Text::_recursiveFontDirSearch(const string& afile, const string& aFontFamToLookFor, string& fontpath) { + if (fontpath != "") { + return true; + } + ofFile tfFile( afile, ofFile::Reference ); + if (tfFile.isDirectory()) { + ofLogVerbose(moduleName()) << __FUNCTION__ << " : searching in directory : " << afile << " | " << ofGetFrameNum(); + ofDirectory tdir; + tdir.listDir(afile); + tdir.sort(); + for (std::size_t i = 0; i < tdir.size(); i++) { + bool youGoodOrWhat = _recursiveFontDirSearch(tdir.getPath(i), aFontFamToLookFor, fontpath); + if( youGoodOrWhat ) { + return true; + } + } + tdir.close(); + } else { + if ( tfFile.getExtension() == "ttf" || tfFile.getExtension() == "otf") { + if (ofToLower( tfFile.getBaseName() ) == ofToLower(aFontFamToLookFor)) { + ofLogNotice(moduleName()) << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; + fontpath = tfFile.getAbsolutePath(); + return true; + } + string tAltFileName = ofToLower(tfFile.getBaseName()); + ofStringReplace(tAltFileName, " ", "-"); + if (tAltFileName == ofToLower(aFontFamToLookFor)) { + ofLogNotice(moduleName()) << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; + fontpath = tfFile.getAbsolutePath(); + return true; + } + } + } + return false; +} + +// must return a reference for some reason here // +//-------------------------------------------------------------- +ofTrueTypeFont& Text::TextSpan::getFont() { + if( Text::fonts.count( fontFamily ) > 0 ) { + Font& tfont = fonts[ fontFamily ]; + if( tfont.sizes.count(fontSize) > 0 ) { + return tfont.sizes[ fontSize ]; + } + } + return defaultFont; +} + +//-------------------------------------------------------------- +void Text::TextSpan::draw(const std::string &astring, bool abCentered ) { + draw( astring, color, abCentered ); +} + +//-------------------------------------------------------------- +void Text::TextSpan::draw(const std::string &astring, const ofColor& acolor, bool abCentered ) { + ofSetColor( acolor ); + auto& cfont = getFont(); + ofRectangle tempBounds = cfont.getStringBoundingBox( astring, 0, 0 ); + float tffontx = abCentered ? rect.getCenter().x - tempBounds.width/2 : rect.x; + // const ofMesh& stringMesh = cfont.getStringMesh( astring, tffontx, rect.y ); + // cfont.drawString(astring, tffontx, rect.y + (tempBounds.getHeight() - lineHeight) ); + cfont.drawString(astring, tffontx, rect.y ); +} + +//-------------------------------------------------------------- +ofTrueTypeFont& Text::getFont() { + if( textSpans.size() > 0 ) { + return textSpans[0]->getFont(); + } + ofLogWarning(moduleName()) << __FUNCTION__ << " : no font detected from text spans, returning default font."; + return defaultFont; +} + +//-------------------------------------------------------------- +ofColor Text::getColor() { + if( textSpans.size() > 0 ) { + return textSpans[0]->color; + } + ofLogWarning(moduleName()) << __FUNCTION__ << " : no font detected from text spans, returning path fill color."; + return path.getFillColor(); +} + +// get the bounding rect for all of the text spans in this svg'ness +// should be called after create // +//-------------------------------------------------------------- +ofRectangle Text::getRectangle() { + ofRectangle temp( 0, 0, 1, 1 ); + for( std::size_t i = 0; i < textSpans.size(); i++ ) { + ofRectangle trect = textSpans[i]->rect; + trect.x = trect.x;// - ogPos.x; + trect.y = trect.y;// - ogPos.y; + trect.y -= textSpans[i]->lineHeight; + if( i == 0 ) { + temp = trect; + } else { + temp.growToInclude( trect ); + } + } + + + temp.x += pos.x; + temp.y += pos.y; + return temp; +} + + + + + + + + + + diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h new file mode 100755 index 00000000000..6f9627450a6 --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -0,0 +1,258 @@ +// +// ofxSvgElements.h +// +// Created by Nick Hardeman on 7/31/15. +// + +#pragma once +#include "ofNode.h" +#include "ofImage.h" +#include "ofPath.h" +#include +#include "ofTrueTypeFont.h" + +namespace ofx::svg { +enum SvgType { + TYPE_ELEMENT = 0, + TYPE_GROUP, + TYPE_RECTANGLE, + TYPE_IMAGE, + TYPE_ELLIPSE, + TYPE_CIRCLE, + TYPE_PATH, + TYPE_TEXT, + TYPE_DOCUMENT, + TYPE_TOTAL +}; + +class Element { +public: + + static std::string sGetTypeAsString(SvgType atype); + static std::string sGetSvgXmlName(SvgType atype); + + virtual SvgType getType() {return TYPE_ELEMENT;} + std::string getTypeAsString(); + + std::string getName() { return name; } + bool isGroup() { + return (getType() == TYPE_GROUP); + } + + void setVisible( bool ab ) { bVisible = ab; } + bool isVisible() { return bVisible; } + + virtual std::string toString(int nlevel = 0); + + std::string name = ""; + float layer = -1.f; + bool bVisible=true; + bool bUseShapeColor = true; + + + // will log messages to this module name + static const std::string moduleName() { return "ofx::svg::Element"; } + + glm::vec2 pos = glm::vec2(0.f, 0.f); + glm::vec2 scale = glm::vec2(1.0f, 1.0f); + float rotation = 0.0f; + + virtual glm::mat4 getTransformMatrix(); + virtual ofNode getNodeTransform(); + + virtual void draw() {} + + virtual void setUseShapeColor( bool ab ) { + bUseShapeColor = ab; + } + + virtual ofPolyline getFirstPolyline() { + ofLogWarning(moduleName()) << __FUNCTION__ << " : Element " << getTypeAsString() << " does not have a path."; + return ofPolyline(); + } +//protected: + // used for saving to set the model position of the current mat4 // + glm::vec2 mModelPos = glm::vec2(0.f, 0.f); + glm::vec2 mModelRotationPoint = glm::vec2(0.f, 0.f); + +}; + +class Path : public Element { +public: + virtual SvgType getType() override {return TYPE_PATH;} + + virtual void setUseShapeColor( bool ab ) override { + Element::setUseShapeColor(ab); + path.setUseShapeColor(ab); + } + + virtual void draw() override { + if(isVisible()) path.draw(); + } + + bool isFilled() { return path.isFilled(); } + bool hasStroke() { return path.hasOutline(); } + float getStrokeWidth() { return path.getStrokeWidth(); } + ofColor getFillColor() { return path.getFillColor(); } + ofColor getStrokeColor() { return path.getStrokeColor(); } + + ofPolyline getFirstPolyline() override { + if( path.getOutline().size() > 0 ) { + return path.getOutline()[0]; + } + ofLogWarning(moduleName()) << __FUNCTION__ << " : path does not have an outline."; + return ofPolyline(); + } + + ofPath path; +}; + +class Rectangle : public Path { +public: + virtual SvgType getType() override {return TYPE_RECTANGLE;} + ofRectangle rectangle; + + float getWidth() { return rectangle.getWidth() * scale.x;} + float getHeight() { return rectangle.getHeight() * scale.y;} + + float round = 0.f; + +// float getWidthScaled() { return rectangle.getWidth() * scale.x;} +// float getHeightScaled() { return rectangle.getHeight() * scale.y;} +}; + +class Image : public Element { +public: + virtual SvgType getType() override {return TYPE_IMAGE;} + + float getWidth() { return width * scale.x;} + float getHeight() { return height * scale.y;} + + ofRectangle getRectangle(); +// float getWidthScaled() { return getWidth() * scale.x;} +// float getHeightScaled() { return getHeight() * scale.y;} + + virtual void draw() override; + glm::vec2 getAnchorPointForPercent( float ax, float ay ); + + std::string getFilePath() { return filepath; } + + void setColor( ofColor aColor ) { + color = aColor; + } + ofColor getColor() { + return color; + } + + ofColor color; + ofImage img; + bool bTryLoad = false; + of::filesystem::path filepath; + float width = 0.f; + float height = 0.f; +}; + +class Circle : public Path { +public: + virtual SvgType getType() override {return TYPE_CIRCLE;} + float getRadius() {return radius;} + float radius = 10.0; +}; + +class Ellipse : public Path { +public: + virtual SvgType getType() override {return TYPE_ELLIPSE;} + float radiusX, radiusY = 10.0f; +}; + + +class Text : public Rectangle { +public: + class Font { + public: + std::string fontFamily; + std::map< int, ofTrueTypeFont > sizes; + std::map< int, ofTexture > textures; + }; + + static std::map< std::string, Font > fonts; + + class TextSpan { + public: + TextSpan() { + text = ""; + fontSize = 12; + lineHeight = 0; + } + + std::string text; + int fontSize = 12; + std::string fontFamily; + ofRectangle rect; + ofColor color; + float lineHeight = 12; + ofTrueTypeFont& getFont(); + void draw( const std::string& astring, bool abCentered ); + void draw(const std::string &astring, const ofColor& acolor, bool abCentered ); + }; + + static bool sortSpanOnFontFamily( const TextSpan& a, const TextSpan& b ) { + return a.fontFamily < b.fontFamily; + } + + static bool sortSpanOnFontSize( const TextSpan& a, const TextSpan& b ) { + return a.fontSize < b.fontSize; + } + +// Text() { type = OFX_SVG_TYPE_TEXT; fdirectory=""; bCentered=false; alpha=1.0; bOverrideColor=false; } + virtual SvgType getType() override {return TYPE_TEXT;} + + ofTrueTypeFont& getFont(); + ofColor getColor(); + + void create(); + void draw() override; + void draw(const std::string &astring, bool abCentered ); + void draw(const std::string &astring, const ofColor& acolor, bool abCentered ); + + void setFontDirectory( std::string aPath ) { + fdirectory = aPath; + // cout << "Setting the font directory to " << fdirectory << endl; + } + + void overrideColor( ofColor aColor ) { + bOverrideColor = true; + _overrideColor = aColor; + } + + ofRectangle getRectangle(); + + std::map< std::string, std::map > meshes; + std::vector< std::shared_ptr > textSpans; + + std::string fdirectory; + bool bCentered = false; + float alpha = 1.; + glm::vec2 ogPos = glm::vec2(0.f, 0.f); + +protected: + static ofTrueTypeFont defaultFont; + bool _recursiveFontDirSearch(const std::string& afile, const std::string& aFontFamToLookFor, std::string& fontpath); + ofFloatColor _overrideColor; + bool bOverrideColor = false; +}; + +} + + + + + + + + + + + + + diff --git a/addons/ofxSvg/src/ofxSvgGroup.cpp b/addons/ofxSvg/src/ofxSvgGroup.cpp new file mode 100755 index 00000000000..c1d3d454d38 --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgGroup.cpp @@ -0,0 +1,289 @@ +// +// ofxSvgGroup.cpp +// +// Created by Nick Hardeman on 7/31/15. +// + +#include "ofxSvgGroup.h" +#include "ofGraphics.h" + +using namespace ofx::svg; +using std::vector; +using std::shared_ptr; +using std::string; + +//-------------------------------------------------------------- +void Group::draw() { + std::size_t numElements = mChildren.size(); + bool bTrans = pos.x != 0 || pos.y != 0.0; + if( bTrans ) { + ofPushMatrix(); + ofTranslate(pos.x, pos.y); + } + for( std::size_t i = 0; i < numElements; i++ ) { + mChildren[i]->draw(); + } + if( bTrans ) { + ofPopMatrix(); + } +} + +//-------------------------------------------------------------- +std::size_t Group::getNumChildren() { + return mChildren.size(); +} + +//-------------------------------------------------------------- +vector< shared_ptr >& Group::getChildren() { + return mChildren; +} + +//-------------------------------------------------------------- +vector< shared_ptr > Group::getAllChildren(bool aBIncludeGroups) { + vector< shared_ptr > retElements; + + for( auto ele : mChildren ) { + _getAllElementsRecursive( retElements, ele, aBIncludeGroups ); + } + + return retElements; +} + +//-------------------------------------------------------------- +std::vector< std::shared_ptr > Group::getAllChildGroups() { + vector< shared_ptr > retGroups; + for( auto ele : mChildren ) { + _getAllGroupsRecursive( retGroups, ele ); + } + return retGroups; +} + +// flattens out hierarchy // +//-------------------------------------------------------------- +void Group::_getAllElementsRecursive( vector< shared_ptr >& aElesToReturn, shared_ptr aele, bool aBIncludeGroups ) { + if( aele ) { + if( aele->isGroup() ) { + shared_ptr tgroup = std::dynamic_pointer_cast(aele); + if(aBIncludeGroups) {aElesToReturn.push_back(tgroup);} + for( auto ele : tgroup->getChildren() ) { + _getAllElementsRecursive( aElesToReturn, ele, aBIncludeGroups ); + } + } else { + aElesToReturn.push_back( aele ); + } + } +} + +//-------------------------------------------------------------- +void Group::_getAllGroupsRecursive( std::vector< std::shared_ptr >& aGroupsToReturn, std::shared_ptr aele ) { + if( aele ) { + if( aele->isGroup() ) { + shared_ptr tgroup = std::dynamic_pointer_cast(aele); + aGroupsToReturn.push_back(tgroup); + for( auto ele : tgroup->getChildren() ) { + if( ele->isGroup() ) { + _getAllGroupsRecursive( aGroupsToReturn, ele ); + } + } + } + } +} + +//-------------------------------------------------------------- +std::vector< std::shared_ptr > Group::getAllElementsWithPath() { + auto allKids = getAllChildren(false); + std::vector< std::shared_ptr > rpaths; + for( auto kid : allKids ) { + if( kid->getType() == TYPE_RECTANGLE ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); + } else if( kid->getType() == TYPE_PATH ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); + } else if( kid->getType() == TYPE_CIRCLE ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); + } else if( kid->getType() == TYPE_ELLIPSE ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); + } + } + + return rpaths; +} + +//-------------------------------------------------------------- +shared_ptr Group::getElementForName( std::string aPath, bool bStrict ) { + + vector< std::string > tsearches; + if( ofIsStringInString( aPath, ":" ) ) { + tsearches = ofSplitString( aPath, ":" ); + } else { + tsearches.push_back( aPath ); + } + + shared_ptr temp; + _getElementForNameRecursive( tsearches, temp, mChildren, bStrict ); + return temp; +} + +//-------------------------------------------------------------- +std::vector< std::shared_ptr > Group::getChildrenForName( const std::string& aname, bool bStrict ) { + std::vector< std::shared_ptr > relements; + for( auto& kid : mChildren ) { + if( bStrict ) { + if( kid->getName() == aname ) { + relements.push_back(kid); + } + } else { + if( ofIsStringInString( kid->getName(), aname )) { + relements.push_back(kid); + } + } + } + return relements; +} + +//-------------------------------------------------------------- +void Group::_getElementForNameRecursive( vector& aNamesToFind, shared_ptr& aTarget, vector< shared_ptr >& aElements, bool bStrict ) { + + if( aNamesToFind.size() < 1 ) { + return; + } + if(aTarget) { + return; + } + + bool bKeepGoing = false; + std::string nameToFind = aNamesToFind[0]; + if( aNamesToFind.size() > 1 ) { + bKeepGoing = (aNamesToFind[0] == "*"); + nameToFind = aNamesToFind[1]; + } + for( std::size_t i = 0; i < aElements.size(); i++ ) { + bool bFound = false; + if(bStrict) { + if( aElements[i]->getName() == nameToFind ) { + bFound = true; + } + } else { +// std::cout << "Group::_getElementForNameRecursive: ele name: " << aElements[i]->getName() << " nameToFind: " << nameToFind << " keep going: " << bKeepGoing << std::endl; + if( ofIsStringInString( aElements[i]->getName(), nameToFind )) { + bFound = true; + } + + if (!bFound && aElements[i]->getType() == TYPE_TEXT) { + + if (aElements[i]->getName() == "No Name") { + // the ids for text block in illustrator are weird, + // so try to grab the name from the text contents // + auto etext = std::dynamic_pointer_cast(aElements[i]); + if (etext) { + if (etext->textSpans.size()) { +// cout << "Searching for " << aNamesToFind[0] << " in " << etext->textSpans.front().text << endl; + if(ofIsStringInString( etext->textSpans.front()->text, aNamesToFind[0] )) { + bFound = true; + } + } + } + } + } + } + + if( bFound && !bKeepGoing ) { + if( !bKeepGoing && aNamesToFind.size() > 0 ) { + aNamesToFind.erase( aNamesToFind.begin() ); + } + if(aNamesToFind.size() == 0 ) { + aTarget = aElements[i]; + break; + } else { + if( aElements[i]->getType() == TYPE_GROUP ) { + auto tgroup = std::dynamic_pointer_cast( aElements[i] ); + _getElementForNameRecursive( aNamesToFind, aTarget, tgroup->getChildren(), bStrict ); + break; + } + } + } + + if( bKeepGoing ) { + if( bFound ) { +// std::cout << "Group::_getElementForNameRecursive: SETTING TARGET: " << aElements[i]->getName() << " keep going: " << bKeepGoing << std::endl; + aTarget = aElements[i]; + break; + } else { + if( aElements[i]->getType() == TYPE_GROUP ) { +// std::cout << "Group::_getElementForNameRecursive: FOUND A GROUP, But still going: " << aElements[i]->getName() << " keep going: " << bKeepGoing << std::endl; + auto tgroup = std::dynamic_pointer_cast( aElements[i] ); + _getElementForNameRecursive( aNamesToFind, aTarget, tgroup->getChildren(), bStrict ); + } + } + } + } +} + +//-------------------------------------------------------------- +bool Group::replace( shared_ptr aOriginal, shared_ptr aNew ) { + bool bReplaced = false; + _replaceElementRecursive( aOriginal, aNew, mChildren, bReplaced ); + return bReplaced; +} + +//-------------------------------------------------------------- +void Group::_replaceElementRecursive( shared_ptr aTarget, shared_ptr aNew, vector< shared_ptr >& aElements, bool& aBSuccessful ) { + for( std::size_t i = 0; i < aElements.size(); i++ ) { + bool bFound = false; + if( aTarget == aElements[i] ) { + bFound = true; + aBSuccessful = true; + aElements[i] = aNew; + aNew->layer = aTarget->layer; + break; + } + if( !bFound ) { + if( aElements[i]->getType() == TYPE_GROUP ) { + auto tgroup = std::dynamic_pointer_cast( aElements[i] ); + _replaceElementRecursive(aTarget, aNew, tgroup->mChildren, aBSuccessful ); + } + } + } +} + +//-------------------------------------------------------------- +string Group::toString(int nlevel) { + + string tstr = ""; + for( int k = 0; k < nlevel; k++ ) { + tstr += " "; + } + tstr += getTypeAsString() + " - " + getName() + "\n"; + + if( mChildren.size() ) { + for( std::size_t i = 0; i < mChildren.size(); i++ ) { + tstr += mChildren[i]->toString( nlevel+1); + } + } + + return tstr; +} + + +//-------------------------------------------------------------- +void Group::disableColors() { + auto telements = getAllChildren(false); + for( auto& ele : telements ) { + ele->setUseShapeColor(false); + } +} + +//-------------------------------------------------------------- +void Group::enableColors() { + auto telements = getAllChildren(false); + for( auto& ele : telements ) { + ele->setUseShapeColor(true); + } +} + + + + + + + + diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h new file mode 100755 index 00000000000..2b9d0259b3f --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -0,0 +1,198 @@ +// +// ofxSvgGroup.h +// +// Created by Nick Hardeman on 7/31/15. +// + +#pragma once +#include "ofxSvgElements.h" + +namespace ofx::svg { +class Group : public Element { +public: + + virtual SvgType getType() override {return TYPE_GROUP;} + + virtual void draw() override; + + std::size_t getNumChildren();// override; + std::vector< std::shared_ptr >& getChildren(); + std::vector< std::shared_ptr > getAllChildren(bool aBIncludeGroups); + std::vector< std::shared_ptr > getAllChildGroups(); + + template + std::vector< std::shared_ptr > getElementsForType( std::string aPathToGroup="", bool bStrict= false ) { + std::shared_ptr temp = std::make_shared(); + auto sType = temp->getType(); + + std::vector< std::shared_ptr > telements; + std::vector< std::shared_ptr > elementsToSearch; + if( aPathToGroup == "" ) { + elementsToSearch = mChildren; + } else { + std::shared_ptr< Element > temp = getElementForName( aPathToGroup, bStrict ); + if( temp ) { + if( temp->isGroup() ) { + std::shared_ptr< Group > tgroup = std::dynamic_pointer_cast( temp ); + elementsToSearch = tgroup->mChildren; + } + } + } + + if( !elementsToSearch.size() && mChildren.size() ) { + ofLogNotice("ofx::svg::Group") << __FUNCTION__ << " did not find group with name: " << aPathToGroup; + elementsToSearch = mChildren; + } + + for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { + if( elementsToSearch[i]->getType() == sType ) { + telements.push_back( std::dynamic_pointer_cast< ofxSvgType>(elementsToSearch[i]) ); + } + } + return telements; + } + + template + std::shared_ptr getFirstElementForType( std::string aPathToGroup="", bool bStrict= false ) { + auto eles = getElementsForType(aPathToGroup, bStrict ); + if( eles.size() > 0 ) { + return eles[0]; + } + return std::shared_ptr(); + } + + template + std::vector< std::shared_ptr > getAllElementsForType() { + + auto temp = std::make_shared(); + auto sType = temp->getType(); + + std::vector< std::shared_ptr > telements; + auto elementsToSearch = getAllChildren(true); + + for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { + if( elementsToSearch[i]->getType() == sType ) { + telements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); + } + } + return telements; + } + + template + std::vector< std::shared_ptr > getAllElementsContainingNameForType(std::string aname) { + + auto temp = std::make_shared(); + auto sType = temp->getType(); + + std::vector< std::shared_ptr > telements; + // get all children does not include groups, since it's meant to flatten hierarchy + + if( sType == ofx::svg::TYPE_GROUP ) { + auto groupsToSearch = getAllChildGroups(); + + for( std::size_t i = 0; i < groupsToSearch.size(); i++ ) { + if( groupsToSearch[i]->getType() == sType ) { + if( ofIsStringInString(groupsToSearch[i]->getName(), aname) ) { + telements.push_back( std::dynamic_pointer_cast(groupsToSearch[i]) ); + } + } + } + + } else { + auto elementsToSearch = getAllChildren(false); + for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { + if( elementsToSearch[i]->getType() == sType ) { + if( ofIsStringInString(elementsToSearch[i]->getName(), aname) ) { + telements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); + } + } + } + } + + + return telements; + } + + std::vector< std::shared_ptr > getAllElementsWithPath(); + + std::shared_ptr getElementForName( std::string aPath, bool bStrict = false ); + std::vector< std::shared_ptr > getChildrenForName( const std::string& aname, bool bStrict = false ); + + template + std::vector< std::shared_ptr > getChildrenForTypeForName( const std::string& aname, bool bStrict = false ) { + auto temp = std::make_shared(); + auto sType = temp->getType(); + + std::vector< std::shared_ptr > relements; + for( auto& kid : mChildren ) { + if( kid->getType() != sType ) {continue;} + if( bStrict ) { + if( kid->getName() == aname ) { + relements.push_back( std::dynamic_pointer_cast(kid)); + } + } else { + if( ofIsStringInString( kid->getName(), aname )) { + relements.push_back(std::dynamic_pointer_cast(kid)); + } + } + } + return relements; + } + + template + std::shared_ptr< ofxSvgType > get( std::string aPath, bool bStrict = false ) { + auto stemp = std::dynamic_pointer_cast( getElementForName( aPath, bStrict ) ); + return stemp; + } + + template + std::shared_ptr< ofxSvgType > get( int aIndex ) { + auto stemp = std::dynamic_pointer_cast( mChildren[ aIndex ] ); + return stemp; + } + + bool replace( std::shared_ptr aOriginal, std::shared_ptr aNew ); + + // adding + template + std::shared_ptr add(std::string aname) { + auto element = std::make_shared(); + element->name = aname; + mChildren.push_back(element); + return element; + }; + + void add( std::shared_ptr aele ) { mChildren.push_back(aele); } + + virtual std::string toString(int nlevel = 0) override; + + void disableColors(); + void enableColors(); + +protected: + void _getElementForNameRecursive( std::vector< std::string >& aNamesToFind, std::shared_ptr& aTarget, std::vector< std::shared_ptr >& aElements, bool bStrict ); + void _getAllElementsRecursive( std::vector< std::shared_ptr >& aElesToReturn, std::shared_ptr aele, bool aBIncludeGroups ); + + void _getAllGroupsRecursive( std::vector< std::shared_ptr >& aGroupsToReturn, std::shared_ptr aele ); + + void _replaceElementRecursive( std::shared_ptr aTarget, std::shared_ptr aNew, std::vector< std::shared_ptr >& aElements, bool& aBSuccessful ); + + std::vector< std::shared_ptr > mChildren; +}; +} + + + + + + + + + + + + + + + + From 0eb4ae638b4d896296f80f95f40a185ffb1018f7 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Sat, 15 Feb 2025 14:17:46 -0500 Subject: [PATCH 2/8] Fix atan2 and return path instead of string for windows. --- addons/ofxSvg/src/ofxSvg.cpp | 2 +- addons/ofxSvg/src/ofxSvgElements.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 00b3ae6dd22..cdbeb81b9c0 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -2156,7 +2156,7 @@ float ofxSvg::_getZRotationRadians(const glm::mat4& amat) { } // Compute rotation angle using atan2 - float angleRadians = std::atan2f(xAxis.y, xAxis.x); + float angleRadians = atan2f(xAxis.y, xAxis.x); return angleRadians; } diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index 6f9627450a6..bad5c2bcf8d 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -135,7 +135,7 @@ class Image : public Element { virtual void draw() override; glm::vec2 getAnchorPointForPercent( float ax, float ay ); - std::string getFilePath() { return filepath; } + std::filesystem::path getFilePath() { return filepath; } void setColor( ofColor aColor ) { color = aColor; From b8ea2ab83e567c497ae8f0f201c5f1c794e77fea Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Sat, 15 Feb 2025 17:40:41 -0500 Subject: [PATCH 3/8] namespace for return type to address windows problems. --- addons/ofxSvg/src/ofxSvg.cpp | 32 ++++++++++++++--------------- addons/ofxSvg/src/ofxSvg.h | 36 +++++++++++++++++++++++++++------ addons/ofxSvg/src/ofxSvgCss.cpp | 4 ++-- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index cdbeb81b9c0..cdc38b8d7c9 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -1859,7 +1859,7 @@ std::shared_ptr ofxSvg::addGroup(std::string aname) { } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::add( const ofPath& apath ) { +std::shared_ptr ofxSvg::add( const ofPath& apath ) { auto path = std::make_shared(); path->path = apath; // _config(path); @@ -1872,7 +1872,7 @@ std::shared_ptr ofxSvg::add( const ofPath& apath ) { } //-------------------------------------------------------------- -std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apaths ) { +std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apaths ) { std::vector< std::shared_ptr > rpaths; for( auto& path : apaths ) { rpaths.push_back( add(path) ); @@ -1881,7 +1881,7 @@ std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apa } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::add( const ofPolyline& apoly ) { +std::shared_ptr ofxSvg::add( const ofPolyline& apoly ) { if( apoly.size() < 2 ) { return std::shared_ptr(); } @@ -1902,7 +1902,7 @@ std::shared_ptr ofxSvg::add( const ofPolyline& apoly ) { } //-------------------------------------------------------------- -std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apolys ) { +std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apolys ) { std::vector< std::shared_ptr > rpaths; for( auto& poly : apolys ) { rpaths.push_back( add(poly) ); @@ -1911,12 +1911,12 @@ std::vector< std::shared_ptr > ofxSvg::add( const std::vector& } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::add( const ofRectangle& arect ) { +std::shared_ptr ofxSvg::add( const ofRectangle& arect ) { return add( arect, 0.0f); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRadius ) { +std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRadius ) { auto rect = std::make_shared(); rect->rectangle = arect; // rect->pos = arect.getPosition(); @@ -1932,12 +1932,12 @@ std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRa } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( float aradius ) { +std::shared_ptr ofxSvg::addCircle( float aradius ) { return addCircle(glm::vec2(0.f, 0.f), aradius ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float aradius ) { +std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float aradius ) { auto circle = std::make_shared(); // circle->pos = apos; _applyModelMatrixToElement( circle, apos ); @@ -1953,22 +1953,22 @@ std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float aradius } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( const glm::vec3& apos, float aradius ) { +std::shared_ptr ofxSvg::addCircle( const glm::vec3& apos, float aradius ) { return addCircle( glm::vec2(apos.x, apos.y), aradius ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( const float& ax, const float& ay, float aradius ) { +std::shared_ptr ofxSvg::addCircle( const float& ax, const float& ay, float aradius ) { return addCircle( glm::vec2(ax, ay), aradius ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( float aradiusX, float aradiusY ) { +std::shared_ptr ofxSvg::addEllipse( float aradiusX, float aradiusY ) { return addEllipse( glm::vec2(0.f, 0.f), aradiusX, aradiusY ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ) { +std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ) { auto ellipse = std::make_shared(); _applyModelMatrixToElement( ellipse, apos ); @@ -1985,22 +1985,22 @@ std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float aradiu } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ) { +std::shared_ptr ofxSvg::addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ) { return addEllipse( glm::vec2(apos.x, apos.y), aradiusX, aradiusY ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ) { +std::shared_ptr ofxSvg::addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ) { return addEllipse( glm::vec2(ax, ay), aradiusX, aradiusY ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addImage( const of::filesystem::path& apath, const ofTexture& atex ) { +std::shared_ptr ofxSvg::addImage( const of::filesystem::path& apath, const ofTexture& atex ) { return addImage(glm::vec2(0.f, 0.f), apath, atex ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ) { +std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ) { auto img = std::make_shared(); img->filepath = apath; img->width = atex.getWidth(); diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index aeba25d50f9..e80f3521d06 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -119,25 +119,49 @@ class ofxSvg : public ofx::svg::Group { /// \param arect an ofRectangle with the dimensions of the bounds. void setBounds( const ofRectangle& arect ); - + /// \brief Push a svg group to be active for adding elements to. + /// \param aname The path to the svg group in the document, if a group is already pushed, search in that group. void pushGroup( const std::string& aname ); + /// \brief Push a svg group to be active for adding elements to. + /// \param aname The group to push and make active. void pushGroup( const std::shared_ptr& agroup ); + /// \brief Remove the most recent pushed group if any. void popGroup(); - + /// \brief Set the current fill color. Any subsequent items using a fill color will adopt this color. + /// \param acolor is the color to set. void setFillColor(ofColor acolor); + /// \brief Set if items should be filled or not. Any subsequent added items will use this value. + /// \param abFilled should the items be filled or not. void setFilled(bool abFilled); - + /// \brief Set the current stroke color. Any subsequent items using a stroke color will adopt this color. + /// \param acolor is the color to set. void setStrokeColor(ofColor acolor); + /// \brief Set the current stroke width. Any subsequent items using a stroke width will adopt this value. + /// \param aLineWidth is the width of the lines. void setStrokeWidth(float aLineWidth); + /// \brief Set if items should have a stroke or not. Any subsequent items using a stroke will adopt this value. + /// \param abStroke activates or deactivates strokes. void setHasStroke(bool abStroke); - + /// \brief Set the circle resolution for rendering. + /// Set this value before calling load. + /// \param ac (int) the resolution to use. void setCircleResolution( int ac ) { mCircleResolution = ac; } + /// \brief Set the curve resolution for rendering. + /// Set this value before calling load. + /// \param ac (int) the resolution to use. void setCurveResolution( int ac ) { mCurveResolution = ac; } + /// \brief Get the circle resolution for rendering. + /// \return int of the circle resolution. int getCircleResolution() { return mCircleResolution; } + /// \brief Get the curve resolution for rendering. + /// \return int of the circle resolution. int getCurveResolution() { return mCurveResolution; }; - + /// \brief Get the current css used for items. + /// \return ofx::svg::CssClass. ofx::svg::CssClass& getCurrentCss() { return mCurrentCss;} - + /// \brief Add a group to the document. This will also push back the group as current. + /// \param aname is the name of the group. + /// \return std::shared_ptr as the group that was created. std::shared_ptr addGroup(std::string aname); std::shared_ptr add( const ofPath& apath ); diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index 7d5d6d18f37..8fe561b6983 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -132,10 +132,10 @@ ofColor CssClass::sGetColor(const std::string& astr ) { float CssClass::sGetFloat(const std::string& astr) { if( astr.empty() ) return 0.f; - bool bHasPix = false; +// bool bHasPix = false; std::string cstr = astr; if( ofIsStringInString(cstr, "px")) { - bHasPix = true; +// bHasPix = true; ofStringReplace(cstr, "px", ""); } return ofToFloat( cstr ); From 5ba6ce9986a1649bfe5a39c364e60f9d7f561e67 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Tue, 18 Feb 2025 10:33:50 -0500 Subject: [PATCH 4/8] filesystem path vs. string. --- addons/ofxSvg/src/ofxSvg.cpp | 6 ++++-- addons/ofxSvg/src/ofxSvg.h | 2 +- addons/ofxSvg/src/ofxSvgCss.cpp | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index cdc38b8d7c9..6fbb5d4dd02 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -476,7 +476,9 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > if(hattr) image->height = hattr.getFloatValue(); auto xlinkAttr = tnode.getAttribute("xlink:href"); if( xlinkAttr ) { - image->filepath = folderPath+xlinkAttr.getValue(); + image->filepath = folderPath; + image->filepath.append(xlinkAttr.getValue()); +// image->filepath = folderPath+xlinkAttr.getValue(); } telement = image; @@ -2269,7 +2271,7 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele } if( !timage->getFilePath().empty() ) { if( auto xattr = txml.appendAttribute("xlink:href")) { - xattr.set(timage->getFilePath()); + xattr.set(timage->getFilePath().string()); } } diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index e80f3521d06..6641dc66858 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -202,7 +202,7 @@ class ofxSvg : public ofx::svg::Group { protected: std::string fontsDirectory = ""; - std::string folderPath, svgPath; + of::filesystem::path folderPath, svgPath; ofRectangle mViewbox; ofRectangle mBounds; void validateXmlSvgRoot( ofXml& aRootSvgNode ); diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index 8fe561b6983..a6b9261961b 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -145,11 +145,11 @@ float CssClass::sGetFloat(const std::string& astr) { bool CssClass::addProperties( std::string aPropertiesString ) { if( aPropertiesString.size() > 0 ) { auto propertiesStr = ofSplitString(aPropertiesString, ";", true, true); - int pindex = 0; +// int pindex = 0; for( auto& propStr : propertiesStr ) { // std::cout << " " << pindex << " - property: " << propStr << std::endl; addProperty(propStr); - pindex++; +// pindex++; } // for( auto& prop : properties ) { From b08f331ebe0300a0cc088bc1e97be00b6ae87d93 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Tue, 18 Feb 2025 11:28:09 -0500 Subject: [PATCH 5/8] remove ofx::svg namespace and replace with ofxSvg for easier compliance with Windows. --- addons/ofxSvg/src/ofxSvg.cpp | 289 +++++++++++++-------------- addons/ofxSvg/src/ofxSvg.h | 92 +++++---- addons/ofxSvg/src/ofxSvgCss.cpp | 99 ++++----- addons/ofxSvg/src/ofxSvgCss.h | 16 +- addons/ofxSvg/src/ofxSvgElements.cpp | 109 +++++----- addons/ofxSvg/src/ofxSvgElements.h | 52 ++--- addons/ofxSvg/src/ofxSvgGroup.cpp | 79 ++++---- addons/ofxSvg/src/ofxSvgGroup.h | 110 +++++----- 8 files changed, 401 insertions(+), 445 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 6fbb5d4dd02..c147d8d1e87 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -9,7 +9,6 @@ #include #include "ofGraphics.h" -using namespace ofx::svg; using std::string; using std::vector; using std::shared_ptr; @@ -55,7 +54,7 @@ bool ofxSvg::load( const of::filesystem::path& fileName ) { ofXml xml; if( !xml.load(tMainXmlBuffer )) { - ofLogWarning(moduleName()) << " unable to load svg from " << fileName; + ofLogWarning("ofxSvg") << " unable to load svg from " << fileName; return false; } @@ -116,30 +115,30 @@ bool ofxSvg::loadFromString(const std::string& data, std::string url) { } if(svgNode) { - ofLogVerbose(moduleName()) << svgNode.findFirst("style").toString() << " bounds: " << mBounds; + ofLogVerbose("ofxSvg") << svgNode.findFirst("style").toString() << " bounds: " << mBounds; } else { - ofLogVerbose( moduleName() ) << __FUNCTION__ << " : NO svgNode: "; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " : NO svgNode: "; } ofXml styleXmlNode = svgNode.findFirst("//style"); if( styleXmlNode ) { - ofLogVerbose(moduleName()) << __FUNCTION__ << " : STYLE NODE" << styleXmlNode.getAttribute("type").getValue() << " string: " << styleXmlNode.getValue(); + ofLogVerbose("ofxSvg") << __FUNCTION__ << " : STYLE NODE" << styleXmlNode.getAttribute("type").getValue() << " string: " << styleXmlNode.getValue(); mSvgCss.parse(styleXmlNode.getValue()); - ofLogVerbose(moduleName()) << "-----------------------------"; + ofLogVerbose("ofxSvg") << "-----------------------------"; ofLogVerbose() << mSvgCss.toString(); - ofLogVerbose(moduleName()) << "-----------------------------"; + ofLogVerbose("ofxSvg") << "-----------------------------"; } else { - ofLogVerbose(moduleName()) << __FUNCTION__ << " : NO STYLE NODE"; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " : NO STYLE NODE"; } // the defs are added in the _parseXmlNode function // _parseXmlNode( svgNode, mChildren ); - ofLogVerbose(moduleName()) << " number of defs elements: " << mDefElements.size(); + ofLogVerbose("ofxSvg") << " number of defs elements: " << mDefElements.size(); } return true; @@ -148,7 +147,7 @@ bool ofxSvg::loadFromString(const std::string& data, std::string url) { //-------------------------------------------------------------- bool ofxSvg::reload() { if( svgPath.empty() ) { - ofLogError(moduleName()) << __FUNCTION__ << " : svg path is empty, please call load with file path before calling reload"; + ofLogError("ofxSvg") << __FUNCTION__ << " : svg path is empty, please call load with file path before calling reload"; return false; } return load( svgPath ); @@ -259,7 +258,7 @@ ofPath& ofxSvg::getPathAt(int n) { getPaths(); } if( n < 0 || n >= mPaths.size() ) { - ofLogWarning(moduleName()) << "getPathAt: " << n << " out of bounds for number of paths: " << mPaths.size(); + ofLogWarning("ofxSvg") << "getPathAt: " << n << " out of bounds for number of paths: " << mPaths.size(); return sDummyPath; } return mPaths[n]; @@ -385,7 +384,7 @@ string ofxSvg::cleanString( string aStr, string aReplace ) { } //-------------------------------------------------------------- -void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr >& aElements ) { +void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr >& aElements ) { auto kids = aParentNode.getChildren(); for( auto& kid : kids ) { @@ -393,20 +392,20 @@ void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr >& a auto fkid = kid.getFirstChild(); if( fkid ) { mCurrentSvgCss.reset(); - auto tgroup = std::make_shared(); + auto tgroup = std::make_shared(); tgroup->layer = mCurrentLayer += 1.0; auto idattr = kid.getAttribute("id"); if( idattr ) { tgroup->name = idattr.getValue(); } - mCurrentSvgCss = std::make_shared( _parseStyle(kid) ); + mCurrentSvgCss = std::make_shared( _parseStyle(kid) ); aElements.push_back( tgroup ); _parseXmlNode( kid, tgroup->getChildren() ); } } else if( kid.getName() == "defs") { - ofLogVerbose(moduleName()) << __FUNCTION__ << " found a defs node."; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " found a defs node."; _parseXmlNode(kid, mDefElements ); } else { @@ -418,43 +417,43 @@ void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr >& a } //-------------------------------------------------------------- -bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr >& aElements ) { - shared_ptr telement; +bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr >& aElements ) { + shared_ptr telement; if( tnode.getName() == "use") { if( auto hrefAtt = tnode.getAttribute("xlink:href")) { - ofLogVerbose(moduleName()) << "found a use node with href " << hrefAtt.getValue(); + ofLogVerbose("ofxSvg") << "found a use node with href " << hrefAtt.getValue(); std::string href = hrefAtt.getValue(); if( href.size() > 1 && href[0] == '#' ) { // try to find by id href = href.substr(1, std::string::npos); - ofLogVerbose(moduleName()) << "going to look for href " << href; + ofLogVerbose("ofxSvg") << "going to look for href " << href; for( auto & def : mDefElements ) { if( def->name == href ) { - if( def->getType() == ofx::svg::TYPE_RECTANGLE ) { - auto drect = std::dynamic_pointer_cast(def); - auto nrect = std::make_shared( *drect ); + if( def->getType() == ofxSvgType::TYPE_RECTANGLE ) { + auto drect = std::dynamic_pointer_cast(def); + auto nrect = std::make_shared( *drect ); telement = nrect; - } else if( def->getType() == ofx::svg::TYPE_IMAGE ) { - auto dimg = std::dynamic_pointer_cast(def); - auto nimg = std::make_shared( *dimg ); - ofLogVerbose(moduleName()) << "created an image node with filepath: " << nimg->getFilePath(); + } else if( def->getType() == ofxSvgType::TYPE_IMAGE ) { + auto dimg = std::dynamic_pointer_cast(def); + auto nimg = std::make_shared( *dimg ); + ofLogVerbose("ofxSvg") << "created an image node with filepath: " << nimg->getFilePath(); telement = nimg; - } else if( def->getType() == ofx::svg::TYPE_ELLIPSE ) { - auto dell= std::dynamic_pointer_cast(def); - auto nell = std::make_shared( *dell ); + } else if( def->getType() == ofxSvgType::TYPE_ELLIPSE ) { + auto dell= std::dynamic_pointer_cast(def); + auto nell = std::make_shared( *dell ); telement = nell; - } else if( def->getType() == ofx::svg::TYPE_CIRCLE ) { - auto dcir= std::dynamic_pointer_cast(def); - auto ncir = std::make_shared( *dcir ); + } else if( def->getType() == ofxSvgType::TYPE_CIRCLE ) { + auto dcir= std::dynamic_pointer_cast(def); + auto ncir = std::make_shared( *dcir ); telement = ncir; - } else if( def->getType() == ofx::svg::TYPE_PATH ) { - auto dpat= std::dynamic_pointer_cast(def); - auto npat = std::make_shared( *dpat ); + } else if( def->getType() == ofxSvgType::TYPE_PATH ) { + auto dpat= std::dynamic_pointer_cast(def); + auto npat = std::make_shared( *dpat ); telement = npat; - } else if( def->getType() == ofx::svg::TYPE_TEXT ) { - auto dtex = std::dynamic_pointer_cast(def); - auto ntex = std::make_shared( *dtex ); + } else if( def->getType() == ofxSvgType::TYPE_TEXT ) { + auto dtex = std::dynamic_pointer_cast(def); + auto ntex = std::make_shared( *dtex ); telement = ntex; } else { ofLogWarning("Parser") << "could not find type for def : " << def->name; @@ -463,13 +462,13 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > } } } else { - ofLogWarning(moduleName()) << "could not parse use node with href : " << href; + ofLogWarning("ofxSvg") << "could not parse use node with href : " << href; } } else { - ofLogWarning(moduleName()) << "found a use node but no href!"; + ofLogWarning("ofxSvg") << "found a use node but no href!"; } } else if( tnode.getName() == "image" ) { - auto image = std::make_shared(); + auto image = std::make_shared(); auto wattr = tnode.getAttribute("width"); if(wattr) image->width = wattr.getFloatValue(); auto hattr = tnode.getAttribute("height"); @@ -483,7 +482,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > telement = image; } else if( tnode.getName() == "ellipse" ) { - auto ellipse = std::make_shared(); + auto ellipse = std::make_shared(); auto cxAttr = tnode.getAttribute("cx"); if(cxAttr) ellipse->pos.x = cxAttr.getFloatValue(); auto cyAttr = tnode.getAttribute("cy"); @@ -503,7 +502,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > telement = ellipse; } else if( tnode.getName() == "circle" ) { - auto circle = std::make_shared(); + auto circle = std::make_shared(); auto cxAttr = tnode.getAttribute("cx"); if(cxAttr) circle->pos.x = cxAttr.getFloatValue(); auto cyAttr = tnode.getAttribute("cy"); @@ -523,7 +522,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > telement = circle; } else if( tnode.getName() == "line" ) { - auto telePath = std::make_shared(); + auto telePath = std::make_shared(); glm::vec3 p1 = {0.f, 0.f, 0.f}; glm::vec3 p2 = {0.f, 0.f, 0.f}; @@ -547,17 +546,17 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > telement = telePath; } else if(tnode.getName() == "polyline" || tnode.getName() == "polygon") { - auto tpath = std::make_shared(); + auto tpath = std::make_shared(); _parsePolylinePolygon(tnode, tpath); _applyStyleToPath( tnode, tpath ); telement = tpath; } else if( tnode.getName() == "path" ) { - auto tpath = std::make_shared(); + auto tpath = std::make_shared(); _parsePath( tnode, tpath ); _applyStyleToPath( tnode, tpath ); telement = tpath; } else if( tnode.getName() == "rect" ) { - auto rect = std::make_shared(); + auto rect = std::make_shared(); auto xattr = tnode.getAttribute("x"); if(xattr) rect->rectangle.x = xattr.getFloatValue(); auto yattr = tnode.getAttribute("y"); @@ -576,10 +575,10 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > rect->path.setCurveResolution(mCurveResolution); // make local so we can apply transform later in the function - if( !CssClass::sIsNone(rxAttr.getValue()) || !CssClass::sIsNone(ryAttr.getValue())) { + if( !ofxSvgCssClass::sIsNone(rxAttr.getValue()) || !ofxSvgCssClass::sIsNone(ryAttr.getValue())) { - rect->round = std::max(CssClass::sGetFloat(rxAttr.getValue()), - CssClass::sGetFloat(ryAttr.getValue())); + rect->round = std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), + ofxSvgCssClass::sGetFloat(ryAttr.getValue())); rect->path.rectRounded(0.f, 0.f, rect->rectangle.getWidth(), rect->rectangle.getHeight(), rect->round @@ -600,7 +599,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > } } else if( tnode.getName() == "text" ) { - auto text = std::make_shared(); + auto text = std::make_shared(); telement = text; // std::cout << "has kids: " << tnode.getFirstChild() << " node value: " << tnode.getValue() << std::endl; if( tnode.getFirstChild() ) { @@ -646,17 +645,17 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > telement->name = idAttr.getValue(); } - if( telement->getType() == TYPE_RECTANGLE || telement->getType() == TYPE_IMAGE || telement->getType() == TYPE_TEXT || telement->getType() == TYPE_CIRCLE || telement->getType() == TYPE_ELLIPSE ) { + if( telement->getType() == ofxSvgType::TYPE_RECTANGLE || telement->getType() == ofxSvgType::TYPE_IMAGE || telement->getType() == ofxSvgType::TYPE_TEXT || telement->getType() == ofxSvgType::TYPE_CIRCLE || telement->getType() == ofxSvgType::TYPE_ELLIPSE ) { auto transAttr = tnode.getAttribute("transform"); if( transAttr ) { // getTransformFromSvgMatrix( transAttr.getValue(), telement->pos, telement->scale.x, telement->scale.y, telement->rotation ); setTransformFromSvgMatrixString( transAttr.getValue(), telement ); } - std::vector typesToApplyTransformToPath = { - TYPE_RECTANGLE, - TYPE_CIRCLE, - TYPE_ELLIPSE + std::vector typesToApplyTransformToPath = { + ofxSvgType::TYPE_RECTANGLE, + ofxSvgType::TYPE_CIRCLE, + ofxSvgType::TYPE_ELLIPSE }; bool bApplyTransformToPath = false; @@ -668,7 +667,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > } if( bApplyTransformToPath ) { - auto epath = std::dynamic_pointer_cast( telement ); + auto epath = std::dynamic_pointer_cast( telement ); auto outlines = epath->path.getOutline(); auto transform = epath->getTransformMatrix(); for( auto& outline : outlines ) { @@ -695,8 +694,8 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr > } } - if( telement->getType() == TYPE_TEXT ) { - auto text = std::dynamic_pointer_cast( telement ); + if( telement->getType() == ofxSvgType::TYPE_TEXT ) { + auto text = std::dynamic_pointer_cast( telement ); text->ogPos = text->pos; text->create(); } @@ -836,15 +835,15 @@ glm::vec2 findArcCenter(glm::vec2 start, glm::vec2 end, double rx, double ry, do //-------------------------------------------------------------- -void ofxSvg::_parsePolylinePolygon( ofXml& tnode, std::shared_ptr aSvgPath ) { +void ofxSvg::_parsePolylinePolygon( ofXml& tnode, std::shared_ptr aSvgPath ) { auto pointsAttr = tnode.getAttribute("points"); if( !pointsAttr ) { - ofLogWarning(moduleName()) << __FUNCTION__ << " polyline or polygon does not have a points attriubute."; + ofLogWarning("ofxSvg") << __FUNCTION__ << " polyline or polygon does not have a points attriubute."; return; } if( pointsAttr.getValue().empty() ) { - ofLogWarning(moduleName()) << __FUNCTION__ << " polyline or polygon does not have points."; + ofLogWarning("ofxSvg") << __FUNCTION__ << " polyline or polygon does not have points."; return; } @@ -865,12 +864,12 @@ void ofxSvg::_parsePolylinePolygon( ofXml& tnode, std::shared_ptr aSvgPath } // reference: https://www.w3.org/TR/SVG2/paths.html#PathData //-------------------------------------------------------------- -void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { +void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { aSvgPath->path.clear(); auto dattr = tnode.getAttribute("d"); if( !dattr ) { - ofLogWarning(moduleName()) << __FUNCTION__ << " path node does not have d attriubute."; + ofLogWarning("ofxSvg") << __FUNCTION__ << " path node does not have d attriubute."; return; } @@ -891,13 +890,13 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { // ofLogNotice(moduleName()) << __FUNCTION__ << " dattr: " << ostring; if( ostring.empty() ) { - ofLogError(moduleName()) << __FUNCTION__ << " there is no data in the d string."; + ofLogError("ofxSvg") << __FUNCTION__ << " there is no data in the d string."; return; } std::size_t index = 0; if( ostring[index] != 'm' && ostring[index] != 'M' ) { - ofLogWarning(moduleName()) << __FUNCTION__ << " first char is not a m or M, ostring[index]: " << ostring[index]; + ofLogWarning("ofxSvg") << __FUNCTION__ << " first char is not a m or M, ostring[index]: " << ostring[index]; return; } @@ -1002,7 +1001,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } - ofLogVerbose(moduleName()) << "["< npositions= {glm::vec3(0.f, 0.f, 0.f)}; @@ -1082,10 +1081,10 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { glm::vec3(arcValues[5], arcValues[6], 0.0f) }; } else { - ofLogError(moduleName()) << "unable to parse arc command, incorrect number of parameters detected: " << arcValues.size(); - ofLogError(moduleName()) << "-- Arc values ---------------------- "; + ofLogError("ofxSvg") << "unable to parse arc command, incorrect number of parameters detected: " << arcValues.size(); + ofLogError("ofxSvg") << "-- Arc values ---------------------- "; for( std::size_t n = 0; n < arcValues.size(); n++ ) { - ofLogError(moduleName()) << n << ": " << arcValues[n]; + ofLogError("ofxSvg") << n << ": " << arcValues[n]; } } @@ -1110,7 +1109,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { currentPos = convertToAbsolute(bRelative, currentPos, tpositions ); npositions[3] = tpositions[0]; } else { - ofLogWarning("ofx::svg::Parser") << "invalid number of arc commands."; + ofLogWarning("ofxSvg") << "invalid number of arc commands."; } } else if( commandT == ofPath::Command::bezierTo ) { if( cchar == 'S' || cchar == 's' ) { @@ -1281,7 +1280,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { worderS = "counter clockwise"; } - ofLogVerbose("Arc winding order is ") << worderS << " order: " << windingOrder << " startDiff: " << startDiff << " endDiff: " << endDiff << " xAxisRotation: " << xAxisRotation; + ofLogVerbose("ofxSvg") << "Arc winding order is: " << worderS << " order: " << windingOrder << " startDiff: " << startDiff << " endDiff: " << endDiff << " xAxisRotation: " << xAxisRotation; ofPolyline tline; @@ -1333,7 +1332,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { npositions.clear(); npositions.push_back(ept); } else { - ofLogWarning("ofx::svg::Parser") << "unable to parse arc segment."; + ofLogWarning("ofxSvg") << "unable to parse arc segment."; } } @@ -1349,8 +1348,8 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } //-------------------------------------------------------------- -CssClass ofxSvg::_parseStyle( ofXml& anode ) { - CssClass css; +ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { + ofxSvgCssClass css; if( mCurrentSvgCss ) { // apply first if we have a global style // @@ -1414,7 +1413,7 @@ CssClass ofxSvg::_parseStyle( ofXml& anode ) { } //-------------------------------------------------------------- -void ofxSvg::_applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ) { +void ofxSvg::_applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ) { auto css = _parseStyle(tnode); // ofLogNotice("_applyStyleToElement" ) << " " << aEle->name << " -----"; if( css.hasAndIsNone("display")) { @@ -1424,13 +1423,13 @@ void ofxSvg::_applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ) } //-------------------------------------------------------------- -void ofxSvg::_applyStyleToPath( ofXml& tnode, std::shared_ptr aSvgPath ) { +void ofxSvg::_applyStyleToPath( ofXml& tnode, std::shared_ptr aSvgPath ) { auto css = _parseStyle(tnode); _applyStyleToPath(css, aSvgPath); } //-------------------------------------------------------------- -void ofxSvg::_applyStyleToPath( CssClass& aclass, std::shared_ptr aSvgPath ) { +void ofxSvg::_applyStyleToPath( ofxSvgCssClass& aclass, std::shared_ptr aSvgPath ) { // now lets figure out if there is any css applied // if( aclass.hasProperty("fill")) { @@ -1468,13 +1467,13 @@ void ofxSvg::_applyStyleToPath( CssClass& aclass, std::shared_ptr aSvgPath } //-------------------------------------------------------------- -void ofxSvg::_applyStyleToText( ofXml& anode, std::shared_ptr aTextSpan ) { +void ofxSvg::_applyStyleToText( ofXml& anode, std::shared_ptr aTextSpan ) { auto css = _parseStyle(anode); _applyStyleToText(css, aTextSpan); } //-------------------------------------------------------------- -void ofxSvg::_applyStyleToText( CssClass& aclass, std::shared_ptr aTextSpan ) { +void ofxSvg::_applyStyleToText( ofxSvgCssClass& aclass, std::shared_ptr aTextSpan ) { // default font family aTextSpan->fontFamily = aclass.getValue("font-family", "Arial"); aTextSpan->fontSize = aclass.getIntValue("font-size", 18 ); @@ -1487,7 +1486,7 @@ void ofxSvg::_applyStyleToText( CssClass& aclass, std::shared_ptr aele ) { +bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr aele ) { aele->scale = glm::vec2(1.0f, 1.0f); aele->rotation = 0.0; aele->mModelRotationPoint = glm::vec2(0.0f, 0.0f); //TODO: implement matrix push and pop structure, similar to renderers - ofLogVerbose(moduleName()) << __FUNCTION__ << " going to parse string: " << aStr << " pos: " << aele->pos; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " going to parse string: " << aStr << " pos: " << aele->pos; glm::mat4 mat = glm::mat4(1.f); if( ofIsStringInString(aStr, "translate")) { auto transStr = aStr; auto tp = _parseMatrixString(transStr, "translate", false ); - ofLogVerbose(moduleName()) << __FUNCTION__ << " translate: " << tp; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " translate: " << tp; // apos += tp; mat = glm::translate(glm::mat4(1.0f), glm::vec3(tp.x, tp.y, 0.0f)); } else { @@ -1579,7 +1578,7 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptrrotation; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " arotation: " << aele->rotation; } if( ofIsStringInString(aStr, "scale")) { @@ -1587,7 +1586,7 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptrscale.x = ts.x; aele->scale.y = ts.y; - ofLogVerbose(moduleName()) << __FUNCTION__ << " scale: " << ts; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " scale: " << ts; mat = glm::scale(mat, glm::vec3(aele->scale.x, aele->scale.y, 1.f)); } @@ -1649,7 +1648,7 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr aele ) { +std::string ofxSvg::getSvgMatrixStringFromElement( std::shared_ptr aele ) { // matrix(1 0 0 1 352.4516 349.0799)"> // there's probably a better way to determine if this should be rotated in a certain way @@ -1696,8 +1695,8 @@ std::string ofxSvg::getSvgMatrixStringFromElement( std::shared_ptr aele } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::_getTextSpanFromXmlNode( ofXml& anode ) { - auto tspan = std::make_shared();; +std::shared_ptr ofxSvg::_getTextSpanFromXmlNode( ofXml& anode ) { + auto tspan = std::make_shared(); string tText = anode.getValue(); float tx = 0; @@ -1788,11 +1787,11 @@ void ofxSvg::setBounds( const ofRectangle& arect ) { //-------------------------------------------------------------- void ofxSvg::pushGroup( const std::string& apath ) { - std::shared_ptr cgroup; + std::shared_ptr cgroup; if( mGroupStack.size() > 0 ) { - mGroupStack.back()->get( apath ); + mGroupStack.back()->get( apath ); } else { - cgroup = get( apath ); + cgroup = get( apath ); } if( cgroup ) { @@ -1803,7 +1802,7 @@ void ofxSvg::pushGroup( const std::string& apath ) { } //-------------------------------------------------------------- -void ofxSvg::pushGroup( const std::shared_ptr& agroup ) { +void ofxSvg::pushGroup( const std::shared_ptr& agroup ) { if( agroup ) { mGroupStack.push_back(agroup); } @@ -1852,8 +1851,8 @@ void ofxSvg::setHasStroke(bool abStroke) { } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addGroup(std::string aname) { - auto tgroup = std::make_shared(); +std::shared_ptr ofxSvg::addGroup(std::string aname) { + auto tgroup = std::make_shared(); tgroup->name = aname; _getPushedGroup()->add(tgroup); recalculateLayers(); @@ -1861,8 +1860,8 @@ std::shared_ptr ofxSvg::addGroup(std::string aname) { } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::add( const ofPath& apath ) { - auto path = std::make_shared(); +std::shared_ptr ofxSvg::add( const ofPath& apath ) { + auto path = std::make_shared(); path->path = apath; // _config(path); _applyModelMatrixToElement( path, glm::vec2(0.f, 0.f) ); @@ -1874,8 +1873,8 @@ std::shared_ptr ofxSvg::add( const ofPath& apath ) { } //-------------------------------------------------------------- -std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apaths ) { - std::vector< std::shared_ptr > rpaths; +std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apaths ) { + std::vector< std::shared_ptr > rpaths; for( auto& path : apaths ) { rpaths.push_back( add(path) ); } @@ -1883,9 +1882,9 @@ std::vector< std::shared_ptr > ofxSvg::add( const std::vector ofxSvg::add( const ofPolyline& apoly ) { +std::shared_ptr ofxSvg::add( const ofPolyline& apoly ) { if( apoly.size() < 2 ) { - return std::shared_ptr(); + return std::shared_ptr(); } ofPath opath; @@ -1904,8 +1903,8 @@ std::shared_ptr ofxSvg::add( const ofPolyline& apoly ) { } //-------------------------------------------------------------- -std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apolys ) { - std::vector< std::shared_ptr > rpaths; +std::vector< std::shared_ptr > ofxSvg::add( const std::vector& apolys ) { + std::vector< std::shared_ptr > rpaths; for( auto& poly : apolys ) { rpaths.push_back( add(poly) ); } @@ -1913,13 +1912,13 @@ std::vector< std::shared_ptr > ofxSvg::add( const std::vector ofxSvg::add( const ofRectangle& arect ) { +std::shared_ptr ofxSvg::add( const ofRectangle& arect ) { return add( arect, 0.0f); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRadius ) { - auto rect = std::make_shared(); +std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRadius ) { + auto rect = std::make_shared(); rect->rectangle = arect; // rect->pos = arect.getPosition(); _applyModelMatrixToElement( rect, arect.getPosition() ); @@ -1934,13 +1933,13 @@ std::shared_ptr ofxSvg::add( const ofRectangle& arect, floa } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( float aradius ) { +std::shared_ptr ofxSvg::addCircle( float aradius ) { return addCircle(glm::vec2(0.f, 0.f), aradius ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float aradius ) { - auto circle = std::make_shared(); +std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float aradius ) { + auto circle = std::make_shared(); // circle->pos = apos; _applyModelMatrixToElement( circle, apos ); circle->radius = aradius; @@ -1955,23 +1954,23 @@ std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, floa } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( const glm::vec3& apos, float aradius ) { +std::shared_ptr ofxSvg::addCircle( const glm::vec3& apos, float aradius ) { return addCircle( glm::vec2(apos.x, apos.y), aradius ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addCircle( const float& ax, const float& ay, float aradius ) { +std::shared_ptr ofxSvg::addCircle( const float& ax, const float& ay, float aradius ) { return addCircle( glm::vec2(ax, ay), aradius ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( float aradiusX, float aradiusY ) { +std::shared_ptr ofxSvg::addEllipse( float aradiusX, float aradiusY ) { return addEllipse( glm::vec2(0.f, 0.f), aradiusX, aradiusY ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ) { - auto ellipse = std::make_shared(); +std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ) { + auto ellipse = std::make_shared(); _applyModelMatrixToElement( ellipse, apos ); ellipse->radiusX = aradiusX; @@ -1987,23 +1986,23 @@ std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, fl } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ) { +std::shared_ptr ofxSvg::addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ) { return addEllipse( glm::vec2(apos.x, apos.y), aradiusX, aradiusY ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ) { +std::shared_ptr ofxSvg::addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ) { return addEllipse( glm::vec2(ax, ay), aradiusX, aradiusY ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addImage( const of::filesystem::path& apath, const ofTexture& atex ) { +std::shared_ptr ofxSvg::addImage( const of::filesystem::path& apath, const ofTexture& atex ) { return addImage(glm::vec2(0.f, 0.f), apath, atex ); } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ) { - auto img = std::make_shared(); +std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ) { + auto img = std::make_shared(); img->filepath = apath; img->width = atex.getWidth(); img->height = atex.getHeight(); @@ -2106,7 +2105,7 @@ void ofxSvg::drawDebug() { } //-------------------------------------------------------------- -ofx::svg::Group* ofxSvg::_getPushedGroup() { +ofxSvgGroup* ofxSvg::_getPushedGroup() { if( mGroupStack.size() > 0 ) { return mGroupStack.back().get(); } @@ -2119,7 +2118,7 @@ bool ofxSvg::_hasPushedMatrix() { } //-------------------------------------------------------------- -void ofxSvg::_applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ) { +void ofxSvg::_applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ) { if(_hasPushedMatrix() ) { aele->pos = aDefaultPos; aele->mModelPos = _getPos2d(mModelMatrix); @@ -2163,8 +2162,8 @@ float ofxSvg::_getZRotationRadians(const glm::mat4& amat) { } //-------------------------------------------------------------- -CssClass& ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath ) { - CssClass tcss; +ofxSvgCssClass& ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath ) { + ofxSvgCssClass tcss; // tcss.name = aSvgPath->name+"st"; tcss.name = "st"; if( aSvgPath->path.isFilled() ) { @@ -2187,24 +2186,18 @@ CssClass& ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath ) { } //-------------------------------------------------------------- -void ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ) { - -// ofLogNotice("Parser::_addCssClassFromPath") << mSvgCss.toString(); - +void ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ) { auto& css = _addCssClassFromPath(aSvgPath); - -// ofLogNotice("Parser::_addCssClassFromPath") << "adding: " << css.name << " fill: " << aSvgPath->getFillColor() << std::endl << mSvgCss.toString(); - if( auto xattr = anode.appendAttribute("class") ) { xattr.set(css.name); } } //-------------------------------------------------------------- -void ofxSvg::_addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ) { +void ofxSvg::_addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ) { if( !aSvgImage->isVisible() ) { - CssClass tcss; + ofxSvgCssClass tcss; tcss.name = "st"; tcss.addProperty("display", "none" ); @@ -2217,15 +2210,15 @@ void ofxSvg::_addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& ano } //-------------------------------------------------------------- -bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { - ofXml txml = aParentNode.appendChild( ofx::svg::Element::sGetSvgXmlName(aele->getType())); +bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { + ofXml txml = aParentNode.appendChild( ofxSvgElement::sGetSvgXmlName(aele->getType())); if( !aele->getName().empty() ) { if( auto iattr = txml.appendAttribute("id")) { iattr.set(aele->getName()); } } - if( aele->getType() == ofx::svg::TYPE_GROUP ) { - auto tgroup = std::dynamic_pointer_cast(aele); + if( aele->getType() == ofxSvgType::TYPE_GROUP ) { + auto tgroup = std::dynamic_pointer_cast(aele); if( tgroup ) { if( tgroup->getNumChildren() > 0 ) { for( auto& kid : tgroup->getChildren() ) { @@ -2233,8 +2226,8 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele } } } - } else if( aele->getType() == ofx::svg::TYPE_RECTANGLE ) { - auto trect = std::dynamic_pointer_cast(aele); + } else if( aele->getType() == ofxSvgType::TYPE_RECTANGLE ) { + auto trect = std::dynamic_pointer_cast(aele); _addCssClassFromPath( trect, txml ); if( auto xattr = txml.appendAttribute("x")) { @@ -2258,8 +2251,8 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele } } - } else if( aele->getType() == ofx::svg::TYPE_IMAGE ) { - auto timage = std::dynamic_pointer_cast(aele); + } else if( aele->getType() == ofxSvgType::TYPE_IMAGE ) { + auto timage = std::dynamic_pointer_cast(aele); _addCssClassFromImage( timage, txml ); @@ -2276,8 +2269,8 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele } - } else if( aele->getType() == ofx::svg::TYPE_ELLIPSE ) { - auto tellipse = std::dynamic_pointer_cast(aele); + } else if( aele->getType() == ofxSvgType::TYPE_ELLIPSE ) { + auto tellipse = std::dynamic_pointer_cast(aele); _addCssClassFromPath( tellipse, txml ); if( auto xattr = txml.appendAttribute("cx")) { @@ -2293,8 +2286,8 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele xattr.set(tellipse->radiusY); } - } else if( aele->getType() == ofx::svg::TYPE_CIRCLE ) { - auto tcircle = std::dynamic_pointer_cast(aele); + } else if( aele->getType() == ofxSvgType::TYPE_CIRCLE ) { + auto tcircle = std::dynamic_pointer_cast(aele); _addCssClassFromPath( tcircle, txml ); if( auto xattr = txml.appendAttribute("cx")) { @@ -2307,8 +2300,8 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele xattr.set(tcircle->getRadius()); } - } else if( aele->getType() == ofx::svg::TYPE_PATH ) { - auto tpath = std::dynamic_pointer_cast(aele); + } else if( aele->getType() == ofxSvgType::TYPE_PATH ) { + auto tpath = std::dynamic_pointer_cast(aele); _addCssClassFromPath( tpath, txml ); @@ -2382,12 +2375,12 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele - } else if( aele->getType() == ofx::svg::TYPE_TEXT ) { + } else if( aele->getType() == ofxSvgType::TYPE_TEXT ) { // TODO: Maybe at some point ;/ } // figure out if we need a transform attribute - if( aele->getType() == TYPE_IMAGE || aele->rotation != 0.0f || aele->scale.x != 1.0f || aele->scale.y != 1.0f ) { + if( aele->getType() == ofxSvgType::TYPE_IMAGE || aele->rotation != 0.0f || aele->scale.x != 1.0f || aele->scale.y != 1.0f ) { if( auto xattr = txml.appendAttribute("transform")) { xattr.set( getSvgMatrixStringFromElement(aele) ); } diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index 6641dc66858..19b77b9110a 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -13,11 +13,10 @@ /// \file /// ofxSVG is used for loading, manipulating, rendering and saving of SVG files. -//namespace ofx::svg { -class ofxSvg : public ofx::svg::Group { +class ofxSvg : public ofxSvgGroup { public: - virtual ofx::svg::SvgType getType() override {return ofx::svg::TYPE_DOCUMENT;} + virtual ofxSvgType getType() override {return ofxSvgType::TYPE_DOCUMENT;} /// \brief Loads an SVG file from the provided filename. /// \return true if the load was successful. @@ -70,7 +69,7 @@ class ofxSvg : public ofx::svg::Group { /// The elements pos, scale and rotation is set. /// \param aStr svg transform string. /// \param aele ofx::svg::Element to be updated. - bool setTransformFromSvgMatrixString( std::string aStr, std::shared_ptr aele ); + bool setTransformFromSvgMatrixString( std::string aStr, std::shared_ptr aele ); /// \brief Return a string used to represent matrix transforms in svg /// The matrix can be represented as an array of values like : /// Or using individual components like tranform(translateX translateY), scale(scaleX scaleY) and rotate(degrees ptx pty ) @@ -78,7 +77,7 @@ class ofxSvg : public ofx::svg::Group { /// Reference: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform /// \param aele the element to extract the svg matrix from using its pos, scale and rotation properties. /// \return string that represents the svg matrix for using in a svg file. - std::string getSvgMatrixStringFromElement( std::shared_ptr aele ); + std::string getSvgMatrixStringFromElement( std::shared_ptr aele ); /// \brief The viewbox width. /// \return float width of the viewbox. float getWidth() const; @@ -124,7 +123,7 @@ class ofxSvg : public ofx::svg::Group { void pushGroup( const std::string& aname ); /// \brief Push a svg group to be active for adding elements to. /// \param aname The group to push and make active. - void pushGroup( const std::shared_ptr& agroup ); + void pushGroup( const std::shared_ptr& agroup ); /// \brief Remove the most recent pushed group if any. void popGroup(); /// \brief Set the current fill color. Any subsequent items using a fill color will adopt this color. @@ -158,33 +157,33 @@ class ofxSvg : public ofx::svg::Group { int getCurveResolution() { return mCurveResolution; }; /// \brief Get the current css used for items. /// \return ofx::svg::CssClass. - ofx::svg::CssClass& getCurrentCss() { return mCurrentCss;} + ofxSvgCssClass& getCurrentCss() { return mCurrentCss;} /// \brief Add a group to the document. This will also push back the group as current. /// \param aname is the name of the group. /// \return std::shared_ptr as the group that was created. - std::shared_ptr addGroup(std::string aname); + std::shared_ptr addGroup(std::string aname); - std::shared_ptr add( const ofPath& apath ); - std::vector< std::shared_ptr > add( const std::vector& apaths ); + std::shared_ptr add( const ofPath& apath ); + std::vector< std::shared_ptr > add( const std::vector& apaths ); - std::shared_ptr add( const ofPolyline& apoly ); - std::vector< std::shared_ptr > add( const std::vector& apolys ); + std::shared_ptr add( const ofPolyline& apoly ); + std::vector< std::shared_ptr > add( const std::vector& apolys ); - std::shared_ptr add( const ofRectangle& arect ); - std::shared_ptr add( const ofRectangle& arect, float aRoundRadius ); + std::shared_ptr add( const ofRectangle& arect ); + std::shared_ptr add( const ofRectangle& arect, float aRoundRadius ); - std::shared_ptr addCircle( float aradius ); - std::shared_ptr addCircle( const glm::vec2& apos, float aradius ); - std::shared_ptr addCircle( const glm::vec3& apos, float aradius ); - std::shared_ptr addCircle( const float& ax, const float& ay, float aradius ); + std::shared_ptr addCircle( float aradius ); + std::shared_ptr addCircle( const glm::vec2& apos, float aradius ); + std::shared_ptr addCircle( const glm::vec3& apos, float aradius ); + std::shared_ptr addCircle( const float& ax, const float& ay, float aradius ); - std::shared_ptr addEllipse( float aradiusX, float aradiusY ); - std::shared_ptr addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ); - std::shared_ptr addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ); - std::shared_ptr addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ); + std::shared_ptr addEllipse( float aradiusX, float aradiusY ); + std::shared_ptr addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ); + std::shared_ptr addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ); + std::shared_ptr addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ); - std::shared_ptr addImage( const of::filesystem::path& apath, const ofTexture& atex ); - std::shared_ptr addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ); + std::shared_ptr addImage( const of::filesystem::path& apath, const ofTexture& atex ); + std::shared_ptr addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ); // adapted from ofGLProgrammableRenderer for some sort of conformity void pushMatrix(); @@ -207,49 +206,49 @@ class ofxSvg : public ofx::svg::Group { ofRectangle mBounds; void validateXmlSvgRoot( ofXml& aRootSvgNode ); std::string cleanString( std::string aStr, std::string aReplace ); - void _parseXmlNode( ofXml& aParentNode, std::vector< std::shared_ptr >& aElements ); - bool _addElementFromXmlNode( ofXml& tnode, std::vector< std::shared_ptr >& aElements ); + void _parseXmlNode( ofXml& aParentNode, std::vector< std::shared_ptr >& aElements ); + bool _addElementFromXmlNode( ofXml& tnode, std::vector< std::shared_ptr >& aElements ); - void _parsePolylinePolygon( ofXml& tnode, std::shared_ptr aSvgPath ); + void _parsePolylinePolygon( ofXml& tnode, std::shared_ptr aSvgPath ); // reference: https://www.w3.org/TR/SVG/paths.html - void _parsePath( ofXml& tnode, std::shared_ptr aSvgPath ); + void _parsePath( ofXml& tnode, std::shared_ptr aSvgPath ); - ofx::svg::CssClass _parseStyle( ofXml& tnode ); - void _applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ); - void _applyStyleToPath( ofXml& tnode, std::shared_ptr aSvgPath ); - void _applyStyleToPath( ofx::svg::CssClass& aclass, std::shared_ptr aSvgPath ); - void _applyStyleToText( ofXml& tnode, std::shared_ptr aTextSpan ); - void _applyStyleToText( ofx::svg::CssClass& aclass, std::shared_ptr aTextSpan ); + ofxSvgCssClass _parseStyle( ofXml& tnode ); + void _applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ); + void _applyStyleToPath( ofXml& tnode, std::shared_ptr aSvgPath ); + void _applyStyleToPath( ofxSvgCssClass& aclass, std::shared_ptr aSvgPath ); + void _applyStyleToText( ofXml& tnode, std::shared_ptr aTextSpan ); + void _applyStyleToText( ofxSvgCssClass& aclass, std::shared_ptr aTextSpan ); glm::vec3 _parseMatrixString(const std::string& input, const std::string& aprefix, bool abDefaultZero ); - std::shared_ptr _getTextSpanFromXmlNode( ofXml& anode ); + std::shared_ptr _getTextSpanFromXmlNode( ofXml& anode ); - ofx::svg::Group* _getPushedGroup(); + ofxSvgGroup* _getPushedGroup(); bool _hasPushedMatrix(); - void _applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ); + void _applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ); glm::vec2 _getPos2d(const glm::mat4& amat); glm::vec2 _getScale2d(const glm::mat4& amat); float _getZRotationRadians( const glm::mat4& amat ); - ofx::svg::CssClass& _addCssClassFromPath( std::shared_ptr aSvgPath ); - void _addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ); - void _addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ); - bool _toXml( ofXml& aParentNode, std::shared_ptr aele ); + ofxSvgCssClass& _addCssClassFromPath( std::shared_ptr aSvgPath ); + void _addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ); + void _addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ); + bool _toXml( ofXml& aParentNode, std::shared_ptr aele ); unsigned int mCurrentLayer = 0; std::string mUnitStr = "px"; - ofx::svg::CssStyleSheet mSvgCss; - ofx::svg::CssClass mCurrentCss; + ofxSvgCssStyleSheet mSvgCss; + ofxSvgCssClass mCurrentCss; ofColor mFillColor, mStrokeColor; - std::vector< std::shared_ptr > mGroupStack; + std::vector< std::shared_ptr > mGroupStack; - std::shared_ptr mCurrentSvgCss; + std::shared_ptr mCurrentSvgCss; - std::vector< std::shared_ptr > mDefElements; + std::vector< std::shared_ptr > mDefElements; // just used for debugging std::vector mCPoints; @@ -266,7 +265,6 @@ class ofxSvg : public ofx::svg::Group { mutable std::vector mPaths; }; -//} diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index a6b9261961b..f36d8d19f5b 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -12,7 +12,6 @@ #include #include -using namespace ofx::svg; std::map sCommonColors = { {"white", ofColor(255, 255, 255)}, @@ -68,13 +67,13 @@ std::map sCommonColors = { }; //-------------------------------------------------------------- -void CssClass::clear() { +void ofxSvgCssClass::clear() { properties.clear(); name = "default"; } //-------------------------------------------------------------- -std::string CssClass::sRgbaToHexString(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { +std::string ofxSvgCssClass::sRgbaToHexString(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { std::stringstream ss; ss << std::hex << std::setfill('0') << std::uppercase; ss << std::setw(2) << static_cast(r); @@ -85,7 +84,7 @@ std::string CssClass::sRgbaToHexString(unsigned char r, unsigned char g, unsigne } //-------------------------------------------------------------- -std::string CssClass::sRgbToHexString(unsigned char r, unsigned char g, unsigned char b) { +std::string ofxSvgCssClass::sRgbToHexString(unsigned char r, unsigned char g, unsigned char b) { std::stringstream ss; ss << std::hex << std::setfill('0') << std::uppercase; ss << std::setw(2) << static_cast(r); @@ -95,7 +94,7 @@ std::string CssClass::sRgbToHexString(unsigned char r, unsigned char g, unsigned } //-------------------------------------------------------------- -bool CssClass::sIsNone( const std::string& astr ) { +bool ofxSvgCssClass::sIsNone( const std::string& astr ) { if( astr.empty() ) { return true; } @@ -106,7 +105,7 @@ bool CssClass::sIsNone( const std::string& astr ) { } //-------------------------------------------------------------- -ofColor CssClass::sGetColor(const std::string& astr ) { +ofColor ofxSvgCssClass::sGetColor(const std::string& astr ) { bool bHasHash = false; std::string cstr = astr; if( ofIsStringInString(cstr, "#")) { @@ -129,7 +128,7 @@ ofColor CssClass::sGetColor(const std::string& astr ) { } //-------------------------------------------------------------- -float CssClass::sGetFloat(const std::string& astr) { +float ofxSvgCssClass::sGetFloat(const std::string& astr) { if( astr.empty() ) return 0.f; // bool bHasPix = false; @@ -142,7 +141,7 @@ float CssClass::sGetFloat(const std::string& astr) { } //-------------------------------------------------------------- -bool CssClass::addProperties( std::string aPropertiesString ) { +bool ofxSvgCssClass::addProperties( std::string aPropertiesString ) { if( aPropertiesString.size() > 0 ) { auto propertiesStr = ofSplitString(aPropertiesString, ";", true, true); // int pindex = 0; @@ -160,7 +159,7 @@ bool CssClass::addProperties( std::string aPropertiesString ) { } //-------------------------------------------------------------- -bool CssClass::addProperty( std::string aPropString ) { +bool ofxSvgCssClass::addProperty( std::string aPropString ) { auto splitProps = ofSplitString(aPropString, ":", true ); if( splitProps.size() == 2 ) { return addProperty(splitProps[0], splitProps[1]); @@ -169,46 +168,34 @@ bool CssClass::addProperty( std::string aPropString ) { } //-------------------------------------------------------------- -bool CssClass::addProperty( std::string aName, std::string avalue ) { +bool ofxSvgCssClass::addProperty( std::string aName, std::string avalue ) { if( !aName.empty() && !avalue.empty() ) { Property newProp; newProp.srcString = avalue; ofStringReplace(newProp.srcString, ";", ""); -// if( ofIsStringInString(newProp.srcString, "px")) { -// newProp.bInPixels = true; -// ofStringReplace(newProp.srcString, "px", ""); -// } -// if( ofIsStringInString(newProp.srcString, "#")) { -// newProp.bHasHash = true; -// ofStringReplace(newProp.srcString, "#", ""); -// } - ofStringReplace(newProp.srcString, "'", ""); properties[aName] = newProp; -// if( newProp.bInPixels ) { -// getFloatValue(aName, 1.f); -// } return true; } return false; } //-------------------------------------------------- -bool CssClass::addProperty( const std::string& aName, const Property& aprop ) { +bool ofxSvgCssClass::addProperty( const std::string& aName, const Property& aprop ) { return addProperty(aName, aprop.srcString); } //-------------------------------------------------- -bool CssClass::addProperty( const std::string& aName, const float& avalue ) { - ofx::svg::CssClass::Property prop; +bool ofxSvgCssClass::addProperty( const std::string& aName, const float& avalue ) { + ofxSvgCssClass::Property prop; prop.fvalue = avalue; prop.srcString = ofToString(avalue); return addProperty(aName, prop ); } //-------------------------------------------------- -bool CssClass::addProperty( const std::string& aName, const ofColor& acolor ) { - ofx::svg::CssClass::Property prop; +bool ofxSvgCssClass::addProperty( const std::string& aName, const ofColor& acolor ) { + ofxSvgCssClass::Property prop; prop.cvalue = acolor; prop.srcString = sRgbToHexString(acolor.r, acolor.g, acolor.b); // ofLogNotice(" CssClass::addProperty") << prop.srcString << " color: " << acolor; @@ -216,47 +203,47 @@ bool CssClass::addProperty( const std::string& aName, const ofColor& acolor ) { } //-------------------------------------------------- -bool CssClass::setFillColor(const ofColor& acolor) { +bool ofxSvgCssClass::setFillColor(const ofColor& acolor) { return addProperty("fill", acolor); } //-------------------------------------------------- -bool CssClass::setNoFill() { +bool ofxSvgCssClass::setNoFill() { return addProperty("fill", "none" ); } //-------------------------------------------------- -bool CssClass::isFilled() { +bool ofxSvgCssClass::isFilled() { return !isNone("fill"); } //-------------------------------------------------- -bool CssClass::setStrokeColor(const ofColor& acolor) { +bool ofxSvgCssClass::setStrokeColor(const ofColor& acolor) { return addProperty("stroke", acolor); } //-------------------------------------------------- -bool CssClass::setStrokeWidth( const float& awidth ) { +bool ofxSvgCssClass::setStrokeWidth( const float& awidth ) { return addProperty("stroke-width", awidth); } //-------------------------------------------------- -bool CssClass::setNoStroke() { +bool ofxSvgCssClass::setNoStroke() { return addProperty("stroke", "none" ); } //-------------------------------------------------- -bool CssClass::hasStroke() { +bool ofxSvgCssClass::hasStroke() { return !isNone("stroke"); } //-------------------------------------------------- -bool CssClass::hasProperty( const std::string& akey ) { +bool ofxSvgCssClass::hasProperty( const std::string& akey ) { return (properties.count(akey) > 0); } //-------------------------------------------------- -CssClass::Property& CssClass::getProperty( const std::string& akey ) { +ofxSvgCssClass::Property& ofxSvgCssClass::getProperty( const std::string& akey ) { if( properties.count(akey) < 1 ) { return dummyProp; } @@ -264,7 +251,7 @@ CssClass::Property& CssClass::getProperty( const std::string& akey ) { } //-------------------------------------------------- -bool CssClass::isNone(const std::string& akey) { +bool ofxSvgCssClass::isNone(const std::string& akey) { if( properties.count(akey) < 1 ) { return true; } @@ -272,7 +259,7 @@ bool CssClass::isNone(const std::string& akey) { } //-------------------------------------------------- -bool CssClass::hasAndIsNone(const std::string& akey) { +bool ofxSvgCssClass::hasAndIsNone(const std::string& akey) { if( hasProperty(akey)) { return sIsNone( properties[akey].srcString ); } @@ -280,7 +267,7 @@ bool CssClass::hasAndIsNone(const std::string& akey) { } //-------------------------------------------------- -std::string CssClass::getValue(const std::string& akey, const std::string& adefault) { +std::string ofxSvgCssClass::getValue(const std::string& akey, const std::string& adefault) { if( properties.count(akey) < 1 ) { return adefault; } @@ -292,7 +279,7 @@ std::string CssClass::getValue(const std::string& akey, const std::string& adefa } //-------------------------------------------------- -int CssClass::getIntValue(const std::string& akey, const int& adefault) { +int ofxSvgCssClass::getIntValue(const std::string& akey, const int& adefault) { if( properties.count(akey) < 1 ) { return adefault; } @@ -304,26 +291,19 @@ int CssClass::getIntValue(const std::string& akey, const int& adefault) { } //-------------------------------------------------- -float CssClass::getFloatValue(const std::string& akey, const float& adefault) { +float ofxSvgCssClass::getFloatValue(const std::string& akey, const float& adefault) { if( properties.count(akey) < 1 ) { return adefault; } auto& prop = properties[akey]; if( !prop.fvalue.has_value() ) { prop.fvalue = sGetFloat(prop.srcString); -// bool bHasPix = false; -// std::string cstr = prop.srcString; -// if( ofIsStringInString(cstr, "px")) { -// bHasPix = true; -// ofStringReplace(cstr, "px", ""); -// } -// prop.fvalue = ofToFloat( cstr ); } return prop.fvalue.value(); } //-------------------------------------------------- -ofColor CssClass::getColor(const std::string& akey) { +ofColor ofxSvgCssClass::getColor(const std::string& akey) { if( properties.count(akey) < 1 ) { return ofColor(255); } @@ -335,7 +315,7 @@ ofColor CssClass::getColor(const std::string& akey) { } //-------------------------------------------------- -std::string CssClass::toString(bool aBPrettyPrint) { +std::string ofxSvgCssClass::toString(bool aBPrettyPrint) { std::stringstream ss; for( auto& piter : properties ) { if(aBPrettyPrint) { @@ -347,7 +327,7 @@ std::string CssClass::toString(bool aBPrettyPrint) { } //-------------------------------------------------- -bool CssStyleSheet::parse( std::string aCssString ) { +bool ofxSvgCssStyleSheet::parse( std::string aCssString ) { if( aCssString.empty() ) { return false; } @@ -418,38 +398,37 @@ bool CssStyleSheet::parse( std::string aCssString ) { } //-------------------------------------------------- -void CssStyleSheet::clear() { +void ofxSvgCssStyleSheet::clear() { classes.clear(); } //-------------------------------------------------- -CssClass& CssStyleSheet::addClass(std::string aname) { +ofxSvgCssClass& ofxSvgCssStyleSheet::addClass(std::string aname) { if( hasClass(aname) ) { -// ofLogWarning("ofx::svg2") << "CssStyleSheet already has class " << aname <<"."; return classes[aname]; } - CssClass tclass; + ofxSvgCssClass tclass; tclass.name = aname; classes[aname] = tclass; return classes[aname]; } //-------------------------------------------------- -bool CssStyleSheet::hasClass(const std::string& aname) { +bool ofxSvgCssStyleSheet::hasClass(const std::string& aname) { return classes.count(aname) > 0; } //-------------------------------------------------- -CssClass& CssStyleSheet::getClass( const std::string& aname ) { +ofxSvgCssClass& ofxSvgCssStyleSheet::getClass( const std::string& aname ) { if( hasClass(aname)) { return classes[aname]; } - ofLogWarning("ofx::svg2::CssStyleSheet") << "could not find class " << aname; + ofLogWarning("ofxSvgCssStyleSheet") << "could not find class " << aname; return dummyClass; } //-------------------------------------------------- -CssClass& CssStyleSheet::getAddClass( CssClass& aclass ) { +ofxSvgCssClass& ofxSvgCssStyleSheet::getAddClass( ofxSvgCssClass& aclass ) { for( auto& tclass : classes ) { bool bFoundAll = true; @@ -505,7 +484,7 @@ CssClass& CssStyleSheet::getAddClass( CssClass& aclass ) { } //-------------------------------------------------- -std::string CssStyleSheet::toString(bool aBPrettyPrint) { +std::string ofxSvgCssStyleSheet::toString(bool aBPrettyPrint) { std::stringstream ss; for( auto& citer : classes ) { ss << std::endl; diff --git a/addons/ofxSvg/src/ofxSvgCss.h b/addons/ofxSvg/src/ofxSvgCss.h index ff3e1c1367c..04794767092 100644 --- a/addons/ofxSvg/src/ofxSvgCss.h +++ b/addons/ofxSvg/src/ofxSvgCss.h @@ -11,8 +11,7 @@ #include "ofLog.h" #include "ofXml.h" -namespace ofx::svg { -class CssClass { +class ofxSvgCssClass { public: // adding this Optional class since std::optional is not a part of all std:: distributions at the moment, looking at you gcc < 10 @@ -114,23 +113,22 @@ class CssClass { Property dummyProp; }; -class CssStyleSheet { +class ofxSvgCssStyleSheet { public: bool parse( std::string aCssString ); void clear(); - CssClass& addClass( std::string aname ); + ofxSvgCssClass& addClass( std::string aname ); bool hasClass( const std::string& aname ); - CssClass& getClass( const std::string& aname ); + ofxSvgCssClass& getClass( const std::string& aname ); - CssClass& getAddClass( CssClass& aclass ); + ofxSvgCssClass& getAddClass( ofxSvgCssClass& aclass ); - std::unordered_map classes; + std::unordered_map classes; std::string toString(bool aBPrettyPrint=true); protected: - CssClass dummyClass; + ofxSvgCssClass dummyClass; }; -} diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index ef11faf1258..968e47e0f99 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -9,39 +9,38 @@ using std::vector; using std::string; -using namespace ofx::svg; -std::map< string, Text::Font > Text::fonts; -ofTrueTypeFont Text::defaultFont; +std::map< string, ofxSvgText::Font > ofxSvgText::fonts; +ofTrueTypeFont ofxSvgText::defaultFont; //-------------------------------------------------------------- -std::string Element::sGetTypeAsString(SvgType atype) { +std::string ofxSvgElement::sGetTypeAsString(ofxSvgType atype) { switch (atype) { - case TYPE_GROUP: + case ofxSvgType::TYPE_GROUP: return "Group"; break; - case TYPE_RECTANGLE: + case ofxSvgType::TYPE_RECTANGLE: return "Rectangle"; break; - case TYPE_IMAGE: + case ofxSvgType::TYPE_IMAGE: return "Image"; break; - case TYPE_ELLIPSE: + case ofxSvgType::TYPE_ELLIPSE: return "Ellipse"; break; - case TYPE_CIRCLE: + case ofxSvgType::TYPE_CIRCLE: return "Circle"; break; - case TYPE_PATH: + case ofxSvgType::TYPE_PATH: return "Path"; break; - case TYPE_TEXT: + case ofxSvgType::TYPE_TEXT: return "Text"; break; - case TYPE_DOCUMENT: + case ofxSvgType::TYPE_DOCUMENT: return "Document"; break; - case TYPE_ELEMENT: + case ofxSvgType::TYPE_ELEMENT: return "Element"; break; default: @@ -51,48 +50,48 @@ std::string Element::sGetTypeAsString(SvgType atype) { } //-------------------------------------------------------------- -std::string Element::sGetSvgXmlName(SvgType atype) { +std::string ofxSvgElement::sGetSvgXmlName(ofxSvgType atype) { switch (atype) { - case TYPE_GROUP: + case ofxSvgType::TYPE_GROUP: return "g"; break; - case TYPE_RECTANGLE: + case ofxSvgType::TYPE_RECTANGLE: return "rect"; break; - case TYPE_IMAGE: + case ofxSvgType::TYPE_IMAGE: return "image"; break; - case TYPE_ELLIPSE: + case ofxSvgType::TYPE_ELLIPSE: return "ellipse"; break; - case TYPE_CIRCLE: + case ofxSvgType::TYPE_CIRCLE: return "circle"; break; - case TYPE_PATH: + case ofxSvgType::TYPE_PATH: return "path"; break; - case TYPE_TEXT: + case ofxSvgType::TYPE_TEXT: return "text"; break; - case TYPE_DOCUMENT: + case ofxSvgType::TYPE_DOCUMENT: return "svg"; break; - case TYPE_ELEMENT: - return "Element"; + case ofxSvgType::TYPE_ELEMENT: + return "element"; break; default: break; } - return "Unknown"; + return "unknown"; } //-------------------------------------------------------------- -string Element::getTypeAsString() { +string ofxSvgElement::getTypeAsString() { return sGetTypeAsString(getType()); } //-------------------------------------------------------------- -string Element::toString( int nlevel ) { +string ofxSvgElement::toString( int nlevel ) { string tstr = ""; for( int k = 0; k < nlevel; k++ ) { @@ -117,7 +116,7 @@ string Element::toString( int nlevel ) { //} //-------------------------------------------------------------- -glm::mat4 Element::getTransformMatrix() { +glm::mat4 ofxSvgElement::getTransformMatrix() { glm::mat4 rmat = glm::translate(glm::mat4(1.0f), glm::vec3(pos.x, pos.y, 0.0f)); if( rotation != 0.0f ) { glm::quat rq = glm::angleAxis(ofDegToRad(rotation), glm::vec3(0.f, 0.f, 1.0f )); @@ -130,7 +129,7 @@ glm::mat4 Element::getTransformMatrix() { }; //-------------------------------------------------------------- -ofNode Element::getNodeTransform() { +ofNode ofxSvgElement::getNodeTransform() { ofNode tnode;// = ofxSvgBase::getNodeTransform(); tnode.setPosition(pos.x, pos.y, 0.0f); if( rotation != 0.0f ) { @@ -146,12 +145,12 @@ ofNode Element::getNodeTransform() { #pragma mark - Image //-------------------------------------------------------------- -ofRectangle Image::getRectangle() { +ofRectangle ofxSvgImage::getRectangle() { return ofRectangle(pos.x, pos.y, getWidth(), getHeight()); } //-------------------------------------------------------------- -void Image::draw() { +void ofxSvgImage::draw() { if( !bTryLoad ) { img.load( getFilePath() ); bTryLoad = true; @@ -171,7 +170,7 @@ void Image::draw() { } //-------------------------------------------------------------- -glm::vec2 Image::getAnchorPointForPercent( float ax, float ay ) { +glm::vec2 ofxSvgImage::getAnchorPointForPercent( float ax, float ay ) { glm::vec2 ap = glm::vec2( width * ax * scale.x, height * ay * scale.y ); ap = glm::rotate(ap, glm::radians(rotation)); return ap; @@ -180,7 +179,7 @@ glm::vec2 Image::getAnchorPointForPercent( float ax, float ay ) { #pragma mark - Text //-------------------------------------------------------------- -void Text::create() { +void ofxSvgText::create() { meshes.clear(); // now lets sort the text based on meshes that we need to create // @@ -240,7 +239,7 @@ void Text::create() { string tfontPath = tfont.fontFamily; if (bHasFontDirectory) { - ofLogNotice(moduleName()) << __FUNCTION__ << " : " << tfont.fontFamily << " : starting off searching directory : " << fontsDirectory; + ofLogNotice("ofxSvgText") << __FUNCTION__ << " : " << tfont.fontFamily << " : starting off searching directory : " << fontsDirectory; string tNewFontPath = ""; bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, tfont.fontFamily, tNewFontPath); if (bFoundTheFont) { @@ -262,7 +261,7 @@ void Text::create() { }*/ } - ofLogNotice(moduleName()) << __FUNCTION__ << " : Trying to load font from: " << tfontPath; + ofLogNotice("ofxSvgText") << __FUNCTION__ << " : Trying to load font from: " << tfontPath; if (tfontPath == "") { bFontLoadOk = false; @@ -276,7 +275,7 @@ void Text::create() { // tfont.sizes[ vIt->first ] = datFontTho; tfont.textures[ vIt->first ] = tfont.sizes[ vIt->first ].getFontTexture(); } else { - ofLogError(moduleName()) << __FUNCTION__ << " : error loading font family: " << tfont.fontFamily << " size: " << vIt->first; + ofLogError("ofxSvgText") << __FUNCTION__ << " : error loading font family: " << tfont.fontFamily << " size: " << vIt->first; tfont.sizes.erase(vIt->first); } } @@ -288,7 +287,7 @@ void Text::create() { ofMesh& tmesh = meshMap[ vIt->first ]; if( !tfont.sizes.count( vIt->first ) ) { - ofLogError(moduleName()) << __FUNCTION__ << " : Could not find that font size in the map: " << vIt->first; + ofLogError("ofxSvgText") << __FUNCTION__ << " : Could not find that font size in the map: " << vIt->first; continue; } @@ -341,7 +340,7 @@ void Text::create() { } //-------------------------------------------------------------- -void Text::draw() { +void ofxSvgText::draw() { if( !isVisible() ) return; // map< string, map > meshes; if(bUseShapeColor) { @@ -404,7 +403,7 @@ void Text::draw() { } //-------------------------------------------------------------- -void Text::draw(const std::string &astring, bool abCentered ) { +void ofxSvgText::draw(const std::string &astring, bool abCentered ) { if( textSpans.size() > 0 ) { ofPushMatrix(); { ofTranslate( pos.x, pos.y ); @@ -412,12 +411,12 @@ void Text::draw(const std::string &astring, bool abCentered ) { textSpans[0]->draw(astring, abCentered ); } ofPopMatrix(); } else { - ofLogVerbose(moduleName()) << __FUNCTION__ << " : no text spans to draw with."; + ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; } } //-------------------------------------------------------------- -void Text::draw(const std::string& astring, const ofColor& acolor, bool abCentered ) { +void ofxSvgText::draw(const std::string& astring, const ofColor& acolor, bool abCentered ) { if( textSpans.size() > 0 ) { ofPushMatrix(); { ofTranslate( pos.x, pos.y ); @@ -425,18 +424,18 @@ void Text::draw(const std::string& astring, const ofColor& acolor, bool abCenter textSpans[0]->draw(astring, acolor, abCentered ); } ofPopMatrix(); } else { - ofLogVerbose(moduleName()) << __FUNCTION__ << " : no text spans to draw with."; + ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; } } //-------------------------------------------------------------- -bool Text::_recursiveFontDirSearch(const string& afile, const string& aFontFamToLookFor, string& fontpath) { +bool ofxSvgText::_recursiveFontDirSearch(const string& afile, const string& aFontFamToLookFor, string& fontpath) { if (fontpath != "") { return true; } ofFile tfFile( afile, ofFile::Reference ); if (tfFile.isDirectory()) { - ofLogVerbose(moduleName()) << __FUNCTION__ << " : searching in directory : " << afile << " | " << ofGetFrameNum(); + ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : searching in directory : " << afile << " | " << ofGetFrameNum(); ofDirectory tdir; tdir.listDir(afile); tdir.sort(); @@ -450,14 +449,14 @@ bool Text::_recursiveFontDirSearch(const string& afile, const string& aFontFamTo } else { if ( tfFile.getExtension() == "ttf" || tfFile.getExtension() == "otf") { if (ofToLower( tfFile.getBaseName() ) == ofToLower(aFontFamToLookFor)) { - ofLogNotice(moduleName()) << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; + ofLogNotice("ofxSvgText") << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; fontpath = tfFile.getAbsolutePath(); return true; } string tAltFileName = ofToLower(tfFile.getBaseName()); ofStringReplace(tAltFileName, " ", "-"); if (tAltFileName == ofToLower(aFontFamToLookFor)) { - ofLogNotice(moduleName()) << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; + ofLogNotice("ofxSvgText") << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; fontpath = tfFile.getAbsolutePath(); return true; } @@ -468,8 +467,8 @@ bool Text::_recursiveFontDirSearch(const string& afile, const string& aFontFamTo // must return a reference for some reason here // //-------------------------------------------------------------- -ofTrueTypeFont& Text::TextSpan::getFont() { - if( Text::fonts.count( fontFamily ) > 0 ) { +ofTrueTypeFont& ofxSvgText::TextSpan::getFont() { + if( ofxSvgText::fonts.count( fontFamily ) > 0 ) { Font& tfont = fonts[ fontFamily ]; if( tfont.sizes.count(fontSize) > 0 ) { return tfont.sizes[ fontSize ]; @@ -479,12 +478,12 @@ ofTrueTypeFont& Text::TextSpan::getFont() { } //-------------------------------------------------------------- -void Text::TextSpan::draw(const std::string &astring, bool abCentered ) { +void ofxSvgText::TextSpan::draw(const std::string &astring, bool abCentered ) { draw( astring, color, abCentered ); } //-------------------------------------------------------------- -void Text::TextSpan::draw(const std::string &astring, const ofColor& acolor, bool abCentered ) { +void ofxSvgText::TextSpan::draw(const std::string &astring, const ofColor& acolor, bool abCentered ) { ofSetColor( acolor ); auto& cfont = getFont(); ofRectangle tempBounds = cfont.getStringBoundingBox( astring, 0, 0 ); @@ -495,27 +494,27 @@ void Text::TextSpan::draw(const std::string &astring, const ofColor& acolor, boo } //-------------------------------------------------------------- -ofTrueTypeFont& Text::getFont() { +ofTrueTypeFont& ofxSvgText::getFont() { if( textSpans.size() > 0 ) { return textSpans[0]->getFont(); } - ofLogWarning(moduleName()) << __FUNCTION__ << " : no font detected from text spans, returning default font."; + ofLogWarning("ofxSvgText") << __FUNCTION__ << " : no font detected from text spans, returning default font."; return defaultFont; } //-------------------------------------------------------------- -ofColor Text::getColor() { +ofColor ofxSvgText::getColor() { if( textSpans.size() > 0 ) { return textSpans[0]->color; } - ofLogWarning(moduleName()) << __FUNCTION__ << " : no font detected from text spans, returning path fill color."; + ofLogWarning("ofxSvgText") << __FUNCTION__ << " : no font detected from text spans, returning path fill color."; return path.getFillColor(); } // get the bounding rect for all of the text spans in this svg'ness // should be called after create // //-------------------------------------------------------------- -ofRectangle Text::getRectangle() { +ofRectangle ofxSvgText::getRectangle() { ofRectangle temp( 0, 0, 1, 1 ); for( std::size_t i = 0; i < textSpans.size(); i++ ) { ofRectangle trect = textSpans[i]->rect; diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index bad5c2bcf8d..a4ccd301ba5 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -11,8 +11,7 @@ #include #include "ofTrueTypeFont.h" -namespace ofx::svg { -enum SvgType { +enum ofxSvgType { TYPE_ELEMENT = 0, TYPE_GROUP, TYPE_RECTANGLE, @@ -25,18 +24,18 @@ enum SvgType { TYPE_TOTAL }; -class Element { +class ofxSvgElement { public: - static std::string sGetTypeAsString(SvgType atype); - static std::string sGetSvgXmlName(SvgType atype); + static std::string sGetTypeAsString(ofxSvgType atype); + static std::string sGetSvgXmlName(ofxSvgType atype); - virtual SvgType getType() {return TYPE_ELEMENT;} + virtual ofxSvgType getType() {return ofxSvgType::TYPE_ELEMENT;} std::string getTypeAsString(); std::string getName() { return name; } bool isGroup() { - return (getType() == TYPE_GROUP); + return (getType() == ofxSvgType::TYPE_GROUP); } void setVisible( bool ab ) { bVisible = ab; } @@ -48,10 +47,6 @@ class Element { float layer = -1.f; bool bVisible=true; bool bUseShapeColor = true; - - - // will log messages to this module name - static const std::string moduleName() { return "ofx::svg::Element"; } glm::vec2 pos = glm::vec2(0.f, 0.f); glm::vec2 scale = glm::vec2(1.0f, 1.0f); @@ -67,7 +62,7 @@ class Element { } virtual ofPolyline getFirstPolyline() { - ofLogWarning(moduleName()) << __FUNCTION__ << " : Element " << getTypeAsString() << " does not have a path."; + ofLogWarning("ofxSvgElement") << __FUNCTION__ << " : Element " << getTypeAsString() << " does not have a path."; return ofPolyline(); } //protected: @@ -77,12 +72,12 @@ class Element { }; -class Path : public Element { +class ofxSvgPath : public ofxSvgElement { public: - virtual SvgType getType() override {return TYPE_PATH;} + virtual ofxSvgType getType() override {return ofxSvgType::TYPE_PATH;} virtual void setUseShapeColor( bool ab ) override { - Element::setUseShapeColor(ab); + ofxSvgElement::setUseShapeColor(ab); path.setUseShapeColor(ab); } @@ -100,16 +95,16 @@ class Path : public Element { if( path.getOutline().size() > 0 ) { return path.getOutline()[0]; } - ofLogWarning(moduleName()) << __FUNCTION__ << " : path does not have an outline."; + ofLogWarning("ofxSvgPath") << __FUNCTION__ << " : path does not have an outline."; return ofPolyline(); } ofPath path; }; -class Rectangle : public Path { +class ofxSvgRectangle : public ofxSvgPath { public: - virtual SvgType getType() override {return TYPE_RECTANGLE;} + virtual ofxSvgType getType() override {return ofxSvgType::TYPE_RECTANGLE;} ofRectangle rectangle; float getWidth() { return rectangle.getWidth() * scale.x;} @@ -121,9 +116,9 @@ class Rectangle : public Path { // float getHeightScaled() { return rectangle.getHeight() * scale.y;} }; -class Image : public Element { +class ofxSvgImage : public ofxSvgElement { public: - virtual SvgType getType() override {return TYPE_IMAGE;} + virtual ofxSvgType getType() override {return ofxSvgType::TYPE_IMAGE;} float getWidth() { return width * scale.x;} float getHeight() { return height * scale.y;} @@ -152,22 +147,22 @@ class Image : public Element { float height = 0.f; }; -class Circle : public Path { +class ofxSvgCircle : public ofxSvgPath { public: - virtual SvgType getType() override {return TYPE_CIRCLE;} + virtual ofxSvgType getType() override {return ofxSvgType::TYPE_CIRCLE;} float getRadius() {return radius;} float radius = 10.0; }; -class Ellipse : public Path { +class ofxSvgEllipse : public ofxSvgPath { public: - virtual SvgType getType() override {return TYPE_ELLIPSE;} + virtual ofxSvgType getType() override {return ofxSvgType::TYPE_ELLIPSE;} float radiusX, radiusY = 10.0f; }; -class Text : public Rectangle { -public: +class ofxSvgText : public ofxSvgRectangle { +public: class Font { public: std::string fontFamily; @@ -205,7 +200,7 @@ class Text : public Rectangle { } // Text() { type = OFX_SVG_TYPE_TEXT; fdirectory=""; bCentered=false; alpha=1.0; bOverrideColor=false; } - virtual SvgType getType() override {return TYPE_TEXT;} + virtual ofxSvgType getType() override {return ofxSvgType::TYPE_TEXT;} ofTrueTypeFont& getFont(); ofColor getColor(); @@ -217,7 +212,6 @@ class Text : public Rectangle { void setFontDirectory( std::string aPath ) { fdirectory = aPath; - // cout << "Setting the font directory to " << fdirectory << endl; } void overrideColor( ofColor aColor ) { @@ -242,8 +236,6 @@ class Text : public Rectangle { bool bOverrideColor = false; }; -} - diff --git a/addons/ofxSvg/src/ofxSvgGroup.cpp b/addons/ofxSvg/src/ofxSvgGroup.cpp index c1d3d454d38..e28138b1c0f 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.cpp +++ b/addons/ofxSvg/src/ofxSvgGroup.cpp @@ -7,13 +7,12 @@ #include "ofxSvgGroup.h" #include "ofGraphics.h" -using namespace ofx::svg; using std::vector; using std::shared_ptr; using std::string; //-------------------------------------------------------------- -void Group::draw() { +void ofxSvgGroup::draw() { std::size_t numElements = mChildren.size(); bool bTrans = pos.x != 0 || pos.y != 0.0; if( bTrans ) { @@ -29,18 +28,18 @@ void Group::draw() { } //-------------------------------------------------------------- -std::size_t Group::getNumChildren() { +std::size_t ofxSvgGroup::getNumChildren() { return mChildren.size(); } //-------------------------------------------------------------- -vector< shared_ptr >& Group::getChildren() { +vector< shared_ptr >& ofxSvgGroup::getChildren() { return mChildren; } //-------------------------------------------------------------- -vector< shared_ptr > Group::getAllChildren(bool aBIncludeGroups) { - vector< shared_ptr > retElements; +vector< shared_ptr > ofxSvgGroup::getAllChildren(bool aBIncludeGroups) { + vector< shared_ptr > retElements; for( auto ele : mChildren ) { _getAllElementsRecursive( retElements, ele, aBIncludeGroups ); @@ -50,8 +49,8 @@ vector< shared_ptr > Group::getAllChildren(bool aBIncludeGroups) { } //-------------------------------------------------------------- -std::vector< std::shared_ptr > Group::getAllChildGroups() { - vector< shared_ptr > retGroups; +std::vector< std::shared_ptr > ofxSvgGroup::getAllChildGroups() { + vector< shared_ptr > retGroups; for( auto ele : mChildren ) { _getAllGroupsRecursive( retGroups, ele ); } @@ -60,10 +59,10 @@ std::vector< std::shared_ptr > Group::getAllChildGroups() { // flattens out hierarchy // //-------------------------------------------------------------- -void Group::_getAllElementsRecursive( vector< shared_ptr >& aElesToReturn, shared_ptr aele, bool aBIncludeGroups ) { +void ofxSvgGroup::_getAllElementsRecursive( vector< shared_ptr >& aElesToReturn, shared_ptr aele, bool aBIncludeGroups ) { if( aele ) { if( aele->isGroup() ) { - shared_ptr tgroup = std::dynamic_pointer_cast(aele); + shared_ptr tgroup = std::dynamic_pointer_cast(aele); if(aBIncludeGroups) {aElesToReturn.push_back(tgroup);} for( auto ele : tgroup->getChildren() ) { _getAllElementsRecursive( aElesToReturn, ele, aBIncludeGroups ); @@ -75,10 +74,10 @@ void Group::_getAllElementsRecursive( vector< shared_ptr >& aElesToRetu } //-------------------------------------------------------------- -void Group::_getAllGroupsRecursive( std::vector< std::shared_ptr >& aGroupsToReturn, std::shared_ptr aele ) { +void ofxSvgGroup::_getAllGroupsRecursive( std::vector< std::shared_ptr >& aGroupsToReturn, std::shared_ptr aele ) { if( aele ) { if( aele->isGroup() ) { - shared_ptr tgroup = std::dynamic_pointer_cast(aele); + shared_ptr tgroup = std::dynamic_pointer_cast(aele); aGroupsToReturn.push_back(tgroup); for( auto ele : tgroup->getChildren() ) { if( ele->isGroup() ) { @@ -90,18 +89,18 @@ void Group::_getAllGroupsRecursive( std::vector< std::shared_ptr > Group::getAllElementsWithPath() { +std::vector< std::shared_ptr > ofxSvgGroup::getAllElementsWithPath() { auto allKids = getAllChildren(false); - std::vector< std::shared_ptr > rpaths; + std::vector< std::shared_ptr > rpaths; for( auto kid : allKids ) { - if( kid->getType() == TYPE_RECTANGLE ) { - rpaths.push_back(std::dynamic_pointer_cast(kid)); - } else if( kid->getType() == TYPE_PATH ) { - rpaths.push_back(std::dynamic_pointer_cast(kid)); - } else if( kid->getType() == TYPE_CIRCLE ) { - rpaths.push_back(std::dynamic_pointer_cast(kid)); - } else if( kid->getType() == TYPE_ELLIPSE ) { - rpaths.push_back(std::dynamic_pointer_cast(kid)); + if( kid->getType() == ofxSvgType::TYPE_RECTANGLE ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); + } else if( kid->getType() == ofxSvgType::TYPE_PATH ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); + } else if( kid->getType() == ofxSvgType::TYPE_CIRCLE ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); + } else if( kid->getType() == ofxSvgType::TYPE_ELLIPSE ) { + rpaths.push_back(std::dynamic_pointer_cast(kid)); } } @@ -109,7 +108,7 @@ std::vector< std::shared_ptr > Group::getAllElementsWithPath() { } //-------------------------------------------------------------- -shared_ptr Group::getElementForName( std::string aPath, bool bStrict ) { +shared_ptr ofxSvgGroup::getElementForName( std::string aPath, bool bStrict ) { vector< std::string > tsearches; if( ofIsStringInString( aPath, ":" ) ) { @@ -118,14 +117,14 @@ shared_ptr Group::getElementForName( std::string aPath, bool bStrict ) tsearches.push_back( aPath ); } - shared_ptr temp; + shared_ptr temp; _getElementForNameRecursive( tsearches, temp, mChildren, bStrict ); return temp; } //-------------------------------------------------------------- -std::vector< std::shared_ptr > Group::getChildrenForName( const std::string& aname, bool bStrict ) { - std::vector< std::shared_ptr > relements; +std::vector< std::shared_ptr > ofxSvgGroup::getChildrenForName( const std::string& aname, bool bStrict ) { + std::vector< std::shared_ptr > relements; for( auto& kid : mChildren ) { if( bStrict ) { if( kid->getName() == aname ) { @@ -141,7 +140,7 @@ std::vector< std::shared_ptr > Group::getChildrenForName( const std::st } //-------------------------------------------------------------- -void Group::_getElementForNameRecursive( vector& aNamesToFind, shared_ptr& aTarget, vector< shared_ptr >& aElements, bool bStrict ) { +void ofxSvgGroup::_getElementForNameRecursive( vector& aNamesToFind, shared_ptr& aTarget, vector< shared_ptr >& aElements, bool bStrict ) { if( aNamesToFind.size() < 1 ) { return; @@ -168,12 +167,12 @@ void Group::_getElementForNameRecursive( vector& aNamesToFind, shared_pt bFound = true; } - if (!bFound && aElements[i]->getType() == TYPE_TEXT) { + if (!bFound && aElements[i]->getType() == ofxSvgType::TYPE_TEXT) { if (aElements[i]->getName() == "No Name") { // the ids for text block in illustrator are weird, // so try to grab the name from the text contents // - auto etext = std::dynamic_pointer_cast(aElements[i]); + auto etext = std::dynamic_pointer_cast(aElements[i]); if (etext) { if (etext->textSpans.size()) { // cout << "Searching for " << aNamesToFind[0] << " in " << etext->textSpans.front().text << endl; @@ -194,8 +193,8 @@ void Group::_getElementForNameRecursive( vector& aNamesToFind, shared_pt aTarget = aElements[i]; break; } else { - if( aElements[i]->getType() == TYPE_GROUP ) { - auto tgroup = std::dynamic_pointer_cast( aElements[i] ); + if( aElements[i]->getType() == ofxSvgType::TYPE_GROUP ) { + auto tgroup = std::dynamic_pointer_cast( aElements[i] ); _getElementForNameRecursive( aNamesToFind, aTarget, tgroup->getChildren(), bStrict ); break; } @@ -208,9 +207,9 @@ void Group::_getElementForNameRecursive( vector& aNamesToFind, shared_pt aTarget = aElements[i]; break; } else { - if( aElements[i]->getType() == TYPE_GROUP ) { + if( aElements[i]->getType() == ofxSvgType::TYPE_GROUP ) { // std::cout << "Group::_getElementForNameRecursive: FOUND A GROUP, But still going: " << aElements[i]->getName() << " keep going: " << bKeepGoing << std::endl; - auto tgroup = std::dynamic_pointer_cast( aElements[i] ); + auto tgroup = std::dynamic_pointer_cast( aElements[i] ); _getElementForNameRecursive( aNamesToFind, aTarget, tgroup->getChildren(), bStrict ); } } @@ -219,14 +218,14 @@ void Group::_getElementForNameRecursive( vector& aNamesToFind, shared_pt } //-------------------------------------------------------------- -bool Group::replace( shared_ptr aOriginal, shared_ptr aNew ) { +bool ofxSvgGroup::replace( shared_ptr aOriginal, shared_ptr aNew ) { bool bReplaced = false; _replaceElementRecursive( aOriginal, aNew, mChildren, bReplaced ); return bReplaced; } //-------------------------------------------------------------- -void Group::_replaceElementRecursive( shared_ptr aTarget, shared_ptr aNew, vector< shared_ptr >& aElements, bool& aBSuccessful ) { +void ofxSvgGroup::_replaceElementRecursive( shared_ptr aTarget, shared_ptr aNew, vector< shared_ptr >& aElements, bool& aBSuccessful ) { for( std::size_t i = 0; i < aElements.size(); i++ ) { bool bFound = false; if( aTarget == aElements[i] ) { @@ -237,8 +236,8 @@ void Group::_replaceElementRecursive( shared_ptr aTarget, shared_ptrgetType() == TYPE_GROUP ) { - auto tgroup = std::dynamic_pointer_cast( aElements[i] ); + if( aElements[i]->getType() == ofxSvgType::TYPE_GROUP ) { + auto tgroup = std::dynamic_pointer_cast( aElements[i] ); _replaceElementRecursive(aTarget, aNew, tgroup->mChildren, aBSuccessful ); } } @@ -246,7 +245,7 @@ void Group::_replaceElementRecursive( shared_ptr aTarget, shared_ptrsetUseShapeColor(false); @@ -273,7 +272,7 @@ void Group::disableColors() { } //-------------------------------------------------------------- -void Group::enableColors() { +void ofxSvgGroup::enableColors() { auto telements = getAllChildren(false); for( auto& ele : telements ) { ele->setUseShapeColor(true); diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h index 2b9d0259b3f..8f4aa9a71cb 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.h +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -7,33 +7,32 @@ #pragma once #include "ofxSvgElements.h" -namespace ofx::svg { -class Group : public Element { +class ofxSvgGroup : public ofxSvgElement { public: - virtual SvgType getType() override {return TYPE_GROUP;} + virtual ofxSvgType getType() override {return TYPE_GROUP;} virtual void draw() override; std::size_t getNumChildren();// override; - std::vector< std::shared_ptr >& getChildren(); - std::vector< std::shared_ptr > getAllChildren(bool aBIncludeGroups); - std::vector< std::shared_ptr > getAllChildGroups(); + std::vector< std::shared_ptr >& getChildren(); + std::vector< std::shared_ptr > getAllChildren(bool aBIncludeGroups); + std::vector< std::shared_ptr > getAllChildGroups(); - template - std::vector< std::shared_ptr > getElementsForType( std::string aPathToGroup="", bool bStrict= false ) { - std::shared_ptr temp = std::make_shared(); + template + std::vector< std::shared_ptr > getElementsForType( std::string aPathToGroup="", bool bStrict= false ) { + auto temp = std::make_shared(); auto sType = temp->getType(); - std::vector< std::shared_ptr > telements; - std::vector< std::shared_ptr > elementsToSearch; + std::vector< std::shared_ptr > telements; + std::vector< std::shared_ptr > elementsToSearch; if( aPathToGroup == "" ) { elementsToSearch = mChildren; } else { - std::shared_ptr< Element > temp = getElementForName( aPathToGroup, bStrict ); + std::shared_ptr< ofxSvgElement > temp = getElementForName( aPathToGroup, bStrict ); if( temp ) { if( temp->isGroup() ) { - std::shared_ptr< Group > tgroup = std::dynamic_pointer_cast( temp ); + std::shared_ptr< ofxSvgGroup > tgroup = std::dynamic_pointer_cast( temp ); elementsToSearch = tgroup->mChildren; } } @@ -46,54 +45,54 @@ class Group : public Element { for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { if( elementsToSearch[i]->getType() == sType ) { - telements.push_back( std::dynamic_pointer_cast< ofxSvgType>(elementsToSearch[i]) ); + telements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); } } return telements; } - template - std::shared_ptr getFirstElementForType( std::string aPathToGroup="", bool bStrict= false ) { - auto eles = getElementsForType(aPathToGroup, bStrict ); + template + std::shared_ptr getFirstElementForType( std::string aPathToGroup="", bool bStrict= false ) { + auto eles = getElementsForType(aPathToGroup, bStrict ); if( eles.size() > 0 ) { return eles[0]; } - return std::shared_ptr(); + return std::shared_ptr(); } - template - std::vector< std::shared_ptr > getAllElementsForType() { + template + std::vector< std::shared_ptr > getAllElementsForType() { - auto temp = std::make_shared(); + auto temp = std::make_shared(); auto sType = temp->getType(); - std::vector< std::shared_ptr > telements; + std::vector< std::shared_ptr > telements; auto elementsToSearch = getAllChildren(true); for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { if( elementsToSearch[i]->getType() == sType ) { - telements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); + telements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); } } return telements; } - template - std::vector< std::shared_ptr > getAllElementsContainingNameForType(std::string aname) { + template + std::vector< std::shared_ptr > getAllElementsContainingNameForType(std::string aname) { - auto temp = std::make_shared(); + auto temp = std::make_shared(); auto sType = temp->getType(); - std::vector< std::shared_ptr > telements; + std::vector< std::shared_ptr > telements; // get all children does not include groups, since it's meant to flatten hierarchy - if( sType == ofx::svg::TYPE_GROUP ) { + if( sType == ofxSvgType::TYPE_GROUP ) { auto groupsToSearch = getAllChildGroups(); for( std::size_t i = 0; i < groupsToSearch.size(); i++ ) { if( groupsToSearch[i]->getType() == sType ) { if( ofIsStringInString(groupsToSearch[i]->getName(), aname) ) { - telements.push_back( std::dynamic_pointer_cast(groupsToSearch[i]) ); + telements.push_back( std::dynamic_pointer_cast(groupsToSearch[i]) ); } } } @@ -103,7 +102,7 @@ class Group : public Element { for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { if( elementsToSearch[i]->getType() == sType ) { if( ofIsStringInString(elementsToSearch[i]->getName(), aname) ) { - telements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); + telements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); } } } @@ -113,56 +112,56 @@ class Group : public Element { return telements; } - std::vector< std::shared_ptr > getAllElementsWithPath(); + std::vector< std::shared_ptr > getAllElementsWithPath(); - std::shared_ptr getElementForName( std::string aPath, bool bStrict = false ); - std::vector< std::shared_ptr > getChildrenForName( const std::string& aname, bool bStrict = false ); + std::shared_ptr getElementForName( std::string aPath, bool bStrict = false ); + std::vector< std::shared_ptr > getChildrenForName( const std::string& aname, bool bStrict = false ); - template - std::vector< std::shared_ptr > getChildrenForTypeForName( const std::string& aname, bool bStrict = false ) { - auto temp = std::make_shared(); + template + std::vector< std::shared_ptr > getChildrenForTypeForName( const std::string& aname, bool bStrict = false ) { + auto temp = std::make_shared(); auto sType = temp->getType(); - std::vector< std::shared_ptr > relements; + std::vector< std::shared_ptr > relements; for( auto& kid : mChildren ) { if( kid->getType() != sType ) {continue;} if( bStrict ) { if( kid->getName() == aname ) { - relements.push_back( std::dynamic_pointer_cast(kid)); + relements.push_back( std::dynamic_pointer_cast(kid)); } } else { if( ofIsStringInString( kid->getName(), aname )) { - relements.push_back(std::dynamic_pointer_cast(kid)); + relements.push_back(std::dynamic_pointer_cast(kid)); } } } return relements; } - template - std::shared_ptr< ofxSvgType > get( std::string aPath, bool bStrict = false ) { - auto stemp = std::dynamic_pointer_cast( getElementForName( aPath, bStrict ) ); + template + std::shared_ptr< ofxSvg_T > get( std::string aPath, bool bStrict = false ) { + auto stemp = std::dynamic_pointer_cast( getElementForName( aPath, bStrict ) ); return stemp; } - template - std::shared_ptr< ofxSvgType > get( int aIndex ) { - auto stemp = std::dynamic_pointer_cast( mChildren[ aIndex ] ); + template + std::shared_ptr< ofxSvg_T > get( int aIndex ) { + auto stemp = std::dynamic_pointer_cast( mChildren[ aIndex ] ); return stemp; } - bool replace( std::shared_ptr aOriginal, std::shared_ptr aNew ); + bool replace( std::shared_ptr aOriginal, std::shared_ptr aNew ); // adding - template - std::shared_ptr add(std::string aname) { - auto element = std::make_shared(); + template + std::shared_ptr add(std::string aname) { + auto element = std::make_shared(); element->name = aname; mChildren.push_back(element); return element; }; - void add( std::shared_ptr aele ) { mChildren.push_back(aele); } + void add( std::shared_ptr aele ) { mChildren.push_back(aele); } virtual std::string toString(int nlevel = 0) override; @@ -170,16 +169,15 @@ class Group : public Element { void enableColors(); protected: - void _getElementForNameRecursive( std::vector< std::string >& aNamesToFind, std::shared_ptr& aTarget, std::vector< std::shared_ptr >& aElements, bool bStrict ); - void _getAllElementsRecursive( std::vector< std::shared_ptr >& aElesToReturn, std::shared_ptr aele, bool aBIncludeGroups ); + void _getElementForNameRecursive( std::vector< std::string >& aNamesToFind, std::shared_ptr& aTarget, std::vector< std::shared_ptr >& aElements, bool bStrict ); + void _getAllElementsRecursive( std::vector< std::shared_ptr >& aElesToReturn, std::shared_ptr aele, bool aBIncludeGroups ); - void _getAllGroupsRecursive( std::vector< std::shared_ptr >& aGroupsToReturn, std::shared_ptr aele ); + void _getAllGroupsRecursive( std::vector< std::shared_ptr >& aGroupsToReturn, std::shared_ptr aele ); - void _replaceElementRecursive( std::shared_ptr aTarget, std::shared_ptr aNew, std::vector< std::shared_ptr >& aElements, bool& aBSuccessful ); + void _replaceElementRecursive( std::shared_ptr aTarget, std::shared_ptr aNew, std::vector< std::shared_ptr >& aElements, bool& aBSuccessful ); - std::vector< std::shared_ptr > mChildren; + std::vector< std::shared_ptr > mChildren; }; -} From 59721718f609fab148bc032c133986b8611aba5c Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Tue, 18 Feb 2025 11:43:24 -0500 Subject: [PATCH 6/8] fixes for filepath and string on windows. --- addons/ofxSvg/src/ofxSvg.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index c147d8d1e87..3ce72cb700c 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -618,17 +618,14 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrtextSpans.push_back( _getTextSpanFromXmlNode( tnode ) ); } } - - string tempFolderPath = folderPath; - if( tempFolderPath.back() != '/' ) { - tempFolderPath += '/'; - } + + string tempFolderPath = ofFilePath::addTrailingSlash(folderPath); if( ofDirectory::doesDirectoryExist( tempFolderPath+"fonts/" )) { text->setFontDirectory( tempFolderPath+"fonts/" ); } if( fontsDirectory != "" ) { if( ofDirectory::doesDirectoryExist(fontsDirectory)) { - text->setFontDirectory( fontsDirectory ); + text->setFontDirectory(fontsDirectory); } } From 187642e596eab2a8c7c53e672b708fe7bb1721f9 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Fri, 21 Feb 2025 18:23:23 -0500 Subject: [PATCH 7/8] Copy and move functions. --- addons/ofxSvg/src/ofxSvg.cpp | 186 ++++++++++++++++++++++++++++++----- addons/ofxSvg/src/ofxSvg.h | 25 ++++- 2 files changed, 183 insertions(+), 28 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 3ce72cb700c..fa76b2d500a 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -44,6 +44,164 @@ Measurement parseMeasurement(const std::string& input) { return result; } +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::clone( const std::shared_ptr& aele ) { + if (aele) { + if( aele->getType() == ofxSvgType::TYPE_ELEMENT ) { + return std::make_shared(*aele); + } else if( aele->getType() == ofxSvgType::TYPE_GROUP ) { + auto pg = std::dynamic_pointer_cast(aele); + return std::make_shared(*pg); + } else if( aele->getType() == ofxSvgType::TYPE_RECTANGLE ) { + auto pg = std::dynamic_pointer_cast(aele); + return std::make_shared(*pg); + } else if( aele->getType() == ofxSvgType::TYPE_IMAGE ) { + auto pg = std::dynamic_pointer_cast(aele); + return std::make_shared(*pg); + } else if( aele->getType() == ofxSvgType::TYPE_ELLIPSE ) { + auto pg = std::dynamic_pointer_cast(aele); + return std::make_shared(*pg); + } else if( aele->getType() == ofxSvgType::TYPE_CIRCLE ) { + auto pg = std::dynamic_pointer_cast(aele); + return std::make_shared(*pg); + } else if( aele->getType() == ofxSvgType::TYPE_PATH ) { + auto pg = std::dynamic_pointer_cast(aele); + return std::make_shared(*pg); + } else if( aele->getType() == ofxSvgType::TYPE_TEXT ) { + auto pg = std::dynamic_pointer_cast(aele); + return std::make_shared(*pg); + } + } + return std::shared_ptr(); +} + +// Function to deep copy a vector of shared_ptrs +std::vector> ofxSvg::deepCopyVector(const std::vector>& original) { + std::vector> copy; + copy.reserve(original.size()); // Reserve space for efficiency + + for (auto ptr : original) { + if (ptr) { + copy.push_back(clone(ptr)); + } else { + ofLogNotice("ofxSvg") << "deepCopyVector :: nullptr"; + copy.push_back(std::shared_ptr()); // Preserve nullptr entries + } + } + return copy; +} + +void ofxSvg::deepCopyFrom( const ofxSvg & mom ) { + if( mom.mChildren.size() > 0 ) { + mChildren = deepCopyVector(mom.mChildren); + } + ofLogNotice("ofxSvg") << "deepCopyFrom mom num children: " << mom.mChildren.size() << " my size: " << mChildren.size(); + if( mom.mDefElements.size() > 0 ) { + mDefElements = deepCopyVector(mom.mDefElements); + } + + mViewbox = mom.mViewbox; + mBounds = mom.mBounds; + + fontsDirectory = mom.fontsDirectory; + folderPath = mom.folderPath; + svgPath = mom.svgPath; + + mCurrentLayer = mom.mCurrentLayer; + mUnitStr = mom.mUnitStr; + + if(mom.mCurrentSvgCss) { + mCurrentSvgCss = std::make_shared(*mom.mCurrentSvgCss); + } + + mSvgCss = mom.mSvgCss; + mCurrentCss = mom.mCurrentCss; + mFillColor = mom.mFillColor; + mStrokeColor = mom.mStrokeColor; + + mModelMatrix = mom.mModelMatrix; + mModelMatrixStack = mom.mModelMatrixStack; + + mCircleResolution = mom.mCircleResolution; + mCurveResolution = mom.mCurveResolution; + + mPaths = mom.mPaths; +} + +//-------------------------------------------------------------- +void ofxSvg::moveFrom( ofxSvg&& mom ) { + mChildren = std::move(mom.mChildren); + mDefElements = std::move(mom.mDefElements); + + mViewbox = mom.mViewbox; + mBounds = mom.mBounds; + + fontsDirectory = mom.fontsDirectory; + folderPath = mom.folderPath; + svgPath = mom.svgPath; + + mCurrentLayer = mom.mCurrentLayer; + mUnitStr = mom.mUnitStr; + + mCurrentSvgCss = mom.mCurrentSvgCss; + + mSvgCss = mom.mSvgCss; + mCurrentCss = mom.mCurrentCss; + mFillColor = mom.mFillColor; + mStrokeColor = mom.mStrokeColor; + + mModelMatrix = mom.mModelMatrix; + mModelMatrixStack = mom.mModelMatrixStack; + + mCircleResolution = mom.mCircleResolution; + mCurveResolution = mom.mCurveResolution; + + mPaths = mom.mPaths; +} + + +// Copy constructor (deep copy) +//-------------------------------------------------------------- +ofxSvg::ofxSvg(const ofxSvg & mom) { + clear(); + ofLogNotice("ofxSvg") << "ofxSvg(const ofxSvg & mom)"; + deepCopyFrom(mom); +} + +// Copy assignment operator (deep copy) +//-------------------------------------------------------------- +ofxSvg& ofxSvg::operator=(const ofxSvg& mom) { + if (this != &mom) { + ofLogNotice("ofxSvg") << "ofxSvg::operator=(const ofxSvg& mom)"; + clear(); + deepCopyFrom(mom); + } + return *this; +} + +// Move constructor +//-------------------------------------------------------------- +ofxSvg::ofxSvg(ofxSvg && mom) { + ofLogNotice("ofxSvg") << "ofxSvg(ofxSvg && mom)"; + clear(); + moveFrom(std::move(mom)); +} + +// Move assignment operator +ofxSvg& ofxSvg::operator=(ofxSvg&& mom) { + if (this != &mom) { + ofLogNotice("ofxSvg") << "ofxSvg::operator=(ofxSvg&& mom)"; + clear(); + moveFrom(std::move(mom)); + } + return *this; +} + +//-------------------------------------------------------------- +ofxSvg::ofxSvg(const of::filesystem::path & fileName) { + load(fileName); +} + //-------------------------------------------------------------- bool ofxSvg::load( const of::filesystem::path& fileName ) { ofFile mainXmlFile( fileName, ofFile::ReadOnly ); @@ -430,32 +588,8 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrname == href ) { - if( def->getType() == ofxSvgType::TYPE_RECTANGLE ) { - auto drect = std::dynamic_pointer_cast(def); - auto nrect = std::make_shared( *drect ); - telement = nrect; - } else if( def->getType() == ofxSvgType::TYPE_IMAGE ) { - auto dimg = std::dynamic_pointer_cast(def); - auto nimg = std::make_shared( *dimg ); - ofLogVerbose("ofxSvg") << "created an image node with filepath: " << nimg->getFilePath(); - telement = nimg; - } else if( def->getType() == ofxSvgType::TYPE_ELLIPSE ) { - auto dell= std::dynamic_pointer_cast(def); - auto nell = std::make_shared( *dell ); - telement = nell; - } else if( def->getType() == ofxSvgType::TYPE_CIRCLE ) { - auto dcir= std::dynamic_pointer_cast(def); - auto ncir = std::make_shared( *dcir ); - telement = ncir; - } else if( def->getType() == ofxSvgType::TYPE_PATH ) { - auto dpat= std::dynamic_pointer_cast(def); - auto npat = std::make_shared( *dpat ); - telement = npat; - } else if( def->getType() == ofxSvgType::TYPE_TEXT ) { - auto dtex = std::dynamic_pointer_cast(def); - auto ntex = std::make_shared( *dtex ); - telement = ntex; - } else { + telement = clone(def); + if( !telement ) { ofLogWarning("Parser") << "could not find type for def : " << def->name; } break; diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index 19b77b9110a..79fc45690aa 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -12,12 +12,33 @@ /// \file /// ofxSVG is used for loading, manipulating, rendering and saving of SVG files. +/// Based on this spec: https://www.w3.org/TR/SVG/Overview.html -class ofxSvg : public ofxSvgGroup { -public: +class ofxSvg : public ofxSvgGroup { +protected: + std::shared_ptr clone( const std::shared_ptr& aele ); + std::vector> deepCopyVector(const std::vector>& original); + void deepCopyFrom( const ofxSvg & mom ); + void moveFrom( ofxSvg&& mom ); +public: virtual ofxSvgType getType() override {return ofxSvgType::TYPE_DOCUMENT;} + // Default constructor + ofxSvg() = default; + // Copy constructor (deep copy) + ofxSvg(const ofxSvg & mom);// = default; + // Copy assignment operator (deep copy) + ofxSvg& operator=(const ofxSvg& mom); + // Move constructor + ofxSvg(ofxSvg&& mom); + // Move assignment operator + ofxSvg& operator=(ofxSvg&& mom); + + ~ofxSvg() = default; + + ofxSvg(const of::filesystem::path & fileName); + /// \brief Loads an SVG file from the provided filename. /// \return true if the load was successful. bool load( const of::filesystem::path & fileName ); From 597eea9e33ca229e6d62a258a9369ef8a98fccf8 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Fri, 21 Feb 2025 22:14:46 -0500 Subject: [PATCH 8/8] fix for fill-opacity and populating css values even if they wont be used. --- addons/ofxSvg/src/ofxSvg.cpp | 99 ++++++++++++++++++++------------- addons/ofxSvg/src/ofxSvgCss.cpp | 1 + 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index fa76b2d500a..ee7bea08c82 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -84,7 +84,7 @@ std::vector> ofxSvg::deepCopyVector(const std::ve if (ptr) { copy.push_back(clone(ptr)); } else { - ofLogNotice("ofxSvg") << "deepCopyVector :: nullptr"; + ofLogVerbose("ofxSvg") << "deepCopyVector :: nullptr"; copy.push_back(std::shared_ptr()); // Preserve nullptr entries } } @@ -95,7 +95,6 @@ void ofxSvg::deepCopyFrom( const ofxSvg & mom ) { if( mom.mChildren.size() > 0 ) { mChildren = deepCopyVector(mom.mChildren); } - ofLogNotice("ofxSvg") << "deepCopyFrom mom num children: " << mom.mChildren.size() << " my size: " << mChildren.size(); if( mom.mDefElements.size() > 0 ) { mDefElements = deepCopyVector(mom.mDefElements); } @@ -164,7 +163,7 @@ void ofxSvg::moveFrom( ofxSvg&& mom ) { //-------------------------------------------------------------- ofxSvg::ofxSvg(const ofxSvg & mom) { clear(); - ofLogNotice("ofxSvg") << "ofxSvg(const ofxSvg & mom)"; + ofLogVerbose("ofxSvg") << "ofxSvg(const ofxSvg & mom)"; deepCopyFrom(mom); } @@ -172,7 +171,7 @@ ofxSvg::ofxSvg(const ofxSvg & mom) { //-------------------------------------------------------------- ofxSvg& ofxSvg::operator=(const ofxSvg& mom) { if (this != &mom) { - ofLogNotice("ofxSvg") << "ofxSvg::operator=(const ofxSvg& mom)"; + ofLogVerbose("ofxSvg") << "ofxSvg::operator=(const ofxSvg& mom)"; clear(); deepCopyFrom(mom); } @@ -182,7 +181,7 @@ ofxSvg& ofxSvg::operator=(const ofxSvg& mom) { // Move constructor //-------------------------------------------------------------- ofxSvg::ofxSvg(ofxSvg && mom) { - ofLogNotice("ofxSvg") << "ofxSvg(ofxSvg && mom)"; + ofLogVerbose("ofxSvg") << "ofxSvg(ofxSvg && mom)"; clear(); moveFrom(std::move(mom)); } @@ -190,7 +189,7 @@ ofxSvg::ofxSvg(ofxSvg && mom) { // Move assignment operator ofxSvg& ofxSvg::operator=(ofxSvg&& mom) { if (this != &mom) { - ofLogNotice("ofxSvg") << "ofxSvg::operator=(ofxSvg&& mom)"; + ofLogVerbose("ofxSvg") << "ofxSvg::operator=(ofxSvg&& mom)"; clear(); moveFrom(std::move(mom)); } @@ -1018,7 +1017,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { 'A', 'a' // elliptical arc }; std::string ostring = dattr.getValue(); -// ofLogNotice(moduleName()) << __FUNCTION__ << " dattr: " << ostring; +// ofLogNotice("ofxSvg") << __FUNCTION__ << " dattr: " << ostring; if( ostring.empty() ) { ofLogError("ofxSvg") << __FUNCTION__ << " there is no data in the d string."; @@ -1097,14 +1096,14 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { break; } -// ofLogNotice(moduleName()) << " o : ["<< ostring[index] <<"]"; + ofLogVerbose("ofxSvg") << " o : ["<< ostring[index] <<"]"; // up to next valid character // std::string currentString; bool bFoundValidNextChar = false; auto pos = index+1; if( pos >= ostring.size() ) { - ofLogVerbose("svgParser") << "pos is greater than string size: " << pos << " / " << ostring.size(); + ofLogVerbose("ofxSvg") << "pos is greater than string size: " << pos << " / " << ostring.size(); // break; breakMe = true; } @@ -1127,7 +1126,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { if( currentString.empty() ) { - ofLogVerbose("svgParser") << "currentString is empty: " << cchar; + ofLogVerbose("ofxSvg") << "currentString is empty: " << cchar; // break; } @@ -1171,7 +1170,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } npositions = parsePoints(currentString); // for( auto& np : npositions ) { -// ofLogNotice(moduleName()) << cchar << " line to: " << np; +// ofLogVerbose("ofxSvg") << cchar << " line to: " << np; // } ctype = ofPath::Command::lineTo; } else if( cchar == 'z' || cchar == 'Z' ) { @@ -1184,7 +1183,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { ctype = ofPath::Command::bezierTo; npositions = parsePoints(currentString); // for( auto& np : npositions ) { -// ofLogNotice(moduleName()) << cchar << " bezier to: " << np; +// ofLogVerbose("ofxSvg") << cchar << " bezier to: " << np; // } } else if( cchar == 'Q' || cchar == 'q' || cchar == 'T' || cchar == 't' ) { if( cchar == 'q' ) { @@ -1195,7 +1194,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { npositions = parsePoints(currentString); // for( auto& np : npositions ) { -// ofLogNotice(moduleName()) << " Quad bezier to: " << np; +// ofLogNotice("ofxSvg") << " Quad bezier to: " << np; // } } else if(cchar == 'a' || cchar == 'A' ) { if( cchar == 'a' ) { @@ -1220,14 +1219,14 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } // for( auto& np : npositions ) { -// ofLogNotice(moduleName()) << " arc parsed positions: " << np; +// ofLogNotice("ofxSvg") << " arc parsed positions: " << np; // } } if( ctype.has_value() ) { // for( auto& np : npositions ) { -// ofLogNotice(moduleName()) << cchar << " position: " << np; +// ofLogNotice("ofxSvg") << cchar << " position: " << np; // } auto prevPos = currentPos; @@ -1253,7 +1252,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } else { // if( commandT == ofPath::Command::moveTo ) { -// ofLogNotice("ofxSvgParser") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; +// ofLogNotice("ofxSvg") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; // } if( npositions.size() > 0 && commandT != ofPath::Command::close ) { currentPos = convertToAbsolute(bRelative, currentPos, npositions ); @@ -1261,7 +1260,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } // if( npositions.size() > 0 ) { -// ofLogNotice("ofxSvgParser") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; +// ofLogNotice("ofxSvg") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; // } if( commandT != ofPath::Command::bezierTo ) { @@ -1276,6 +1275,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } else if( commandT == ofPath::Command::lineTo ) { aSvgPath->path.lineTo(currentPos); } else if( commandT == ofPath::Command::close ) { +// ofLogNotice("ofxSvg") << "Closing the path"; aSvgPath->path.close(); } else if( commandT == ofPath::Command::bezierTo ) { @@ -1471,7 +1471,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { // mCPoints.insert( mCPoints.end(), npositions.begin(), npositions.end() ); } -// ofLogNotice(moduleName()) << "["<properties ) { + if( tprop.first.empty() ) { + ofLogNotice("ofxSvg") << "First prop is empty"; + } css.addProperty(tprop.first, tprop.second); } } @@ -1494,10 +1497,10 @@ ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { if( auto classAttr = anode.getAttribute("class") ) { // get a list of classes, is this separated by commas? auto classList = ofSplitString(classAttr.getValue(), ","); -// ofLogNotice("ofx::svg::Parser") << " going to try and parse style classes string: " << classAttr.getValue(); +// ofLogNotice("ofxSvg") << " going to try and parse style classes string: " << classAttr.getValue(); for( auto& className : classList ) { if( mSvgCss.hasClass(className) ) { -// ofLogNotice("ofx::svg::Parser") << " has class " << className; +// ofLogNotice("ofxSvg") << " has class " << className; // now lets try to apply it to the path auto& tCss = mSvgCss.getClass(className); for( auto& tprop : tCss.properties ) { @@ -1509,15 +1512,26 @@ ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { // locally set on node overrides the class listing // are there any properties on the node? - if( auto fillAttr = anode.getAttribute("fill")) { - css.addProperty("fill", fillAttr.getValue()); - } - if( auto strokeAttr = anode.getAttribute("stroke")) { - css.addProperty("stroke", strokeAttr.getValue()); - } - if( auto strokeWidthAttr = anode.getAttribute("stroke-width")) { - css.addProperty("stroke-width", strokeWidthAttr.getValue()); + // avoid the following + std::vector reservedAtts = { + "d", "id", "xlink:href", "width", "height", "rx", "ry", "cx", "cy", "r", "style", "font-family", + "x","y","x1","y1","x2","y2" + }; + + // lets try to do this a better way + for( auto& att : anode.getAttributes() ) { + auto atName = ofToLower(att.getName()); + bool bFileIt = true; + for( auto& rattName : reservedAtts ) { + if( atName == rattName ) { + bFileIt=false; + break; + } + } + if( bFileIt ) { + css.addProperty(att.getName(), att.getValue()); + } } if( auto ffattr = anode.getAttribute("font-family") ) { @@ -1526,29 +1540,19 @@ ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { css.addProperty("font-family", tFontFam); } - if( auto fsattr = anode.getAttribute("font-size") ) { - css.addProperty("font-size", fsattr.getValue() ); - } - // and lastly style if( auto styleAttr = anode.getAttribute("style") ) { css.addProperties(styleAttr.getValue()); } - // override anything else if set directly on the node - if( auto disAttr = anode.getAttribute("display") ) { - css.addProperties(disAttr.getValue()); - } - return css; } //-------------------------------------------------------------- void ofxSvg::_applyStyleToElement( ofXml& tnode, std::shared_ptr aEle ) { auto css = _parseStyle(tnode); -// ofLogNotice("_applyStyleToElement" ) << " " << aEle->name << " -----"; if( css.hasAndIsNone("display")) { -// ofLogNotice("parser") << "setting element to invisible: " << aEle->name; + ofLogVerbose("ofxSvg") << "setting element to invisible: " << aEle->name; aEle->setVisible(false); } } @@ -1574,6 +1578,21 @@ void ofxSvg::_applyStyleToPath( ofxSvgCssClass& aclass, std::shared_ptrpath.setFillColor(ofColor(0)); } + if( aclass.hasProperty("fill-opacity")) { + if( aclass.isNone("fill-opacity")) { + aSvgPath->path.setFilled(false); + } else { + float val = aclass.getFloatValue("fill-opacity", 1.0f); + if( val <= 0.0001f ) { + aSvgPath->path.setFilled(false); + } else { + auto pcolor = aSvgPath->path.getFillColor(); + pcolor.a = val; + aSvgPath->path.setFillColor(pcolor); + } + } + } + if( !aclass.isNone("stroke") ) { aSvgPath->path.setStrokeColor(aclass.getColor("stroke")); } @@ -1707,7 +1726,7 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptrrotation), glm::vec3(0.f, 0.f, 1.f))); } -// ofLogNotice("svg parser") << "rcenter: " << rcenter.x << ", " << rcenter.y; +// ofLogNotice("ofxSvg") << "rcenter: " << rcenter.x << ", " << rcenter.y; } ofLogVerbose("ofxSvg") << __FUNCTION__ << " arotation: " << aele->rotation; } diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index f36d8d19f5b..7d9a93e1a4a 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -118,6 +118,7 @@ ofColor ofxSvgCssClass::sGetColor(const std::string& astr ) { ofColor tcolor(255); int hint = ofHexToInt(cstr); tcolor.setHex(hint); +// ofLogNotice("ofxSvgCssClass") << "color: " << cstr << " ofColor: " << tcolor; return tcolor; } else if( !astr.empty() ) { if( sCommonColors.count(cstr)) {