From 61a3ae2223b7589590310a02c8c4873f658ab57f Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Sat, 15 Feb 2025 13:38:21 -0500 Subject: [PATCH 01/21] 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 02/21] 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 03/21] 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 04/21] 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 05/21] 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 06/21] 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 07/21] 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 08/21] 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)) { From 65c241cc82bb081db5fd8d9639e40eeae275573c Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Sun, 17 Aug 2025 22:47:01 -0400 Subject: [PATCH 09/21] Updates for more path parsing support, text parsing, embedded images and deep copy --- addons/ofxSvg/src/ofxSvg.cpp | 572 ++++++++++++++------- addons/ofxSvg/src/ofxSvg.h | 41 +- addons/ofxSvg/src/ofxSvgCss.cpp | 110 +++- addons/ofxSvg/src/ofxSvgCss.h | 28 +- addons/ofxSvg/src/ofxSvgElements.cpp | 736 ++++++++++++++++++++------- addons/ofxSvg/src/ofxSvgElements.h | 318 +++++++++--- addons/ofxSvg/src/ofxSvgFontBook.cpp | 291 +++++++++++ addons/ofxSvg/src/ofxSvgFontBook.h | 72 +++ addons/ofxSvg/src/ofxSvgGroup.cpp | 71 ++- addons/ofxSvg/src/ofxSvgGroup.h | 293 ++++++++--- addons/ofxSvg/src/ofxSvgUtils.cpp | 262 ++++++++++ addons/ofxSvg/src/ofxSvgUtils.h | 17 + 12 files changed, 2248 insertions(+), 563 deletions(-) create mode 100644 addons/ofxSvg/src/ofxSvgFontBook.cpp create mode 100644 addons/ofxSvg/src/ofxSvgFontBook.h create mode 100644 addons/ofxSvg/src/ofxSvgUtils.cpp create mode 100644 addons/ofxSvg/src/ofxSvgUtils.h diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index ee7bea08c82..dd3c2484c8d 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -1,13 +1,8 @@ -// -// ofxSvgParser.cpp -// -// Created by Nick Hardeman on 8/31/24. -// - #include "ofxSvg.h" #include "ofUtils.h" #include #include "ofGraphics.h" +#include "ofxSvgUtils.h" using std::string; using std::vector; @@ -44,37 +39,6 @@ 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; @@ -82,16 +46,16 @@ std::vector> ofxSvg::deepCopyVector(const std::ve for (auto ptr : original) { if (ptr) { - copy.push_back(clone(ptr)); - } else { - ofLogVerbose("ofxSvg") << "deepCopyVector :: nullptr"; - copy.push_back(std::shared_ptr()); // Preserve nullptr entries + copy.push_back(ptr->clone()); } } return copy; } void ofxSvg::deepCopyFrom( const ofxSvg & mom ) { + + ofLogVerbose("ofxSvg::deepCopyFrom"); + if( mom.mChildren.size() > 0 ) { mChildren = deepCopyVector(mom.mChildren); } @@ -109,15 +73,19 @@ void ofxSvg::deepCopyFrom( const ofxSvg & mom ) { mCurrentLayer = mom.mCurrentLayer; mUnitStr = mom.mUnitStr; - if(mom.mCurrentSvgCss) { - mCurrentSvgCss = std::make_shared(*mom.mCurrentSvgCss); - } +// if(mom.mCurrentSvgCss) { +// mCurrentSvgCss = std::make_shared(*mom.mCurrentSvgCss); +// } mSvgCss = mom.mSvgCss; mCurrentCss = mom.mCurrentCss; + mDocumentCss = mom.mDocumentCss; mFillColor = mom.mFillColor; mStrokeColor = mom.mStrokeColor; + mCssClassStack = mom.mCssClassStack; + _buildCurrentSvgCssFromStack(); + mModelMatrix = mom.mModelMatrix; mModelMatrixStack = mom.mModelMatrixStack; @@ -129,6 +97,8 @@ void ofxSvg::deepCopyFrom( const ofxSvg & mom ) { //-------------------------------------------------------------- void ofxSvg::moveFrom( ofxSvg&& mom ) { + ofLogVerbose("ofxSvg::moveFrom"); + mChildren = std::move(mom.mChildren); mDefElements = std::move(mom.mDefElements); @@ -142,20 +112,24 @@ void ofxSvg::moveFrom( ofxSvg&& mom ) { mCurrentLayer = mom.mCurrentLayer; mUnitStr = mom.mUnitStr; - mCurrentSvgCss = mom.mCurrentSvgCss; +// mCurrentSvgCss = mom.mCurrentSvgCss; - mSvgCss = mom.mSvgCss; - mCurrentCss = mom.mCurrentCss; + mSvgCss = std::move(mom.mSvgCss); + mCurrentCss = std::move(mom.mCurrentCss); + mDocumentCss = std::move(mom.mDocumentCss); mFillColor = mom.mFillColor; mStrokeColor = mom.mStrokeColor; + mCssClassStack = std::move(mom.mCssClassStack); + _buildCurrentSvgCssFromStack(); + mModelMatrix = mom.mModelMatrix; mModelMatrixStack = mom.mModelMatrixStack; mCircleResolution = mom.mCircleResolution; mCurveResolution = mom.mCurveResolution; - mPaths = mom.mPaths; + mPaths = std::move(mom.mPaths); } @@ -208,13 +182,13 @@ bool ofxSvg::load( const of::filesystem::path& fileName ) { svgPath = fileName; folderPath = ofFilePath::getEnclosingDirectory( fileName, false ); - + ofXml xml; - if( !xml.load(tMainXmlBuffer )) { - ofLogWarning("ofxSvg") << " unable to load svg from " << fileName; + if (!xml.load(tMainXmlBuffer)) { + ofLogWarning("ofxSvg") << " unable to load svg from " << fileName << " mainXmlFile: " << mainXmlFile.getAbsolutePath(); return false; } - + return loadFromString(tMainXmlBuffer.getText()); } @@ -372,16 +346,21 @@ void ofxSvg::clear() { mChildren.clear(); mDefElements.clear(); mCurrentLayer = 0; - mCurrentSvgCss.reset(); +// mCurrentSvgCss.reset(); mSvgCss.clear(); mCPoints.clear(); mCenterPoints.clear(); mCurrentCss.clear(); + mCssClassStack.clear(); mGroupStack.clear(); mModelMatrix = glm::mat4(1.f); mModelMatrixStack = std::stack(); + loadIdentityMatrix(); + + mFillColor = ofColor(0); + mStrokeColor = ofColor(0); mPaths.clear(); } @@ -394,7 +373,7 @@ const int ofxSvg::getTotalLayers(){ //-------------------------------------------------------------- void ofxSvg::recalculateLayers() { mCurrentLayer = 0; - auto allKids = getAllChildren(true); + auto allKids = getAllElements(true); for( auto& kid : allKids ) { kid->layer = mCurrentLayer += 1.0; } @@ -439,10 +418,12 @@ const std::vector& ofxSvg::getPaths() const { //-------------------------------------------------------------- void ofxSvg::setFontsDirectory( string aDir ) { - fontsDirectory = aDir; - if( fontsDirectory.back() != '/' ) { - fontsDirectory += '/'; + auto fontsDir = aDir; + if( fontsDir.size() > 1 && fontsDir.back() != '/' ) { + fontsDir += '/'; } + fontsDirectory = fontsDir; + ofxSvgFontBook::setFontDirectory(fontsDir); } //-------------------------------------------------------------- @@ -548,7 +529,6 @@ void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr(); tgroup->layer = mCurrentLayer += 1.0; auto idattr = kid.getAttribute("id"); @@ -556,10 +536,25 @@ void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptrname = idattr.getValue(); } - mCurrentSvgCss = std::make_shared( _parseStyle(kid) ); + auto kidStyle = _parseStyle(kid); + _pushCssClass(kidStyle); + + // lets figure out the parsing of the transform + auto transAttr = kid.getAttribute("transform"); + if( transAttr ) { + pushMatrix(); + auto gmat = setTransformFromSvgMatrixString( transAttr.getValue(), tgroup ); + multMatrix(gmat); + } aElements.push_back( tgroup ); _parseXmlNode( kid, tgroup->getChildren() ); + + if( transAttr ) { + popMatrix(); + } + + _popCssClass(); } } else if( kid.getName() == "defs") { ofLogVerbose("ofxSvg") << __FUNCTION__ << " found a defs node."; @@ -587,9 +582,11 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrname == href ) { - telement = clone(def); +// ofLogNotice("ofxSvg") << "Found a mDefElement with href: " << def->getName(); +// telement = clone(def); + telement = def->clone(); if( !telement ) { - ofLogWarning("Parser") << "could not find type for def : " << def->name; + ofLogWarning("ofxSvg") << "could not find type for def : " << def->name; } break; } @@ -608,11 +605,19 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrheight = hattr.getFloatValue(); auto xlinkAttr = tnode.getAttribute("xlink:href"); if( xlinkAttr ) { - image->filepath = folderPath; - image->filepath.append(xlinkAttr.getValue()); -// image->filepath = folderPath+xlinkAttr.getValue(); + // determine if this is an embedded image // + if( ofIsStringInString(xlinkAttr.getValue(), "image/png;base64")) { + auto decodedPix = ofxSvgUtils::base64_decode(xlinkAttr.getValue() ); + if(decodedPix.isAllocated() && decodedPix.getWidth() > 0 && decodedPix.getHeight() > 0 ) { + image->img.setFromPixels(decodedPix); + } + } else { + image->filepath = folderPath; + image->filepath.append(xlinkAttr.getValue()); + telement = image; + } } - telement = image; + } else if( tnode.getName() == "ellipse" ) { auto ellipse = std::make_shared(); @@ -734,24 +739,50 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr(); telement = text; -// std::cout << "has kids: " << tnode.getFirstChild() << " node value: " << tnode.getValue() << std::endl; + + auto textCss = _parseStyle( tnode ); + _pushCssClass(textCss); +// auto tempCss = mCurrentCss; +// textCss.addMissingClassProperties(tempCss); +// mCurrentCss = textCss; + +// ofLogNotice("ofxSvg") << "_addElementFromXmlNode :: text: " << "has kids: " << tnode.getFirstChild() << " node value: " << tnode.getValue() << std::endl; +// ofLogNotice("ofxSvg") << "_addElementFromXmlNode :: text: " << "name:" << tnode.getName() << " to string: " << tnode.toString() << std::endl; + if( tnode.getAttribute("id")) { +// ofLogNotice("ofxSvg") << "_addElementFromXmlNode :: text: " << tnode.getAttribute("id").getValue(); + } + if( tnode.getFirstChild() ) { auto kids = tnode.getChildren(); for( auto& kid : kids ) { if(kid) { if( kid.getName() == "tspan" ) { - text->textSpans.push_back( _getTextSpanFromXmlNode( kid ) ); +// text->textSpans.push_back( _getTextSpanFromXmlNode( kid ) ); + _getTextSpanFromXmlNode( kid, text->textSpans ); } } } // this may not be a text block or it may have no text // if( text->textSpans.size() == 0 ) { - text->textSpans.push_back( _getTextSpanFromXmlNode( tnode ) ); + // ok lets see if the node has a value / text + if( !tnode.getValue().empty() ) { + _getTextSpanFromXmlNode( tnode, text->textSpans ); + } } } +// ofLogNotice("ofxSvg") << "_addElementFromXmlNode :: text:: num spans: " << text->textSpans.size(); +// for( auto& tspan : text->textSpans ) { +// ofLogNotice("ofxSvg") << "_addElementFromXmlNode :: text:: " << tspan->text; +// } + + + _popCssClass(); + +// mCurrentCss = tempCss; + string tempFolderPath = ofFilePath::addTrailingSlash(folderPath); if( ofDirectory::doesDirectoryExist( tempFolderPath+"fonts/" )) { text->setFontDirectory( tempFolderPath+"fonts/" ); @@ -775,26 +806,27 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrname = idAttr.getValue(); } - 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 ) { +// if( telement->getType() == OFXSVG_TYPE_RECTANGLE || telement->getType() == OFXSVG_TYPE_IMAGE || telement->getType() == OFXSVG_TYPE_TEXT || telement->getType() == OFXSVG_TYPE_CIRCLE || telement->getType() == OFXSVG_TYPE_ELLIPSE ) { + if( telement->getType() != OFXSVG_TYPE_DOCUMENT ) { 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 = { - ofxSvgType::TYPE_RECTANGLE, - ofxSvgType::TYPE_CIRCLE, - ofxSvgType::TYPE_ELLIPSE - }; +// std::vector typesToApplyTransformToPath = { +// OFXSVG_TYPE_RECTANGLE, +// OFXSVG_TYPE_CIRCLE, +// OFXSVG_TYPE_ELLIPSE +// }; bool bApplyTransformToPath = false; - for( auto & etype : typesToApplyTransformToPath ) { - if( etype == telement->getType() ) { - bApplyTransformToPath = true; - break; - } - } +// for( auto & etype : typesToApplyTransformToPath ) { +// if( etype == telement->getType() ) { +// bApplyTransformToPath = true; +// break; +// } +// } if( bApplyTransformToPath ) { auto epath = std::dynamic_pointer_cast( telement ); @@ -824,9 +856,9 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrgetType() == ofxSvgType::TYPE_TEXT ) { + if( telement->getType() == OFXSVG_TYPE_TEXT ) { auto text = std::dynamic_pointer_cast( telement ); - text->ogPos = text->pos; +// text->ogPos = text->pos; text->create(); } @@ -995,6 +1027,9 @@ void ofxSvg::_parsePolylinePolygon( ofXml& tnode, std::shared_ptr aS // reference: https://www.w3.org/TR/SVG2/paths.html#PathData //-------------------------------------------------------------- void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { + // path4160 + + aSvgPath->path.clear(); auto dattr = tnode.getAttribute("d"); @@ -1072,7 +1107,27 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { return aCurrentPos; }; + auto lineToRelativeRecursive = [](glm::vec3& aStartPos, glm::vec3& acurrentPos, std::vector& aposes, std::shared_ptr aPath ) { +// int ncounter = 0; + auto cp = aStartPos; + for( auto& np : aposes ) { + auto relativePos = np-aStartPos; + auto newPos = relativePos + cp; +// ofLogNotice("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " cp: " << cp << " np: " << np; +// ofLogVerbose("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " np: " << np << " relative: " << relativePos << " newPos: " << newPos << " currentPos: " << currentPos; + aPath->path.lineTo(newPos); + cp = newPos;//relativePos+prevPos; +// ncounter++; + } + acurrentPos = cp; + }; + std::string tname; + if( auto tattr = tnode.getAttribute("id")) { + tname = tattr.getValue(); + } + + ofLogVerbose("ofxSvg::_parsePath") << " ------ PARSE-" << tname << "-----------------------" ; aSvgPath->path.clear(); @@ -1096,14 +1151,15 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { break; } - ofLogVerbose("ofxSvg") << " o : ["<< ostring[index] <<"]"; + + ofLogVerbose("ofxSvg") << tname << "- o : ["<< ostring[index] <<"]"; // up to next valid character // std::string currentString; bool bFoundValidNextChar = false; auto pos = index+1; if( pos >= ostring.size() ) { - ofLogVerbose("ofxSvg") << "pos is greater than string size: " << pos << " / " << ostring.size(); +// ofLogVerbose("ofxSvg") << "pos is greater than string size: " << pos << " / " << ostring.size(); // break; breakMe = true; } @@ -1122,6 +1178,8 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { currentString.push_back(ostring[pos]); } + + int cindex = index; index += currentString.size()+1; @@ -1131,7 +1189,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } - ofLogVerbose("ofxSvg") << "["< npositions= {glm::vec3(0.f, 0.f, 0.f)}; @@ -1139,10 +1197,26 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { // check if we are looking for a position if( cchar == 'm' || cchar == 'M' ) { - if( cchar == 'm' ) { + /* ------------------------------------------------ +// https://www.w3.org/TR/SVG/paths.html + "Start a new sub-path at the given (x,y) coordinates. M (uppercase) indicates that absolute coordinates will follow; + m (lowercase) indicates that relative coordinates will follow. + If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands. + Hence, implicit lineto commands will be relative if the moveto is relative, and absolute if the moveto is absolute. + If a relative moveto (m) appears as the first element of the path, then it is treated as a pair of absolute coordinates. + In this case, subsequent pairs of coordinates are treated as relative even though the initial moveto is interpreted as an absolute moveto." + ------------------------------------------------ */ + + if( cchar == 'm' && cindex > 0 ) { bRelative = true; } npositions = parsePoints(currentString); + for( int ni = 0; ni < npositions.size(); ni++ ) { + ofLogVerbose("ofxSvg::_parsePath") << ni << "-" << npositions[ni]; + } +// if( npositions.size() > 0 && bRelative ) { +// mCurrentPathPos = npositions[0]; +// } ctype = ofPath::Command::moveTo; } else if( cchar == 'v' || cchar == 'V' ) { if( cchar == 'v' ) { @@ -1153,6 +1227,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } npositions[0].y = ofToFloat(currentString); + //ofLogVerbose("ofxSvg") << cchar << " line to: " << npositions[0] << " current pos: " << currentPos; ctype = ofPath::Command::lineTo; } else if( cchar == 'H' || cchar == 'h' ) { if( cchar == 'h' ) { @@ -1255,6 +1330,10 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { // ofLogNotice("ofxSvg") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; // } if( npositions.size() > 0 && commandT != ofPath::Command::close ) { + if( commandT != ofPath::Command::moveTo ) { + //currentPos = {0.f, 0.f, 0.f}; + } +// currentPos = convertToAbsolute(bRelative, currentPos, npositions ); currentPos = convertToAbsolute(bRelative, currentPos, npositions ); } } @@ -1272,8 +1351,57 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { if( commandT == ofPath::Command::moveTo ) { aSvgPath->path.moveTo(currentPos); + if(npositions.size() > 1 ) { + + bool bLineToRelative = bRelative; + // determine if these points started with m and is the first character + if( cchar == 'm' && cindex == 0 ) { + bLineToRelative = true; + } + + if( bLineToRelative ) { + auto newPoses = npositions; + newPoses.erase(newPoses.begin()); + lineToRelativeRecursive(prevPos, currentPos, newPoses, aSvgPath); + } else { + for( int ki = 1; ki < npositions.size(); ki++ ) { + aSvgPath->path.lineTo(npositions[ki]); + } + } + } } else if( commandT == ofPath::Command::lineTo ) { - aSvgPath->path.lineTo(currentPos); + if( npositions.size() > 0 ) { + // current pos is already set above + // so just worry about adding paths + if( bRelative ) { + + lineToRelativeRecursive(prevPos, currentPos, npositions, aSvgPath ); +// mCenterPoints.clear(); +// int ncounter = 0; +// auto cp = prevPos; +// for( auto& np : npositions ) { +// auto relativePos = np-prevPos; +// auto newPos = relativePos + cp; +//// ofLogNotice("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " cp: " << cp << " np: " << np; +//// ofLogVerbose("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " np: " << np << " relative: " << relativePos << " newPos: " << newPos << " currentPos: " << currentPos; +// aSvgPath->path.lineTo(newPos); +// mCenterPoints.push_back(newPos); +// cp = newPos;//relativePos+prevPos; +// ncounter++; +// } +// currentPos = cp; +// // } + } else { +// int ncounter = 0; + for( auto& np : npositions ) { +// ofLogVerbose("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " np: " << np; + aSvgPath->path.lineTo(np); +// ncounter++; + } + } + } +// aSvgPath->path.moveTo(currentPos); +// aSvgPath->path.lineTo(currentPos); } else if( commandT == ofPath::Command::close ) { // ofLogNotice("ofxSvg") << "Closing the path"; aSvgPath->path.close(); @@ -1476,21 +1604,23 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { justInCase++; } + +// mCurrentPathPos = currentPos; } //-------------------------------------------------------------- ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { ofxSvgCssClass css; - if( mCurrentSvgCss ) { +// if( mCurrentSvgCss ) { // apply first if we have a global style // - for( auto& tprop : mCurrentSvgCss->properties ) { + for( auto& tprop : mCurrentCss.properties ) { if( tprop.first.empty() ) { ofLogNotice("ofxSvg") << "First prop is empty"; } 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 // @@ -1560,77 +1690,15 @@ void ofxSvg::_applyStyleToElement( ofXml& tnode, std::shared_ptr //-------------------------------------------------------------- void ofxSvg::_applyStyleToPath( ofXml& tnode, std::shared_ptr aSvgPath ) { auto css = _parseStyle(tnode); - _applyStyleToPath(css, aSvgPath); -} - -//-------------------------------------------------------------- -void ofxSvg::_applyStyleToPath( ofxSvgCssClass& 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.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")); - } - - 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); - } - } + aSvgPath->applyStyle(css); } //-------------------------------------------------------------- void ofxSvg::_applyStyleToText( ofXml& anode, std::shared_ptr aTextSpan ) { auto css = _parseStyle(anode); - _applyStyleToText(css, aTextSpan); + aTextSpan->applyStyle(css); } -//-------------------------------------------------------------- -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 ); - aTextSpan->color = aclass.getColor("fill"); -} - - //-------------------------------------------------------------- glm::vec3 ofxSvg::_parseMatrixString(const std::string& input, const std::string& aprefix, bool abDefaultZero ) { @@ -1648,6 +1716,8 @@ glm::vec3 ofxSvg::_parseMatrixString(const std::string& input, const std::string // Extract the part inside the parentheses std::string inside = input.substr(startPos, endPos - startPos); + std::replace(inside.begin(), inside.end(), ',', ' '); + // Ensure numbers like ".5" are correctly handled by adding a leading zero if needed if (inside[0] == '.') { inside = "0" + inside; @@ -1679,24 +1749,29 @@ glm::vec3 ofxSvg::_parseMatrixString(const std::string& input, const std::string //-------------------------------------------------------------- //bool Parser::getTransformFromSvgMatrix( string aStr, glm::vec2& apos, float& scaleX, float& scaleY, float& arotation ) { -bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr aele ) { - +glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr aele ) { + ofLogVerbose("-----------ofxSvg") << __FUNCTION__ << " name: " << aele->getName() +"----------------"; 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("ofxSvg") << __FUNCTION__ << " going to parse string: " << aStr << " pos: " << aele->pos; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " going to parse string: " << aStr << " pos: " << aele->pos; glm::mat4 mat = glm::mat4(1.f); + glm::mat4 gmat = mModelMatrix; if( ofIsStringInString(aStr, "translate")) { auto transStr = aStr; auto tp = _parseMatrixString(transStr, "translate", false ); - ofLogVerbose("ofxSvg") << __FUNCTION__ << " translate: " << tp; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " translate: " << tp; // apos += tp; mat = glm::translate(glm::mat4(1.0f), glm::vec3(tp.x, tp.y, 0.0f)); + gmat = glm::translate(gmat, glm::vec3(tp.x, tp.y, 0.0f)); +// aele->pos.x = tp.x; +// aele->pos.y = tp.y; } else { mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.f, 0.f, 0.0f)); + gmat = glm::translate(gmat, glm::vec3(0.f, 0.f, 0.0f)); } if( ofIsStringInString(aStr, "rotate")) { @@ -1723,12 +1798,15 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptrrotation), glm::vec3(0.f, 0.f, 1.f))); +// mat = mat * glm::toMat4((const glm::quat&)glm::angleAxis(glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f))); + mat = glm::rotate(mat, glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f)); + gmat = glm::rotate(gmat, glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f)); } // ofLogNotice("ofxSvg") << "rcenter: " << rcenter.x << ", " << rcenter.y; } - ofLogVerbose("ofxSvg") << __FUNCTION__ << " arotation: " << aele->rotation; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " arotation: " << aele->rotation << " trot: " << tr; } if( ofIsStringInString(aStr, "scale")) { @@ -1736,25 +1814,52 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptrscale.x = ts.x; aele->scale.y = ts.y; - ofLogVerbose("ofxSvg") << __FUNCTION__ << " scale: " << ts; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " scale: " << ts; mat = glm::scale(mat, glm::vec3(aele->scale.x, aele->scale.y, 1.f)); + gmat = glm::scale(gmat, 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 ); +// pos3 = gmat * glm::vec4( aele->pos.x, aele->pos.y, 0.0f, 1.f ); aele->pos.x = pos3.x; aele->pos.y = pos3.y; +// aele + +// aele->mMat = mat; + +// glm::vec3 skew; +// glm::vec4 perspective; +// glm::quat orientation; +// glm::vec3 translation, scale; + +// decompose( ofVec3f& translation, +// ofQuaternion& rotation, +// ofVec3f& scale, +// ofQuaternion& so ) +// glm::decompose(mat, scale, orientation, translation, skew, perspective); + +// aele->pos = glm::vec2(translation); +// aele->scale = glm::vec2(scale); +// aele->rotation = glm::degrees(glm::eulerAngles(orientation).z); + + if( ofIsStringInString(aStr, "matrix")) { + + // g3978-7 + // transform="matrix(-1,0,0,-1,358.9498,1564.4744)" + auto matrix = aStr; ofStringReplace(matrix, "matrix(", ""); ofStringReplace(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; + std::cout << aele->getCleanName() << " matrix[" << i << "] = " << matrixF[i] << " string version is " << matrixNum[i] << std::endl; } if( matrixNum.size() == 6 ) { @@ -1763,7 +1868,8 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptrrotation = 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))); +// mat = mat * glm::toMat4((const glm::quat&)glm::angleAxis(glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f))); + mat = glm::rotate(mat, 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]); @@ -1794,7 +1900,8 @@ bool ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptrpos, 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) @@ -1845,7 +1949,10 @@ std::string ofxSvg::getSvgMatrixStringFromElement( std::shared_ptr ofxSvg::_getTextSpanFromXmlNode( ofXml& anode ) { +void ofxSvg::_getTextSpanFromXmlNode( ofXml& anode, std::vector< std::shared_ptr >& aspans ) { +// if( anode.getName() != "tspan") { +// return; +// } auto tspan = std::make_shared(); string tText = anode.getValue(); @@ -1863,10 +1970,29 @@ std::shared_ptr ofxSvg::_getTextSpanFromXmlNode( ofXml& an tspan->text = tText; tspan->rect.x = tx; tspan->rect.y = ty; +// tspan->ogPos.x = tx; +// tspan->ogPos.y = ty; _applyStyleToText(anode, tspan); + +// ofLogNotice("ofxSvg::_getTextSpanFromXmlNode") << anode.getValue() << " anode string: " << anode.toString(); + + aspans.push_back(tspan); + + _pushCssClass(tspan->getCss()); + for( auto& kid : anode.getChildren() ) { + if( kid ) { + if( kid.getName() == "tspan") { +// ofLogNotice("ofxSvg::_getTextSpanFromXmlNode") << anode.getValue() << " anode string: " << anode.toString(); + _getTextSpanFromXmlNode( kid, aspans ); + } + } + } + _popCssClass(); + +// ofLogNotice("ofxSvg::_getTextSpanFromXmlNode") << "text: " << tspan->text; - return tspan; +// return tspan; } //-------------------------------------------------------------- @@ -1947,7 +2073,7 @@ void ofxSvg::pushGroup( const std::string& apath ) { if( cgroup ) { pushGroup(cgroup); } else { - ofLogWarning("ofx::svg::Parser") << "could not find group with path " << apath; + ofLogWarning("ofxSvg") << "could not find group with path " << apath; } } @@ -1968,14 +2094,17 @@ void ofxSvg::popGroup() { //-------------------------------------------------------------- void ofxSvg::setFillColor(ofColor acolor) { mFillColor = acolor; + mDocumentCss.setFillColor(acolor); mCurrentCss.setFillColor(acolor); } //-------------------------------------------------------------- void ofxSvg::setFilled(bool abFilled) { if( abFilled ) { + mDocumentCss.setFillColor(mFillColor); mCurrentCss.setFillColor(mFillColor); } else { + mDocumentCss.setNoFill(); mCurrentCss.setNoFill(); } } @@ -1984,19 +2113,23 @@ void ofxSvg::setFilled(bool abFilled) { void ofxSvg::setStrokeColor(ofColor acolor) { mStrokeColor = acolor; mCurrentCss.setStrokeColor(acolor); + mDocumentCss.setStrokeColor(acolor); } //-------------------------------------------------------------- void ofxSvg::setStrokeWidth(float aLineWidth) { mCurrentCss.setStrokeWidth(aLineWidth); + mDocumentCss.setStrokeWidth(aLineWidth); } //-------------------------------------------------------------- void ofxSvg::setHasStroke(bool abStroke) { if( abStroke ) { mCurrentCss.setStrokeColor(mStrokeColor); + mDocumentCss.setStrokeColor(mStrokeColor); } else { mCurrentCss.setNoStroke(); + mDocumentCss.setNoStroke(); } } @@ -2015,7 +2148,8 @@ std::shared_ptr ofxSvg::add( const ofPath& apath ) { path->path = apath; // _config(path); _applyModelMatrixToElement( path, glm::vec2(0.f, 0.f) ); - _applyStyleToPath( mCurrentCss, path ); +// _applyStyleToPath( mCurrentCss, path ); + path->applyStyle(mCurrentCss); _getPushedGroup()->add(path); recalculateLayers(); mPaths.clear(); @@ -2075,7 +2209,8 @@ std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aR rect->round = aRoundRadius; rect->path.rectangle(arect); // _config(rect); - _applyStyleToPath( mCurrentCss, rect ); +// _applyStyleToPath( mCurrentCss, rect ); + rect->applyStyle(mCurrentCss); _getPushedGroup()->add(rect); recalculateLayers(); mPaths.clear(); @@ -2096,7 +2231,8 @@ std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float ar circle->path.setCircleResolution(mCircleResolution); circle->path.circle(apos, aradius); // _config(circle); - _applyStyleToPath( mCurrentCss, circle ); +// _applyStyleToPath( mCurrentCss, circle ); + circle->applyStyle(mCurrentCss); _getPushedGroup()->add(circle); recalculateLayers(); mPaths.clear(); @@ -2128,7 +2264,8 @@ std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float ellipse->path.setCircleResolution(mCircleResolution); ellipse->path.ellipse(apos, aradiusX, aradiusY); - _applyStyleToPath( mCurrentCss, ellipse ); +// _applyStyleToPath( mCurrentCss, ellipse ); + ellipse->applyStyle(mCurrentCss); _getPushedGroup()->add(ellipse); recalculateLayers(); mPaths.clear(); @@ -2162,6 +2299,18 @@ std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of:: return img; } +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addEmbeddedImage( const ofPixels& apixels ) { + auto img = std::make_shared(); + img->img.setFromPixels(apixels); + img->width = apixels.getWidth(); + img->height = apixels.getHeight(); + _applyModelMatrixToElement( img, glm::vec2(0.f, 0.f) ); + _getPushedGroup()->add(img); + recalculateLayers(); + return img; +} + //---------------------------------------------------------- void ofxSvg::pushMatrix() { mModelMatrixStack.push(mModelMatrix); @@ -2247,9 +2396,16 @@ void ofxSvg::drawDebug() { ofFill(); - ofSetColor( ofColor::orange ); + + int tcounter = 0; for( auto& cp : mCenterPoints ) { + if(tcounter == 0) { + ofSetColor( ofColor::cyan ); + } else { + ofSetColor( ofColor::orange ); + } ofDrawCircle(cp, 4.f); + tcounter++; } } @@ -2367,7 +2523,7 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { iattr.set(aele->getName()); } } - if( aele->getType() == ofxSvgType::TYPE_GROUP ) { + if( aele->getType() == OFXSVG_TYPE_GROUP ) { auto tgroup = std::dynamic_pointer_cast(aele); if( tgroup ) { if( tgroup->getNumChildren() > 0 ) { @@ -2376,7 +2532,7 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { } } } - } else if( aele->getType() == ofxSvgType::TYPE_RECTANGLE ) { + } else if( aele->getType() == OFXSVG_TYPE_RECTANGLE ) { auto trect = std::dynamic_pointer_cast(aele); _addCssClassFromPath( trect, txml ); @@ -2401,7 +2557,7 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { } } - } else if( aele->getType() == ofxSvgType::TYPE_IMAGE ) { + } else if( aele->getType() == OFXSVG_TYPE_IMAGE ) { auto timage = std::dynamic_pointer_cast(aele); _addCssClassFromImage( timage, txml ); @@ -2416,10 +2572,33 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { if( auto xattr = txml.appendAttribute("xlink:href")) { xattr.set(timage->getFilePath().string()); } + } else { + // check for embedded image // + if( timage->img.getPixels().isAllocated() ) { + // embed the pixels // + if( auto xattr = txml.appendAttribute("xlink:href")) { +// ofPixels& pix = timage->img.getPixels(); +// size_t ilen = pix.getWidth() * pix.getHeight() * pix.getNumChannels(); +// +// ofBuffer tbuffer; +// ofSaveImage(pix, tbuffer); +// +//// auto base64String = base64_encode( pix.getData(), ilen, false ); +//// std::string base64String = encode( pix.getData(), ilen ); +// auto buffStr = tbuffer.getText(); +// const unsigned char* data = reinterpret_cast(buffStr.data()); +//// std::string base64String = encode(data, tbuffer.size()); +// auto base64String = ofxSvgUtils::base64_encode( data, tbuffer.size(), false ); +//// std::string str = pix.getData(); + auto base64String = ofxSvgUtils::base64_encode( timage->img.getPixels() ); + std::string encString = "data:image/png;base64,"+base64String; + xattr.set(encString); + } + } } - } else if( aele->getType() == ofxSvgType::TYPE_ELLIPSE ) { + } else if( aele->getType() == OFXSVG_TYPE_ELLIPSE ) { auto tellipse = std::dynamic_pointer_cast(aele); _addCssClassFromPath( tellipse, txml ); @@ -2436,7 +2615,7 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { xattr.set(tellipse->radiusY); } - } else if( aele->getType() == ofxSvgType::TYPE_CIRCLE ) { + } else if( aele->getType() == OFXSVG_TYPE_CIRCLE ) { auto tcircle = std::dynamic_pointer_cast(aele); _addCssClassFromPath( tcircle, txml ); @@ -2450,7 +2629,7 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { xattr.set(tcircle->getRadius()); } - } else if( aele->getType() == ofxSvgType::TYPE_PATH ) { + } else if( aele->getType() == OFXSVG_TYPE_PATH ) { auto tpath = std::dynamic_pointer_cast(aele); _addCssClassFromPath( tpath, txml ); @@ -2525,20 +2704,41 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { - } else if( aele->getType() == ofxSvgType::TYPE_TEXT ) { + } else if( aele->getType() == OFXSVG_TYPE_TEXT ) { // TODO: Maybe at some point ;/ } // figure out if we need a transform attribute - if( aele->getType() == ofxSvgType::TYPE_IMAGE || aele->rotation != 0.0f || aele->scale.x != 1.0f || aele->scale.y != 1.0f ) { + if( aele->getType() == OFXSVG_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; } +//-------------------------------------------------------------- +void ofxSvg::_pushCssClass( const ofxSvgCssClass& acss ) { + mCssClassStack.push_back(acss); + _buildCurrentSvgCssFromStack(); +} + +//-------------------------------------------------------------- +void ofxSvg::_popCssClass() { + if( mCssClassStack.size() > 0 ) { + mCssClassStack.pop_back(); + _buildCurrentSvgCssFromStack(); + } +} + +//-------------------------------------------------------------- +void ofxSvg::_buildCurrentSvgCssFromStack() { + // maybe not efficient, but should account for removing / adding + mCurrentCss.clear(); + mCurrentCss.setClassProperties(mDocumentCss); + for( auto& css : mCssClassStack ) { + mCurrentCss.setClassProperties(css); + } +} + diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index 79fc45690aa..ae3203b1401 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -1,9 +1,3 @@ -// -// ofxSvgParser.h -// -// Created by Nick Hardeman on 8/31/24. -// - #pragma once #include "ofxSvgGroup.h" #include "ofXml.h" @@ -11,23 +5,22 @@ #include /// \file -/// ofxSVG is used for loading, manipulating, rendering and saving of SVG files. +/// 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 { +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;} + virtual ofxSvgType getType() override {return OFXSVG_TYPE_DOCUMENT;} // Default constructor ofxSvg() = default; // Copy constructor (deep copy) - ofxSvg(const ofxSvg & mom);// = default; + ofxSvg(const ofxSvg & mom); // Copy assignment operator (deep copy) ofxSvg& operator=(const ofxSvg& mom); // Move constructor @@ -90,7 +83,7 @@ class ofxSvg : public ofxSvgGroup { /// 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 ); + glm::mat4 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 ) @@ -177,11 +170,13 @@ class ofxSvg : public ofxSvgGroup { /// \return int of the circle resolution. int getCurveResolution() { return mCurveResolution; }; /// \brief Get the current css used for items. - /// \return ofx::svg::CssClass. + /// \return ofxSvgCssClass. ofxSvgCssClass& getCurrentCss() { return mCurrentCss;} + + ofxSvgCssStyleSheet& getCssStyleSheet() {return mSvgCss; } /// \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. + /// \return std::shared_ptr as the group that was created. std::shared_ptr addGroup(std::string aname); std::shared_ptr add( const ofPath& apath ); @@ -205,6 +200,7 @@ class ofxSvg : public ofxSvgGroup { 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 addEmbeddedImage( const ofPixels& apixels ); // adapted from ofGLProgrammableRenderer for some sort of conformity void pushMatrix(); @@ -237,13 +233,11 @@ class ofxSvg : public ofxSvgGroup { 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 ); + void _getTextSpanFromXmlNode( ofXml& anode, std::vector< std::shared_ptr >& aspans ); ofxSvgGroup* _getPushedGroup(); bool _hasPushedMatrix(); @@ -257,18 +251,22 @@ class ofxSvg : public ofxSvgGroup { void _addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ); bool _toXml( ofXml& aParentNode, std::shared_ptr aele ); + void _pushCssClass( const ofxSvgCssClass& acss ); + void _popCssClass(); + void _buildCurrentSvgCssFromStack(); + unsigned int mCurrentLayer = 0; std::string mUnitStr = "px"; ofxSvgCssStyleSheet mSvgCss; - ofxSvgCssClass mCurrentCss; - ofColor mFillColor, mStrokeColor; + ofxSvgCssClass mCurrentCss, mDocumentCss; + std::vector mCssClassStack; - std::vector< std::shared_ptr > mGroupStack; - std::shared_ptr mCurrentSvgCss; + ofColor mFillColor, mStrokeColor; + std::vector< std::shared_ptr > mGroupStack; std::vector< std::shared_ptr > mDefElements; // just used for debugging @@ -284,6 +282,7 @@ class ofxSvg : public ofxSvgGroup { // for legacy purposes // static ofPath sDummyPath; mutable std::vector mPaths; + }; diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index 7d9a93e1a4a..5bc3be7134f 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -1,10 +1,3 @@ -// -// ofxSvg2Css.cp -// Example -// -// Created by Nick Hardeman on 8/22/24. -// - #include "ofxSvgCss.h" #include "ofUtils.h" #include "ofLog.h" @@ -72,6 +65,18 @@ void ofxSvgCssClass::clear() { name = "default"; } +//-------------------------------------------------------------- +void ofxSvgCssClass::scaleNumericalValues( float ascale ) { + for( auto& piter : properties ) { + if(piter.second.fvalue.has_value()) { + piter.second.fvalue = piter.second.fvalue.value() * ascale; + } + if( piter.second.ivalue.has_value() ) { + piter.second.ivalue = piter.second.ivalue.value() * ascale; + } + } +} + //-------------------------------------------------------------- std::string ofxSvgCssClass::sRgbaToHexString(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { std::stringstream ss; @@ -113,11 +118,33 @@ ofColor ofxSvgCssClass::sGetColor(const std::string& astr ) { bHasHash = true; } cstr = ofToLower(cstr); + // if this is only 3 chars, it is short hand and needs to be expanded to 6 characters + if( cstr.size() == 3 ) { + std::string fullHex; + for( char c : cstr ) { + fullHex += c; + fullHex += c; + } + cstr = fullHex; + } if( bHasHash ) { ofColor tcolor(255); - int hint = ofHexToInt(cstr); - tcolor.setHex(hint); + if( cstr.size() == 8 ) { + ofLogVerbose("SvgCss") << "going to try to get the hex from: " << cstr; + unsigned long hexValue = std::stoul(cstr, nullptr, 16); + // Manually extract components from the 8-digit hex value (RRGGBBAA) + int r = (hexValue >> 24) & 0xFF; // Extract the red component + int g = (hexValue >> 16) & 0xFF; // Extract the green component + int b = (hexValue >> 8) & 0xFF; // Extract the blue component + int a = hexValue & 0xFF; // Extract the alpha component + + // Create the color using the extracted components + tcolor.set(r, g, b, a); + } else { + int hint = ofHexToInt(cstr); + tcolor.setHex(hint); + } // ofLogNotice("ofxSvgCssClass") << "color: " << cstr << " ofColor: " << tcolor; return tcolor; } else if( !astr.empty() ) { @@ -141,6 +168,24 @@ float ofxSvgCssClass::sGetFloat(const std::string& astr) { return ofToFloat( cstr ); } +//-------------------------------------------------------------- +bool ofxSvgCssClass::addMissingClassProperties( const ofxSvgCssClass& aclass ) { + for( auto& propI : aclass.properties ) { + if( properties.count(propI.first) < 1 ) { + properties[propI.first] = propI.second; + } + } + return properties.size() > 0; +} + +//-------------------------------------------------------------- +bool ofxSvgCssClass::setClassProperties( const ofxSvgCssClass& aclass ) { + for( auto& propI : aclass.properties ) { + properties[propI.first] = propI.second; + } + return properties.size() > 0; +} + //-------------------------------------------------------------- bool ofxSvgCssClass::addProperties( std::string aPropertiesString ) { if( aPropertiesString.size() > 0 ) { @@ -238,6 +283,44 @@ bool ofxSvgCssClass::hasStroke() { return !isNone("stroke"); } +//-------------------------------------------------- +bool ofxSvgCssClass::setFontSize( int asize ) { + return addProperty("font-size", asize); +} + +//-------------------------------------------------- +int ofxSvgCssClass::getFontSize(int adefault) { + return getIntValue("font-size", adefault); +} + +//-------------------------------------------------- +bool ofxSvgCssClass::setFontFamily( std::string aFontFamily ) { + return addProperty("font-family", aFontFamily); +} + +//-------------------------------------------------- +std::string ofxSvgCssClass::getFontFamily( std::string aDefaultFontFamily ) { + return getValue("font-family", aDefaultFontFamily); +} + +//-------------------------------------------------- +bool ofxSvgCssClass::isFontBold() { + bool bold = false; + if( ofIsStringInString(getValue("font-weight", "" ), "bold")) { + bold = true; + } + return bold; +} + +//-------------------------------------------------- +bool ofxSvgCssClass::isFontItalic() { + bool italic = false; + if( ofIsStringInString(getValue("font-style", "" ), "italic")) { + italic = true; + } + return italic; +} + //-------------------------------------------------- bool ofxSvgCssClass::hasProperty( const std::string& akey ) { return (properties.count(akey) > 0); @@ -306,7 +389,7 @@ float ofxSvgCssClass::getFloatValue(const std::string& akey, const float& adefau //-------------------------------------------------- ofColor ofxSvgCssClass::getColor(const std::string& akey) { if( properties.count(akey) < 1 ) { - return ofColor(255); + return ofColor(0); } auto& prop = properties[akey]; if( !prop.cvalue.has_value() ) { @@ -403,6 +486,13 @@ void ofxSvgCssStyleSheet::clear() { classes.clear(); } +//-------------------------------------------------- +void ofxSvgCssStyleSheet::scaleNumericalValues( float ascale ) { + for( auto& tclass : classes ) { + tclass.second.scaleNumericalValues(ascale); + } +} + //-------------------------------------------------- ofxSvgCssClass& ofxSvgCssStyleSheet::addClass(std::string aname) { if( hasClass(aname) ) { diff --git a/addons/ofxSvg/src/ofxSvgCss.h b/addons/ofxSvg/src/ofxSvgCss.h index 04794767092..71d347ceec2 100644 --- a/addons/ofxSvg/src/ofxSvgCss.h +++ b/addons/ofxSvg/src/ofxSvgCss.h @@ -1,10 +1,3 @@ -// -// ofxSvg2Css.h -// Example -// -// Created by Nick Hardeman on 8/22/24. -// - #pragma once #include #include "ofColor.h" @@ -14,7 +7,7 @@ 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 + // adding this Optional class since std::optional is not a part of all std:: distributions at the moment, looking at you gcc < 10 nh template class Optional { public: @@ -40,7 +33,7 @@ class ofxSvgCssClass { T& value() { // if (!hasValue) throw std::runtime_error("No value present"); if (!hasValue) { - ofLogError("ofx::svg::CssClass") << "No value present"; + ofLogError("ofxSvgCssClass") << "No value present"; } return data; } @@ -48,7 +41,7 @@ class ofxSvgCssClass { const T& value() const { // if (!hasValue) throw std::runtime_error("No value present"); if (!hasValue) { - ofLogError("ofx::svg::CssClass") << "No value present"; + ofLogError("ofxSvgCssClass") << "No value present"; } return data; } @@ -74,6 +67,7 @@ class ofxSvgCssClass { std::string name = "default"; void clear(); + void scaleNumericalValues( float ascale ); 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); @@ -81,6 +75,9 @@ class ofxSvgCssClass { static ofColor sGetColor(const std::string& astr); static float sGetFloat(const std::string& astr); + bool addMissingClassProperties( const ofxSvgCssClass& aclass ); + bool setClassProperties( const ofxSvgCssClass& aclass ); + bool addProperties( std::string aPropertiesString ); bool addProperty( std::string aPropString ); bool addProperty( std::string aName, std::string avalue ); @@ -97,6 +94,15 @@ class ofxSvgCssClass { bool setNoStroke(); bool hasStroke(); + bool setFontSize( int asize ); + int getFontSize(int adefault); + + bool setFontFamily( std::string aFontFamily ); + std::string getFontFamily( std::string aDefaultFontFamily ); + + bool isFontBold(); + bool isFontItalic(); + bool hasProperty( const std::string& akey ); Property& getProperty( const std::string& akey ); bool isNone(const std::string& akey); @@ -119,6 +125,8 @@ class ofxSvgCssStyleSheet { bool parse( std::string aCssString ); void clear(); + void scaleNumericalValues( float ascale ); + ofxSvgCssClass& addClass( std::string aname ); bool hasClass( const std::string& aname ); ofxSvgCssClass& getClass( const std::string& aname ); diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index 968e47e0f99..ea84bb1a161 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -1,46 +1,38 @@ -// -// ofxSvgElements.cpp -// -// Created by Nick Hardeman on 7/31/15. -// - #include "ofxSvgElements.h" #include "ofGraphics.h" +#include using std::vector; using std::string; -std::map< string, ofxSvgText::Font > ofxSvgText::fonts; -ofTrueTypeFont ofxSvgText::defaultFont; - //-------------------------------------------------------------- std::string ofxSvgElement::sGetTypeAsString(ofxSvgType atype) { switch (atype) { - case ofxSvgType::TYPE_GROUP: + case OFXSVG_TYPE_GROUP: return "Group"; break; - case ofxSvgType::TYPE_RECTANGLE: + case OFXSVG_TYPE_RECTANGLE: return "Rectangle"; break; - case ofxSvgType::TYPE_IMAGE: + case OFXSVG_TYPE_IMAGE: return "Image"; break; - case ofxSvgType::TYPE_ELLIPSE: + case OFXSVG_TYPE_ELLIPSE: return "Ellipse"; break; - case ofxSvgType::TYPE_CIRCLE: + case OFXSVG_TYPE_CIRCLE: return "Circle"; break; - case ofxSvgType::TYPE_PATH: + case OFXSVG_TYPE_PATH: return "Path"; break; - case ofxSvgType::TYPE_TEXT: + case OFXSVG_TYPE_TEXT: return "Text"; break; - case ofxSvgType::TYPE_DOCUMENT: + case OFXSVG_TYPE_DOCUMENT: return "Document"; break; - case ofxSvgType::TYPE_ELEMENT: + case OFXSVG_TYPE_ELEMENT: return "Element"; break; default: @@ -52,31 +44,31 @@ std::string ofxSvgElement::sGetTypeAsString(ofxSvgType atype) { //-------------------------------------------------------------- std::string ofxSvgElement::sGetSvgXmlName(ofxSvgType atype) { switch (atype) { - case ofxSvgType::TYPE_GROUP: + case OFXSVG_TYPE_GROUP: return "g"; break; - case ofxSvgType::TYPE_RECTANGLE: + case OFXSVG_TYPE_RECTANGLE: return "rect"; break; - case ofxSvgType::TYPE_IMAGE: + case OFXSVG_TYPE_IMAGE: return "image"; break; - case ofxSvgType::TYPE_ELLIPSE: + case OFXSVG_TYPE_ELLIPSE: return "ellipse"; break; - case ofxSvgType::TYPE_CIRCLE: + case OFXSVG_TYPE_CIRCLE: return "circle"; break; - case ofxSvgType::TYPE_PATH: + case OFXSVG_TYPE_PATH: return "path"; break; - case ofxSvgType::TYPE_TEXT: + case OFXSVG_TYPE_TEXT: return "text"; break; - case ofxSvgType::TYPE_DOCUMENT: + case OFXSVG_TYPE_DOCUMENT: return "svg"; break; - case ofxSvgType::TYPE_ELEMENT: + case OFXSVG_TYPE_ELEMENT: return "element"; break; default: @@ -90,6 +82,37 @@ string ofxSvgElement::getTypeAsString() { return sGetTypeAsString(getType()); } +//-------------------------------------------------------------- +string ofxSvgElement::getCleanName() { + if( name.empty() ) { + return name; + } + + string nstr = name; + + // Step 2: Replace known patterns from Illustrator + const std::vector> tregs = { + {std::regex("_x5F_"), "_"}, + {std::regex("_x28_"), "("}, + {std::regex("_x29_"), ")"}, + {std::regex("_x3B_"), ";"}, + {std::regex("_x2C_"), ","} + }; + + for (const auto& [regexPattern, replacement] : tregs) { + nstr = std::regex_replace(nstr, regexPattern, replacement); + } + + // this regex pattern removes the added numbers added by illustrator + // ie. lelbow_00000070086365269320197030000010368508730034196876_ becomes lelbow + // Define a regular expression pattern to match '__' + std::regex pattern("_\\d+_"); + // Use regex_replace to remove the pattern from the input string + nstr = std::regex_replace(nstr, pattern, ""); + + return nstr; +} + //-------------------------------------------------------------- string ofxSvgElement::toString( int nlevel ) { @@ -102,25 +125,13 @@ string ofxSvgElement::toString( int nlevel ) { 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 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 )); - rmat = rmat * glm::toMat4((const glm::quat&)rq); +// glm::quat rq = glm::angleAxis(ofDegToRad(rotation), glm::vec3(0.f, 0.f, 1.0f )); +// rmat = rmat * glm::toMat4((const glm::quat&)rq); + rmat = glm::rotate(rmat, glm::radians(rotation), glm::vec3(0.f, 0.f, 1.f)); } if( scale.x != 1.0f || scale.y != 1.0f ) { rmat = glm::scale(rmat, glm::vec3(scale.x, scale.y, 1.0f)); @@ -143,17 +154,122 @@ ofNode ofxSvgElement::getNodeTransform() { return tnode; } -#pragma mark - Image //-------------------------------------------------------------- -ofRectangle ofxSvgImage::getRectangle() { - return ofRectangle(pos.x, pos.y, getWidth(), getHeight()); +void ofxSvgPath::applyStyle(ofxSvgCssClass& aclass) { + if( aclass.hasProperty("fill")) { + if( !aclass.isNone("fill")) { + path.setFillColor(aclass.getColor("fill")); + } else { + path.setFilled(false); + } + } else { + // aSvgPath->path.setFilled(false); + path.setFillColor(ofColor(0)); + } + + if( aclass.hasProperty("fill-opacity")) { + if( aclass.isNone("fill-opacity")) { + path.setFilled(false); + } else { + float val = aclass.getFloatValue("fill-opacity", 1.0f); + if( val <= 0.0001f ) { + path.setFilled(false); + } else { + auto pcolor = path.getFillColor(); + pcolor.a = val; + path.setFillColor(pcolor); + } + } + } + + if( !aclass.isNone("stroke") ) { + path.setStrokeColor(aclass.getColor("stroke")); + } + + if( aclass.hasProperty("stroke-width")) { + if( aclass.isNone("stroke-width")) { + path.setStrokeWidth(0.f); + } else { + 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")) { + path.setStrokeWidth(1.f); + } + } + + if( aclass.hasProperty("opacity")) { + if( path.isFilled() ) { + auto fcolor = path.getFillColor(); + fcolor.a *= aclass.getFloatValue("opacity", 1.f); + path.setFillColor( fcolor ); + } + if( path.hasOutline() ) { + auto scolor = path.getStrokeColor(); + scolor.a *= aclass.getFloatValue("opacity", 1.f); + path.setStrokeColor( scolor ); + } + } + } +//-------------------------------------------------------------- +void ofxSvgPath::draw() { + ofPushMatrix(); { + ofSetColor( ofColor::orange ); +// ofDrawCircle(0, 0, 15); + + ofTranslate(pos.x, pos.y); + + ofSetColor( ofColor::green ); +// ofDrawCircle(0, 0, 10); + + if( rotation != 0.0 ) ofRotateZDeg( rotation ); + ofScale( scale.x, scale.y ); + if(isVisible()) { + ofColor fillColor = getFillColor(); + ofColor strokeColor = getStrokeColor(); + if( alpha != 1.f ) { + if( isFilled() ) { + path.setFillColor(ofColor(fillColor.r, fillColor.g, fillColor.b, alpha * (float)fillColor.a )); + } + if( hasStroke() ) { + path.setStrokeColor(ofColor(strokeColor.r, strokeColor.g, strokeColor.b, alpha * (float)strokeColor.a )); + } + } + path.draw(); + + if( alpha != 1.f ) { + if( isFilled() ) { + path.setFillColor(fillColor); + } + if( hasStroke() ) { + path.setStrokeColor(strokeColor); + } + } + } + } ofPopMatrix(); +} + +#pragma mark - Image +//-------------------------------------------------------------- +//ofRectangle ofxSvgImage::getRectangle() { +// return ofRectangle(pos.x, pos.y, getWidth(), getHeight()); +//} + //-------------------------------------------------------------- void ofxSvgImage::draw() { if( !bTryLoad ) { - img.load( getFilePath() ); - bTryLoad = true; + if( getFilePath().empty() ) { + img.load( getFilePath() ); + bTryLoad = true; + } } if( isVisible() ) { @@ -176,7 +292,326 @@ glm::vec2 ofxSvgImage::getAnchorPointForPercent( float ax, float ay ) { return ap; } + #pragma mark - Text +std::vector ofxSvgText::splitBySpanTags(const std::string& input) { + std::vector result; + std::regex span_regex(R"(<[^>]+>[\s\S]*?]+>)"); // Match tags with content including newlines + std::sregex_iterator begin(input.begin(), input.end(), span_regex); + std::sregex_iterator end; + + size_t last_pos = 0; + for (auto it = begin; it != end; ++it) { + std::smatch match = *it; + size_t match_start = match.position(); + size_t match_end = match_start + match.length(); + + // Add text before match + if (match_start > last_pos) { + result.push_back(input.substr(last_pos, match_start - last_pos)); + } + + // Add the matched tag + result.push_back(match.str()); + + last_pos = match_end; + } + + // Add any remaining text after the last match + if (last_pos < input.length()) { + result.push_back(input.substr(last_pos)); + } + + return result; +} + +//struct SpanData { +// std::string style; +// std::string content; +//}; + +ofxSvgText::SpanData ofxSvgText::extractSpanData(const std::string& spanTag) { + SpanData data; + + // Extract style + std::regex style_regex(R"(style\s*=\s*["']([^"']*)["'])"); + std::smatch style_match; + if (std::regex_search(spanTag, style_match, style_regex)) { + data.style = style_match[1].str(); + } + + // Extract inner content (even across newlines) + // std::regex inner_text_regex(R"(]*>([\s\S]*?))"); + std::regex inner_text_regex(R"(]*>([\s\S]*?)<\/span>)"); + std::smatch content_match; + if (std::regex_search(spanTag, content_match, inner_text_regex)) { + data.content = content_match[1].str(); + } + + return data; +} + +bool ofxSvgText::endsWithLineEnding(const std::string& astr) { + if (astr.size() >= 2 && astr.substr(astr.size() - 2) == "\r\n") { + // Windows line ending + return true; + } else if (!astr.empty() && astr.back() == '\n') { + // Unix line ending + return true; + } + return false; +} + +std::vector ofxSvgText::splitWordsAndLineEndings(const std::string& input) { + std::vector result; + + // Match a single \r or \n or \r\n, or a word + std::regex token_regex(R"(\r\n|[\r\n]|[^\s\r\n]+)"); + std::sregex_iterator begin(input.begin(), input.end(), token_regex); + std::sregex_iterator end; + + for (auto it = begin; it != end; ++it) { + result.push_back(it->str()); + } + + return result; +} + +// build the text spans from a string and not from xml // +//-------------------------------------------------------------- +void ofxSvgText::setText( const std::string& astring, std::string aFontFamily, int aFontSize, float aMaxWidth ) { + meshes.clear(); + textSpans.clear(); + + if( astring.empty() ) { + ofLogWarning("ofxSvgText::setText") << "string argument is empty. "; + return; + } + + auto spanStrings = splitBySpanTags(astring); + // ofLogNotice("ofxSvgText") << "number of strings: " << spanStrings.size(); + float ex = 0.f; + float ey = 0.f; + int spanCounter = 0; + for( auto& spanString : spanStrings ) { + std::vector twords;// = ofSplitString(spanString, " ", true, true); + bool bLastCharIsSpace = false; + + // ofLogNotice("ofxSvgText") << "spanString: |" < tag.\n"; +// std::cout << " Style: [" << data.style << "]\n"; +// std::cout << " Content: [" << data.content << "]\n"; + css.addProperties(data.style); + if (!data.content.empty() && data.content.back() == ' ') { + bLastCharIsSpace = true; + data.content.pop_back(); + } + // twords = ofSplitString(data.content, " " , false, false ); + twords = splitWordsAndLineEndings(data.content); + } else { +// std::cout << "Regular text: [" << spanString << "]\n"; + + if (!spanString.empty() && spanString.back() == ' ') { + bLastCharIsSpace = true; + spanString.pop_back(); + } + + // twords = ofSplitString(spanString, " " , false, false ); + twords = splitWordsAndLineEndings(spanString); + } + // cspan->applyStyle(css); + + auto cspan = std::make_shared(); + cspan->applyStyle(css); + + ofLogVerbose("ofxSvgText" ) << "going to try and load: " << spanString << " bold: " << cspan->isBold() << " italic: " << cspan->isItalic(); + if(!ofxSvgFontBook::loadFont(fdirectory, cspan->getFontFamily(), cspan->getFontSize(), cspan->isBold(), cspan->isItalic() )) { + continue; + } + + auto& font = cspan->getFont(); + + // if( spanString=="\n") { + // ex = 0.f; + // ey += font.getLineHeight(); + // continue; + // } + +// if( spanCounter == 0 ) { +// // shift everything down on the first line so it's within the desired bounds +// ey = font.stringHeight("M"); +// } + + cspan->rect.x = ex; + cspan->rect.y = ey; + + + std::stringstream ss; + std::string tCurrentString = ""; + + // ofRectangle addedBounds(ex, ey,1, 1); + + bool bFirstBreak = true; + + bool bWasAStringLineBreak = false; + + // std::cout << "_____________________________________________________" << std::endl; + // for( std::size_t i = 0; i < twords.size(); i++ ) { + // auto& token = twords[i]; + // if (token == "\n") { + // std::cout << "[\\n]" << std::endl; + // } else if (token == "\r\n") { + // std::cout << "[\\r\\n]" << std::endl; + // } else if (token == "\r") { + // std::cout << "[\\r]" << std::endl; + // } else { + // std::cout << "[" << token << "]" << std::endl; + // } + // } + // std::cout << "_____________________________________________________" << std::endl; + + for( std::size_t i = 0; i < twords.size(); i++ ) { + + bool bFinalWord = (i == twords.size()-1); + + bool bAddedBreak = false; + tCurrentString += twords[i];//+ " "; + if( i < twords.size()-1 ) { //|| (bFinalWord && bLastCharIsSpace )) { + tCurrentString += " "; + } else { + if(bFinalWord && bLastCharIsSpace) { + tCurrentString += "f"; + } + } + + bool bIsAStringLineBreak = false; + if( twords[i] == "\r\n" || twords[i] == "\n" ) { + bIsAStringLineBreak = true; + } + + // ofLogNotice("textSpan word string") << "|"<rect.x > aMaxWidth || bIsAStringLineBreak > 0 ) { + // if( ex > aMaxWidth ) { + + tCurrentString = twords[i]; + if( bLastCharIsSpace ) { + tCurrentString += "f"; + } + // ofLogNotice("ofxSvgText") << "we have a break, taking ex from string: |" << tCurrentString << "|"; + ex = font.getStringBoundingBox(tCurrentString,0,0,true).getRight(); + // if( bLastCharIsSpace ) { + // ex += 60;//font.getCharWidth(' '); + // } + + if(bIsAStringLineBreak) { + ex = 0.f; + tCurrentString = ""; + } + + if( bFirstBreak && spanCounter > 0 ) { + bFirstBreak = false; + // if( spanCounter > 0 ) { + cspan->text = ss.str(); + // cspan->rect.height = font.stringHeight(cspan->text+"M"); + //ey += cspan->rect.height; + ey += font.getLineHeight(); + + if( ss.str().size() > 0 ) { + // ofLogNotice("ofxSvgText") << "LINE Break: adding cspan: " << textSpans.size() << " x: " << cspan->rect.x << " text: |" << cspan->text << "|"; + textSpans.push_back(cspan); + } + + cspan = std::make_shared(); + cspan->applyStyle(css); + ss.str(""); + ss.clear(); + // ofLogNotice("Adding a new line I think") << "ss: " <rect.x = ex; + cspan->rect.y = ey; + bAddedBreak = true; + // } + // } + } else { + //ey += font.stringHeight("M"); + bAddedBreak = true; + if( spanCounter == 0 && i == 0 ) { + + } else { + ey += font.getLineHeight(); + ss << std::endl; + } + } + + bFirstBreak = false; + + // addedBounds.growToInclude(font.getStringBoundingBox(cspan->text+"M", 0, 0)); + // ey += addedBounds.height; + // ey += font.getLineHeight(); + + // if(!bFirstBreak) { + // cspan->rect.x = ex; + // } + } + + if( !bAddedBreak && i > 0 && !bWasAStringLineBreak) { + // if( !bFinalWord ) { + ss << " "; + // } + } + if( !bIsAStringLineBreak ) { + ss << twords[i]; + } + + bWasAStringLineBreak = bIsAStringLineBreak; + } + + // ey += font.stringHeight(ss.str()); + // if( !bFirstBreak ) { + // ey += font.stringHeight(ss.str()+"M"); + // } + + ex += cspan->rect.x; + + if( ss.str().size() > 0 ) { + if( endsWithLineEnding(ss.str())) { + ex = 0.f; + } + // if(!bLastCharIsSpace && bFirstBreak) { + // ss << " "; + //ex += 30.f;//font.getCharWidth(' ') * 4.0f;//font.getStringBoundingBox(" ",0.f, 0.f).getRight(); + // ex += font.getCharWidth('f'); + // } + cspan->text = ss.str(); + // ofLogNotice("ofxSvgText") << "adding cspan: " << textSpans.size() << " x: " << cspan->rect.x << " text: |" << cspan->text << "|"; + cspan->rect.height = font.getStringBoundingBox(cspan->text, 0.f, 0.f ).getHeight();//font.stringHeight(cspan->text); + if( bFirstBreak ) { + cspan->rect.height = font.getLineHeight(); + // ey += font.getLineHeight(); + } else { + // cspan->rect.height += font.getLineHeight(); + } + // ey = cspan->rect.y + cspan->rect.height; + //ey += cspan->rect.height; + textSpans.push_back(cspan); + } + + spanCounter++; + } + + // for( std::size_t i = 0; i < textSpans.size(); i++ ) { + // ofLogNotice("TextSpan") << i << " - " << "|"<text<<"|"; + // } + + create(); +} //-------------------------------------------------------------- void ofxSvgText::create() { @@ -184,39 +619,32 @@ void ofxSvgText::create() { // now lets sort the text based on meshes that we need to create // vector< std::shared_ptr > tspans = textSpans; + + for( auto& tspan : textSpans ) { +// auto tkey = ofxSvgFontBook::getFontKey(tspan->getFontFamily(), tspan->isBold(), tspan->isItalic() ); + if( !ofxSvgFontBook::hasFont(tspan->getFontFamily(), tspan->getFontSize(), tspan->isBold(), tspan->isItalic() )) { + ofLogVerbose("ofxSvgText") << "Trying to load font " << tspan->getFontFamily() << " bold: " << tspan->isBold() << " italic: " << tspan->isItalic() << " | " << ofGetFrameNum(); + // loadFont( const std::string& aDirectory, const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ) + ofxSvgFontBook::loadFont(fdirectory, tspan->getFontFamily(), tspan->getFontSize(), tspan->isBold(), tspan->isItalic() ); + } + } 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 ) { + if( tspanFonts.count( tspans[i]->getFontKey() ) == 0 ) { std::map< int, vector< std::shared_ptr> > tmapap; - tspanFonts[ tspans[i]->fontFamily ] = tmapap; + tspanFonts[ tspans[i]->getFontKey() ] = tmapap; } - std::map< int, vector< std::shared_ptr> >& spanMap = tspanFonts[ tspans[i]->fontFamily ]; - if( spanMap.count(tspans[i]->fontSize) == 0 ) { + std::map< int, vector< std::shared_ptr> >& spanMap = tspanFonts[ tspans[i]->getFontKey() ]; + if( spanMap.count(tspans[i]->getFontSize()) == 0 ) { vector< std::shared_ptr > tvec; - spanMap[ tspans[i]->fontSize ] = tvec; + spanMap[ tspans[i]->getFontSize() ] = 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; + spanMap[ tspans[i]->getFontSize() ].push_back( tspans[i] ); } 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; @@ -225,73 +653,25 @@ void ofxSvgText::create() { meshes[ mainIt->first ] = tempMeshMap; } - Font& tfont = fonts[ mainIt->first ]; + // 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("ofxSvgText") << __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("ofxSvgText") << __FUNCTION__ << " : Trying to load font from: " << tfontPath; + if( !ofxSvgFontBook::hasFontForKey(mainIt->first, vIt->first )) { + ofLogError("ofxSvgText") << __FUNCTION__ << " : Could not find that font size in the map: " << vIt->first; + continue; + } - 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("ofxSvgText") << __FUNCTION__ << " : error loading font family: " << tfont.fontFamily << " size: " << vIt->first; - tfont.sizes.erase(vIt->first); - } - } - if( !bFontLoadOk ) continue; - - if( meshMap.count(vIt->first) == 0 ) { + if( meshMap.count(vIt->first) == 0 ) { meshMap[ vIt->first ] = ofMesh(); } ofMesh& tmesh = meshMap[ vIt->first ]; - if( !tfont.sizes.count( vIt->first ) ) { - ofLogError("ofxSvgText") << __FUNCTION__ << " : Could not find that font size in the map: " << vIt->first; - continue; - } - - ofTrueTypeFont& ttfont = tfont.sizes[ vIt->first ]; + // ofTrueTypeFont& ttfont = tfont.sizes[ vIt->first ]; + auto& ttfont = ofxSvgFontBook::getFontForKey(mainIt->first, vIt->first); for( std::size_t i = 0; i < spanSpans.size(); i++ ) { // create a mesh here // std::shared_ptr& cspan = spanSpans[i]; @@ -331,7 +711,8 @@ void ofxSvgText::create() { 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->lineHeight = tfont.getStringBoundingBox("M", 0, 0).height; + tempSpan->lineHeight = tfont.getLineHeight(); // tempSpan.rect.x = tempSpan.rect.x - ogPos.x; // tempSpan.rect.y = tempSpan.rect.x - ogPos.x; //tempSpan.rect.y -= tempSpan.lineHeight; @@ -349,33 +730,26 @@ void ofxSvgText::draw() { 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 ); + if( rotation != 0 ) {ofRotateZDeg( rotation );} ofTexture* tex = NULL; for( mainIt = meshes.begin(); mainIt != meshes.end(); ++mainIt ) { - string fontFam = mainIt->first; + string fontKey = mainIt->first; std::map< int, ofMesh >::iterator mIt; - for( mIt = meshes[ fontFam ].begin(); mIt != meshes[ fontFam ].end(); ++mIt ) { + for( mIt = meshes[ fontKey ].begin(); mIt != meshes[ fontKey ].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 ) ) { + // if( fonts.count( fontKey ) ) { + if( ofxSvgFontBook::hasBookFont(fontKey)) { + auto& fbook = ofxSvgFontBook::getBookFont(fontKey); + if( fbook.textures.count( fontSize ) ) { bHasTexture = true; - tex = &fonts[ fontFam ].textures[ fontSize ]; + tex = &fbook.textures[ fontSize ]; } } @@ -429,52 +803,49 @@ void ofxSvgText::draw(const std::string& astring, const ofColor& acolor, bool ab } //-------------------------------------------------------------- -bool ofxSvgText::_recursiveFontDirSearch(const string& afile, const string& aFontFamToLookFor, string& fontpath) { - if (fontpath != "") { - return true; +void ofxSvgText::TextSpan::applyStyle(ofxSvgCssClass& aclass) { + mSvgCssClass = aclass; + +// fontFamily = aclass.getValue("font-family", "Arial"); +// fontFamily = mSvgCssClass.getFontFamily("Arial"); +// fontSize = aclass.getIntValue("font-size", 18 ); +// fontSize = mSvgCssClass.getFontSize(18); + color = ofColor(0); + if( aclass.hasProperty("color")) { + color = aclass.getColor("color"); + } else if( aclass.hasProperty("fill")) { + color = aclass.getColor("fill"); } - ofFile tfFile( afile, ofFile::Reference ); - if (tfFile.isDirectory()) { - ofLogVerbose("ofxSvgText") << __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("ofxSvgText") << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; - fontpath = tfFile.getAbsolutePath(); - return true; - } - string tAltFileName = ofToLower(tfFile.getBaseName()); - ofStringReplace(tAltFileName, " ", "-"); - if (tAltFileName == ofToLower(aFontFamToLookFor)) { - ofLogNotice("ofxSvgText") << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; - fontpath = tfFile.getAbsolutePath(); - return true; - } - } + + if( aclass.hasProperty("opacity")) { + color.a *= aclass.getFloatValue("opacity", 1.f); } - return false; + + +// bold = false; +// if( ofIsStringInString(aclass.getValue("font-weight", "" ), "bold")) { +// bold = true; +// } + +// bold = mSvgCssClass.isFontBold(); + +// italic = false; +// if( ofIsStringInString(aclass.getValue("font-style", "" ), "italic")) { +// italic = true; +// } + +// italic = mSvgCssClass.isFontItalic(); + + + ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << "text: " << text; + ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << " css class: " << aclass.toString() << std::endl << " color: " << color; + } // must return a reference for some reason here // //-------------------------------------------------------------- ofTrueTypeFont& ofxSvgText::TextSpan::getFont() { - if( ofxSvgText::fonts.count( fontFamily ) > 0 ) { - Font& tfont = fonts[ fontFamily ]; - if( tfont.sizes.count(fontSize) > 0 ) { - return tfont.sizes[ fontSize ]; - } - } - return defaultFont; + return ofxSvgFontBook::getFontForKey( getFontKey(), getFontSize() ); } //-------------------------------------------------------------- @@ -499,7 +870,7 @@ ofTrueTypeFont& ofxSvgText::getFont() { return textSpans[0]->getFont(); } ofLogWarning("ofxSvgText") << __FUNCTION__ << " : no font detected from text spans, returning default font."; - return defaultFont; + return ofxSvgFontBook::defaultFont; } //-------------------------------------------------------------- @@ -514,7 +885,7 @@ ofColor ofxSvgText::getColor() { // get the bounding rect for all of the text spans in this svg'ness // should be called after create // //-------------------------------------------------------------- -ofRectangle ofxSvgText::getRectangle() { +ofRectangle ofxSvgText::getBoundingBox() { ofRectangle temp( 0, 0, 1, 1 ); for( std::size_t i = 0; i < textSpans.size(); i++ ) { ofRectangle trect = textSpans[i]->rect; @@ -528,7 +899,6 @@ ofRectangle ofxSvgText::getRectangle() { } } - temp.x += pos.x; temp.y += pos.y; return temp; diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index a4ccd301ba5..7c3b6c69e51 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -1,49 +1,56 @@ -// -// 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" +#include "ofxSvgCss.h" +#include "ofxSvgFontBook.h" enum ofxSvgType { - TYPE_ELEMENT = 0, - TYPE_GROUP, - TYPE_RECTANGLE, - TYPE_IMAGE, - TYPE_ELLIPSE, - TYPE_CIRCLE, - TYPE_PATH, - TYPE_TEXT, - TYPE_DOCUMENT, - TYPE_TOTAL + OFXSVG_TYPE_ELEMENT = 0, + OFXSVG_TYPE_GROUP, + OFXSVG_TYPE_RECTANGLE, + OFXSVG_TYPE_IMAGE, + OFXSVG_TYPE_ELLIPSE, + OFXSVG_TYPE_CIRCLE, + OFXSVG_TYPE_PATH, + OFXSVG_TYPE_TEXT, + OFXSVG_TYPE_DOCUMENT, + OFXSVG_TYPE_TOTAL }; class ofxSvgElement { + friend class ofxSvg; + friend class ofxSvgGroup; public: static std::string sGetTypeAsString(ofxSvgType atype); static std::string sGetSvgXmlName(ofxSvgType atype); - virtual ofxSvgType getType() {return ofxSvgType::TYPE_ELEMENT;} + virtual ofxSvgType getType() {return OFXSVG_TYPE_ELEMENT;} std::string getTypeAsString(); std::string getName() { return name; } + std::string getCleanName(); + bool isGroup() { - return (getType() == ofxSvgType::TYPE_GROUP); + return (getType() == OFXSVG_TYPE_GROUP); } - void setVisible( bool ab ) { bVisible = ab; } + // override this in ofxSvgGroup + virtual void setVisible( bool ab ) { bVisible = ab; } bool isVisible() { return bVisible; } + virtual void setAlpha( float aAlpha ) { + alpha = aAlpha; + } + float getAlpha() { + return alpha; + } + virtual std::string toString(int nlevel = 0); - std::string name = ""; float layer = -1.f; bool bVisible=true; bool bUseShapeColor = true; @@ -57,39 +64,60 @@ class ofxSvgElement { virtual void draw() {} + virtual void applyStyle(ofxSvgCssClass& aclass) {}; + virtual void setUseShapeColor( bool ab ) { bUseShapeColor = ab; } + /// \brief Get the bounding box of the element , taking into account + /// all the points and rotation to determine the extents of the element aligned with x and y axes. + virtual ofRectangle getBoundingBox() { return ofRectangle( pos.x, pos.y, 1, 1 ); }; + virtual ofPolyline getFirstPolyline() { ofLogWarning("ofxSvgElement") << __FUNCTION__ << " : Element " << getTypeAsString() << " does not have a path."; return ofPolyline(); } -//protected: +protected: + float alpha = 1.f; + std::string name = ""; // 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); + virtual std::shared_ptr clone() { + return std::make_shared(*this); + }; }; class ofxSvgPath : public ofxSvgElement { public: - virtual ofxSvgType getType() override {return ofxSvgType::TYPE_PATH;} + virtual ofxSvgType getType() override {return OFXSVG_TYPE_PATH;} virtual void setUseShapeColor( bool ab ) override { ofxSvgElement::setUseShapeColor(ab); path.setUseShapeColor(ab); } - virtual void draw() override { - if(isVisible()) path.draw(); - } + virtual void applyStyle(ofxSvgCssClass& aclass) override; + + virtual void draw() override; bool isFilled() { return path.isFilled(); } + ofColor getFillColor() { return path.getFillColor(); } + void setFillColor( const ofColor& acolor ) { + path.setFillColor(acolor); + } + bool hasStroke() { return path.hasOutline(); } float getStrokeWidth() { return path.getStrokeWidth(); } - ofColor getFillColor() { return path.getFillColor(); } + void setStrokeWidth( const float& awidth ) { + path.setStrokeWidth(awidth); + } ofColor getStrokeColor() { return path.getStrokeColor(); } + void setStrokeColor( const ofColor& acolor ) { + path.setStrokeColor( acolor ); + } ofPolyline getFirstPolyline() override { if( path.getOutline().size() > 0 ) { @@ -99,12 +127,35 @@ class ofxSvgPath : public ofxSvgElement { return ofPolyline(); } + /// \brief Get the bounding box of the path, taking into account + /// all the points; the points are global, not relative to pos and don't have rotation. + virtual ofRectangle getBoundingBox() override { + ofRectangle brect; + if( path.getOutline().size() > 0 ) { + bool bFirst = true; + const auto& outlines = path.getOutline(); + for( auto& outline : outlines ) { + if( bFirst ) { + brect = outline.getBoundingBox(); + } else { + brect.growToInclude( outline.getBoundingBox() ); + } + } + } + return brect; + }; + ofPath path; + +protected: + virtual std::shared_ptr clone() override { + return std::make_shared(*this); + }; }; class ofxSvgRectangle : public ofxSvgPath { public: - virtual ofxSvgType getType() override {return ofxSvgType::TYPE_RECTANGLE;} + virtual ofxSvgType getType() override {return OFXSVG_TYPE_RECTANGLE;} ofRectangle rectangle; float getWidth() { return rectangle.getWidth() * scale.x;} @@ -112,20 +163,23 @@ class ofxSvgRectangle : public ofxSvgPath { float round = 0.f; -// float getWidthScaled() { return rectangle.getWidth() * scale.x;} -// float getHeightScaled() { return rectangle.getHeight() * scale.y;} +protected: + virtual std::shared_ptr clone() override { + return std::make_shared(*this); + }; }; class ofxSvgImage : public ofxSvgElement { public: - virtual ofxSvgType getType() override {return ofxSvgType::TYPE_IMAGE;} + virtual ofxSvgType getType() override {return OFXSVG_TYPE_IMAGE;} - float getWidth() { return width * scale.x;} - float getHeight() { return height * scale.y;} + float getWidth() const { return width * scale.x;} + float getHeight() const { return height * scale.y;} - ofRectangle getRectangle(); -// float getWidthScaled() { return getWidth() * scale.x;} -// float getHeightScaled() { return getHeight() * scale.y;} + /// \brief Get the bounding box of the image. + virtual ofRectangle getBoundingBox() override { + return ofRectangle(pos.x, pos.y, getWidth(), getHeight()); + }; virtual void draw() override; glm::vec2 getAnchorPointForPercent( float ax, float ay ); @@ -136,75 +190,181 @@ class ofxSvgImage : public ofxSvgElement { color = aColor; } ofColor getColor() { - return color; + return ofColor( color.r, color.g, color.b, alpha * (float)color.a ); } ofColor color; ofImage img; - bool bTryLoad = false; + of::filesystem::path filepath; float width = 0.f; float height = 0.f; + +protected: + bool bTryLoad = false; + + virtual std::shared_ptr clone() override { + return std::make_shared(*this); + }; + }; class ofxSvgCircle : public ofxSvgPath { public: - virtual ofxSvgType getType() override {return ofxSvgType::TYPE_CIRCLE;} + virtual ofxSvgType getType() override {return OFXSVG_TYPE_CIRCLE;} float getRadius() {return radius;} float radius = 10.0; + +protected: + virtual std::shared_ptr clone() override { + return std::make_shared(*this); + }; }; class ofxSvgEllipse : public ofxSvgPath { public: - virtual ofxSvgType getType() override {return ofxSvgType::TYPE_ELLIPSE;} + virtual ofxSvgType getType() override {return OFXSVG_TYPE_ELLIPSE;} float radiusX, radiusY = 10.0f; + +protected: + virtual std::shared_ptr clone() override { + return std::make_shared(*this); + }; }; class ofxSvgText : public ofxSvgRectangle { 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; +// fontSize = 12; lineHeight = 0; +// mSvgCssClass.setFontSize(fontSize); +// fontFamily = "Arial"; +// mSvgCssClass.setFontFamily(fontFamily); + } + + std::string getFontKey() { + return ofxSvgFontBook::getFontKey(getFontFamily(), isBold(), isItalic()); + } + + std::string getFontFamily() { + return mSvgCssClass.getFontFamily("Arial"); + } + void setFontFamily(std::string aFontFamily) { + mSvgCssClass.setFontFamily(aFontFamily); + } + + int getFontSize() { + return mSvgCssClass.getFontSize(12); + } + void setFontSize( int asize ) { + mSvgCssClass.setFontSize(asize); + } + + bool isBold() { + return mSvgCssClass.isFontBold(); + } + void setBold( bool ab ) { + if( ab ) { + mSvgCssClass.addProperty("font-weight", "bold"); + } else { + mSvgCssClass.addProperty("font-weight", "regular"); + } + } + + bool isItalic() { + return mSvgCssClass.isFontItalic(); + } + void setItalic( bool ab ) { + if( ab ) { + mSvgCssClass.addProperty("font-style", "italic"); + } else { + mSvgCssClass.addProperty("font-style", "regular"); + } } std::string text; - int fontSize = 12; - std::string fontFamily; +// int fontSize = 12; +// std::string fontFamily; ofRectangle rect; ofColor color; - float lineHeight = 12; + float lineHeight = 0; + + void applyStyle(ofxSvgCssClass& aclass); + ofxSvgCssClass& getCss() { return mSvgCssClass; } + ofTrueTypeFont& getFont(); void draw( const std::string& astring, bool abCentered ); void draw(const std::string &astring, const ofColor& acolor, bool abCentered ); + + protected: + ofxSvgCssClass mSvgCssClass; }; - static bool sortSpanOnFontFamily( const TextSpan& a, const TextSpan& b ) { - return a.fontFamily < b.fontFamily; + + ofxSvgText() = default; + + // Deep-copy constructor + ofxSvgText(const ofxSvgText& other) { + pos = other.pos; + scale = other.scale; + rotation = other.rotation; + + layer = other.layer; + bVisible = other.bVisible; + + fdirectory = other.fdirectory; + bCentered = other.bCentered; + _overrideColor = other._overrideColor; + bOverrideColor = other.bOverrideColor; + + textSpans.reserve(other.textSpans.size()); + for (const auto& ptr : other.textSpans) { + // Create a new shared_ptr to a new Item copy or nullptr + textSpans.push_back(ptr ? std::make_shared(*ptr) : nullptr); + } + + create(); } - static bool sortSpanOnFontSize( const TextSpan& a, const TextSpan& b ) { - return a.fontSize < b.fontSize; + // Deep-copy assignment + ofxSvgText& operator=(const ofxSvgText& other) { + if (this != &other) { + pos = other.pos; + scale = other.scale; + rotation = other.rotation; + + layer = other.layer; + bVisible = other.bVisible; + + fdirectory = other.fdirectory; + bCentered = other.bCentered; + _overrideColor = other._overrideColor; + bOverrideColor = other.bOverrideColor; + + textSpans.clear(); + textSpans.reserve(other.textSpans.size()); + for (const auto& ptr : other.textSpans) { + // Create a new shared_ptr to a new Item copy or nullptr + textSpans.push_back(ptr ? std::make_shared(*ptr) : nullptr); + } + create(); + } + return *this; } -// Text() { type = OFX_SVG_TYPE_TEXT; fdirectory=""; bCentered=false; alpha=1.0; bOverrideColor=false; } - virtual ofxSvgType getType() override {return ofxSvgType::TYPE_TEXT;} + + + virtual ofxSvgType getType() override {return OFXSVG_TYPE_TEXT;} ofTrueTypeFont& getFont(); ofColor getColor(); - + + void setText( const std::string& astring, std::string aFontFamily, int aFontSize, float aMaxWidth ); void create(); void draw() override; void draw(const std::string &astring, bool abCentered ); @@ -214,24 +374,56 @@ class ofxSvgText : public ofxSvgRectangle { fdirectory = aPath; } + std::string getText() { + std::string ttext; + for( auto& tspan : textSpans ) { + ttext += tspan->text; + } + return ttext; + } + + void layoutTextSpans(); + void overrideColor( ofColor aColor ) { bOverrideColor = true; _overrideColor = aColor; } - ofRectangle getRectangle(); +/// \brief Get the bounding box of all of the text spans. + virtual ofRectangle getBoundingBox() override; 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); + + virtual std::shared_ptr clone() override { + auto newEle = std::make_shared(*this); + newEle->textSpans.clear(); + newEle->textSpans.reserve(textSpans.size()); + for (const auto& ptr : textSpans) { + // Create a new shared_ptr to a new Item copy or nullptr + // lets not copy over the nullptrs + if( ptr ) { + newEle->textSpans.push_back(std::make_shared(*ptr)); + } + } + return newEle; + }; + + struct SpanData { + std::string style; + std::string content; + }; + + std::vectorsplitBySpanTags(const std::string& input); + SpanData extractSpanData(const std::string& spanTag); + bool endsWithLineEnding(const std::string& astr); + std::vector splitWordsAndLineEndings(const std::string& input); + ofFloatColor _overrideColor; bool bOverrideColor = false; }; diff --git a/addons/ofxSvg/src/ofxSvgFontBook.cpp b/addons/ofxSvg/src/ofxSvgFontBook.cpp new file mode 100644 index 00000000000..760066fef19 --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgFontBook.cpp @@ -0,0 +1,291 @@ +#include "ofxSvgFontBook.h" + +using std::string; + +std::string ofxSvgFontBook::mFontDirectory; +ofTrueTypeFont ofxSvgFontBook::defaultFont; +std::map< string, ofxSvgFontBook::Font > ofxSvgFontBook::fonts; + +ofxSvgFontBook::Font ofxSvgFontBook::defaultBookFont; + +//-------------------------------------------------------------- +bool ofxSvgFontBook::loadFont(const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ) { + return loadFont(mFontDirectory, aFontFamily, aFontSize, aBBold, aBItalic ); +} + +//-------------------------------------------------------------- +bool ofxSvgFontBook::loadFont( const std::string& aDirectory, const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ) { + ofLogVerbose("ofxFontBook") << "checking font: " << aFontFamily << " bold: " << aBBold << " italic: " << aBItalic << " fkey: "; + auto fkey = ofxSvgFontBook::getFontKey(aFontFamily, aBBold, aBItalic ); + + ofLogVerbose("ofxFontBook") << "checking font: " << aFontFamily << " bold: " << aBBold << " italic: " << aBItalic; + + if( fonts.count(fkey) == 0 ) { + Font tafont; + tafont.fontFamily = aFontFamily; + tafont.bold = aBBold; + tafont.italic = aBItalic; + fonts[fkey] = tafont; + } + bool bFontLoadOk = true; + + Font& tfont = fonts[ fkey ]; + if (tfont.sizes.count(aFontSize) == 0) { + bool bHasFontDirectory = false; + // cout << "checking directory: " << fdirectory+"/fonts/" << endl; + std::string fontsDirectory = "";// = ofToDataPath("", true); + if( aDirectory != "" ) { + fontsDirectory = aDirectory; + } + + if( !ofFile::doesFileExist(fontsDirectory)) { + if( ofFile::doesFileExist(mFontDirectory)) { + fontsDirectory = mFontDirectory; + } + } + +// if( !ofFile::doesFileExist(fontsDirectory)) { +//// fs::path fontPath = fs::path(getenv("HOME")) / "Library" / "Fonts"; +// #if defined(TARGET_OSX) +// std::filesystem::path fontPath = std::filesystem::path(getenv("HOME")) / "Library" / "Fonts"; +// if( ofDirectory::doesDirectoryExist(fontPath) ) { +// fontsDirectory = ofToDataPath(fontPath, true); +// } +// #endif +// } + if( !ofFile::doesFileExist( fontsDirectory )) { + fontsDirectory = ofToDataPath("", true); + } + + if( ofFile::doesFileExist( fontsDirectory )) { + bHasFontDirectory = true; + } + + std::vector fontNamesToSearch = {aFontFamily}; + // sometimes there are fallback fonts included with a comma separator + if( ofIsStringInString(aFontFamily, ",")) { + std::vector splitNames = ofSplitString(aFontFamily, ",", true, true); + for( auto& sname : splitNames ) { + // remove spaces + ofStringReplace(sname, " ", "" ); + } +// fontNamesToSearch.insert(fontNamesToSearch.end(), splitNames.begin(), splitNames.end()); + fontNamesToSearch = splitNames; + } + + + +// 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) { + std::stringstream fs; + bool bf = true; + for( auto& fname : fontNamesToSearch ) { + if( !bf ) { + fs << ", "; + } + fs << fname; + bf = false; + } + + ofLogNotice("ofxSvgFontBook") << __FUNCTION__ << " : " << fs.str() << " : starting off searching directory : " << fontsDirectory; + string tNewFontPath = ""; + + std::vector subStrs; + std::vector excludeStrs; + if( aBBold ) { + subStrs.push_back("bold"); + } else { + excludeStrs.push_back("bold"); + } + if( aBItalic ) { + subStrs.push_back("italic"); + } else { + excludeStrs.push_back("italic"); + } + + bool bMightHaveFoundTheFont = false; + ofLogVerbose("ofxSvgFontBook") << "trying to load font: " << tfont.fontFamily << " bold: " << aBBold << " italic: " << aBItalic; +// bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, tfont.fontFamily, tNewFontPath, subStrs, excludeStrs, 0); + for( auto& fontFam : fontNamesToSearch ) { + bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, fontFam, tNewFontPath, subStrs, excludeStrs, 0); + if (bFoundTheFont) { + tfontPath = tNewFontPath; + bMightHaveFoundTheFont = true; + ofLogVerbose("ofxSvgFontBook") << "Found the font at " << tfontPath; + break; + } + } + + if( !bMightHaveFoundTheFont ) { + // search the system level // + #if defined(TARGET_OSX) + std::filesystem::path fontPath = std::filesystem::path(getenv("HOME")) / "Library" / "Fonts"; + if( ofDirectory::doesDirectoryExist(fontPath) ) { + fontsDirectory = ofToDataPath(fontPath, true); + + for( auto& fontFam : fontNamesToSearch ) { + bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, fontFam, tNewFontPath, subStrs, excludeStrs, 0); + if (bFoundTheFont) { + tfontPath = tNewFontPath; + bMightHaveFoundTheFont = true; + ofLogVerbose("ofxSvgFontBook") << "Found the font at " << tfontPath; + break; + } + } + + } + #endif + } + + if( !bMightHaveFoundTheFont ) { + // search the system level // + #if defined(TARGET_OSX) + std::filesystem::path fontPath = "/Library/Fonts"; + if( ofDirectory::doesDirectoryExist(fontPath) ) { + fontsDirectory = ofToDataPath(fontPath, true); + + for( auto& fontFam : fontNamesToSearch ) { + bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, fontFam, tNewFontPath, subStrs, excludeStrs, 0); + if (bFoundTheFont) { + tfontPath = tNewFontPath; + bMightHaveFoundTheFont = true; + ofLogVerbose("ofxSvgFontBook") << "Found the font at " << tfontPath; + break; + } + } + + } + #endif + } + + /*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("ofxSvgFontBook") << __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[aFontSize].load(tfontPath, aFontSize, true, true, false, 0.5, 72); + if( bFontLoadOk && tfont.pathToFont.empty() ) { + tfont.pathToFont = tfontPath; + } + } + if(bFontLoadOk) { +// tfont.sizes[ vIt->first ].setSpaceSize( 0.57 ); +// tfont.sizes[ vIt->first ] = datFontTho; + tfont.textures[ aFontSize ] = tfont.sizes[ aFontSize ].getFontTexture(); + } else { + ofLogError("ofxSvgFontBook") << __FUNCTION__ << " : error loading font family: " << tfont.fontFamily << " size: " << aFontSize; + tfont.sizes.erase(aFontSize); + } + } + return bFontLoadOk; +} + +//-------------------------------------------------------------- +bool ofxSvgFontBook::_recursiveFontDirSearch( + const string& afile, const string& aFontFamToLookFor, string& fontpath, + const std::vector& aAddNames, + const std::vector& aExcludeNames, + int aNumRecursions) { + if (fontpath != "") { + return true; + } + int numRecursions = aNumRecursions+1; + if( numRecursions > 20 ) { + ofLogVerbose("ofxSvgFontBook") << __FUNCTION__ << " too many recursions, aborting."; + return false; + } + ofFile tfFile( afile, ofFile::Reference ); + if (tfFile.isDirectory()) { + ofLogVerbose("ofxSvgFontBook") << __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, aAddNames, aExcludeNames, numRecursions); + if( youGoodOrWhat ) { + return true; + } + } + tdir.close(); + } else { + if ( tfFile.getExtension() == "ttf" || tfFile.getExtension() == "otf") { + auto tfbase = ofToLower(tfFile.getBaseName()); + auto fontFamLower = ofToLower(aFontFamToLookFor); + + if( aAddNames.size() > 0 || aExcludeNames.size() > 0 ) { + + if(ofIsStringInString(tfbase, fontFamLower)) { + bool bAllFound = true; + for( auto& subName : aAddNames ) { + if( !ofIsStringInString(tfbase, ofToLower(subName))) { +// ofLogNotice("ofxSvgFontBook") << __FUNCTION__ << " add name not found: " << subName << " font: " << fontFamLower; + bAllFound = false; + break; + } + } + if(aExcludeNames.size() > 0 ) { + for( auto& subName : aExcludeNames ) { + if( ofIsStringInString(tfbase, ofToLower(subName))) { +// ofLogNotice("ofxSvgFontBook") << __FUNCTION__ << " exclude name not found: " << subName << " font: " << fontFamLower; + bAllFound = false; + break; + } + } + } + +// ofLogNotice("ofxSvgFontBook") << __FUNCTION__ << " checking font fam: " << fontFamLower << " file: " << tfbase << " allfound: " << bAllFound; + + if(bAllFound) { + fontpath = tfFile.getAbsolutePath(); + return true; + } + } + } else { + if (ofToLower( tfFile.getBaseName() ) == fontFamLower) { + ofLogVerbose("ofxSvgFontBook") << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; + fontpath = tfFile.getAbsolutePath(); + return true; + } + string tAltFileName = ofToLower(tfFile.getBaseName()); + ofStringReplace(tAltFileName, " ", "-"); + if (tAltFileName == fontFamLower) { + ofLogVerbose("ofxSvgFontBook") << __FUNCTION__ << " : found font file for " << aFontFamToLookFor; + fontpath = tfFile.getAbsolutePath(); + return true; + } + } + } + } + return false; +} + +//-------------------------------------------------------------- +ofTrueTypeFont& ofxSvgFontBook::getFontForKey( const std::string& aFontKey, int aFontSize ) { + if( fonts.count(aFontKey) > 0 ) { + if( fonts[aFontKey].sizes.count(aFontSize) > 0 ) { + return fonts[aFontKey].sizes[aFontSize]; + } + } + return defaultFont; +} diff --git a/addons/ofxSvg/src/ofxSvgFontBook.h b/addons/ofxSvg/src/ofxSvgFontBook.h new file mode 100644 index 00000000000..d6118826f64 --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgFontBook.h @@ -0,0 +1,72 @@ +#pragma once +#include "ofTrueTypeFont.h" +#include + +/// @brief This class is mostly for internal use. +/// Used by the ofxSvgText elements for managing fonts +class ofxSvgFontBook { +public: + class Font { + public: + std::string fontFamily; + std::map< int, ofTrueTypeFont > sizes; + std::map< int, ofTexture > textures; + bool bold = false; + bool italic = false; + of::filesystem::path pathToFont; + }; + + static void setFontDirectory( std::string adir ) { + mFontDirectory = adir; + } + + static bool loadFont(const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ); + static bool loadFont( const std::string& aDirectory, const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ); + + static std::string getFontKey( const std::string& aFontFamily, bool aBBold, bool aBItalic ) { + auto fkey = aFontFamily; + if( aBBold ) {fkey += "_bold";} + if( aBItalic) {fkey += "_italic";} + return fkey; + } + + static bool hasFont(const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ) { + auto fkey = getFontKey(aFontFamily, aBBold, aBItalic); + return hasFontForKey(fkey, aFontSize); + } + + static bool hasFontForKey(const std::string& aFontKey, int aFontSize ) { + if( fonts.count(aFontKey) > 0 ) { + auto& font = fonts[aFontKey]; + if( font.sizes.count(aFontSize) > 0 ) { + return true; + } + } + return false; + } + + static bool hasBookFont( const std::string& aFontKey ) { + return ( fonts.count(aFontKey) > 0 ); + } + + static Font& getBookFont( const std::string& aFontKey ) { + if( fonts.count(aFontKey) > 0 ) { + return fonts[aFontKey]; + } + return defaultBookFont; + } + + static ofTrueTypeFont& getFontForKey( const std::string& aFontKey, int aFontSize ); + + static ofTrueTypeFont defaultFont; + +protected: + static Font defaultBookFont; + static std::string mFontDirectory; + static std::map< std::string, Font > fonts; + static bool _recursiveFontDirSearch(const std::string& afile, const std::string& aFontFamToLookFor, + std::string& fontpath, + const std::vector& aAddNames, + const std::vector& aExcludeNames, + int aNumRecursions); +}; diff --git a/addons/ofxSvg/src/ofxSvgGroup.cpp b/addons/ofxSvg/src/ofxSvgGroup.cpp index e28138b1c0f..5678338ce7a 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.cpp +++ b/addons/ofxSvg/src/ofxSvgGroup.cpp @@ -1,9 +1,3 @@ -// -// ofxSvgGroup.cpp -// -// Created by Nick Hardeman on 7/31/15. -// - #include "ofxSvgGroup.h" #include "ofGraphics.h" @@ -13,11 +7,14 @@ using std::string; //-------------------------------------------------------------- void ofxSvgGroup::draw() { + if( !isVisible() ) return; std::size_t numElements = mChildren.size(); - bool bTrans = pos.x != 0 || pos.y != 0.0; + bool bTrans = (pos.x != 0 || pos.y != 0.0 || rotation != 0.f || scale.x != 0.f || scale.y != 0.f); if( bTrans ) { ofPushMatrix(); ofTranslate(pos.x, pos.y); + if( rotation != 0.0 ) ofRotateZDeg( rotation ); + ofScale( scale.x, scale.y ); } for( std::size_t i = 0; i < numElements; i++ ) { mChildren[i]->draw(); @@ -38,7 +35,7 @@ vector< shared_ptr >& ofxSvgGroup::getChildren() { } //-------------------------------------------------------------- -vector< shared_ptr > ofxSvgGroup::getAllChildren(bool aBIncludeGroups) { +vector< shared_ptr > ofxSvgGroup::getAllElements(bool aBIncludeGroups) { vector< shared_ptr > retElements; for( auto ele : mChildren ) { @@ -49,7 +46,7 @@ vector< shared_ptr > ofxSvgGroup::getAllChildren(bool aBIncludeGr } //-------------------------------------------------------------- -std::vector< std::shared_ptr > ofxSvgGroup::getAllChildGroups() { +std::vector< std::shared_ptr > ofxSvgGroup::getAllGroups() { vector< shared_ptr > retGroups; for( auto ele : mChildren ) { _getAllGroupsRecursive( retGroups, ele ); @@ -62,7 +59,7 @@ std::vector< std::shared_ptr > ofxSvgGroup::getAllChildGroups() { 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); + auto tgroup = std::dynamic_pointer_cast(aele); if(aBIncludeGroups) {aElesToReturn.push_back(tgroup);} for( auto ele : tgroup->getChildren() ) { _getAllElementsRecursive( aElesToReturn, ele, aBIncludeGroups ); @@ -77,7 +74,7 @@ void ofxSvgGroup::_getAllElementsRecursive( vector< shared_ptr >& 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); + auto tgroup = std::dynamic_pointer_cast(aele); aGroupsToReturn.push_back(tgroup); for( auto ele : tgroup->getChildren() ) { if( ele->isGroup() ) { @@ -90,16 +87,16 @@ void ofxSvgGroup::_getAllGroupsRecursive( std::vector< std::shared_ptr > ofxSvgGroup::getAllElementsWithPath() { - auto allKids = getAllChildren(false); + auto allKids = getAllElements(false); std::vector< std::shared_ptr > rpaths; for( auto kid : allKids ) { - if( kid->getType() == ofxSvgType::TYPE_RECTANGLE ) { + if( kid->getType() == OFXSVG_TYPE_RECTANGLE ) { rpaths.push_back(std::dynamic_pointer_cast(kid)); - } else if( kid->getType() == ofxSvgType::TYPE_PATH ) { + } else if( kid->getType() == OFXSVG_TYPE_PATH ) { rpaths.push_back(std::dynamic_pointer_cast(kid)); - } else if( kid->getType() == ofxSvgType::TYPE_CIRCLE ) { + } else if( kid->getType() == OFXSVG_TYPE_CIRCLE ) { rpaths.push_back(std::dynamic_pointer_cast(kid)); - } else if( kid->getType() == ofxSvgType::TYPE_ELLIPSE ) { + } else if( kid->getType() == OFXSVG_TYPE_ELLIPSE ) { rpaths.push_back(std::dynamic_pointer_cast(kid)); } } @@ -108,7 +105,7 @@ std::vector< std::shared_ptr > ofxSvgGroup::getAllElementsWithPath() } //-------------------------------------------------------------- -shared_ptr ofxSvgGroup::getElementForName( std::string aPath, bool bStrict ) { +shared_ptr ofxSvgGroup::getElement( std::string aPath, bool bStrict ) { vector< std::string > tsearches; if( ofIsStringInString( aPath, ":" ) ) { @@ -122,16 +119,34 @@ shared_ptr ofxSvgGroup::getElementForName( std::string aPath, boo return temp; } +//-------------------------------------------------------------- +std::vector< std::shared_ptr > ofxSvgGroup::getAllElementsForName( const std::string& aname, bool bStrict ) { + std::vector< std::shared_ptr > relements; + auto allElements = getAllElements(true); + for( auto& ele : allElements ) { + if( bStrict ) { + if( ele->getCleanName() == aname ) { + relements.push_back(ele); + } + } else { + if( ofIsStringInString( ele->getCleanName(), aname )) { + relements.push_back(ele); + } + } + } + return 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 ) { + if( kid->getCleanName() == aname ) { relements.push_back(kid); } } else { - if( ofIsStringInString( kid->getName(), aname )) { + if( ofIsStringInString( kid->getCleanName(), aname )) { relements.push_back(kid); } } @@ -152,8 +167,10 @@ void ofxSvgGroup::_getElementForNameRecursive( vector& aNamesToFind, sha bool bKeepGoing = false; std::string nameToFind = aNamesToFind[0]; if( aNamesToFind.size() > 1 ) { - bKeepGoing = (aNamesToFind[0] == "*"); - nameToFind = aNamesToFind[1]; + if( aNamesToFind[0] == "*" ) { + bKeepGoing = true; + nameToFind = aNamesToFind[1]; + } } for( std::size_t i = 0; i < aElements.size(); i++ ) { bool bFound = false; @@ -167,7 +184,7 @@ void ofxSvgGroup::_getElementForNameRecursive( vector& aNamesToFind, sha bFound = true; } - if (!bFound && aElements[i]->getType() == ofxSvgType::TYPE_TEXT) { + if (!bFound && aElements[i]->getType() == OFXSVG_TYPE_TEXT) { if (aElements[i]->getName() == "No Name") { // the ids for text block in illustrator are weird, @@ -193,7 +210,7 @@ void ofxSvgGroup::_getElementForNameRecursive( vector& aNamesToFind, sha aTarget = aElements[i]; break; } else { - if( aElements[i]->getType() == ofxSvgType::TYPE_GROUP ) { + if( aElements[i]->getType() == OFXSVG_TYPE_GROUP ) { auto tgroup = std::dynamic_pointer_cast( aElements[i] ); _getElementForNameRecursive( aNamesToFind, aTarget, tgroup->getChildren(), bStrict ); break; @@ -207,7 +224,7 @@ void ofxSvgGroup::_getElementForNameRecursive( vector& aNamesToFind, sha aTarget = aElements[i]; break; } else { - if( aElements[i]->getType() == ofxSvgType::TYPE_GROUP ) { + if( aElements[i]->getType() == OFXSVG_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 ); @@ -236,7 +253,7 @@ void ofxSvgGroup::_replaceElementRecursive( shared_ptr aTarget, s break; } if( !bFound ) { - if( aElements[i]->getType() == ofxSvgType::TYPE_GROUP ) { + if( aElements[i]->getType() == OFXSVG_TYPE_GROUP ) { auto tgroup = std::dynamic_pointer_cast( aElements[i] ); _replaceElementRecursive(aTarget, aNew, tgroup->mChildren, aBSuccessful ); } @@ -265,7 +282,7 @@ string ofxSvgGroup::toString(int nlevel) { //-------------------------------------------------------------- void ofxSvgGroup::disableColors() { - auto telements = getAllChildren(false); + auto telements = getAllElements(false); for( auto& ele : telements ) { ele->setUseShapeColor(false); } @@ -273,7 +290,7 @@ void ofxSvgGroup::disableColors() { //-------------------------------------------------------------- void ofxSvgGroup::enableColors() { - auto telements = getAllChildren(false); + auto telements = getAllElements(false); for( auto& ele : telements ) { ele->setUseShapeColor(true); } diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h index 8f4aa9a71cb..719c168c62c 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.h +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -1,26 +1,146 @@ -// -// ofxSvgGroup.h -// -// Created by Nick Hardeman on 7/31/15. -// - #pragma once #include "ofxSvgElements.h" class ofxSvgGroup : public ofxSvgElement { public: - virtual ofxSvgType getType() override {return TYPE_GROUP;} + virtual ofxSvgType getType() override {return OFXSVG_TYPE_GROUP;} + + ofxSvgGroup() = default; + + /// \brief Deep-copy constructor. Clone the children so that the new group does not have references to the children in this class. + ofxSvgGroup(const ofxSvgGroup& other) { + mChildren.reserve(other.mChildren.size()); + for (const auto& ptr : other.mChildren) { + // Create a new shared_ptr to a new item copy. + if( ptr ) { + mChildren.push_back(ptr->clone()); + } + } + } + + /// \brief Deep-copy assignment. Clone / create copies of the children from other. + ofxSvgGroup& operator=(const ofxSvgGroup& other) { + if (this != &other) { + mChildren.clear(); + mChildren.reserve(other.mChildren.size()); + for (const auto& ptr : other.mChildren) { + // Create a new shared_ptr to a new item copy. + if( ptr ) { + mChildren.push_back(ptr->clone()); + } + } + } + return *this; + } + virtual void draw() override; - std::size_t getNumChildren();// override; + /// \brief Set the visibility of the group. Does not set visibility of each child. The group only draws its children if the group is visible. + /// \param bool aBVisible set to true for visible. + virtual void setVisible( bool aBVisible ) override { + ofxSvgElement::setVisible(aBVisible); + } + + /// \brief Set the alpha of the group and call setAlpha(aAlpha) on its children. + /// \param float aAlpha in range from 0-1 where 0 is transparent and 1 is full opacity. + virtual void setAlpha( float aAlpha ) override { + alpha = aAlpha; + for( auto& kid : mChildren ) { + kid->setAlpha( aAlpha ); + } + } + + /// \brief Get the bounding box of all of the elements in this group. + /// \return ofRectangle encapsulating all of the elements in the group. + virtual ofRectangle getBoundingBox() override { + ofRectangle rrect; + bool bFirstRect = true; + auto allEs = getAllElements(false); + for( auto& ele : allEs ) { + auto erect = ele->getBoundingBox(); + if( erect.width > 0 && erect.height > 0 ) { + if(bFirstRect) { + rrect = erect; + } else { + rrect.growToInclude(erect); + } + bFirstRect = false; + } + } + return rrect; + }; + + /// \brief Get the number of immediate children in this group. + /// \return std::size_t number of chilren. + std::size_t getNumChildren(); + /// \brief Get immediate children in this group. + /// \return std::vector< std::shared_ptr > all of the children in this group as a reference. std::vector< std::shared_ptr >& getChildren(); - std::vector< std::shared_ptr > getAllChildren(bool aBIncludeGroups); - std::vector< std::shared_ptr > getAllChildGroups(); + /// \brief Get all of the elements under this group recursively creating a flattened vector. If groups are included, + /// the returned groups will point to shared_ptrs in their children but also in the returned vector. + /// \param bool aBIncludeGroups true if groups should be included in the returning vector. + /// \return std::vector< std::shared_ptr > as a flat vector. + std::vector< std::shared_ptr > getAllElements(bool aBIncludeGroups); + /// \brief Get all of the groups under this group recursively creating a flattened vector. + /// \return std::vector< std::shared_ptr > of all the groups. + std::vector< std::shared_ptr > getAllGroups(); + + + + /* + /// -- Search Functions with aPath argument for searching and retrieving elements. -- + + /// \param std::string aPath to the element separated by semi colons and ending with the name of the desired element. + /// If empty, will search children. + /// For example: "MyGroup:MyElement" will attempt to retrieve an element with name "MyElement" from inside the group "MyGroup" + + /// Wildcards are also acceptable and will recursively search until the element with the name is found. + /// For example: "*:MyElement" will recursively search for the element "MyElement". + + /// \param bool bStrict if true, will include elements with a name that matches the final component of the apath. + /// If bStrict is false, will include elements that contain the final component of the aPath. + /// -- For example: Argument aPath = "MyElement" -- + /// Argument bStrict = true; Will NOT return an element with name "MyElement2". + /// Argument bStrict = false; Will return an element with name "MyElement2". + */ + + + + /// \brief Retrieve an element at a path, casted to a type. See notes above about search functions. + /// Example call auto myEle = group->get("MyRectangle", false); + /// \param std::string aPath to the element separated by semi colons and ending with the name of the desired element. If empty, will search children. + /// \param bool bStrict if true, search only elements with a name that matches the final element of the aPath component. + /// \return std::shared_ptr< ofxSvg_T > and invalid if not found. template - std::vector< std::shared_ptr > getElementsForType( std::string aPathToGroup="", bool bStrict= false ) { + std::shared_ptr< ofxSvg_T > get( std::string aPath, bool bStrict = false ) { + auto stemp = getElement( aPath, bStrict ); + if( !stemp ) { + return std::shared_ptr(); + } + return std::dynamic_pointer_cast( stemp ); + } + + /// \brief Retrieve an element from the vector of children. + /// \param int aIndex the index to the element in the vector of children. + /// \return std::shared_ptr< ofxSvg_T > and invalid if not found. + template + std::shared_ptr< ofxSvg_T > get( int aIndex ) { + if( aIndex > -1 && aIndex < mChildren.size() ) { + return std::dynamic_pointer_cast( mChildren[ aIndex ] ); + } + return std::shared_ptr< ofxSvg_T >(); + } + + /// \brief Retrieve elements in a group of a specific type. See notes above about search functions. + /// \param std::string aPathToGroup path to the group to be searched separated by semi colons and ending with the name of the desired group. + /// If empty, will search children. + /// \param bool bStrict if true, search only elements with a name that matches the final element of the aPath component. + /// \return std::vector< std::shared_ptr > casted to type. + template + std::vector< std::shared_ptr > getElementsForType( std::string aPathToGroup, bool bStrict= false ) { auto temp = std::make_shared(); auto sType = temp->getType(); @@ -29,7 +149,7 @@ class ofxSvgGroup : public ofxSvgElement { if( aPathToGroup == "" ) { elementsToSearch = mChildren; } else { - std::shared_ptr< ofxSvgElement > temp = getElementForName( aPathToGroup, bStrict ); + auto temp = getElement( aPathToGroup, bStrict ); if( temp ) { if( temp->isGroup() ) { std::shared_ptr< ofxSvgGroup > tgroup = std::dynamic_pointer_cast( temp ); @@ -51,8 +171,13 @@ class ofxSvgGroup : public ofxSvgElement { return telements; } + /// \brief Retrieve the first element in a group of a specific type. See notes above about search functions. + /// \param std::string aPathToGroup path to group to be searched separated by semi colons and ending with the name of the desired group. + /// If empty, will search children. + /// \param bool bStrict if true, search only elements with a name that matches the final element of the aPath component. + /// \return std::shared_ptr casted to type and invalid if not found. template - std::shared_ptr getFirstElementForType( std::string aPathToGroup="", bool bStrict= false ) { + std::shared_ptr getFirstElementForType( std::string aPathToGroup, bool bStrict= false ) { auto eles = getElementsForType(aPathToGroup, bStrict ); if( eles.size() > 0 ) { return eles[0]; @@ -60,6 +185,9 @@ class ofxSvgGroup : public ofxSvgElement { return std::shared_ptr(); } + + /// \brief Retrieve all of the elements recursively in the group of a specific type. + /// \return std::vector< std::shared_ptr > casted to type. template std::vector< std::shared_ptr > getAllElementsForType() { @@ -67,7 +195,7 @@ class ofxSvgGroup : public ofxSvgElement { auto sType = temp->getType(); std::vector< std::shared_ptr > telements; - auto elementsToSearch = getAllChildren(true); + auto elementsToSearch = getAllElements(true); for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { if( elementsToSearch[i]->getType() == sType ) { @@ -77,82 +205,89 @@ class ofxSvgGroup : public ofxSvgElement { return telements; } + /// \brief Retrieve all of the elements recursively in the group of a specific type filtered by the name provided. + /// \param std::string aname Name to be matched against the elements name. + /// \param bool bStrict if true, the element name mush match aname, if false, the element name must be contained in aname. + /// \return std::vector< std::shared_ptr > casted to type. 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 == 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]) ); - } + std::vector< std::shared_ptr > getAllElementsForTypeForName(std::string aname, bool bStrict = false) { + std::vector< std::shared_ptr > relements; + auto elementsToSearch = getAllElementsForType(); + for( std::size_t i = 0; i < elementsToSearch.size(); i++ ) { + if( bStrict ) { + if( elementsToSearch[i]->getCleanName() == aname ) { + relements.push_back( std::dynamic_pointer_cast(elementsToSearch[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]) ); - } + } else { + if( ofIsStringInString(elementsToSearch[i]->getCleanName(), aname) ) { + relements.push_back( std::dynamic_pointer_cast(elementsToSearch[i]) ); } } } - - return telements; + return relements; } + /// \brief Retrieve all of the elements recursively in the group that have a path. + /// Including ofxSvgPath, ofxSvgRectangle, ofxSvgCircle, ofxSvgEllipse + /// \return std::vector< std::shared_ptr >. std::vector< std::shared_ptr > getAllElementsWithPath(); - std::shared_ptr getElementForName( std::string aPath, bool bStrict = false ); + /// \brief Retrieve an element in this group for a path. See notes above about search functions. + /// \param aPath to the element separated by semi colons and ending with the name of the desired element. + /// \param bool bStrict if true, search only elements with a name that matches the final element of the aPath component. + /// \return std::shared_ptr. Valid if found. + std::shared_ptr getElement( std::string aPath, bool bStrict = false ); + + /// \brief Retrieve all of the elements recursively in the group filtered by the name provided. + /// \param std::string aname Name to be matched against the elements name. + /// \param bool bStrict if true, the element name mush match aname, if false, the element name must be contained in aname. + /// \return std::vector< std::shared_ptr >. + std::vector< std::shared_ptr > getAllElementsForName( const std::string& aname, bool bStrict = false ); + + /// \brief Retrieve child elements in the group filtered by the name provided. + /// \param std::string aname Name to be matched against the elements name. + /// \param bool bStrict if true, the element name mush match aname, if false, the element name must be contained in aname. + /// \return std::vector< std::shared_ptr >. std::vector< std::shared_ptr > getChildrenForName( const std::string& aname, bool bStrict = false ); + /// \brief Retrieve child elements in the group of a specific type filtered by the name provided. + /// \param std::string aname Name to be matched against the elements name. + /// \param bool bStrict if true, the element name mush match aname, if false, the element name must be contained in aname. + /// \return std::vector< std::shared_ptr > casted to type. 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)); - } - } + auto namedKids = getChildrenForName(aname); + for( auto& namedKid : namedKids ) { + if( namedKid->getType() != sType ) continue; + relements.push_back(std::dynamic_pointer_cast(namedKid)); } return relements; } + /// \brief Retrieve child elements in the group of a specific type. + /// \return std::vector< std::shared_ptr > casted to type. template - std::shared_ptr< ofxSvg_T > get( std::string aPath, bool bStrict = false ) { - auto stemp = std::dynamic_pointer_cast( getElementForName( aPath, bStrict ) ); - return stemp; + std::vector< std::shared_ptr > getChildrenForType() { + return getElementsForType(""); } + /// \brief Retrieve the first child element of a specific type. + /// \return std::shared_ptr casted to type; Invalid if not found. template - std::shared_ptr< ofxSvg_T > get( int aIndex ) { - auto stemp = std::dynamic_pointer_cast( mChildren[ aIndex ] ); - return stemp; + std::shared_ptr getFirstChildForType() { + return getFirstElementForType(""); } bool replace( std::shared_ptr aOriginal, std::shared_ptr aNew ); - // adding + /// \brief Add a child element of provided type. + /// \param std::string aname Name to give the created element. + /// \return std::shared_ptr. template std::shared_ptr add(std::string aname) { auto element = std::make_shared(); @@ -161,22 +296,54 @@ class ofxSvgGroup : public ofxSvgElement { return element; }; + /// \brief Add a child element. + /// \param std::shared_ptr aele Element to add as a child of this group. void add( std::shared_ptr aele ) { mChildren.push_back(aele); } + /// \brief Create a string representation of the group hierarchy. + /// \param int nlevel (optional) used for indentation. virtual std::string toString(int nlevel = 0) override; + /// \brief Disable shape colors on children. void disableColors(); + + /// \brief Disable shape colors on children. 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 _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; + + + virtual std::shared_ptr clone() override { + //ofLogVerbose("ofxSvgGroup") << "std::shared_ptr clone():"; + auto newEle = std::make_shared(*this); + newEle->mChildren.clear(); + newEle->mChildren.reserve(mChildren.size()); + for( const auto& ptr : mChildren ) { + if( ptr ) { + newEle->mChildren.push_back(ptr->clone()); + } + } + return newEle; + }; }; diff --git a/addons/ofxSvg/src/ofxSvgUtils.cpp b/addons/ofxSvg/src/ofxSvgUtils.cpp new file mode 100644 index 00000000000..93c78e1f76b --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgUtils.cpp @@ -0,0 +1,262 @@ +#include "ofxSvgUtils.h" +#include +#include "ofImage.h" + +/*----------------------------------------------------------------------*/ +/* + + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + + Version: 2.rc.09 (release candidate) + + Copyright (C) 2004-2017, 2020-2022 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + + */ + + +/* + https://github.com/ReneNyffenegger/cpp-base64/blob/master/base64.cpp + This code has been adapted to work in only a .cpp file. + // certain functions have been ommitted. + */ + +static const char* base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"}; + + +static unsigned int pos_of_char(const unsigned char chr) { + // + // Return the position of chr within base64_encode() + // + + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' + else + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + throw std::runtime_error("Input is not valid base64-encoded data."); +} + + +std::string ofxSvgUtils::base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { + + size_t len_encoded = (in_len +2) / 3 * 4; + + unsigned char trailing_char = url ? '.' : '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = base64_chars[url]; + + std::string ret; + ret.reserve(len_encoded); + + unsigned int pos = 0; + + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos+1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos+2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { + + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } + + pos += 3; + } + + + return ret; +} + +//template // nh removed to only work with string +std::string ofxSvgUtils::base64_decode(std::string const& encoded_string, bool remove_linebreaks) { + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // + + if (encoded_string.empty()) return std::string(); + + if (remove_linebreaks) { + + std::string copy(encoded_string); + + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + + return base64_decode(copy, false); // nh modified to work without .cpp and .h + // return base64_decode(copy, false); + } + + size_t length_of_string = encoded_string.length(); + size_t pos = 0; + + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string) { + // + // Iterate over encoded input string in chunks. The size of all + // chunks except the last one is 4 bytes. + // + // The last chunk might be padded with equal signs or dots + // in order to make it 4 bytes in size as well, but this + // is not required as per RFC 2045. + // + // All chunks except the last one produce three output bytes. + // + // The last chunk produces at least one and up to three bytes. + // + + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) ); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); + + if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos+2) != '=' && + encoded_string.at(pos+2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) + { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) ); + ret.push_back(static_cast( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); + + if ( ( pos + 3 < length_of_string ) && + encoded_string.at(pos+3) != '=' && + encoded_string.at(pos+3) != '.' + ) + { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3)) )); + } + } + + pos += 4; + } + + return ret; +} + +/* end base64 encoding / decoding */ +/*----------------------------------------------------------------------*/ + + +//--------------------------------------------------------------- +std::string ofxSvgUtils::base64_encode( const ofPixels& apixels ) { + ofBuffer tbuffer; + ofSaveImage(apixels, tbuffer); + auto buffStr = tbuffer.getText(); + const unsigned char* data = reinterpret_cast(buffStr.data()); + return ofxSvgUtils::base64_encode( data, tbuffer.size(), false ); +} + +//--------------------------------------------------------------- +ofPixels ofxSvgUtils::base64_decode(std::string const& encoded_string ) { + ofBuffer tbuffer; + + ofPixels rpix; + + std::string estring = encoded_string; + ofStringReplace(estring, "data:image/png;base64,", "" ); + + if( estring.empty() ) { + return rpix; + } + + try { + std::string decoded_string = base64_decode(estring, false ); + + if( decoded_string.size() < 1 ) { + return rpix; + } + tbuffer.set(decoded_string); + + bool bok = ofLoadImage(rpix, tbuffer); + ofLogNotice("ofxSvgUtils::base64_decode") << "pixels ok: " << bok << " pixels: " << rpix.getWidth() << " x " << rpix.getHeight(); + + } catch (const std::runtime_error& e) { + if (std::string(e.what()) == "Input is not valid base64-encoded data.") { + ofLogError("ofxSvgUtils::base64_decode") << "Caught specific base64 error: " << e.what() << "\n"; + } else { + ofLogError("ofxSvgUtils::base64_decode") << "Caught runtime_error: " << e.what() << "\n"; + } + } + catch (...) { + ofLogError("ofxSvgUtils::base64_decode") << "Caught unknown exception.\n"; + } + + + +// bool bok = ofLoadImage(rpix, tbuffer); +// ofLogNotice("ofxSvgUtils::base64_decode") << "pixels ok: " << bok << " pixels: " << rpix.getWidth() << " x " << rpix.getHeight(); + return rpix; +} diff --git a/addons/ofxSvg/src/ofxSvgUtils.h b/addons/ofxSvg/src/ofxSvgUtils.h new file mode 100644 index 00000000000..13d7ee832b7 --- /dev/null +++ b/addons/ofxSvg/src/ofxSvgUtils.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include "ofPixels.h" + +class ofxSvgUtils { +public: + +// Adapted from code by René Nyffenegger. +// base64 encoding and decoding with C++. +// More information at +// https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + static std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url); + static std::string base64_decode(std::string const& encoded_string, bool remove_linebreaks); + + static std::string base64_encode( const ofPixels& apixels ); + static ofPixels base64_decode(std::string const& encoded_string ); +}; From 648aeda72efa9c59dd1bf9e0a35b0e3cd932ba57 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Wed, 20 Aug 2025 23:49:41 -0400 Subject: [PATCH 10/21] more support for legacy transforms and more robust parsing. --- addons/ofxSvg/src/ofxSvg.cpp | 217 +++++++++++++++++++-------- addons/ofxSvg/src/ofxSvgElements.cpp | 2 +- addons/ofxSvg/src/ofxSvgGroup.h | 2 + 3 files changed, 157 insertions(+), 64 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index dd3c2484c8d..90264fdc34f 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -869,7 +869,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr parsePoints(const std::string& input) { +std::vector parseToFloats(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; @@ -885,7 +885,29 @@ std::vector parsePoints(const std::string& input) { } } - // Create vec2 pairs from the values + return values; +} + +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; +// } +// } + + std::vector points; + auto values = parseToFloats( input ); + + // Create vec3 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); @@ -902,6 +924,44 @@ std::vector parsePoints(const std::string& input) { } +std::vector parsePointsDefaultY(const std::string& input, float aYpos ) { + std::vector points; + auto values = parseToFloats( input ); + + // Create vec3 pairs from the values + for (size_t i = 0; i < values.size(); i++) { + glm::vec3 point(values[i], aYpos, 0.f); + points.push_back(point); + } + + if( values.size() == 1 && points.size() < 1) { + glm::vec3 point(values[0], aYpos, 0.f); + points.push_back(point); + } + + return points; +} + +std::vector parsePointsDefaultX(const std::string& input, float aXpos ) { + std::vector points; + auto values = parseToFloats( input ); + + // Create vec3 pairs from the values + for (size_t i = 0; i < values.size(); i++) { + glm::vec3 point(aXpos, values[i], 0.f); + points.push_back(point); + } + + if( values.size() == 1 && points.size() < 1) { + glm::vec3 point(aXpos, values[0], 0.f); + points.push_back(point); + } + + return points; +} + + + //---------------------------------------------------- std::vector _parseSvgArc(const std::string& arcStr) { std::vector result; @@ -1027,8 +1087,7 @@ void ofxSvg::_parsePolylinePolygon( ofXml& tnode, std::shared_ptr aS // reference: https://www.w3.org/TR/SVG2/paths.html#PathData //-------------------------------------------------------------- void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { - // path4160 - +// path27340-8 aSvgPath->path.clear(); @@ -1107,14 +1166,13 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { return aCurrentPos; }; - auto lineToRelativeRecursive = [](glm::vec3& aStartPos, glm::vec3& acurrentPos, std::vector& aposes, std::shared_ptr aPath ) { -// int ncounter = 0; + auto lineToRelativeFromAbsoluteRecursive = [](glm::vec3& aStartPos, glm::vec3& acurrentPos, std::vector& aposes, std::shared_ptr aPath ) { + // int ncounter = 0; auto cp = aStartPos; for( auto& np : aposes ) { auto relativePos = np-aStartPos; +// auto relativePos = np; auto newPos = relativePos + cp; -// ofLogNotice("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " cp: " << cp << " np: " << np; -// ofLogVerbose("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " np: " << np << " relative: " << relativePos << " newPos: " << newPos << " currentPos: " << currentPos; aPath->path.lineTo(newPos); cp = newPos;//relativePos+prevPos; // ncounter++; @@ -1131,6 +1189,8 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { aSvgPath->path.clear(); +// auto prevCmd = ofPath::Command::close; + unsigned int justInCase = 0; // std::vector commands; bool breakMe = false; @@ -1212,31 +1272,37 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } npositions = parsePoints(currentString); for( int ni = 0; ni < npositions.size(); ni++ ) { - ofLogVerbose("ofxSvg::_parsePath") << ni << "-" << npositions[ni]; + ofLogVerbose("ofxSvg::_parsePath") << ni << "->" << npositions[ni]; } // if( npositions.size() > 0 && bRelative ) { // mCurrentPathPos = npositions[0]; // } ctype = ofPath::Command::moveTo; } else if( cchar == 'v' || cchar == 'V' ) { + + float xvalue = 0.f; if( cchar == 'v' ) { bRelative = true; - npositions[0].x = 0.f; +// npositions[0].x = 0.f; } else { - npositions[0].x = currentPos.x; +// npositions[0].x = currentPos.x; + xvalue = currentPos.x; } - - npositions[0].y = ofToFloat(currentString); + npositions = parsePointsDefaultX(currentString,xvalue); +// npositions[0].y = ofToFloat(currentString); //ofLogVerbose("ofxSvg") << cchar << " line to: " << npositions[0] << " current pos: " << currentPos; ctype = ofPath::Command::lineTo; } else if( cchar == 'H' || cchar == 'h' ) { + float yvalue = 0.f; if( cchar == 'h' ) { bRelative = true; - npositions[0].y = 0.f; +// npositions[0].y = 0.f; } else { - npositions[0].y = currentPos.y; +// npositions[0].y = currentPos.y; + yvalue = currentPos.y; } - npositions[0].x = ofToFloat(currentString); + npositions = parsePointsDefaultY(currentString,yvalue); +// npositions[0].x = ofToFloat(currentString); ctype = ofPath::Command::lineTo; } else if( cchar == 'L' || cchar == 'l' ) { @@ -1300,12 +1366,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { if( ctype.has_value() ) { -// for( auto& np : npositions ) { -// ofLogNotice("ofxSvg") << cchar << " position: " << np; -// } - auto prevPos = currentPos; - auto commandT = ctype.value(); if( commandT == ofPath::Command::arc ) { @@ -1326,77 +1387,91 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { // TODO: Check quad bezier for poly bezier like cubic bezier } else { -// if( commandT == ofPath::Command::moveTo ) { -// ofLogNotice("ofxSvg") << "before current pos is altered: move to: " << npositions[0] << " current Pos: " << currentPos << " relative: " << bRelative; -// } if( npositions.size() > 0 && commandT != ofPath::Command::close ) { - if( commandT != ofPath::Command::moveTo ) { - //currentPos = {0.f, 0.f, 0.f}; - } + if( commandT == ofPath::Command::moveTo ) { + // going to handle this below; + // inside the if( commandT == ofPath::Command::moveTo ) { check. + } else { // currentPos = convertToAbsolute(bRelative, currentPos, npositions ); - currentPos = convertToAbsolute(bRelative, currentPos, npositions ); + currentPos = convertToAbsolute(bRelative, currentPos, npositions ); + } } } -// if( npositions.size() > 0 ) { -// ofLogNotice("ofxSvg") << "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); + + if( cchar == 'm' ) { + if(npositions.size() > 0 ) { + if(cindex == 0 ) { + // this is the first m, so the moveTo is absolute but the subsequent points are relative + currentPos = npositions[0]; + } else { + currentPos += npositions[0]; + } + } + } else { + if(npositions.size() > 0 ) { + currentPos = npositions[0]; + } + } + + if( npositions.size() > 0 ) { + ofLogVerbose("ofxSvg::moveTo") << npositions[0] << " currentPos: " << currentPos;// << " path pos: " << aSvgPath->pos; + aSvgPath->path.moveTo(currentPos); +// mCenterPoints.push_back(npositions[0] + pathOffset); + } + if(npositions.size() > 1 ) { - bool bLineToRelative = bRelative; // determine if these points started with m and is the first character - if( cchar == 'm' && cindex == 0 ) { + if( cchar == 'm') { bLineToRelative = true; } if( bLineToRelative ) { - auto newPoses = npositions; - newPoses.erase(newPoses.begin()); - lineToRelativeRecursive(prevPos, currentPos, newPoses, aSvgPath); + for( int ki = 1; ki < npositions.size(); ki++ ) { +// auto newPos = npositions[ki] + cp; + currentPos += npositions[ki]; + aSvgPath->path.lineTo(currentPos); +// cp = newPos; + } +// currentPos = cp; + } else { for( int ki = 1; ki < npositions.size(); ki++ ) { +// mCPoints.push_back(npositions[ki]); +// ofLogVerbose("ofxSvg::lineTo") << ki << "--->" << npositions[ki]; aSvgPath->path.lineTo(npositions[ki]); +// mCenterPoints.push_back(npositions[ki] + pathOffset); + } + if(npositions.size() > 0 ) { + currentPos = npositions.back(); } } } + + secondControlPoint = currentPos; + qControlPoint = currentPos; + } else if( commandT == ofPath::Command::lineTo ) { if( npositions.size() > 0 ) { // current pos is already set above // so just worry about adding paths if( bRelative ) { - - lineToRelativeRecursive(prevPos, currentPos, npositions, aSvgPath ); -// mCenterPoints.clear(); -// int ncounter = 0; -// auto cp = prevPos; -// for( auto& np : npositions ) { -// auto relativePos = np-prevPos; -// auto newPos = relativePos + cp; -//// ofLogNotice("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " cp: " << cp << " np: " << np; -//// ofLogVerbose("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " np: " << np << " relative: " << relativePos << " newPos: " << newPos << " currentPos: " << currentPos; -// aSvgPath->path.lineTo(newPos); -// mCenterPoints.push_back(newPos); -// cp = newPos;//relativePos+prevPos; -// ncounter++; -// } -// currentPos = cp; -// // } + lineToRelativeFromAbsoluteRecursive(prevPos, currentPos, npositions, aSvgPath ); } else { -// int ncounter = 0; + int ncounter = 0; for( auto& np : npositions ) { -// ofLogVerbose("ofxSvg::_parsePath") << ncounter << " - l: " << prevPos << " np: " << np; + ofLogVerbose("ofxSvg::lineTo") << ncounter << "--->"<< np << " prevPos:" << prevPos; aSvgPath->path.lineTo(np); -// ncounter++; + ncounter++; } } } @@ -1404,6 +1479,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { // aSvgPath->path.lineTo(currentPos); } else if( commandT == ofPath::Command::close ) { // ofLogNotice("ofxSvg") << "Closing the path"; + // TODO: Not sure if we need to draw a line to the start point here aSvgPath->path.close(); } else if( commandT == ofPath::Command::bezierTo ) { @@ -1595,6 +1671,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } } +// prevCmd = commandT; // mCenterPoints.push_back(currentPos); // mCPoints.insert( mCPoints.end(), npositions.begin(), npositions.end() ); } @@ -1859,7 +1936,7 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< vector matrixF; for(std::size_t i = 0; i < matrixNum.size(); i++){ matrixF.push_back(ofToFloat(matrixNum[i])); - std::cout << aele->getCleanName() << " matrix[" << i << "] = " << matrixF[i] << " string version is " << matrixNum[i] << std::endl; + ofLogVerbose("ofxSvg::setTransformFromSvgMatrixString") << aele->getCleanName() << " matrix[" << i << "] = " << matrixF[i] << " string version is " << matrixNum[i]; } if( matrixNum.size() == 6 ) { @@ -1867,13 +1944,25 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< mat = glm::translate(glm::mat4(1.0f), glm::vec3(matrixF[4], matrixF[5], 0.0f)); aele->rotation = glm::degrees( atan2f(matrixF[1],matrixF[0]) ); + + + 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]); + + if (matrixF[0] < 0) aele->scale.x *= -1.f; + if (matrixF[3] < 0) aele->scale.y *= -1.f; + + // Avoid double-rotating when both scale = -1 and rotation = 180 + if (aele->scale.x < 0 && aele->scale.y < 0 && glm::abs(aele->rotation - 180.0f) < 0.01f) { + aele->rotation = 0.f; + } + 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))); mat = glm::rotate(mat, 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)); @@ -1882,6 +1971,8 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< aele->pos.x = pos3.x; aele->pos.y = pos3.y; + ofLogNotice("ofxSvg::setTransformFromSvgMatrixString") << "pos: " << aele->pos << " rotation: " << aele->rotation << " scale: " << aele->scale; + // apos.x = matrixF[4]; // apos.y = matrixF[5]; // @@ -2383,7 +2474,7 @@ void ofxSvg::drawDebug() { // cindex ++; // } // ofFill(); - + for( std::size_t k = 0; k < mCPoints.size(); k += 3 ) { ofSetColor( ofColor::orange ); ofDrawCircle( mCPoints[k+0], 6.f ); diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index ea84bb1a161..943bc43ca99 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -266,7 +266,7 @@ void ofxSvgPath::draw() { //-------------------------------------------------------------- void ofxSvgImage::draw() { if( !bTryLoad ) { - if( getFilePath().empty() ) { + if( !getFilePath().empty() ) { img.load( getFilePath() ); bTryLoad = true; } diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h index 719c168c62c..f73cf9a3158 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.h +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -10,6 +10,7 @@ class ofxSvgGroup : public ofxSvgElement { /// \brief Deep-copy constructor. Clone the children so that the new group does not have references to the children in this class. ofxSvgGroup(const ofxSvgGroup& other) { + ofLogVerbose("ofxSvgGroup") << "ofxSvgGroup(const ofxSvgGroup& other)"; mChildren.reserve(other.mChildren.size()); for (const auto& ptr : other.mChildren) { // Create a new shared_ptr to a new item copy. @@ -22,6 +23,7 @@ class ofxSvgGroup : public ofxSvgElement { /// \brief Deep-copy assignment. Clone / create copies of the children from other. ofxSvgGroup& operator=(const ofxSvgGroup& other) { if (this != &other) { + ofLogVerbose("ofxSvgGroup") << "operator=(const ofxSvgGroup& other)"; mChildren.clear(); mChildren.reserve(other.mChildren.size()); for (const auto& ptr : other.mChildren) { From e353686cdf0e5ab93d35d44e7fe96ac1a171c6e6 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Fri, 22 Aug 2025 17:07:32 -0400 Subject: [PATCH 11/21] Extend ofNode for easier transform maintenance --- addons/ofxSvg/src/ofxSvg.cpp | 436 ++++++++++++++++----------- addons/ofxSvg/src/ofxSvg.h | 4 +- addons/ofxSvg/src/ofxSvgCss.cpp | 12 +- addons/ofxSvg/src/ofxSvgCss.h | 3 + addons/ofxSvg/src/ofxSvgElements.cpp | 298 +++++++++--------- addons/ofxSvg/src/ofxSvgElements.h | 292 +++++++++++++----- addons/ofxSvg/src/ofxSvgGroup.cpp | 17 +- addons/ofxSvg/src/ofxSvgGroup.h | 30 +- 8 files changed, 685 insertions(+), 407 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 90264fdc34f..26f286b984f 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -58,6 +58,9 @@ void ofxSvg::deepCopyFrom( const ofxSvg & mom ) { if( mom.mChildren.size() > 0 ) { mChildren = deepCopyVector(mom.mChildren); + for( auto& kid : mChildren ) { + kid->setParent(*this); + } } if( mom.mDefElements.size() > 0 ) { mDefElements = deepCopyVector(mom.mDefElements); @@ -100,6 +103,11 @@ void ofxSvg::moveFrom( ofxSvg&& mom ) { ofLogVerbose("ofxSvg::moveFrom"); mChildren = std::move(mom.mChildren); + + for( auto& kid : mChildren ) { + kid->setParent(*this); + } + mDefElements = std::move(mom.mDefElements); mViewbox = mom.mViewbox; @@ -268,6 +276,10 @@ bool ofxSvg::loadFromString(const std::string& data, std::string url) { // the defs are added in the _parseXmlNode function // _parseXmlNode( svgNode, mChildren ); + // then set the parent to be the document +// for( auto& child : mChildren ) { +// child->setParent(*this); +// } ofLogVerbose("ofxSvg") << " number of defs elements: " << mDefElements.size(); } @@ -410,7 +422,7 @@ const std::vector& ofxSvg::getPaths() const { std::size_t num = spaths.size(); mPaths.resize(num); for( std::size_t i = 0; i < num; i++ ) { - mPaths[i] = spaths[i]->path; + mPaths[i] = spaths[i]->getPath(); } } return mPaths; @@ -539,20 +551,22 @@ void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr 0 ) { +// auto pgroup = mGroupStack.back(); +// tgroup->setParent(*pgroup.get()); +// } + tgroup->setParent(*_getPushedGroup(), false); + + pushGroup(tgroup); aElements.push_back( tgroup ); _parseXmlNode( kid, tgroup->getChildren() ); - - if( transAttr ) { - popMatrix(); - } + popGroup(); _popCssClass(); } @@ -561,7 +575,7 @@ void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr >& aElements ) { +shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr >& aElements ) { shared_ptr telement; if( tnode.getName() == "use") { @@ -622,9 +636,13 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr(); auto cxAttr = tnode.getAttribute("cx"); - if(cxAttr) ellipse->pos.x = cxAttr.getFloatValue(); + + auto tpos = glm::vec2(0.f, 0.f); + if(cxAttr) {tpos.x = cxAttr.getFloatValue();} auto cyAttr = tnode.getAttribute("cy"); - if(cyAttr) ellipse->pos.y = cyAttr.getFloatValue(); + if(cyAttr) {tpos.y = cyAttr.getFloatValue();} + + ellipse->setPosition( tpos.x, tpos.y, 0.0f); auto rxAttr = tnode.getAttribute( "rx" ); if(rxAttr) ellipse->radiusX = rxAttr.getFloatValue(); @@ -641,10 +659,14 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr(); - 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 tpos = glm::vec2(0.f, 0.f); + if(auto cxAttr = tnode.getAttribute("cx")) { + tpos.x = cxAttr.getFloatValue(); + } + if(auto cyAttr = tnode.getAttribute("cy")) { + tpos.y = cyAttr.getFloatValue(); + } + circle->setPosition(tpos.x, tpos.y, 0.f); auto rAttr = tnode.getAttribute( "r" ); if(rAttr) circle->radius = rAttr.getFloatValue(); @@ -695,16 +717,21 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr(); - 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 tpos = glm::vec2(0.f, 0.f); + if(auto xattr = tnode.getAttribute("x")) { + tpos.x = xattr.getFloatValue(); + } + if(auto yattr = tnode.getAttribute("y")) { + tpos.y = yattr.getFloatValue(); + } + if(auto wattr = tnode.getAttribute("width")) { + rect->width = wattr.getFloatValue(); + } + if(auto hattr = tnode.getAttribute("height")) { + rect->height = hattr.getFloatValue(); + } + rect->setPosition(tpos.x, tpos.y, 0.0f); auto rxAttr = tnode.getAttribute("rx"); auto ryAttr = tnode.getAttribute("ry"); @@ -714,13 +741,14 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrroundRadius = -1.f; // force an update in setRoundRadius + rect->setRoundRadius(std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), + ofxSvgCssClass::sGetFloat(ryAttr.getValue())) + ); +// rect->roundRadius = std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), +// ofxSvgCssClass::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 - ); +// rect->path.rectRounded(0.f, 0.f, rect->width, rect->height, rect->roundRadius); } else { rect->path.rectangle(0.f, 0.f, rect->getWidth(), rect->getHeight()); @@ -748,9 +776,9 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrtextSpans.size(); -// for( auto& tspan : text->textSpans ) { -// ofLogNotice("ofxSvg") << "_addElementFromXmlNode :: text:: " << tspan->text; -// } - - _popCssClass(); -// mCurrentCss = tempCss; - string tempFolderPath = ofFilePath::addTrailingSlash(folderPath); if( ofDirectory::doesDirectoryExist( tempFolderPath+"fonts/" )) { text->setFontDirectory( tempFolderPath+"fonts/" ); @@ -798,7 +817,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr(); } auto idAttr = tnode.getAttribute("id"); @@ -820,7 +839,7 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrgetType() ) { // bApplyTransformToPath = true; @@ -828,32 +847,33 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr( 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( bApplyTransformToPath ) { +// auto epath = std::dynamic_pointer_cast( telement ); +// auto outlines = epath->path.getOutline(); +//// auto transform = epath->getTransformMatrix(); +// auto transform = epath->getGlobalTransformMatrix(); +// 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() == OFXSVG_TYPE_TEXT ) { @@ -866,7 +886,16 @@ bool ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptrlayer = mCurrentLayer += 1.0; aElements.push_back( telement ); - return true; + + if( mGroupStack.size() > 0 ) { + auto pgroup = mGroupStack.back(); + ofLogVerbose("ofxSvg::_addElementFromXmlNode") << "element: " << telement->getTypeAsString() << " -" << telement->getCleanName() << "- pos: " << telement->getPosition() << "- parent: " << pgroup->getCleanName(); +// telement->setParent(*pgroup.get(), false); + telement->setParent(*_getPushedGroup(), false); + //ofLogNotice(""); + } + + return telement; } std::vector parseToFloats(const std::string& input) { @@ -1827,35 +1856,39 @@ glm::vec3 ofxSvg::_parseMatrixString(const std::string& input, const std::string //-------------------------------------------------------------- //bool Parser::getTransformFromSvgMatrix( string aStr, glm::vec2& apos, float& scaleX, float& scaleY, float& arotation ) { glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr aele ) { - ofLogVerbose("-----------ofxSvg") << __FUNCTION__ << " name: " << aele->getName() +"----------------"; - aele->scale = glm::vec2(1.0f, 1.0f); - aele->rotation = 0.0; + ofLogVerbose("-----------ofxSvg::setTransformFromSvgMatrixString") << aele->getTypeAsString() << " name: " << aele->getName() +"----------------"; +// aele->scale = glm::vec2(1.0f, 1.0f); +// aele->rotation = 0.0; + aele->setScale(1.f); aele->mModelRotationPoint = glm::vec2(0.0f, 0.0f); //TODO: implement matrix push and pop structure, similar to renderers - ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " going to parse string: " << aStr << " pos: " << aele->pos; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " going to parse string: " << aStr << " pos: " << aele->getPosition(); + float trotation = 0.f; glm::mat4 mat = glm::mat4(1.f); - glm::mat4 gmat = mModelMatrix; +// glm::mat4 gmat = mModelMatrix; if( ofIsStringInString(aStr, "translate")) { auto transStr = aStr; auto tp = _parseMatrixString(transStr, "translate", false ); - ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " translate: " << tp; + tp.z = 0.f; + ofLogVerbose("ofxSvg::setTransformFromSvgMatrixString") << aele->getTypeAsString() << " name: " << aele->getName() << " translate: " << tp; // apos += tp; mat = glm::translate(glm::mat4(1.0f), glm::vec3(tp.x, tp.y, 0.0f)); - gmat = glm::translate(gmat, glm::vec3(tp.x, tp.y, 0.0f)); +// gmat = glm::translate(gmat, glm::vec3(tp.x, tp.y, 0.0f)); // aele->pos.x = tp.x; // aele->pos.y = tp.y; +// aele->setPosition(tp.x, tp.y, 0.0f); } else { mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.f, 0.f, 0.0f)); - gmat = glm::translate(gmat, glm::vec3(0.f, 0.f, 0.0f)); +// gmat = glm::translate(gmat, 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 ) { + trotation = tr.x; + if( trotation != 0.f ) { glm::vec2 rcenter(0.f, 0.f); if( tr.y != 0.0f || tr.z != 0.0f ) { rcenter.x = tr.y; @@ -1868,39 +1901,44 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< 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) ); + glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), glm::radians(trotation), 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; - gmat = backToPivot * rotation * toOrigin * gmat; +// gmat = backToPivot * rotation * toOrigin * gmat; } else { // mat = mat * glm::toMat4((const glm::quat&)glm::angleAxis(glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f))); - mat = glm::rotate(mat, glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f)); - gmat = glm::rotate(gmat, glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f)); + mat = glm::rotate(mat, glm::radians(trotation), glm::vec3(0.f, 0.f, 1.f)); +// gmat = glm::rotate(gmat, glm::radians(trotation), glm::vec3(0.f, 0.f, 1.f)); } + + // now we need to apply the rotation + aele->setOrientation(glm::angleAxis(glm::radians(trotation), glm::vec3(0.f, 0.f, 1.f) )); // ofLogNotice("ofxSvg") << "rcenter: " << rcenter.x << ", " << rcenter.y; } - ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " arotation: " << aele->rotation << " trot: " << tr; + ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " arotation: " << trotation << " trot: " << tr; } if( ofIsStringInString(aStr, "scale")) { auto transStr = aStr; auto ts = _parseMatrixString(transStr, "scale", false ); - aele->scale.x = ts.x; - aele->scale.y = ts.y; +// aele->scale.x = ts.x; +// aele->scale.y = ts.y; + aele->setScale(ts.x, ts.y, 1.f); ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " scale: " << ts; - mat = glm::scale(mat, glm::vec3(aele->scale.x, aele->scale.y, 1.f)); - gmat = glm::scale(gmat, glm::vec3(aele->scale.x, aele->scale.y, 1.f)); + mat = glm::scale(mat, glm::vec3(aele->getScale().x, aele->getScale().y, 1.f)); +// gmat = glm::scale(gmat, glm::vec3(aele->getScale().x, aele->getScale().y, 1.f)); } - glm::vec3 pos3 = mat * glm::vec4( aele->pos.x, aele->pos.y, 0.0f, 1.f ); + glm::vec3 pos3 = mat * glm::vec4( aele->getPosition().x, aele->getPosition().y, 0.0f, 1.f ); // pos3 = gmat * glm::vec4( aele->pos.x, aele->pos.y, 0.0f, 1.f ); - aele->pos.x = pos3.x; - aele->pos.y = pos3.y; +// aele->pos.x = pos3.x; +// aele->pos.y = pos3.y; + aele->setPosition( pos3.x, pos3.y, 0.0f); // aele @@ -1943,35 +1981,42 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< mat = glm::translate(glm::mat4(1.0f), glm::vec3(matrixF[4], matrixF[5], 0.0f)); - aele->rotation = glm::degrees( atan2f(matrixF[1],matrixF[0]) ); + aele->setPosition(matrixF[4], matrixF[5], 0.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]); + float trotation = glm::degrees( atan2f(matrixF[1],matrixF[0]) ); - if (matrixF[0] < 0) aele->scale.x *= -1.f; - if (matrixF[3] < 0) aele->scale.y *= -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]); + float sx = glm::sqrt(matrixF[0] * matrixF[0] + matrixF[1] * matrixF[1]); + float sy = glm::sqrt(matrixF[2] * matrixF[2] + matrixF[3] * matrixF[3]); + + if (matrixF[0] < 0) sx *= -1.f; + if (matrixF[3] < 0) sy *= -1.f; + + aele->setScale(sx, sy, 1.f); // Avoid double-rotating when both scale = -1 and rotation = 180 - if (aele->scale.x < 0 && aele->scale.y < 0 && glm::abs(aele->rotation - 180.0f) < 0.01f) { - aele->rotation = 0.f; + if (sx < 0 && sy < 0 && glm::abs(trotation - 180.0f) < 0.01f) { + trotation = 0.f; } - if( aele->rotation != 0.f ) { + if( trotation != 0.f ) { // mat = mat * glm::toMat4((const glm::quat&)glm::angleAxis(glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f))); - mat = glm::rotate(mat, glm::radians(aele->rotation), glm::vec3(0.f, 0.f, 1.f)); + mat = glm::rotate(mat, glm::radians(trotation), glm::vec3(0.f, 0.f, 1.f)); +// aele->setOrientation(glm::angleAxis(glm::radians(trotation), glm::vec3(0.f, 0.f, 1.f) )); + aele->setRotationDeg(trotation); } + mat = glm::scale(mat, glm::vec3(aele->getScale().x, aele->getScale().y, 1.f)); +// pos3 = mat * glm::vec4( aele->getPosition().x, aele->getPosition().y, 0.0f, 1.f ); +// aele->setPosition( pos3.x, pos3.y, 0.0f); +// aele->pos.x = pos3.x; +// aele->pos.y = pos3.y; - 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; - - ofLogNotice("ofxSvg::setTransformFromSvgMatrixString") << "pos: " << aele->pos << " rotation: " << aele->rotation << " scale: " << aele->scale; + ofLogNotice("ofxSvg::setTransformFromSvgMatrixString") << "pos: " << aele->getPosition() << " rotation: " << trotation << " scale: " << aele->getScale(); // apos.x = matrixF[4]; // apos.y = matrixF[5]; @@ -1998,43 +2043,86 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< //-------------------------------------------------------------- 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 - 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 <<")"; + + std::ostringstream matrixStream; + matrixStream << std::fixed << std::setprecision(6); + bool bFirst = true; + + if( aele->getPosition().x != 0.f || aele->getPosition().y != 0.f ) { + bFirst = false; + matrixStream << "translate(" << aele->getPosition().x << "," << aele->getPosition().y << ")"; + } + if( aele->getRotationDeg() != 0.f ) { + if(!bFirst) { + matrixStream << " "; } - 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 << ")"; + bFirst = false; + if( aele->mModelRotationPoint.x != 0.0f || aele->mModelRotationPoint.y != 0.0f ) { + matrixStream << "rotate(" << aele->getRotationDeg() << " " << aele->mModelRotationPoint.x << " " << aele->mModelRotationPoint.y <<")"; + } else { + matrixStream << "rotate(" << aele->getRotationDeg() <<")"; + } + } + + if( aele->getScale().x != 1.f || aele->getScale().y != 1.f ) { + if(!bFirst) { + matrixStream << " "; + } + bFirst = false; + matrixStream << "scale(" << aele->getScale().x << " " << aele->getScale().y <<")"; + } + + if( matrixStream.str().size() > 3 ) { return matrixStream.str(); } + + + // 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; +// +//// float rotationZDeg = glm::degrees(glm::eulerAngles(aele->getOrientationQuat()).z); +// +// // if we are a group, path or image, then save position +// // rect and circle / ellipse pull their position +// +// +// // TODO: SAVE POSITION! +// std::ostringstream matrixStream; +// matrixStream << std::fixed << std::setprecision(6) << "rotate(" << aele->getRotationDeg() << " " << rcenter.x << " " << rcenter.y <<")"; +// if( aele->getScale().x != 1.f || aele->getScale().y != 1.f ) { +// matrixStream << " scale(" << aele->getScale().x << " " << aele->getScale().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) ); +//// +//// auto transform = aele->getLocalTransformMatrix(); +//// +//// // 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 ""; } @@ -2058,11 +2146,9 @@ void ofxSvg::_getTextSpanFromXmlNode( ofXml& anode, std::vector< std::shared_ptr ty = tyattr.getFloatValue(); } - tspan->text = tText; + tspan->setText(tText); tspan->rect.x = tx; tspan->rect.y = ty; -// tspan->ogPos.x = tx; -// tspan->ogPos.y = ty; _applyStyleToText(anode, tspan); @@ -2081,9 +2167,6 @@ void ofxSvg::_getTextSpanFromXmlNode( ofXml& anode, std::vector< std::shared_ptr } _popCssClass(); -// ofLogNotice("ofxSvg::_getTextSpanFromXmlNode") << "text: " << tspan->text; - -// return tspan; } //-------------------------------------------------------------- @@ -2294,11 +2377,14 @@ std::shared_ptr ofxSvg::add( const ofRectangle& arect ) { //-------------------------------------------------------------- std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRadius ) { auto rect = std::make_shared(); - rect->rectangle = arect; +// rect->rectangle = arect; // rect->pos = arect.getPosition(); _applyModelMatrixToElement( rect, arect.getPosition() ); - rect->round = aRoundRadius; - rect->path.rectangle(arect); +// rect->round = aRoundRadius; +// rect->path.rectangle(arect); + rect->roundRadius = -1; // force setting round + rect->setRoundRadius(std::max(0.f,aRoundRadius)); + // _config(rect); // _applyStyleToPath( mCurrentCss, rect ); rect->applyStyle(mCurrentCss); @@ -2318,9 +2404,12 @@ std::shared_ptr ofxSvg::addCircle( const glm::vec2& apos, float ar auto circle = std::make_shared(); // circle->pos = apos; _applyModelMatrixToElement( circle, apos ); - circle->radius = aradius; +// circle->radius = aradius; circle->path.setCircleResolution(mCircleResolution); - circle->path.circle(apos, aradius); + circle->radius = -1.f; + circle->setRadius(std::max(0.f,aradius)); +// circle->path.circle(apos, aradius); + circle->setPosition( apos.x, apos.y, 0.0f); // _config(circle); // _applyStyleToPath( mCurrentCss, circle ); circle->applyStyle(mCurrentCss); @@ -2515,16 +2604,17 @@ bool ofxSvg::_hasPushedMatrix() { } //-------------------------------------------------------------- +// TODO: CHECK ON THIS AFTER ofNode is implemented void ofxSvg::_applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ) { if(_hasPushedMatrix() ) { - aele->pos = aDefaultPos; +// aele->pos = aDefaultPos; aele->mModelPos = _getPos2d(mModelMatrix); - aele->rotation = glm::degrees(_getZRotationRadians(mModelMatrix)); - aele->scale = _getScale2d(mModelMatrix); +// aele->rotation = glm::degrees(_getZRotationRadians(mModelMatrix)); +// aele->scale = _getScale2d(mModelMatrix); } else { aele->mModelPos = glm::vec2(0.f, 0.f); - aele->pos = aDefaultPos; +// aele->pos = aDefaultPos; } } @@ -2628,23 +2718,23 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { _addCssClassFromPath( trect, txml ); if( auto xattr = txml.appendAttribute("x")) { - xattr.set(trect->pos.x); + xattr.set(trect->getPosition().x); } if( auto xattr = txml.appendAttribute("y")) { - xattr.set(trect->pos.y); + xattr.set(trect->getPosition().y); } if( auto xattr = txml.appendAttribute("width")) { - xattr.set(trect->rectangle.getWidth()); + xattr.set(trect->getWidth()); } if( auto xattr = txml.appendAttribute("height")) { - xattr.set(trect->rectangle.getHeight()); + xattr.set(trect->getHeight()); } - if( trect->round > 0.0f ) { + if( trect->getRoundRadius() > 0.0f ) { if( auto xattr = txml.appendAttribute("rx")) { - xattr.set(trect->round); + xattr.set(trect->getRoundRadius()); } if( auto xattr = txml.appendAttribute("ry")) { - xattr.set(trect->round); + xattr.set(trect->getRoundRadius()); } } @@ -2694,10 +2784,10 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { _addCssClassFromPath( tellipse, txml ); if( auto xattr = txml.appendAttribute("cx")) { - xattr.set(tellipse->pos.x); + xattr.set(tellipse->getPosition().x); } if( auto xattr = txml.appendAttribute("cy")) { - xattr.set(tellipse->pos.y); + xattr.set(tellipse->getPosition().y); } if( auto xattr = txml.appendAttribute("rx")) { xattr.set(tellipse->radiusX); @@ -2711,10 +2801,10 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { _addCssClassFromPath( tcircle, txml ); if( auto xattr = txml.appendAttribute("cx")) { - xattr.set(tcircle->pos.x); + xattr.set(tcircle->getPosition().x); } if( auto xattr = txml.appendAttribute("cy")) { - xattr.set(tcircle->pos.y); + xattr.set(tcircle->getPosition().y); } if( auto xattr = txml.appendAttribute("r")) { xattr.set(tcircle->getRadius()); @@ -2800,7 +2890,7 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { } // figure out if we need a transform attribute - if( aele->getType() == OFXSVG_TYPE_IMAGE || aele->rotation != 0.0f || aele->scale.x != 1.0f || aele->scale.y != 1.0f ) { + if( aele->getType() == OFXSVG_TYPE_IMAGE || aele->getRotationDeg() != 0.0f || aele->getScale().x != 1.0f || aele->getScale().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 ae3203b1401..57644cf6468 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -224,7 +224,7 @@ class ofxSvg : public ofxSvgGroup { 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 ); + std::shared_ptr _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 @@ -239,6 +239,8 @@ class ofxSvg : public ofxSvgGroup { void _getTextSpanFromXmlNode( ofXml& anode, std::vector< std::shared_ptr >& aspans ); + void _setNodeParentGroupStack( std::shared_ptr aele ); + ofxSvgGroup* _getPushedGroup(); bool _hasPushedMatrix(); void _applyModelMatrixToElement( std::shared_ptr aele, glm::vec2 aDefaultPos ); diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index 5bc3be7134f..cf6e102bf9e 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -248,6 +248,11 @@ bool ofxSvgCssClass::addProperty( const std::string& aName, const ofColor& acolo return addProperty(aName, prop ); } +//-------------------------------------------------- +bool ofxSvgCssClass::setColor(const ofColor& acolor) { + return addProperty("color", acolor); +} + //-------------------------------------------------- bool ofxSvgCssClass::setFillColor(const ofColor& acolor) { return addProperty("fill", acolor); @@ -388,8 +393,13 @@ float ofxSvgCssClass::getFloatValue(const std::string& akey, const float& adefau //-------------------------------------------------- ofColor ofxSvgCssClass::getColor(const std::string& akey) { + return getColor(akey, ofColor(0)); +} + +//-------------------------------------------------- +ofColor ofxSvgCssClass::getColor(const std::string& akey, const ofColor& adefault) { if( properties.count(akey) < 1 ) { - return ofColor(0); + return adefault; } auto& prop = properties[akey]; if( !prop.cvalue.has_value() ) { diff --git a/addons/ofxSvg/src/ofxSvgCss.h b/addons/ofxSvg/src/ofxSvgCss.h index 71d347ceec2..a3ab8f9b708 100644 --- a/addons/ofxSvg/src/ofxSvgCss.h +++ b/addons/ofxSvg/src/ofxSvgCss.h @@ -85,6 +85,8 @@ class ofxSvgCssClass { bool addProperty( const std::string& aName, const float& avalue ); bool addProperty( const std::string& aName, const ofColor& acolor ); + bool setColor(const ofColor& acolor); + bool setFillColor(const ofColor& acolor); bool setNoFill(); bool isFilled(); @@ -112,6 +114,7 @@ class ofxSvgCssClass { int getIntValue(const std::string& akey, const int& adefault); float getFloatValue(const std::string& akey, const float& adefault); ofColor getColor(const std::string& akey); + ofColor getColor(const std::string& akey, const ofColor& adefault); std::string toString(bool aBPrettyPrint=true); diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index 943bc43ca99..062598697db 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -125,34 +125,34 @@ string ofxSvgElement::toString( int nlevel ) { return tstr; } -//-------------------------------------------------------------- -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::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 )); +//// rmat = rmat * glm::toMat4((const glm::quat&)rq); +// rmat = glm::rotate(rmat, glm::radians(rotation), glm::vec3(0.f, 0.f, 1.f)); +// } +// if( scale.x != 1.0f || scale.y != 1.0f ) { +// rmat = glm::scale(rmat, glm::vec3(scale.x, scale.y, 1.0f)); +// } +// return rmat; +//}; +// +////-------------------------------------------------------------- +//ofNode ofxSvgElement::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 )); -// rmat = rmat * glm::toMat4((const glm::quat&)rq); - rmat = glm::rotate(rmat, glm::radians(rotation), glm::vec3(0.f, 0.f, 1.f)); - } - if( scale.x != 1.0f || scale.y != 1.0f ) { - rmat = glm::scale(rmat, glm::vec3(scale.x, scale.y, 1.0f)); - } - return rmat; -}; - -//-------------------------------------------------------------- -ofNode ofxSvgElement::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; -} +// 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; +//} //-------------------------------------------------------------- void ofxSvgPath::applyStyle(ofxSvgCssClass& aclass) { @@ -221,17 +221,18 @@ void ofxSvgPath::applyStyle(ofxSvgCssClass& aclass) { //-------------------------------------------------------------- void ofxSvgPath::draw() { - ofPushMatrix(); { +// ofPushMatrix(); { + transformGL(); { ofSetColor( ofColor::orange ); -// ofDrawCircle(0, 0, 15); + // ofDrawCircle(0, 0, 15); - ofTranslate(pos.x, pos.y); +// ofTranslate(pos.x, pos.y); ofSetColor( ofColor::green ); -// ofDrawCircle(0, 0, 10); + // ofDrawCircle(0, 0, 10); - if( rotation != 0.0 ) ofRotateZDeg( rotation ); - ofScale( scale.x, scale.y ); +// if( rotation != 0.0 ) ofRotateZDeg( rotation ); +// ofScale( scale.x, scale.y ); if(isVisible()) { ofColor fillColor = getFillColor(); ofColor strokeColor = getStrokeColor(); @@ -254,7 +255,8 @@ void ofxSvgPath::draw() { } } } - } ofPopMatrix(); + } restoreTransformGL(); +// } ofPopMatrix(); } #pragma mark - Image @@ -264,33 +266,43 @@ void ofxSvgPath::draw() { //} //-------------------------------------------------------------- -void ofxSvgImage::draw() { +void ofxSvgImage::load() { if( !bTryLoad ) { if( !getFilePath().empty() ) { img.load( getFilePath() ); bTryLoad = true; } } +} + +//-------------------------------------------------------------- +void ofxSvgImage::draw() { + if( !bTryLoad ) { + load(); + } if( isVisible() ) { if( img.isAllocated() ) { - ofPushMatrix(); { - ofTranslate( pos.x, pos.y ); - if( rotation != 0.0 ) ofRotateZDeg( rotation ); - ofScale( scale.x, scale.y ); +// ofPushMatrix(); { + transformGL(); { +// 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(); + // } ofPopMatrix(); + } restoreTransformGL(); } } } -//-------------------------------------------------------------- -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; -} +////-------------------------------------------------------------- +//glm::vec2 ofxSvgImage::getAnchorPointForPercent( float ax, float ay ) { +// glm::vec2 ap = glm::vec2( width * ax * getScale().x, height * ay * getScale().y ); +//// ap = glm::rotate(ap, glm::radians(rotation)); +// ap = glm::rotate(ap, atan2f(getSideDir().y, getSideDir().x)); +// return ap; +//} #pragma mark - Text @@ -401,7 +413,7 @@ void ofxSvgText::setText( const std::string& astring, std::string aFontFamily, i ofxSvgCssClass css; css.addProperty("font-family", aFontFamily); css.addProperty("font-size", aFontSize); - css.addProperty("color", getFillColor() ); + css.addProperty("color", getColor() ); if (spanString.find(" tag.\n"; @@ -691,7 +703,7 @@ void ofxSvgText::create() { tsIndices[k] = tsIndices[k] + offsetIndex; } - ofFloatColor tcolor = cspan->color; + ofFloatColor tcolor = cspan->getColor(); vector< ofFloatColor > tcolors; tcolors.assign( stringMesh.getVertices().size(), tcolor ); @@ -706,11 +718,16 @@ void ofxSvgText::create() { // 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->mBTextDirty = false; + tempSpan->fontRect = tfont.getStringBoundingBox( tempSpan->text, 0, 0 ); +// ofLogNotice("ofxSvgText::create") << getCleanName() << "-Updating text span with text: " << tempSpan->text << " width: " << tempSpan->fontRect.getWidth() << " | " << ofGetFrameNum(); + // rect is used for drawing the font. tempSpan->fontRect is used for calculating bounding box. + tempSpan->rect.width = tempSpan->fontRect.width; + tempSpan->rect.height = tempSpan->fontRect.height; +// ofRectangle() // tempSpan->lineHeight = tfont.getStringBoundingBox("M", 0, 0).height; tempSpan->lineHeight = tfont.getLineHeight(); // tempSpan.rect.x = tempSpan.rect.x - ogPos.x; @@ -723,67 +740,63 @@ void ofxSvgText::create() { //-------------------------------------------------------------- void ofxSvgText::draw() { if( !isVisible() ) return; -// map< string, map > meshes; - if(bUseShapeColor) { + + if(bUseShapeColor) { ofSetColor( 255, 255, 255, 255.f * alpha ); } std::map< string, std::map >::iterator mainIt; + + if(areTextSpansDirty()) { + // sets textSpan->mBTextDirty to false; + create(); + } - ofPushMatrix(); { - - 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 fontKey = mainIt->first; + transformGL(); { + ofTexture* tex = NULL; + for( mainIt = meshes.begin(); mainIt != meshes.end(); ++mainIt ) { + string fontKey = mainIt->first; std::map< int, ofMesh >::iterator mIt; - for( mIt = meshes[ fontKey ].begin(); mIt != meshes[ fontKey ].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( fontKey ) ) { + for( mIt = meshes[ fontKey ].begin(); mIt != meshes[ fontKey ].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; + // if( fonts.count( fontKey ) ) { if( ofxSvgFontBook::hasBookFont(fontKey)) { auto& fbook = ofxSvgFontBook::getBookFont(fontKey); - if( fbook.textures.count( fontSize ) ) { - bHasTexture = true; - tex = &fbook.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(); - + if( fbook.textures.count( fontSize ) ) { + bHasTexture = true; + tex = &fbook.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(); + } + } + } restoreTransformGL(); } //-------------------------------------------------------------- void ofxSvgText::draw(const std::string &astring, bool abCentered ) { if( textSpans.size() > 0 ) { - ofPushMatrix(); { - ofTranslate( pos.x, pos.y ); - if( rotation > 0 ) ofRotateZDeg( rotation ); + transformGL(); { textSpans[0]->draw(astring, abCentered ); - } ofPopMatrix(); + } restoreTransformGL(); } else { ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; } @@ -792,11 +805,9 @@ void ofxSvgText::draw(const std::string &astring, bool abCentered ) { //-------------------------------------------------------------- void ofxSvgText::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 ); + transformGL(); { textSpans[0]->draw(astring, acolor, abCentered ); - } ofPopMatrix(); + } restoreTransformGL(); } else { ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; } @@ -806,39 +817,21 @@ void ofxSvgText::draw(const std::string& astring, const ofColor& acolor, bool ab void ofxSvgText::TextSpan::applyStyle(ofxSvgCssClass& aclass) { mSvgCssClass = aclass; -// fontFamily = aclass.getValue("font-family", "Arial"); -// fontFamily = mSvgCssClass.getFontFamily("Arial"); -// fontSize = aclass.getIntValue("font-size", 18 ); -// fontSize = mSvgCssClass.getFontSize(18); - color = ofColor(0); - if( aclass.hasProperty("color")) { - color = aclass.getColor("color"); - } else if( aclass.hasProperty("fill")) { - color = aclass.getColor("fill"); + if( !aclass.hasProperty("color") ) { + if( aclass.hasProperty("fill")) { + aclass.addProperty("color", aclass.getColor("fill") ); + } else { + aclass.addProperty("color", ofColor(0)); + } } + alpha = 1.f; if( aclass.hasProperty("opacity")) { - color.a *= aclass.getFloatValue("opacity", 1.f); + alpha = aclass.getFloatValue("opacity", 1.f); } - -// bold = false; -// if( ofIsStringInString(aclass.getValue("font-weight", "" ), "bold")) { -// bold = true; -// } - -// bold = mSvgCssClass.isFontBold(); - -// italic = false; -// if( ofIsStringInString(aclass.getValue("font-style", "" ), "italic")) { -// italic = true; -// } - -// italic = mSvgCssClass.isFontItalic(); - - ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << "text: " << text; - ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << " css class: " << aclass.toString() << std::endl << " color: " << color; + ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << " css class: " << aclass.toString() << std::endl;// << " color: " << color; } @@ -850,7 +843,7 @@ ofTrueTypeFont& ofxSvgText::TextSpan::getFont() { //-------------------------------------------------------------- void ofxSvgText::TextSpan::draw(const std::string &astring, bool abCentered ) { - draw( astring, color, abCentered ); + draw( astring, getColor(), abCentered ); } //-------------------------------------------------------------- @@ -873,25 +866,28 @@ ofTrueTypeFont& ofxSvgText::getFont() { return ofxSvgFontBook::defaultFont; } -//-------------------------------------------------------------- -ofColor ofxSvgText::getColor() { - if( textSpans.size() > 0 ) { - return textSpans[0]->color; - } - ofLogWarning("ofxSvgText") << __FUNCTION__ << " : no font detected from text spans, returning path fill color."; - return path.getFillColor(); -} +////-------------------------------------------------------------- +//ofColor ofxSvgText::getColor() { +// if( textSpans.size() > 0 ) { +// return textSpans[0]->getColor(); +// } +// ofLogWarning("ofxSvgText") << __FUNCTION__ << " : no font detected from text spans, returning black."; +// return ofColor(0,255); +//} -// get the bounding rect for all of the text spans in this svg'ness +// get the bounding rect for all of the text spans in this text element // should be called after create // //-------------------------------------------------------------- ofRectangle ofxSvgText::getBoundingBox() { + if(areTextSpansDirty()) { + create(); + } + 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; + ofRectangle trect = textSpans[i]->fontRect; + trect.x += textSpans[i]->rect.x; + trect.y += textSpans[i]->rect.y; if( i == 0 ) { temp = trect; } else { @@ -899,11 +895,21 @@ ofRectangle ofxSvgText::getBoundingBox() { } } - temp.x += pos.x; - temp.y += pos.y; + // we want a local rect return temp; } +//-------------------------------------------------------------- +bool ofxSvgText::areTextSpansDirty() { + bool bDirty = false; + for( auto& tspan : textSpans ) { + if( tspan->mBTextDirty ) { + bDirty=true; + break; + } + } + return bDirty; +} diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index 7c3b6c69e51..681ddfd5a2d 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -20,7 +20,8 @@ enum ofxSvgType { OFXSVG_TYPE_TOTAL }; -class ofxSvgElement { +class ofxSvgElement : public ofNode { + // adding some friend classes so we can set protected properties from other classes. friend class ofxSvg; friend class ofxSvgGroup; public: @@ -38,6 +39,20 @@ class ofxSvgElement { return (getType() == OFXSVG_TYPE_GROUP); } + void setRotationDeg( float adegrees ) { + setRotationRad(glm::radians(adegrees)); + } + void setRotationRad( float aradians ) { + setOrientation(glm::angleAxis(aradians, glm::vec3(0.f, 0.f, 1.f) )); + } + + float getRotationDeg() { + return getRollDeg(); + } + float getRotationRad() { + return getRollRad(); + } + // override this in ofxSvgGroup virtual void setVisible( bool ab ) { bVisible = ab; } bool isVisible() { return bVisible; } @@ -49,18 +64,13 @@ class ofxSvgElement { return alpha; } + /// \brief Output a string description + /// \param nlevel (optional) is the indentation amount. + /// \return string with type and name. virtual std::string toString(int nlevel = 0); float layer = -1.f; bool bVisible=true; - bool bUseShapeColor = true; - - 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() {} @@ -69,16 +79,48 @@ class ofxSvgElement { virtual void setUseShapeColor( bool ab ) { bUseShapeColor = ab; } + bool isUsingShapeColor() { + return bUseShapeColor; + } - /// \brief Get the bounding box of the element , taking into account - /// all the points and rotation to determine the extents of the element aligned with x and y axes. - virtual ofRectangle getBoundingBox() { return ofRectangle( pos.x, pos.y, 1, 1 ); }; + /// \brief Get the bounding box of the element without transforms applied. + virtual ofRectangle getBoundingBox() { return ofRectangle( 0.0f, 0.0f, 1.f, 1.f ); }; + + /// \brief Get the axis aligned bounding box of the element, taking into account + /// global position, rotation and scale to determine the extents of the element aligned with x and y axes. + virtual ofRectangle getGlobalBoundingBox() { + auto localRect = getBoundingBox(); + std::vector rverts = { localRect.getTopLeft(), localRect.getTopRight(), localRect.getBottomRight(), localRect.getBottomLeft()}; + auto gtrans = getGlobalTransformMatrix(); + + for( auto& rv : rverts ) { + rv = glm::vec3(gtrans * glm::vec4(rv, 1.f)); + } + return _calculateExtents( rverts ); + }; virtual ofPolyline getFirstPolyline() { ofLogWarning("ofxSvgElement") << __FUNCTION__ << " : Element " << getTypeAsString() << " does not have a path."; return ofPolyline(); } protected: + ofRectangle _calculateExtents( const std::vector& averts ) { + glm::vec2 min = {std::numeric_limits::max(), std::numeric_limits::max()}; + glm::vec2 max = {std::numeric_limits::min(), std::numeric_limits::min()}; + + for( auto& rv : averts ) { + min.x = std::min( min.x, rv.x ); + min.y = std::min( min.y, rv.y ); + + max.x = std::max( max.x, rv.x ); + max.y = std::max( max.y, rv.y ); + } + + return ofRectangle( min.x, min.y, max.x-min.x, max.y-min.y ); + } + + bool bUseShapeColor = true; + float alpha = 1.f; std::string name = ""; // used for saving to set the model position of the current mat4 // @@ -91,6 +133,7 @@ class ofxSvgElement { }; class ofxSvgPath : public ofxSvgElement { + friend class ofxSvg; public: virtual ofxSvgType getType() override {return OFXSVG_TYPE_PATH;} @@ -127,64 +170,110 @@ class ofxSvgPath : public ofxSvgElement { return ofPolyline(); } - /// \brief Get the bounding box of the path, taking into account - /// all the points; the points are global, not relative to pos and don't have rotation. + /// \brief Get the local bounding box of the path, no transforms applied. virtual ofRectangle getBoundingBox() override { - ofRectangle brect; - if( path.getOutline().size() > 0 ) { - bool bFirst = true; + if(mBBoxNeedsRecalc) { + mBBoxNeedsRecalc = false; +// ofRectangle brect; const auto& outlines = path.getOutline(); - for( auto& outline : outlines ) { - if( bFirst ) { - brect = outline.getBoundingBox(); - } else { - brect.growToInclude( outline.getBoundingBox() ); + if( outlines.size() > 0 ) { + bool bFirst = true; + for( auto& outline : outlines ) { + if( bFirst ) { + bFirst = false; + mBounds = outline.getBoundingBox(); + } else { + mBounds.growToInclude( outline.getBoundingBox() ); + } } } } - return brect; + return mBounds; }; - ofPath path; + ofPath& getPath() { + mBBoxNeedsRecalc = true; + return path; + }; protected: + ofPath path; + bool mBBoxNeedsRecalc = true; + ofRectangle mBounds; + virtual std::shared_ptr clone() override { return std::make_shared(*this); }; }; class ofxSvgRectangle : public ofxSvgPath { + friend class ofxSvg; public: virtual ofxSvgType getType() override {return OFXSVG_TYPE_RECTANGLE;} - ofRectangle rectangle; - float getWidth() { return rectangle.getWidth() * scale.x;} - float getHeight() { return rectangle.getHeight() * scale.y;} + float getWidth() { return width;} + float getHeight() { return height;} + + float getGlobalWidth() { return width * getGlobalScale().x;} + float getGlobalHeight() { return height * getGlobalScale().y;} + + /// \brief Get the local bounding box of the rectangle, no transforms applied. + virtual ofRectangle getBoundingBox() override { + return ofRectangle(0.f, 0.f, getWidth(), getHeight()); + }; + + void setRoundRadius( float aRoundAmount ) { + if( aRoundAmount != roundRadius ) { + roundRadius = aRoundAmount; + path.clear(); + if( roundRadius > 0.0f ) { + path.rectRounded(0.f, 0.f, width, height, roundRadius); + } else { + path.rectangle(0.f, 0.f, width, height ); + } + } + } - float round = 0.f; + float getRoundRadius() { + return roundRadius; + } protected: + float roundRadius = 0.f; + float width = 0.f; + float height = 0.f; + virtual std::shared_ptr clone() override { return std::make_shared(*this); }; }; class ofxSvgImage : public ofxSvgElement { + friend class ofxSvg; public: virtual ofxSvgType getType() override {return OFXSVG_TYPE_IMAGE;} - float getWidth() const { return width * scale.x;} - float getHeight() const { return height * scale.y;} + float getWidth() const { return width;} + float getHeight() const { return height;} - /// \brief Get the bounding box of the image. + float getGlobalWidth() { return width * getGlobalScale().x;} + float getGlobalHeight() { return height * getGlobalScale().y;} + + /// \brief Get the local bounding box of the image, no transforms applied. virtual ofRectangle getBoundingBox() override { - return ofRectangle(pos.x, pos.y, getWidth(), getHeight()); + return ofRectangle(0.f, 0.f, getWidth(), getHeight()); }; + void load(); + bool isLoaded() { + return (img.isAllocated() && img.getWidth() > 0 && img.getHeight() > 0); + } + virtual void draw() override; - glm::vec2 getAnchorPointForPercent( float ax, float ay ); +// glm::vec2 getAnchorPointForPercent( float ax, float ay ); - std::filesystem::path getFilePath() { return filepath; } + const std::filesystem::path& getFilePath() { return filepath; } + ofImage& getImage() { return img; } void setColor( ofColor aColor ) { color = aColor; @@ -193,14 +282,14 @@ class ofxSvgImage : public ofxSvgElement { return ofColor( color.r, color.g, color.b, alpha * (float)color.a ); } +protected: + of::filesystem::path filepath; + ofColor color; ofImage img; - of::filesystem::path filepath; float width = 0.f; float height = 0.f; - -protected: bool bTryLoad = false; virtual std::shared_ptr clone() override { @@ -210,41 +299,66 @@ class ofxSvgImage : public ofxSvgElement { }; class ofxSvgCircle : public ofxSvgPath { + friend class ofxSvg; public: virtual ofxSvgType getType() override {return OFXSVG_TYPE_CIRCLE;} float getRadius() {return radius;} - float radius = 10.0; + float getGlobalRadius() {return radius * getGlobalScale().x;} + + /// \brief Get the local bounding box of the circle, no transforms applied. + virtual ofRectangle getBoundingBox() override { + return ofRectangle(-getRadius(), -getRadius(), getRadius()*2.f, getRadius()*2.f ); + }; + + void setRadius( float aradius ) { + if( aradius != radius ) { + radius = aradius; + path.clear(); + path.circle(0, 0, radius); + } + } protected: + float radius = 10.0; + virtual std::shared_ptr clone() override { return std::make_shared(*this); }; }; class ofxSvgEllipse : public ofxSvgPath { + friend class ofxSvg; public: virtual ofxSvgType getType() override {return OFXSVG_TYPE_ELLIPSE;} - float radiusX, radiusY = 10.0f; + float getRadiusX() {return radiusX;} + float getRadiusY() {return radiusY;} + + float getGlobalRadiusX() {return radiusX * getGlobalScale().x;} + float getGlobalRadiusY() {return radiusY * getGlobalScale().x;} + + /// \brief Get the local bounding box of the circle, no transforms applied. + virtual ofRectangle getBoundingBox() override { + return ofRectangle(-getRadiusX(), -getRadiusY(), getRadiusX()*2.f, getRadiusY()*2.f ); + }; protected: + float radiusX, radiusY = 10.0f; + virtual std::shared_ptr clone() override { return std::make_shared(*this); }; }; -class ofxSvgText : public ofxSvgRectangle { +class ofxSvgText : public ofxSvgElement { + friend class ofxSvg; public: - class TextSpan { + friend class ofxSvgText; public: TextSpan() { text = ""; -// fontSize = 12; lineHeight = 0; -// mSvgCssClass.setFontSize(fontSize); -// fontFamily = "Arial"; -// mSvgCssClass.setFontFamily(fontFamily); } std::string getFontKey() { @@ -265,6 +379,16 @@ class ofxSvgText : public ofxSvgRectangle { mSvgCssClass.setFontSize(asize); } + ofColor getColor() { + auto tcolor = mSvgCssClass.getColor("color"); + tcolor.a *= alpha; + return tcolor; + } + + void setColor( const ofColor& acolor ) { + mSvgCssClass.setColor(acolor); + } + bool isBold() { return mSvgCssClass.isFontBold(); } @@ -287,12 +411,18 @@ class ofxSvgText : public ofxSvgRectangle { } } - std::string text; -// int fontSize = 12; -// std::string fontFamily; + std::string getText() { + return text; + } + + void setText( const std::string& atext ) { + mBTextDirty = true; + text = atext; + } + ofRectangle rect; - ofColor color; float lineHeight = 0; + float alpha = 1.f; void applyStyle(ofxSvgCssClass& aclass); ofxSvgCssClass& getCss() { return mSvgCssClass; } @@ -302,7 +432,10 @@ class ofxSvgText : public ofxSvgRectangle { void draw(const std::string &astring, const ofColor& acolor, bool abCentered ); protected: + ofRectangle fontRect; + std::string text; ofxSvgCssClass mSvgCssClass; + bool mBTextDirty = true; }; @@ -310,17 +443,19 @@ class ofxSvgText : public ofxSvgRectangle { // Deep-copy constructor ofxSvgText(const ofxSvgText& other) { - pos = other.pos; - scale = other.scale; - rotation = other.rotation; + setPosition(other.getPosition()); + setOrientation(other.getOrientationQuat()); + setScale(other.getScale()); layer = other.layer; bVisible = other.bVisible; + alpha = other.alpha; fdirectory = other.fdirectory; bCentered = other.bCentered; - _overrideColor = other._overrideColor; - bOverrideColor = other.bOverrideColor; +// _overrideColor = other._overrideColor; +// bOverrideColor = other.bOverrideColor; + mSvgCssClass = other.mSvgCssClass; textSpans.reserve(other.textSpans.size()); for (const auto& ptr : other.textSpans) { @@ -334,17 +469,20 @@ class ofxSvgText : public ofxSvgRectangle { // Deep-copy assignment ofxSvgText& operator=(const ofxSvgText& other) { if (this != &other) { - pos = other.pos; - scale = other.scale; - rotation = other.rotation; + setPosition(other.getPosition()); + setOrientation(other.getOrientationQuat()); + setScale(other.getScale()); layer = other.layer; bVisible = other.bVisible; + alpha = other.alpha; + name = other.name; fdirectory = other.fdirectory; bCentered = other.bCentered; - _overrideColor = other._overrideColor; - bOverrideColor = other.bOverrideColor; +// _overrideColor = other._overrideColor; +// bOverrideColor = other.bOverrideColor; + mSvgCssClass = other.mSvgCssClass; textSpans.clear(); textSpans.reserve(other.textSpans.size()); @@ -362,7 +500,7 @@ class ofxSvgText : public ofxSvgRectangle { virtual ofxSvgType getType() override {return OFXSVG_TYPE_TEXT;} ofTrueTypeFont& getFont(); - ofColor getColor(); +// ofColor getColor(); void setText( const std::string& astring, std::string aFontFamily, int aFontSize, float aMaxWidth ); void create(); @@ -373,32 +511,48 @@ class ofxSvgText : public ofxSvgRectangle { void setFontDirectory( std::string aPath ) { fdirectory = aPath; } + of::filesystem::path getFontDirectory() { + return fdirectory; + } std::string getText() { std::string ttext; for( auto& tspan : textSpans ) { - ttext += tspan->text; + ttext += tspan->getText(); } return ttext; } - void layoutTextSpans(); +// void overrideColor( ofColor aColor ) { +// bOverrideColor = true; +// _overrideColor = aColor; +// } - void overrideColor( ofColor aColor ) { - bOverrideColor = true; - _overrideColor = aColor; + void setColor( ofColor acolor ) { + mSvgCssClass.setColor(acolor); } -/// \brief Get the bounding box of all of the text spans. + ofColor getColor() { + auto tcolor = mSvgCssClass.getColor("color", ofColor(255)); + tcolor.a *= alpha; + return tcolor; + } + +/// \brief Get the local bounding box of all of the text spans. virtual ofRectangle getBoundingBox() override; std::map< std::string, std::map > meshes; std::vector< std::shared_ptr > textSpans; - std::string fdirectory; + bool areTextSpansDirty(); + bool bCentered = false; protected: + of::filesystem::path fdirectory; + + ofxSvgCssClass mSvgCssClass; + virtual std::shared_ptr clone() override { auto newEle = std::make_shared(*this); @@ -424,8 +578,8 @@ class ofxSvgText : public ofxSvgRectangle { bool endsWithLineEnding(const std::string& astr); std::vector splitWordsAndLineEndings(const std::string& input); - ofFloatColor _overrideColor; - bool bOverrideColor = false; +// ofFloatColor _overrideColor; +// bool bOverrideColor = false; }; diff --git a/addons/ofxSvg/src/ofxSvgGroup.cpp b/addons/ofxSvg/src/ofxSvgGroup.cpp index 5678338ce7a..d8825664e3b 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.cpp +++ b/addons/ofxSvg/src/ofxSvgGroup.cpp @@ -9,19 +9,9 @@ using std::string; void ofxSvgGroup::draw() { if( !isVisible() ) return; std::size_t numElements = mChildren.size(); - bool bTrans = (pos.x != 0 || pos.y != 0.0 || rotation != 0.f || scale.x != 0.f || scale.y != 0.f); - if( bTrans ) { - ofPushMatrix(); - ofTranslate(pos.x, pos.y); - if( rotation != 0.0 ) ofRotateZDeg( rotation ); - ofScale( scale.x, scale.y ); - } - for( std::size_t i = 0; i < numElements; i++ ) { + for( std::size_t i = 0; i < numElements; i++ ) { mChildren[i]->draw(); - } - if( bTrans ) { - ofPopMatrix(); - } + } } //-------------------------------------------------------------- @@ -192,8 +182,7 @@ void ofxSvgGroup::_getElementForNameRecursive( vector& aNamesToFind, sha 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] )) { + if(ofIsStringInString( etext->textSpans.front()->getText(), aNamesToFind[0] )) { bFound = true; } } diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h index f73cf9a3158..cc685234be7 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.h +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -12,10 +12,21 @@ class ofxSvgGroup : public ofxSvgElement { ofxSvgGroup(const ofxSvgGroup& other) { ofLogVerbose("ofxSvgGroup") << "ofxSvgGroup(const ofxSvgGroup& other)"; mChildren.reserve(other.mChildren.size()); + + setPosition(other.getPosition()); + setOrientation(other.getOrientationQuat()); + setScale(other.getScale()); + + bVisible = other.bVisible; + alpha = other.alpha; + layer = other.layer; + name = other.name; for (const auto& ptr : other.mChildren) { // Create a new shared_ptr to a new item copy. if( ptr ) { - mChildren.push_back(ptr->clone()); + auto newKid = ptr->clone(); + newKid->setParent(*this); + mChildren.push_back(newKid); } } } @@ -24,12 +35,23 @@ class ofxSvgGroup : public ofxSvgElement { ofxSvgGroup& operator=(const ofxSvgGroup& other) { if (this != &other) { ofLogVerbose("ofxSvgGroup") << "operator=(const ofxSvgGroup& other)"; + + setPosition(other.getPosition()); + setOrientation(other.getOrientationQuat()); + setScale(other.getScale()); + + bVisible = other.bVisible; + alpha = other.alpha; + layer = other.layer; + name = other.name; mChildren.clear(); mChildren.reserve(other.mChildren.size()); for (const auto& ptr : other.mChildren) { // Create a new shared_ptr to a new item copy. if( ptr ) { - mChildren.push_back(ptr->clone()); + auto newKid = ptr->clone(); + newKid->setParent(*this); + mChildren.push_back(newKid); } } } @@ -341,7 +363,9 @@ class ofxSvgGroup : public ofxSvgElement { newEle->mChildren.reserve(mChildren.size()); for( const auto& ptr : mChildren ) { if( ptr ) { - newEle->mChildren.push_back(ptr->clone()); + auto newKid = ptr->clone(); + newKid->setParent(*this); + newEle->mChildren.push_back(newKid); } } return newEle; From f7dc8a86be6896cde328a144771fd97af5faacc5 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Fri, 22 Aug 2025 17:39:03 -0400 Subject: [PATCH 12/21] revert to string for filepath due to msys error. Override customDraw ofNode function. --- addons/ofxSvg/src/ofxSvg.cpp | 2 +- addons/ofxSvg/src/ofxSvgElements.cpp | 18 +++++++++--------- addons/ofxSvg/src/ofxSvgElements.h | 15 ++++++++------- addons/ofxSvg/src/ofxSvgFontBook.cpp | 2 +- addons/ofxSvg/src/ofxSvgGroup.cpp | 3 ++- addons/ofxSvg/src/ofxSvgGroup.h | 5 +++-- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 26f286b984f..d1deb31d869 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -2016,7 +2016,7 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< // aele->pos.x = pos3.x; // aele->pos.y = pos3.y; - ofLogNotice("ofxSvg::setTransformFromSvgMatrixString") << "pos: " << aele->getPosition() << " rotation: " << trotation << " scale: " << aele->getScale(); + ofLogVerbose("ofxSvg::setTransformFromSvgMatrixString") << "pos: " << aele->getPosition() << " rotation: " << trotation << " scale: " << aele->getScale(); // apos.x = matrixF[4]; // apos.y = matrixF[5]; diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index 062598697db..e134544ec7b 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -220,9 +220,9 @@ void ofxSvgPath::applyStyle(ofxSvgCssClass& aclass) { } //-------------------------------------------------------------- -void ofxSvgPath::draw() { +void ofxSvgPath::customDraw() { // ofPushMatrix(); { - transformGL(); { +// transformGL(); { ofSetColor( ofColor::orange ); // ofDrawCircle(0, 0, 15); @@ -255,7 +255,7 @@ void ofxSvgPath::draw() { } } } - } restoreTransformGL(); +// } restoreTransformGL(); // } ofPopMatrix(); } @@ -276,7 +276,7 @@ void ofxSvgImage::load() { } //-------------------------------------------------------------- -void ofxSvgImage::draw() { +void ofxSvgImage::customDraw() { if( !bTryLoad ) { load(); } @@ -284,14 +284,14 @@ void ofxSvgImage::draw() { if( isVisible() ) { if( img.isAllocated() ) { // ofPushMatrix(); { - transformGL(); { +// transformGL(); { // 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(); - } restoreTransformGL(); +// } restoreTransformGL(); } } } @@ -738,7 +738,7 @@ void ofxSvgText::create() { } //-------------------------------------------------------------- -void ofxSvgText::draw() { +void ofxSvgText::customDraw() { if( !isVisible() ) return; if(bUseShapeColor) { @@ -751,7 +751,7 @@ void ofxSvgText::draw() { create(); } - transformGL(); { +// transformGL(); { ofTexture* tex = NULL; for( mainIt = meshes.begin(); mainIt != meshes.end(); ++mainIt ) { string fontKey = mainIt->first; @@ -788,7 +788,7 @@ void ofxSvgText::draw() { tMeshMesh.enableColors(); } } - } restoreTransformGL(); +// } restoreTransformGL(); } //-------------------------------------------------------------- diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index 681ddfd5a2d..de03be08bde 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -55,7 +55,7 @@ class ofxSvgElement : public ofNode { // override this in ofxSvgGroup virtual void setVisible( bool ab ) { bVisible = ab; } - bool isVisible() { return bVisible; } + bool isVisible() const { return bVisible; } virtual void setAlpha( float aAlpha ) { alpha = aAlpha; @@ -72,7 +72,8 @@ class ofxSvgElement : public ofNode { float layer = -1.f; bool bVisible=true; - virtual void draw() {} + /// \brief Override the function in ofNode meant for drawing + virtual void customDraw() override {}; virtual void applyStyle(ofxSvgCssClass& aclass) {}; @@ -144,7 +145,7 @@ class ofxSvgPath : public ofxSvgElement { virtual void applyStyle(ofxSvgCssClass& aclass) override; - virtual void draw() override; + virtual void customDraw() override; bool isFilled() { return path.isFilled(); } ofColor getFillColor() { return path.getFillColor(); } @@ -269,7 +270,7 @@ class ofxSvgImage : public ofxSvgElement { return (img.isAllocated() && img.getWidth() > 0 && img.getHeight() > 0); } - virtual void draw() override; + virtual void customDraw() override; // glm::vec2 getAnchorPointForPercent( float ax, float ay ); const std::filesystem::path& getFilePath() { return filepath; } @@ -504,14 +505,14 @@ class ofxSvgText : public ofxSvgElement { void setText( const std::string& astring, std::string aFontFamily, int aFontSize, float aMaxWidth ); void create(); - void draw() override; + void customDraw() 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; } - of::filesystem::path getFontDirectory() { + std::string getFontDirectory() { return fdirectory; } @@ -549,7 +550,7 @@ class ofxSvgText : public ofxSvgElement { bool bCentered = false; protected: - of::filesystem::path fdirectory; + std::string fdirectory; ofxSvgCssClass mSvgCssClass; diff --git a/addons/ofxSvg/src/ofxSvgFontBook.cpp b/addons/ofxSvg/src/ofxSvgFontBook.cpp index 760066fef19..dead2e23f4e 100644 --- a/addons/ofxSvg/src/ofxSvgFontBook.cpp +++ b/addons/ofxSvg/src/ofxSvgFontBook.cpp @@ -34,7 +34,7 @@ bool ofxSvgFontBook::loadFont( const std::string& aDirectory, const std::string& bool bHasFontDirectory = false; // cout << "checking directory: " << fdirectory+"/fonts/" << endl; std::string fontsDirectory = "";// = ofToDataPath("", true); - if( aDirectory != "" ) { + if( !aDirectory.empty() ) { fontsDirectory = aDirectory; } diff --git a/addons/ofxSvg/src/ofxSvgGroup.cpp b/addons/ofxSvg/src/ofxSvgGroup.cpp index d8825664e3b..177b35014bf 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.cpp +++ b/addons/ofxSvg/src/ofxSvgGroup.cpp @@ -6,8 +6,9 @@ using std::shared_ptr; using std::string; //-------------------------------------------------------------- -void ofxSvgGroup::draw() { +void ofxSvgGroup::draw() const { if( !isVisible() ) return; + std::size_t numElements = mChildren.size(); for( std::size_t i = 0; i < numElements; i++ ) { mChildren[i]->draw(); diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h index cc685234be7..b03c0e7375f 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.h +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -58,8 +58,9 @@ class ofxSvgGroup : public ofxSvgElement { return *this; } - - virtual void draw() override; + // we need to override this so that we can draw the children correctly + // without this transform getting applied to the children + virtual void draw() const override; /// \brief Set the visibility of the group. Does not set visibility of each child. The group only draws its children if the group is visible. /// \param bool aBVisible set to true for visible. From f128108d874560715c74fc4ed8101fe5d9451037 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Mon, 25 Aug 2025 10:13:20 -0400 Subject: [PATCH 13/21] added offset for paths, for importing and exporting. --- addons/ofxSvg/src/ofxSvg.cpp | 186 ++++++++++++++++++++------- addons/ofxSvg/src/ofxSvg.h | 1 + addons/ofxSvg/src/ofxSvgElements.cpp | 50 ++++--- addons/ofxSvg/src/ofxSvgElements.h | 168 +++++++++++++++--------- addons/ofxSvg/src/ofxSvgGroup.h | 6 +- 5 files changed, 282 insertions(+), 129 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index d1deb31d869..921530a90fb 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -586,6 +586,12 @@ void ofxSvg::_parseXmlNode( ofXml& aParentNode, vector< shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< shared_ptr >& aElements ) { shared_ptr telement; + bool bHasMatOrTrans = false; + + if(auto transAttr = tnode.getAttribute("transform") ) { + bHasMatOrTrans = true; + } + if( tnode.getName() == "use") { if( auto hrefAtt = tnode.getAttribute("xlink:href")) { ofLogVerbose("ofxSvg") << "found a use node with href " << hrefAtt.getValue(); @@ -635,24 +641,33 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< } else if( tnode.getName() == "ellipse" ) { auto ellipse = std::make_shared(); - auto cxAttr = tnode.getAttribute("cx"); auto tpos = glm::vec2(0.f, 0.f); + auto cxAttr = tnode.getAttribute("cx"); if(cxAttr) {tpos.x = cxAttr.getFloatValue();} auto cyAttr = tnode.getAttribute("cy"); if(cyAttr) {tpos.y = cyAttr.getFloatValue();} - ellipse->setPosition( tpos.x, tpos.y, 0.0f); + if( bHasMatOrTrans ) { + ellipse->setOffsetPathPosition(tpos.x,tpos.y); + } else { + ellipse->setPosition( tpos.x, tpos.y, 0.0f); + } + + glm::vec2 radii(0.f, 0.f); - auto rxAttr = tnode.getAttribute( "rx" ); - if(rxAttr) ellipse->radiusX = rxAttr.getFloatValue(); - auto ryAttr = tnode.getAttribute( "ry" ); - if(ryAttr) ellipse->radiusY = ryAttr.getFloatValue(); + if(auto rxAttr = tnode.getAttribute( "rx" )) { + radii.x = rxAttr.getFloatValue(); + } + if(auto ryAttr = tnode.getAttribute( "ry" )) { + radii.y = 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 ); +// ellipse->path.ellipse({0.f,0.f}, ellipse->radiusX * 2.0f, ellipse->radiusY * 2.0f ); + ellipse->setRadius(radii.x, radii.y); _applyStyleToPath( tnode, ellipse ); @@ -660,22 +675,28 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< } else if( tnode.getName() == "circle" ) { auto circle = std::make_shared(); auto tpos = glm::vec2(0.f, 0.f); + if(auto cxAttr = tnode.getAttribute("cx")) { tpos.x = cxAttr.getFloatValue(); } if(auto cyAttr = tnode.getAttribute("cy")) { tpos.y = cyAttr.getFloatValue(); } - circle->setPosition(tpos.x, tpos.y, 0.f); - auto rAttr = tnode.getAttribute( "r" ); - if(rAttr) circle->radius = rAttr.getFloatValue(); + if( bHasMatOrTrans ) { + circle->setOffsetPathPosition(tpos.x,tpos.y); + } else { + circle->setPosition(tpos.x, tpos.y, 0.f); + } // 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 ); +// circle->path.circle({0.f,0.f}, circle->radius ); + if(auto rAttr = tnode.getAttribute( "r" )) { + circle->setRadius(rAttr.getFloatValue()); + } _applyStyleToPath( tnode, circle ); @@ -720,18 +741,23 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< auto tpos = glm::vec2(0.f, 0.f); if(auto xattr = tnode.getAttribute("x")) { - tpos.x = xattr.getFloatValue(); + tpos.x = xattr.getFloatValue(); } if(auto yattr = tnode.getAttribute("y")) { - tpos.y = yattr.getFloatValue(); + tpos.y = yattr.getFloatValue(); } if(auto wattr = tnode.getAttribute("width")) { - rect->width = wattr.getFloatValue(); + rect->width = wattr.getFloatValue(); } if(auto hattr = tnode.getAttribute("height")) { - rect->height = hattr.getFloatValue(); + rect->height = hattr.getFloatValue(); + } + + if( bHasMatOrTrans ) { + rect->setOffsetPathPosition(tpos.x,tpos.y); + } else { + rect->setPosition(tpos.x, tpos.y, 0.0f); } - rect->setPosition(tpos.x, tpos.y, 0.0f); auto rxAttr = tnode.getAttribute("rx"); auto ryAttr = tnode.getAttribute("ry"); @@ -739,20 +765,30 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< rect->path.setCircleResolution(mCircleResolution); rect->path.setCurveResolution(mCurveResolution); - // make local so we can apply transform later in the function + float rRadius = 0.0f; + if( !ofxSvgCssClass::sIsNone(rxAttr.getValue()) || !ofxSvgCssClass::sIsNone(ryAttr.getValue())) { - rect->roundRadius = -1.f; // force an update in setRoundRadius - rect->setRoundRadius(std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), - ofxSvgCssClass::sGetFloat(ryAttr.getValue())) - ); -// rect->roundRadius = std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), -// ofxSvgCssClass::sGetFloat(ryAttr.getValue())); - -// rect->path.rectRounded(0.f, 0.f, rect->width, rect->height, rect->roundRadius); - - } else { - rect->path.rectangle(0.f, 0.f, rect->getWidth(), rect->getHeight()); - } + rRadius = std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), + ofxSvgCssClass::sGetFloat(ryAttr.getValue())); + } + rect->roundRadius = -1.f; + rect->setRoundRadius(rRadius); + +// // make local so we can apply transform later in the function +// if( !ofxSvgCssClass::sIsNone(rxAttr.getValue()) || !ofxSvgCssClass::sIsNone(ryAttr.getValue())) { +// rect->roundRadius = -1.f; // force an update in setRoundRadius +// rect->setRoundRadius(std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), +// ofxSvgCssClass::sGetFloat(ryAttr.getValue())) +// ); +//// rect->roundRadius = std::max(ofxSvgCssClass::sGetFloat(rxAttr.getValue()), +//// ofxSvgCssClass::sGetFloat(ryAttr.getValue())); +//// +//// +//// rect->path.rectRounded(tpos.x, tpos.y, rect->width, rect->height, rect->roundRadius); +// +// } else { +// rect->path.rectangle(tpos.x, tpos.y, rect->getWidth(), rect->getHeight()); +// } telement = rect; @@ -887,6 +923,11 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< telement->layer = mCurrentLayer += 1.0; aElements.push_back( telement ); + if( telement->getType() == OFXSVG_TYPE_RECTANGLE ) { + auto rect = std::dynamic_pointer_cast( telement ); + ofLogNotice("ofxSvg::_addElementFromXmlNode") << "rect->pos: " << rect->getGlobalPosition() << " shape: " << rect->getOffsetPathPosition(); + } + if( mGroupStack.size() > 0 ) { auto pgroup = mGroupStack.back(); ofLogVerbose("ofxSvg::_addElementFromXmlNode") << "element: " << telement->getTypeAsString() << " -" << telement->getCleanName() << "- pos: " << telement->getPosition() << "- parent: " << pgroup->getCleanName(); @@ -1878,7 +1919,7 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< // gmat = glm::translate(gmat, glm::vec3(tp.x, tp.y, 0.0f)); // aele->pos.x = tp.x; // aele->pos.y = tp.y; -// aele->setPosition(tp.x, tp.y, 0.0f); + aele->setPosition(tp.x, tp.y, 0.0f); } else { mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.f, 0.f, 0.0f)); // gmat = glm::translate(gmat, glm::vec3(0.f, 0.f, 0.0f)); @@ -1938,7 +1979,7 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< // pos3 = gmat * glm::vec4( aele->pos.x, aele->pos.y, 0.0f, 1.f ); // aele->pos.x = pos3.x; // aele->pos.y = pos3.y; - aele->setPosition( pos3.x, pos3.y, 0.0f); +// aele->setPosition( pos3.x, pos3.y, 0.0f); // aele @@ -2045,13 +2086,15 @@ std::string ofxSvg::getSvgMatrixStringFromElement( std::shared_ptr std::ostringstream matrixStream; - matrixStream << std::fixed << std::setprecision(6); + matrixStream << std::fixed << std::setprecision(1); bool bFirst = true; - if( aele->getPosition().x != 0.f || aele->getPosition().y != 0.f ) { - bFirst = false; - matrixStream << "translate(" << aele->getPosition().x << "," << aele->getPosition().y << ")"; - } +// if( aele->getType() != OFXSVG_TYPE_RECTANGLE && aele->getType() != OFXSVG_TYPE_CIRCLE && aele->getType() != OFXSVG_TYPE_ELLIPSE ) { + if( aele->getPosition().x != 0.f || aele->getPosition().y != 0.f ) { + bFirst = false; + matrixStream << "translate(" << aele->getPosition().x << "," << aele->getPosition().y << ")"; + } +// } if( aele->getRotationDeg() != 0.f ) { if(!bFirst) { matrixStream << " "; @@ -2156,7 +2199,7 @@ void ofxSvg::_getTextSpanFromXmlNode( ofXml& anode, std::vector< std::shared_ptr aspans.push_back(tspan); - _pushCssClass(tspan->getCss()); + _pushCssClass(tspan->getCssClass()); for( auto& kid : anode.getChildren() ) { if( kid ) { if( kid.getName() == "tspan") { @@ -2696,6 +2739,34 @@ void ofxSvg::_addCssClassFromImage( std::shared_ptr aSvgImage, ofXm } } +//-------------------------------------------------------------- +void ofxSvg::_addCssClassFromTextSpan( std::shared_ptr aSvgTextSpan, ofXml& anode ) { + + auto textCss = aSvgTextSpan->getCssClass(); + + if( textCss.name.empty() ) { + textCss.name = "ts"; + } + + auto& tcss = mSvgCss.getAddClass(textCss); + + if( auto xattr = anode.appendAttribute("class") ) { + xattr.set(tcss.name); + } + +// if( !aSvgTextSpan->isVisible() ) { +// ofxSvgCssClass tcss; +// tcss.name = "st"; +// tcss.addProperty("display", "none" ); +// +// auto& addedClass = mSvgCss.getAddClass(tcss); +// +// if( auto xattr = anode.appendAttribute("class") ) { +// xattr.set(addedClass.name); +// } +// } +} + //-------------------------------------------------------------- bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { ofXml txml = aParentNode.appendChild( ofxSvgElement::sGetSvgXmlName(aele->getType())); @@ -2718,10 +2789,12 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { _addCssClassFromPath( trect, txml ); if( auto xattr = txml.appendAttribute("x")) { - xattr.set(trect->getPosition().x); +// xattr.set(trect->getPosition().x); + xattr.set(trect->getOffsetPathPosition().x); } if( auto xattr = txml.appendAttribute("y")) { - xattr.set(trect->getPosition().y); +// xattr.set(trect->getPosition().y); + xattr.set(trect->getOffsetPathPosition().y); } if( auto xattr = txml.appendAttribute("width")) { xattr.set(trect->getWidth()); @@ -2784,10 +2857,10 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { _addCssClassFromPath( tellipse, txml ); if( auto xattr = txml.appendAttribute("cx")) { - xattr.set(tellipse->getPosition().x); + xattr.set(tellipse->getOffsetPathPosition().x); } if( auto xattr = txml.appendAttribute("cy")) { - xattr.set(tellipse->getPosition().y); + xattr.set(tellipse->getOffsetPathPosition().y); } if( auto xattr = txml.appendAttribute("rx")) { xattr.set(tellipse->radiusX); @@ -2801,10 +2874,10 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { _addCssClassFromPath( tcircle, txml ); if( auto xattr = txml.appendAttribute("cx")) { - xattr.set(tcircle->getPosition().x); + xattr.set(tcircle->getOffsetPathPosition().x); } if( auto xattr = txml.appendAttribute("cy")) { - xattr.set(tcircle->getPosition().y); + xattr.set(tcircle->getOffsetPathPosition().y); } if( auto xattr = txml.appendAttribute("r")) { xattr.set(tcircle->getRadius()); @@ -2887,14 +2960,33 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { } else if( aele->getType() == OFXSVG_TYPE_TEXT ) { // TODO: Maybe at some point ;/ + auto ttext = std::dynamic_pointer_cast(aele); + for( auto tspan : ttext->textSpans ) { + if( auto spanXml = txml.appendChild("tspan")) { + if( auto xattr = spanXml.appendAttribute("x")) { + xattr.set(tspan->rect.x); + } + if( auto yattr = spanXml.appendAttribute("y")) { + yattr.set(tspan->rect.y); + } + spanXml.set(tspan->getText()); + _addCssClassFromTextSpan( tspan, spanXml ); + } + } } // figure out if we need a transform attribute - if( aele->getType() == OFXSVG_TYPE_IMAGE || aele->getRotationDeg() != 0.0f || aele->getScale().x != 1.0f || aele->getScale().y != 1.0f ) { - if( auto xattr = txml.appendAttribute("transform")) { - xattr.set( getSvgMatrixStringFromElement(aele) ); +// if( aele->getType() == OFXSVG_TYPE_IMAGE || aele->getRotationDeg() != 0.0f || aele->getScale().x != 1.0f || aele->getScale().y != 1.0f ) { + auto matrixString = getSvgMatrixStringFromElement(aele); + if( !matrixString.empty() ) { + if( auto xattr = txml.appendAttribute("transform")) { + xattr.set(matrixString); + } } - } +// 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 index 57644cf6468..965aed5f062 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -251,6 +251,7 @@ class ofxSvg : public ofxSvgGroup { ofxSvgCssClass& _addCssClassFromPath( std::shared_ptr aSvgPath ); void _addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ); void _addCssClassFromImage( std::shared_ptr aSvgImage, ofXml& anode ); + void _addCssClassFromTextSpan( std::shared_ptr aSvgTextSpan, ofXml& anode ); bool _toXml( ofXml& aParentNode, std::shared_ptr aele ); void _pushCssClass( const ofxSvgCssClass& acss ); diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index e134544ec7b..e12e40c5e89 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -223,6 +223,13 @@ void ofxSvgPath::applyStyle(ofxSvgCssClass& aclass) { void ofxSvgPath::customDraw() { // ofPushMatrix(); { // transformGL(); { + + bool bHasOffset = mOffsetPos.x != 0.f || mOffsetPos.y != 0.f; + if(bHasOffset) { + ofPushMatrix(); + ofTranslate(mOffsetPos.x, mOffsetPos.y); + } + ofSetColor( ofColor::orange ); // ofDrawCircle(0, 0, 15); @@ -233,28 +240,33 @@ void ofxSvgPath::customDraw() { // if( rotation != 0.0 ) ofRotateZDeg( rotation ); // ofScale( scale.x, scale.y ); - if(isVisible()) { - ofColor fillColor = getFillColor(); - ofColor strokeColor = getStrokeColor(); - if( alpha != 1.f ) { - if( isFilled() ) { - path.setFillColor(ofColor(fillColor.r, fillColor.g, fillColor.b, alpha * (float)fillColor.a )); - } - if( hasStroke() ) { - path.setStrokeColor(ofColor(strokeColor.r, strokeColor.g, strokeColor.b, alpha * (float)strokeColor.a )); - } + if(isVisible()) { + ofColor fillColor = getFillColor(); + ofColor strokeColor = getStrokeColor(); + if( alpha != 1.f ) { + if( isFilled() ) { + path.setFillColor(ofColor(fillColor.r, fillColor.g, fillColor.b, alpha * (float)fillColor.a )); } - path.draw(); - - if( alpha != 1.f ) { - if( isFilled() ) { - path.setFillColor(fillColor); - } - if( hasStroke() ) { - path.setStrokeColor(strokeColor); - } + if( hasStroke() ) { + path.setStrokeColor(ofColor(strokeColor.r, strokeColor.g, strokeColor.b, alpha * (float)strokeColor.a )); } } + path.draw(); + + if( alpha != 1.f ) { + if( isFilled() ) { + path.setFillColor(fillColor); + } + if( hasStroke() ) { + path.setStrokeColor(strokeColor); + } + } + } + + if(bHasOffset) { + ofPopMatrix(); + } + // } restoreTransformGL(); // } ofPopMatrix(); } diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index de03be08bde..acefce285e2 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -33,27 +33,42 @@ class ofxSvgElement : public ofNode { std::string getTypeAsString(); std::string getName() { return name; } + + /// \brief Get name with escaped characters and attempts to remove added naming patterns. + /// Removes the numbers added to the name by illustrator + /// ie. lelbow_00000070086365269320197030000010368508730034196876_ becomes lelbow std::string getCleanName(); bool isGroup() { return (getType() == OFXSVG_TYPE_GROUP); } + /// \brief Set the rotation around the z-axis. + /// \param adegrees rotation in degrees. void setRotationDeg( float adegrees ) { setRotationRad(glm::radians(adegrees)); } + + /// \brief Set the rotation around the z-axis. + /// \param aradians rotation in radians. void setRotationRad( float aradians ) { setOrientation(glm::angleAxis(aradians, glm::vec3(0.f, 0.f, 1.f) )); } + /// \brief Convenience function that wraps getRollDeg() from ofNode. + /// \return Rotation around the z-axis in degrees. float getRotationDeg() { return getRollDeg(); } + + /// \brief Convenience function that wraps getRollRad() from ofNode. + /// \return Rotation around the z-axis in radians. float getRotationRad() { return getRollRad(); } - // override this in ofxSvgGroup + /// \brief Set the visibility of the element, will not draw if not visible. + /// \param bool aBVisible set to true for visible. virtual void setVisible( bool ab ) { bVisible = ab; } bool isVisible() const { return bVisible; } @@ -84,7 +99,7 @@ class ofxSvgElement : public ofNode { return bUseShapeColor; } - /// \brief Get the bounding box of the element without transforms applied. + /// \brief Get the local axis aligned bounding box of the element without transforms applied. virtual ofRectangle getBoundingBox() { return ofRectangle( 0.0f, 0.0f, 1.f, 1.f ); }; /// \brief Get the axis aligned bounding box of the element, taking into account @@ -163,6 +178,8 @@ class ofxSvgPath : public ofxSvgElement { path.setStrokeColor( acolor ); } + /// \brief Get the first polyline if available. + /// \return ofPolyline returned from path.getOutline()[0]; ofPolyline getFirstPolyline() override { if( path.getOutline().size() > 0 ) { return path.getOutline()[0]; @@ -171,7 +188,7 @@ class ofxSvgPath : public ofxSvgElement { return ofPolyline(); } - /// \brief Get the local bounding box of the path, no transforms applied. + /// \brief Get the local axis aligned bounding box of the path, no transforms applied. virtual ofRectangle getBoundingBox() override { if(mBBoxNeedsRecalc) { mBBoxNeedsRecalc = false; @@ -180,11 +197,17 @@ class ofxSvgPath : public ofxSvgElement { if( outlines.size() > 0 ) { bool bFirst = true; for( auto& outline : outlines ) { + auto bbox = outline.getBoundingBox(); + bbox.x += mOffsetPos.x; + bbox.y += mOffsetPos.y; + if( bFirst ) { bFirst = false; - mBounds = outline.getBoundingBox(); + mBounds = bbox; +// mBounds = outline.getBoundingBox(); } else { - mBounds.growToInclude( outline.getBoundingBox() ); + mBounds.growToInclude( bbox ); +// mBounds.growToInclude( outline.getBoundingBox() ); } } } @@ -192,6 +215,17 @@ class ofxSvgPath : public ofxSvgElement { return mBounds; }; + /// \brief Offset the local shape position. Helpful with offset rotations. + void setOffsetPathPosition( float ax, float ay ) { + if( ax != mOffsetPos.x || ay != mOffsetPos.y ) { + mBBoxNeedsRecalc=true; + } + mOffsetPos = {ax, ay, 0.f}; + } + glm::vec3& getOffsetPathPosition() { + return mOffsetPos; + } + ofPath& getPath() { mBBoxNeedsRecalc = true; return path; @@ -202,6 +236,8 @@ class ofxSvgPath : public ofxSvgElement { bool mBBoxNeedsRecalc = true; ofRectangle mBounds; + glm::vec3 mOffsetPos = {0.f, 0.f, 0.f}; + virtual std::shared_ptr clone() override { return std::make_shared(*this); }; @@ -220,9 +256,11 @@ class ofxSvgRectangle : public ofxSvgPath { /// \brief Get the local bounding box of the rectangle, no transforms applied. virtual ofRectangle getBoundingBox() override { - return ofRectangle(0.f, 0.f, getWidth(), getHeight()); + return ofRectangle(mOffsetPos.x, mOffsetPos.y, getWidth(), getHeight()); }; + /// \brief Set the radius for rounded corners. + /// \param aRoundAmount the curve radius void setRoundRadius( float aRoundAmount ) { if( aRoundAmount != roundRadius ) { roundRadius = aRoundAmount; @@ -249,55 +287,6 @@ class ofxSvgRectangle : public ofxSvgPath { }; }; -class ofxSvgImage : public ofxSvgElement { - friend class ofxSvg; -public: - virtual ofxSvgType getType() override {return OFXSVG_TYPE_IMAGE;} - - float getWidth() const { return width;} - float getHeight() const { return height;} - - float getGlobalWidth() { return width * getGlobalScale().x;} - float getGlobalHeight() { return height * getGlobalScale().y;} - - /// \brief Get the local bounding box of the image, no transforms applied. - virtual ofRectangle getBoundingBox() override { - return ofRectangle(0.f, 0.f, getWidth(), getHeight()); - }; - - void load(); - bool isLoaded() { - return (img.isAllocated() && img.getWidth() > 0 && img.getHeight() > 0); - } - - virtual void customDraw() override; -// glm::vec2 getAnchorPointForPercent( float ax, float ay ); - - const std::filesystem::path& getFilePath() { return filepath; } - ofImage& getImage() { return img; } - - void setColor( ofColor aColor ) { - color = aColor; - } - ofColor getColor() { - return ofColor( color.r, color.g, color.b, alpha * (float)color.a ); - } - -protected: - of::filesystem::path filepath; - - ofColor color; - ofImage img; - - float width = 0.f; - float height = 0.f; - bool bTryLoad = false; - - virtual std::shared_ptr clone() override { - return std::make_shared(*this); - }; - -}; class ofxSvgCircle : public ofxSvgPath { friend class ofxSvg; @@ -308,19 +297,19 @@ class ofxSvgCircle : public ofxSvgPath { /// \brief Get the local bounding box of the circle, no transforms applied. virtual ofRectangle getBoundingBox() override { - return ofRectangle(-getRadius(), -getRadius(), getRadius()*2.f, getRadius()*2.f ); + return ofRectangle(-getRadius()+mOffsetPos.x, -getRadius()+mOffsetPos.y, getRadius()*2.f, getRadius()*2.f ); }; void setRadius( float aradius ) { if( aradius != radius ) { radius = aradius; path.clear(); - path.circle(0, 0, radius); + path.circle(0.f, 0.f, radius); } } protected: - float radius = 10.0; + float radius = 0.0; virtual std::shared_ptr clone() override { return std::make_shared(*this); @@ -339,9 +328,18 @@ class ofxSvgEllipse : public ofxSvgPath { /// \brief Get the local bounding box of the circle, no transforms applied. virtual ofRectangle getBoundingBox() override { - return ofRectangle(-getRadiusX(), -getRadiusY(), getRadiusX()*2.f, getRadiusY()*2.f ); + return ofRectangle(-getRadiusX()+mOffsetPos.x, -getRadiusY()+mOffsetPos.y, getRadiusX()*2.f, getRadiusY()*2.f ); }; + void setRadius( float aRadiusX, float aRadiusY ) { + if( aRadiusX != radiusX || aRadiusY != radiusY ) { + path.clear(); + radiusX = aRadiusX; + radiusY = aRadiusY; + path.ellipse({0.f,0.f}, radiusX * 2.0f, radiusY * 2.0f ); + } + } + protected: float radiusX, radiusY = 10.0f; @@ -351,6 +349,56 @@ class ofxSvgEllipse : public ofxSvgPath { }; +class ofxSvgImage : public ofxSvgElement { + friend class ofxSvg; +public: + virtual ofxSvgType getType() override {return OFXSVG_TYPE_IMAGE;} + + float getWidth() const { return width;} + float getHeight() const { return height;} + + float getGlobalWidth() { return width * getGlobalScale().x;} + float getGlobalHeight() { return height * getGlobalScale().y;} + + /// \brief Get the local bounding box of the image, no transforms applied. + virtual ofRectangle getBoundingBox() override { + return ofRectangle(0.f, 0.f, getWidth(), getHeight()); + }; + + void load(); + bool isLoaded() { + return (img.isAllocated() && img.getWidth() > 0 && img.getHeight() > 0); + } + + virtual void customDraw() override; + // glm::vec2 getAnchorPointForPercent( float ax, float ay ); + + const std::filesystem::path& getFilePath() { return filepath; } + ofImage& getImage() { return img; } + + void setColor( ofColor aColor ) { + color = aColor; + } + ofColor getColor() { + return ofColor( color.r, color.g, color.b, alpha * (float)color.a ); + } + +protected: + of::filesystem::path filepath; + + ofColor color; + ofImage img; + + float width = 0.f; + float height = 0.f; + bool bTryLoad = false; + + virtual std::shared_ptr clone() override { + return std::make_shared(*this); + }; +}; + + class ofxSvgText : public ofxSvgElement { friend class ofxSvg; public: @@ -426,7 +474,7 @@ class ofxSvgText : public ofxSvgElement { float alpha = 1.f; void applyStyle(ofxSvgCssClass& aclass); - ofxSvgCssClass& getCss() { return mSvgCssClass; } + ofxSvgCssClass& getCssClass() { return mSvgCssClass; } ofTrueTypeFont& getFont(); void draw( const std::string& astring, bool abCentered ); diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h index b03c0e7375f..0302c87f1c1 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.h +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -64,9 +64,9 @@ class ofxSvgGroup : public ofxSvgElement { /// \brief Set the visibility of the group. Does not set visibility of each child. The group only draws its children if the group is visible. /// \param bool aBVisible set to true for visible. - virtual void setVisible( bool aBVisible ) override { - ofxSvgElement::setVisible(aBVisible); - } +// virtual void setVisible( bool aBVisible ) override { +// ofxSvgElement::setVisible(aBVisible); +// } /// \brief Set the alpha of the group and call setAlpha(aAlpha) on its children. /// \param float aAlpha in range from 0-1 where 0 is transparent and 1 is full opacity. From 267030745d11e514243906c0f7d8045d94b79792 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Mon, 25 Aug 2025 11:29:15 -0400 Subject: [PATCH 14/21] custom optional class for legacy support. --- addons/ofxSvg/src/ofxSvg.cpp | 3 +- addons/ofxSvg/src/ofxSvgCss.cpp | 2 +- addons/ofxSvg/src/ofxSvgCss.h | 57 +++------------------------- addons/ofxSvg/src/ofxSvgElements.cpp | 42 ++++++++++---------- addons/ofxSvg/src/ofxSvgElements.h | 5 ++- addons/ofxSvg/src/ofxSvgUtils.cpp | 1 + addons/ofxSvg/src/ofxSvgUtils.h | 52 +++++++++++++++++++++++++ 7 files changed, 86 insertions(+), 76 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 921530a90fb..ced2cbe4f19 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -1323,7 +1323,8 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { bool bRelative = false; std::vector npositions= {glm::vec3(0.f, 0.f, 0.f)}; - std::optional ctype; + /// \note: ofxSvgOptional is declared in ofxSvgUtils + ofxSvgOptional ctype; // check if we are looking for a position if( cchar == 'm' || cchar == 'M' ) { diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index cf6e102bf9e..b0aae830a80 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -3,7 +3,7 @@ #include "ofLog.h" #include #include -#include +//#include std::map sCommonColors = { diff --git a/addons/ofxSvg/src/ofxSvgCss.h b/addons/ofxSvg/src/ofxSvgCss.h index a3ab8f9b708..9dcb0f0b628 100644 --- a/addons/ofxSvg/src/ofxSvgCss.h +++ b/addons/ofxSvg/src/ofxSvgCss.h @@ -3,64 +3,19 @@ #include "ofColor.h" #include "ofLog.h" #include "ofXml.h" +#include "ofxSvgUtils.h" 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 nh - 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("ofxSvgCssClass") << "No value present"; - } - return data; - } - - const T& value() const { -// if (!hasValue) throw std::runtime_error("No value present"); - if (!hasValue) { - ofLogError("ofxSvgCssClass") << "No value present"; - } - return data; - } - - // Reset to an empty state - void reset() { hasValue = false; } - - private: - bool hasValue; - T data; - }; - + /// \note: ofxSvgOptional is declared in ofxSvgUtils class Property { public: std::string srcString; - Optional fvalue; - Optional ivalue; - Optional svalue; - Optional cvalue; + ofxSvgOptional fvalue; + ofxSvgOptional ivalue; + ofxSvgOptional svalue; + ofxSvgOptional cvalue; }; std::unordered_map properties; diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index e12e40c5e89..fa0f76119df 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -803,27 +803,27 @@ void ofxSvgText::customDraw() { // } restoreTransformGL(); } -//-------------------------------------------------------------- -void ofxSvgText::draw(const std::string &astring, bool abCentered ) { - if( textSpans.size() > 0 ) { - transformGL(); { - textSpans[0]->draw(astring, abCentered ); - } restoreTransformGL(); - } else { - ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; - } -} - -//-------------------------------------------------------------- -void ofxSvgText::draw(const std::string& astring, const ofColor& acolor, bool abCentered ) { - if( textSpans.size() > 0 ) { - transformGL(); { - textSpans[0]->draw(astring, acolor, abCentered ); - } restoreTransformGL(); - } else { - ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; - } -} +////-------------------------------------------------------------- +//void ofxSvgText::draw(const std::string &astring, bool abCentered ) { +// if( textSpans.size() > 0 ) { +// transformGL(); { +// textSpans[0]->draw(astring, abCentered ); +// } restoreTransformGL(); +// } else { +// ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; +// } +//} +// +////-------------------------------------------------------------- +//void ofxSvgText::draw(const std::string& astring, const ofColor& acolor, bool abCentered ) { +// if( textSpans.size() > 0 ) { +// transformGL(); { +// textSpans[0]->draw(astring, acolor, abCentered ); +// } restoreTransformGL(); +// } else { +// ofLogVerbose("ofxSvgText") << __FUNCTION__ << " : no text spans to draw with."; +// } +//} //-------------------------------------------------------------- void ofxSvgText::TextSpan::applyStyle(ofxSvgCssClass& aclass) { diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index acefce285e2..e4cd447fa3f 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -554,8 +554,9 @@ class ofxSvgText : public ofxSvgElement { void setText( const std::string& astring, std::string aFontFamily, int aFontSize, float aMaxWidth ); void create(); void customDraw() override; - void draw(const std::string &astring, bool abCentered ); - void draw(const std::string &astring, const ofColor& acolor, bool abCentered ); + // going to 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; diff --git a/addons/ofxSvg/src/ofxSvgUtils.cpp b/addons/ofxSvg/src/ofxSvgUtils.cpp index 93c78e1f76b..9220932f369 100644 --- a/addons/ofxSvg/src/ofxSvgUtils.cpp +++ b/addons/ofxSvg/src/ofxSvgUtils.cpp @@ -260,3 +260,4 @@ ofPixels ofxSvgUtils::base64_decode(std::string const& encoded_string ) { // ofLogNotice("ofxSvgUtils::base64_decode") << "pixels ok: " << bok << " pixels: " << rpix.getWidth() << " x " << rpix.getHeight(); return rpix; } + diff --git a/addons/ofxSvg/src/ofxSvgUtils.h b/addons/ofxSvg/src/ofxSvgUtils.h index 13d7ee832b7..39049564307 100644 --- a/addons/ofxSvg/src/ofxSvgUtils.h +++ b/addons/ofxSvg/src/ofxSvgUtils.h @@ -2,6 +2,55 @@ #include #include "ofPixels.h" +// adding this Optional class since std::optional is not a part of all std:: distributions at the moment, looking at you gcc < 10 nh +// and not included in older versions of OF on Windows. +template +class ofxSvgOptional { +public: + ofxSvgOptional() : hasValue(false) {} // Default constructor, no value + ofxSvgOptional(const T& value) : hasValue(true), data(value) {} // Construct with a value + ofxSvgOptional(T&& value) : hasValue(true), data(std::move(value)) {} // Move constructor + + // Copy and move constructors + ofxSvgOptional(const ofxSvgOptional& other) = default; + ofxSvgOptional(ofxSvgOptional&& other) noexcept = default; + + // Assignment operators + ofxSvgOptional& operator=(const ofxSvgOptional& other) = default; + ofxSvgOptional& operator=(ofxSvgOptional&& other) noexcept = default; + + // Destructor + ~ofxSvgOptional() = 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("ofxSvgCssClass") << "No value present"; + } + return data; + } + + const T& value() const { + // if (!hasValue) throw std::runtime_error("No value present"); + if (!hasValue) { + ofLogError("ofxSvgCssClass") << "No value present"; + } + return data; + } + + // Reset to an empty state + void reset() { hasValue = false; } + +private: + bool hasValue; + T data; +}; + + class ofxSvgUtils { public: @@ -14,4 +63,7 @@ class ofxSvgUtils { static std::string base64_encode( const ofPixels& apixels ); static ofPixels base64_decode(std::string const& encoded_string ); + + + }; From 7882ce7087ef9dd81f24ef3e8b76180c3e12603c Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Wed, 3 Sep 2025 22:29:50 -0400 Subject: [PATCH 15/21] use ofNode as base --- addons/ofxSvg/src/ofxSvg.cpp | 363 ++++++++++----------------- addons/ofxSvg/src/ofxSvg.h | 33 +-- addons/ofxSvg/src/ofxSvgCss.cpp | 10 + addons/ofxSvg/src/ofxSvgCss.h | 3 + addons/ofxSvg/src/ofxSvgElements.cpp | 178 +++++-------- addons/ofxSvg/src/ofxSvgElements.h | 116 +++++---- addons/ofxSvg/src/ofxSvgFontBook.cpp | 66 +++-- addons/ofxSvg/src/ofxSvgFontBook.h | 8 +- addons/ofxSvg/src/ofxSvgGroup.cpp | 4 +- addons/ofxSvg/src/ofxSvgUtils.h | 2 +- 10 files changed, 334 insertions(+), 449 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index ced2cbe4f19..385857e4ba4 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -369,7 +369,7 @@ void ofxSvg::clear() { mGroupStack.clear(); mModelMatrix = glm::mat4(1.f); mModelMatrixStack = std::stack(); - loadIdentityMatrix(); +// loadIdentityMatrix(); mFillColor = ofColor(0); mStrokeColor = ofColor(0); @@ -925,7 +925,7 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< if( telement->getType() == OFXSVG_TYPE_RECTANGLE ) { auto rect = std::dynamic_pointer_cast( telement ); - ofLogNotice("ofxSvg::_addElementFromXmlNode") << "rect->pos: " << rect->getGlobalPosition() << " shape: " << rect->getOffsetPathPosition(); + ofLogVerbose("ofxSvg::_addElementFromXmlNode") << "rect->pos: " << rect->getGlobalPosition() << " shape: " << rect->getOffsetPathPosition(); } if( mGroupStack.size() > 0 ) { @@ -1157,7 +1157,6 @@ void ofxSvg::_parsePolylinePolygon( ofXml& tnode, std::shared_ptr aS // reference: https://www.w3.org/TR/SVG2/paths.html#PathData //-------------------------------------------------------------- void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { -// path27340-8 aSvgPath->path.clear(); @@ -1259,8 +1258,6 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { aSvgPath->path.clear(); -// auto prevCmd = ofPath::Command::close; - unsigned int justInCase = 0; // std::vector commands; bool breakMe = false; @@ -1324,12 +1321,13 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { bool bRelative = false; std::vector npositions= {glm::vec3(0.f, 0.f, 0.f)}; /// \note: ofxSvgOptional is declared in ofxSvgUtils + /// Using a custom class because older versions of OF did not include std::optional. ofxSvgOptional ctype; // check if we are looking for a position if( cchar == 'm' || cchar == 'M' ) { /* ------------------------------------------------ -// https://www.w3.org/TR/SVG/paths.html +// Reference: https://www.w3.org/TR/SVG/paths.html "Start a new sub-path at the given (x,y) coordinates. M (uppercase) indicates that absolute coordinates will follow; m (lowercase) indicates that relative coordinates will follow. If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands. @@ -1760,15 +1758,13 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { ofxSvgCssClass css; -// if( mCurrentSvgCss ) { - // apply first if we have a global style // - for( auto& tprop : mCurrentCss.properties ) { - if( tprop.first.empty() ) { - ofLogNotice("ofxSvg") << "First prop is empty"; - } - css.addProperty(tprop.first, tprop.second); + // apply first if we have a global style // + for( auto& tprop : mCurrentCss.properties ) { + if( tprop.first.empty() ) { + ofLogNotice("ofxSvg") << "First prop is empty"; } -// } + 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 // @@ -1850,8 +1846,6 @@ void ofxSvg::_applyStyleToText( ofXml& anode, std::shared_ptr aele ) { ofLogVerbose("-----------ofxSvg::setTransformFromSvgMatrixString") << aele->getTypeAsString() << " name: " << aele->getName() +"----------------"; // aele->scale = glm::vec2(1.0f, 1.0f); // aele->rotation = 0.0; aele->setScale(1.f); aele->mModelRotationPoint = glm::vec2(0.0f, 0.0f); - //TODO: implement matrix push and pop structure, similar to renderers + // TODO: Should a matrix push and pop structure, similar to renderers, be implemented? ofLogVerbose("ofxSvg") << __FUNCTION__ << " name: " << aele->getName() << " going to parse string: " << aStr << " pos: " << aele->getPosition(); float trotation = 0.f; @@ -1918,8 +1911,6 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< // apos += tp; mat = glm::translate(glm::mat4(1.0f), glm::vec3(tp.x, tp.y, 0.0f)); // gmat = glm::translate(gmat, glm::vec3(tp.x, tp.y, 0.0f)); -// aele->pos.x = tp.x; -// aele->pos.y = tp.y; aele->setPosition(tp.x, tp.y, 0.0f); } else { mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.f, 0.f, 0.0f)); @@ -1982,30 +1973,9 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< // aele->pos.y = pos3.y; // aele->setPosition( pos3.x, pos3.y, 0.0f); -// aele - -// aele->mMat = mat; - -// glm::vec3 skew; -// glm::vec4 perspective; -// glm::quat orientation; -// glm::vec3 translation, scale; - -// decompose( ofVec3f& translation, -// ofQuaternion& rotation, -// ofVec3f& scale, -// ofQuaternion& so ) -// glm::decompose(mat, scale, orientation, translation, skew, perspective); - -// aele->pos = glm::vec2(translation); -// aele->scale = glm::vec2(scale); -// aele->rotation = glm::degrees(glm::eulerAngles(orientation).z); - - - if( ofIsStringInString(aStr, "matrix")) { - // g3978-7 + // example transform string for matrix form. // transform="matrix(-1,0,0,-1,358.9498,1564.4744)" auto matrix = aStr; @@ -2084,18 +2054,15 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< //-------------------------------------------------------------- std::string ofxSvg::getSvgMatrixStringFromElement( std::shared_ptr aele ) { - // matrix(1 0 0 1 352.4516 349.0799)"> - std::ostringstream matrixStream; matrixStream << std::fixed << std::setprecision(1); bool bFirst = true; -// if( aele->getType() != OFXSVG_TYPE_RECTANGLE && aele->getType() != OFXSVG_TYPE_CIRCLE && aele->getType() != OFXSVG_TYPE_ELLIPSE ) { - if( aele->getPosition().x != 0.f || aele->getPosition().y != 0.f ) { - bFirst = false; - matrixStream << "translate(" << aele->getPosition().x << "," << aele->getPosition().y << ")"; - } -// } + if( aele->getPosition().x != 0.f || aele->getPosition().y != 0.f ) { + bFirst = false; + matrixStream << "translate(" << aele->getPosition().x << "," << aele->getPosition().y << ")"; + } + if( aele->getRotationDeg() != 0.f ) { if(!bFirst) { matrixStream << " "; @@ -2120,53 +2087,6 @@ std::string ofxSvg::getSvgMatrixStringFromElement( std::shared_ptrmModelRotationPoint.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; -// -//// float rotationZDeg = glm::degrees(glm::eulerAngles(aele->getOrientationQuat()).z); -// -// // if we are a group, path or image, then save position -// // rect and circle / ellipse pull their position -// -// -// // TODO: SAVE POSITION! -// std::ostringstream matrixStream; -// matrixStream << std::fixed << std::setprecision(6) << "rotate(" << aele->getRotationDeg() << " " << rcenter.x << " " << rcenter.y <<")"; -// if( aele->getScale().x != 1.f || aele->getScale().y != 1.f ) { -// matrixStream << " scale(" << aele->getScale().x << " " << aele->getScale().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) ); -//// -//// auto transform = aele->getLocalTransformMatrix(); -//// -//// // 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 ""; } @@ -2364,9 +2284,6 @@ std::shared_ptr ofxSvg::addGroup(std::string aname) { 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 ); path->applyStyle(mCurrentCss); _getPushedGroup()->add(path); recalculateLayers(); @@ -2386,7 +2303,7 @@ std::vector< std::shared_ptr > ofxSvg::add( const std::vector ofxSvg::add( const ofPolyline& apoly ) { if( apoly.size() < 2 ) { - return std::shared_ptr(); + ofLogWarning("ofxSvg::add") << "polyline has less than 2 vertices."; } ofPath opath; @@ -2421,16 +2338,9 @@ std::shared_ptr ofxSvg::add( const ofRectangle& arect ) { //-------------------------------------------------------------- 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); + rect->setPosition(arect.x, arect.y, 0.0f); rect->roundRadius = -1; // force setting round rect->setRoundRadius(std::max(0.f,aRoundRadius)); - -// _config(rect); -// _applyStyleToPath( mCurrentCss, rect ); rect->applyStyle(mCurrentCss); _getPushedGroup()->add(rect); recalculateLayers(); @@ -2446,16 +2356,11 @@ std::shared_ptr ofxSvg::addCircle( float 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->setPosition( apos.x, apos.y, 0.f); circle->path.setCircleResolution(mCircleResolution); circle->radius = -1.f; circle->setRadius(std::max(0.f,aradius)); -// circle->path.circle(apos, aradius); circle->setPosition( apos.x, apos.y, 0.0f); -// _config(circle); -// _applyStyleToPath( mCurrentCss, circle ); circle->applyStyle(mCurrentCss); _getPushedGroup()->add(circle); recalculateLayers(); @@ -2481,14 +2386,13 @@ std::shared_ptr ofxSvg::addEllipse( float aradiusX, float aradius //-------------------------------------------------------------- std::shared_ptr ofxSvg::addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ) { auto ellipse = std::make_shared(); - _applyModelMatrixToElement( ellipse, apos ); + ellipse->setPosition(apos.x, apos.y, 0.f); ellipse->radiusX = aradiusX; ellipse->radiusY = aradiusY; ellipse->path.setCircleResolution(mCircleResolution); ellipse->path.ellipse(apos, aradiusX, aradiusY); -// _applyStyleToPath( mCurrentCss, ellipse ); ellipse->applyStyle(mCurrentCss); _getPushedGroup()->add(ellipse); recalculateLayers(); @@ -2517,80 +2421,82 @@ std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of:: img->filepath = apath; img->width = atex.getWidth(); img->height = atex.getHeight(); - _applyModelMatrixToElement( img, apos ); +// _applyModelMatrixToElement( img, apos ); + img->setPosition(apos.x, apos.y, 0.0f); _getPushedGroup()->add(img); recalculateLayers(); return img; } //-------------------------------------------------------------- -std::shared_ptr ofxSvg::addEmbeddedImage( const ofPixels& apixels ) { +std::shared_ptr ofxSvg::addEmbeddedImage(const glm::vec2& apos, const ofPixels& apixels ) { auto img = std::make_shared(); img->img.setFromPixels(apixels); img->width = apixels.getWidth(); img->height = apixels.getHeight(); - _applyModelMatrixToElement( img, glm::vec2(0.f, 0.f) ); +// _applyModelMatrixToElement( img, glm::vec2(0.f, 0.f) ); + img->setPosition(apos.x, apos.y, 0.0f); _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::translate(const glm::vec2 & p) { - translate(p.x, p.y); -} - -//---------------------------------------------------------- -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)); -} - -//---------------------------------------------------------- -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::pushMatrix() { +// mModelMatrixStack.push(mModelMatrix); +//} +// +////---------------------------------------------------------- +//bool ofxSvg::popMatrix() { +// if( !mModelMatrixStack.empty() ) { +// mModelMatrix = mModelMatrixStack.top(); +// mModelMatrixStack.pop(); +// return true; +// } else { +// loadIdentityMatrix(); +// } +// return false; +//} +// +////---------------------------------------------------------- +//void ofxSvg::translate(const glm::vec2 & p) { +// translate(p.x, p.y); +//} +// +////---------------------------------------------------------- +//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)); +//} +// +////---------------------------------------------------------- +//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); +//} //-------------------------------------------------------------- @@ -2642,55 +2548,55 @@ ofxSvgGroup* ofxSvg::_getPushedGroup() { return this; } -//-------------------------------------------------------------- -bool ofxSvg::_hasPushedMatrix() { - return mModelMatrix != glm::mat4(1.0f); -} +////-------------------------------------------------------------- +//bool ofxSvg::_hasPushedMatrix() { +// return mModelMatrix != glm::mat4(1.0f); +//} //-------------------------------------------------------------- // TODO: CHECK ON THIS AFTER ofNode is implemented -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 = atan2f(xAxis.y, xAxis.x); - return angleRadians; -} +//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 = atan2f(xAxis.y, xAxis.x); +// return angleRadians; +//} //-------------------------------------------------------------- ofxSvgCssClass& ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath ) { @@ -2832,19 +2738,6 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { if( timage->img.getPixels().isAllocated() ) { // embed the pixels // if( auto xattr = txml.appendAttribute("xlink:href")) { -// ofPixels& pix = timage->img.getPixels(); -// size_t ilen = pix.getWidth() * pix.getHeight() * pix.getNumChannels(); -// -// ofBuffer tbuffer; -// ofSaveImage(pix, tbuffer); -// -//// auto base64String = base64_encode( pix.getData(), ilen, false ); -//// std::string base64String = encode( pix.getData(), ilen ); -// auto buffStr = tbuffer.getText(); -// const unsigned char* data = reinterpret_cast(buffStr.data()); -//// std::string base64String = encode(data, tbuffer.size()); -// auto base64String = ofxSvgUtils::base64_encode( data, tbuffer.size(), false ); -//// std::string str = pix.getData(); auto base64String = ofxSvgUtils::base64_encode( timage->img.getPixels() ); std::string encString = "data:image/png;base64,"+base64String; xattr.set(encString); diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index 965aed5f062..c988aad6011 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -200,19 +200,20 @@ class ofxSvg : public ofxSvgGroup { 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 addEmbeddedImage( const ofPixels& apixels ); + std::shared_ptr addEmbeddedImage( const glm::vec2& apos, const ofPixels& apixels ); // 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(); + // this will be handled by the ofNode functionality +// 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(); @@ -242,11 +243,11 @@ class ofxSvg : public ofxSvgGroup { void _setNodeParentGroupStack( std::shared_ptr aele ); ofxSvgGroup* _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 ); +// 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 ); ofxSvgCssClass& _addCssClassFromPath( std::shared_ptr aSvgPath ); void _addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ); diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index b0aae830a80..87a81764560 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -308,6 +308,11 @@ std::string ofxSvgCssClass::getFontFamily( std::string aDefaultFontFamily ) { return getValue("font-family", aDefaultFontFamily); } +//-------------------------------------------------- +bool ofxSvgCssClass::setFontBold( bool ab ) { + return addProperty("font-weight", ab ? "bold" : "regular" ); +} + //-------------------------------------------------- bool ofxSvgCssClass::isFontBold() { bool bold = false; @@ -317,6 +322,11 @@ bool ofxSvgCssClass::isFontBold() { return bold; } +//-------------------------------------------------- +bool ofxSvgCssClass::setFontItalic(bool ab) { + return addProperty("font-style", ab ? "italic" : "regular" ); +} + //-------------------------------------------------- bool ofxSvgCssClass::isFontItalic() { bool italic = false; diff --git a/addons/ofxSvg/src/ofxSvgCss.h b/addons/ofxSvg/src/ofxSvgCss.h index 9dcb0f0b628..f173a8bca55 100644 --- a/addons/ofxSvg/src/ofxSvgCss.h +++ b/addons/ofxSvg/src/ofxSvgCss.h @@ -57,7 +57,10 @@ class ofxSvgCssClass { bool setFontFamily( std::string aFontFamily ); std::string getFontFamily( std::string aDefaultFontFamily ); + bool setFontBold( bool ab ); bool isFontBold(); + + bool setFontItalic( bool ab ); bool isFontItalic(); bool hasProperty( const std::string& akey ); diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index fa0f76119df..d6a46280232 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -125,35 +125,6 @@ string ofxSvgElement::toString( int nlevel ) { return tstr; } -////-------------------------------------------------------------- -//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 )); -//// rmat = rmat * glm::toMat4((const glm::quat&)rq); -// rmat = glm::rotate(rmat, glm::radians(rotation), glm::vec3(0.f, 0.f, 1.f)); -// } -// if( scale.x != 1.0f || scale.y != 1.0f ) { -// rmat = glm::scale(rmat, glm::vec3(scale.x, scale.y, 1.0f)); -// } -// return rmat; -//}; -// -////-------------------------------------------------------------- -//ofNode ofxSvgElement::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; -//} - //-------------------------------------------------------------- void ofxSvgPath::applyStyle(ofxSvgCssClass& aclass) { if( aclass.hasProperty("fill")) { @@ -221,8 +192,6 @@ void ofxSvgPath::applyStyle(ofxSvgCssClass& aclass) { //-------------------------------------------------------------- void ofxSvgPath::customDraw() { -// ofPushMatrix(); { -// transformGL(); { bool bHasOffset = mOffsetPos.x != 0.f || mOffsetPos.y != 0.f; if(bHasOffset) { @@ -230,16 +199,6 @@ void ofxSvgPath::customDraw() { ofTranslate(mOffsetPos.x, mOffsetPos.y); } - ofSetColor( ofColor::orange ); - // ofDrawCircle(0, 0, 15); - -// ofTranslate(pos.x, pos.y); - - ofSetColor( ofColor::green ); - // ofDrawCircle(0, 0, 10); - -// if( rotation != 0.0 ) ofRotateZDeg( rotation ); -// ofScale( scale.x, scale.y ); if(isVisible()) { ofColor fillColor = getFillColor(); ofColor strokeColor = getStrokeColor(); @@ -266,17 +225,9 @@ void ofxSvgPath::customDraw() { if(bHasOffset) { ofPopMatrix(); } - -// } restoreTransformGL(); -// } ofPopMatrix(); } #pragma mark - Image -//-------------------------------------------------------------- -//ofRectangle ofxSvgImage::getRectangle() { -// return ofRectangle(pos.x, pos.y, getWidth(), getHeight()); -//} - //-------------------------------------------------------------- void ofxSvgImage::load() { if( !bTryLoad ) { @@ -295,15 +246,10 @@ void ofxSvgImage::customDraw() { if( isVisible() ) { if( img.isAllocated() ) { -// ofPushMatrix(); { -// transformGL(); { -// 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(); -// } restoreTransformGL(); + if(bUseShapeColor) { + ofSetColor( getColor() ); + } + img.draw( 0, 0 ); } } } @@ -318,6 +264,7 @@ void ofxSvgImage::customDraw() { #pragma mark - Text +//-------------------------------------------------------------- std::vector ofxSvgText::splitBySpanTags(const std::string& input) { std::vector result; std::regex span_regex(R"(<[^>]+>[\s\S]*?]+>)"); // Match tags with content including newlines @@ -349,11 +296,8 @@ std::vector ofxSvgText::splitBySpanTags(const std::string& input) { return result; } -//struct SpanData { -// std::string style; -// std::string content; -//}; +//-------------------------------------------------------------- ofxSvgText::SpanData ofxSvgText::extractSpanData(const std::string& spanTag) { SpanData data; @@ -375,6 +319,7 @@ ofxSvgText::SpanData ofxSvgText::extractSpanData(const std::string& spanTag) { return data; } +//-------------------------------------------------------------- bool ofxSvgText::endsWithLineEnding(const std::string& astr) { if (astr.size() >= 2 && astr.substr(astr.size() - 2) == "\r\n") { // Windows line ending @@ -386,6 +331,7 @@ bool ofxSvgText::endsWithLineEnding(const std::string& astr) { return false; } +//-------------------------------------------------------------- std::vector ofxSvgText::splitWordsAndLineEndings(const std::string& input) { std::vector result; @@ -401,9 +347,18 @@ std::vector ofxSvgText::splitWordsAndLineEndings(const std::string& return result; } -// build the text spans from a string and not from xml // +// build the text spans from a string and not from xml / svg file structure // //-------------------------------------------------------------- void ofxSvgText::setText( const std::string& astring, std::string aFontFamily, int aFontSize, float aMaxWidth ) { + ofxSvgCssClass css; + css.addProperty("font-family", aFontFamily); + css.addProperty("font-size", aFontSize); + css.addProperty("color", getColor() ); + setText( astring, css, aMaxWidth ); +} + +//-------------------------------------------------------------- +void ofxSvgText::setText( const std::string& astring, const ofxSvgCssClass& aSvgCssClass, float aMaxWidth ) { meshes.clear(); textSpans.clear(); @@ -422,10 +377,15 @@ void ofxSvgText::setText( const std::string& astring, std::string aFontFamily, i bool bLastCharIsSpace = false; // ofLogNotice("ofxSvgText") << "spanString: |" < tag.\n"; @@ -645,6 +605,10 @@ void ofxSvgText::create() { vector< std::shared_ptr > tspans = textSpans; for( auto& tspan : textSpans ) { + + // lets add any missing properties for the text spans from the ofxSvgText class + tspan->mSvgCssClass.addMissingClassProperties(mSvgCssClass); + // auto tkey = ofxSvgFontBook::getFontKey(tspan->getFontFamily(), tspan->isBold(), tspan->isItalic() ); if( !ofxSvgFontBook::hasFont(tspan->getFontFamily(), tspan->getFontSize(), tspan->isBold(), tspan->isItalic() )) { ofLogVerbose("ofxSvgText") << "Trying to load font " << tspan->getFontFamily() << " bold: " << tspan->isBold() << " italic: " << tspan->isItalic() << " | " << ofGetFrameNum(); @@ -742,9 +706,6 @@ void ofxSvgText::create() { // ofRectangle() // tempSpan->lineHeight = tfont.getStringBoundingBox("M", 0, 0).height; tempSpan->lineHeight = tfont.getLineHeight(); -// tempSpan.rect.x = tempSpan.rect.x - ogPos.x; -// tempSpan.rect.y = tempSpan.rect.x - ogPos.x; - //tempSpan.rect.y -= tempSpan.lineHeight; } } } @@ -752,10 +713,10 @@ void ofxSvgText::create() { //-------------------------------------------------------------- void ofxSvgText::customDraw() { if( !isVisible() ) return; - + if(bUseShapeColor) { - ofSetColor( 255, 255, 255, 255.f * alpha ); - } + ofSetColor( 255, 255, 255, 255.f * alpha ); + } std::map< string, std::map >::iterator mainIt; if(areTextSpansDirty()) { @@ -763,44 +724,38 @@ void ofxSvgText::customDraw() { create(); } -// transformGL(); { - ofTexture* tex = NULL; - for( mainIt = meshes.begin(); mainIt != meshes.end(); ++mainIt ) { - string fontKey = mainIt->first; - std::map< int, ofMesh >::iterator mIt; - for( mIt = meshes[ fontKey ].begin(); mIt != meshes[ fontKey ].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; - // if( fonts.count( fontKey ) ) { - if( ofxSvgFontBook::hasBookFont(fontKey)) { - auto& fbook = ofxSvgFontBook::getBookFont(fontKey); - if( fbook.textures.count( fontSize ) ) { - bHasTexture = true; - tex = &fbook.textures[ fontSize ]; - } + ofTexture* tex = NULL; + for( mainIt = meshes.begin(); mainIt != meshes.end(); ++mainIt ) { + string fontKey = mainIt->first; + std::map< int, ofMesh >::iterator mIt; + for( mIt = meshes[ fontKey ].begin(); mIt != meshes[ fontKey ].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; + // if( fonts.count( fontKey ) ) { + if( ofxSvgFontBook::hasBookFont(fontKey)) { + auto& fbook = ofxSvgFontBook::getBookFont(fontKey); + if( fbook.textures.count( fontSize ) ) { + bHasTexture = true; + tex = &fbook.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(); + } + + if( bHasTexture ) tex->bind(); + ofMesh& tMeshMesh = mIt->second; + if( bUseShapeColor ) { + vector< ofFloatColor >& tcolors = tMeshMesh.getColors(); + for( auto& tc : tcolors ) { + tc.a = alpha; } - tMeshMesh.draw(); - if( bHasTexture ) tex->unbind(); - tMeshMesh.enableColors(); + } else { + tMeshMesh.disableColors(); } + tMeshMesh.draw(); + if( bHasTexture ) tex->unbind(); + tMeshMesh.enableColors(); } -// } restoreTransformGL(); + } } ////-------------------------------------------------------------- @@ -878,15 +833,6 @@ ofTrueTypeFont& ofxSvgText::getFont() { return ofxSvgFontBook::defaultFont; } -////-------------------------------------------------------------- -//ofColor ofxSvgText::getColor() { -// if( textSpans.size() > 0 ) { -// return textSpans[0]->getColor(); -// } -// ofLogWarning("ofxSvgText") << __FUNCTION__ << " : no font detected from text spans, returning black."; -// return ofColor(0,255); -//} - // get the bounding rect for all of the text spans in this text element // should be called after create // //-------------------------------------------------------------- diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index e4cd447fa3f..f73ae15f9ef 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -26,19 +26,28 @@ class ofxSvgElement : public ofNode { friend class ofxSvgGroup; public: + /// \brief Get the ofxSvgType as a string. + /// \param ofxSvgType atype. + /// \return std::string as readable type. static std::string sGetTypeAsString(ofxSvgType atype); + /// \brief Get the xml name from the ofxSvgType. + /// \param ofxSvgType atype. + /// \return std::string as readable type. static std::string sGetSvgXmlName(ofxSvgType atype); - + /// \brief Get the ofxSvgType for this element. + /// \return ofxSvgType. virtual ofxSvgType getType() {return OFXSVG_TYPE_ELEMENT;} + /// \brief Get the ofxSvgType as a string. + /// \return std::string as readable type. std::string getTypeAsString(); - + /// \brief Get the name of the element; or the id attribute from the xml node. + /// \return std::string name of element. std::string getName() { return name; } - /// \brief Get name with escaped characters and attempts to remove added naming patterns. /// Removes the numbers added to the name by illustrator /// ie. lelbow_00000070086365269320197030000010368508730034196876_ becomes lelbow std::string getCleanName(); - + /// \return bool if this element is a group or not. bool isGroup() { return (getType() == OFXSVG_TYPE_GROUP); } @@ -70,35 +79,40 @@ class ofxSvgElement : public ofNode { /// \brief Set the visibility of the element, will not draw if not visible. /// \param bool aBVisible set to true for visible. virtual void setVisible( bool ab ) { bVisible = ab; } + /// \return bool if this element is visible. bool isVisible() const { return bVisible; } - + /// \brief Set the drawing alpha value of the element. + /// \param float aAlpha from 0-1; transparent to full opacity. virtual void setAlpha( float aAlpha ) { alpha = aAlpha; } + /// \return float alpha value of the element. float getAlpha() { return alpha; } + /// \brief The layer order of the element in the document hierarchy. + /// \return float layer of the element. + float getLayer() { + return layer; + } + /// \brief Enable or disable using colors from paths or text spans. + /// Set to false so ofSetColor() outside of this class will have effect/ + /// \param bool Enable or disable the shape colors. + virtual void setUseColors( bool ab ) { + bUseShapeColor = ab; + } + /// \brief Output a string description /// \param nlevel (optional) is the indentation amount. /// \return string with type and name. virtual std::string toString(int nlevel = 0); - float layer = -1.f; - bool bVisible=true; - /// \brief Override the function in ofNode meant for drawing virtual void customDraw() override {}; - + /// \brief Apply css class to the element. Meant to be overridden in subsequent classes. virtual void applyStyle(ofxSvgCssClass& aclass) {}; - virtual void setUseShapeColor( bool ab ) { - bUseShapeColor = ab; - } - bool isUsingShapeColor() { - return bUseShapeColor; - } - /// \brief Get the local axis aligned bounding box of the element without transforms applied. virtual ofRectangle getBoundingBox() { return ofRectangle( 0.0f, 0.0f, 1.f, 1.f ); }; @@ -139,8 +153,10 @@ class ofxSvgElement : public ofNode { float alpha = 1.f; std::string name = ""; - // used for saving to set the model position of the current mat4 // - glm::vec2 mModelPos = glm::vec2(0.f, 0.f); + float layer = -1.f; + bool bVisible=true; + + // used for storing the offset model rotation point // glm::vec2 mModelRotationPoint = glm::vec2(0.f, 0.f); virtual std::shared_ptr clone() { @@ -153,8 +169,8 @@ class ofxSvgPath : public ofxSvgElement { public: virtual ofxSvgType getType() override {return OFXSVG_TYPE_PATH;} - virtual void setUseShapeColor( bool ab ) override { - ofxSvgElement::setUseShapeColor(ab); + virtual void setUseColors( bool ab ) override { + ofxSvgElement::setUseColors(ab); path.setUseShapeColor(ab); } @@ -300,6 +316,8 @@ class ofxSvgCircle : public ofxSvgPath { return ofRectangle(-getRadius()+mOffsetPos.x, -getRadius()+mOffsetPos.y, getRadius()*2.f, getRadius()*2.f ); }; + /// \brief Set the radius of the circle. Only updates if the stored radius differs from the radius argument. + /// \param float desired radius of the circle. void setRadius( float aradius ) { if( aradius != radius ) { radius = aradius; @@ -331,6 +349,9 @@ class ofxSvgEllipse : public ofxSvgPath { return ofRectangle(-getRadiusX()+mOffsetPos.x, -getRadiusY()+mOffsetPos.y, getRadiusX()*2.f, getRadiusY()*2.f ); }; + /// \brief Set the radius X and radius Y of the ellipse. Only updates if the stored radius X or radius Y differs from the radius arguments. + /// \param float aRadiusX desired radius X of the ellipse. + /// \param float aRadiusY desired radius Y of the ellipse. void setRadius( float aRadiusX, float aRadiusY ) { if( aRadiusX != radiusX || aRadiusY != radiusY ) { path.clear(); @@ -442,22 +463,14 @@ class ofxSvgText : public ofxSvgElement { return mSvgCssClass.isFontBold(); } void setBold( bool ab ) { - if( ab ) { - mSvgCssClass.addProperty("font-weight", "bold"); - } else { - mSvgCssClass.addProperty("font-weight", "regular"); - } + mSvgCssClass.setFontBold(ab); } bool isItalic() { return mSvgCssClass.isFontItalic(); } void setItalic( bool ab ) { - if( ab ) { - mSvgCssClass.addProperty("font-style", "italic"); - } else { - mSvgCssClass.addProperty("font-style", "regular"); - } + mSvgCssClass.setFontItalic(ab); } std::string getText() { @@ -499,11 +512,11 @@ class ofxSvgText : public ofxSvgElement { layer = other.layer; bVisible = other.bVisible; alpha = other.alpha; + name = other.name; + bUseShapeColor = other.bUseShapeColor; fdirectory = other.fdirectory; bCentered = other.bCentered; -// _overrideColor = other._overrideColor; -// bOverrideColor = other.bOverrideColor; mSvgCssClass = other.mSvgCssClass; textSpans.reserve(other.textSpans.size()); @@ -526,11 +539,10 @@ class ofxSvgText : public ofxSvgElement { bVisible = other.bVisible; alpha = other.alpha; name = other.name; + bUseShapeColor = other.bUseShapeColor; fdirectory = other.fdirectory; bCentered = other.bCentered; -// _overrideColor = other._overrideColor; -// bOverrideColor = other.bOverrideColor; mSvgCssClass = other.mSvgCssClass; textSpans.clear(); @@ -549,19 +561,19 @@ class ofxSvgText : public ofxSvgElement { virtual ofxSvgType getType() override {return OFXSVG_TYPE_TEXT;} ofTrueTypeFont& getFont(); -// ofColor getColor(); - + void setText( const std::string& astring, std::string aFontFamily, int aFontSize, float aMaxWidth ); + void setText( const std::string& astring, const ofxSvgCssClass& aSvgCssClass, float aMaxWidth ); void create(); void customDraw() override; // going to 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 ) { + void setFontDirectory( const of::filesystem::path& aPath ) { fdirectory = aPath; } - std::string getFontDirectory() { + of::filesystem::path getFontDirectory() { return fdirectory; } @@ -573,21 +585,28 @@ class ofxSvgText : public ofxSvgElement { return ttext; } -// void overrideColor( ofColor aColor ) { -// bOverrideColor = true; -// _overrideColor = aColor; -// } - void setColor( ofColor acolor ) { mSvgCssClass.setColor(acolor); } + /// \brief Apply css class to the element. Text spans will use this style unless overridden by their css. + virtual void applyStyle(ofxSvgCssClass& aclass) override { + mSvgCssClass = aclass; + } + ofColor getColor() { - auto tcolor = mSvgCssClass.getColor("color", ofColor(255)); + auto tcolor = mSvgCssClass.getColor("color", ofColor(0)); tcolor.a *= alpha; return tcolor; } + void setCentered(bool ab) { + if( ab != bCentered ) { + bCentered = ab; + create(); + } + } + /// \brief Get the local bounding box of all of the text spans. virtual ofRectangle getBoundingBox() override; @@ -596,13 +615,12 @@ class ofxSvgText : public ofxSvgElement { bool areTextSpansDirty(); - bool bCentered = false; - protected: - std::string fdirectory; + of::filesystem::path fdirectory; - ofxSvgCssClass mSvgCssClass; + bool bCentered = false; + ofxSvgCssClass mSvgCssClass; virtual std::shared_ptr clone() override { auto newEle = std::make_shared(*this); @@ -628,8 +646,6 @@ class ofxSvgText : public ofxSvgElement { bool endsWithLineEnding(const std::string& astr); std::vector splitWordsAndLineEndings(const std::string& input); -// ofFloatColor _overrideColor; -// bool bOverrideColor = false; }; diff --git a/addons/ofxSvg/src/ofxSvgFontBook.cpp b/addons/ofxSvg/src/ofxSvgFontBook.cpp index dead2e23f4e..9fb2f4a5571 100644 --- a/addons/ofxSvg/src/ofxSvgFontBook.cpp +++ b/addons/ofxSvg/src/ofxSvgFontBook.cpp @@ -14,28 +14,43 @@ bool ofxSvgFontBook::loadFont(const std::string& aFontFamily, int aFontSize, boo } //-------------------------------------------------------------- -bool ofxSvgFontBook::loadFont( const std::string& aDirectory, const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ) { - ofLogVerbose("ofxFontBook") << "checking font: " << aFontFamily << " bold: " << aBBold << " italic: " << aBItalic << " fkey: "; - auto fkey = ofxSvgFontBook::getFontKey(aFontFamily, aBBold, aBItalic ); +bool ofxSvgFontBook::loadFont(const of::filesystem::path& aDirectory, const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ) { + ofxSvgCssClass css; + css.setFontFamily(aFontFamily); + css.setFontSize(aFontSize); + css.setFontBold(aBBold); + css.setFontItalic(aBItalic); + return loadFont( aDirectory, css ); +} + +//-------------------------------------------------------------- +bool ofxSvgFontBook::loadFont(const of::filesystem::path& aDirectory, ofxSvgCssClass& aCssClass ) { + auto fontFamily = aCssClass.getFontFamily("Arial"); + auto fontSize = aCssClass.getFontSize(12); + bool bBold = aCssClass.isFontBold(); + bool bItalic = aCssClass.isFontItalic(); + + ofLogVerbose("ofxFontBook") << "checking font: " << fontFamily << " bold: " << bBold << " italic: " << bItalic << " fkey: "; + auto fkey = ofxSvgFontBook::getFontKey(fontFamily, bBold, bItalic ); - ofLogVerbose("ofxFontBook") << "checking font: " << aFontFamily << " bold: " << aBBold << " italic: " << aBItalic; + ofLogVerbose("ofxFontBook") << "checking font: " << fontFamily << " bold: " << bBold << " italic: " << bItalic; if( fonts.count(fkey) == 0 ) { Font tafont; - tafont.fontFamily = aFontFamily; - tafont.bold = aBBold; - tafont.italic = aBItalic; + tafont.fontFamily = fontFamily; + tafont.bold = bBold; + tafont.italic = bItalic; fonts[fkey] = tafont; } bool bFontLoadOk = true; Font& tfont = fonts[ fkey ]; - if (tfont.sizes.count(aFontSize) == 0) { + if (tfont.sizes.count(fontSize) == 0) { bool bHasFontDirectory = false; // cout << "checking directory: " << fdirectory+"/fonts/" << endl; std::string fontsDirectory = "";// = ofToDataPath("", true); if( !aDirectory.empty() ) { - fontsDirectory = aDirectory; + fontsDirectory = aDirectory.string(); } if( !ofFile::doesFileExist(fontsDirectory)) { @@ -61,10 +76,10 @@ bool ofxSvgFontBook::loadFont( const std::string& aDirectory, const std::string& bHasFontDirectory = true; } - std::vector fontNamesToSearch = {aFontFamily}; + std::vector fontNamesToSearch = {fontFamily}; // sometimes there are fallback fonts included with a comma separator - if( ofIsStringInString(aFontFamily, ",")) { - std::vector splitNames = ofSplitString(aFontFamily, ",", true, true); + if( ofIsStringInString(fontFamily, ",")) { + std::vector splitNames = ofSplitString(fontFamily, ",", true, true); for( auto& sname : splitNames ) { // remove spaces ofStringReplace(sname, " ", "" ); @@ -95,19 +110,19 @@ bool ofxSvgFontBook::loadFont( const std::string& aDirectory, const std::string& std::vector subStrs; std::vector excludeStrs; - if( aBBold ) { + if( bBold ) { subStrs.push_back("bold"); } else { excludeStrs.push_back("bold"); } - if( aBItalic ) { + if( bItalic ) { subStrs.push_back("italic"); } else { excludeStrs.push_back("italic"); } bool bMightHaveFoundTheFont = false; - ofLogVerbose("ofxSvgFontBook") << "trying to load font: " << tfont.fontFamily << " bold: " << aBBold << " italic: " << aBItalic; + ofLogVerbose("ofxSvgFontBook") << "trying to load font: " << tfont.fontFamily << " bold: " << bBold << " italic: " << bItalic; // bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, tfont.fontFamily, tNewFontPath, subStrs, excludeStrs, 0); for( auto& fontFam : fontNamesToSearch ) { bool bFoundTheFont = _recursiveFontDirSearch(fontsDirectory, fontFam, tNewFontPath, subStrs, excludeStrs, 0); @@ -184,7 +199,7 @@ bool ofxSvgFontBook::loadFont( const std::string& aDirectory, const std::string& bFontLoadOk = false; } else { // load(const std::string& _filename, int _fontSize, bool _bAntiAliased, bool _bFullCharacterSet, bool _makeContours, float _simplifyAmt, int _dpi) - bFontLoadOk = tfont.sizes[aFontSize].load(tfontPath, aFontSize, true, true, false, 0.5, 72); + bFontLoadOk = tfont.sizes[fontSize].load(tfontPath, fontSize, true, true, false, 0.5, 72); if( bFontLoadOk && tfont.pathToFont.empty() ) { tfont.pathToFont = tfontPath; } @@ -192,21 +207,20 @@ bool ofxSvgFontBook::loadFont( const std::string& aDirectory, const std::string& if(bFontLoadOk) { // tfont.sizes[ vIt->first ].setSpaceSize( 0.57 ); // tfont.sizes[ vIt->first ] = datFontTho; - tfont.textures[ aFontSize ] = tfont.sizes[ aFontSize ].getFontTexture(); + tfont.textures[ fontSize ] = tfont.sizes[ fontSize ].getFontTexture(); } else { - ofLogError("ofxSvgFontBook") << __FUNCTION__ << " : error loading font family: " << tfont.fontFamily << " size: " << aFontSize; - tfont.sizes.erase(aFontSize); + ofLogError("ofxSvgFontBook") << __FUNCTION__ << " : error loading font family: " << tfont.fontFamily << " size: " << fontSize; + tfont.sizes.erase(fontSize); } } return bFontLoadOk; } //-------------------------------------------------------------- -bool ofxSvgFontBook::_recursiveFontDirSearch( - const string& afile, const string& aFontFamToLookFor, string& fontpath, - const std::vector& aAddNames, - const std::vector& aExcludeNames, - int aNumRecursions) { +bool ofxSvgFontBook::_recursiveFontDirSearch(const string& afile, const string& aFontFamToLookFor, string& fontpath, + const std::vector& aAddNames, + const std::vector& aExcludeNames, + int aNumRecursions) { if (fontpath != "") { return true; } @@ -222,8 +236,8 @@ bool ofxSvgFontBook::_recursiveFontDirSearch( tdir.listDir(afile); tdir.sort(); for (std::size_t i = 0; i < tdir.size(); i++) { - bool youGoodOrWhat = _recursiveFontDirSearch(tdir.getPath(i), aFontFamToLookFor, fontpath, aAddNames, aExcludeNames, numRecursions); - if( youGoodOrWhat ) { + bool bFontFound = _recursiveFontDirSearch(tdir.getPath(i), aFontFamToLookFor, fontpath, aAddNames, aExcludeNames, numRecursions); + if( bFontFound ) { return true; } } diff --git a/addons/ofxSvg/src/ofxSvgFontBook.h b/addons/ofxSvg/src/ofxSvgFontBook.h index d6118826f64..9e3c1e70f3b 100644 --- a/addons/ofxSvg/src/ofxSvgFontBook.h +++ b/addons/ofxSvg/src/ofxSvgFontBook.h @@ -1,6 +1,7 @@ #pragma once #include "ofTrueTypeFont.h" #include +#include "ofxSvgCss.h" /// @brief This class is mostly for internal use. /// Used by the ofxSvgText elements for managing fonts @@ -19,9 +20,10 @@ class ofxSvgFontBook { static void setFontDirectory( std::string adir ) { mFontDirectory = adir; } - - static bool loadFont(const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ); - static bool loadFont( const std::string& aDirectory, const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ); + + static bool loadFont(const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ); + static bool loadFont(const of::filesystem::path& aDirectory, ofxSvgCssClass& aCssClass ); + static bool loadFont(const of::filesystem::path& aDirectory, const std::string& aFontFamily, int aFontSize, bool aBBold, bool aBItalic ); static std::string getFontKey( const std::string& aFontFamily, bool aBBold, bool aBItalic ) { auto fkey = aFontFamily; diff --git a/addons/ofxSvg/src/ofxSvgGroup.cpp b/addons/ofxSvg/src/ofxSvgGroup.cpp index 177b35014bf..130c48f758b 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.cpp +++ b/addons/ofxSvg/src/ofxSvgGroup.cpp @@ -274,7 +274,7 @@ string ofxSvgGroup::toString(int nlevel) { void ofxSvgGroup::disableColors() { auto telements = getAllElements(false); for( auto& ele : telements ) { - ele->setUseShapeColor(false); + ele->setUseColors(false); } } @@ -282,7 +282,7 @@ void ofxSvgGroup::disableColors() { void ofxSvgGroup::enableColors() { auto telements = getAllElements(false); for( auto& ele : telements ) { - ele->setUseShapeColor(true); + ele->setUseColors(true); } } diff --git a/addons/ofxSvg/src/ofxSvgUtils.h b/addons/ofxSvg/src/ofxSvgUtils.h index 39049564307..a63759876e3 100644 --- a/addons/ofxSvg/src/ofxSvgUtils.h +++ b/addons/ofxSvg/src/ofxSvgUtils.h @@ -3,7 +3,7 @@ #include "ofPixels.h" // adding this Optional class since std::optional is not a part of all std:: distributions at the moment, looking at you gcc < 10 nh -// and not included in older versions of OF on Windows. +// and not included in older versions of OF on Windows, ie. 12.0. template class ofxSvgOptional { public: From 554fd821a4a769630b50aeedfda17195545a7bdf Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Wed, 3 Sep 2025 22:30:49 -0400 Subject: [PATCH 16/21] svg parse example --- .../bin/data/ofLogoDesserts.svg | 156 ++++++++++++++++++ .../input_output/svgParseExample/src/main.cpp | 18 ++ .../svgParseExample/src/ofApp.cpp | 146 ++++++++++++++++ .../input_output/svgParseExample/src/ofApp.h | 25 +++ .../svgParseExample/svgParseExample.png | Bin 0 -> 49277 bytes 5 files changed, 345 insertions(+) create mode 100644 examples/input_output/svgParseExample/bin/data/ofLogoDesserts.svg create mode 100755 examples/input_output/svgParseExample/src/main.cpp create mode 100755 examples/input_output/svgParseExample/src/ofApp.cpp create mode 100755 examples/input_output/svgParseExample/src/ofApp.h create mode 100644 examples/input_output/svgParseExample/svgParseExample.png diff --git a/examples/input_output/svgParseExample/bin/data/ofLogoDesserts.svg b/examples/input_output/svgParseExample/bin/data/ofLogoDesserts.svg new file mode 100644 index 00000000000..0dedffaa165 --- /dev/null +++ b/examples/input_output/svgParseExample/bin/data/ofLogoDesserts.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/input_output/svgParseExample/src/main.cpp b/examples/input_output/svgParseExample/src/main.cpp new file mode 100755 index 00000000000..e2a9df057c8 --- /dev/null +++ b/examples/input_output/svgParseExample/src/main.cpp @@ -0,0 +1,18 @@ +#include "ofMain.h" +#include "ofApp.h" + +//======================================================================== +int main( ){ + + //Use ofGLFWWindowSettings for more options like multi-monitor fullscreen + ofGLWindowSettings settings; + settings.setSize(1024, 768); + settings.setGLVersion(3,2); + settings.windowMode = OF_WINDOW; //can also be OF_FULLSCREEN + + auto window = ofCreateWindow(settings); + + ofRunApp(window, std::make_shared()); + ofRunMainLoop(); + +} diff --git a/examples/input_output/svgParseExample/src/ofApp.cpp b/examples/input_output/svgParseExample/src/ofApp.cpp new file mode 100755 index 00000000000..f8254ff24d5 --- /dev/null +++ b/examples/input_output/svgParseExample/src/ofApp.cpp @@ -0,0 +1,146 @@ +#include "ofApp.h" + +//-------------------------------------------------------------- +void ofApp::setup(){ + + ofSetBackgroundColor(250); + + ofSetFrameRate( 60 ); + svg.load("ofLogoDesserts.svg"); + // Print the svg structure to the console. + ofLogNotice("Svg Structure") << std::endl << svg.toString(); + + // get all the paths in the Donut -> Sprinkles group + auto sprinklePaths = svg.getElementsForType("Donut:Sprinkles"); + // A wildcard (*) is also acceptable. Allowing traversal of groups until the group with the name after the colon is found. + // In this case "Sprinkles" +// auto sprinklePaths = svg.getElementsForType("*:Sprinkles"); + + for( auto& sprinklePath : sprinklePaths ) { + // Grab and store the first polyline in the path so we can set it on the svg element later. + auto spoly = sprinklePath->getFirstPolyline(); + if( spoly.size() > 2 ) { + // get the center point from the bounding box + auto centerPos = spoly.getBoundingBox().getCenter(); + sprinklePath->setPosition(centerPos); + + sprinklePath->getPath().clear(); + int counter = 0; + + // now lets convert the polyline to be local around the center point. + for( auto& vert : spoly.getVertices() ) { + vert -= centerPos; + if( counter < 1 ) { + sprinklePath->getPath().moveTo(vert); + } else { + sprinklePath->getPath().lineTo(vert); + } + counter++; + } + + if( spoly.isClosed() ) { + sprinklePath->getPath().close(); + } + // Store in a vector so that we can manipulate these later. + mSprinkles.push_back(sprinklePath); + } + } +} + +//-------------------------------------------------------------- +void ofApp::update(){ + + // store the elapsed time in a variable since it can be an expensive operation in a loop. + float etimef = ofGetElapsedTimef(); + int counter = 0; + for( auto& sprinkle : mSprinkles ) { + if( ofGetMousePressed() ) { + // Store the difference from the sprinkle position to the mouse position. + auto diff = glm::normalize(glm::vec2( ofGetMouseX(), ofGetMouseY() ) - glm::vec2( sprinkle->getPosition().x, sprinkle->getPosition().y )); + // Convert to a angle rotation in degrees. + float targetRotation = glm::degrees(atan2f( diff.y, diff.x )); + // Lerp to the target rotation. + // Calling ofLerpDegrees handles wrapping and edge cases when using degrees. + sprinkle->setRotationDeg(ofLerpDegrees(sprinkle->getRotationDeg(), targetRotation, 0.1f )); + sprinkle->setFillColor(ofColor(255)); + } else { + // Store the current rotation. + float sprinkleRotation = sprinkle->getRotationDeg(); + // Rotate a small amount based on cos and the sprinkle x position. + float rotationAmount = 2.f * ofClamp( cosf( sprinkle->getPosition().x * 0.1f + etimef ), -0.1f, 1.f); + sprinkle->setRotationDeg(sprinkleRotation+rotationAmount); + ofColor tcolor( 101,163,253 ); + // Change the hue a bit for a color changing effect. + tcolor.setHue( tcolor.getHue() + (sin(etimef*0.5f + counter * 2.f)) * 50.f ); + sprinkle->setFillColor(tcolor); + } + counter++; + } + +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + + auto wrect = ofRectangle(0,0,ofGetWidth(), ofGetHeight()); + auto srect = svg.getBounds(); + // scale the bounds of the svg to fit within the window rectangle + srect.scaleTo(wrect, OF_SCALEMODE_FIT); + + ofPushMatrix(); { + ofTranslate(srect.x, srect.y); + ofScale( srect.getWidth() / svg.getBounds().getWidth() ); + svg.draw(); + + ofSetColor( 120 ); + ofNoFill(); + ofDrawRectangle( svg.getBounds() ); + ofFill(); + } ofPopMatrix(); + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/examples/input_output/svgParseExample/src/ofApp.h b/examples/input_output/svgParseExample/src/ofApp.h new file mode 100755 index 00000000000..99d6fda2ba4 --- /dev/null +++ b/examples/input_output/svgParseExample/src/ofApp.h @@ -0,0 +1,25 @@ +#pragma once + +#include "ofMain.h" +#include "ofxSvg.h" + +class ofApp : public ofBaseApp{ + public: + void setup(); + void update(); + void draw(); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + + ofxSvg svg; + std::vector< std::shared_ptr > mSprinkles; + +}; diff --git a/examples/input_output/svgParseExample/svgParseExample.png b/examples/input_output/svgParseExample/svgParseExample.png new file mode 100644 index 0000000000000000000000000000000000000000..545ffd5b62cc5e662621bbee9095237727ba0760 GIT binary patch literal 49277 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 PfPcr2ni>=yb_x3*M34j? literal 0 HcmV?d00001 From 0122e95d13fdc47f482d1008a60a06a1c3a54d40 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Wed, 3 Sep 2025 23:34:26 -0400 Subject: [PATCH 17/21] fixes for no stroke and adding elements --- addons/ofxSvg/src/ofxSvg.cpp | 102 ++--------------------------- addons/ofxSvg/src/ofxSvgCss.cpp | 16 +++-- addons/ofxSvg/src/ofxSvgCss.h | 2 + addons/ofxSvg/src/ofxSvgElements.h | 5 ++ 4 files changed, 25 insertions(+), 100 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 385857e4ba4..5cb97434929 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -868,53 +868,10 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< // getTransformFromSvgMatrix( transAttr.getValue(), telement->pos, telement->scale.x, telement->scale.y, telement->rotation ); setTransformFromSvgMatrixString( transAttr.getValue(), telement ); } - -// std::vector typesToApplyTransformToPath = { -// OFXSVG_TYPE_RECTANGLE, -// OFXSVG_TYPE_CIRCLE, -// OFXSVG_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(); -// auto transform = epath->getGlobalTransformMatrix(); -// 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() == OFXSVG_TYPE_TEXT ) { auto text = std::dynamic_pointer_cast( telement ); -// text->ogPos = text->pos; text->create(); } @@ -931,9 +888,7 @@ shared_ptr ofxSvg::_addElementFromXmlNode( ofXml& tnode, vector< if( mGroupStack.size() > 0 ) { auto pgroup = mGroupStack.back(); ofLogVerbose("ofxSvg::_addElementFromXmlNode") << "element: " << telement->getTypeAsString() << " -" << telement->getCleanName() << "- pos: " << telement->getPosition() << "- parent: " << pgroup->getCleanName(); -// telement->setParent(*pgroup.get(), false); telement->setParent(*_getPushedGroup(), false); - //ofLogNotice(""); } return telement; @@ -959,21 +914,6 @@ std::vector parseToFloats(const std::string& input) { } 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; -// } -// } - std::vector points; auto values = parseToFloats( input ); @@ -1739,19 +1679,12 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { ofLogWarning("ofxSvg") << "unable to parse arc segment."; } } - -// prevCmd = commandT; -// mCenterPoints.push_back(currentPos); -// mCPoints.insert( mCPoints.end(), npositions.begin(), npositions.end() ); } // ofLogNotice("ofxSvg") << "["<pos.y = pos3.y; ofLogVerbose("ofxSvg::setTransformFromSvgMatrixString") << "pos: " << aele->getPosition() << " rotation: " << trotation << " scale: " << aele->getScale(); - -// 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; } } @@ -2339,6 +2255,8 @@ std::shared_ptr ofxSvg::add( const ofRectangle& arect ) { std::shared_ptr ofxSvg::add( const ofRectangle& arect, float aRoundRadius ) { auto rect = std::make_shared(); rect->setPosition(arect.x, arect.y, 0.0f); + rect->width = arect.getWidth(); + rect->height = arect.getHeight(); rect->roundRadius = -1; // force setting round rect->setRoundRadius(std::max(0.f,aRoundRadius)); rect->applyStyle(mCurrentCss); @@ -2853,7 +2771,6 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { } else if( aele->getType() == OFXSVG_TYPE_TEXT ) { - // TODO: Maybe at some point ;/ auto ttext = std::dynamic_pointer_cast(aele); for( auto tspan : ttext->textSpans ) { if( auto spanXml = txml.appendChild("tspan")) { @@ -2870,17 +2787,12 @@ bool ofxSvg::_toXml( ofXml& aParentNode, std::shared_ptr aele ) { } // figure out if we need a transform attribute -// if( aele->getType() == OFXSVG_TYPE_IMAGE || aele->getRotationDeg() != 0.0f || aele->getScale().x != 1.0f || aele->getScale().y != 1.0f ) { - auto matrixString = getSvgMatrixStringFromElement(aele); - if( !matrixString.empty() ) { - if( auto xattr = txml.appendAttribute("transform")) { - xattr.set(matrixString); - } + auto matrixString = getSvgMatrixStringFromElement(aele); + if( !matrixString.empty() ) { + if( auto xattr = txml.appendAttribute("transform")) { + xattr.set(matrixString); } -// if( auto xattr = txml.appendAttribute("transform")) { -// xattr.set( getSvgMatrixStringFromElement(aele) ); -// } -// } + } return txml; } diff --git a/addons/ofxSvg/src/ofxSvgCss.cpp b/addons/ofxSvg/src/ofxSvgCss.cpp index 87a81764560..10705d39791 100644 --- a/addons/ofxSvg/src/ofxSvgCss.cpp +++ b/addons/ofxSvg/src/ofxSvgCss.cpp @@ -196,10 +196,6 @@ bool ofxSvgCssClass::addProperties( std::string aPropertiesString ) { addProperty(propStr); // pindex++; } - -// for( auto& prop : properties ) { -// ofLogNotice("ofx::svg2::CssClass") << " prop: " << prop.first << " : " << prop.second.srcString; -// } } return properties.size() > 0; } @@ -248,6 +244,13 @@ bool ofxSvgCssClass::addProperty( const std::string& aName, const ofColor& acolo return addProperty(aName, prop ); } +//-------------------------------------------------- +bool ofxSvgCssClass::removeProperty( std::string aPropString ) { + bool bHas = hasProperty(aPropString); + properties.erase(aPropString); + return bHas; +} + //-------------------------------------------------- bool ofxSvgCssClass::setColor(const ofColor& acolor) { return addProperty("color", acolor); @@ -280,7 +283,10 @@ bool ofxSvgCssClass::setStrokeWidth( const float& awidth ) { //-------------------------------------------------- bool ofxSvgCssClass::setNoStroke() { - return addProperty("stroke", "none" ); +// return addProperty("stroke", "none" ); + bool bstroke = removeProperty("stroke"); + bool bstrokeW = removeProperty("stroke-width"); + return bstroke || bstrokeW; } //-------------------------------------------------- diff --git a/addons/ofxSvg/src/ofxSvgCss.h b/addons/ofxSvg/src/ofxSvgCss.h index f173a8bca55..af3b597a392 100644 --- a/addons/ofxSvg/src/ofxSvgCss.h +++ b/addons/ofxSvg/src/ofxSvgCss.h @@ -40,6 +40,8 @@ class ofxSvgCssClass { bool addProperty( const std::string& aName, const float& avalue ); bool addProperty( const std::string& aName, const ofColor& acolor ); + bool removeProperty( std::string aPropString ); + bool setColor(const ofColor& acolor); bool setFillColor(const ofColor& acolor); diff --git a/addons/ofxSvg/src/ofxSvgElements.h b/addons/ofxSvg/src/ofxSvgElements.h index f73ae15f9ef..3b97a908826 100755 --- a/addons/ofxSvg/src/ofxSvgElements.h +++ b/addons/ofxSvg/src/ofxSvgElements.h @@ -43,6 +43,11 @@ class ofxSvgElement : public ofNode { /// \brief Get the name of the element; or the id attribute from the xml node. /// \return std::string name of element. std::string getName() { return name; } + /// \brief Set the name of the element; or the id attribute of the xml node. + /// \param aname string; name to be used for the element + void setName( const std::string& aname ) { + name = aname; + } /// \brief Get name with escaped characters and attempts to remove added naming patterns. /// Removes the numbers added to the name by illustrator /// ie. lelbow_00000070086365269320197030000010368508730034196876_ becomes lelbow From 82e2d6d2c11b40583a793d6aaa9f6bb007d7ecc2 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Wed, 3 Sep 2025 23:34:56 -0400 Subject: [PATCH 18/21] save example --- .../svgSaveExample/bin/data/ofxSvg.svg | 20 +++ .../input_output/svgSaveExample/src/main.cpp | 18 ++ .../input_output/svgSaveExample/src/ofApp.cpp | 159 ++++++++++++++++++ .../input_output/svgSaveExample/src/ofApp.h | 34 ++++ .../svgSaveExample/svgSaveExample.png | Bin 0 -> 23277 bytes 5 files changed, 231 insertions(+) create mode 100644 examples/input_output/svgSaveExample/bin/data/ofxSvg.svg create mode 100644 examples/input_output/svgSaveExample/src/main.cpp create mode 100644 examples/input_output/svgSaveExample/src/ofApp.cpp create mode 100644 examples/input_output/svgSaveExample/src/ofApp.h create mode 100644 examples/input_output/svgSaveExample/svgSaveExample.png diff --git a/examples/input_output/svgSaveExample/bin/data/ofxSvg.svg b/examples/input_output/svgSaveExample/bin/data/ofxSvg.svg new file mode 100644 index 00000000000..440b665153d --- /dev/null +++ b/examples/input_output/svgSaveExample/bin/data/ofxSvg.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/input_output/svgSaveExample/src/main.cpp b/examples/input_output/svgSaveExample/src/main.cpp new file mode 100644 index 00000000000..5ce9270df19 --- /dev/null +++ b/examples/input_output/svgSaveExample/src/main.cpp @@ -0,0 +1,18 @@ +#include "ofMain.h" +#include "ofApp.h" + +//======================================================================== +int main( ){ + + //Use ofGLFWWindowSettings for more options like multi-monitor fullscreen + ofGLWindowSettings settings; + settings.setSize(1024, 768); + settings.setGLVersion(3,2); + settings.windowMode = OF_WINDOW; //can also be OF_FULLSCREEN + + auto window = ofCreateWindow(settings); + + ofRunApp(window, std::make_shared()); + ofRunMainLoop(); + +} diff --git a/examples/input_output/svgSaveExample/src/ofApp.cpp b/examples/input_output/svgSaveExample/src/ofApp.cpp new file mode 100644 index 00000000000..e87ccfa55e2 --- /dev/null +++ b/examples/input_output/svgSaveExample/src/ofApp.cpp @@ -0,0 +1,159 @@ +#include "ofApp.h" + +//-------------------------------------------------------------- +void ofApp::setup(){ + ofSetBackgroundColor( 230 ); + + svgAddTypes = { + OFXSVG_TYPE_RECTANGLE, + OFXSVG_TYPE_CIRCLE, + OFXSVG_TYPE_PATH + }; + + svg.load("ofxSvg.svg"); + +} + +//-------------------------------------------------------------- +void ofApp::update(){ + svg.setBoundsWidth(ofGetWidth()); + svg.setBoundsHeight(ofGetHeight()); +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + svg.draw(); + ofSetColor( ofColor::cyan ); + polyline.draw(); + + std::stringstream ss; + ss << "Add Type (left/right): " << (svgTypeIndex+1) << " / " << svgAddTypes.size() << " - " << ofxSvgElement::sGetTypeAsString(svgAddTypes[svgTypeIndex]); + if( svgAddTypes[svgTypeIndex] == OFXSVG_TYPE_PATH ) { + ss << std::endl << "Click and drag mouse to add points."; + } + ss << std::endl << "Size (up/down): " << size; + ss << std::endl << "Save (s)"; + ss << std::endl << "Clear (delete)"; + ofDrawBitmapStringHighlight(ss.str(), 40, 40); +} + +//-------------------------------------------------------------- +void ofApp::exit(){ + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + if( key == OF_KEY_DEL || key == OF_KEY_BACKSPACE ) { + svg.clear(); + } + if( key == 's' ) { + std::string filename = ofGetTimestampString()+".svg"; + ofLogNotice("ofApp") << "saving svg to file: " << filename; + svg.save(filename); + } + if( key == OF_KEY_RIGHT ) { + svgTypeIndex++; + svgTypeIndex %= svgAddTypes.size(); + } + if( key == OF_KEY_LEFT ) { + svgTypeIndex--; + if( svgTypeIndex < 0 ) { + svgTypeIndex = svgAddTypes.size()-1; + } + } + if( key == OF_KEY_UP ) { + size += 2; + } + if( key == OF_KEY_DOWN ) { + size -= 2; + } + + size = ofClamp( size, 2, 2000 ); +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + if(polyline.size() > 0 ) { + polyline.addVertex(glm::vec3(x,y,0.f)); + } +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + polyline.clear(); + svg.setFilled(true); + svg.setHasStroke(false); +// svg.setStrokeWidth(0); + if( svgAddTypes[svgTypeIndex] == OFXSVG_TYPE_RECTANGLE ) { + ofRectangle rect; + rect.setFromCenter(x, y, size, size); + svg.setFillColor(ofColor(255, x%255, (y*10) % 255)); + auto svgRect = svg.add( rect ); + svgRect->setName("r-"+ofToString(ofGetFrameNum())); + } else if( svgAddTypes[svgTypeIndex] == OFXSVG_TYPE_CIRCLE ) { + svg.setFillColor(ofColor(x%255, 205, (y*10) % 255)); + auto svgCircle = svg.addCircle( glm::vec2(x,y), size ); + svgCircle->setName("c-"+ofToString(ofGetFrameNum())); + } else if( svgAddTypes[svgTypeIndex] == OFXSVG_TYPE_PATH ) { + svg.setFilled(false); + svg.setStrokeColor(ofColor::magenta); + svg.setStrokeWidth(3); + polyline.addVertex(glm::vec3(x,y,0.f)); + } + + std::cout << "------------------------------------" << std::endl; + std::cout << svg.toString() << std::endl; + std::cout << "------------------------------------" << std::endl; +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + if( svgAddTypes[svgTypeIndex] == OFXSVG_TYPE_PATH ) { + if( polyline.size() > 2 ) { + auto svgPath = svg.add(polyline); + svgPath->setName("p-"+ofToString(ofGetFrameNum())); + polyline.clear(); + } + } +} + +//-------------------------------------------------------------- +void ofApp::mouseScrolled(int x, int y, float scrollX, float scrollY){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/examples/input_output/svgSaveExample/src/ofApp.h b/examples/input_output/svgSaveExample/src/ofApp.h new file mode 100644 index 00000000000..7aa3b9fefb0 --- /dev/null +++ b/examples/input_output/svgSaveExample/src/ofApp.h @@ -0,0 +1,34 @@ +#pragma once + +#include "ofMain.h" +#include "ofxSvg.h" + +class ofApp : public ofBaseApp{ + + public: + void setup() override; + void update() override; + void draw() override; + void exit() override; + + void keyPressed(int key) override; + void keyReleased(int key) override; + void mouseMoved(int x, int y ) override; + void mouseDragged(int x, int y, int button) override; + void mousePressed(int x, int y, int button) override; + void mouseReleased(int x, int y, int button) override; + void mouseScrolled(int x, int y, float scrollX, float scrollY) override; + void mouseEntered(int x, int y) override; + void mouseExited(int x, int y) override; + void windowResized(int w, int h) override; + void dragEvent(ofDragInfo dragInfo) override; + void gotMessage(ofMessage msg) override; + + ofxSvg svg; + + std::vector svgAddTypes; + int svgTypeIndex = 0; + int size = 20; + ofPolyline polyline; + +}; diff --git a/examples/input_output/svgSaveExample/svgSaveExample.png b/examples/input_output/svgSaveExample/svgSaveExample.png new file mode 100644 index 0000000000000000000000000000000000000000..0ea5180f11712ab7f0c442d6b33eee6a43cf7c1d GIT binary patch literal 23277 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 f8EJs8(9m#a2c$VLf1mt9?qvs_v`-wVF4=~ literal 0 HcmV?d00001 From b54f4b5e29ed43666bb984c92acc4f509989cb52 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Thu, 4 Sep 2025 10:38:01 -0400 Subject: [PATCH 19/21] inline documentation and more addImage functions --- addons/ofxSvg/src/ofxSvg.cpp | 125 ++++------------------------------- addons/ofxSvg/src/ofxSvg.h | 109 ++++++++++++++++++++++++------ 2 files changed, 102 insertions(+), 132 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 5cb97434929..17bb95400c0 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -2330,16 +2330,25 @@ std::shared_ptr ofxSvg::addEllipse( const float& ax, const float& //-------------------------------------------------------------- std::shared_ptr ofxSvg::addImage( const of::filesystem::path& apath, const ofTexture& atex ) { - return addImage(glm::vec2(0.f, 0.f), apath, atex ); + return addImage(glm::vec2(0.f, 0.f), apath, atex.getWidth(), atex.getHeight() ); } //-------------------------------------------------------------- std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ) { + return addImage( apos, apath, atex.getWidth(), atex.getHeight() ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addImage( const of::filesystem::path& apath, const float& awidth, const float& aheight ) { + return addImage(glm::vec2(0.f, 0.f), apath, awidth, aheight ); +} + +//-------------------------------------------------------------- +std::shared_ptr ofxSvg::addImage( const glm::vec2& apos, const of::filesystem::path& apath, const float& awidth, const float& aheight ) { auto img = std::make_shared(); img->filepath = apath; - img->width = atex.getWidth(); - img->height = atex.getHeight(); -// _applyModelMatrixToElement( img, apos ); + img->width = awidth; + img->height = aheight; img->setPosition(apos.x, apos.y, 0.0f); _getPushedGroup()->add(img); recalculateLayers(); @@ -2359,64 +2368,6 @@ std::shared_ptr ofxSvg::addEmbeddedImage(const glm::vec2& apos, con 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::translate(const glm::vec2 & p) { -// translate(p.x, p.y); -//} -// -////---------------------------------------------------------- -//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)); -//} -// -////---------------------------------------------------------- -//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(); @@ -2466,56 +2417,6 @@ ofxSvgGroup* ofxSvg::_getPushedGroup() { return this; } -////-------------------------------------------------------------- -//bool ofxSvg::_hasPushedMatrix() { -// return mModelMatrix != glm::mat4(1.0f); -//} - -//-------------------------------------------------------------- -// TODO: CHECK ON THIS AFTER ofNode is implemented -//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 = atan2f(xAxis.y, xAxis.x); -// return angleRadians; -//} - //-------------------------------------------------------------- ofxSvgCssClass& ofxSvg::_addCssClassFromPath( std::shared_ptr aSvgPath ) { ofxSvgCssClass tcss; diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index c988aad6011..dfbe630029a 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -174,47 +174,121 @@ class ofxSvg : public ofxSvgGroup { ofxSvgCssClass& getCurrentCss() { return mCurrentCss;} ofxSvgCssStyleSheet& getCssStyleSheet() {return mSvgCss; } - /// \brief Add a group to the document. This will also push back the group as current. + /// \brief Add a ofxSvgGroup 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); + /// \brief Add a ofxSvgPath to the document. The ofxSvgPath will be added to the current group. + /// \param ofPath apath is the path to be added. + /// \return std::shared_ptr as the path that was created and added to the document. std::shared_ptr add( const ofPath& apath ); + /// \brief Add multiple ofxSvgPaths to the document. The ofxSvgPaths will be added to the current group. + /// \param std::vector apaths are the paths to be added. + /// \return std::vector< std::shared_ptr > the paths that were created and added to the document. std::vector< std::shared_ptr > add( const std::vector& apaths ); - + /// \brief Add a ofxSvgPath to the document. The ofxSvgPath will be added to the current group. + /// \param ofPolyline apoly is the polyline that will create an ofxSvgPath and added. + /// \return std::shared_ptr as the path that was created and added to the document. std::shared_ptr add( const ofPolyline& apoly ); + /// \brief Add multiple ofxSvgPaths to the document. The ofxSvgPaths will be added to the current group. + /// \param std::vector apolys are the polylines used to create and add ofxSvgPaths to the document. + /// \return std::vector< std::shared_ptr > the paths that were created and added to the document. std::vector< std::shared_ptr > add( const std::vector& apolys ); + /// \brief Add a ofxSvgRectangle to the document. The ofxSvgRectangle will be added to the current group. + /// \param ofRectangle arect is the rectangle that will create an ofxSvgRectangle. + /// \return std::shared_ptr as the rectangle that was created and added to the document. std::shared_ptr add( const ofRectangle& arect ); + /// \brief Add a ofxSvgRectangle to the document. The ofxSvgRectangle will be added to the current group. + /// \param ofRectangle arect is the rectangle that will create an ofxSvgRectangle. + /// \param float aRoundRadius is the corner radius used to round the corners. + /// \return std::shared_ptr as the rectangle that was created and added to the document. std::shared_ptr add( const ofRectangle& arect, float aRoundRadius ); + /// \brief Add a ofxSvgCircle to the document. The ofxSvgCircle will be added to the current group. + /// The default position is 0,0. Use the returned ofxSvgCircle to set properties directly. + /// \param float aradius is the radius used to create an ofxSvgCircle. + /// \return std::shared_ptr as the circle that was created and added to the document. std::shared_ptr addCircle( float aradius ); + /// \brief Add a ofxSvgCircle to the document. The ofxSvgCircle will be added to the current group. + /// \param glm::vec2 apos is the center position of the circle. + /// \param float aradius is the radius used to create an ofxSvgCircle. + /// \return std::shared_ptr as the circle that was created and added to the document. std::shared_ptr addCircle( const glm::vec2& apos, float aradius ); + /// \brief Add a ofxSvgCircle to the document. The ofxSvgCircle will be added to the current group. + /// \param glm::vec3 apos is the center position of the circle. Note: z is not used. + /// \param float aradius is the radius used to create an ofxSvgCircle. + /// \return std::shared_ptr as the circle that was created and added to the document. std::shared_ptr addCircle( const glm::vec3& apos, float aradius ); + /// \brief Add a ofxSvgCircle to the document. The ofxSvgCircle will be added to the current group. + /// \param float ax is the x center position of the circle. + /// \param float ay is the y center position of the circle. + /// \param float aradius is the radius used to create an ofxSvgCircle. + /// \return std::shared_ptr as the circle that was created and added to the document. std::shared_ptr addCircle( const float& ax, const float& ay, float aradius ); + /// \brief Add a ofxSvgEllipse to the document. The ofxSvgEllipse will be added to the current group. + /// The default position is 0,0. Use the returned ofxSvgEllipse to set properties directly. + /// \param float aradiusX is the horizontal radius used to create an ofxSvgEllipse. + /// \param float aradiusY is the vertical radius used to create an ofxSvgEllipse. + /// \return std::shared_ptr as the ellipse that was created and added to the document. std::shared_ptr addEllipse( float aradiusX, float aradiusY ); + /// \brief Add a ofxSvgEllipse to the document. The ofxSvgEllipse will be added to the current group. + /// The default position is 0,0. Use the returned ofxSvgEllipse to set properties directly. + /// \param glm::vec2 apos is the center position of the ellipse. + /// \param float aradiusX is the horizontal radius used to create an ofxSvgEllipse. + /// \param float aradiusY is the vertical radius used to create an ofxSvgEllipse. + /// \return std::shared_ptr as the ellipse that was created and added to the document. std::shared_ptr addEllipse( const glm::vec2& apos, float aradiusX, float aradiusY ); + /// \brief Add a ofxSvgEllipse to the document. The ofxSvgEllipse will be added to the current group. + /// The default position is 0,0. Use the returned ofxSvgEllipse to set properties directly. + /// \param glm::vec3 apos is the center position of the ellipse. Note: z is not used. + /// \param float aradiusX is the horizontal radius used to create an ofxSvgEllipse. + /// \param float aradiusY is the vertical radius used to create an ofxSvgEllipse. + /// \return std::shared_ptr as the ellipse that was created and added to the document. std::shared_ptr addEllipse( const glm::vec3& apos, float aradiusX, float aradiusY ); + /// \brief Add a ofxSvgEllipse to the document. The ofxSvgEllipse will be added to the current group. + /// The default position is 0,0. Use the returned ofxSvgEllipse to set properties directly. + /// \param float ax is the x center position of the ellipse. + /// \param float ay is the y center position of the ellipse. + /// \param float aradiusX is the horizontal radius used to create an ofxSvgEllipse. + /// \param float aradiusY is the vertical radius used to create an ofxSvgEllipse. + /// \return std::shared_ptr as the ellipse that was created and added to the document. std::shared_ptr addEllipse( const float& ax, const float& ay, float aradiusX, float aradiusY ); + /// \brief Add a ofxSvgImage to the document. The ofxSvgImage will be added to the current group. + /// The default position is 0,0. Use the returned ofxSvgImage to set properties directly. + /// \param of::filesystem::path apath is the path to the linked image file. + /// \param ofTexture atex provides the width and height of the image. + /// \return std::shared_ptr as the image that was created and added to the document. std::shared_ptr addImage( const of::filesystem::path& apath, const ofTexture& atex ); + /// \brief Add a ofxSvgImage to the document. The ofxSvgImage will be added to the current group. + /// \param glm::vec2 apos is the position; anchored to the top left. + /// \param of::filesystem::path apath is the path to the linked image file. + /// \param ofTexture atex provides the width and height of the image. + /// \return std::shared_ptr as the image that was created and added to the document. std::shared_ptr addImage( const glm::vec2& apos, const of::filesystem::path& apath, const ofTexture& atex ); + /// \brief Add a ofxSvgImage to the document. The ofxSvgImage will be added to the current group. + /// \param of::filesystem::path apath is the path to the linked image file. + /// \param float awidth provides the width of the image. + /// \param float aheight provides the height of the image. + /// \return std::shared_ptr as the image that was created and added to the document. + std::shared_ptr addImage( const of::filesystem::path& apath, const float& awidth, const float& aheight ); + /// \brief Add a ofxSvgImage to the document. The ofxSvgImage will be added to the current group. + /// \param glm::vec2 apos is the position; anchored to the top left. + /// \param of::filesystem::path apath is the path to the linked image file. + /// \param float awidth provides the width of the image. + /// \param float aheight provides the height of the image. + /// \return std::shared_ptr as the image that was created and added to the document. + std::shared_ptr addImage( const glm::vec2& apos, const of::filesystem::path& apath, const float& awidth, const float& aheight ); + /// \brief Add a ofxSvgImage to the document. The ofxSvgImage will be added to the current group. + /// \param glm::vec2 apos is the position; anchored to the top left. + /// \param ofPixels apixels is the pixels to embed in the svg document. Note: This can increase file size dramatically. + /// \return std::shared_ptr as the image that was created and added to the document. std::shared_ptr addEmbeddedImage( const glm::vec2& apos, const ofPixels& apixels ); - // adapted from ofGLProgrammableRenderer for some sort of conformity - // this will be handled by the ofNode functionality -// 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(); - + /// \brief Used for development to provide insight into anchor point / control point placements. virtual void drawDebug(); protected: @@ -243,11 +317,6 @@ class ofxSvg : public ofxSvgGroup { void _setNodeParentGroupStack( std::shared_ptr aele ); ofxSvgGroup* _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 ); ofxSvgCssClass& _addCssClassFromPath( std::shared_ptr aSvgPath ); void _addCssClassFromPath( std::shared_ptr aSvgPath, ofXml& anode ); From 82fd0e0f623af738047b2b4bdaaa9482df7185d0 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Thu, 4 Sep 2025 21:44:29 -0400 Subject: [PATCH 20/21] added remove functionality and update svgSave example to use remove functions. --- addons/ofxSvg/src/ofxSvg.cpp | 20 +++++++ addons/ofxSvg/src/ofxSvg.h | 9 +++ addons/ofxSvg/src/ofxSvgElements.cpp | 2 +- addons/ofxSvg/src/ofxSvgGroup.cpp | 57 +++++++++++++++++++ addons/ofxSvg/src/ofxSvgGroup.h | 37 ++++++++++++ .../input_output/svgSaveExample/src/ofApp.cpp | 28 ++++++++- 6 files changed, 150 insertions(+), 3 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 17bb95400c0..3e0c64f1fb4 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -2368,6 +2368,26 @@ std::shared_ptr ofxSvg::addEmbeddedImage(const glm::vec2& apos, con return img; } +//-------------------------------------------------------------- +bool ofxSvg::remove( std::shared_ptr aelement ) { + bool bRemoved = ofxSvgGroup::remove(aelement); + if( bRemoved ) { + recalculateLayers(); + } + return bRemoved; +} + +////-------------------------------------------------------------- +//bool ofxSvg::remove( std::vector > aelements ) { +// bool bAllRemoved = ofxSvgGroup::remove(aelements); +// // we should just recalculate the layers if there was more than one element +// // since the function only returns true if all of the elements were found and removed. +// if( aelements.size() > 0 ) { +// recalculateLayers(); +// } +// return bAllRemoved; +//} + //-------------------------------------------------------------- void ofxSvg::drawDebug() { // Group::draw(); diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index dfbe630029a..67fa5a5fd81 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -288,6 +288,15 @@ class ofxSvg : public ofxSvgGroup { /// \return std::shared_ptr as the image that was created and added to the document. std::shared_ptr addEmbeddedImage( const glm::vec2& apos, const ofPixels& apixels ); + /// \brief Remove an element from this document or child groups. + /// \param shared_ptr aelement to be removed. + /// \return bool true if element was found and removed. + bool remove( std::shared_ptr aelement ) override; + /// \brief Remove elements in a vector from this document or child groups. + /// \param vector > aelements Elements to be removed. + /// \return bool true if all of the elements were found and removed. +// bool remove( std::vector > aelements ) override; + /// \brief Used for development to provide insight into anchor point / control point placements. virtual void drawDebug(); diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index d6a46280232..4d45ce3b765 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -120,7 +120,7 @@ string ofxSvgElement::toString( int nlevel ) { for( int k = 0; k < nlevel; k++ ) { tstr += " "; } - tstr += getTypeAsString() + " - " + getName() + "\n"; + tstr += ofToString(layer)+": " + getTypeAsString() + " - " + getName() + "\n"; return tstr; } diff --git a/addons/ofxSvg/src/ofxSvgGroup.cpp b/addons/ofxSvg/src/ofxSvgGroup.cpp index 130c48f758b..acd0bdea745 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.cpp +++ b/addons/ofxSvg/src/ofxSvgGroup.cpp @@ -240,6 +240,10 @@ void ofxSvgGroup::_replaceElementRecursive( shared_ptr aTarget, s aBSuccessful = true; aElements[i] = aNew; aNew->layer = aTarget->layer; + if( aTarget->getParent() ) { + aNew->setParent(*aTarget->getParent(), true); + aTarget->clearParent(); + } break; } if( !bFound ) { @@ -251,6 +255,59 @@ void ofxSvgGroup::_replaceElementRecursive( shared_ptr aTarget, s } } +//-------------------------------------------------------------- +bool ofxSvgGroup::remove( std::shared_ptr aelement ) { + if( !aelement ) { + ofLogWarning("ofxSvgGroup::remove") << "element is invalid."; + return false; + } + bool bRemoved = false; + _removeElementRecursive( aelement, mChildren, bRemoved ); + return bRemoved; +} + +////-------------------------------------------------------------- +//bool ofxSvgGroup::remove( std::vector > aelements ) { +// if( aelements.size() < 1 ) { +// return false; +// } +// +// bool bAllRemoved = true; +// for( auto& aele : aelements ) { +// if( !aele ) { +// ofLogWarning("ofxSvgGroup::remove") << "element is invalid."; +// bAllRemoved = false; +// continue; +// } +// bool bEleRemoved = remove( aele ); +// if( !bEleRemoved ) { +// bAllRemoved = false; +// } +// } +// return bAllRemoved; +//} + +//-------------------------------------------------------------- +void ofxSvgGroup::_removeElementRecursive( shared_ptr aTarget, 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.erase(aElements.begin() + i); + // make sure to break here since the size() will change + break; + } + if( !bFound ) { + if( aElements[i]->getType() == OFXSVG_TYPE_GROUP ) { + auto tgroup = std::dynamic_pointer_cast( aElements[i] ); + _removeElementRecursive(aTarget, tgroup->mChildren, aBSuccessful ); + } + } + } +} + //-------------------------------------------------------------- string ofxSvgGroup::toString(int nlevel) { diff --git a/addons/ofxSvg/src/ofxSvgGroup.h b/addons/ofxSvg/src/ofxSvgGroup.h index 0302c87f1c1..753d11b239d 100755 --- a/addons/ofxSvg/src/ofxSvgGroup.h +++ b/addons/ofxSvg/src/ofxSvgGroup.h @@ -308,8 +308,40 @@ class ofxSvgGroup : public ofxSvgElement { return getFirstElementForType(""); } + /// \brief Replace an item with a new item. + /// \param shared_ptr aOriginal Element to be replaced. + /// \param shared_ptr aNew Element used to replace aOriginal. + /// \return bool true if aOriginal was replaced. bool replace( std::shared_ptr aOriginal, std::shared_ptr aNew ); + /// \brief Remove an element from this group or child groups. + /// \param shared_ptr aelement to be removed. + /// \return bool true if element was found and removed. + virtual bool remove( std::shared_ptr aelement ); + /// \brief Remove elements in a vector from this group or child groups. Example: svg.remove(); + /// \param vector > aelements to be removed. + /// \return bool true if all of the elements were found and removed. + template + bool removeElements( std::vector > aelements ) { + if( aelements.size() < 1 ) { + return false; + } + + bool bAllRemoved = true; + for( auto& aele : aelements ) { + if( !aele ) { + ofLogWarning("ofxSvgGroup::remove") << "element is invalid."; + bAllRemoved = false; + continue; + } + bool bEleRemoved = remove( aele ); + if( !bEleRemoved ) { + bAllRemoved = false; + } + } + return bAllRemoved; + } + /// \brief Add a child element of provided type. /// \param std::string aname Name to give the created element. /// \return std::shared_ptr. @@ -354,6 +386,11 @@ class ofxSvgGroup : public ofxSvgElement { bool& aBSuccessful ); + void _removeElementRecursive(std::shared_ptr aTarget, + std::vector >& aElements, + bool& aBSuccessful + ); + std::vector< std::shared_ptr > mChildren; diff --git a/examples/input_output/svgSaveExample/src/ofApp.cpp b/examples/input_output/svgSaveExample/src/ofApp.cpp index e87ccfa55e2..43dd197b8ee 100644 --- a/examples/input_output/svgSaveExample/src/ofApp.cpp +++ b/examples/input_output/svgSaveExample/src/ofApp.cpp @@ -32,6 +32,8 @@ void ofApp::draw(){ ss << std::endl << "Click and drag mouse to add points."; } ss << std::endl << "Size (up/down): " << size; + ss << std::endl << "Remove circles (c)"; + ss << std::endl << "Remove rectangles (r)"; ss << std::endl << "Save (s)"; ss << std::endl << "Clear (delete)"; ofDrawBitmapStringHighlight(ss.str(), 40, 40); @@ -52,6 +54,28 @@ void ofApp::keyPressed(int key){ ofLogNotice("ofApp") << "saving svg to file: " << filename; svg.save(filename); } + + if( key == 'c' ) { + // get all of the circles + auto circles = svg.getAllElementsForType(); + // now lets remove all of them. + svg.removeElements( circles ); + // another option would be to loop through the circles and call remove on each one. +// for( auto& circle : circles ) { +// svg.remove(circle); +// } + } + if( key == 'r' ) { + // our original document contains rectangles + // or maybe paths since Illustrator sometimes converts them. + // lets try grabbing them by name. + // The second argument of this function determines if the name is strictly matched or not. + // We pass in false because we name our rectangles with myrect + frame num. + // So we want to get all of the rects that contain the string "myrect" in their name. + auto myRects = svg.getAllElementsForTypeForName("myrect", false); + svg.removeElements(myRects); + } + if( key == OF_KEY_RIGHT ) { svgTypeIndex++; svgTypeIndex %= svgAddTypes.size(); @@ -100,11 +124,11 @@ void ofApp::mousePressed(int x, int y, int button){ rect.setFromCenter(x, y, size, size); svg.setFillColor(ofColor(255, x%255, (y*10) % 255)); auto svgRect = svg.add( rect ); - svgRect->setName("r-"+ofToString(ofGetFrameNum())); + svgRect->setName("myrect-"+ofToString(ofGetFrameNum())); } else if( svgAddTypes[svgTypeIndex] == OFXSVG_TYPE_CIRCLE ) { svg.setFillColor(ofColor(x%255, 205, (y*10) % 255)); auto svgCircle = svg.addCircle( glm::vec2(x,y), size ); - svgCircle->setName("c-"+ofToString(ofGetFrameNum())); + svgCircle->setName("mycircle-"+ofToString(ofGetFrameNum())); } else if( svgAddTypes[svgTypeIndex] == OFXSVG_TYPE_PATH ) { svg.setFilled(false); svg.setStrokeColor(ofColor::magenta); From 70fd1dbeccf5a88885ea58b9dfff0fa1e2c4b7a3 Mon Sep 17 00:00:00 2001 From: NickHardeman Date: Sun, 7 Sep 2025 10:39:10 -0400 Subject: [PATCH 21/21] fix for paths with all closed sub paths to use default winding mode of OF_POLY_WINDING_NONZERO with a function to set it differently. --- addons/ofxSvg/src/ofxSvg.cpp | 101 ++++++++++----------------- addons/ofxSvg/src/ofxSvg.h | 14 ++-- addons/ofxSvg/src/ofxSvgElements.cpp | 12 ++-- addons/ofxSvg/src/ofxSvgFontBook.cpp | 4 +- 4 files changed, 54 insertions(+), 77 deletions(-) diff --git a/addons/ofxSvg/src/ofxSvg.cpp b/addons/ofxSvg/src/ofxSvg.cpp index 3e0c64f1fb4..4a85953595b 100755 --- a/addons/ofxSvg/src/ofxSvg.cpp +++ b/addons/ofxSvg/src/ofxSvg.cpp @@ -1106,6 +1106,15 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { return; } +// OF_POLY_WINDING_ODD + /// OF_POLY_WINDING_NONZERO + /// OF_POLY_WINDING_POSITIVE + /// OF_POLY_WINDING_NEGATIVE + /// OF_POLY_WINDING_ABS_GEQ_TWO +// aSvgPath->path.setPolyWindingMode(OF_POLY_WINDING_POSITIVE); +// aSvgPath->path.setPolyWindingMode(mDefaultPathWindingMode); + + aSvgPath->path.setCircleResolution(mCircleResolution); aSvgPath->path.setCurveResolution(mCurveResolution); @@ -1137,6 +1146,9 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { glm::vec3 secondControlPoint = currentPos; glm::vec3 qControlPoint = currentPos; + int numSubPathsClosed = 0; + int numSubPaths = 0; + auto convertToAbsolute = [](bool aBRelative, glm::vec3& aCurrentPos, std::vector& aposes) -> glm::vec3 { for(auto& apos : aposes ) { if( aBRelative ) { @@ -1283,36 +1295,27 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { for( int ni = 0; ni < npositions.size(); ni++ ) { ofLogVerbose("ofxSvg::_parsePath") << ni << "->" << npositions[ni]; } -// if( npositions.size() > 0 && bRelative ) { -// mCurrentPathPos = npositions[0]; -// } ctype = ofPath::Command::moveTo; + numSubPaths++; } else if( cchar == 'v' || cchar == 'V' ) { float xvalue = 0.f; if( cchar == 'v' ) { bRelative = true; -// npositions[0].x = 0.f; } else { -// npositions[0].x = currentPos.x; xvalue = currentPos.x; } npositions = parsePointsDefaultX(currentString,xvalue); -// npositions[0].y = ofToFloat(currentString); //ofLogVerbose("ofxSvg") << cchar << " line to: " << npositions[0] << " current pos: " << currentPos; ctype = ofPath::Command::lineTo; } else if( cchar == 'H' || cchar == 'h' ) { float yvalue = 0.f; if( cchar == 'h' ) { bRelative = true; -// npositions[0].y = 0.f; } else { -// npositions[0].y = currentPos.y; yvalue = currentPos.y; } npositions = parsePointsDefaultY(currentString,yvalue); -// npositions[0].x = ofToFloat(currentString); - ctype = ofPath::Command::lineTo; } else if( cchar == 'L' || cchar == 'l' ) { if( cchar == 'l' ) { @@ -1401,7 +1404,6 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { // going to handle this below; // inside the if( commandT == ofPath::Command::moveTo ) { check. } else { -// currentPos = convertToAbsolute(bRelative, currentPos, npositions ); currentPos = convertToAbsolute(bRelative, currentPos, npositions ); } } @@ -1434,7 +1436,6 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { if( npositions.size() > 0 ) { ofLogVerbose("ofxSvg::moveTo") << npositions[0] << " currentPos: " << currentPos;// << " path pos: " << aSvgPath->pos; aSvgPath->path.moveTo(currentPos); -// mCenterPoints.push_back(npositions[0] + pathOffset); } if(npositions.size() > 1 ) { @@ -1446,19 +1447,12 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { if( bLineToRelative ) { for( int ki = 1; ki < npositions.size(); ki++ ) { -// auto newPos = npositions[ki] + cp; currentPos += npositions[ki]; aSvgPath->path.lineTo(currentPos); -// cp = newPos; } -// currentPos = cp; - } else { for( int ki = 1; ki < npositions.size(); ki++ ) { -// mCPoints.push_back(npositions[ki]); -// ofLogVerbose("ofxSvg::lineTo") << ki << "--->" << npositions[ki]; aSvgPath->path.lineTo(npositions[ki]); -// mCenterPoints.push_back(npositions[ki] + pathOffset); } if(npositions.size() > 0 ) { currentPos = npositions.back(); @@ -1484,12 +1478,11 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } } } -// aSvgPath->path.moveTo(currentPos); -// aSvgPath->path.lineTo(currentPos); } else if( commandT == ofPath::Command::close ) { // ofLogNotice("ofxSvg") << "Closing the path"; // TODO: Not sure if we need to draw a line to the start point here aSvgPath->path.close(); + numSubPathsClosed++; } else if( commandT == ofPath::Command::bezierTo ) { if( cchar == 'S' || cchar == 's' ) { @@ -1507,12 +1500,6 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } npositions = ppositions; - -// if( npositions.size() == 2 ) { -// auto cp2 = (secondControlPoint - prevPos) * -1.f; -// cp2 += prevPos; -// npositions.insert(npositions.begin(), cp2 ); -// } } auto tcpos = prevPos; @@ -1525,19 +1512,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { 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 ) { @@ -1564,9 +1539,7 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { 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 ); @@ -1629,19 +1602,12 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { 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() ) { @@ -1666,11 +1632,6 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } } -// 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(); @@ -1682,6 +1643,14 @@ void ofxSvg::_parsePath( ofXml& tnode, std::shared_ptr aSvgPath ) { } // ofLogNotice("ofxSvg") << "["<path.setPolyWindingMode(mDefaultClosedPathWindingMode); + } justInCase++; } @@ -1711,6 +1680,7 @@ ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { // now lets try to apply it to the path auto& tCss = mSvgCss.getClass(className); for( auto& tprop : tCss.properties ) { +// ofLogNotice("ofxSvg") << " adding property " << tprop.first << " value: " << tprop.second.srcString; css.addProperty(tprop.first, tprop.second); } } @@ -1723,7 +1693,7 @@ ofxSvgCssClass ofxSvg::_parseStyle( ofXml& anode ) { // 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" + "x","y","x1","y1","x2","y2","transform" }; // lets try to do this a better way @@ -1825,7 +1795,6 @@ glm::vec3 ofxSvg::_parseMatrixString(const std::string& input, const std::string //-------------------------------------------------------------- glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr aele ) { ofLogVerbose("-----------ofxSvg::setTransformFromSvgMatrixString") << aele->getTypeAsString() << " name: " << aele->getName() +"----------------"; -// aele->scale = glm::vec2(1.0f, 1.0f); // aele->rotation = 0.0; aele->setScale(1.f); aele->mModelRotationPoint = glm::vec2(0.0f, 0.0f); @@ -1834,7 +1803,6 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< float trotation = 0.f; glm::mat4 mat = glm::mat4(1.f); -// glm::mat4 gmat = mModelMatrix; if( ofIsStringInString(aStr, "translate")) { auto transStr = aStr; @@ -1900,7 +1868,7 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< // gmat = glm::scale(gmat, glm::vec3(aele->getScale().x, aele->getScale().y, 1.f)); } - glm::vec3 pos3 = mat * glm::vec4( aele->getPosition().x, aele->getPosition().y, 0.0f, 1.f ); +// glm::vec3 pos3 = mat * glm::vec4( aele->getPosition().x, aele->getPosition().y, 0.0f, 1.f ); // pos3 = gmat * glm::vec4( aele->pos.x, aele->pos.y, 0.0f, 1.f ); // aele->pos.x = pos3.x; // aele->pos.y = pos3.y; @@ -1928,10 +1896,8 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< aele->setPosition(matrixF[4], matrixF[5], 0.f); - float trotation = glm::degrees( atan2f(matrixF[1],matrixF[0]) ); - // 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]); float sx = glm::sqrt(matrixF[0] * matrixF[0] + matrixF[1] * matrixF[1]); @@ -1956,11 +1922,6 @@ glm::mat4 ofxSvg::setTransformFromSvgMatrixString( string aStr, std::shared_ptr< mat = glm::scale(mat, glm::vec3(aele->getScale().x, aele->getScale().y, 1.f)); -// pos3 = mat * glm::vec4( aele->getPosition().x, aele->getPosition().y, 0.0f, 1.f ); -// aele->setPosition( pos3.x, pos3.y, 0.0f); -// aele->pos.x = pos3.x; -// aele->pos.y = pos3.y; - ofLogVerbose("ofxSvg::setTransformFromSvgMatrixString") << "pos: " << aele->getPosition() << " rotation: " << trotation << " scale: " << aele->getScale(); } } @@ -2187,6 +2148,16 @@ void ofxSvg::setHasStroke(bool abStroke) { } } +//-------------------------------------------------------------- +void ofxSvg::setDefaultClosedPathWindingMode( ofPolyWindingMode aWindingMode ) { + mDefaultClosedPathWindingMode = aWindingMode; +} + +//-------------------------------------------------------------- +ofPolyWindingMode ofxSvg::getDefaultClosedPathWindingMode() { + return mDefaultClosedPathWindingMode; +} + //-------------------------------------------------------------- std::shared_ptr ofxSvg::addGroup(std::string aname) { auto tgroup = std::make_shared(); diff --git a/addons/ofxSvg/src/ofxSvg.h b/addons/ofxSvg/src/ofxSvg.h index 67fa5a5fd81..c58438bc3b9 100755 --- a/addons/ofxSvg/src/ofxSvg.h +++ b/addons/ofxSvg/src/ofxSvg.h @@ -172,6 +172,14 @@ class ofxSvg : public ofxSvgGroup { /// \brief Get the current css used for items. /// \return ofxSvgCssClass. ofxSvgCssClass& getCurrentCss() { return mCurrentCss;} + /// \brief Set the default winding mode of imported paths with all sub paths that are closed. The default is OF_POLY_WINDING_NONZERO. + /// If not all of the sub paths are closed, the winding mode is not set. + /// Must be called before load() to apply to newly imported paths. + /// \param ofPolyWindingMode to be used as default. + void setDefaultClosedPathWindingMode( ofPolyWindingMode aWindingMode ); + /// \brief Get the default winding mode of imported paths. + /// \return ofPolyWindingMode. + ofPolyWindingMode getDefaultClosedPathWindingMode(); ofxSvgCssStyleSheet& getCssStyleSheet() {return mSvgCss; } /// \brief Add a ofxSvgGroup to the document. This will also push back the group as current. @@ -292,10 +300,6 @@ class ofxSvg : public ofxSvgGroup { /// \param shared_ptr aelement to be removed. /// \return bool true if element was found and removed. bool remove( std::shared_ptr aelement ) override; - /// \brief Remove elements in a vector from this document or child groups. - /// \param vector > aelements Elements to be removed. - /// \return bool true if all of the elements were found and removed. -// bool remove( std::vector > aelements ) override; /// \brief Used for development to provide insight into anchor point / control point placements. virtual void drawDebug(); @@ -365,6 +369,8 @@ class ofxSvg : public ofxSvgGroup { static ofPath sDummyPath; mutable std::vector mPaths; + ofPolyWindingMode mDefaultClosedPathWindingMode = OF_POLY_WINDING_NONZERO; + }; diff --git a/addons/ofxSvg/src/ofxSvgElements.cpp b/addons/ofxSvg/src/ofxSvgElements.cpp index 4d45ce3b765..2d9128aca26 100755 --- a/addons/ofxSvg/src/ofxSvgElements.cpp +++ b/addons/ofxSvg/src/ofxSvgElements.cpp @@ -786,19 +786,19 @@ void ofxSvgText::TextSpan::applyStyle(ofxSvgCssClass& aclass) { if( !aclass.hasProperty("color") ) { if( aclass.hasProperty("fill")) { - aclass.addProperty("color", aclass.getColor("fill") ); + mSvgCssClass.addProperty("color", aclass.getColor("fill") ); } else { - aclass.addProperty("color", ofColor(0)); + mSvgCssClass.addProperty("color", ofColor(0)); } } alpha = 1.f; - if( aclass.hasProperty("opacity")) { - alpha = aclass.getFloatValue("opacity", 1.f); + if( mSvgCssClass.hasProperty("opacity")) { + alpha = mSvgCssClass.getFloatValue("opacity", 1.f); } - ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << "text: " << text; - ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << " css class: " << aclass.toString() << std::endl;// << " color: " << color; + ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << "text: " << text << " has fill: " << mSvgCssClass.hasProperty("fill") << " color: " << getColor(); + ofLogVerbose("ofxSvgText::TextSpan::applyStyle") << " css class: " << mSvgCssClass.toString() << std::endl;// << " color: " << color; } diff --git a/addons/ofxSvg/src/ofxSvgFontBook.cpp b/addons/ofxSvg/src/ofxSvgFontBook.cpp index 9fb2f4a5571..f9514a48e10 100644 --- a/addons/ofxSvg/src/ofxSvgFontBook.cpp +++ b/addons/ofxSvg/src/ofxSvgFontBook.cpp @@ -105,7 +105,7 @@ bool ofxSvgFontBook::loadFont(const of::filesystem::path& aDirectory, ofxSvgCssC bf = false; } - ofLogNotice("ofxSvgFontBook") << __FUNCTION__ << " : " << fs.str() << " : starting off searching directory : " << fontsDirectory; + ofLogVerbose("ofxSvgFontBook") << __FUNCTION__ << " : " << fs.str() << " : starting off searching directory : " << fontsDirectory; string tNewFontPath = ""; std::vector subStrs; @@ -193,7 +193,7 @@ bool ofxSvgFontBook::loadFont(const of::filesystem::path& aDirectory, ofxSvgCssC - ofLogNotice("ofxSvgFontBook") << __FUNCTION__ << " : Trying to load font from: " << tfontPath; + ofLogVerbose("ofxSvgFontBook") << __FUNCTION__ << " : Trying to load font from: " << tfontPath; if (tfontPath == "") { bFontLoadOk = false;