/*
 * 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 <cmath>
#include <cstdlib>
#include <cstring>

#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_MID      0 // preserveAspectRatio default
#define NSVG_ALIGN_MIN      1
#define NSVG_ALIGN_MAX      2
#define NSVG_ALIGN_MEET     0 // preserveAspectRatio default
#define NSVG_ALIGN_NONE     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 NSVGstyles
{
	char*	name;
	char*   description;
	struct NSVGstyles* next;
} NSVGstyles;

typedef struct NSVGparser
{
    NSVGattrib attr[NSVG_MAX_ATTR];
    int attrHead;
    float* pts;
    int npts;
    int cpts;
    NSVGpath* plist;
    NSVGimage* image;
    NSVGstyles* styles;
    NSVGgradientData* gradients;
    NSVGshape* shapesTail;
    float   viewMinx, viewMiny, viewWidth, viewHeight;
    int     alignX, alignY, alignType;
    float   dpi;
    char    pathFlag;
    char    defsFlag;
    char    styleFlag;
} 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      = 0;
    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__deleteStyles( NSVGstyles* style )
{
    while( style )
    {
        NSVGstyles* next = style->next;
        if( style->name != NULL )
            free( style->name );
        if( style->description != NULL )
            free( style->description );
        free( style );
        style = next;
    }
}


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__deleteStyles( p->styles );
        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 if( strcmp( name, "class" ) == 0 )
    {
        NSVGstyles* style = p->styles;
        while( style )
        {
            if( strcmp( style->name + 1, value ) == 0 )
            {
                nsvg__parseStyle( p, style->description );
            }
            style = style->next;
        }
    }
    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], "class" ) == 0 )
        {
            tmp[0] = attr[i];
            tmp[1] = attr[i + 1];
            tmp[2] = 0;
            tmp[3] = 0;
            nsvg__parseAttribs( p, tmp );
        }
    }

    for( i = 0; attr[i]; i += 2 )
    {
        if( strcmp( attr[i], "d" ) == 0 )
        {
            s = attr[i + 1];
        }
        else if( strcmp( attr[i], "class" ) != 0 )
        {
            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 and styles 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 );
        }
        else if( strcmp( el, "style" ) == 0 )
        {
            p->styleFlag = 1;
        }

        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 );
    }
    else if( strcmp( el, "style" ) == 0 )
    {
        p->styleFlag = 1;
    }
}


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;
    }
    else if( strcmp( el, "style" ) == 0 )
    {
        p->styleFlag = 0;
    }
}


static char* nsvg__strndup( const char* s, size_t n )
{
    char*  result;
    size_t len = strlen( s );

    if( n < len )
        len = n;

    result = (char*) malloc( len + 1 );
    if( !result )
        return 0;

    result[len] = '\0';
    return (char*) memcpy( result, s, len );
}


static void nsvg__content( void* ud, const char* s )
{
    NSVGparser* p = (NSVGparser*) ud;
    if( p->styleFlag )
    {
        int         state = 0;
        int         class_count = 0;
        const char* start = s;
        while( *s )
        {
            char c = *s;
            if( state == 2 )
            {
                if( c == '{' )
                {
                    start = s + 1;
                }
                else if( c == '}' )
                {
                    NSVGstyles* style = p->styles;
                    while( class_count > 0 )
                    {
                        style->description = nsvg__strndup( start, (size_t) ( s - start ) );
                        style = style->next;
                        --class_count;
                    }
                    state = 0;
                }
            }
            else if( nsvg__isspace( c ) || c == '{' || c == ',' )
            {
                if( state == 1 )
                {
                    if( *start == '.' )
                    {
                        NSVGstyles* next = p->styles;
                        p->styles = (NSVGstyles*) malloc( sizeof( NSVGstyles ) );
                        p->styles->description = NULL;
                        p->styles->next = next;
                        p->styles->name = nsvg__strndup( start, (size_t) ( s - start ) );
                        ++class_count;
                    }
                    start = s + 1;
                    state = c == ',' ? 0 : 2;
                }
            }
            else if( state == 0 )
            {
                start = s;
                state = 1;
            }
            s++;
        }
    }
}


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;
    bool    updw = false;
    bool    updh = false;

    // 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;
        updw = true;
    }

    if( p->image->height == 0 )
    {
        p->image->height = p->viewHeight;
        updh = true;
    }

    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 );

        if( updw )
            p->image->width = p->viewWidth * sx;

        if( updh )
            p->image->height = p->viewHeight * 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 );

        if( updw )
            p->image->width = p->viewWidth * sx;

        if( updh )
            p->image->height = p->viewHeight * 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( FILE *fp, const char* units, float dpi )
{
    long    size;
    char*   data = NULL;
    NSVGimage* image = NULL;

    if( !fp )
        return NULL;

    fseek( fp, 0, SEEK_END );
    size = ftell( fp );

    if( size < 0 )
        goto error;

    fseek( fp, 0, SEEK_SET );
    data = (char*) malloc( size + 1 );

    if( data == NULL )
        goto error;

    // This test works only if fp is open in binary mode, i.e. if the CRLF eol is not
    // replaced by LF when reading the file
    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 );
}
