diff --git a/pcbnew/import_gfx/nanosvg.cpp b/pcbnew/import_gfx/nanosvg.cpp index 8ffad72b4b..55a490e2cd 100644 --- a/pcbnew/import_gfx/nanosvg.cpp +++ b/pcbnew/import_gfx/nanosvg.cpp @@ -1,6 +1,3776 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors 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 software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * 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 software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example + * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) + * + * Arc calculation code based on canvg (https://code.google.com/p/canvg/) + * + * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + * + */ + +#include "nanosvg.h" + #include #include +#include #include -#define NANOSVG_IMPLEMENTATION -#include "nanosvg.h" +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NSVG_ALIGN_MIN 0 +#define NSVG_ALIGN_MID 1 +#define NSVG_ALIGN_MAX 2 +#define NSVG_ALIGN_NONE 0 +#define NSVG_ALIGN_MEET 1 +#define NSVG_ALIGN_SLICE 2 + +#define NSVG_NOTUSED( v ) do { (void) ( 1 ? (void) 0 : ( (void) (v) ) ); } \ + while( 0 ) +#define NSVG_RGB( r, g, \ + b ) ( ( (unsigned int) r ) | ( (unsigned int) g << 8 ) | \ + ( (unsigned int) b << 16 ) ) + +#ifdef _MSC_VER + #pragma warning (disable: 4996) // Switch off security warnings + #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings + #ifdef __cplusplus + #define NSVG_INLINE inline + #else + #define NSVG_INLINE + #endif +#else + #define NSVG_INLINE inline +#endif + + +static int nsvg__isspace( char c ) +{ + return strchr( " \t\n\v\f\r", c ) != 0; +} + + +static int nsvg__isdigit( char c ) +{ + return c >= '0' && c <= '9'; +} + + +static int nsvg__isnum( char c ) +{ + return strchr( "0123456789+-.eE", c ) != 0; +} + + +static NSVG_INLINE float nsvg__minf( float a, float b ) +{ + return a < b ? a : b; +} + + +static NSVG_INLINE float nsvg__maxf( float a, float b ) +{ + return a > b ? a : b; +} + + +// Simple XML parser + +#define NSVG_XML_TAG 1 +#define NSVG_XML_CONTENT 2 +#define NSVG_XML_MAX_ATTRIBS 256 + +static void nsvg__parseContent( char* s, + void (* contentCb)( void* ud, const char* s ), + void* ud ) +{ + // Trim start white spaces + while( *s && nsvg__isspace( *s ) ) + s++; + + if( !*s ) + return; + + if( contentCb ) + (*contentCb)( ud, s ); +} + + +static void nsvg__parseElement( char* s, + void (* startelCb)( void* ud, const char* el, const char** attr ), + void (* endelCb)( void* ud, const char* el ), + void* ud ) +{ + const char* attr[NSVG_XML_MAX_ATTRIBS]; + int nattr = 0; + char* e_name; + int start = 0; + int end = 0; + char quote; + + // Skip white space after the '<' + while( *s && nsvg__isspace( *s ) ) + s++; + + // Check if the tag is end tag + if( *s == '/' ) + { + s++; + end = 1; + } + else + { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if( !*s || *s == '?' || *s == '!' ) + return; + + // Get tag name + e_name = s; + + while( *s && !nsvg__isspace( *s ) ) + s++; + + if( *s ) + { + *s++ = '\0'; + } + + // Get attribs + while( !end && *s && nattr < NSVG_XML_MAX_ATTRIBS - 3 ) + { + char* name = NULL; + char* value = NULL; + + // Skip white space before the attrib name + while( *s && nsvg__isspace( *s ) ) + s++; + + if( !*s ) + break; + + if( *s == '/' ) + { + end = 1; + break; + } + + name = s; + + // Find end of the attrib name. + while( *s && !nsvg__isspace( *s ) && *s != '=' ) + s++; + + if( *s ) + { + *s++ = '\0'; + } + + // Skip until the beginning of the value. + while( *s && *s != '\"' && *s != '\'' ) + s++; + + if( !*s ) + break; + + quote = *s; + s++; + // Store value and find the end of it. + value = s; + + while( *s && *s != quote ) + s++; + + if( *s ) + { + *s++ = '\0'; + } + + // Store only well formed attributes + if( name && value ) + { + attr[nattr++] = name; + attr[nattr++] = value; + } + } + + // List terminator + attr[nattr++] = 0; + attr[nattr++] = 0; + + // Call callbacks. + if( start && startelCb ) + (*startelCb)( ud, e_name, attr ); + + if( end && endelCb ) + (*endelCb)( ud, e_name ); +} + + +int nsvg__parseXML( char* input, + void (* startelCb)( void* ud, const char* el, const char** attr ), + void (* endelCb)( void* ud, const char* el ), + void (* contentCb)( void* ud, const char* s ), + void* ud ) +{ + char* s = input; + char* mark = s; + int state = NSVG_XML_CONTENT; + + while( *s ) + { + if( *s == '<' && state == NSVG_XML_CONTENT ) + { + // Start of a tag + *s++ = '\0'; + nsvg__parseContent( mark, contentCb, ud ); + mark = s; + state = NSVG_XML_TAG; + } + else if( *s == '>' && state == NSVG_XML_TAG ) + { + // Start of a content or new tag. + *s++ = '\0'; + nsvg__parseElement( mark, startelCb, endelCb, ud ); + mark = s; + state = NSVG_XML_CONTENT; + } + else + { + s++; + } + } + + return 1; +} + + +/* Simple SVG parser. */ + +#define NSVG_MAX_ATTR 128 + +enum NSVGgradientUnits +{ + NSVG_USER_SPACE = 0, + NSVG_OBJECT_SPACE = 1 +}; + +#define NSVG_MAX_DASHES 8 + +enum NSVGunits +{ + NSVG_UNITS_USER, + NSVG_UNITS_PX, + NSVG_UNITS_PT, + NSVG_UNITS_PC, + NSVG_UNITS_MM, + NSVG_UNITS_CM, + NSVG_UNITS_IN, + NSVG_UNITS_PERCENT, + NSVG_UNITS_EM, + NSVG_UNITS_EX +}; + +typedef struct NSVGcoordinate +{ + float value; + int units; +} NSVGcoordinate; + +typedef struct NSVGlinearData +{ + NSVGcoordinate x1, y1, x2, y2; +} NSVGlinearData; + +typedef struct NSVGradialData +{ + NSVGcoordinate cx, cy, r, fx, fy; +} NSVGradialData; + +typedef struct NSVGgradientData +{ + char id[64]; + char ref[64]; + char type; + union + { + NSVGlinearData linear; + NSVGradialData radial; + }; + char spread; + char units; + float xform[6]; + int nstops; + NSVGgradientStop* stops; + struct NSVGgradientData* next; +} NSVGgradientData; + +typedef struct NSVGattrib +{ + char id[64]; + float xform[6]; + unsigned int fillColor; + unsigned int strokeColor; + float opacity; + float fillOpacity; + float strokeOpacity; + char fillGradient[64]; + char strokeGradient[64]; + float strokeWidth; + float strokeDashOffset; + float strokeDashArray[NSVG_MAX_DASHES]; + int strokeDashCount; + char strokeLineJoin; + char strokeLineCap; + float miterLimit; + char fillRule; + float fontSize; + unsigned int stopColor; + float stopOpacity; + float stopOffset; + char hasFill; + char hasStroke; + char visible; +} NSVGattrib; + +typedef struct NSVGparser +{ + NSVGattrib attr[NSVG_MAX_ATTR]; + int attrHead; + float* pts; + int npts; + int cpts; + NSVGpath* plist; + NSVGimage* image; + NSVGgradientData* gradients; + NSVGshape* shapesTail; + float viewMinx, viewMiny, viewWidth, viewHeight; + int alignX, alignY, alignType; + float dpi; + char pathFlag; + char defsFlag; +} NSVGparser; + +static void nsvg__xformIdentity( float* t ) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + + +static void nsvg__xformSetTranslation( float* t, float tx, float ty ) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = tx; t[5] = ty; +} + + +static void nsvg__xformSetScale( float* t, float sx, float sy ) +{ + t[0] = sx; t[1] = 0.0f; + t[2] = 0.0f; t[3] = sy; + t[4] = 0.0f; t[5] = 0.0f; +} + + +static void nsvg__xformSetSkewX( float* t, float a ) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = tanf( a ); t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + + +static void nsvg__xformSetSkewY( float* t, float a ) +{ + t[0] = 1.0f; t[1] = tanf( a ); + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + + +static void nsvg__xformSetRotation( float* t, float a ) +{ + float cs = cosf( a ), sn = sinf( a ); + + t[0] = cs; t[1] = sn; + t[2] = -sn; t[3] = cs; + t[4] = 0.0f; t[5] = 0.0f; +} + + +static void nsvg__xformMultiply( float* t, float* s ) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + + +static void nsvg__xformInverse( float* inv, float* t ) +{ + double invdet, det = (double) t[0] * t[3] - (double) t[2] * t[1]; + + if( det > -1e-6 && det < 1e-6 ) + { + nsvg__xformIdentity( t ); + return; + } + + invdet = 1.0 / det; + inv[0] = (float) (t[3] * invdet); + inv[2] = (float) (-t[2] * invdet); + inv[4] = (float) ( ( (double) t[2] * t[5] - (double) t[3] * t[4] ) * invdet ); + inv[1] = (float) (-t[1] * invdet); + inv[3] = (float) (t[0] * invdet); + inv[5] = (float) ( ( (double) t[1] * t[4] - (double) t[0] * t[5] ) * invdet ); +} + + +static void nsvg__xformPremultiply( float* t, float* s ) +{ + float s2[6]; + + memcpy( s2, s, sizeof(float) * 6 ); + nsvg__xformMultiply( s2, t ); + memcpy( t, s2, sizeof(float) * 6 ); +} + + +static void nsvg__xformPoint( float* dx, float* dy, float x, float y, float* t ) +{ + *dx = x * t[0] + y * t[2] + t[4]; + *dy = x * t[1] + y * t[3] + t[5]; +} + + +static void nsvg__xformVec( float* dx, float* dy, float x, float y, float* t ) +{ + *dx = x * t[0] + y * t[2]; + *dy = x * t[1] + y * t[3]; +} + + +#define NSVG_EPSILON (1e-12) + +static int nsvg__ptInBounds( float* pt, float* bounds ) +{ + return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; +} + + +static double nsvg__evalBezier( double t, double p0, double p1, double p2, double p3 ) +{ + double it = 1.0 - t; + + return it * it * it * p0 + 3.0 * it * it * t * p1 + 3.0 * it * t * t * p2 + t * t * t * p3; +} + + +static void nsvg__curveBounds( float* bounds, float* curve ) +{ + int i, j, count; + double roots[2], a, b, c, b2ac, t, v; + float* v0 = &curve[0]; + float* v1 = &curve[2]; + float* v2 = &curve[4]; + float* v3 = &curve[6]; + + // Start the bounding box by end points + bounds[0] = nsvg__minf( v0[0], v3[0] ); + bounds[1] = nsvg__minf( v0[1], v3[1] ); + bounds[2] = nsvg__maxf( v0[0], v3[0] ); + bounds[3] = nsvg__maxf( v0[1], v3[1] ); + + // Bezier curve fits inside the convex hull of it's control points. + // If control points are inside the bounds, we're done. + if( nsvg__ptInBounds( v1, bounds ) && nsvg__ptInBounds( v2, bounds ) ) + return; + + // Add bezier curve inflection points in X and Y. + for( i = 0; i < 2; i++ ) + { + a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; + b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; + c = 3.0 * v1[i] - 3.0 * v0[i]; + count = 0; + + if( fabs( a ) < NSVG_EPSILON ) + { + if( fabs( b ) > NSVG_EPSILON ) + { + t = -c / b; + + if( t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON ) + roots[count++] = t; + } + } + else + { + b2ac = b * b - 4.0 * c * a; + + if( b2ac > NSVG_EPSILON ) + { + t = ( -b + sqrt( b2ac ) ) / (2.0 * a); + + if( t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON ) + roots[count++] = t; + + t = ( -b - sqrt( b2ac ) ) / (2.0 * a); + + if( t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON ) + roots[count++] = t; + } + } + + for( j = 0; j < count; j++ ) + { + v = nsvg__evalBezier( roots[j], v0[i], v1[i], v2[i], v3[i] ); + bounds[0 + i] = nsvg__minf( bounds[0 + i], (float) v ); + bounds[2 + i] = nsvg__maxf( bounds[2 + i], (float) v ); + } + } +} + + +static NSVGparser* nsvg__createParser() +{ + NSVGparser* p; + + p = (NSVGparser*) malloc( sizeof(NSVGparser) ); + + if( p == NULL ) + goto error; + + memset( p, 0, sizeof(NSVGparser) ); + + p->image = (NSVGimage*) malloc( sizeof(NSVGimage) ); + + if( p->image == NULL ) + goto error; + + memset( p->image, 0, sizeof(NSVGimage) ); + + // Init style + nsvg__xformIdentity( p->attr[0].xform ); + memset( p->attr[0].id, 0, sizeof p->attr[0].id ); + p->attr[0].fillColor = NSVG_RGB( 0, 0, 0 ); + p->attr[0].strokeColor = NSVG_RGB( 0, 0, 0 ); + p->attr[0].opacity = 1; + p->attr[0].fillOpacity = 1; + p->attr[0].strokeOpacity = 1; + p->attr[0].stopOpacity = 1; + p->attr[0].strokeWidth = 1; + p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; + p->attr[0].strokeLineCap = NSVG_CAP_BUTT; + p->attr[0].miterLimit = 4; + p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; + p->attr[0].hasFill = 1; + p->attr[0].visible = 1; + + return p; + +error: + + if( p ) + { + if( p->image ) + free( p->image ); + + free( p ); + } + + return NULL; +} + + +static void nsvg__deletePaths( NSVGpath* path ) +{ + while( path ) + { + NSVGpath* next = path->next; + + if( path->pts != NULL ) + free( path->pts ); + + free( path ); + path = next; + } +} + + +static void nsvg__deletePaint( NSVGpaint* paint ) +{ + if( paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT ) + free( paint->gradient ); +} + + +static void nsvg__deleteGradientData( NSVGgradientData* grad ) +{ + NSVGgradientData* next; + + while( grad != NULL ) + { + next = grad->next; + free( grad->stops ); + free( grad ); + grad = next; + } +} + + +static void nsvg__deleteParser( NSVGparser* p ) +{ + if( p != NULL ) + { + nsvg__deletePaths( p->plist ); + nsvg__deleteGradientData( p->gradients ); + nsvgDelete( p->image ); + free( p->pts ); + free( p ); + } +} + + +static void nsvg__resetPath( NSVGparser* p ) +{ + p->npts = 0; +} + + +static void nsvg__addPoint( NSVGparser* p, float x, float y ) +{ + if( p->npts + 1 > p->cpts ) + { + p->cpts = p->cpts ? p->cpts * 2 : 8; + p->pts = (float*) realloc( p->pts, p->cpts * 2 * sizeof(float) ); + + if( !p->pts ) + return; + } + + p->pts[p->npts * 2 + 0] = x; + p->pts[p->npts * 2 + 1] = y; + p->npts++; +} + + +static void nsvg__moveTo( NSVGparser* p, float x, float y ) +{ + if( p->npts > 0 ) + { + p->pts[(p->npts - 1) * 2 + 0] = x; + p->pts[(p->npts - 1) * 2 + 1] = y; + } + else + { + nsvg__addPoint( p, x, y ); + } +} + + +static void nsvg__lineTo( NSVGparser* p, float x, float y ) +{ + float px, py, dx, dy; + + if( p->npts > 0 ) + { + px = p->pts[(p->npts - 1) * 2 + 0]; + py = p->pts[(p->npts - 1) * 2 + 1]; + dx = x - px; + dy = y - py; + nsvg__addPoint( p, px + dx / 3.0f, py + dy / 3.0f ); + nsvg__addPoint( p, x - dx / 3.0f, y - dy / 3.0f ); + nsvg__addPoint( p, x, y ); + } +} + + +static void nsvg__cubicBezTo( NSVGparser* p, + float cpx1, + float cpy1, + float cpx2, + float cpy2, + float x, + float y ) +{ + nsvg__addPoint( p, cpx1, cpy1 ); + nsvg__addPoint( p, cpx2, cpy2 ); + nsvg__addPoint( p, x, y ); +} + + +static NSVGattrib* nsvg__getAttr( NSVGparser* p ) +{ + return &p->attr[p->attrHead]; +} + + +static void nsvg__pushAttr( NSVGparser* p ) +{ + if( p->attrHead < NSVG_MAX_ATTR - 1 ) + { + p->attrHead++; + memcpy( &p->attr[p->attrHead], &p->attr[p->attrHead - 1], sizeof(NSVGattrib) ); + } +} + + +static void nsvg__popAttr( NSVGparser* p ) +{ + if( p->attrHead > 0 ) + p->attrHead--; +} + + +static float nsvg__actualOrigX( NSVGparser* p ) +{ + return p->viewMinx; +} + + +static float nsvg__actualOrigY( NSVGparser* p ) +{ + return p->viewMiny; +} + + +static float nsvg__actualWidth( NSVGparser* p ) +{ + return p->viewWidth; +} + + +static float nsvg__actualHeight( NSVGparser* p ) +{ + return p->viewHeight; +} + + +static float nsvg__actualLength( NSVGparser* p ) +{ + float w = nsvg__actualWidth( p ), h = nsvg__actualHeight( p ); + + return sqrtf( w * w + h * h ) / sqrtf( 2.0f ); +} + + +static float nsvg__convertToPixels( NSVGparser* p, NSVGcoordinate c, float orig, float length ) +{ + NSVGattrib* attr = nsvg__getAttr( p ); + + switch( c.units ) + { + case NSVG_UNITS_USER: + return c.value; + + case NSVG_UNITS_PX: + return c.value; + + case NSVG_UNITS_PT: + return c.value / 72.0f * p->dpi; + + case NSVG_UNITS_PC: + return c.value / 6.0f * p->dpi; + + case NSVG_UNITS_MM: + return c.value / 25.4f * p->dpi; + + case NSVG_UNITS_CM: + return c.value / 2.54f * p->dpi; + + case NSVG_UNITS_IN: + return c.value * p->dpi; + + case NSVG_UNITS_EM: + return c.value * attr->fontSize; + + case NSVG_UNITS_EX: + return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. + + case NSVG_UNITS_PERCENT: + return orig + c.value / 100.0f * length; + + default: + return c.value; + } + + return c.value; +} + + +static NSVGgradientData* nsvg__findGradientData( NSVGparser* p, const char* id ) +{ + NSVGgradientData* grad = p->gradients; + + while( grad ) + { + if( strcmp( grad->id, id ) == 0 ) + return grad; + + grad = grad->next; + } + + return NULL; +} + + +static NSVGgradient* nsvg__createGradient( NSVGparser* p, + const char* id, + const float* localBounds, + char* paintType ) +{ + NSVGattrib* attr = nsvg__getAttr( p ); + NSVGgradientData* data = NULL; + NSVGgradientData* ref = NULL; + NSVGgradientStop* stops = NULL; + NSVGgradient* grad; + float ox, oy, sw, sh, sl; + int nstops = 0; + + data = nsvg__findGradientData( p, id ); + + if( data == NULL ) + return NULL; + + // TODO: use ref to fill in all unset values too. + ref = data; + + while( ref != NULL ) + { + if( stops == NULL && ref->stops != NULL ) + { + stops = ref->stops; + nstops = ref->nstops; + break; + } + + ref = nsvg__findGradientData( p, ref->ref ); + } + + if( stops == NULL ) + return NULL; + + grad = (NSVGgradient*) malloc( sizeof(NSVGgradient) + sizeof(NSVGgradientStop) * (nstops - 1) ); + + if( grad == NULL ) + return NULL; + + // The shape width and height. + if( data->units == NSVG_OBJECT_SPACE ) + { + ox = localBounds[0]; + oy = localBounds[1]; + sw = localBounds[2] - localBounds[0]; + sh = localBounds[3] - localBounds[1]; + } + else + { + ox = nsvg__actualOrigX( p ); + oy = nsvg__actualOrigY( p ); + sw = nsvg__actualWidth( p ); + sh = nsvg__actualHeight( p ); + } + + sl = sqrtf( sw * sw + sh * sh ) / sqrtf( 2.0f ); + + if( data->type == NSVG_PAINT_LINEAR_GRADIENT ) + { + float x1, y1, x2, y2, dx, dy; + x1 = nsvg__convertToPixels( p, data->linear.x1, ox, sw ); + y1 = nsvg__convertToPixels( p, data->linear.y1, oy, sh ); + x2 = nsvg__convertToPixels( p, data->linear.x2, ox, sw ); + y2 = nsvg__convertToPixels( p, data->linear.y2, oy, sh ); + // Calculate transform aligned to the line + dx = x2 - x1; + dy = y2 - y1; + grad->xform[0] = dy; grad->xform[1] = -dx; + grad->xform[2] = dx; grad->xform[3] = dy; + grad->xform[4] = x1; grad->xform[5] = y1; + } + else + { + float cx, cy, fx, fy, r; + cx = nsvg__convertToPixels( p, data->radial.cx, ox, sw ); + cy = nsvg__convertToPixels( p, data->radial.cy, oy, sh ); + fx = nsvg__convertToPixels( p, data->radial.fx, ox, sw ); + fy = nsvg__convertToPixels( p, data->radial.fy, oy, sh ); + r = nsvg__convertToPixels( p, data->radial.r, 0, sl ); + // Calculate transform aligned to the circle + grad->xform[0] = r; grad->xform[1] = 0; + grad->xform[2] = 0; grad->xform[3] = r; + grad->xform[4] = cx; grad->xform[5] = cy; + grad->fx = fx / r; + grad->fy = fy / r; + } + + nsvg__xformMultiply( grad->xform, data->xform ); + nsvg__xformMultiply( grad->xform, attr->xform ); + + grad->spread = data->spread; + memcpy( grad->stops, stops, nstops * sizeof(NSVGgradientStop) ); + grad->nstops = nstops; + + *paintType = data->type; + + return grad; +} + + +static float nsvg__getAverageScale( float* t ) +{ + float sx = sqrtf( t[0] * t[0] + t[2] * t[2] ); + float sy = sqrtf( t[1] * t[1] + t[3] * t[3] ); + + return (sx + sy) * 0.5f; +} + + +static void nsvg__getLocalBounds( float* bounds, NSVGshape* shape, float* xform ) +{ + NSVGpath* path; + float curve[4 * 2], curveBounds[4]; + int i, first = 1; + + for( path = shape->paths; path != NULL; path = path->next ) + { + nsvg__xformPoint( &curve[0], &curve[1], path->pts[0], path->pts[1], xform ); + + for( i = 0; i < path->npts - 1; i += 3 ) + { + nsvg__xformPoint( &curve[2], &curve[3], path->pts[(i + 1) * 2], + path->pts[(i + 1) * 2 + 1], xform ); + nsvg__xformPoint( &curve[4], &curve[5], path->pts[(i + 2) * 2], + path->pts[(i + 2) * 2 + 1], xform ); + nsvg__xformPoint( &curve[6], &curve[7], path->pts[(i + 3) * 2], + path->pts[(i + 3) * 2 + 1], xform ); + nsvg__curveBounds( curveBounds, curve ); + + if( first ) + { + bounds[0] = curveBounds[0]; + bounds[1] = curveBounds[1]; + bounds[2] = curveBounds[2]; + bounds[3] = curveBounds[3]; + first = 0; + } + else + { + bounds[0] = nsvg__minf( bounds[0], curveBounds[0] ); + bounds[1] = nsvg__minf( bounds[1], curveBounds[1] ); + bounds[2] = nsvg__maxf( bounds[2], curveBounds[2] ); + bounds[3] = nsvg__maxf( bounds[3], curveBounds[3] ); + } + + curve[0] = curve[6]; + curve[1] = curve[7]; + } + } +} + + +static void nsvg__addShape( NSVGparser* p ) +{ + NSVGattrib* attr = nsvg__getAttr( p ); + float scale = 1.0f; + NSVGshape* shape; + NSVGpath* path; + int i; + + if( p->plist == NULL ) + return; + + shape = (NSVGshape*) malloc( sizeof(NSVGshape) ); + + if( shape == NULL ) + goto error; + + memset( shape, 0, sizeof(NSVGshape) ); + + memcpy( shape->id, attr->id, sizeof shape->id ); + scale = nsvg__getAverageScale( attr->xform ); + shape->strokeWidth = attr->strokeWidth * scale; + shape->strokeDashOffset = attr->strokeDashOffset * scale; + shape->strokeDashCount = (char) attr->strokeDashCount; + + for( i = 0; i < attr->strokeDashCount; i++ ) + shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; + + shape->strokeLineJoin = attr->strokeLineJoin; + shape->strokeLineCap = attr->strokeLineCap; + shape->miterLimit = attr->miterLimit; + shape->fillRule = attr->fillRule; + shape->opacity = attr->opacity; + + shape->paths = p->plist; + p->plist = NULL; + + // Calculate shape bounds + shape->bounds[0] = shape->paths->bounds[0]; + shape->bounds[1] = shape->paths->bounds[1]; + shape->bounds[2] = shape->paths->bounds[2]; + shape->bounds[3] = shape->paths->bounds[3]; + + for( path = shape->paths->next; path != NULL; path = path->next ) + { + shape->bounds[0] = nsvg__minf( shape->bounds[0], path->bounds[0] ); + shape->bounds[1] = nsvg__minf( shape->bounds[1], path->bounds[1] ); + shape->bounds[2] = nsvg__maxf( shape->bounds[2], path->bounds[2] ); + shape->bounds[3] = nsvg__maxf( shape->bounds[3], path->bounds[3] ); + } + + // Set fill + if( attr->hasFill == 0 ) + { + shape->fill.type = NSVG_PAINT_NONE; + } + else if( attr->hasFill == 1 ) + { + shape->fill.type = NSVG_PAINT_COLOR; + shape->fill.color = attr->fillColor; + shape->fill.color |= (unsigned int) (attr->fillOpacity * 255) << 24; + } + else if( attr->hasFill == 2 ) + { + float inv[6], localBounds[4]; + nsvg__xformInverse( inv, attr->xform ); + nsvg__getLocalBounds( localBounds, shape, inv ); + shape->fill.gradient = nsvg__createGradient( p, + attr->fillGradient, + localBounds, + &shape->fill.type ); + + if( shape->fill.gradient == NULL ) + { + shape->fill.type = NSVG_PAINT_NONE; + } + } + + // Set stroke + if( attr->hasStroke == 0 ) + { + shape->stroke.type = NSVG_PAINT_NONE; + } + else if( attr->hasStroke == 1 ) + { + shape->stroke.type = NSVG_PAINT_COLOR; + shape->stroke.color = attr->strokeColor; + shape->stroke.color |= (unsigned int) (attr->strokeOpacity * 255) << 24; + } + else if( attr->hasStroke == 2 ) + { + float inv[6], localBounds[4]; + nsvg__xformInverse( inv, attr->xform ); + nsvg__getLocalBounds( localBounds, shape, inv ); + shape->stroke.gradient = nsvg__createGradient( p, + attr->strokeGradient, + localBounds, + &shape->stroke.type ); + + if( shape->stroke.gradient == NULL ) + shape->stroke.type = NSVG_PAINT_NONE; + } + + // Set flags + shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); + + // Add to tail + if( p->image->shapes == NULL ) + p->image->shapes = shape; + else + p->shapesTail->next = shape; + + p->shapesTail = shape; + + return; + +error: + + if( shape ) + free( shape ); +} + + +static void nsvg__addPath( NSVGparser* p, char closed ) +{ + NSVGattrib* attr = nsvg__getAttr( p ); + NSVGpath* path = NULL; + float bounds[4]; + float* curve; + int i; + + if( p->npts < 4 ) + return; + + if( closed ) + nsvg__lineTo( p, p->pts[0], p->pts[1] ); + + path = (NSVGpath*) malloc( sizeof(NSVGpath) ); + + if( path == NULL ) + goto error; + + memset( path, 0, sizeof(NSVGpath) ); + + path->pts = (float*) malloc( p->npts * 2 * sizeof(float) ); + + if( path->pts == NULL ) + goto error; + + path->closed = closed; + path->npts = p->npts; + + // Transform path. + for( i = 0; i < p->npts; ++i ) + nsvg__xformPoint( &path->pts[i * 2], + &path->pts[i * 2 + 1], + p->pts[i * 2], + p->pts[i * 2 + 1], + attr->xform ); + + // Find bounds + for( i = 0; i < path->npts - 1; i += 3 ) + { + curve = &path->pts[i * 2]; + nsvg__curveBounds( bounds, curve ); + + if( i == 0 ) + { + path->bounds[0] = bounds[0]; + path->bounds[1] = bounds[1]; + path->bounds[2] = bounds[2]; + path->bounds[3] = bounds[3]; + } + else + { + path->bounds[0] = nsvg__minf( path->bounds[0], bounds[0] ); + path->bounds[1] = nsvg__minf( path->bounds[1], bounds[1] ); + path->bounds[2] = nsvg__maxf( path->bounds[2], bounds[2] ); + path->bounds[3] = nsvg__maxf( path->bounds[3], bounds[3] ); + } + } + + path->next = p->plist; + p->plist = path; + + return; + +error: + + if( path != NULL ) + { + if( path->pts != NULL ) + free( path->pts ); + + free( path ); + } +} + + +// We roll our own string to float because the std library one uses locale and messes things up. +static double nsvg__atof( const char* s ) +{ + char* cur = (char*) s; + char* end = NULL; + double res = 0.0, sign = 1.0; + long long intPart = 0, fracPart = 0; + char hasIntPart = 0, hasFracPart = 0; + + // Parse optional sign + if( *cur == '+' ) + { + cur++; + } + else if( *cur == '-' ) + { + sign = -1; + cur++; + } + + // Parse integer part + if( nsvg__isdigit( *cur ) ) + { + // Parse digit sequence + intPart = (double) strtoll( cur, &end, 10 ); + + if( cur != end ) + { + res = (double) intPart; + hasIntPart = 1; + cur = end; + } + } + + // Parse fractional part. + if( *cur == '.' ) + { + cur++; // Skip '.' + + if( nsvg__isdigit( *cur ) ) + { + // Parse digit sequence + fracPart = strtoll( cur, &end, 10 ); + + if( cur != end ) + { + res += (double) fracPart / pow( 10.0, (double) (end - cur) ); + hasFracPart = 1; + cur = end; + } + } + } + + // A valid number should have integer or fractional part. + if( !hasIntPart && !hasFracPart ) + return 0.0; + + // Parse optional exponent + if( *cur == 'e' || *cur == 'E' ) + { + int expPart = 0; + cur++; // skip 'E' + expPart = strtol( cur, &end, 10 ); // Parse digit sequence with sign + + if( cur != end ) + { + res *= pow( 10.0, (double) expPart ); + } + } + + return res * sign; +} + + +static const char* nsvg__parseNumber( const char* s, char* it, const int size ) +{ + const int last = size - 1; + int i = 0; + + // sign + if( *s == '-' || *s == '+' ) + { + if( i < last ) + it[i++] = *s; + + s++; + } + + // integer part + while( *s && nsvg__isdigit( *s ) ) + { + if( i < last ) + it[i++] = *s; + + s++; + } + + if( *s == '.' ) + { + // decimal point + if( i < last ) + it[i++] = *s; + + s++; + + // fraction part + while( *s && nsvg__isdigit( *s ) ) + { + if( i < last ) + it[i++] = *s; + + s++; + } + } + + // exponent + if( *s == 'e' || *s == 'E' ) + { + if( i < last ) + it[i++] = *s; + + s++; + + if( *s == '-' || *s == '+' ) + { + if( i < last ) + it[i++] = *s; + + s++; + } + + while( *s && nsvg__isdigit( *s ) ) + { + if( i < last ) + it[i++] = *s; + + s++; + } + } + + it[i] = '\0'; + + return s; +} + + +static const char* nsvg__getNextPathItem( const char* s, char* it ) +{ + it[0] = '\0'; + + // Skip white spaces and commas + while( *s && (nsvg__isspace( *s ) || *s == ',') ) + s++; + + if( !*s ) + return s; + + if( *s == '-' || *s == '+' || *s == '.' || nsvg__isdigit( *s ) ) + { + s = nsvg__parseNumber( s, it, 64 ); + } + else + { + // Parse command + it[0] = *s++; + it[1] = '\0'; + return s; + } + + return s; +} + + +static unsigned int nsvg__parseColorHex( const char* str ) +{ + unsigned int c = 0, r = 0, g = 0, b = 0; + int n = 0; + + str++; // skip # + + // Calculate number of characters. + while( str[n] && !nsvg__isspace( str[n] ) ) + n++; + + if( n == 6 ) + { + sscanf( str, "%x", &c ); + } + else if( n == 3 ) + { + sscanf( str, "%x", &c ); + c = (c & 0xf) | ( (c & 0xf0) << 4 ) | ( (c & 0xf00) << 8 ); + c |= c << 4; + } + + r = (c >> 16) & 0xff; + g = (c >> 8) & 0xff; + b = c & 0xff; + return NSVG_RGB( r, g, b ); +} + + +static unsigned int nsvg__parseColorRGB( const char* str ) +{ + int r = -1, g = -1, b = -1; + char s1[32] = "", s2[32] = ""; + + sscanf( str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b ); + + if( strchr( s1, '%' ) ) + { + return NSVG_RGB( (r * 255) / 100, (g * 255) / 100, (b * 255) / 100 ); + } + else + { + return NSVG_RGB( r, g, b ); + } +} + + +typedef struct NSVGNamedColor +{ + const char* name; + unsigned int color; +} NSVGNamedColor; + +NSVGNamedColor nsvg__colors[] = +{ + { "red", NSVG_RGB( 255, 0, 0 ) }, + { "green", NSVG_RGB( 0, 128, 0 ) }, + { "blue", NSVG_RGB( 0, 0, 255 ) }, + { "yellow", NSVG_RGB( 255, 255, 0 ) }, + { "cyan", NSVG_RGB( 0, 255, 255 ) }, + { "magenta", NSVG_RGB( 255, 0, 255 ) }, + { "black", NSVG_RGB( 0, 0, 0 ) }, + { "grey", NSVG_RGB( 128, 128, 128 ) }, + { "gray", NSVG_RGB( 128, 128, 128 ) }, + { "white", NSVG_RGB( 255, 255, 255 ) }, + +#ifdef NANOSVG_ALL_COLOR_KEYWORDS + { "aliceblue", NSVG_RGB( 240, 248, 255 ) }, + { "antiquewhite", NSVG_RGB( 250, 235, 215 ) }, + { "aqua", NSVG_RGB( 0, 255, 255 ) }, + { "aquamarine", NSVG_RGB( 127, 255, 212 ) }, + { "azure", NSVG_RGB( 240, 255, 255 ) }, + { "beige", NSVG_RGB( 245, 245, 220 ) }, + { "bisque", NSVG_RGB( 255, 228, 196 ) }, + { "blanchedalmond", NSVG_RGB( 255, 235, 205 ) }, + { "blueviolet", NSVG_RGB( 138, 43, 226 ) }, + { "brown", NSVG_RGB( 165, 42, 42 ) }, + { "burlywood", NSVG_RGB( 222, 184, 135 ) }, + { "cadetblue", NSVG_RGB( 95, 158, 160 ) }, + { "chartreuse", NSVG_RGB( 127, 255, 0 ) }, + { "chocolate", NSVG_RGB( 210, 105, 30 ) }, + { "coral", NSVG_RGB( 255, 127, 80 ) }, + { "cornflowerblue", NSVG_RGB( 100, 149, 237 ) }, + { "cornsilk", NSVG_RGB( 255, 248, 220 ) }, + { "crimson", NSVG_RGB( 220, 20, 60 ) }, + { "darkblue", NSVG_RGB( 0, 0, 139 ) }, + { "darkcyan", NSVG_RGB( 0, 139, 139 ) }, + { "darkgoldenrod", NSVG_RGB( 184, 134, 11 ) }, + { "darkgray", NSVG_RGB( 169, 169, 169 ) }, + { "darkgreen", NSVG_RGB( 0, 100, 0 ) }, + { "darkgrey", NSVG_RGB( 169, 169, 169 ) }, + { "darkkhaki", NSVG_RGB( 189, 183, 107 ) }, + { "darkmagenta", NSVG_RGB( 139, 0, 139 ) }, + { "darkolivegreen", NSVG_RGB( 85, 107, 47 ) }, + { "darkorange", NSVG_RGB( 255, 140, 0 ) }, + { "darkorchid", NSVG_RGB( 153, 50, 204 ) }, + { "darkred", NSVG_RGB( 139, 0, 0 ) }, + { "darksalmon", NSVG_RGB( 233, 150, 122 ) }, + { "darkseagreen", NSVG_RGB( 143, 188, 143 ) }, + { "darkslateblue", NSVG_RGB( 72, 61, 139 ) }, + { "darkslategray", NSVG_RGB( 47, 79, 79 ) }, + { "darkslategrey", NSVG_RGB( 47, 79, 79 ) }, + { "darkturquoise", NSVG_RGB( 0, 206, 209 ) }, + { "darkviolet", NSVG_RGB( 148, 0, 211 ) }, + { "deeppink", NSVG_RGB( 255, 20, 147 ) }, + { "deepskyblue", NSVG_RGB( 0, 191, 255 ) }, + { "dimgray", NSVG_RGB( 105, 105, 105 ) }, + { "dimgrey", NSVG_RGB( 105, 105, 105 ) }, + { "dodgerblue", NSVG_RGB( 30, 144, 255 ) }, + { "firebrick", NSVG_RGB( 178, 34, 34 ) }, + { "floralwhite", NSVG_RGB( 255, 250, 240 ) }, + { "forestgreen", NSVG_RGB( 34, 139, 34 ) }, + { "fuchsia", NSVG_RGB( 255, 0, 255 ) }, + { "gainsboro", NSVG_RGB( 220, 220, 220 ) }, + { "ghostwhite", NSVG_RGB( 248, 248, 255 ) }, + { "gold", NSVG_RGB( 255, 215, 0 ) }, + { "goldenrod", NSVG_RGB( 218, 165, 32 ) }, + { "greenyellow", NSVG_RGB( 173, 255, 47 ) }, + { "honeydew", NSVG_RGB( 240, 255, 240 ) }, + { "hotpink", NSVG_RGB( 255, 105, 180 ) }, + { "indianred", NSVG_RGB( 205, 92, 92 ) }, + { "indigo", NSVG_RGB( 75, 0, 130 ) }, + { "ivory", NSVG_RGB( 255, 255, 240 ) }, + { "khaki", NSVG_RGB( 240, 230, 140 ) }, + { "lavender", NSVG_RGB( 230, 230, 250 ) }, + { "lavenderblush", NSVG_RGB( 255, 240, 245 ) }, + { "lawngreen", NSVG_RGB( 124, 252, 0 ) }, + { "lemonchiffon", NSVG_RGB( 255, 250, 205 ) }, + { "lightblue", NSVG_RGB( 173, 216, 230 ) }, + { "lightcoral", NSVG_RGB( 240, 128, 128 ) }, + { "lightcyan", NSVG_RGB( 224, 255, 255 ) }, + { "lightgoldenrodyellow", NSVG_RGB( 250, 250, 210 ) }, + { "lightgray", NSVG_RGB( 211, 211, 211 ) }, + { "lightgreen", NSVG_RGB( 144, 238, 144 ) }, + { "lightgrey", NSVG_RGB( 211, 211, 211 ) }, + { "lightpink", NSVG_RGB( 255, 182, 193 ) }, + { "lightsalmon", NSVG_RGB( 255, 160, 122 ) }, + { "lightseagreen", NSVG_RGB( 32, 178, 170 ) }, + { "lightskyblue", NSVG_RGB( 135, 206, 250 ) }, + { "lightslategray", NSVG_RGB( 119, 136, 153 ) }, + { "lightslategrey", NSVG_RGB( 119, 136, 153 ) }, + { "lightsteelblue", NSVG_RGB( 176, 196, 222 ) }, + { "lightyellow", NSVG_RGB( 255, 255, 224 ) }, + { "lime", NSVG_RGB( 0, 255, 0 ) }, + { "limegreen", NSVG_RGB( 50, 205, 50 ) }, + { "linen", NSVG_RGB( 250, 240, 230 ) }, + { "maroon", NSVG_RGB( 128, 0, 0 ) }, + { "mediumaquamarine", NSVG_RGB( 102, 205, 170 ) }, + { "mediumblue", NSVG_RGB( 0, 0, 205 ) }, + { "mediumorchid", NSVG_RGB( 186, 85, 211 ) }, + { "mediumpurple", NSVG_RGB( 147, 112, 219 ) }, + { "mediumseagreen", NSVG_RGB( 60, 179, 113 ) }, + { "mediumslateblue", NSVG_RGB( 123, 104, 238 ) }, + { "mediumspringgreen", NSVG_RGB( 0, 250, 154 ) }, + { "mediumturquoise", NSVG_RGB( 72, 209, 204 ) }, + { "mediumvioletred", NSVG_RGB( 199, 21, 133 ) }, + { "midnightblue", NSVG_RGB( 25, 25, 112 ) }, + { "mintcream", NSVG_RGB( 245, 255, 250 ) }, + { "mistyrose", NSVG_RGB( 255, 228, 225 ) }, + { "moccasin", NSVG_RGB( 255, 228, 181 ) }, + { "navajowhite", NSVG_RGB( 255, 222, 173 ) }, + { "navy", NSVG_RGB( 0, 0, 128 ) }, + { "oldlace", NSVG_RGB( 253, 245, 230 ) }, + { "olive", NSVG_RGB( 128, 128, 0 ) }, + { "olivedrab", NSVG_RGB( 107, 142, 35 ) }, + { "orange", NSVG_RGB( 255, 165, 0 ) }, + { "orangered", NSVG_RGB( 255, 69, 0 ) }, + { "orchid", NSVG_RGB( 218, 112, 214 ) }, + { "palegoldenrod", NSVG_RGB( 238, 232, 170 ) }, + { "palegreen", NSVG_RGB( 152, 251, 152 ) }, + { "paleturquoise", NSVG_RGB( 175, 238, 238 ) }, + { "palevioletred", NSVG_RGB( 219, 112, 147 ) }, + { "papayawhip", NSVG_RGB( 255, 239, 213 ) }, + { "peachpuff", NSVG_RGB( 255, 218, 185 ) }, + { "peru", NSVG_RGB( 205, 133, 63 ) }, + { "pink", NSVG_RGB( 255, 192, 203 ) }, + { "plum", NSVG_RGB( 221, 160, 221 ) }, + { "powderblue", NSVG_RGB( 176, 224, 230 ) }, + { "purple", NSVG_RGB( 128, 0, 128 ) }, + { "rosybrown", NSVG_RGB( 188, 143, 143 ) }, + { "royalblue", NSVG_RGB( 65, 105, 225 ) }, + { "saddlebrown", NSVG_RGB( 139, 69, 19 ) }, + { "salmon", NSVG_RGB( 250, 128, 114 ) }, + { "sandybrown", NSVG_RGB( 244, 164, 96 ) }, + { "seagreen", NSVG_RGB( 46, 139, 87 ) }, + { "seashell", NSVG_RGB( 255, 245, 238 ) }, + { "sienna", NSVG_RGB( 160, 82, 45 ) }, + { "silver", NSVG_RGB( 192, 192, 192 ) }, + { "skyblue", NSVG_RGB( 135, 206, 235 ) }, + { "slateblue", NSVG_RGB( 106, 90, 205 ) }, + { "slategray", NSVG_RGB( 112, 128, 144 ) }, + { "slategrey", NSVG_RGB( 112, 128, 144 ) }, + { "snow", NSVG_RGB( 255, 250, 250 ) }, + { "springgreen", NSVG_RGB( 0, 255, 127 ) }, + { "steelblue", NSVG_RGB( 70, 130, 180 ) }, + { "tan", NSVG_RGB( 210, 180, 140 ) }, + { "teal", NSVG_RGB( 0, 128, 128 ) }, + { "thistle", NSVG_RGB( 216, 191, 216 ) }, + { "tomato", NSVG_RGB( 255, 99, 71 ) }, + { "turquoise", NSVG_RGB( 64, 224, 208 ) }, + { "violet", NSVG_RGB( 238, 130, 238 ) }, + { "wheat", NSVG_RGB( 245, 222, 179 ) }, + { "whitesmoke", NSVG_RGB( 245, 245, 245 ) }, + { "yellowgreen", NSVG_RGB( 154, 205, 50 ) }, +#endif +}; + +static unsigned int nsvg__parseColorName( const char* str ) +{ + int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); + + for( i = 0; i < ncolors; i++ ) + { + if( strcmp( nsvg__colors[i].name, str ) == 0 ) + { + return nsvg__colors[i].color; + } + } + + return NSVG_RGB( 128, 128, 128 ); +} + + +static unsigned int nsvg__parseColor( const char* str ) +{ + size_t len = 0; + + while( *str == ' ' ) + ++str; + + len = strlen( str ); + + if( len >= 1 && *str == '#' ) + return nsvg__parseColorHex( str ); + else if( len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(' ) + return nsvg__parseColorRGB( str ); + + return nsvg__parseColorName( str ); +} + + +static float nsvg__parseOpacity( const char* str ) +{ + float val = 0; + + sscanf( str, "%f", &val ); + + if( val < 0.0f ) + val = 0.0f; + + if( val > 1.0f ) + val = 1.0f; + + return val; +} + + +static float nsvg__parseMiterLimit( const char* str ) +{ + float val = 0; + + sscanf( str, "%f", &val ); + + if( val < 0.0f ) + val = 0.0f; + + return val; +} + + +static int nsvg__parseUnits( const char* units ) +{ + if( units[0] == 'p' && units[1] == 'x' ) + return NSVG_UNITS_PX; + else if( units[0] == 'p' && units[1] == 't' ) + return NSVG_UNITS_PT; + else if( units[0] == 'p' && units[1] == 'c' ) + return NSVG_UNITS_PC; + else if( units[0] == 'm' && units[1] == 'm' ) + return NSVG_UNITS_MM; + else if( units[0] == 'c' && units[1] == 'm' ) + return NSVG_UNITS_CM; + else if( units[0] == 'i' && units[1] == 'n' ) + return NSVG_UNITS_IN; + else if( units[0] == '%' ) + return NSVG_UNITS_PERCENT; + else if( units[0] == 'e' && units[1] == 'm' ) + return NSVG_UNITS_EM; + else if( units[0] == 'e' && units[1] == 'x' ) + return NSVG_UNITS_EX; + + return NSVG_UNITS_USER; +} + + +static NSVGcoordinate nsvg__parseCoordinateRaw( const char* str ) +{ + NSVGcoordinate coord = { 0, NSVG_UNITS_USER }; + char units[32] = ""; + + sscanf( str, "%f%31s", &coord.value, units ); + coord.units = nsvg__parseUnits( units ); + return coord; +} + + +static NSVGcoordinate nsvg__coord( float v, int units ) +{ + NSVGcoordinate coord = { v, units }; + + return coord; +} + + +static float nsvg__parseCoordinate( NSVGparser* p, const char* str, float orig, float length ) +{ + NSVGcoordinate coord = nsvg__parseCoordinateRaw( str ); + + return nsvg__convertToPixels( p, coord, orig, length ); +} + + +static int nsvg__parseTransformArgs( const char* str, float* args, int maxNa, int* na ) +{ + const char* end; + const char* ptr; + char it[64]; + + *na = 0; + ptr = str; + + while( *ptr && *ptr != '(' ) + ++ptr; + + if( *ptr == 0 ) + return 1; + + end = ptr; + + while( *end && *end != ')' ) + ++end; + + if( *end == 0 ) + return 1; + + while( ptr < end ) + { + if( *ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit( *ptr ) ) + { + if( *na >= maxNa ) + return 0; + + ptr = nsvg__parseNumber( ptr, it, 64 ); + args[(*na)++] = (float) nsvg__atof( it ); + } + else + { + ++ptr; + } + } + + return (int) (end - str); +} + + +static int nsvg__parseMatrix( float* xform, const char* str ) +{ + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs( str, t, 6, &na ); + + if( na != 6 ) + return len; + + memcpy( xform, t, sizeof(float) * 6 ); + return len; +} + + +static int nsvg__parseTranslate( float* xform, const char* str ) +{ + float args[2]; + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs( str, args, 2, &na ); + + if( na == 1 ) + args[1] = 0.0; + + nsvg__xformSetTranslation( t, args[0], args[1] ); + memcpy( xform, t, sizeof(float) * 6 ); + return len; +} + + +static int nsvg__parseScale( float* xform, const char* str ) +{ + float args[2]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs( str, args, 2, &na ); + + if( na == 1 ) + args[1] = args[0]; + + nsvg__xformSetScale( t, args[0], args[1] ); + memcpy( xform, t, sizeof(float) * 6 ); + return len; +} + + +static int nsvg__parseSkewX( float* xform, const char* str ) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs( str, args, 1, &na ); + + nsvg__xformSetSkewX( t, args[0] / 180.0f * NSVG_PI ); + memcpy( xform, t, sizeof(float) * 6 ); + return len; +} + + +static int nsvg__parseSkewY( float* xform, const char* str ) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs( str, args, 1, &na ); + + nsvg__xformSetSkewY( t, args[0] / 180.0f * NSVG_PI ); + memcpy( xform, t, sizeof(float) * 6 ); + return len; +} + + +static int nsvg__parseRotate( float* xform, const char* str ) +{ + float args[3]; + int na = 0; + float m[6]; + float t[6]; + int len = nsvg__parseTransformArgs( str, args, 3, &na ); + + if( na == 1 ) + args[1] = args[2] = 0.0f; + + nsvg__xformIdentity( m ); + + if( na > 1 ) + { + nsvg__xformSetTranslation( t, -args[1], -args[2] ); + nsvg__xformMultiply( m, t ); + } + + nsvg__xformSetRotation( t, args[0] / 180.0f * NSVG_PI ); + nsvg__xformMultiply( m, t ); + + if( na > 1 ) + { + nsvg__xformSetTranslation( t, args[1], args[2] ); + nsvg__xformMultiply( m, t ); + } + + memcpy( xform, m, sizeof(float) * 6 ); + + return len; +} + + +static void nsvg__parseTransform( float* xform, const char* str ) +{ + float t[6]; + + nsvg__xformIdentity( xform ); + + while( *str ) + { + if( strncmp( str, "matrix", 6 ) == 0 ) + str += nsvg__parseMatrix( t, str ); + else if( strncmp( str, "translate", 9 ) == 0 ) + str += nsvg__parseTranslate( t, str ); + else if( strncmp( str, "scale", 5 ) == 0 ) + str += nsvg__parseScale( t, str ); + else if( strncmp( str, "rotate", 6 ) == 0 ) + str += nsvg__parseRotate( t, str ); + else if( strncmp( str, "skewX", 5 ) == 0 ) + str += nsvg__parseSkewX( t, str ); + else if( strncmp( str, "skewY", 5 ) == 0 ) + str += nsvg__parseSkewY( t, str ); + else + { + ++str; + continue; + } + + nsvg__xformPremultiply( xform, t ); + } +} + + +static void nsvg__parseUrl( char* id, const char* str ) +{ + int i = 0; + + str += 4; // "url("; + + if( *str == '#' ) + str++; + + while( i < 63 && *str != ')' ) + { + id[i] = *str++; + i++; + } + + id[i] = '\0'; +} + + +static char nsvg__parseLineCap( const char* str ) +{ + if( strcmp( str, "butt" ) == 0 ) + return NSVG_CAP_BUTT; + else if( strcmp( str, "round" ) == 0 ) + return NSVG_CAP_ROUND; + else if( strcmp( str, "square" ) == 0 ) + return NSVG_CAP_SQUARE; + + // TODO: handle inherit. + return NSVG_CAP_BUTT; +} + + +static char nsvg__parseLineJoin( const char* str ) +{ + if( strcmp( str, "miter" ) == 0 ) + return NSVG_JOIN_MITER; + else if( strcmp( str, "round" ) == 0 ) + return NSVG_JOIN_ROUND; + else if( strcmp( str, "bevel" ) == 0 ) + return NSVG_JOIN_BEVEL; + + // TODO: handle inherit. + return NSVG_JOIN_MITER; +} + + +static char nsvg__parseFillRule( const char* str ) +{ + if( strcmp( str, "nonzero" ) == 0 ) + return NSVG_FILLRULE_NONZERO; + else if( strcmp( str, "evenodd" ) == 0 ) + return NSVG_FILLRULE_EVENODD; + + // TODO: handle inherit. + return NSVG_FILLRULE_NONZERO; +} + + +static const char* nsvg__getNextDashItem( const char* s, char* it ) +{ + int n = 0; + + it[0] = '\0'; + + // Skip white spaces and commas + while( *s && (nsvg__isspace( *s ) || *s == ',') ) + s++; + + // Advance until whitespace, comma or end. + while( *s && (!nsvg__isspace( *s ) && *s != ',') ) + { + if( n < 63 ) + it[n++] = *s; + + s++; + } + + it[n++] = '\0'; + return s; +} + + +static int nsvg__parseStrokeDashArray( NSVGparser* p, const char* str, float* strokeDashArray ) +{ + char item[64]; + int count = 0, i; + float sum = 0.0f; + + // Handle "none" + if( str[0] == 'n' ) + return 0; + + // Parse dashes + while( *str ) + { + str = nsvg__getNextDashItem( str, item ); + + if( !*item ) + break; + + if( count < NSVG_MAX_DASHES ) + strokeDashArray[count++] = + fabsf( nsvg__parseCoordinate( p, item, 0.0f, nsvg__actualLength( p ) ) ); + } + + for( i = 0; i < count; i++ ) + sum += strokeDashArray[i]; + + if( sum <= 1e-6f ) + count = 0; + + return count; +} + + +static void nsvg__parseStyle( NSVGparser* p, const char* str ); + +static int nsvg__parseAttr( NSVGparser* p, const char* name, const char* value ) +{ + float xform[6]; + NSVGattrib* attr = nsvg__getAttr( p ); + + if( !attr ) + return 0; + + if( strcmp( name, "style" ) == 0 ) + { + nsvg__parseStyle( p, value ); + } + else if( strcmp( name, "display" ) == 0 ) + { + if( strcmp( value, "none" ) == 0 ) + attr->visible = 0; + + // Don't reset ->visible on display:inline, one display:none hides the whole subtree + } + else if( strcmp( name, "fill" ) == 0 ) + { + if( strcmp( value, "none" ) == 0 ) + { + attr->hasFill = 0; + } + else if( strncmp( value, "url(", 4 ) == 0 ) + { + attr->hasFill = 2; + nsvg__parseUrl( attr->fillGradient, value ); + } + else + { + attr->hasFill = 1; + attr->fillColor = nsvg__parseColor( value ); + } + } + else if( strcmp( name, "opacity" ) == 0 ) + { + attr->opacity = nsvg__parseOpacity( value ); + } + else if( strcmp( name, "fill-opacity" ) == 0 ) + { + attr->fillOpacity = nsvg__parseOpacity( value ); + } + else if( strcmp( name, "stroke" ) == 0 ) + { + if( strcmp( value, "none" ) == 0 ) + { + attr->hasStroke = 0; + } + else if( strncmp( value, "url(", 4 ) == 0 ) + { + attr->hasStroke = 2; + nsvg__parseUrl( attr->strokeGradient, value ); + } + else + { + attr->hasStroke = 1; + attr->strokeColor = nsvg__parseColor( value ); + } + } + else if( strcmp( name, "stroke-width" ) == 0 ) + { + attr->strokeWidth = nsvg__parseCoordinate( p, value, 0.0f, nsvg__actualLength( p ) ); + } + else if( strcmp( name, "stroke-dasharray" ) == 0 ) + { + attr->strokeDashCount = nsvg__parseStrokeDashArray( p, value, attr->strokeDashArray ); + } + else if( strcmp( name, "stroke-dashoffset" ) == 0 ) + { + attr->strokeDashOffset = nsvg__parseCoordinate( p, value, 0.0f, nsvg__actualLength( p ) ); + } + else if( strcmp( name, "stroke-opacity" ) == 0 ) + { + attr->strokeOpacity = nsvg__parseOpacity( value ); + } + else if( strcmp( name, "stroke-linecap" ) == 0 ) + { + attr->strokeLineCap = nsvg__parseLineCap( value ); + } + else if( strcmp( name, "stroke-linejoin" ) == 0 ) + { + attr->strokeLineJoin = nsvg__parseLineJoin( value ); + } + else if( strcmp( name, "stroke-miterlimit" ) == 0 ) + { + attr->miterLimit = nsvg__parseMiterLimit( value ); + } + else if( strcmp( name, "fill-rule" ) == 0 ) + { + attr->fillRule = nsvg__parseFillRule( value ); + } + else if( strcmp( name, "font-size" ) == 0 ) + { + attr->fontSize = nsvg__parseCoordinate( p, value, 0.0f, nsvg__actualLength( p ) ); + } + else if( strcmp( name, "transform" ) == 0 ) + { + nsvg__parseTransform( xform, value ); + nsvg__xformPremultiply( attr->xform, xform ); + } + else if( strcmp( name, "stop-color" ) == 0 ) + { + attr->stopColor = nsvg__parseColor( value ); + } + else if( strcmp( name, "stop-opacity" ) == 0 ) + { + attr->stopOpacity = nsvg__parseOpacity( value ); + } + else if( strcmp( name, "offset" ) == 0 ) + { + attr->stopOffset = nsvg__parseCoordinate( p, value, 0.0f, 1.0f ); + } + else if( strcmp( name, "id" ) == 0 ) + { + strncpy( attr->id, value, 63 ); + attr->id[63] = '\0'; + } + else + { + return 0; + } + + return 1; +} + + +static int nsvg__parseNameValue( NSVGparser* p, const char* start, const char* end ) +{ + const char* str; + const char* val; + char name[512]; + char value[512]; + int n; + + str = start; + + while( str < end && *str != ':' ) + ++str; + + val = str; + + // Right Trim + while( str > start && ( *str == ':' || nsvg__isspace( *str ) ) ) + --str; + + ++str; + + n = (int) (str - start); + + if( n > 511 ) + n = 511; + + if( n ) + memcpy( name, start, n ); + + name[n] = 0; + + while( val < end && ( *val == ':' || nsvg__isspace( *val ) ) ) + ++val; + + n = (int) (end - val); + + if( n > 511 ) + n = 511; + + if( n ) + memcpy( value, val, n ); + + value[n] = 0; + + return nsvg__parseAttr( p, name, value ); +} + + +static void nsvg__parseStyle( NSVGparser* p, const char* str ) +{ + const char* start; + const char* end; + + while( *str ) + { + // Left Trim + while( *str && nsvg__isspace( *str ) ) + ++str; + + start = str; + + while( *str && *str != ';' ) + ++str; + + end = str; + + // Right Trim + while( end > start && ( *end == ';' || nsvg__isspace( *end ) ) ) + --end; + + ++end; + + nsvg__parseNameValue( p, start, end ); + + if( *str ) + ++str; + } +} + + +static void nsvg__parseAttribs( NSVGparser* p, const char** attr ) +{ + int i; + + for( i = 0; attr[i]; i += 2 ) + { + if( strcmp( attr[i], "style" ) == 0 ) + nsvg__parseStyle( p, attr[i + 1] ); + else + nsvg__parseAttr( p, attr[i], attr[i + 1] ); + } +} + + +static int nsvg__getArgsPerElement( char cmd ) +{ + switch( cmd ) + { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + + case 'c': + case 'C': + return 6; + + case 'a': + case 'A': + return 7; + } + + return 0; +} + + +static void nsvg__pathMoveTo( NSVGparser* p, float* cpx, float* cpy, float* args, int rel ) +{ + if( rel ) + { + *cpx += args[0]; + *cpy += args[1]; + } + else + { + *cpx = args[0]; + *cpy = args[1]; + } + + nsvg__moveTo( p, *cpx, *cpy ); +} + + +static void nsvg__pathLineTo( NSVGparser* p, float* cpx, float* cpy, float* args, int rel ) +{ + if( rel ) + { + *cpx += args[0]; + *cpy += args[1]; + } + else + { + *cpx = args[0]; + *cpy = args[1]; + } + + nsvg__lineTo( p, *cpx, *cpy ); +} + + +static void nsvg__pathHLineTo( NSVGparser* p, float* cpx, float* cpy, float* args, int rel ) +{ + if( rel ) + *cpx += args[0]; + else + *cpx = args[0]; + + nsvg__lineTo( p, *cpx, *cpy ); +} + + +static void nsvg__pathVLineTo( NSVGparser* p, float* cpx, float* cpy, float* args, int rel ) +{ + if( rel ) + *cpy += args[0]; + else + *cpy = args[0]; + + nsvg__lineTo( p, *cpx, *cpy ); +} + + +static void nsvg__pathCubicBezTo( NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel ) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if( rel ) + { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } + else + { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo( p, cx1, cy1, cx2, cy2, x2, y2 ); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + + +static void nsvg__pathCubicBezShortTo( NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel ) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + + if( rel ) + { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } + else + { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2 * x1 - *cpx2; + cy1 = 2 * y1 - *cpy2; + + nsvg__cubicBezTo( p, cx1, cy1, cx2, cy2, x2, y2 ); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + + +static void nsvg__pathQuadBezTo( NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel ) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + + if( rel ) + { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } + else + { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f / 3.0f * (cx - x1); + cy1 = y1 + 2.0f / 3.0f * (cy - y1); + cx2 = x2 + 2.0f / 3.0f * (cx - x2); + cy2 = y2 + 2.0f / 3.0f * (cy - y2); + + nsvg__cubicBezTo( p, cx1, cy1, cx2, cy2, x2, y2 ); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + + +static void nsvg__pathQuadBezShortTo( NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel ) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + + if( rel ) + { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } + else + { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2 * x1 - *cpx2; + cy = 2 * y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f / 3.0f * (cx - x1); + cy1 = y1 + 2.0f / 3.0f * (cy - y1); + cx2 = x2 + 2.0f / 3.0f * (cx - x2); + cy2 = y2 + 2.0f / 3.0f * (cy - y2); + + nsvg__cubicBezTo( p, cx1, cy1, cx2, cy2, x2, y2 ); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + + +static float nsvg__sqr( float x ) +{ + return x * x; +} + + +static float nsvg__vmag( float x, float y ) +{ + return sqrtf( x * x + y * y ); +} + + +static float nsvg__vecrat( float ux, float uy, float vx, float vy ) +{ + return (ux * vx + uy * vy) / ( nsvg__vmag( ux, uy ) * nsvg__vmag( vx, vy ) ); +} + + +static float nsvg__vecang( float ux, float uy, float vx, float vy ) +{ + float r = nsvg__vecrat( ux, uy, vx, vy ); + + if( r < -1.0f ) + r = -1.0f; + + if( r > 1.0f ) + r = 1.0f; + + return ( (ux * vy < uy * vx) ? -1.0f : 1.0f ) * acosf( r ); +} + + +static void nsvg__pathArcTo( NSVGparser* p, float* cpx, float* cpy, float* args, int rel ) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf( args[0] ); // y radius + ry = fabsf( args[1] ); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle + fa = fabsf( args[3] ) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf( args[4] ) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + + if( rel ) // end point + { + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } + else + { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf( dx * dx + dy * dy ); + + if( d < 1e-6f || rx < 1e-6f || ry < 1e-6f ) + { + // The arc degenerates to a line + nsvg__lineTo( p, x2, y2 ); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf( rotx ); + cosrx = cosf( rotx ); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr( x1p ) / nsvg__sqr( rx ) + nsvg__sqr( y1p ) / nsvg__sqr( ry ); + + if( d > 1 ) + { + d = sqrtf( d ); + rx *= d; + ry *= d; + } + + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr( rx ) * nsvg__sqr( ry ) - nsvg__sqr( rx ) * nsvg__sqr( y1p ) - nsvg__sqr( ry ) * + nsvg__sqr( x1p ); + sb = nsvg__sqr( rx ) * nsvg__sqr( y1p ) + nsvg__sqr( ry ) * nsvg__sqr( x1p ); + + if( sa < 0.0f ) + sa = 0.0f; + + if( sb > 0.0f ) + s = sqrtf( sa / sb ); + + if( fa == fs ) + s = -s; + + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2) / 2.0f + cosrx * cxp - sinrx * cyp; + cy = (y1 + y2) / 2.0f + sinrx * cxp + cosrx * cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang( 1.0f, 0.0f, ux, uy ); // Initial angle + da = nsvg__vecang( ux, uy, vx, vy ); // Delta angle + +// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; +// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if( fs == 0 && da > 0 ) + da -= 2 * NSVG_PI; + else if( fs == 1 && da < 0 ) + da += 2 * NSVG_PI; + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; t[1] = sinrx; + t[2] = -sinrx; t[3] = cosrx; + t[4] = cx; t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this +1. + ndivs = (int) (fabsf( da ) / (NSVG_PI * 0.5f) + 1.0f); + hda = (da / (float) ndivs) / 2.0f; + kappa = fabsf( 4.0f / 3.0f * ( 1.0f - cosf( hda ) ) / sinf( hda ) ); + + if( da < 0.0f ) + kappa = -kappa; + + for( i = 0; i <= ndivs; i++ ) + { + a = a1 + da * ( (float) i / (float) ndivs ); + dx = cosf( a ); + dy = sinf( a ); + nsvg__xformPoint( &x, &y, dx * rx, dy * ry, t ); // position + nsvg__xformVec( &tanx, &tany, -dy * rx * kappa, dx * ry * kappa, t ); // tangent + + if( i > 0 ) + nsvg__cubicBezTo( p, px + ptanx, py + ptany, x - tanx, y - tany, x, y ); + + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + + +static void nsvg__parsePath( NSVGparser* p, const char** attr ) +{ + const char* s = NULL; + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + float cpx, cpy, cpx2, cpy2; + const char* tmp[4]; + char closedFlag; + int i; + char item[64]; + + for( i = 0; attr[i]; i += 2 ) + { + if( strcmp( attr[i], "d" ) == 0 ) + { + s = attr[i + 1]; + } + else + { + tmp[0] = attr[i]; + tmp[1] = attr[i + 1]; + tmp[2] = 0; + tmp[3] = 0; + nsvg__parseAttribs( p, tmp ); + } + } + + if( s ) + { + nsvg__resetPath( p ); + cpx = 0; cpy = 0; + cpx2 = 0; cpy2 = 0; + closedFlag = 0; + nargs = 0; + + while( *s ) + { + s = nsvg__getNextPathItem( s, item ); + + if( !*item ) + break; + + if( nsvg__isnum( item[0] ) ) + { + if( nargs < 10 ) + args[nargs++] = (float) nsvg__atof( item ); + + if( nargs >= rargs ) + { + switch( cmd ) + { + case 'm': + case 'M': + nsvg__pathMoveTo( p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0 ); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement( cmd ); + cpx2 = cpx; cpy2 = cpy; + break; + + case 'l': + case 'L': + nsvg__pathLineTo( p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0 ); + cpx2 = cpx; cpy2 = cpy; + break; + + case 'H': + case 'h': + nsvg__pathHLineTo( p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0 ); + cpx2 = cpx; cpy2 = cpy; + break; + + case 'V': + case 'v': + nsvg__pathVLineTo( p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0 ); + cpx2 = cpx; cpy2 = cpy; + break; + + case 'C': + case 'c': + nsvg__pathCubicBezTo( p, &cpx, &cpy, &cpx2, &cpy2, args, + cmd == 'c' ? 1 : 0 ); + break; + + case 'S': + case 's': + nsvg__pathCubicBezShortTo( p, + &cpx, + &cpy, + &cpx2, + &cpy2, + args, + cmd == 's' ? 1 : 0 ); + break; + + case 'Q': + case 'q': + nsvg__pathQuadBezTo( p, &cpx, &cpy, &cpx2, &cpy2, args, + cmd == 'q' ? 1 : 0 ); + break; + + case 'T': + case 't': + nsvg__pathQuadBezShortTo( p, + &cpx, + &cpy, + &cpx2, + &cpy2, + args, + cmd == 't' ? 1 : 0 ); + break; + + case 'A': + case 'a': + nsvg__pathArcTo( p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0 ); + cpx2 = cpx; cpy2 = cpy; + break; + + default: + + if( nargs >= 2 ) + { + cpx = args[nargs - 2]; + cpy = args[nargs - 1]; + cpx2 = cpx; cpy2 = cpy; + } + + break; + } + + nargs = 0; + } + } + else + { + cmd = item[0]; + rargs = nsvg__getArgsPerElement( cmd ); + + if( cmd == 'M' || cmd == 'm' ) + { + // Commit path. + if( p->npts > 0 ) + nsvg__addPath( p, closedFlag ); + + // Start new subpath. + nsvg__resetPath( p ); + closedFlag = 0; + nargs = 0; + } + else if( cmd == 'Z' || cmd == 'z' ) + { + closedFlag = 1; + + // Commit path. + if( p->npts > 0 ) + { + // Move current point to first point + cpx = p->pts[0]; + cpy = p->pts[1]; + cpx2 = cpx; cpy2 = cpy; + nsvg__addPath( p, closedFlag ); + } + + // Start new subpath. + nsvg__resetPath( p ); + nsvg__moveTo( p, cpx, cpy ); + closedFlag = 0; + nargs = 0; + } + } + } + + // Commit path. + if( p->npts ) + nsvg__addPath( p, closedFlag ); + } + + nsvg__addShape( p ); +} + + +static void nsvg__parseRect( NSVGparser* p, const char** attr ) +{ + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + float rx = -1.0f; // marks not set + float ry = -1.0f; + int i; + + for( i = 0; attr[i]; i += 2 ) + { + if( !nsvg__parseAttr( p, attr[i], attr[i + 1] ) ) + { + if( strcmp( attr[i], "x" ) == 0 ) + x = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigX( + p ), nsvg__actualWidth( p ) ); + + if( strcmp( attr[i], "y" ) == 0 ) + y = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigY( + p ), nsvg__actualHeight( p ) ); + + if( strcmp( attr[i], "width" ) == 0 ) + w = nsvg__parseCoordinate( p, attr[i + 1], 0.0f, nsvg__actualWidth( p ) ); + + if( strcmp( attr[i], "height" ) == 0 ) + h = nsvg__parseCoordinate( p, attr[i + 1], 0.0f, nsvg__actualHeight( p ) ); + + if( strcmp( attr[i], "rx" ) == 0 ) + rx = fabsf( nsvg__parseCoordinate( p, attr[i + 1], 0.0f, nsvg__actualWidth( p ) ) ); + + + if( strcmp( attr[i], "ry" ) == 0 ) + ry = + fabsf( nsvg__parseCoordinate( p, attr[i + 1], 0.0f, nsvg__actualHeight( p ) ) ); + + } + } + + if( rx < 0.0f && ry > 0.0f ) + rx = ry; + + if( ry < 0.0f && rx > 0.0f ) + ry = rx; + + if( rx < 0.0f ) + rx = 0.0f; + + if( ry < 0.0f ) + ry = 0.0f; + + if( rx > w / 2.0f ) + rx = w / 2.0f; + + if( ry > h / 2.0f ) + ry = h / 2.0f; + + if( w != 0.0f && h != 0.0f ) + { + nsvg__resetPath( p ); + + if( rx < 0.00001f || ry < 0.0001f ) + { + nsvg__moveTo( p, x, y ); + nsvg__lineTo( p, x + w, y ); + nsvg__lineTo( p, x + w, y + h ); + nsvg__lineTo( p, x, y + h ); + } + else + { + // Rounded rectangle + nsvg__moveTo( p, x + rx, y ); + nsvg__lineTo( p, x + w - rx, y ); + nsvg__cubicBezTo( p, x + w - rx * (1 - NSVG_KAPPA90), y, x + w, + y + ry * (1 - NSVG_KAPPA90), x + w, y + ry ); + nsvg__lineTo( p, x + w, y + h - ry ); + nsvg__cubicBezTo( p, x + w, y + h - ry * (1 - NSVG_KAPPA90), + x + w - rx * (1 - NSVG_KAPPA90), y + h, x + w - rx, y + h ); + nsvg__lineTo( p, x + rx, y + h ); + nsvg__cubicBezTo( p, x + rx * (1 - NSVG_KAPPA90), y + h, x, + y + h - ry * (1 - NSVG_KAPPA90), x, y + h - ry ); + nsvg__lineTo( p, x, y + ry ); + nsvg__cubicBezTo( p, + x, + y + ry * (1 - NSVG_KAPPA90), + x + rx * (1 - NSVG_KAPPA90), + y, + x + rx, + y ); + } + + nsvg__addPath( p, 1 ); + + nsvg__addShape( p ); + } +} + + +static void nsvg__parseCircle( NSVGparser* p, const char** attr ) +{ + float cx = 0.0f; + float cy = 0.0f; + float r = 0.0f; + int i; + + for( i = 0; attr[i]; i += 2 ) + { + if( !nsvg__parseAttr( p, attr[i], attr[i + 1] ) ) + { + if( strcmp( attr[i], "cx" ) == 0 ) + cx = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigX( + p ), nsvg__actualWidth( p ) ); + + if( strcmp( attr[i], "cy" ) == 0 ) + cy = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigY( + p ), nsvg__actualHeight( p ) ); + + if( strcmp( attr[i], "r" ) == 0 ) + r = fabsf( nsvg__parseCoordinate( p, attr[i + 1], 0.0f, nsvg__actualLength( p ) ) ); + + } + } + + if( r > 0.0f ) + { + nsvg__resetPath( p ); + + nsvg__moveTo( p, cx + r, cy ); + nsvg__cubicBezTo( p, + cx + r, + cy + r * NSVG_KAPPA90, + cx + r * NSVG_KAPPA90, + cy + r, + cx, + cy + r ); + nsvg__cubicBezTo( p, + cx - r * NSVG_KAPPA90, + cy + r, + cx - r, + cy + r * NSVG_KAPPA90, + cx - r, + cy ); + nsvg__cubicBezTo( p, + cx - r, + cy - r * NSVG_KAPPA90, + cx - r * NSVG_KAPPA90, + cy - r, + cx, + cy - r ); + nsvg__cubicBezTo( p, + cx + r * NSVG_KAPPA90, + cy - r, + cx + r, + cy - r * NSVG_KAPPA90, + cx + r, + cy ); + + nsvg__addPath( p, 1 ); + + nsvg__addShape( p ); + } +} + + +static void nsvg__parseEllipse( NSVGparser* p, const char** attr ) +{ + float cx = 0.0f; + float cy = 0.0f; + float rx = 0.0f; + float ry = 0.0f; + int i; + + for( i = 0; attr[i]; i += 2 ) + { + if( !nsvg__parseAttr( p, attr[i], attr[i + 1] ) ) + { + if( strcmp( attr[i], "cx" ) == 0 ) + cx = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigX( + p ), nsvg__actualWidth( p ) ); + + if( strcmp( attr[i], "cy" ) == 0 ) + cy = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigY( + p ), nsvg__actualHeight( p ) ); + + if( strcmp( attr[i], "rx" ) == 0 ) + rx = fabsf( nsvg__parseCoordinate( p, attr[i + 1], 0.0f, nsvg__actualWidth( p ) ) ); + + + if( strcmp( attr[i], "ry" ) == 0 ) + ry = + fabsf( nsvg__parseCoordinate( p, attr[i + 1], 0.0f, nsvg__actualHeight( p ) ) ); + + } + } + + if( rx > 0.0f && ry > 0.0f ) + { + nsvg__resetPath( p ); + + nsvg__moveTo( p, cx + rx, cy ); + nsvg__cubicBezTo( p, + cx + rx, + cy + ry * NSVG_KAPPA90, + cx + rx * NSVG_KAPPA90, + cy + ry, + cx, + cy + ry ); + nsvg__cubicBezTo( p, + cx - rx * NSVG_KAPPA90, + cy + ry, + cx - rx, + cy + ry * NSVG_KAPPA90, + cx - rx, + cy ); + nsvg__cubicBezTo( p, + cx - rx, + cy - ry * NSVG_KAPPA90, + cx - rx * NSVG_KAPPA90, + cy - ry, + cx, + cy - ry ); + nsvg__cubicBezTo( p, + cx + rx * NSVG_KAPPA90, + cy - ry, + cx + rx, + cy - ry * NSVG_KAPPA90, + cx + rx, + cy ); + + nsvg__addPath( p, 1 ); + + nsvg__addShape( p ); + } +} + + +static void nsvg__parseLine( NSVGparser* p, const char** attr ) +{ + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + int i; + + for( i = 0; attr[i]; i += 2 ) + { + if( !nsvg__parseAttr( p, attr[i], attr[i + 1] ) ) + { + if( strcmp( attr[i], "x1" ) == 0 ) + x1 = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigX( + p ), nsvg__actualWidth( p ) ); + + if( strcmp( attr[i], "y1" ) == 0 ) + y1 = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigY( + p ), nsvg__actualHeight( p ) ); + + if( strcmp( attr[i], "x2" ) == 0 ) + x2 = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigX( + p ), nsvg__actualWidth( p ) ); + + if( strcmp( attr[i], "y2" ) == 0 ) + y2 = nsvg__parseCoordinate( p, attr[i + 1], nsvg__actualOrigY( + p ), nsvg__actualHeight( p ) ); + } + } + + nsvg__resetPath( p ); + + nsvg__moveTo( p, x1, y1 ); + nsvg__lineTo( p, x2, y2 ); + + nsvg__addPath( p, 0 ); + + nsvg__addShape( p ); +} + + +static void nsvg__parsePoly( NSVGparser* p, const char** attr, int closeFlag ) +{ + int i; + const char* s; + float args[2]; + int nargs, npts = 0; + char item[64]; + + nsvg__resetPath( p ); + + for( i = 0; attr[i]; i += 2 ) + { + if( !nsvg__parseAttr( p, attr[i], attr[i + 1] ) ) + { + if( strcmp( attr[i], "points" ) == 0 ) + { + s = attr[i + 1]; + nargs = 0; + + while( *s ) + { + s = nsvg__getNextPathItem( s, item ); + args[nargs++] = (float) nsvg__atof( item ); + + if( nargs >= 2 ) + { + if( npts == 0 ) + nsvg__moveTo( p, args[0], args[1] ); + else + nsvg__lineTo( p, args[0], args[1] ); + + nargs = 0; + npts++; + } + } + } + } + } + + nsvg__addPath( p, (char) closeFlag ); + + nsvg__addShape( p ); +} + + +static void nsvg__parseSVG( NSVGparser* p, const char** attr ) +{ + int i; + + for( i = 0; attr[i]; i += 2 ) + { + if( !nsvg__parseAttr( p, attr[i], attr[i + 1] ) ) + { + if( strcmp( attr[i], "width" ) == 0 ) + { + p->image->width = nsvg__parseCoordinate( p, attr[i + 1], 0.0f, 0.0f ); + } + else if( strcmp( attr[i], "height" ) == 0 ) + { + p->image->height = nsvg__parseCoordinate( p, attr[i + 1], 0.0f, 0.0f ); + } + else if( strcmp( attr[i], "viewBox" ) == 0 ) + { + sscanf( attr[i + 1], + "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", + &p->viewMinx, + &p->viewMiny, + &p->viewWidth, + &p->viewHeight ); + } + else if( strcmp( attr[i], "preserveAspectRatio" ) == 0 ) + { + if( strstr( attr[i + 1], "none" ) != 0 ) + { + // No uniform scaling + p->alignType = NSVG_ALIGN_NONE; + } + else + { + // Parse X align + if( strstr( attr[i + 1], "xMin" ) != 0 ) + p->alignX = NSVG_ALIGN_MIN; + else if( strstr( attr[i + 1], "xMid" ) != 0 ) + p->alignX = NSVG_ALIGN_MID; + else if( strstr( attr[i + 1], "xMax" ) != 0 ) + p->alignX = NSVG_ALIGN_MAX; + + // Parse X align + if( strstr( attr[i + 1], "yMin" ) != 0 ) + p->alignY = NSVG_ALIGN_MIN; + else if( strstr( attr[i + 1], "yMid" ) != 0 ) + p->alignY = NSVG_ALIGN_MID; + else if( strstr( attr[i + 1], "yMax" ) != 0 ) + p->alignY = NSVG_ALIGN_MAX; + + // Parse meet/slice + p->alignType = NSVG_ALIGN_MEET; + + if( strstr( attr[i + 1], "slice" ) != 0 ) + p->alignType = NSVG_ALIGN_SLICE; + } + } + } + } +} + + +static void nsvg__parseGradient( NSVGparser* p, const char** attr, char type ) +{ + int i; + NSVGgradientData* grad = (NSVGgradientData*) malloc( sizeof(NSVGgradientData) ); + + if( grad == NULL ) + return; + + memset( grad, 0, sizeof(NSVGgradientData) ); + grad->units = NSVG_OBJECT_SPACE; + grad->type = type; + + if( grad->type == NSVG_PAINT_LINEAR_GRADIENT ) + { + grad->linear.x1 = nsvg__coord( 0.0f, NSVG_UNITS_PERCENT ); + grad->linear.y1 = nsvg__coord( 0.0f, NSVG_UNITS_PERCENT ); + grad->linear.x2 = nsvg__coord( 100.0f, NSVG_UNITS_PERCENT ); + grad->linear.y2 = nsvg__coord( 0.0f, NSVG_UNITS_PERCENT ); + } + else if( grad->type == NSVG_PAINT_RADIAL_GRADIENT ) + { + grad->radial.cx = nsvg__coord( 50.0f, NSVG_UNITS_PERCENT ); + grad->radial.cy = nsvg__coord( 50.0f, NSVG_UNITS_PERCENT ); + grad->radial.r = nsvg__coord( 50.0f, NSVG_UNITS_PERCENT ); + } + + nsvg__xformIdentity( grad->xform ); + + for( i = 0; attr[i]; i += 2 ) + { + if( strcmp( attr[i], "id" ) == 0 ) + { + strncpy( grad->id, attr[i + 1], 63 ); + grad->id[63] = '\0'; + } + else if( !nsvg__parseAttr( p, attr[i], attr[i + 1] ) ) + { + if( strcmp( attr[i], "gradientUnits" ) == 0 ) + { + if( strcmp( attr[i + 1], "objectBoundingBox" ) == 0 ) + grad->units = NSVG_OBJECT_SPACE; + else + grad->units = NSVG_USER_SPACE; + } + else if( strcmp( attr[i], "gradientTransform" ) == 0 ) + { + nsvg__parseTransform( grad->xform, attr[i + 1] ); + } + else if( strcmp( attr[i], "cx" ) == 0 ) + { + grad->radial.cx = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "cy" ) == 0 ) + { + grad->radial.cy = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "r" ) == 0 ) + { + grad->radial.r = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "fx" ) == 0 ) + { + grad->radial.fx = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "fy" ) == 0 ) + { + grad->radial.fy = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "x1" ) == 0 ) + { + grad->linear.x1 = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "y1" ) == 0 ) + { + grad->linear.y1 = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "x2" ) == 0 ) + { + grad->linear.x2 = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "y2" ) == 0 ) + { + grad->linear.y2 = nsvg__parseCoordinateRaw( attr[i + 1] ); + } + else if( strcmp( attr[i], "spreadMethod" ) == 0 ) + { + if( strcmp( attr[i + 1], "pad" ) == 0 ) + grad->spread = NSVG_SPREAD_PAD; + else if( strcmp( attr[i + 1], "reflect" ) == 0 ) + grad->spread = NSVG_SPREAD_REFLECT; + else if( strcmp( attr[i + 1], "repeat" ) == 0 ) + grad->spread = NSVG_SPREAD_REPEAT; + } + else if( strcmp( attr[i], "xlink:href" ) == 0 ) + { + const char* href = attr[i + 1]; + strncpy( grad->ref, href + 1, 62 ); + grad->ref[62] = '\0'; + } + } + } + + grad->next = p->gradients; + p->gradients = grad; +} + + +static void nsvg__parseGradientStop( NSVGparser* p, const char** attr ) +{ + NSVGattrib* curAttr = nsvg__getAttr( p ); + NSVGgradientData* grad; + NSVGgradientStop* stop; + int i, idx; + + curAttr->stopOffset = 0; + curAttr->stopColor = 0; + curAttr->stopOpacity = 1.0f; + + for( i = 0; attr[i]; i += 2 ) + { + nsvg__parseAttr( p, attr[i], attr[i + 1] ); + } + + // Add stop to the last gradient. + grad = p->gradients; + + if( grad == NULL ) + return; + + grad->nstops++; + grad->stops = + (NSVGgradientStop*) realloc( grad->stops, sizeof(NSVGgradientStop) * grad->nstops ); + + if( grad->stops == NULL ) + return; + + // Insert + idx = grad->nstops - 1; + + for( i = 0; i < grad->nstops - 1; i++ ) + { + if( curAttr->stopOffset < grad->stops[i].offset ) + { + idx = i; + break; + } + } + + if( idx != grad->nstops - 1 ) + { + for( i = grad->nstops - 1; i > idx; i-- ) + grad->stops[i] = grad->stops[i - 1]; + } + + stop = &grad->stops[idx]; + stop->color = curAttr->stopColor; + stop->color |= (unsigned int) (curAttr->stopOpacity * 255) << 24; + stop->offset = curAttr->stopOffset; +} + + +static void nsvg__startElement( void* ud, const char* el, const char** attr ) +{ + NSVGparser* p = (NSVGparser*) ud; + + if( p->defsFlag ) + { + // Skip everything but gradients in defs + if( strcmp( el, "linearGradient" ) == 0 ) + { + nsvg__parseGradient( p, attr, NSVG_PAINT_LINEAR_GRADIENT ); + } + else if( strcmp( el, "radialGradient" ) == 0 ) + { + nsvg__parseGradient( p, attr, NSVG_PAINT_RADIAL_GRADIENT ); + } + else if( strcmp( el, "stop" ) == 0 ) + { + nsvg__parseGradientStop( p, attr ); + } + + return; + } + + if( strcmp( el, "g" ) == 0 ) + { + nsvg__pushAttr( p ); + nsvg__parseAttribs( p, attr ); + } + else if( strcmp( el, "path" ) == 0 ) + { + if( p->pathFlag ) // Do not allow nested paths. + return; + + nsvg__pushAttr( p ); + nsvg__parsePath( p, attr ); + nsvg__popAttr( p ); + } + else if( strcmp( el, "rect" ) == 0 ) + { + nsvg__pushAttr( p ); + nsvg__parseRect( p, attr ); + nsvg__popAttr( p ); + } + else if( strcmp( el, "circle" ) == 0 ) + { + nsvg__pushAttr( p ); + nsvg__parseCircle( p, attr ); + nsvg__popAttr( p ); + } + else if( strcmp( el, "ellipse" ) == 0 ) + { + nsvg__pushAttr( p ); + nsvg__parseEllipse( p, attr ); + nsvg__popAttr( p ); + } + else if( strcmp( el, "line" ) == 0 ) + { + nsvg__pushAttr( p ); + nsvg__parseLine( p, attr ); + nsvg__popAttr( p ); + } + else if( strcmp( el, "polyline" ) == 0 ) + { + nsvg__pushAttr( p ); + nsvg__parsePoly( p, attr, 0 ); + nsvg__popAttr( p ); + } + else if( strcmp( el, "polygon" ) == 0 ) + { + nsvg__pushAttr( p ); + nsvg__parsePoly( p, attr, 1 ); + nsvg__popAttr( p ); + } + else if( strcmp( el, "linearGradient" ) == 0 ) + { + nsvg__parseGradient( p, attr, NSVG_PAINT_LINEAR_GRADIENT ); + } + else if( strcmp( el, "radialGradient" ) == 0 ) + { + nsvg__parseGradient( p, attr, NSVG_PAINT_RADIAL_GRADIENT ); + } + else if( strcmp( el, "stop" ) == 0 ) + { + nsvg__parseGradientStop( p, attr ); + } + else if( strcmp( el, "defs" ) == 0 ) + { + p->defsFlag = 1; + } + else if( strcmp( el, "svg" ) == 0 ) + { + nsvg__parseSVG( p, attr ); + } +} + + +static void nsvg__endElement( void* ud, const char* el ) +{ + NSVGparser* p = (NSVGparser*) ud; + + if( strcmp( el, "g" ) == 0 ) + { + nsvg__popAttr( p ); + } + else if( strcmp( el, "path" ) == 0 ) + { + p->pathFlag = 0; + } + else if( strcmp( el, "defs" ) == 0 ) + { + p->defsFlag = 0; + } +} + + +static void nsvg__content( void* ud, const char* s ) +{ + NSVG_NOTUSED( ud ); + NSVG_NOTUSED( s ); + // empty +} + + +static void nsvg__imageBounds( NSVGparser* p, float* bounds ) +{ + NSVGshape* shape; + + shape = p->image->shapes; + + if( shape == NULL ) + { + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; + return; + } + + bounds[0] = shape->bounds[0]; + bounds[1] = shape->bounds[1]; + bounds[2] = shape->bounds[2]; + bounds[3] = shape->bounds[3]; + + for( shape = shape->next; shape != NULL; shape = shape->next ) + { + bounds[0] = nsvg__minf( bounds[0], shape->bounds[0] ); + bounds[1] = nsvg__minf( bounds[1], shape->bounds[1] ); + bounds[2] = nsvg__maxf( bounds[2], shape->bounds[2] ); + bounds[3] = nsvg__maxf( bounds[3], shape->bounds[3] ); + } +} + + +static float nsvg__viewAlign( float content, float container, int type ) +{ + if( type == NSVG_ALIGN_MIN ) + return 0; + else if( type == NSVG_ALIGN_MAX ) + return container - content; + + // mid + return (container - content) * 0.5f; +} + + +static void nsvg__scaleGradient( NSVGgradient* grad, float tx, float ty, float sx, float sy ) +{ + float t[6]; + + nsvg__xformSetTranslation( t, tx, ty ); + nsvg__xformMultiply( grad->xform, t ); + + nsvg__xformSetScale( t, sx, sy ); + nsvg__xformMultiply( grad->xform, t ); +} + + +static void nsvg__scaleToViewbox( NSVGparser* p, const char* units ) +{ + NSVGshape* shape; + NSVGpath* path; + float tx, ty, sx, sy, us, bounds[4], t[6], avgs; + int i; + float* pt; + + // Guess image size if not set completely. + nsvg__imageBounds( p, bounds ); + + if( p->viewWidth == 0 ) + { + if( p->image->width > 0 ) + { + p->viewWidth = p->image->width; + } + else + { + p->viewMinx = bounds[0]; + p->viewWidth = bounds[2] - bounds[0]; + } + } + + if( p->viewHeight == 0 ) + { + if( p->image->height > 0 ) + { + p->viewHeight = p->image->height; + } + else + { + p->viewMiny = bounds[1]; + p->viewHeight = bounds[3] - bounds[1]; + } + } + + if( p->image->width == 0 ) + p->image->width = p->viewWidth; + + if( p->image->height == 0 ) + p->image->height = p->viewHeight; + + tx = -p->viewMinx; + ty = -p->viewMiny; + sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; + sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; + // Unit scaling + us = 1.0f / + nsvg__convertToPixels( p, nsvg__coord( 1.0f, nsvg__parseUnits( units ) ), 0.0f, 1.0f ); + + // Fix aspect ratio + if( p->alignType == NSVG_ALIGN_MEET ) + { + // fit whole image into viewbox + sx = sy = nsvg__minf( sx, sy ); + tx += nsvg__viewAlign( p->viewWidth * sx, p->image->width, p->alignX ) / sx; + ty += nsvg__viewAlign( p->viewHeight * sy, p->image->height, p->alignY ) / sy; + } + else if( p->alignType == NSVG_ALIGN_SLICE ) + { + // fill whole viewbox with image + sx = sy = nsvg__maxf( sx, sy ); + tx += nsvg__viewAlign( p->viewWidth * sx, p->image->width, p->alignX ) / sx; + ty += nsvg__viewAlign( p->viewHeight * sy, p->image->height, p->alignY ) / sy; + } + + // Transform + sx *= us; + sy *= us; + avgs = (sx + sy) / 2.0f; + + for( shape = p->image->shapes; shape != NULL; shape = shape->next ) + { + shape->bounds[0] = (shape->bounds[0] + tx) * sx; + shape->bounds[1] = (shape->bounds[1] + ty) * sy; + shape->bounds[2] = (shape->bounds[2] + tx) * sx; + shape->bounds[3] = (shape->bounds[3] + ty) * sy; + + for( path = shape->paths; path != NULL; path = path->next ) + { + path->bounds[0] = (path->bounds[0] + tx) * sx; + path->bounds[1] = (path->bounds[1] + ty) * sy; + path->bounds[2] = (path->bounds[2] + tx) * sx; + path->bounds[3] = (path->bounds[3] + ty) * sy; + + for( i = 0; i < path->npts; i++ ) + { + pt = &path->pts[i * 2]; + pt[0] = (pt[0] + tx) * sx; + pt[1] = (pt[1] + ty) * sy; + } + } + + if( shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT + || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT ) + { + nsvg__scaleGradient( shape->fill.gradient, tx, ty, sx, sy ); + memcpy( t, shape->fill.gradient->xform, sizeof(float) * 6 ); + nsvg__xformInverse( shape->fill.gradient->xform, t ); + } + + if( shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT + || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT ) + { + nsvg__scaleGradient( shape->stroke.gradient, tx, ty, sx, sy ); + memcpy( t, shape->stroke.gradient->xform, sizeof(float) * 6 ); + nsvg__xformInverse( shape->stroke.gradient->xform, t ); + } + + shape->strokeWidth *= avgs; + shape->strokeDashOffset *= avgs; + + for( i = 0; i < shape->strokeDashCount; i++ ) + shape->strokeDashArray[i] *= avgs; + } +} + + +NSVGimage* nsvgParse( char* input, const char* units, float dpi ) +{ + NSVGparser* p; + NSVGimage* ret = 0; + + p = nsvg__createParser(); + + if( p == NULL ) + { + return NULL; + } + + p->dpi = dpi; + + nsvg__parseXML( input, nsvg__startElement, nsvg__endElement, nsvg__content, p ); + + // Scale to viewBox + nsvg__scaleToViewbox( p, units ); + + ret = p->image; + p->image = NULL; + + nsvg__deleteParser( p ); + + return ret; +} + + +NSVGimage* nsvgParseFromFile( const char* filename, const char* units, float dpi ) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + NSVGimage* image = NULL; + + fp = fopen( filename, "rb" ); + + if( !fp ) + goto error; + + fseek( fp, 0, SEEK_END ); + size = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + data = (char*) malloc( size + 1 ); + + if( data == NULL ) + goto error; + + if( fread( data, 1, size, fp ) != size ) + goto error; + + data[size] = '\0'; // Must be null terminated. + fclose( fp ); + image = nsvgParse( data, units, dpi ); + free( data ); + + return image; + +error: + + if( fp ) + fclose( fp ); + + if( data ) + free( data ); + + if( image ) + nsvgDelete( image ); + + return NULL; +} + + +NSVGpath* nsvgDuplicatePath( NSVGpath* p ) +{ + NSVGpath* res = NULL; + + if( p == NULL ) + return NULL; + + res = (NSVGpath*) malloc( sizeof(NSVGpath) ); + + if( res == NULL ) + goto error; + + memset( res, 0, sizeof(NSVGpath) ); + + res->pts = (float*) malloc( p->npts * 2 * sizeof(float) ); + + if( res->pts == NULL ) + goto error; + + memcpy( res->pts, p->pts, p->npts * sizeof(float) * 2 ); + res->npts = p->npts; + + memcpy( res->bounds, p->bounds, sizeof(p->bounds) ); + + res->closed = p->closed; + + return res; + +error: + + if( res != NULL ) + { + free( res->pts ); + free( res ); + } + + return NULL; +} + + +void nsvgDelete( NSVGimage* image ) +{ + NSVGshape* snext, * shape; + + if( image == NULL ) + return; + + shape = image->shapes; + + while( shape != NULL ) + { + snext = shape->next; + nsvg__deletePaths( shape->paths ); + nsvg__deletePaint( &shape->fill ); + nsvg__deletePaint( &shape->stroke ); + free( shape ); + shape = snext; + } + + free( image ); +} diff --git a/pcbnew/import_gfx/nanosvg.h b/pcbnew/import_gfx/nanosvg.h index 80ce4bb96a..c4ed0617a2 100644 --- a/pcbnew/import_gfx/nanosvg.h +++ b/pcbnew/import_gfx/nanosvg.h @@ -29,9 +29,6 @@ #ifndef NANOSVG_H #define NANOSVG_H -#ifdef __cplusplus -extern "C" { -#endif // NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. // @@ -45,15 +42,15 @@ extern "C" { // NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose // to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. // -// The units passed to NanoVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. +// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. // DPI (dots-per-inch) controls how the unit conversion is done. // // If you don't know or care about the units stuff, "px" and 96 should get you going. /* Example Usage: - // Load - NSVGImage* image; + // Load SVG + NSVGimage* image; image = nsvgParseFromFile("test.svg", "px", 96); printf("size: %f x %f\n", image->width, image->height); // Use... @@ -73,30 +70,30 @@ enum NSVGpaintType { NSVG_PAINT_NONE = 0, NSVG_PAINT_COLOR = 1, NSVG_PAINT_LINEAR_GRADIENT = 2, - NSVG_PAINT_RADIAL_GRADIENT = 3, + NSVG_PAINT_RADIAL_GRADIENT = 3 }; enum NSVGspreadType { NSVG_SPREAD_PAD = 0, NSVG_SPREAD_REFLECT = 1, - NSVG_SPREAD_REPEAT = 2, + NSVG_SPREAD_REPEAT = 2 }; enum NSVGlineJoin { NSVG_JOIN_MITER = 0, NSVG_JOIN_ROUND = 1, - NSVG_JOIN_BEVEL = 2, + NSVG_JOIN_BEVEL = 2 }; enum NSVGlineCap { NSVG_CAP_BUTT = 0, NSVG_CAP_ROUND = 1, - NSVG_CAP_SQUARE = 2, + NSVG_CAP_SQUARE = 2 }; enum NSVGfillRule { NSVG_FILLRULE_NONZERO = 0, - NSVG_FILLRULE_EVENODD = 1, + NSVG_FILLRULE_EVENODD = 1 }; enum NSVGflags { @@ -141,10 +138,11 @@ typedef struct NSVGshape float opacity; // Opacity of the shape. float strokeWidth; // Stroke width (scaled). float strokeDashOffset; // Stroke dash offset (scaled). - float strokeDashArray[8]; // Stroke dash array (scaled). - char strokeDashCount; // Number of dash values in dash array. + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. char strokeLineJoin; // Stroke join type. char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit char fillRule; // Fill rule, see NSVGfillRule. unsigned char flags; // Logical or of NSVG_FLAGS_* flags float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. @@ -166,2684 +164,10 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) // Important note: changes the string. NSVGimage* nsvgParse(char* input, const char* units, float dpi); -// Deletes list of paths. +// Duplicates a path. +NSVGpath* nsvgDuplicatePath(NSVGpath* p); + +// Deletes an image. void nsvgDelete(NSVGimage* image); -#ifdef __cplusplus -}; -#endif - #endif // NANOSVG_H - -#ifdef NANOSVG_IMPLEMENTATION - -#include -#include -#include - -#define NSVG_PI (3.14159265358979323846264338327f) -#define NSVG_KAPPA90 (0.5522847493f) // Lenght proportional to radius of a cubic bezier handle for 90deg arcs. - -#define NSVG_ALIGN_MIN 0 -#define NSVG_ALIGN_MID 1 -#define NSVG_ALIGN_MAX 2 -#define NSVG_ALIGN_NONE 0 -#define NSVG_ALIGN_MEET 1 -#define NSVG_ALIGN_SLICE 2 - -#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) -#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) - -#ifdef _MSC_VER - #pragma warning (disable: 4996) // Switch off security warnings - #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings - #ifdef __cplusplus - #define NSVG_INLINE inline - #else - #define NSVG_INLINE - #endif -#else - #define NSVG_INLINE inline -#endif - - -static int nsvg__isspace(char c) -{ - return strchr(" \t\n\v\f\r", c) != 0; -} - -static int nsvg__isdigit(char c) -{ - return strchr("0123456789", c) != 0; -} - -static int nsvg__isnum(char c) -{ - return strchr("0123456789+-.eE", c) != 0; -} - -static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } -static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } - - -// Simple XML parser - -#define NSVG_XML_TAG 1 -#define NSVG_XML_CONTENT 2 -#define NSVG_XML_MAX_ATTRIBS 256 - -static void nsvg__parseContent(char* s, - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - // Trim start white spaces - while (*s && nsvg__isspace(*s)) s++; - if (!*s) return; - - if (contentCb) - (*contentCb)(ud, s); -} - -static void nsvg__parseElement(char* s, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void* ud) -{ - const char* attr[NSVG_XML_MAX_ATTRIBS]; - int nattr = 0; - char* name; - int start = 0; - int end = 0; - char quote; - - // Skip white space after the '<' - while (*s && nsvg__isspace(*s)) s++; - - // Check if the tag is end tag - if (*s == '/') { - s++; - end = 1; - } else { - start = 1; - } - - // Skip comments, data and preprocessor stuff. - if (!*s || *s == '?' || *s == '!') - return; - - // Get tag name - name = s; - while (*s && !nsvg__isspace(*s)) s++; - if (*s) { *s++ = '\0'; } - - // Get attribs - while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { - // Skip white space before the attrib name - while (*s && nsvg__isspace(*s)) s++; - if (!*s) break; - if (*s == '/') { - end = 1; - break; - } - attr[nattr++] = s; - // Find end of the attrib name. - while (*s && !nsvg__isspace(*s) && *s != '=') s++; - if (*s) { *s++ = '\0'; } - // Skip until the beginning of the value. - while (*s && *s != '\"' && *s != '\'') s++; - if (!*s) break; - quote = *s; - s++; - // Store value and find the end of it. - attr[nattr++] = s; - while (*s && *s != quote) s++; - if (*s) { *s++ = '\0'; } - } - - // List terminator - attr[nattr++] = 0; - attr[nattr++] = 0; - - // Call callbacks. - if (start && startelCb) - (*startelCb)(ud, name, attr); - if (end && endelCb) - (*endelCb)(ud, name); -} - -int nsvg__parseXML(char* input, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - char* s = input; - char* mark = s; - int state = NSVG_XML_CONTENT; - while (*s) { - if (*s == '<' && state == NSVG_XML_CONTENT) { - // Start of a tag - *s++ = '\0'; - nsvg__parseContent(mark, contentCb, ud); - mark = s; - state = NSVG_XML_TAG; - } else if (*s == '>' && state == NSVG_XML_TAG) { - // Start of a content or new tag. - *s++ = '\0'; - nsvg__parseElement(mark, startelCb, endelCb, ud); - mark = s; - state = NSVG_XML_CONTENT; - } else { - s++; - } - } - - return 1; -} - - -/* Simple SVG parser. */ - -#define NSVG_MAX_ATTR 128 - -enum NSVGgradientUnits { - NSVG_USER_SPACE = 0, - NSVG_OBJECT_SPACE = 1, -}; - -#define NSVG_MAX_DASHES 8 - -enum NSVGunits { - NSVG_UNITS_USER, - NSVG_UNITS_PX, - NSVG_UNITS_PT, - NSVG_UNITS_PC, - NSVG_UNITS_MM, - NSVG_UNITS_CM, - NSVG_UNITS_IN, - NSVG_UNITS_PERCENT, - NSVG_UNITS_EM, - NSVG_UNITS_EX, -}; - -typedef struct NSVGcoordinate { - float value; - int units; -} NSVGcoordinate; - -typedef struct NSVGlinearData { - NSVGcoordinate x1, y1, x2, y2; -} NSVGlinearData; - -typedef struct NSVGradialData { - NSVGcoordinate cx, cy, r, fx, fy; -} NSVGradialData; - -typedef struct NSVGgradientData -{ - char id[64]; - char ref[64]; - char type; - union { - NSVGlinearData linear; - NSVGradialData radial; - }; - char spread; - char units; - float xform[6]; - int nstops; - NSVGgradientStop* stops; - struct NSVGgradientData* next; -} NSVGgradientData; - -typedef struct NSVGattrib -{ - char id[64]; - float xform[6]; - unsigned int fillColor; - unsigned int strokeColor; - float opacity; - float fillOpacity; - float strokeOpacity; - char fillGradient[64]; - char strokeGradient[64]; - float strokeWidth; - float strokeDashOffset; - float strokeDashArray[NSVG_MAX_DASHES]; - int strokeDashCount; - char strokeLineJoin; - char strokeLineCap; - char fillRule; - float fontSize; - unsigned int stopColor; - float stopOpacity; - float stopOffset; - char hasFill; - char hasStroke; - char visible; -} NSVGattrib; - -typedef struct NSVGparser -{ - NSVGattrib attr[NSVG_MAX_ATTR]; - int attrHead; - float* pts; - int npts; - int cpts; - NSVGpath* plist; - NSVGimage* image; - NSVGgradientData* gradients; - float viewMinx, viewMiny, viewWidth, viewHeight; - int alignX, alignY, alignType; - float dpi; - char pathFlag; - char defsFlag; -} NSVGparser; - -static void nsvg__xformIdentity(float* t) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetTranslation(float* t, float tx, float ty) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = tx; t[5] = ty; -} - -static void nsvg__xformSetScale(float* t, float sx, float sy) -{ - t[0] = sx; t[1] = 0.0f; - t[2] = 0.0f; t[3] = sy; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewX(float* t, float a) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = tanf(a); t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewY(float* t, float a) -{ - t[0] = 1.0f; t[1] = tanf(a); - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetRotation(float* t, float a) -{ - float cs = cosf(a), sn = sinf(a); - t[0] = cs; t[1] = sn; - t[2] = -sn; t[3] = cs; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformMultiply(float* t, float* s) -{ - float t0 = t[0] * s[0] + t[1] * s[2]; - float t2 = t[2] * s[0] + t[3] * s[2]; - float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; - t[1] = t[0] * s[1] + t[1] * s[3]; - t[3] = t[2] * s[1] + t[3] * s[3]; - t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; - t[0] = t0; - t[2] = t2; - t[4] = t4; -} - -static void nsvg__xformInverse(float* inv, float* t) -{ - double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; - if (det > -1e-6 && det < 1e-6) { - nsvg__xformIdentity(t); - return; - } - invdet = 1.0 / det; - inv[0] = (float)(t[3] * invdet); - inv[2] = (float)(-t[2] * invdet); - inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); - inv[1] = (float)(-t[1] * invdet); - inv[3] = (float)(t[0] * invdet); - inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); -} - -static void nsvg__xformPremultiply(float* t, float* s) -{ - float s2[6]; - memcpy(s2, s, sizeof(float)*6); - nsvg__xformMultiply(s2, t); - memcpy(t, s2, sizeof(float)*6); -} - -static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2] + t[4]; - *dy = x*t[1] + y*t[3] + t[5]; -} - -static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2]; - *dy = x*t[1] + y*t[3]; -} - -#define NSVG_EPSILON (1e-12) - -static int nsvg__ptInBounds(float* pt, float* bounds) -{ - return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; -} - - -static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) -{ - double it = 1.0-t; - return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; -} - -static void nsvg__curveBounds(float* bounds, float* curve) -{ - int i, j, count; - double roots[2], a, b, c, b2ac, t, v; - float* v0 = &curve[0]; - float* v1 = &curve[2]; - float* v2 = &curve[4]; - float* v3 = &curve[6]; - - // Start the bounding box by end points - bounds[0] = nsvg__minf(v0[0], v3[0]); - bounds[1] = nsvg__minf(v0[1], v3[1]); - bounds[2] = nsvg__maxf(v0[0], v3[0]); - bounds[3] = nsvg__maxf(v0[1], v3[1]); - - // Bezier curve fits inside the convex hull of it's control points. - // If control points are inside the bounds, we're done. - if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) - return; - - // Add bezier curve inflection points in X and Y. - for (i = 0; i < 2; i++) { - a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; - b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; - c = 3.0 * v1[i] - 3.0 * v0[i]; - count = 0; - if (fabs(a) < NSVG_EPSILON) { - if (fabs(b) > NSVG_EPSILON) { - t = -c / b; - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } else { - b2ac = b*b - 4.0*c*a; - if (b2ac > NSVG_EPSILON) { - t = (-b + sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - t = (-b - sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } - for (j = 0; j < count; j++) { - v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); - bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); - bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); - } - } -} - -static NSVGparser* nsvg__createParser() -{ - NSVGparser* p; - p = (NSVGparser*)malloc(sizeof(NSVGparser)); - if (p == NULL) goto error; - memset(p, 0, sizeof(NSVGparser)); - - p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); - if (p->image == NULL) goto error; - memset(p->image, 0, sizeof(NSVGimage)); - - // Init style - nsvg__xformIdentity(p->attr[0].xform); - memset(p->attr[0].id, 0, sizeof p->attr[0].id); - p->attr[0].fillColor = NSVG_RGB(0,0,0); - p->attr[0].strokeColor = NSVG_RGB(0,0,0); - p->attr[0].opacity = 1; - p->attr[0].fillOpacity = 1; - p->attr[0].strokeOpacity = 1; - p->attr[0].stopOpacity = 1; - p->attr[0].strokeWidth = 1; - p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; - p->attr[0].strokeLineCap = NSVG_CAP_BUTT; - p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; - p->attr[0].hasFill = 1; - p->attr[0].visible = 1; - - return p; - -error: - if (p) { - if (p->image) free(p->image); - free(p); - } - return NULL; -} - -static void nsvg__deletePaths(NSVGpath* path) -{ - while (path) { - NSVGpath *next = path->next; - if (path->pts != NULL) - free(path->pts); - free(path); - path = next; - } -} - -static void nsvg__deletePaint(NSVGpaint* paint) -{ - if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) - free(paint->gradient); -} - -static void nsvg__deleteGradientData(NSVGgradientData* grad) -{ - NSVGgradientData* next; - while (grad != NULL) { - next = grad->next; - free(grad->stops); - free(grad); - grad = next; - } -} - -static void nsvg__deleteParser(NSVGparser* p) -{ - if (p != NULL) { - nsvg__deletePaths(p->plist); - nsvg__deleteGradientData(p->gradients); - nsvgDelete(p->image); - free(p->pts); - free(p); - } -} - -static void nsvg__resetPath(NSVGparser* p) -{ - p->npts = 0; -} - -static void nsvg__addPoint(NSVGparser* p, float x, float y) -{ - if (p->npts+1 > p->cpts) { - p->cpts = p->cpts ? p->cpts*2 : 8; - p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); - if (!p->pts) return; - } - p->pts[p->npts*2+0] = x; - p->pts[p->npts*2+1] = y; - p->npts++; -} - -static void nsvg__moveTo(NSVGparser* p, float x, float y) -{ - if (p->npts > 0) { - p->pts[(p->npts-1)*2+0] = x; - p->pts[(p->npts-1)*2+1] = y; - } else { - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__lineTo(NSVGparser* p, float x, float y) -{ - float px,py, dx,dy; - if (p->npts > 0) { - px = p->pts[(p->npts-1)*2+0]; - py = p->pts[(p->npts-1)*2+1]; - dx = x - px; - dy = y - py; - nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); - nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) -{ - nsvg__addPoint(p, cpx1, cpy1); - nsvg__addPoint(p, cpx2, cpy2); - nsvg__addPoint(p, x, y); -} - -static NSVGattrib* nsvg__getAttr(NSVGparser* p) -{ - return &p->attr[p->attrHead]; -} - -static void nsvg__pushAttr(NSVGparser* p) -{ - if (p->attrHead < NSVG_MAX_ATTR-1) { - p->attrHead++; - memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); - } -} - -static void nsvg__popAttr(NSVGparser* p) -{ - if (p->attrHead > 0) - p->attrHead--; -} - -static float nsvg__actualOrigX(NSVGparser* p) -{ - return p->viewMinx; -} - -static float nsvg__actualOrigY(NSVGparser* p) -{ - return p->viewMiny; -} - -static float nsvg__actualWidth(NSVGparser* p) -{ - return p->viewWidth; -} - -static float nsvg__actualHeight(NSVGparser* p) -{ - return p->viewHeight; -} - -static float nsvg__actualLength(NSVGparser* p) -{ - float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); - return sqrtf(w*w + h*h) / sqrtf(2.0f); -} - -static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) -{ - NSVGattrib* attr = nsvg__getAttr(p); - switch (c.units) { - case NSVG_UNITS_USER: return c.value; - case NSVG_UNITS_PX: return c.value; - case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; - case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; - case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; - case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; - case NSVG_UNITS_IN: return c.value * p->dpi; - case NSVG_UNITS_EM: return c.value * attr->fontSize; - case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. - case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; - default: return c.value; - } - return c.value; -} - -static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) -{ - NSVGgradientData* grad = p->gradients; - while (grad) { - if (strcmp(grad->id, id) == 0) - return grad; - grad = grad->next; - } - return NULL; -} - -static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGgradientData* data = NULL; - NSVGgradientData* ref = NULL; - NSVGgradientStop* stops = NULL; - NSVGgradient* grad; - float ox, oy, sw, sh, sl; - int nstops = 0; - - data = nsvg__findGradientData(p, id); - if (data == NULL) return NULL; - - // TODO: use ref to fill in all unset values too. - ref = data; - while (ref != NULL) { - if (stops == NULL && ref->stops != NULL) { - stops = ref->stops; - nstops = ref->nstops; - break; - } - ref = nsvg__findGradientData(p, ref->ref); - } - if (stops == NULL) return NULL; - - grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); - if (grad == NULL) return NULL; - - // The shape width and height. - if (data->units == NSVG_OBJECT_SPACE) { - ox = localBounds[0]; - oy = localBounds[1]; - sw = localBounds[2] - localBounds[0]; - sh = localBounds[3] - localBounds[1]; - } else { - ox = nsvg__actualOrigX(p); - oy = nsvg__actualOrigY(p); - sw = nsvg__actualWidth(p); - sh = nsvg__actualHeight(p); - } - sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); - - if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { - float x1, y1, x2, y2, dx, dy; - x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); - y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); - x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); - y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); - // Calculate transform aligned to the line - dx = x2 - x1; - dy = y2 - y1; - grad->xform[0] = dy; grad->xform[1] = -dx; - grad->xform[2] = dx; grad->xform[3] = dy; - grad->xform[4] = x1; grad->xform[5] = y1; - } else { - float cx, cy, fx, fy, r; - cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); - cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); - fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); - fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); - r = nsvg__convertToPixels(p, data->radial.r, 0, sl); - // Calculate transform aligned to the circle - grad->xform[0] = r; grad->xform[1] = 0; - grad->xform[2] = 0; grad->xform[3] = r; - grad->xform[4] = cx; grad->xform[5] = cy; - grad->fx = fx / r; - grad->fy = fy / r; - } - - nsvg__xformMultiply(grad->xform, data->xform); - nsvg__xformMultiply(grad->xform, attr->xform); - - grad->spread = data->spread; - memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); - grad->nstops = nstops; - - *paintType = data->type; - - return grad; -} - -static float nsvg__getAverageScale(float* t) -{ - float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); - float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); - return (sx + sy) * 0.5f; -} - -static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) -{ - NSVGpath* path; - float curve[4*2], curveBounds[4]; - int i, first = 1; - for (path = shape->paths; path != NULL; path = path->next) { - nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); - for (i = 0; i < path->npts-1; i += 3) { - nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); - nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); - nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); - nsvg__curveBounds(curveBounds, curve); - if (first) { - bounds[0] = curveBounds[0]; - bounds[1] = curveBounds[1]; - bounds[2] = curveBounds[2]; - bounds[3] = curveBounds[3]; - first = 0; - } else { - bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); - bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); - bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); - bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); - } - curve[0] = curve[6]; - curve[1] = curve[7]; - } - } -} - -static void nsvg__addShape(NSVGparser* p) -{ - NSVGattrib* attr = nsvg__getAttr(p); - float scale = 1.0f; - NSVGshape *shape, *cur, *prev; - NSVGpath* path; - int i; - - if (p->plist == NULL) - return; - - shape = (NSVGshape*)malloc(sizeof(NSVGshape)); - if (shape == NULL) goto error; - memset(shape, 0, sizeof(NSVGshape)); - - memcpy(shape->id, attr->id, sizeof shape->id); - scale = nsvg__getAverageScale(attr->xform); - shape->strokeWidth = attr->strokeWidth * scale; - shape->strokeDashOffset = attr->strokeDashOffset * scale; - shape->strokeDashCount = attr->strokeDashCount; - for (i = 0; i < attr->strokeDashCount; i++) - shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; - shape->strokeLineJoin = attr->strokeLineJoin; - shape->strokeLineCap = attr->strokeLineCap; - shape->fillRule = attr->fillRule; - shape->opacity = attr->opacity; - - shape->paths = p->plist; - p->plist = NULL; - - // Calculate shape bounds - shape->bounds[0] = shape->paths->bounds[0]; - shape->bounds[1] = shape->paths->bounds[1]; - shape->bounds[2] = shape->paths->bounds[2]; - shape->bounds[3] = shape->paths->bounds[3]; - for (path = shape->paths->next; path != NULL; path = path->next) { - shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); - shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); - shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); - shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); - } - - // Set fill - if (attr->hasFill == 0) { - shape->fill.type = NSVG_PAINT_NONE; - } else if (attr->hasFill == 1) { - shape->fill.type = NSVG_PAINT_COLOR; - shape->fill.color = attr->fillColor; - shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; - } else if (attr->hasFill == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); - if (shape->fill.gradient == NULL) { - shape->fill.type = NSVG_PAINT_NONE; - } - } - - // Set stroke - if (attr->hasStroke == 0) { - shape->stroke.type = NSVG_PAINT_NONE; - } else if (attr->hasStroke == 1) { - shape->stroke.type = NSVG_PAINT_COLOR; - shape->stroke.color = attr->strokeColor; - shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; - } else if (attr->hasStroke == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); - if (shape->stroke.gradient == NULL) - shape->stroke.type = NSVG_PAINT_NONE; - } - - // Set flags - shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); - - // Add to tail - prev = NULL; - cur = p->image->shapes; - while (cur != NULL) { - prev = cur; - cur = cur->next; - } - if (prev == NULL) - p->image->shapes = shape; - else - prev->next = shape; - - return; - -error: - if (shape) free(shape); -} - -static void nsvg__addPath(NSVGparser* p, char closed) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGpath* path = NULL; - float bounds[4]; - float* curve; - int i; - - if (p->npts < 4) - return; - - if (closed) - nsvg__lineTo(p, p->pts[0], p->pts[1]); - - path = (NSVGpath*)malloc(sizeof(NSVGpath)); - if (path == NULL) goto error; - memset(path, 0, sizeof(NSVGpath)); - - path->pts = (float*)malloc(p->npts*2*sizeof(float)); - if (path->pts == NULL) goto error; - path->closed = closed; - path->npts = p->npts; - - // Transform path. - for (i = 0; i < p->npts; ++i) - nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); - - // Find bounds - for (i = 0; i < path->npts-1; i += 3) { - curve = &path->pts[i*2]; - nsvg__curveBounds(bounds, curve); - if (i == 0) { - path->bounds[0] = bounds[0]; - path->bounds[1] = bounds[1]; - path->bounds[2] = bounds[2]; - path->bounds[3] = bounds[3]; - } else { - path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); - path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); - path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); - path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); - } - } - - path->next = p->plist; - p->plist = path; - - return; - -error: - if (path != NULL) { - if (path->pts != NULL) free(path->pts); - free(path); - } -} - -static const char* nsvg__parseNumber(const char* s, char* it, const int size) -{ - const int last = size-1; - int i = 0; - - // sign - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - // integer part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - if (*s == '.') { - // decimal point - if (i < last) it[i++] = *s; - s++; - // fraction part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - // exponent - if (*s == 'e' || *s == 'E') { - if (i < last) it[i++] = *s; - s++; - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - it[i] = '\0'; - - return s; -} - -static const char* nsvg__getNextPathItem(const char* s, char* it) -{ - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - if (!*s) return s; - if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { - s = nsvg__parseNumber(s, it, 64); - } else { - // Parse command - it[0] = *s++; - it[1] = '\0'; - return s; - } - - return s; -} - -static unsigned int nsvg__parseColorHex(const char* str) -{ - unsigned int c = 0, r = 0, g = 0, b = 0; - int n = 0; - str++; // skip # - // Calculate number of characters. - while(str[n] && !nsvg__isspace(str[n])) - n++; - if (n == 6) { - sscanf(str, "%x", &c); - } else if (n == 3) { - sscanf(str, "%x", &c); - c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); - c |= c<<4; - } - r = (c >> 16) & 0xff; - g = (c >> 8) & 0xff; - b = c & 0xff; - return NSVG_RGB(r,g,b); -} - -static unsigned int nsvg__parseColorRGB(const char* str) -{ - int r = -1, g = -1, b = -1; - char s1[32]="", s2[32]=""; - sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); - if (strchr(s1, '%')) { - return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); - } else { - return NSVG_RGB(r,g,b); - } -} - -typedef struct NSVGNamedColor { - const char* name; - unsigned int color; -} NSVGNamedColor; - -NSVGNamedColor nsvg__colors[] = { - - { "red", NSVG_RGB(255, 0, 0) }, - { "green", NSVG_RGB( 0, 128, 0) }, - { "blue", NSVG_RGB( 0, 0, 255) }, - { "yellow", NSVG_RGB(255, 255, 0) }, - { "cyan", NSVG_RGB( 0, 255, 255) }, - { "magenta", NSVG_RGB(255, 0, 255) }, - { "black", NSVG_RGB( 0, 0, 0) }, - { "grey", NSVG_RGB(128, 128, 128) }, - { "gray", NSVG_RGB(128, 128, 128) }, - { "white", NSVG_RGB(255, 255, 255) }, - -#ifdef NANOSVG_ALL_COLOR_KEYWORDS - { "aliceblue", NSVG_RGB(240, 248, 255) }, - { "antiquewhite", NSVG_RGB(250, 235, 215) }, - { "aqua", NSVG_RGB( 0, 255, 255) }, - { "aquamarine", NSVG_RGB(127, 255, 212) }, - { "azure", NSVG_RGB(240, 255, 255) }, - { "beige", NSVG_RGB(245, 245, 220) }, - { "bisque", NSVG_RGB(255, 228, 196) }, - { "blanchedalmond", NSVG_RGB(255, 235, 205) }, - { "blueviolet", NSVG_RGB(138, 43, 226) }, - { "brown", NSVG_RGB(165, 42, 42) }, - { "burlywood", NSVG_RGB(222, 184, 135) }, - { "cadetblue", NSVG_RGB( 95, 158, 160) }, - { "chartreuse", NSVG_RGB(127, 255, 0) }, - { "chocolate", NSVG_RGB(210, 105, 30) }, - { "coral", NSVG_RGB(255, 127, 80) }, - { "cornflowerblue", NSVG_RGB(100, 149, 237) }, - { "cornsilk", NSVG_RGB(255, 248, 220) }, - { "crimson", NSVG_RGB(220, 20, 60) }, - { "darkblue", NSVG_RGB( 0, 0, 139) }, - { "darkcyan", NSVG_RGB( 0, 139, 139) }, - { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, - { "darkgray", NSVG_RGB(169, 169, 169) }, - { "darkgreen", NSVG_RGB( 0, 100, 0) }, - { "darkgrey", NSVG_RGB(169, 169, 169) }, - { "darkkhaki", NSVG_RGB(189, 183, 107) }, - { "darkmagenta", NSVG_RGB(139, 0, 139) }, - { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, - { "darkorange", NSVG_RGB(255, 140, 0) }, - { "darkorchid", NSVG_RGB(153, 50, 204) }, - { "darkred", NSVG_RGB(139, 0, 0) }, - { "darksalmon", NSVG_RGB(233, 150, 122) }, - { "darkseagreen", NSVG_RGB(143, 188, 143) }, - { "darkslateblue", NSVG_RGB( 72, 61, 139) }, - { "darkslategray", NSVG_RGB( 47, 79, 79) }, - { "darkslategrey", NSVG_RGB( 47, 79, 79) }, - { "darkturquoise", NSVG_RGB( 0, 206, 209) }, - { "darkviolet", NSVG_RGB(148, 0, 211) }, - { "deeppink", NSVG_RGB(255, 20, 147) }, - { "deepskyblue", NSVG_RGB( 0, 191, 255) }, - { "dimgray", NSVG_RGB(105, 105, 105) }, - { "dimgrey", NSVG_RGB(105, 105, 105) }, - { "dodgerblue", NSVG_RGB( 30, 144, 255) }, - { "firebrick", NSVG_RGB(178, 34, 34) }, - { "floralwhite", NSVG_RGB(255, 250, 240) }, - { "forestgreen", NSVG_RGB( 34, 139, 34) }, - { "fuchsia", NSVG_RGB(255, 0, 255) }, - { "gainsboro", NSVG_RGB(220, 220, 220) }, - { "ghostwhite", NSVG_RGB(248, 248, 255) }, - { "gold", NSVG_RGB(255, 215, 0) }, - { "goldenrod", NSVG_RGB(218, 165, 32) }, - { "greenyellow", NSVG_RGB(173, 255, 47) }, - { "honeydew", NSVG_RGB(240, 255, 240) }, - { "hotpink", NSVG_RGB(255, 105, 180) }, - { "indianred", NSVG_RGB(205, 92, 92) }, - { "indigo", NSVG_RGB( 75, 0, 130) }, - { "ivory", NSVG_RGB(255, 255, 240) }, - { "khaki", NSVG_RGB(240, 230, 140) }, - { "lavender", NSVG_RGB(230, 230, 250) }, - { "lavenderblush", NSVG_RGB(255, 240, 245) }, - { "lawngreen", NSVG_RGB(124, 252, 0) }, - { "lemonchiffon", NSVG_RGB(255, 250, 205) }, - { "lightblue", NSVG_RGB(173, 216, 230) }, - { "lightcoral", NSVG_RGB(240, 128, 128) }, - { "lightcyan", NSVG_RGB(224, 255, 255) }, - { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, - { "lightgray", NSVG_RGB(211, 211, 211) }, - { "lightgreen", NSVG_RGB(144, 238, 144) }, - { "lightgrey", NSVG_RGB(211, 211, 211) }, - { "lightpink", NSVG_RGB(255, 182, 193) }, - { "lightsalmon", NSVG_RGB(255, 160, 122) }, - { "lightseagreen", NSVG_RGB( 32, 178, 170) }, - { "lightskyblue", NSVG_RGB(135, 206, 250) }, - { "lightslategray", NSVG_RGB(119, 136, 153) }, - { "lightslategrey", NSVG_RGB(119, 136, 153) }, - { "lightsteelblue", NSVG_RGB(176, 196, 222) }, - { "lightyellow", NSVG_RGB(255, 255, 224) }, - { "lime", NSVG_RGB( 0, 255, 0) }, - { "limegreen", NSVG_RGB( 50, 205, 50) }, - { "linen", NSVG_RGB(250, 240, 230) }, - { "maroon", NSVG_RGB(128, 0, 0) }, - { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, - { "mediumblue", NSVG_RGB( 0, 0, 205) }, - { "mediumorchid", NSVG_RGB(186, 85, 211) }, - { "mediumpurple", NSVG_RGB(147, 112, 219) }, - { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, - { "mediumslateblue", NSVG_RGB(123, 104, 238) }, - { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, - { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, - { "mediumvioletred", NSVG_RGB(199, 21, 133) }, - { "midnightblue", NSVG_RGB( 25, 25, 112) }, - { "mintcream", NSVG_RGB(245, 255, 250) }, - { "mistyrose", NSVG_RGB(255, 228, 225) }, - { "moccasin", NSVG_RGB(255, 228, 181) }, - { "navajowhite", NSVG_RGB(255, 222, 173) }, - { "navy", NSVG_RGB( 0, 0, 128) }, - { "oldlace", NSVG_RGB(253, 245, 230) }, - { "olive", NSVG_RGB(128, 128, 0) }, - { "olivedrab", NSVG_RGB(107, 142, 35) }, - { "orange", NSVG_RGB(255, 165, 0) }, - { "orangered", NSVG_RGB(255, 69, 0) }, - { "orchid", NSVG_RGB(218, 112, 214) }, - { "palegoldenrod", NSVG_RGB(238, 232, 170) }, - { "palegreen", NSVG_RGB(152, 251, 152) }, - { "paleturquoise", NSVG_RGB(175, 238, 238) }, - { "palevioletred", NSVG_RGB(219, 112, 147) }, - { "papayawhip", NSVG_RGB(255, 239, 213) }, - { "peachpuff", NSVG_RGB(255, 218, 185) }, - { "peru", NSVG_RGB(205, 133, 63) }, - { "pink", NSVG_RGB(255, 192, 203) }, - { "plum", NSVG_RGB(221, 160, 221) }, - { "powderblue", NSVG_RGB(176, 224, 230) }, - { "purple", NSVG_RGB(128, 0, 128) }, - { "rosybrown", NSVG_RGB(188, 143, 143) }, - { "royalblue", NSVG_RGB( 65, 105, 225) }, - { "saddlebrown", NSVG_RGB(139, 69, 19) }, - { "salmon", NSVG_RGB(250, 128, 114) }, - { "sandybrown", NSVG_RGB(244, 164, 96) }, - { "seagreen", NSVG_RGB( 46, 139, 87) }, - { "seashell", NSVG_RGB(255, 245, 238) }, - { "sienna", NSVG_RGB(160, 82, 45) }, - { "silver", NSVG_RGB(192, 192, 192) }, - { "skyblue", NSVG_RGB(135, 206, 235) }, - { "slateblue", NSVG_RGB(106, 90, 205) }, - { "slategray", NSVG_RGB(112, 128, 144) }, - { "slategrey", NSVG_RGB(112, 128, 144) }, - { "snow", NSVG_RGB(255, 250, 250) }, - { "springgreen", NSVG_RGB( 0, 255, 127) }, - { "steelblue", NSVG_RGB( 70, 130, 180) }, - { "tan", NSVG_RGB(210, 180, 140) }, - { "teal", NSVG_RGB( 0, 128, 128) }, - { "thistle", NSVG_RGB(216, 191, 216) }, - { "tomato", NSVG_RGB(255, 99, 71) }, - { "turquoise", NSVG_RGB( 64, 224, 208) }, - { "violet", NSVG_RGB(238, 130, 238) }, - { "wheat", NSVG_RGB(245, 222, 179) }, - { "whitesmoke", NSVG_RGB(245, 245, 245) }, - { "yellowgreen", NSVG_RGB(154, 205, 50) }, -#endif -}; - -static unsigned int nsvg__parseColorName(const char* str) -{ - int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); - - for (i = 0; i < ncolors; i++) { - if (strcmp(nsvg__colors[i].name, str) == 0) { - return nsvg__colors[i].color; - } - } - - return NSVG_RGB(128, 128, 128); -} - -static unsigned int nsvg__parseColor(const char* str) -{ - size_t len = 0; - while(*str == ' ') ++str; - len = strlen(str); - if (len >= 1 && *str == '#') - return nsvg__parseColorHex(str); - else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') - return nsvg__parseColorRGB(str); - return nsvg__parseColorName(str); -} - -static float nsvg__parseOpacity(const char* str) -{ - float val = 0; - sscanf(str, "%f", &val); - if (val < 0.0f) val = 0.0f; - if (val > 1.0f) val = 1.0f; - return val; -} - -static int nsvg__parseUnits(const char* units) -{ - if (units[0] == 'p' && units[1] == 'x') - return NSVG_UNITS_PX; - else if (units[0] == 'p' && units[1] == 't') - return NSVG_UNITS_PT; - else if (units[0] == 'p' && units[1] == 'c') - return NSVG_UNITS_PC; - else if (units[0] == 'm' && units[1] == 'm') - return NSVG_UNITS_MM; - else if (units[0] == 'c' && units[1] == 'm') - return NSVG_UNITS_CM; - else if (units[0] == 'i' && units[1] == 'n') - return NSVG_UNITS_IN; - else if (units[0] == '%') - return NSVG_UNITS_PERCENT; - else if (units[0] == 'e' && units[1] == 'm') - return NSVG_UNITS_EM; - else if (units[0] == 'e' && units[1] == 'x') - return NSVG_UNITS_EX; - return NSVG_UNITS_USER; -} - -static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) -{ - NSVGcoordinate coord = {0, NSVG_UNITS_USER}; - char units[32]=""; - sscanf(str, "%f%s", &coord.value, units); - coord.units = nsvg__parseUnits(units); - return coord; -} - -static NSVGcoordinate nsvg__coord(float v, int units) -{ - NSVGcoordinate coord = {v, units}; - return coord; -} - -static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) -{ - NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); - return nsvg__convertToPixels(p, coord, orig, length); -} - -static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) -{ - const char* end; - const char* ptr; - char it[64]; - - *na = 0; - ptr = str; - while (*ptr && *ptr != '(') ++ptr; - if (*ptr == 0) - return 1; - end = ptr; - while (*end && *end != ')') ++end; - if (*end == 0) - return 1; - - while (ptr < end) { - if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { - if (*na >= maxNa) return 0; - ptr = nsvg__parseNumber(ptr, it, 64); - args[(*na)++] = (float)atof(it); - } else { - ++ptr; - } - } - return (int)(end - str); -} - - -static int nsvg__parseMatrix(float* xform, const char* str) -{ - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, t, 6, &na); - if (na != 6) return len; - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseTranslate(float* xform, const char* str) -{ - float args[2]; - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = 0.0; - - nsvg__xformSetTranslation(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseScale(float* xform, const char* str) -{ - float args[2]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = args[0]; - nsvg__xformSetScale(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewX(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewY(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseRotate(float* xform, const char* str) -{ - float args[3]; - int na = 0; - float m[6]; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 3, &na); - if (na == 1) - args[1] = args[2] = 0.0f; - nsvg__xformIdentity(m); - - if (na > 1) { - nsvg__xformSetTranslation(t, -args[1], -args[2]); - nsvg__xformMultiply(m, t); - } - - nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); - nsvg__xformMultiply(m, t); - - if (na > 1) { - nsvg__xformSetTranslation(t, args[1], args[2]); - nsvg__xformMultiply(m, t); - } - - memcpy(xform, m, sizeof(float)*6); - - return len; -} - -static void nsvg__parseTransform(float* xform, const char* str) -{ - float t[6]; - nsvg__xformIdentity(xform); - while (*str) - { - if (strncmp(str, "matrix", 6) == 0) - str += nsvg__parseMatrix(t, str); - else if (strncmp(str, "translate", 9) == 0) - str += nsvg__parseTranslate(t, str); - else if (strncmp(str, "scale", 5) == 0) - str += nsvg__parseScale(t, str); - else if (strncmp(str, "rotate", 6) == 0) - str += nsvg__parseRotate(t, str); - else if (strncmp(str, "skewX", 5) == 0) - str += nsvg__parseSkewX(t, str); - else if (strncmp(str, "skewY", 5) == 0) - str += nsvg__parseSkewY(t, str); - else{ - ++str; - continue; - } - - nsvg__xformPremultiply(xform, t); - } -} - -static void nsvg__parseUrl(char* id, const char* str) -{ - int i = 0; - str += 4; // "url("; - if (*str == '#') - str++; - while (i < 63 && *str != ')') { - id[i] = *str++; - i++; - } - id[i] = '\0'; -} - -static char nsvg__parseLineCap(const char* str) -{ - if (strcmp(str, "butt") == 0) - return NSVG_CAP_BUTT; - else if (strcmp(str, "round") == 0) - return NSVG_CAP_ROUND; - else if (strcmp(str, "square") == 0) - return NSVG_CAP_SQUARE; - // TODO: handle inherit. - return NSVG_CAP_BUTT; -} - -static char nsvg__parseLineJoin(const char* str) -{ - if (strcmp(str, "miter") == 0) - return NSVG_JOIN_MITER; - else if (strcmp(str, "round") == 0) - return NSVG_JOIN_ROUND; - else if (strcmp(str, "bevel") == 0) - return NSVG_JOIN_BEVEL; - // TODO: handle inherit. - return NSVG_CAP_BUTT; -} - -static char nsvg__parseFillRule(const char* str) -{ - if (strcmp(str, "nonzero") == 0) - return NSVG_FILLRULE_NONZERO; - else if (strcmp(str, "evenodd") == 0) - return NSVG_FILLRULE_EVENODD; - // TODO: handle inherit. - return NSVG_FILLRULE_NONZERO; -} - -static const char* nsvg__getNextDashItem(const char* s, char* it) -{ - int n = 0; - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - // Advance until whitespace, comma or end. - while (*s && (!nsvg__isspace(*s) && *s != ',')) { - if (n < 63) - it[n++] = *s; - s++; - } - it[n++] = '\0'; - return s; -} - -static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) -{ - char item[64]; - int count = 0, i; - float sum = 0.0f; - - // Handle "none" - if (str[0] == 'n') - return 0; - - // Parse dashes - while (*str) { - str = nsvg__getNextDashItem(str, item); - if (!*item) break; - if (count < NSVG_MAX_DASHES) - strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); - } - - for (i = 0; i < count; i++) - sum += strokeDashArray[i]; - if (sum <= 1e-6f) - count = 0; - - return count; -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str); - -static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) -{ - float xform[6]; - NSVGattrib* attr = nsvg__getAttr(p); - if (!attr) return 0; - - if (strcmp(name, "style") == 0) { - nsvg__parseStyle(p, value); - } else if (strcmp(name, "display") == 0) { - if (strcmp(value, "none") == 0) - attr->visible = 0; - // Don't reset ->visible on display:inline, one display:none hides the whole subtree - - } else if (strcmp(name, "fill") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasFill = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasFill = 2; - nsvg__parseUrl(attr->fillGradient, value); - } else { - attr->hasFill = 1; - attr->fillColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "opacity") == 0) { - attr->opacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "fill-opacity") == 0) { - attr->fillOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasStroke = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasStroke = 2; - nsvg__parseUrl(attr->strokeGradient, value); - } else { - attr->hasStroke = 1; - attr->strokeColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "stroke-width") == 0) { - attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-dasharray") == 0) { - attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); - } else if (strcmp(name, "stroke-dashoffset") == 0) { - attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-opacity") == 0) { - attr->strokeOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke-linecap") == 0) { - attr->strokeLineCap = nsvg__parseLineCap(value); - } else if (strcmp(name, "stroke-linejoin") == 0) { - attr->strokeLineJoin = nsvg__parseLineJoin(value); - } else if (strcmp(name, "fill-rule") == 0) { - attr->fillRule = nsvg__parseFillRule(value); - } else if (strcmp(name, "font-size") == 0) { - attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "transform") == 0) { - nsvg__parseTransform(xform, value); - nsvg__xformPremultiply(attr->xform, xform); - } else if (strcmp(name, "stop-color") == 0) { - attr->stopColor = nsvg__parseColor(value); - } else if (strcmp(name, "stop-opacity") == 0) { - attr->stopOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "offset") == 0) { - attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); - } else if (strcmp(name, "id") == 0) { - strncpy(attr->id, value, 63); - attr->id[63] = '\0'; - } else { - return 0; - } - return 1; -} - -static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) -{ - const char* str; - const char* val; - char name[512]; - char value[512]; - int n; - - str = start; - while (str < end && *str != ':') ++str; - - val = str; - - // Right Trim - while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; - ++str; - - n = (int)(str - start); - if (n > 511) n = 511; - if (n) memcpy(name, start, n); - name[n] = 0; - - while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; - - n = (int)(end - val); - if (n > 511) n = 511; - if (n) memcpy(value, val, n); - value[n] = 0; - - return nsvg__parseAttr(p, name, value); -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str) -{ - const char* start; - const char* end; - - while (*str) { - // Left Trim - while(*str && nsvg__isspace(*str)) ++str; - start = str; - while(*str && *str != ';') ++str; - end = str; - - // Right Trim - while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; - ++end; - - nsvg__parseNameValue(p, start, end); - if (*str) ++str; - } -} - -static void nsvg__parseAttribs(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) - { - if (strcmp(attr[i], "style") == 0) - nsvg__parseStyle(p, attr[i + 1]); - else - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } -} - -static int nsvg__getArgsPerElement(char cmd) -{ - switch (cmd) { - case 'v': - case 'V': - case 'h': - case 'H': - return 1; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - return 2; - case 'q': - case 'Q': - case 's': - case 'S': - return 4; - case 'c': - case 'C': - return 6; - case 'a': - case 'A': - return 7; - } - return 0; -} - -static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__moveTo(p, *cpx, *cpy); -} - -static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpx += args[0]; - else - *cpx = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpy += args[0]; - else - *cpy = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x2, y2, cx1, cy1, cx2, cy2; - - if (rel) { - cx1 = *cpx + args[0]; - cy1 = *cpy + args[1]; - cx2 = *cpx + args[2]; - cy2 = *cpy + args[3]; - x2 = *cpx + args[4]; - y2 = *cpy + args[5]; - } else { - cx1 = args[0]; - cy1 = args[1]; - cx2 = args[2]; - cy2 = args[3]; - x2 = args[4]; - y2 = args[5]; - } - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx2 = *cpx + args[0]; - cy2 = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx2 = args[0]; - cy2 = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - cx1 = 2*x1 - *cpx2; - cy1 = 2*y1 - *cpy2; - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx = *cpx + args[0]; - cy = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx = args[0]; - cy = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - // Convert to cubic bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - x2 = *cpx + args[0]; - y2 = *cpy + args[1]; - } else { - x2 = args[0]; - y2 = args[1]; - } - - cx = 2*x1 - *cpx2; - cy = 2*y1 - *cpy2; - - // Convert to cubix bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static float nsvg__sqr(float x) { return x*x; } -static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } - -static float nsvg__vecrat(float ux, float uy, float vx, float vy) -{ - return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); -} - -static float nsvg__vecang(float ux, float uy, float vx, float vy) -{ - float r = nsvg__vecrat(ux,uy, vx,vy); - if (r < -1.0f) r = -1.0f; - if (r > 1.0f) r = 1.0f; - return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); -} - -static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - // Ported from canvg (https://code.google.com/p/canvg/) - float rx, ry, rotx; - float x1, y1, x2, y2, cx, cy, dx, dy, d; - float x1p, y1p, cxp, cyp, s, sa, sb; - float ux, uy, vx, vy, a1, da; - float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; - float sinrx, cosrx; - int fa, fs; - int i, ndivs; - float hda, kappa; - - rx = fabsf(args[0]); // y radius - ry = fabsf(args[1]); // x radius - rotx = args[2] / 180.0f * NSVG_PI; // x rotation engle - fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc - fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction - x1 = *cpx; // start point - y1 = *cpy; - if (rel) { // end point - x2 = *cpx + args[5]; - y2 = *cpy + args[6]; - } else { - x2 = args[5]; - y2 = args[6]; - } - - dx = x1 - x2; - dy = y1 - y2; - d = sqrtf(dx*dx + dy*dy); - if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { - // The arc degenerates to a line - nsvg__lineTo(p, x2, y2); - *cpx = x2; - *cpy = y2; - return; - } - - sinrx = sinf(rotx); - cosrx = cosf(rotx); - - // Convert to center point parameterization. - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - // 1) Compute x1', y1' - x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; - y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; - d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); - if (d > 1) { - d = sqrtf(d); - rx *= d; - ry *= d; - } - // 2) Compute cx', cy' - s = 0.0f; - sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); - sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); - if (sa < 0.0f) sa = 0.0f; - if (sb > 0.0f) - s = sqrtf(sa / sb); - if (fa == fs) - s = -s; - cxp = s * rx * y1p / ry; - cyp = s * -ry * x1p / rx; - - // 3) Compute cx,cy from cx',cy' - cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; - cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; - - // 4) Calculate theta1, and delta theta. - ux = (x1p - cxp) / rx; - uy = (y1p - cyp) / ry; - vx = (-x1p - cxp) / rx; - vy = (-y1p - cyp) / ry; - a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle - da = nsvg__vecang(ux,uy, vx,vy); // Delta angle - -// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; -// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; - - if (fa) { - // Choose large arc - if (da > 0.0f) - da = da - 2*NSVG_PI; - else - da = 2*NSVG_PI + da; - } - - // Approximate the arc using cubic spline segments. - t[0] = cosrx; t[1] = sinrx; - t[2] = -sinrx; t[3] = cosrx; - t[4] = cx; t[5] = cy; - - // Split arc into max 90 degree segments. - // The loop assumes an iteration per end point (including start and end), this +1. - ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); - hda = (da / (float)ndivs) / 2.0f; - kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); - if (da < 0.0f) - kappa = -kappa; - - for (i = 0; i <= ndivs; i++) { - a = a1 + da * (i/(float)ndivs); - dx = cosf(a); - dy = sinf(a); - nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position - nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent - if (i > 0) - nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); - px = x; - py = y; - ptanx = tanx; - ptany = tany; - } - - *cpx = x2; - *cpy = y2; -} - -static void nsvg__parsePath(NSVGparser* p, const char** attr) -{ - const char* s = NULL; - char cmd = '\0'; - float args[10]; - int nargs; - int rargs = 0; - float cpx, cpy, cpx2, cpy2; - const char* tmp[4]; - char closedFlag; - int i; - char item[64]; - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "d") == 0) { - s = attr[i + 1]; - } else { - tmp[0] = attr[i]; - tmp[1] = attr[i + 1]; - tmp[2] = 0; - tmp[3] = 0; - nsvg__parseAttribs(p, tmp); - } - } - - if (s) { - nsvg__resetPath(p); - cpx = 0; cpy = 0; - cpx2 = 0; cpy2 = 0; - closedFlag = 0; - nargs = 0; - - while (*s) { - s = nsvg__getNextPathItem(s, item); - if (!*item) break; - if (nsvg__isnum(item[0])) { - if (nargs < 10) - args[nargs++] = (float)atof(item); - if (nargs >= rargs) { - switch (cmd) { - case 'm': - case 'M': - nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); - // Moveto can be followed by multiple coordinate pairs, - // which should be treated as linetos. - cmd = (cmd == 'm') ? 'l' : 'L'; - rargs = nsvg__getArgsPerElement(cmd); - cpx2 = cpx; cpy2 = cpy; - break; - case 'l': - case 'L': - nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'H': - case 'h': - nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'V': - case 'v': - nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'C': - case 'c': - nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); - break; - case 'S': - case 's': - nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); - break; - case 'Q': - case 'q': - nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); - break; - case 'T': - case 't': - nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); - break; - case 'A': - case 'a': - nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - default: - if (nargs >= 2) { - cpx = args[nargs-2]; - cpy = args[nargs-1]; - cpx2 = cpx; cpy2 = cpy; - } - break; - } - nargs = 0; - } - } else { - cmd = item[0]; - rargs = nsvg__getArgsPerElement(cmd); - if (cmd == 'M' || cmd == 'm') { - // Commit path. - if (p->npts > 0) - nsvg__addPath(p, closedFlag); - // Start new subpath. - nsvg__resetPath(p); - closedFlag = 0; - nargs = 0; - } else if (cmd == 'Z' || cmd == 'z') { - closedFlag = 1; - // Commit path. - if (p->npts > 0) { - // Move current point to first point - cpx = p->pts[0]; - cpy = p->pts[1]; - cpx2 = cpx; cpy2 = cpy; - nsvg__addPath(p, closedFlag); - } - // Start new subpath. - nsvg__resetPath(p); - nsvg__moveTo(p, cpx, cpy); - closedFlag = 0; - nargs = 0; - } - } - } - // Commit path. - if (p->npts) - nsvg__addPath(p, closedFlag); - } - - nsvg__addShape(p); -} - -static void nsvg__parseRect(NSVGparser* p, const char** attr) -{ - float x = 0.0f; - float y = 0.0f; - float w = 0.0f; - float h = 0.0f; - float rx = -1.0f; // marks not set - float ry = -1.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); - if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx < 0.0f && ry > 0.0f) rx = ry; - if (ry < 0.0f && rx > 0.0f) ry = rx; - if (rx < 0.0f) rx = 0.0f; - if (ry < 0.0f) ry = 0.0f; - if (rx > w/2.0f) rx = w/2.0f; - if (ry > h/2.0f) ry = h/2.0f; - - if (w != 0.0f && h != 0.0f) { - nsvg__resetPath(p); - - if (rx < 0.00001f || ry < 0.0001f) { - nsvg__moveTo(p, x, y); - nsvg__lineTo(p, x+w, y); - nsvg__lineTo(p, x+w, y+h); - nsvg__lineTo(p, x, y+h); - } else { - // Rounded rectangle - nsvg__moveTo(p, x+rx, y); - nsvg__lineTo(p, x+w-rx, y); - nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); - nsvg__lineTo(p, x+w, y+h-ry); - nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); - nsvg__lineTo(p, x+rx, y+h); - nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); - nsvg__lineTo(p, x, y+ry); - nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); - } - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseCircle(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float r = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); - } - } - - if (r > 0.0f) { - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+r, cy); - nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); - nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); - nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); - nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseEllipse(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float rx = 0.0f; - float ry = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx > 0.0f && ry > 0.0f) { - - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+rx, cy); - nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); - nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); - nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); - nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseLine(NSVGparser* p, const char** attr) -{ - float x1 = 0.0; - float y1 = 0.0; - float x2 = 0.0; - float y2 = 0.0; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - } - } - - nsvg__resetPath(p); - - nsvg__moveTo(p, x1, y1); - nsvg__lineTo(p, x2, y2); - - nsvg__addPath(p, 0); - - nsvg__addShape(p); -} - -static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) -{ - int i; - const char* s; - float args[2]; - int nargs, npts = 0; - char item[64]; - - nsvg__resetPath(p); - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "points") == 0) { - s = attr[i + 1]; - nargs = 0; - while (*s) { - s = nsvg__getNextPathItem(s, item); - args[nargs++] = (float)atof(item); - if (nargs >= 2) { - if (npts == 0) - nsvg__moveTo(p, args[0], args[1]); - else - nsvg__lineTo(p, args[0], args[1]); - nargs = 0; - npts++; - } - } - } - } - } - - nsvg__addPath(p, (char)closeFlag); - - nsvg__addShape(p); -} - -static void nsvg__parseSVG(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "width") == 0) { - p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 1.0f); - } else if (strcmp(attr[i], "height") == 0) { - p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 1.0f); - } else if (strcmp(attr[i], "viewBox") == 0) { - sscanf(attr[i + 1], "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", &p->viewMinx, &p->viewMiny, &p->viewWidth, &p->viewHeight); - } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { - if (strstr(attr[i + 1], "none") != 0) { - // No uniform scaling - p->alignType = NSVG_ALIGN_NONE; - } else { - // Parse X align - if (strstr(attr[i + 1], "xMin") != 0) - p->alignX = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "xMid") != 0) - p->alignX = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "xMax") != 0) - p->alignX = NSVG_ALIGN_MAX; - // Parse X align - if (strstr(attr[i + 1], "yMin") != 0) - p->alignY = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "yMid") != 0) - p->alignY = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "yMax") != 0) - p->alignY = NSVG_ALIGN_MAX; - // Parse meet/slice - p->alignType = NSVG_ALIGN_MEET; - if (strstr(attr[i + 1], "slice") != 0) - p->alignType = NSVG_ALIGN_SLICE; - } - } - } - } -} - -static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) -{ - int i; - NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); - if (grad == NULL) return; - memset(grad, 0, sizeof(NSVGgradientData)); - grad->units = NSVG_OBJECT_SPACE; - grad->type = type; - if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { - grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); - grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { - grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - } - - nsvg__xformIdentity(grad->xform); - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "id") == 0) { - strncpy(grad->id, attr[i+1], 63); - grad->id[63] = '\0'; - } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "gradientUnits") == 0) { - if (strcmp(attr[i+1], "objectBoundingBox") == 0) - grad->units = NSVG_OBJECT_SPACE; - else - grad->units = NSVG_USER_SPACE; - } else if (strcmp(attr[i], "gradientTransform") == 0) { - nsvg__parseTransform(grad->xform, attr[i + 1]); - } else if (strcmp(attr[i], "cx") == 0) { - grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "cy") == 0) { - grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "r") == 0) { - grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fx") == 0) { - grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fy") == 0) { - grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x1") == 0) { - grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y1") == 0) { - grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x2") == 0) { - grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y2") == 0) { - grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "spreadMethod") == 0) { - if (strcmp(attr[i+1], "pad") == 0) - grad->spread = NSVG_SPREAD_PAD; - else if (strcmp(attr[i+1], "reflect") == 0) - grad->spread = NSVG_SPREAD_REFLECT; - else if (strcmp(attr[i+1], "repeat") == 0) - grad->spread = NSVG_SPREAD_REPEAT; - } else if (strcmp(attr[i], "xlink:href") == 0) { - const char *href = attr[i+1]; - strncpy(grad->ref, href+1, 62); - grad->ref[62] = '\0'; - } - } - } - - grad->next = p->gradients; - p->gradients = grad; -} - -static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) -{ - NSVGattrib* curAttr = nsvg__getAttr(p); - NSVGgradientData* grad; - NSVGgradientStop* stop; - int i, idx; - - curAttr->stopOffset = 0; - curAttr->stopColor = 0; - curAttr->stopOpacity = 1.0f; - - for (i = 0; attr[i]; i += 2) { - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } - - // Add stop to the last gradient. - grad = p->gradients; - if (grad == NULL) return; - - grad->nstops++; - grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); - if (grad->stops == NULL) return; - - // Insert - idx = grad->nstops-1; - for (i = 0; i < grad->nstops-1; i++) { - if (curAttr->stopOffset < grad->stops[i].offset) { - idx = i; - break; - } - } - if (idx != grad->nstops-1) { - for (i = grad->nstops-1; i > idx; i--) - grad->stops[i] = grad->stops[i-1]; - } - - stop = &grad->stops[idx]; - stop->color = curAttr->stopColor; - stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; - stop->offset = curAttr->stopOffset; -} - -static void nsvg__startElement(void* ud, const char* el, const char** attr) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (p->defsFlag) { - // Skip everything but gradients in defs - if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } - return; - } - - if (strcmp(el, "g") == 0) { - nsvg__pushAttr(p); - nsvg__parseAttribs(p, attr); - } else if (strcmp(el, "path") == 0) { - if (p->pathFlag) // Do not allow nested paths. - return; - nsvg__pushAttr(p); - nsvg__parsePath(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "rect") == 0) { - nsvg__pushAttr(p); - nsvg__parseRect(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "circle") == 0) { - nsvg__pushAttr(p); - nsvg__parseCircle(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "ellipse") == 0) { - nsvg__pushAttr(p); - nsvg__parseEllipse(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "line") == 0) { - nsvg__pushAttr(p); - nsvg__parseLine(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "polyline") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 0); - nsvg__popAttr(p); - } else if (strcmp(el, "polygon") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 1); - nsvg__popAttr(p); - } else if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 1; - } else if (strcmp(el, "svg") == 0) { - nsvg__parseSVG(p, attr); - } -} - -static void nsvg__endElement(void* ud, const char* el) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (strcmp(el, "g") == 0) { - nsvg__popAttr(p); - } else if (strcmp(el, "path") == 0) { - p->pathFlag = 0; - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 0; - } -} - -static void nsvg__content(void* ud, const char* s) -{ - NSVG_NOTUSED(ud); - NSVG_NOTUSED(s); - // empty -} - -static void nsvg__imageBounds(NSVGparser* p, float* bounds) -{ - NSVGshape* shape; - shape = p->image->shapes; - if (shape == NULL) { - bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; - return; - } - bounds[0] = shape->bounds[0]; - bounds[1] = shape->bounds[1]; - bounds[2] = shape->bounds[2]; - bounds[3] = shape->bounds[3]; - for (shape = shape->next; shape != NULL; shape = shape->next) { - bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); - bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); - bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); - bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); - } -} - -static float nsvg__viewAlign(float content, float container, int type) -{ - if (type == NSVG_ALIGN_MIN) - return 0; - else if (type == NSVG_ALIGN_MAX) - return container - content; - // mid - return (container - content) * 0.5f; -} - -static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) -{ - grad->xform[0] *= sx; - grad->xform[1] *= sx; - grad->xform[2] *= sy; - grad->xform[3] *= sy; - grad->xform[4] += tx*sx; - grad->xform[5] += ty*sx; -} - -static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) -{ - NSVGshape* shape; - NSVGpath* path; - float tx, ty, sx, sy, us, bounds[4], t[6], avgs; - int i; - float* pt; - - // Guess image size if not set completely. - nsvg__imageBounds(p, bounds); - - if (p->viewWidth == 0) { - if (p->image->width > 0) { - p->viewWidth = p->image->width; - } else { - p->viewMinx = bounds[0]; - p->viewWidth = bounds[2] - bounds[0]; - } - } - if (p->viewHeight == 0) { - if (p->image->height > 0) { - p->viewHeight = p->image->height; - } else { - p->viewMiny = bounds[1]; - p->viewHeight = bounds[3] - bounds[1]; - } - } - if (p->image->width == 0) - p->image->width = p->viewWidth; - if (p->image->height == 0) - p->image->height = p->viewHeight; - - tx = -p->viewMinx; - ty = -p->viewMiny; - sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; - sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; - // Unit scaling - us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); - - // Fix aspect ratio - if (p->alignType == NSVG_ALIGN_MEET) { - // fit whole image into viewbox - sx = sy = nsvg__minf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } else if (p->alignType == NSVG_ALIGN_SLICE) { - // fill whole viewbox with image - sx = sy = nsvg__maxf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } - - // Transform - sx *= us; - sy *= us; - avgs = (sx+sy) / 2.0f; - for (shape = p->image->shapes; shape != NULL; shape = shape->next) { - shape->bounds[0] = (shape->bounds[0] + tx) * sx; - shape->bounds[1] = (shape->bounds[1] + ty) * sy; - shape->bounds[2] = (shape->bounds[2] + tx) * sx; - shape->bounds[3] = (shape->bounds[3] + ty) * sy; - for (path = shape->paths; path != NULL; path = path->next) { - path->bounds[0] = (path->bounds[0] + tx) * sx; - path->bounds[1] = (path->bounds[1] + ty) * sy; - path->bounds[2] = (path->bounds[2] + tx) * sx; - path->bounds[3] = (path->bounds[3] + ty) * sy; - for (i =0; i < path->npts; i++) { - pt = &path->pts[i*2]; - pt[0] = (pt[0] + tx) * sx; - pt[1] = (pt[1] + ty) * sy; - } - } - - if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); - memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->fill.gradient->xform, t); - } - if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); - memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->stroke.gradient->xform, t); - } - - shape->strokeWidth *= avgs; - shape->strokeDashOffset *= avgs; - for (i = 0; i < shape->strokeDashCount; i++) - shape->strokeDashArray[i] *= avgs; - } -} - -NSVGimage* nsvgParse(char* input, const char* units, float dpi) -{ - NSVGparser* p; - NSVGimage* ret = 0; - - p = nsvg__createParser(); - if (p == NULL) { - return NULL; - } - p->dpi = dpi; - - nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); - - // Scale to viewBox - nsvg__scaleToViewbox(p, units); - - ret = p->image; - p->image = NULL; - - nsvg__deleteParser(p); - - return ret; -} - -NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) -{ - FILE* fp = NULL; - size_t size; - char* data = NULL; - NSVGimage* image = NULL; - - fp = fopen(filename, "rb"); - if (!fp) goto error; - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - data = (char*)malloc(size+1); - if (data == NULL) goto error; - if (fread(data, 1, size, fp) != size) goto error; - data[size] = '\0'; // Must be null terminated. - fclose(fp); - image = nsvgParse(data, units, dpi); - free(data); - - return image; - -error: - if (fp) fclose(fp); - if (data) free(data); - if (image) nsvgDelete(image); - return NULL; -} - -void nsvgDelete(NSVGimage* image) -{ - NSVGshape *snext, *shape; - if (image == NULL) return; - shape = image->shapes; - while (shape != NULL) { - snext = shape->next; - nsvg__deletePaths(shape->paths); - nsvg__deletePaint(&shape->fill); - nsvg__deletePaint(&shape->stroke); - free(shape); - shape = snext; - } - free(image); -} - -#endif