#include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" #include "cwFile.h" #include "cwSvg.h" #include "cwText.h" namespace cw { namespace svg { enum { kLineTId, kPLineTId, kRectTId, kTextTId }; typedef struct attr_str { unsigned strokeColor; unsigned strokeWidth; double strokeOpacity; unsigned fillColor; double fillOpacity; } attr_t; typedef struct css_str { unsigned id; char* label; attr_t attr; struct css_str* link; } css_t; typedef struct ele_str { unsigned typeId; double x0; double y0; double x1; double y1; char* text; double* xV; double* yV; unsigned yN; attr_t attr; unsigned cssClassId; unsigned flags; struct ele_str* link; } ele_t; typedef struct color_map_str { unsigned id; unsigned colorN; unsigned* colorV; struct color_map_str* link; } color_map_t; typedef struct svg_str { unsigned strokeColorIdx; unsigned strokeWidth; unsigned fillColorIdx; css_t* cssL; color_map_t* cmapL; ele_t* eleL; double dx; double dy; ele_t* curEle; } svg_t; inline svg_t* _handleToPtr(handle_t h ) { return handleToPtr(h); } rc_t _destroy( struct svg_str* p ) { ele_t* e = p->eleL; while( e != nullptr ) { ele_t* e0 = e->link; mem::release(e->text); mem::release(e->xV); mem::release(e->yV); mem::release(e); e = e0; } color_map_t* c = p->cmapL; while( c != nullptr ) { color_map_t* c0 = c->link; mem::release(c->colorV); mem::release(c); c = c0; } css_t* r = p->cssL; while( r != nullptr ) { css_t* r0 = r->link; mem::release(r->label); mem::release(r); r = r0; } mem::release(p); return kOkRC; } rc_t _install_cmap( svg_t* p, unsigned id, unsigned* cV, unsigned cN ) { color_map_t* cmap = mem::allocZ(1); cmap->id = id; cmap->colorN = cN; cmap->colorV = cV; cmap->link = p->cmapL; p->cmapL = cmap; return kOkRC; } color_map_t* _cmap_from_id( svg_t* p, unsigned id ) { color_map_t* cm; for(cm=p->cmapL; cm!=nullptr; cm=cm->link) if( cm->id == id ) return cm; cwLogWarning("Unknown color map id:%i.", id ); return nullptr; } rc_t _install_basic_eight_cmap( svg_t* p ) { unsigned colorN = 8; unsigned* colorV = mem::allocZ(colorN); colorV[0] = 0x0000ff; colorV[1] = 0x00ff00; colorV[2] = 0xff0000; colorV[3] = 0x00ffff; colorV[4] = 0xffff00; colorV[5] = 0xff00ff; colorV[6] = 0x000000; colorV[7] = 0xffffff; return _install_cmap( p, kStandardColorMapId, colorV, colorN ); } rc_t _install_gray_scale_cmap( svg_t* p ) { unsigned colorN = 255; unsigned* colorV = mem::allocZ(colorN); for(unsigned i=0; i(cm->colorN); for(unsigned i=0; icolorN; ++i) colorV[i] = 0xffffff - cm->colorV[i]; return _install_cmap( p, kInvGrayScaleColorMapId, colorV, cm->colorN ); } rc_t _install_heat_cmap( svg_t* p ) { unsigned colorN = 255; unsigned* colorV = mem::allocZ(colorN); double rV[] = { 0.0, 0.0, 1.0, 1.0 }; double gV[] = { 1.0, 0.0, 0.0, 1.0 }; double bV[] = { 1.0, 1.0, 0.0, 0.0 }; const unsigned xN = cwCountOf(rV); for(unsigned i=1,k=0; i(1); r->id = cssClassId; r->label = mem::duplStr(cssClassLabel==nullptr?" ":cssClassLabel); r->attr.strokeColor = strokeColor; r->attr.strokeWidth = strokeWidth; r->attr.strokeOpacity = strokeOpacity; r->attr.fillColor = fillColor; r->attr.fillOpacity = fillOpacity; r->link = p->cssL; p->cssL = r; return kOkRC; } rc_t _insert( svg_t* p, unsigned typeId, double x, double y, double w, double h, unsigned cssClassId, const char* text, const double* yV=nullptr, unsigned yN=0, const double* xV=nullptr ) { rc_t rc = kOkRC; ele_t* e = mem::allocZ(1); e->typeId = typeId; e->x0 = x; e->y0 = y; e->x1 = x+w; e->y1 = y+h; e->text = mem::duplStr(text); e->cssClassId = cssClassId; e->yV = yN>0 && yV!=nullptr ? mem::allocDupl(yV,yN) : nullptr; e->xV = yN>0 && xV!=nullptr ? mem::allocDupl(xV,yN) : nullptr; e->yN = yN; if( yN == 0 ) { e->x0 += p->dx; e->y0 += p->dy; e->x1 += p->dx; e->y1 += p->dy; } else { if( e->xV == nullptr ) { e->xV = mem::alloc(e->yN); for(unsigned i=0; iyN; ++i) e->xV[i] = i; } for(unsigned i=0; iyV[i] += p->dy; e->xV[i] += p->dx; } } ele_t* e0 = nullptr; ele_t* e1 = p->eleL; while( e1!=nullptr) { e0 = e1; e1 = e1->link; } if( e0 == NULL ) p->eleL = e; else e0->link = e; p->curEle = e; return rc; } void _offsetEles(svg_t* p, double dx, double dy ) { for(ele_t* e=p->eleL; e!=nullptr; e=e->link) { if( e->yN == 0 ) { e->x0 += dx; e->x1 += dx; e->y0 += dy; e->y1 += dy; } else { for(unsigned i=0; iyN; ++i) { e->yV[i] += dy; e->xV[i] += dx; } } } } void _ele_extents( ele_t* e, double& min_x, double& min_y, double& max_x, double& max_y ) { if( e->yN==0 ) { min_x = std::min(e->x0,e->x1); max_x = std::max(e->x0,e->x1); min_y = std::min(e->y0,e->y1); max_y = std::max(e->y0,e->y1); } else { min_y = e->yV[0]; max_y = e->yV[0]; min_x = e->xV[0]; max_x = e->xV[0]; for(unsigned i=0; iyN; ++i) { min_x = std::min( min_x, e->yV[i] ); max_x = std::max( max_x, e->yV[i] ); min_y = std::min( min_y, e->xV[i] ); max_y = std::max( max_y, e->xV[i] ); } } } void _extents( svg_t* p, double& min_x, double& min_y, double& max_x, double& max_y ) { if( p->eleL == NULL ) return; ele_t* e = p->eleL; _ele_extents(e,min_x,min_y,max_x,max_y); for(e=e->link; e!=NULL; e=e->link) { double e_min_x,e_min_y,e_max_x,e_max_y; _ele_extents(e,e_min_x,e_min_y,e_max_x,e_max_y); min_x = std::min(min_x,e_min_x); max_x = std::max(max_x,e_max_x); min_y = std::min(min_y,e_min_y); max_y = std::max(max_y,e_max_y); } } double _size( svg_t* p, double& widthRef, double& heightRef ) { double min_x,min_y,max_x,max_y; widthRef = 0; heightRef = 0; _extents( p, min_x, min_y, max_x, max_y ); widthRef = max_x - min_x; heightRef = max_y - min_y; return max_y; } void _flipY( svg_t* p, unsigned height ) { ele_t* e = p->eleL; for(; e!=NULL; e=e->link) { if( e->yN == 0 ) { e->y0 = (-e->y0) + height; e->y1 = (-e->y1) + height; if( e->typeId == kRectTId ) { double t = e->y1; e->y1 = e->y0; e->y0 = t; } } else { for(unsigned i=0; iyN; ++i) e->yV[i] = (-e->yV[i]) + height; } } } rc_t _writeCssFile( svg_t* p, const char* fn ) { rc_t rc; file::handle_t fH; css_t* r; if((rc = file::open(fH,fn,file::kWriteFl)) != kOkRC ) return cwLogError(rc,"CSS file create failed on '%s'.",cwStringNullGuard(fn)); for(r=p->cssL; r!=nullptr; r=r->link) { file::printf(fH,".%s {\nstroke:#%06x;\nfill:#%06x;\nstroke-width:%i;\nstroke-opacity:%f;\nfill-opacity:%f\n}\n", r->label,r->attr.strokeColor,r->attr.fillColor,r->attr.strokeWidth,r->attr.strokeOpacity,r->attr.fillOpacity); } file::close(fH); return rc; } css_t* _cssIdToRecd( svg_t* p, unsigned cssClassId ) { css_t* r = p->cssL; for(; r!=nullptr; r=r->link) if( r->id == cssClassId ) return r; return nullptr; } rc_t _allocStyleString( svg_t* p, const ele_t* e, bool genInlineFl, char*& s ) { s = nullptr; css_t* r; if((r = _cssIdToRecd(p,e->cssClassId)) == nullptr ) return cwLogError(kGetAttrFailRC,"Unable to locate SVG class id %i.", e->cssClassId ); if( genInlineFl || e->flags != 0 ) { attr_t a; a.strokeColor = cwIsFlag(e->flags,kStrokeColorArgId) ? e->attr.strokeColor : r->attr.strokeColor; a.strokeOpacity = cwIsFlag(e->flags,kStrokeOpacityArgId) ? e->attr.strokeOpacity : r->attr.strokeOpacity; a.strokeWidth = cwIsFlag(e->flags,kStrokeWidthArgId) ? e->attr.strokeWidth : r->attr.strokeWidth; a.fillColor = cwIsFlag(e->flags,kFillColorArgId) ? e->attr.fillColor : r->attr.fillColor; a.fillOpacity = cwIsFlag(e->flags,kFillOpacityArgId) ? e->attr.fillOpacity : r->attr.fillOpacity; s = mem::printf(s,"style=\"stroke:#%06x;fill:#%06x;stroke-width:%i;stroke-opacity:%f;fill-opacity:%f\"",a.strokeColor,a.fillColor,a.strokeWidth,a.strokeOpacity,a.fillOpacity); } else { s = mem::printf(s,"class=\"%s\"",r->label); } return kOkRC; } char* _write_pline(char* bodyStr, ele_t* e, const char* styleStr) { if( e->yN == 0 ) return bodyStr; bodyStr = mem::printp(bodyStr,"yN; ++i) bodyStr = mem::printp(bodyStr,"%f,%f ", e->xV[i], e->yV[i] ); return mem::printp(bodyStr,"\" %s />",styleStr); } } } cw::rc_t cw::svg::create( handle_t& h ) { rc_t rc; if((rc = destroy(h)) != kOkRC ) return rc; svg_t* p = mem::allocZ(1); p->strokeColorIdx = kInvalidIdx; p->strokeWidth = 1; p->fillColorIdx = kInvalidIdx; _install_basic_eight_cmap(p); _install_gray_scale_cmap(p); _install_inv_gray_scale_cmap(p); _install_heat_cmap(p); _install_css(p, kRectCssId, "rect" ); _install_css(p, kLineCssId, "line" ); _install_css(p, kPLineCssId,"pline" ); _install_css(p, kTextCssId, "text",0,1,0 ); h.set(p); return rc; } cw::rc_t cw::svg::destroy( handle_t& h ) { rc_t rc = kOkRC; if( !h.isValid()) return rc; svg_t* p = _handleToPtr(h); if((rc = _destroy(p)) != kOkRC ) return rc; h.clear(); return rc; } cw::rc_t cw::svg::install_color_map( handle_t h, const unsigned* colorV, unsigned colorN, unsigned id ) { return _install_cmap( _handleToPtr(h), id, mem::allocDupl(colorV,colorN), colorN ); } cw::rc_t cw::svg::install_css( handle_t h, unsigned cssClassId, const char* cssClassLabel, unsigned strokeColor, unsigned strokeWidth, unsigned fillColor, unsigned strokeOpacity, double fillOpacity ) { svg_t* p = _handleToPtr(h); return _install_css(p, cssClassId, cssClassLabel, strokeColor, strokeWidth, fillColor, strokeOpacity, fillOpacity ); } void cw::svg::offset( handle_t h, double dx, double dy ) { svg_t* p = _handleToPtr(h); p->dx = dx; p->dy = dy; } unsigned cw::svg::color( handle_t h, unsigned colorMapId, double colorMin, double colorMax, double colorValue ) { color_map_t* cm; svg_t* p = _handleToPtr(h); if((cm = _cmap_from_id(p, colorMapId )) == nullptr ) { cwLogWarning("Unknown color map id:%i.", colorMapId ); return 0; } double c = std::min( colorMax, std::max( colorMin, colorValue ) ); unsigned idx = cm->colorN * (c - colorMin)/(colorMax - colorMin); assert(idxcolorN); return cm->colorV[idx]; } unsigned cw::svg::color( handle_t h, unsigned colorMapId, unsigned colorIdx ) { color_map_t* cm; svg_t* p = _handleToPtr(h); if((cm = _cmap_from_id(p, colorMapId )) == nullptr ) return 0; colorIdx = colorIdx % cm->colorN; return cm->colorV[colorIdx]; } cw::rc_t cw::svg::_set_attr( handle_t h, argId_t id, const int& value ) { rc_t rc = kOkRC; svg_t* p = _handleToPtr(h); switch( id ) { case kStrokeColorArgId: p->curEle->attr.strokeColor = value; break; case kStrokeWidthArgId: p->curEle->attr.strokeWidth = value; break; case kFillColorArgId: p->curEle->attr.fillColor = value; break; default: rc = cwLogError(kSetAttrFailRC,"Unknown SVG attribute id: %i", id); } if( rc == kOkRC ) p->curEle->flags |= id; return rc; } cw::rc_t cw::svg::_set_attr( handle_t h, argId_t id, double value ) { rc_t rc = kOkRC; svg_t* p = _handleToPtr(h); switch( id ) { case kStrokeOpacityArgId: p->curEle->attr.strokeOpacity = value; break; case kFillOpacityArgId: p->curEle->attr.fillOpacity = value; break; default: rc = cwLogError(kSetAttrFailRC,"Unknown SVG attribute id: %i", id); } if( rc == kOkRC ) p->curEle->flags |= id; return rc; } cw::rc_t cw::svg::_rect( handle_t h, double x, double y, double ww, double hh, unsigned cssClassId ) { svg_t* p = _handleToPtr(h); return _insert( p, kRectTId, x, y, ww, hh, cssClassId, nullptr); } cw::rc_t cw::svg::_line( handle_t h, double x0, double y0, double x1, double y1, unsigned cssClassId ) { svg_t* p = _handleToPtr(h); return _insert( p, kLineTId, x0, y0, x1-x0, y1-y0, cssClassId, nullptr); } cw::rc_t cw::svg::_pline( handle_t h, const double* yV, unsigned n, const double* xV, unsigned cssClassId ) { svg_t* p = _handleToPtr(h); return _insert( p, kPLineTId, 0,0,0,0, cssClassId, nullptr, yV, n, xV); } cw::rc_t cw::svg::_text( handle_t h, double x, double y, const char* text, unsigned cssClassId ) { svg_t* p = _handleToPtr(h); return _insert( p, kTextTId, x, y, 0, 0, cssClassId, text); } cw::rc_t cw::svg::image( handle_t h, const float* xM, unsigned rowN, unsigned colN, unsigned pixSize, unsigned cmapId ) { for(unsigned i=0; ieleL; bool standAloneFl = cwIsFlag(flags,kStandAloneFl); bool panZoomFl = cwIsFlag(flags,kPanZoomFl); bool genInlineStyleFl = cwIsFlag(flags,kGenInlineStyleFl); bool genCssFileFl = cwIsFlag(flags,kGenCssFileFl) && cssFn!=nullptr; //bool drawFrameFl = cwIsFlag(flags,kDrawFrameFl); file::handle_t fH; char panZoomHdr[] = "\n" "\n"; char standAloneFmt[] = "\n" "\n" "\n" "\n" "%s" "%s\n" "\n" "\n"; char svgFmt[] = "\n"; char cssFmt[] = "\n"; double max_y = _size(p, svgWidth, svgHeight ); _flipY( p, max_y ); _offsetEles(p, bordR, bordT ); svgWidth += bordR + bordL; svgHeight += bordT + bordB; if( genCssFileFl ) _writeCssFile( p, cssFn ); cssStr = mem::printf(cssStr,cssFn==nullptr ? "%s" : cssFmt, cssFn==nullptr ? " " : cssFn); fileHdr = mem::printf(fileHdr, standAloneFmt, cssStr, panZoomFl ? panZoomHdr : ""); svgHdr = mem::printf(svgHdr,"%s%s", standAloneFl ? fileHdr : "", svgFmt); fileHdr = mem::printf(fileHdr,svgHdr,svgWidth,svgHeight); mem::release(svgHdr); mem::release(cssStr); for(; e!=NULL; e=e->link) { char* styleStr = nullptr; if((rc = _allocStyleString( p, e, genInlineStyleFl, styleStr )) != kOkRC ) goto errLabel; switch( e->typeId ) { case kRectTId: bodyStr = mem::printp(bodyStr,"\n",e->x0,e->y0,e->x1-e->x0,e->y1-e->y0,styleStr); break; case kLineTId: bodyStr = mem::printp(bodyStr,"\n",e->x0,e->y0,e->x1,e->y1,styleStr); break; case kPLineTId: bodyStr = _write_pline(bodyStr,e,styleStr); break; case kTextTId: bodyStr = mem::printp(bodyStr,"%s\n",e->x0,e->y0, styleStr,e->text); break; } mem::release(styleStr); } if( (bodyStr = textAppend(bodyStr,"\n")) == NULL ) { rc = cwLogError(kMemAllocFailRC,"File suffix write failed."); goto errLabel; } if( standAloneFl ) bodyStr = textAppend(bodyStr,"\n\n"); if((rc = file::open(fH,outFn,file::kWriteFl)) != kOkRC ) { rc = cwLogError(rc,"SVG file create failed for '%s'.",cwStringNullGuard(outFn)); goto errLabel; } fileHdr = textAppend(fileHdr,bodyStr); if((rc = file::printf(fH,fileHdr)) != kOkRC ) { rc = cwLogError(rc,"SVG file write failed on '%s'.",outFn); goto errLabel; } errLabel: file::close(fH); mem::release(fileHdr); mem::release(bodyStr); return rc; } cw::rc_t cw::svg::test( const char* outFn, const char* cssFn ) { rc_t rc = kOkRC; handle_t h; if((rc = create(h)) != kOkRC ) cwLogError(rc,"SVG Test failed on create."); double yV[] = { 0, 10, 30, 60, 90 }; unsigned yN = cwCountOf(yV); rect(h, 0, 0, 100, 100, kRectCssId, kFillColorArgId, 0x7f7f7f ); line(h, 0, 0, 100, 100, kLineCssId, kStrokeColorArgId, 0xff0000 ); line(h, 0,100, 100, 0, kLineCssId, kStrokeColorArgId, 0x00ff00, kStrokeWidthArgId, 3, kStrokeOpacityArgId, 0.5 ); pline(h, yV, yN ); text(h, 10, 10, "foo"); write(h,outFn, cssFn, kStandAloneFl | kGenCssFileFl, 10,10,10,10); if((rc = destroy(h)) != kOkRC ) cwLogError(rc,"SVG destroy failed."); return rc; }