diff options
Diffstat (limited to 'generic')
50 files changed, 6219 insertions, 178 deletions
diff --git a/generic/nanosvg.h b/generic/nanosvg.h new file mode 100755 index 0000000..ade6ec7 --- /dev/null +++ b/generic/nanosvg.h @@ -0,0 +1,3084 @@ +/* + * 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 + * + */ + +#ifndef NANOSVG_H +#define NANOSVG_H + +#ifdef __cplusplus +extern "C" { +#endif + +// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. +// +// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. +// +// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! +// +// The shapes in the SVG images are transformed by the viewBox and converted to specified units. +// That is, you should get the same looking data as your designed in your favorite app. +// +// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose +// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. +// +// The units passed to NanoVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. +// DPI (dots-per-inch) controls how the unit conversion is done. +// +// If you don't know or care about the units stuff, "px" and 96 should get you going. + + +/* Example Usage: + // Load + NSVGImage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + printf("size: %f x %f\n", image->width, image->height); + // Use... + for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + for (int i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); + } + } + } + // Delete + nsvgDelete(image); +*/ + +#ifndef NANOSVG_SCOPE +#define NANOSVG_SCOPE +#endif + +#ifndef NANOSVG_malloc +#define NANOSVG_malloc malloc +#endif + +#ifndef NANOSVG_realloc +#define NANOSVG_realloc realloc +#endif + +#ifndef NANOSVG_free +#define NANOSVG_free free +#endif + +// float emulation for MS VC6++ compiler +#if (_MSC_VER == 1200) +#define tanf(a) (float)tan(a) +#define cosf(a) (float)cos(a) +#define sinf(a) (float)sin(a) +#define sqrtf(a) (float)sqrt(a) +#define fabsf(a) (float)abs(a) +#define acosf(a) (float)acos(a) +#define atan2f(a,b) (float)atan2(a,b) +#define ceilf(a) (float)ceil(a) +#define fmodf(a,b) (float)fmod(a,b) +#define floorf(a) (float)floor(a) +#endif + +enum NSVGpaintType { + NSVG_PAINT_NONE = 0, + NSVG_PAINT_COLOR = 1, + NSVG_PAINT_LINEAR_GRADIENT = 2, + NSVG_PAINT_RADIAL_GRADIENT = 3 +}; + +enum NSVGspreadType { + NSVG_SPREAD_PAD = 0, + NSVG_SPREAD_REFLECT = 1, + NSVG_SPREAD_REPEAT = 2 +}; + +enum NSVGlineJoin { + NSVG_JOIN_MITER = 0, + NSVG_JOIN_ROUND = 1, + NSVG_JOIN_BEVEL = 2 +}; + +enum NSVGlineCap { + NSVG_CAP_BUTT = 0, + NSVG_CAP_ROUND = 1, + NSVG_CAP_SQUARE = 2 +}; + +enum NSVGfillRule { + NSVG_FILLRULE_NONZERO = 0, + NSVG_FILLRULE_EVENODD = 1 +}; + +enum NSVGflags { + NSVG_FLAGS_VISIBLE = 0x01 +}; + +typedef struct NSVGgradientStop { + unsigned int color; + float offset; +} NSVGgradientStop; + +typedef struct NSVGgradient { + float xform[6]; + char spread; + float fx, fy; + int nstops; + NSVGgradientStop stops[1]; +} NSVGgradient; + +typedef struct NSVGpaint { + char type; + union { + unsigned int color; + NSVGgradient* gradient; + }; +} NSVGpaint; + +typedef struct NSVGpath +{ + float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + int npts; // Total number of bezier points. + char closed; // Flag indicating if shapes should be treated as closed. + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + struct NSVGpath* next; // Pointer to next path, or NULL if last element. +} NSVGpath; + +typedef struct NSVGshape +{ + char id[64]; // Optional 'id' attr of the shape or its group + NSVGpaint fill; // Fill paint + NSVGpaint stroke; // Stroke paint + float opacity; // Opacity of the shape. + float strokeWidth; // Stroke width (scaled). + float strokeDashOffset; // Stroke dash offset (scaled). + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. + char strokeLineJoin; // Stroke join type. + char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit + char fillRule; // Fill rule, see NSVGfillRule. + unsigned char flags; // Logical or of NSVG_FLAGS_* flags + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + NSVGpath* paths; // Linked list of paths in the image. + struct NSVGshape* next; // Pointer to next shape, or NULL if last element. +} NSVGshape; + +typedef struct NSVGimage +{ + float width; // Width of the image. + float height; // Height of the image. + NSVGshape* shapes; // Linked list of shapes in the image. +} NSVGimage; + +// Parses SVG file from a file, returns SVG image as paths. +NANOSVG_SCOPE NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); + +// Parses SVG file from a null terminated string, returns SVG image as paths. +// Important note: changes the string. +NANOSVG_SCOPE NSVGimage* nsvgParse(char* input, const char* units, float dpi); + +// Deletes list of paths. +NANOSVG_SCOPE void nsvgDelete(NSVGimage* image); + +#ifdef __cplusplus +} +#endif + +#endif // NANOSVG_H + +#ifdef NANOSVG_IMPLEMENTATION + +#include <string.h> +#include <stdlib.h> +#include <math.h> + +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NSVG_ALIGN_MIN 0 +#define NSVG_ALIGN_MID 1 +#define NSVG_ALIGN_MAX 2 +#define NSVG_ALIGN_NONE 0 +#define NSVG_ALIGN_MEET 1 +#define NSVG_ALIGN_SLICE 2 + +#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) +#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) + +#ifdef _MSC_VER + #pragma warning (disable: 4996) // Switch off security warnings + #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings + #ifdef __cplusplus + #define NSVG_INLINE inline + #else + #define NSVG_INLINE + #endif + #if !defined(strtoll) // old MSVC versions do not have strtoll() + #define strtoll _strtoi64 + #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* name; + int start = 0; + int end = 0; + char quote; + + // Skip white space after the '<' + while (*s && nsvg__isspace(*s)) s++; + + // Check if the tag is end tag + if (*s == '/') { + s++; + end = 1; + } else { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if (!*s || *s == '?' || *s == '!') + return; + + // Get tag name + name = s; + while (*s && !nsvg__isspace(*s)) s++; + if (*s) { *s++ = '\0'; } + + // Get attribs + while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { + 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, name, attr); + if (end && endelCb) + (*endelCb)(ud, name); +} + +NANOSVG_SCOPE +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__parseContent(mark, contentCb, ud); + 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 +}; + +enum NSVGvisible { + NSVG_VIS_DISPLAY = 1, + NSVG_VIS_VISIBLE = 2 +}; + +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*)NANOSVG_malloc(sizeof(NSVGparser)); + if (p == NULL) goto error; + memset(p, 0, sizeof(NSVGparser)); + + p->image = (NSVGimage*)NANOSVG_malloc(sizeof(NSVGimage)); + if (p->image == NULL) goto error; + memset(p->image, 0, sizeof(NSVGimage)); + + // Init style + nsvg__xformIdentity(p->attr[0].xform); + memset(p->attr[0].id, 0, sizeof p->attr[0].id); + p->attr[0].fillColor = NSVG_RGB(0,0,0); + p->attr[0].strokeColor = NSVG_RGB(0,0,0); + p->attr[0].opacity = 1; + p->attr[0].fillOpacity = 1; + p->attr[0].strokeOpacity = 1; + p->attr[0].stopOpacity = 1; + p->attr[0].strokeWidth = 1; + p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; + p->attr[0].strokeLineCap = NSVG_CAP_BUTT; + p->attr[0].miterLimit = 4; + p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; + p->attr[0].hasFill = 1; + p->attr[0].visible = NSVG_VIS_DISPLAY | NSVG_VIS_VISIBLE; + + return p; + +error: + if (p) { + if (p->image) NANOSVG_free(p->image); + NANOSVG_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) + NANOSVG_free(path->pts); + NANOSVG_free(path); + path = next; + } +} + +static void nsvg__deletePaint(NSVGpaint* paint) +{ + if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) + NANOSVG_free(paint->gradient); +} + +static void nsvg__deleteGradientData(NSVGgradientData* grad) +{ + NSVGgradientData* next; + while (grad != NULL) { + next = grad->next; + NANOSVG_free(grad->stops); + NANOSVG_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); + NANOSVG_free(p->pts); + NANOSVG_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*)NANOSVG_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*)NANOSVG_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*)NANOSVG_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_VIS_DISPLAY) && (attr->visible & NSVG_VIS_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) NANOSVG_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*)NANOSVG_malloc(sizeof(NSVGpath)); + if (path == NULL) goto error; + memset(path, 0, sizeof(NSVGpath)); + + path->pts = (float*)NANOSVG_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) NANOSVG_free(path->pts); + NANOSVG_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; +#if (_MSC_VER == 1200) + __int64 intPart = 0, fracPart = 0; +#else + long long intPart = 0, fracPart = 0; +#endif + 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 +#if (_MSC_VER == 1200) + intPart = strtol(cur, &end, 10); +#else + intPart = strtoll(cur, &end, 10); +#endif + if (cur != end) { + res = (double)intPart; + hasIntPart = 1; + cur = end; + } + } + + // Parse fractional part. + if (*cur == '.') { + cur++; // Skip '.' + if (nsvg__isdigit(*cur)) { + // Parse digit sequence +#if (_MSC_VER == 1200) + fracPart = strtol(cur, &end, 10); +#else + fracPart = strtoll(cur, &end, 10); +#endif + 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%s", &coord.value, units); + coord.units = nsvg__parseUnits(units); + return coord; +} + +static NSVGcoordinate nsvg__coord(float v, int units) +{ + NSVGcoordinate coord = {v, units}; + return coord; +} + +static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) +{ + NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); + return nsvg__convertToPixels(p, coord, orig, length); +} + +static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) +{ + const char* end; + const char* ptr; + char it[64]; + + *na = 0; + ptr = str; + while (*ptr && *ptr != '(') ++ptr; + if (*ptr == 0) + return 1; + end = ptr; + while (*end && *end != ')') ++end; + if (*end == 0) + return 1; + + while (ptr < end) { + if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { + if (*na >= maxNa) return 0; + ptr = nsvg__parseNumber(ptr, it, 64); + args[(*na)++] = (float)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 &= ~NSVG_VIS_DISPLAY; + // Don't reset ->visible on display:inline, one display:none hides the whole subtree + + } else if (strcmp(name, "visibility") == 0) { + if (strcmp(value, "hidden") == 0) { + attr->visible &= ~NSVG_VIS_VISIBLE; + } else if (strcmp(value, "visible") == 0) { + attr->visible |= NSVG_VIS_VISIBLE; + } + } 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) { + break; + } + style = style->next; + } + if (style) { + nsvg__parseStyle(p, style->description); + } + } else { + return 0; + } + return 1; +} + +static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) +{ + const char* str; + const char* val; + char name[512]; + char value[512]; + int n; + + str = start; + while (str < end && *str != ':') ++str; + + val = str; + + // Right Trim + while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; + ++str; + + n = (int)(str - start); + if (n > 511) n = 511; + if (n) memcpy(name, start, n); + name[n] = 0; + + while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; + + n = (int)(end - val); + if (n > 511) n = 511; + if (n) memcpy(value, val, n); + value[n] = 0; + + return nsvg__parseAttr(p, name, value); +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str) +{ + const char* start; + const char* end; + + while (*str) { + // Left Trim + while(*str && nsvg__isspace(*str)) ++str; + start = str; + while(*str && *str != ';') ++str; + end = str; + + // Right Trim + while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; + ++end; + + nsvg__parseNameValue(p, start, end); + if (*str) ++str; + } +} + +static void nsvg__parseAttribs(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) + { + if (strcmp(attr[i], "style") == 0) + nsvg__parseStyle(p, attr[i + 1]); + else + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } +} + +static int nsvg__getArgsPerElement(char cmd) +{ + switch (cmd) { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + case 'c': + case 'C': + return 6; + case 'a': + case 'A': + return 7; + } + return 0; +} + +static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__moveTo(p, *cpx, *cpy); +} + +static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpx += args[0]; + else + *cpx = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpy += args[0]; + else + *cpy = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if (rel) { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } else { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2*x1 - *cpx2; + cy1 = 2*y1 - *cpy2; + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } else { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2*x1 - *cpx2; + cy = 2*y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static float nsvg__sqr(float x) { return x*x; } +static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } + +static float nsvg__vecrat(float ux, float uy, float vx, float vy) +{ + return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); +} + +static float nsvg__vecang(float ux, float uy, float vx, float vy) +{ + float r = nsvg__vecrat(ux,uy, vx,vy); + if (r < -1.0f) r = -1.0f; + if (r > 1.0f) r = 1.0f; + return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); +} + +static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf(args[0]); // y radius + ry = fabsf(args[1]); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle + fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + if (rel) { // end point + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } else { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf(dx*dx + dy*dy); + if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { + // The arc degenerates to a line + nsvg__lineTo(p, x2, y2); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf(rotx); + cosrx = cosf(rotx); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); + if (d > 1) { + d = sqrtf(d); + rx *= d; + ry *= d; + } + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); + sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); + if (sa < 0.0f) sa = 0.0f; + if (sb > 0.0f) + s = sqrtf(sa / sb); + if (fa == fs) + s = -s; + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; + cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle + da = nsvg__vecang(ux,uy, vx,vy); // Delta angle + +// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; +// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if (fs == 0 && da > 0) + da -= 2 * NSVG_PI; + else if (fs == 1 && da < 0) + da += 2 * NSVG_PI; + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; t[1] = sinrx; + t[2] = -sinrx; t[3] = cosrx; + t[4] = cx; t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this +1. + ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); + hda = (da / (float)ndivs) / 2.0f; + kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + if (da < 0.0f) + kappa = -kappa; + + for (i = 0; i <= ndivs; i++) { + a = a1 + da * ((float)i/(float)ndivs); + dx = cosf(a); + dy = sinf(a); + nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position + nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent + if (i > 0) + nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + +static void nsvg__parsePath(NSVGparser* p, const char** attr) +{ + const char* s = NULL; + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + float cpx, cpy, cpx2, cpy2; + const char* tmp[4]; + char closedFlag; + int i; + char item[64]; + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "d") == 0) { + s = attr[i + 1]; + } else { + tmp[0] = attr[i]; + tmp[1] = attr[i + 1]; + tmp[2] = 0; + tmp[3] = 0; + nsvg__parseAttribs(p, tmp); + } + } + + if (s) { + nsvg__resetPath(p); + cpx = 0; cpy = 0; + cpx2 = 0; cpy2 = 0; + closedFlag = 0; + nargs = 0; + + while (*s) { + s = nsvg__getNextPathItem(s, item); + if (!*item) break; + if (nsvg__isnum(item[0])) { + if (nargs < 10) + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= rargs) { + switch (cmd) { + case 'm': + case 'M': + nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement(cmd); + cpx2 = cpx; cpy2 = cpy; + break; + case 'l': + case 'L': + nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'H': + case 'h': + nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'V': + case 'v': + nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'C': + case 'c': + nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); + break; + case 'S': + case 's': + nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); + break; + case 'Q': + case 'q': + nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); + break; + case 'T': + case 't': + nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); + break; + case 'A': + case 'a': + nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + default: + if (nargs >= 2) { + cpx = args[nargs-2]; + cpy = args[nargs-1]; + cpx2 = cpx; cpy2 = cpy; + } + break; + } + nargs = 0; + } + } else { + cmd = item[0]; + rargs = nsvg__getArgsPerElement(cmd); + if (cmd == 'M' || cmd == 'm') { + // Commit path. + if (p->npts > 0) + nsvg__addPath(p, closedFlag); + // Start new subpath. + nsvg__resetPath(p); + closedFlag = 0; + nargs = 0; + } else if (cmd == 'Z' || cmd == 'z') { + closedFlag = 1; + // Commit path. + if (p->npts > 0) { + // Move current point to first point + cpx = p->pts[0]; + cpy = p->pts[1]; + cpx2 = cpx; cpy2 = cpy; + nsvg__addPath(p, closedFlag); + } + // Start new subpath. + nsvg__resetPath(p); + nsvg__moveTo(p, cpx, cpy); + closedFlag = 0; + nargs = 0; + } + } + } + // Commit path. + if (p->npts) + nsvg__addPath(p, closedFlag); + } + + nsvg__addShape(p); +} + +static void nsvg__parseRect(NSVGparser* p, const char** attr) +{ + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + float rx = -1.0f; // marks not set + float ry = -1.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); + if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx < 0.0f && ry > 0.0f) rx = ry; + if (ry < 0.0f && rx > 0.0f) ry = rx; + if (rx < 0.0f) rx = 0.0f; + if (ry < 0.0f) ry = 0.0f; + if (rx > w/2.0f) rx = w/2.0f; + if (ry > h/2.0f) ry = h/2.0f; + + if (w != 0.0f && h != 0.0f) { + nsvg__resetPath(p); + + if (rx < 0.00001f || ry < 0.0001f) { + nsvg__moveTo(p, x, y); + nsvg__lineTo(p, x+w, y); + nsvg__lineTo(p, x+w, y+h); + nsvg__lineTo(p, x, y+h); + } else { + // Rounded rectangle + nsvg__moveTo(p, x+rx, y); + nsvg__lineTo(p, x+w-rx, y); + nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); + nsvg__lineTo(p, x+w, y+h-ry); + nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); + nsvg__lineTo(p, x+rx, y+h); + nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); + nsvg__lineTo(p, x, y+ry); + nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); + } + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseCircle(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float r = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); + } + } + + if (r > 0.0f) { + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+r, cy); + nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); + nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); + nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); + nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseEllipse(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float rx = 0.0f; + float ry = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx > 0.0f && ry > 0.0f) { + + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+rx, cy); + nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); + nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); + nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); + nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseLine(NSVGparser* p, const char** attr) +{ + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + } + } + + nsvg__resetPath(p); + + nsvg__moveTo(p, x1, y1); + nsvg__lineTo(p, x2, y2); + + nsvg__addPath(p, 0); + + nsvg__addShape(p); +} + +static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) +{ + int i; + const char* s; + float args[2]; + int nargs, npts = 0; + char item[64]; + + nsvg__resetPath(p); + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "points") == 0) { + s = attr[i + 1]; + nargs = 0; + while (*s) { + s = nsvg__getNextPathItem(s, item); + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= 2) { + if (npts == 0) + nsvg__moveTo(p, args[0], args[1]); + else + nsvg__lineTo(p, args[0], args[1]); + nargs = 0; + npts++; + } + } + } + } + } + + nsvg__addPath(p, (char)closeFlag); + + nsvg__addShape(p); +} + +static void nsvg__parseSVG(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "width") == 0) { + p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "height") == 0) { + p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "viewBox") == 0) { + sscanf(attr[i + 1], "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", &p->viewMinx, &p->viewMiny, &p->viewWidth, &p->viewHeight); + } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { + if (strstr(attr[i + 1], "none") != 0) { + // No uniform scaling + p->alignType = NSVG_ALIGN_NONE; + } else { + // Parse X align + if (strstr(attr[i + 1], "xMin") != 0) + p->alignX = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "xMid") != 0) + p->alignX = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "xMax") != 0) + p->alignX = NSVG_ALIGN_MAX; + // Parse X align + if (strstr(attr[i + 1], "yMin") != 0) + p->alignY = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "yMid") != 0) + p->alignY = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "yMax") != 0) + p->alignY = NSVG_ALIGN_MAX; + // Parse meet/slice + p->alignType = NSVG_ALIGN_MEET; + if (strstr(attr[i + 1], "slice") != 0) + p->alignType = NSVG_ALIGN_SLICE; + } + } + } + } +} + +static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) +{ + int i; + NSVGgradientData* grad = (NSVGgradientData*)NANOSVG_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*)NANOSVG_realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); + if (grad->stops == NULL) return; + + // Insert + idx = grad->nstops-1; + for (i = 0; i < grad->nstops-1; i++) { + if (curAttr->stopOffset < grad->stops[i].offset) { + idx = i; + break; + } + } + if (idx != grad->nstops-1) { + for (i = grad->nstops-1; i > idx; i--) + grad->stops[i] = grad->stops[i-1]; + } + + stop = &grad->stops[idx]; + stop->color = curAttr->stopColor; + stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; + stop->offset = curAttr->stopOffset; +} + +static void nsvg__startElement(void* ud, const char* el, const char** attr) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (p->defsFlag) { + // Skip everything but gradients in defs + if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } + return; + } + + if (strcmp(el, "g") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + } else if (strcmp(el, "path") == 0) { + if (p->pathFlag) // Do not allow nested paths. + return; + nsvg__pushAttr(p); + nsvg__parsePath(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "rect") == 0) { + nsvg__pushAttr(p); + nsvg__parseRect(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "circle") == 0) { + nsvg__pushAttr(p); + nsvg__parseCircle(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "ellipse") == 0) { + nsvg__pushAttr(p); + nsvg__parseEllipse(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "line") == 0) { + nsvg__pushAttr(p); + nsvg__parseLine(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "polyline") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 0); + nsvg__popAttr(p); + } else if (strcmp(el, "polygon") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 1); + nsvg__popAttr(p); + } else if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 1; + } else if (strcmp(el, "svg") == 0) { + nsvg__parseSVG(p, attr); + } 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*)NANOSVG_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; + const char* start = NULL; + while (*s) { + char c = *s; + if (nsvg__isspace(c) || c == '{') { + if (state == 1) { + NSVGstyles* next = p->styles; + + p->styles = (NSVGstyles*)malloc(sizeof(NSVGstyles)); + p->styles->next = next; + p->styles->name = nsvg__strndup(start, (size_t)(s - start)); + start = s + 1; + state = 2; + } + } else if (state == 2 && c == '}') { + p->styles->description = nsvg__strndup(start, (size_t)(s - start)); + state = 0; + } + else if (state == 0) { + start = s; + state = 1; + } + 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++; + } + */ + } + + } +} + +static void nsvg__imageBounds(NSVGparser* p, float* bounds) +{ + NSVGshape* shape; + shape = p->image->shapes; + if (shape == NULL) { + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; + return; + } + bounds[0] = shape->bounds[0]; + bounds[1] = shape->bounds[1]; + bounds[2] = shape->bounds[2]; + bounds[3] = shape->bounds[3]; + for (shape = shape->next; shape != NULL; shape = shape->next) { + bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); + bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); + bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); + bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); + } +} + +static float nsvg__viewAlign(float content, float container, int type) +{ + if (type == NSVG_ALIGN_MIN) + return 0; + else if (type == NSVG_ALIGN_MAX) + return container - content; + // mid + return (container - content) * 0.5f; +} + +static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) +{ + float t[6]; + nsvg__xformSetTranslation(t, tx, ty); + nsvg__xformMultiply (grad->xform, t); + + nsvg__xformSetScale(t, sx, sy); + nsvg__xformMultiply (grad->xform, t); +} + +static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) +{ + NSVGshape* shape; + NSVGpath* path; + float tx, ty, sx, sy, us, bounds[4], t[6], avgs; + int i; + float* pt; + + // Guess image size if not set completely. + nsvg__imageBounds(p, bounds); + + if (p->viewWidth == 0) { + if (p->image->width > 0) { + p->viewWidth = p->image->width; + } else { + p->viewMinx = bounds[0]; + p->viewWidth = bounds[2] - bounds[0]; + } + } + if (p->viewHeight == 0) { + if (p->image->height > 0) { + p->viewHeight = p->image->height; + } else { + p->viewMiny = bounds[1]; + p->viewHeight = bounds[3] - bounds[1]; + } + } + if (p->image->width == 0) + p->image->width = p->viewWidth; + if (p->image->height == 0) + p->image->height = p->viewHeight; + + tx = -p->viewMinx; + ty = -p->viewMiny; + sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; + sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; + // Unit scaling + us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); + + // Fix aspect ratio + if (p->alignType == NSVG_ALIGN_MEET) { + // fit whole image into viewbox + sx = sy = nsvg__minf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } else if (p->alignType == NSVG_ALIGN_SLICE) { + // fill whole viewbox with image + sx = sy = nsvg__maxf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } + + // Transform + sx *= us; + sy *= us; + avgs = (sx+sy) / 2.0f; + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + shape->bounds[0] = (shape->bounds[0] + tx) * sx; + shape->bounds[1] = (shape->bounds[1] + ty) * sy; + shape->bounds[2] = (shape->bounds[2] + tx) * sx; + shape->bounds[3] = (shape->bounds[3] + ty) * sy; + for (path = shape->paths; path != NULL; path = path->next) { + path->bounds[0] = (path->bounds[0] + tx) * sx; + path->bounds[1] = (path->bounds[1] + ty) * sy; + path->bounds[2] = (path->bounds[2] + tx) * sx; + path->bounds[3] = (path->bounds[3] + ty) * sy; + for (i =0; i < path->npts; i++) { + pt = &path->pts[i*2]; + pt[0] = (pt[0] + tx) * sx; + pt[1] = (pt[1] + ty) * sy; + } + } + + if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); + memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->fill.gradient->xform, t); + } + if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); + memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->stroke.gradient->xform, t); + } + + shape->strokeWidth *= avgs; + shape->strokeDashOffset *= avgs; + for (i = 0; i < shape->strokeDashCount; i++) + shape->strokeDashArray[i] *= avgs; + } +} + +NANOSVG_SCOPE +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; +} + +NANOSVG_SCOPE +NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + NSVGimage* image = NULL; + + fp = fopen(filename, "rb"); + if (!fp) goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char*)NANOSVG_malloc(size+1); + if (data == NULL) goto error; + if (fread(data, 1, size, fp) != size) goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + image = nsvgParse(data, units, dpi); + NANOSVG_free(data); + + return image; + +error: + if (fp) fclose(fp); + if (data) NANOSVG_free(data); + if (image) nsvgDelete(image); + return NULL; +} + +NANOSVG_SCOPE +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); + NANOSVG_free(shape); + shape = snext; + } + NANOSVG_free(image); +} + +#endif diff --git a/generic/nanosvgrast.h b/generic/nanosvgrast.h new file mode 100644 index 0000000..37636fe --- /dev/null +++ b/generic/nanosvgrast.h @@ -0,0 +1,1467 @@ +/* + * 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 polygon rasterization is heavily based on stb_truetype rasterizer + * by Sean Barrett - http://nothings.org/ + * + */ + +#ifndef NANOSVGRAST_H +#define NANOSVGRAST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef NANOSVG_SCOPE +#define NANOSVG_SCOPE +#endif + +#ifndef NANOSVG_malloc +#define NANOSVG_malloc malloc +#endif + +#ifndef NANOSVG_realloc +#define NANOSVG_realloc realloc +#endif + +#ifndef NANOSVG_free +#define NANOSVG_free free +#endif + +typedef struct NSVGrasterizer NSVGrasterizer; + +/* Example Usage: + // Load SVG + struct SNVGImage* image = nsvgParseFromFile("test.svg."); + + // Create rasterizer (can be used to render multiple images). + struct NSVGrasterizer* rast = nsvgCreateRasterizer(); + // Allocate memory for image + unsigned char* img = malloc(w*h*4); + // Rasterize + nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); +*/ + +// Allocated rasterizer context. +NANOSVG_SCOPE NSVGrasterizer* nsvgCreateRasterizer(); + +// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) +// r - pointer to rasterizer context +// image - pointer to image to rasterize +// tx,ty - image offset (applied after scaling) +// scale - image scale +// dst - pointer to destination image data, 4 bytes per pixel (RGBA) +// w - width of the image to render +// h - height of the image to render +// stride - number of bytes per scaleline in the destination buffer +NANOSVG_SCOPE void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride); + +// Deletes rasterizer context. +NANOSVG_SCOPE void nsvgDeleteRasterizer(NSVGrasterizer*); + + +#ifdef __cplusplus +} +#endif + +#endif // NANOSVGRAST_H + +#ifdef NANOSVGRAST_IMPLEMENTATION + +#include <math.h> + +#define NSVG__SUBSAMPLES 5 +#define NSVG__FIXSHIFT 10 +#define NSVG__FIX (1 << NSVG__FIXSHIFT) +#define NSVG__FIXMASK (NSVG__FIX-1) +#define NSVG__MEMPAGE_SIZE 1024 + +typedef struct NSVGedge { + float x0,y0, x1,y1; + int dir; + struct NSVGedge* next; +} NSVGedge; + +typedef struct NSVGpoint { + float x, y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +} NSVGpoint; + +typedef struct NSVGactiveEdge { + int x,dx; + float ey; + int dir; + struct NSVGactiveEdge *next; +} NSVGactiveEdge; + +typedef struct NSVGmemPage { + unsigned char mem[NSVG__MEMPAGE_SIZE]; + int size; + struct NSVGmemPage* next; +} NSVGmemPage; + +typedef struct NSVGcachedPaint { + char type; + char spread; + float xform[6]; + unsigned int colors[256]; +} NSVGcachedPaint; + +struct NSVGrasterizer +{ + float px, py; + + float tessTol; + float distTol; + + NSVGedge* edges; + int nedges; + int cedges; + + NSVGpoint* points; + int npoints; + int cpoints; + + NSVGpoint* points2; + int npoints2; + int cpoints2; + + NSVGactiveEdge* freelist; + NSVGmemPage* pages; + NSVGmemPage* curpage; + + unsigned char* scanline; + int cscanline; + + unsigned char* bitmap; + int width, height, stride; +}; + +NANOSVG_SCOPE +NSVGrasterizer* nsvgCreateRasterizer() +{ + NSVGrasterizer* r = (NSVGrasterizer*)NANOSVG_malloc(sizeof(NSVGrasterizer)); + if (r == NULL) goto error; + memset(r, 0, sizeof(NSVGrasterizer)); + + r->tessTol = 0.25f; + r->distTol = 0.01f; + + return r; + +error: + nsvgDeleteRasterizer(r); + return NULL; +} + +NANOSVG_SCOPE +void nsvgDeleteRasterizer(NSVGrasterizer* r) +{ + NSVGmemPage* p; + + if (r == NULL) return; + + p = r->pages; + while (p != NULL) { + NSVGmemPage* next = p->next; + NANOSVG_free(p); + p = next; + } + + if (r->edges) NANOSVG_free(r->edges); + if (r->points) NANOSVG_free(r->points); + if (r->points2) NANOSVG_free(r->points2); + if (r->scanline) NANOSVG_free(r->scanline); + + NANOSVG_free(r); +} + +static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) +{ + NSVGmemPage *newp; + + // If using existing chain, return the next page in chain + if (cur != NULL && cur->next != NULL) { + return cur->next; + } + + // Alloc new page + newp = (NSVGmemPage*)NANOSVG_malloc(sizeof(NSVGmemPage)); + if (newp == NULL) return NULL; + memset(newp, 0, sizeof(NSVGmemPage)); + + // Add to linked list + if (cur != NULL) + cur->next = newp; + else + r->pages = newp; + + return newp; +} + +static void nsvg__resetPool(NSVGrasterizer* r) +{ + NSVGmemPage* p = r->pages; + while (p != NULL) { + p->size = 0; + p = p->next; + } + r->curpage = r->pages; +} + +static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) +{ + unsigned char* buf; + if (size > NSVG__MEMPAGE_SIZE) return NULL; + if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { + r->curpage = nsvg__nextPage(r, r->curpage); + } + buf = &r->curpage->mem[r->curpage->size]; + r->curpage->size += size; + return buf; +} + +static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) +{ + NSVGpoint* pt; + + if (r->npoints > 0) { + pt = &r->points[r->npoints-1]; + if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { + pt->flags = (unsigned char)(pt->flags | flags); + return; + } + } + + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)NANOSVG_realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + + pt = &r->points[r->npoints]; + pt->x = x; + pt->y = y; + pt->flags = (unsigned char)flags; + r->npoints++; +} + +static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) +{ + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)NANOSVG_realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + r->points[r->npoints] = pt; + r->npoints++; +} + +static void nsvg__duplicatePoints(NSVGrasterizer* r) +{ + if (r->npoints > r->cpoints2) { + r->cpoints2 = r->npoints; + r->points2 = (NSVGpoint*)NANOSVG_realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); + if (r->points2 == NULL) return; + } + + memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); + r->npoints2 = r->npoints; +} + +static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) +{ + NSVGedge* e; + + // Skip horizontal edges + if (y0 == y1) + return; + + if (r->nedges+1 > r->cedges) { + r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; + r->edges = (NSVGedge*)NANOSVG_realloc(r->edges, sizeof(NSVGedge) * r->cedges); + if (r->edges == NULL) return; + } + + e = &r->edges[r->nedges]; + r->nedges++; + + if (y0 < y1) { + e->x0 = x0; + e->y0 = y0; + e->x1 = x1; + e->y1 = y1; + e->dir = 1; + } else { + e->x0 = x1; + e->y0 = y1; + e->x1 = x0; + e->y1 = y0; + e->dir = -1; + } +} + +static float nsvg__normalize(float *x, float* y) +{ + float d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static float nsvg__absf(float x) { return x < 0 ? -x : x; } + +static void nsvg__flattenCubicBez(NSVGrasterizer* r, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { + nsvg__addPathPoint(r, x4, y4, type); + return; + } + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j; + NSVGpath* path; + + for (path = shape->paths; path != NULL; path = path->next) { + r->npoints = 0; + // Flatten path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + } + // Close path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + // Build edges + for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) + nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); + } +} + +enum NSVGpointFlags +{ + NSVG_PT_CORNER = 0x01, + NSVG_PT_BEVEL = 0x02, + NSVG_PT_LEFT = 0x04 +}; + +static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + float len = nsvg__normalize(&dx, &dy); + float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x - dx*w, py = p->y - dy*w; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +#ifndef NSVG_PI +#define NSVG_PI (3.14159265358979323846264338327f) +#endif + +static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) +{ + int i; + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; + + for (i = 0; i < ncap; i++) { + float a = (float)i/(float)(ncap-1)*NSVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + float x = px - dlx*ax - dx*ay; + float y = py - dly*ax - dy*ay; + + if (i > 0) + nsvg__addEdge(r, prevx, prevy, x, y); + + prevx = x; + prevy = y; + + if (i == 0) { + lx = x; ly = y; + } else if (i == ncap-1) { + rx = x; ry = y; + } + } + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); + float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); + float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); + float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); + + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0, rx0, lx1, rx1; + float ly0, ry0, ly1, ry1; + + if (p1->flags & NSVG_PT_LEFT) { + lx0 = lx1 = p1->x - p1->dmx * w; + ly0 = ly1 = p1->y - p1->dmy * w; + nsvg__addEdge(r, lx1, ly1, left->x, left->y); + + rx0 = p1->x + (dlx0 * w); + ry0 = p1->y + (dly0 * w); + rx1 = p1->x + (dlx1 * w); + ry1 = p1->y + (dly1 * w); + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + } else { + lx0 = p1->x - (dlx0 * w); + ly0 = p1->y - (dly0 * w); + lx1 = p1->x - (dlx1 * w); + ly1 = p1->y - (dly1 * w); + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + rx0 = rx1 = p1->x + p1->dmx * w; + ry0 = ry1 = p1->y + p1->dmy * w; + nsvg__addEdge(r, right->x, right->y, rx1, ry1); + } + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) +{ + int i, n; + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float a0 = atan2f(dly0, dlx0); + float a1 = atan2f(dly1, dlx1); + float da = a1 - a0; + float lx, ly, rx, ry; + + if (da < NSVG_PI) da += NSVG_PI*2; + if (da > NSVG_PI) da -= NSVG_PI*2; + + n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); + if (n < 2) n = 2; + if (n > ncap) n = ncap; + + lx = left->x; + ly = left->y; + rx = right->x; + ry = right->y; + + for (i = 0; i < n; i++) { + float u = (float)i/(float)(n-1); + float a = a0 + u*da; + float ax = cosf(a) * w, ay = sinf(a) * w; + float lx1 = p1->x - ax, ly1 = p1->y - ay; + float rx1 = p1->x + ax, ry1 = p1->y + ay; + + nsvg__addEdge(r, lx1, ly1, lx, ly); + nsvg__addEdge(r, rx, ry, rx1, ry1); + + lx = lx1; ly = ly1; + rx = rx1; ry = ry1; + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); + float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); + + nsvg__addEdge(r, lx, ly, left->x, left->y); + nsvg__addEdge(r, right->x, right->y, rx, ry); + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static int nsvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + int divs = (int)ceilf(arc / da); + if (divs < 2) divs = 2; + return divs; +} + +static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) +{ + int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. + NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; + NSVGpoint* p0, *p1; + int j, s, e; + + // Build stroke edges + if (closed) { + // Looping + p0 = &points[npoints-1]; + p1 = &points[0]; + s = 0; + e = npoints; + } else { + // Add cap + p0 = &points[0]; + p1 = &points[1]; + s = 1; + e = npoints-1; + } + + if (closed) { + nsvg__initClosed(&left, &right, p0, p1, lineWidth); + firstLeft = left; + firstRight = right; + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); + } + + for (j = s; j < e; ++j) { + if (p1->flags & NSVG_PT_CORNER) { + if (lineJoin == NSVG_JOIN_ROUND) + nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); + else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) + nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); + else + nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); + } else { + nsvg__straightJoin(r, &left, &right, p1, lineWidth); + } + p0 = p1++; + } + + if (closed) { + // Loop it + nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); + nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); + } +} + +static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) +{ + int i, j; + NSVGpoint* p0, *p1; + + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (i = 0; i < r->npoints; i++) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nsvg__normalize(&p0->dx, &p0->dy); + // Advance + p0 = p1++; + } + + // calculate joins + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (j = 0; j < r->npoints; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float s2 = 1.0f / dmr2; + if (s2 > 600.0f) { + s2 = 600.0f; + } + p1->dmx *= s2; + p1->dmy *= s2; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) + p1->flags |= NSVG_PT_LEFT; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NSVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { + p1->flags |= NSVG_PT_BEVEL; + } + } + + p0 = p1++; + } +} + +static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j, closed; + NSVGpath* path; + NSVGpoint* p0, *p1; + float miterLimit = shape->miterLimit; + int lineJoin = shape->strokeLineJoin; + int lineCap = shape->strokeLineCap; + float lineWidth = shape->strokeWidth * scale; + + for (path = shape->paths; path != NULL; path = path->next) { + // Flatten path + r->npoints = 0; + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); + } + if (r->npoints < 2) + continue; + + closed = path->closed; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { + r->npoints--; + p0 = &r->points[r->npoints-1]; + closed = 1; + } + + if (shape->strokeDashCount > 0) { + int idash = 0, dashState = 1; + float totalDist = 0, dashLen, allDashLen, dashOffset; + NSVGpoint cur; + + if (closed) + nsvg__appendPathPoint(r, r->points[0]); + + // Duplicate points -> points2. + nsvg__duplicatePoints(r); + + r->npoints = 0; + cur = r->points2[0]; + nsvg__appendPathPoint(r, cur); + + // Figure out dash offset. + allDashLen = 0; + for (j = 0; j < shape->strokeDashCount; j++) + allDashLen += shape->strokeDashArray[j]; + if (shape->strokeDashCount & 1) + allDashLen *= 2.0f; + // Find location inside pattern + dashOffset = fmodf(shape->strokeDashOffset, allDashLen); + if (dashOffset < 0.0f) + dashOffset += allDashLen; + + while (dashOffset > shape->strokeDashArray[idash]) { + dashOffset -= shape->strokeDashArray[idash]; + idash = (idash + 1) % shape->strokeDashCount; + } + dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + + for (j = 1; j < r->npoints2; ) { + float dx = r->points2[j].x - cur.x; + float dy = r->points2[j].y - cur.y; + float dist = sqrtf(dx*dx + dy*dy); + + if ((totalDist + dist) > dashLen) { + // Calculate intermediate point + float d = (dashLen - totalDist) / dist; + float x = cur.x + dx * d; + float y = cur.y + dy * d; + nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); + + // Stroke + if (r->npoints > 1 && dashState) { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } + // Advance dash pattern + dashState = !dashState; + idash = (idash+1) % shape->strokeDashCount; + dashLen = shape->strokeDashArray[idash] * scale; + // Restart + cur.x = x; + cur.y = y; + cur.flags = NSVG_PT_CORNER; + totalDist = 0.0f; + r->npoints = 0; + nsvg__appendPathPoint(r, cur); + } else { + totalDist += dist; + cur = r->points2[j]; + nsvg__appendPathPoint(r, cur); + j++; + } + } + // Stroke any leftover path + if (r->npoints > 1 && dashState) + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } else { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); + } + } +} + +static int nsvg__cmpEdge(const void *p, const void *q) +{ + const NSVGedge* a = (const NSVGedge*)p; + const NSVGedge* b = (const NSVGedge*)q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + + +static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) +{ + NSVGactiveEdge* z; + float dxdy; + + if (r->freelist != NULL) { + // Restore from freelist. + z = r->freelist; + r->freelist = z->next; + } else { + // Alloc new edge. + z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); + if (z == NULL) return NULL; + } + + dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); +// STBTT_assert(e->y0 <= start_point); + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); + else + z->dx = (int)floorf(NSVG__FIX * dxdy); + z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); +// z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->dir = e->dir; + + return z; +} + +static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) +{ + z->next = r->freelist; + r->freelist = z; +} + +static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) +{ + int i = x0 >> NSVG__FIXSHIFT; + int j = x1 >> NSVG__FIXSHIFT; + if (i < *xmin) *xmin = i; + if (j > *xmax) *xmax = j; + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = (unsigned char)(scanline[i] + maxWeight); + } + } +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) +{ + // non-zero winding fill + int x0 = 0, w = 0; + + if (fillRule == NSVG_FILLRULE_NONZERO) { + // Non-zero + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->dir; + } else { + int x1 = e->x; w += e->dir; + // if we went to zero, we need to draw + if (w == 0) + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } else if (fillRule == NSVG_FILLRULE_EVENODD) { + // Even-odd + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w = 1; + } else { + int x1 = e->x; w = 0; + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } +} + +static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; + int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; + int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; + int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static unsigned int nsvg__applyOpacity(unsigned int c, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (c) & 0xff; + int g = (c>>8) & 0xff; + int b = (c>>16) & 0xff; + int a = (((c>>24) & 0xff)*iu) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static inline int nsvg__div255(int x) +{ + return ((x+1) * 257) >> 16; +} + +static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, + float tx, float ty, float scale, NSVGcachedPaint* cache) +{ + + if (cache->type == NSVG_PAINT_COLOR) { + int i, cr, cg, cb, ca; + cr = cache->colors[0] & 0xff; + cg = (cache->colors[0] >> 8) & 0xff; + cb = (cache->colors[0] >> 16) & 0xff; + ca = (cache->colors[0] >> 24) & 0xff; + + for (i = 0; i < count; i++) { + int r,g,b; + int a = nsvg__div255((int)cover[0] * ca); + int ia = 255 - a; + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + } + } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + float fx, fy, dx, gy; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gy = fx*t[1] + fy*t[3] + t[5]; + c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + // TODO: focus (fx,fy) + float fx, fy, dx, gx, gy, gd; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gx = fx*t[0] + fy*t[2] + t[4]; + gy = fx*t[1] + fy*t[3] + t[5]; + gd = sqrtf(gx*gx + gy*gy); + c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } +} + +static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) +{ + NSVGactiveEdge *active = NULL; + int y, s; + int e = 0; + int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline + int xmin, xmax; + + for (y = 0; y < r->height; y++) { + memset(r->scanline, 0, r->width); + xmin = r->width; + xmax = 0; + for (s = 0; s < NSVG__SUBSAMPLES; ++s) { + // find center of pixel for this scanline + float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; + NSVGactiveEdge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + NSVGactiveEdge *z = *step; + if (z->ey <= scany) { + *step = z->next; // delete from list +// NSVG__assert(z->valid); + nsvg__freeActive(r, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for (;;) { + int changed = 0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + NSVGactiveEdge* t = *step; + NSVGactiveEdge* q = t->next; + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e < r->nedges && r->edges[e].y0 <= scany) { + if (r->edges[e].y1 > scany) { + NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); + if (z == NULL) break; + // find insertion point + if (active == NULL) { + active = z; + } else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + NSVGactiveEdge* p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + e++; + } + + // now process all active edges in non-zero fashion + if (active != NULL) + nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); + } + // Blit + if (xmin < 0) xmin = 0; + if (xmax > r->width-1) xmax = r->width-1; + if (xmin <= xmax) { + nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); + } + } + +} + +static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) +{ + int x,y; + + // Unpremultiply + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = row[0], g = row[1], b = row[2], a = row[3]; + if (a != 0) { + row[0] = (unsigned char)(r*255/a); + row[1] = (unsigned char)(g*255/a); + row[2] = (unsigned char)(b*255/a); + } + row += 4; + } + } + + // Defringe + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = 0, g = 0, b = 0, a = row[3], n = 0; + if (a == 0) { + if (x-1 > 0 && row[-1] != 0) { + r += row[-4]; + g += row[-3]; + b += row[-2]; + n++; + } + if (x+1 < w && row[7] != 0) { + r += row[4]; + g += row[5]; + b += row[6]; + n++; + } + if (y-1 > 0 && row[-stride+3] != 0) { + r += row[-stride]; + g += row[-stride+1]; + b += row[-stride+2]; + n++; + } + if (y+1 < h && row[stride+3] != 0) { + r += row[stride]; + g += row[stride+1]; + b += row[stride+2]; + n++; + } + if (n > 0) { + row[0] = (unsigned char)(r/n); + row[1] = (unsigned char)(g/n); + row[2] = (unsigned char)(b/n); + } + } + row += 4; + } + } +} + + +static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) +{ + int i, j; + NSVGgradient* grad; + + cache->type = paint->type; + + if (paint->type == NSVG_PAINT_COLOR) { + cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); + return; + } + + grad = paint->gradient; + + cache->spread = grad->spread; + memcpy(cache->xform, grad->xform, sizeof(float)*6); + + if (grad->nstops == 0) { + for (i = 0; i < 256; i++) + cache->colors[i] = 0; + } if (grad->nstops == 1) { + for (i = 0; i < 256; i++) + cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); + } else { + unsigned int ca, cb = 0; + float ua, ub, du, u; + int ia, ib, count; + + ca = nsvg__applyOpacity(grad->stops[0].color, opacity); + ua = nsvg__clampf(grad->stops[0].offset, 0, 1); + ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + for (i = 0; i < ia; i++) { + cache->colors[i] = ca; + } + + for (i = 0; i < grad->nstops-1; i++) { + ca = nsvg__applyOpacity(grad->stops[i].color, opacity); + cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); + ua = nsvg__clampf(grad->stops[i].offset, 0, 1); + ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + count = ib - ia; + if (count <= 0) continue; + u = 0; + du = 1.0f / (float)count; + for (j = 0; j < count; j++) { + cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); + u += du; + } + } + + for (i = ib; i < 256; i++) + cache->colors[i] = cb; + } + +} + +/* +static void dumpEdges(NSVGrasterizer* r, const char* name) +{ + float xmin = 0, xmax = 0, ymin = 0, ymax = 0; + NSVGedge *e = NULL; + int i; + if (r->nedges == 0) return; + FILE* fp = fopen(name, "w"); + if (fp == NULL) return; + + xmin = xmax = r->edges[0].x0; + ymin = ymax = r->edges[0].y0; + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + xmin = nsvg__minf(xmin, e->x0); + xmin = nsvg__minf(xmin, e->x1); + xmax = nsvg__maxf(xmax, e->x0); + xmax = nsvg__maxf(xmax, e->x1); + ymin = nsvg__minf(ymin, e->y0); + ymin = nsvg__minf(ymin, e->y1); + ymax = nsvg__maxf(ymax, e->y0); + ymax = nsvg__maxf(ymax, e->y1); + } + + fprintf(fp, "<svg viewBox=\"%f %f %f %f\" xmlns=\"http://www.w3.org/2000/svg\">", xmin, ymin, (xmax - xmin), (ymax - ymin)); + + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + fprintf(fp ,"<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke:#000;\" />", e->x0,e->y0, e->x1,e->y1); + } + + for (i = 0; i < r->npoints; i++) { + if (i+1 < r->npoints) + fprintf(fp ,"<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke:#f00;\" />", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); + fprintf(fp ,"<circle cx=\"%f\" cy=\"%f\" r=\"1\" style=\"fill:%s;\" />", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); + } + + fprintf(fp, "</svg>"); + fclose(fp); +} +*/ + +NANOSVG_SCOPE +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) +{ + NSVGshape *shape = NULL; + NSVGedge *e = NULL; + NSVGcachedPaint cache; + int i; + + r->bitmap = dst; + r->width = w; + r->height = h; + r->stride = stride; + + if (w > r->cscanline) { + r->cscanline = w; + r->scanline = (unsigned char*)NANOSVG_realloc(r->scanline, w); + if (r->scanline == NULL) return; + } + + for (i = 0; i < h; i++) + memset(&dst[i*stride], 0, w*4); + + for (shape = image->shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + + if (shape->fill.type != NSVG_PAINT_NONE) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShape(r, shape, scale); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->fill, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + } + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeStroke(r, shape, scale); + +// dumpEdges(r, "edge.svg"); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->stroke, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + } + } + + nsvg__unpremultiplyAlpha(dst, w, h, stride); + + r->bitmap = NULL; + r->width = 0; + r->height = 0; + r->stride = 0; +} + +#endif diff --git a/generic/tk.h b/generic/tk.h index e00ea5c..98cdd5f 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -832,10 +832,11 @@ typedef struct Tk_FakeWin { int internalBorderBottom; int minReqWidth; int minReqHeight; - char *dummy20; /* geometryMaster */ #ifdef TK_USE_INPUT_METHODS - int dummy21; + int dummy20; #endif /* TK_USE_INPUT_METHODS */ + char *dummy21; /* geomMgrName */ + Tk_Window dummy22; /* maintainerPtr */ } Tk_FakeWin; /* @@ -1036,6 +1037,8 @@ typedef int (Tk_ItemAreaProc)(Tk_Canvas canvas, Tk_Item *itemPtr, double *rectPtr); typedef int (Tk_ItemPostscriptProc)(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass); +typedef void (Tk_ItemRotateProc)(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRadians); typedef void (Tk_ItemScaleProc)(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -1119,7 +1122,9 @@ typedef struct Tk_ItemType { /* Procedure to delete characters from an * item. */ struct Tk_ItemType *nextPtr;/* Used to link types together into a list. */ - char *reserved1; /* Reserved for future extension. */ + Tk_ItemRotateProc *rotateProc; + /* Procedure to rotate an item's coordinates + * about a point. */ int reserved2; /* Carefully compatible with */ char *reserved3; /* Jan Nijtmans dash patch */ char *reserved4; diff --git a/generic/tkButton.c b/generic/tkButton.c index 9755861..d006d88 100644 --- a/generic/tkButton.c +++ b/generic/tkButton.c @@ -1175,7 +1175,7 @@ ConfigureButton( */ if ((butPtr->type == TYPE_RADIO_BUTTON) && - (*Tcl_GetString(butPtr->onValuePtr) == 0)) { + (*Tcl_GetString(butPtr->onValuePtr) == '\0')) { butPtr->flags |= SELECTED; } } diff --git a/generic/tkCanvArc.c b/generic/tkCanvArc.c index d9c2eca..cd7ce9d 100644 --- a/generic/tkCanvArc.c +++ b/generic/tkCanvArc.c @@ -13,6 +13,8 @@ #include "tkInt.h" #include "tkCanvas.h" +#include "float.h" + /* * The structure below defines the record for each arc item. */ @@ -183,7 +185,7 @@ static void ComputeArcBbox(Tk_Canvas canvas, ArcItem *arcPtr); static int ConfigureArc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int objc, Tcl_Obj *const objv[], int flags); -static void ComputeArcFromHeight(ArcItem *arcPtr); +static void ComputeArcParametersFromHeight(ArcItem *arcPtr); static int CreateArc(Tcl_Interp *interp, Tk_Canvas canvas, struct Tk_Item *itemPtr, int objc, Tcl_Obj *const objv[]); @@ -214,6 +216,8 @@ static int HorizLineToArc(double x1, double x2, static int VertLineToArc(double x, double y1, double y2, double rx, double ry, double start, double extent); +static void RotateArc(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); /* * The structures below defines the arc item types by means of functions that @@ -241,7 +245,8 @@ Tk_ItemType tkArcType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateArc, /* rotateProc */ + 0, NULL, NULL }; /* @@ -469,13 +474,12 @@ ConfigureArc( } /* - * If either the height is provided then the start and extent will be - * overridden. + * Override the start and extent if the height is given. */ - if (arcPtr->height != 0) { - ComputeArcFromHeight(arcPtr); - ComputeArcBbox(canvas, arcPtr); - } + + ComputeArcParametersFromHeight(arcPtr); + + ComputeArcBbox(canvas, arcPtr); i = (int) (arcPtr->start/360.0); arcPtr->start -= i*360.0; @@ -589,7 +593,7 @@ ConfigureArc( /* *-------------------------------------------------------------- * - * ComputeArcFromHeight -- + * ComputeArcParametersFromHeight -- * * This function calculates the arc parameters given start-point, * end-point and height (!= 0). @@ -604,17 +608,30 @@ ConfigureArc( */ static void -ComputeArcFromHeight( +ComputeArcParametersFromHeight( ArcItem* arcPtr) { double chordLen, chordDir[2], chordCen[2], arcCen[2], d, radToDeg, radius; /* - * The chord. + * Do nothing if no height has been specified. + */ + + if (arcPtr->height == 0) + return; + + /* + * Calculate the chord length, return early if it is too small. */ chordLen = hypot(arcPtr->endPoint[1] - arcPtr->startPoint[1], arcPtr->startPoint[0] - arcPtr->endPoint[0]); + + if (chordLen < DBL_EPSILON) { + arcPtr->start = arcPtr->extent = arcPtr->height = 0; + return; + } + chordDir[0] = (arcPtr->endPoint[0] - arcPtr->startPoint[0]) / chordLen; chordDir[1] = (arcPtr->endPoint[1] - arcPtr->startPoint[1]) / chordLen; chordCen[0] = (arcPtr->startPoint[0] + arcPtr->endPoint[0]) / 2; @@ -791,7 +808,7 @@ ComputeArcBbox( ComputeArcOutline(canvas,arcPtr); /* - * To compute the bounding box, start with the the bbox formed by the two + * To compute the bounding box, start with the bbox formed by the two * endpoints of the arc. Then add in the center of the arc's oval (if * relevant) and the 3-o'clock, 6-o'clock, 9-o'clock, and 12-o'clock * positions, if they are relevant. @@ -1493,6 +1510,60 @@ ScaleArc( /* *-------------------------------------------------------------- * + * RotateArc -- + * + * This function is called to rotate an arc by a given amount. + * + * Results: + * None. + * + * Side effects: + * The position of the arc is rotated by angleRad radians about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateArc( + Tk_Canvas canvas, + Tk_Item *itemPtr, + double originX, + double originY, + double angleRad) +{ + ArcItem *arcPtr = (ArcItem *) itemPtr; + double newX, newY, oldX, oldY; + + /* + * Compute the centre of the box, then rotate that about the origin. + */ + + newX = oldX = (arcPtr->bbox[0] + arcPtr->bbox[2]) / 2.0; + newY = oldY = (arcPtr->bbox[1] + arcPtr->bbox[3]) / 2.0; + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &newX, &newY); + + /* + * Apply the translation to the box. + */ + + arcPtr->bbox[0] += newX - oldX; + arcPtr->bbox[1] += newY - oldY; + arcPtr->bbox[2] += newX - oldX; + arcPtr->bbox[3] += newY - oldY; + + /* + * TODO: update the arc endpoints? + */ + + ComputeArcBbox(canvas, arcPtr); +} + +/* + *-------------------------------------------------------------- + * * TranslateArc -- * * This function is called to move an arc by a given amount. diff --git a/generic/tkCanvBmap.c b/generic/tkCanvBmap.c index 1fbfddf..1fdfdf5 100644 --- a/generic/tkCanvBmap.c +++ b/generic/tkCanvBmap.c @@ -105,6 +105,8 @@ static void DeleteBitmap(Tk_Canvas canvas, static void DisplayBitmap(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display, Drawable dst, int x, int y, int width, int height); +static void RotateBitmap(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleBitmap(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -137,7 +139,8 @@ Tk_ItemType tkBitmapType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateBitmap, /* rotateProc */ + 0, NULL, NULL }; /* @@ -790,6 +793,39 @@ ScaleBitmap( /* *-------------------------------------------------------------- * + * RotateBitmap -- + * + * This function is called to rotate a bitmap's origin by a given amount. + * + * Results: + * None. + * + * Side effects: + * The position of the bitmap is rotated by angleRad radians about + * (originX, originY), and the bounding box is updated in the generic + * part of the item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateBitmap( + Tk_Canvas canvas, + Tk_Item *itemPtr, + double originX, + double originY, + double angleRad) +{ + BitmapItem *bmapPtr = (BitmapItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &bmapPtr->x, &bmapPtr->y); + ComputeBitmapBbox(canvas, bmapPtr); +} + +/* + *-------------------------------------------------------------- + * * TranslateBitmap -- * * This function is called to move an item by a given amount. diff --git a/generic/tkCanvImg.c b/generic/tkCanvImg.c index 76b3689..19e1c70 100644 --- a/generic/tkCanvImg.c +++ b/generic/tkCanvImg.c @@ -94,6 +94,8 @@ static void DeleteImage(Tk_Canvas canvas, static void DisplayImage(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display, Drawable dst, int x, int y, int width, int height); +static void RotateImage(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleImage(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -126,7 +128,8 @@ Tk_ItemType tkImageType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateImage, /* rotateProc */ + 0, NULL, NULL }; /* @@ -761,6 +764,40 @@ ImageToPostscript( /* *-------------------------------------------------------------- * + * RotateImage -- + * + * This function is called to rotate an image's origin by a given amount. + * This does *not* rotate the contents of the image. + * + * Results: + * None. + * + * Side effects: + * The position of the image anchor is rotated by angleRad radians about + * (originX, originY), and the bounding box is updated in the generic + * part of the item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateImage( + Tk_Canvas canvas, + Tk_Item *itemPtr, + double originX, + double originY, + double angleRad) +{ + ImageItem *imgPtr = (ImageItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &imgPtr->x, &imgPtr->y); + ComputeImageBbox(canvas, imgPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleImage -- * * This function is invoked to rescale an item. diff --git a/generic/tkCanvLine.c b/generic/tkCanvLine.c index dd0a6cb..62251fc 100644 --- a/generic/tkCanvLine.c +++ b/generic/tkCanvLine.c @@ -117,6 +117,8 @@ static int ParseArrowShape(ClientData clientData, static const char * PrintArrowShape(ClientData clientData, Tk_Window tkwin, char *recordPtr, int offset, Tcl_FreeProc **freeProcPtr); +static void RotateLine(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleLine(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -239,7 +241,8 @@ Tk_ItemType tkLineType = { LineInsert, /* insertProc */ LineDeleteCoords, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateLine, /* rotateProc */ + 0, NULL, NULL }; /* @@ -1856,6 +1859,56 @@ TranslateLine( /* *-------------------------------------------------------------- * + * RotateLine -- + * + * This function is called to rotate a line by a given amount about a + * point. + * + * Results: + * None. + * + * Side effects: + * The position of the line is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateLine( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being moved. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + LineItem *linePtr = (LineItem *) itemPtr; + double *coordPtr; + int i; + double s = sin(angleRad), c = cos(angleRad); + + for (i = 0, coordPtr = linePtr->coordPtr; i < linePtr->numPoints; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + if (linePtr->firstArrowPtr != NULL) { + for (i = 0, coordPtr = linePtr->firstArrowPtr; i < PTS_IN_ARROW; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + } + if (linePtr->lastArrowPtr != NULL) { + for (i = 0, coordPtr = linePtr->lastArrowPtr; i < PTS_IN_ARROW; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + } + ComputeLineBbox(canvas, linePtr); +} + +/* + *-------------------------------------------------------------- + * * ParseArrowShape -- * * This function is called back during option parsing to parse arrow diff --git a/generic/tkCanvPoly.c b/generic/tkCanvPoly.c index f10aae7..0b2d134 100644 --- a/generic/tkCanvPoly.c +++ b/generic/tkCanvPoly.c @@ -176,6 +176,8 @@ static double PolygonToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *pointPtr); static int PolygonToPostscript(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass); +static void RotatePolygon(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScalePolygon(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -202,13 +204,14 @@ Tk_ItemType tkPolygonType = { PolygonToPostscript, /* postscriptProc */ ScalePolygon, /* scaleProc */ TranslatePolygon, /* translateProc */ - GetPolygonIndex, /* indexProc */ + GetPolygonIndex, /* indexProc */ NULL, /* icursorProc */ NULL, /* selectionProc */ - PolygonInsert, /* insertProc */ + PolygonInsert, /* insertProc */ PolygonDeleteCoords, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotatePolygon, /* rotateProc */ + 0, NULL, NULL }; /* @@ -1738,6 +1741,44 @@ GetPolygonIndex( /* *-------------------------------------------------------------- * + * RotatePolygon -- + * + * This function is called to rotate a polygon by a given amount about a + * point. + * + * Results: + * None. + * + * Side effects: + * The position of the polygon is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotatePolygon( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being moved. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + PolygonItem *polyPtr = (PolygonItem *) itemPtr; + double *coordPtr; + int i; + double s = sin(angleRad), c = cos(angleRad); + + for (i = 0, coordPtr = polyPtr->coordPtr; i < polyPtr->numPoints; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + ComputePolygonBbox(canvas, polyPtr); +} + +/* + *-------------------------------------------------------------- + * * TranslatePolygon -- * * This function is called to move a polygon by a given amount. diff --git a/generic/tkCanvText.c b/generic/tkCanvText.c index cfffc62..b9607a5 100644 --- a/generic/tkCanvText.c +++ b/generic/tkCanvText.c @@ -170,6 +170,8 @@ static double TextToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *pointPtr); static int TextToPostscript(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass); +static void RotateText(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void TranslateText(Tk_Canvas canvas, Tk_Item *itemPtr, double deltaX, double deltaY); @@ -199,7 +201,8 @@ Tk_ItemType tkTextType = { TextInsert, /* insertProc */ TextDeleteChars, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateText, /* rotateProc */ + 0, NULL, NULL }; #define ROUND(d) ((int) floor((d) + 0.5)) @@ -1007,7 +1010,7 @@ TextInsert( { TextItem *textPtr = (TextItem *) itemPtr; int byteIndex, charsAdded; - size_t byteCount; + TkSizeT byteCount; char *newStr, *text; const char *string; Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr; @@ -1029,7 +1032,7 @@ TextInsert( } newStr = ckalloc(textPtr->numBytes + byteCount + 1); - memcpy(newStr, text, (size_t) byteIndex); + memcpy(newStr, text, byteIndex); strcpy(newStr + byteIndex, string); strcpy(newStr + byteIndex + byteCount, text + byteIndex); @@ -1110,7 +1113,7 @@ TextDeleteChars( - (text + byteIndex); newStr = ckalloc(textPtr->numBytes + 1 - byteCount); - memcpy(newStr, text, (size_t) byteIndex); + memcpy(newStr, text, byteIndex); strcpy(newStr + byteIndex, text + byteIndex + byteCount); ckfree(text); @@ -1250,6 +1253,39 @@ TextToArea( /* *-------------------------------------------------------------- * + * RotateText -- + * + * This function is called to rotate a text item by a given amount about a + * point. Note that this does *not* rotate the text of the item. + * + * Results: + * None. + * + * Side effects: + * The position of the text anchor is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateText( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being rotated. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + TextItem *textPtr = (TextItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &textPtr->x, &textPtr->y); + ComputeTextBbox(canvas, textPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleText -- * * This function is invoked to rescale a text item. @@ -1344,7 +1380,7 @@ GetTextIndex( * index. */ { TextItem *textPtr = (TextItem *) itemPtr; - size_t length; + TkSizeT length; int c; TkCanvas *canvasPtr = (TkCanvas *) canvas; Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr; @@ -1505,7 +1541,7 @@ GetSelText( if (byteCount <= 0) { return 0; } - memcpy(buffer, selStart + offset, (size_t) byteCount); + memcpy(buffer, selStart + offset, byteCount); buffer[byteCount] = '\0'; return byteCount; } diff --git a/generic/tkCanvUtil.c b/generic/tkCanvUtil.c index 6ce671d..52d7bf6 100644 --- a/generic/tkCanvUtil.c +++ b/generic/tkCanvUtil.c @@ -1265,7 +1265,6 @@ Tk_ChangeOutlineGC( } return 0; } - /* *-------------------------------------------------------------- @@ -1865,6 +1864,43 @@ TkCanvTranslatePath( } /* + *-------------------------------------------------------------- + * + * TkRotatePoint -- + * + * Rotate a point about another point. The angle should be converted into + * its sine and cosine before calling this function. + * + * Results: + * None + * + * Side effects: + * The point in (*xPtr,*yPtr) is updated to be rotated about + * (originX,originY) by the amount given by the sine and cosine of the + * angle to rotate. + * + *-------------------------------------------------------------- + */ + +void +TkRotatePoint( + double originX, double originY, /* The point about which to rotate. */ + double sine, double cosine, /* How much to rotate? */ + double *xPtr, double *yPtr) /* The point to be rotated. (INOUT) */ +{ + double x = *xPtr - originX; + double y = *yPtr - originY; + + /* + * Beware! The canvas coordinate space is flipped vertically, so rotations + * go the "wrong" way with respect to mathematics. + */ + + *xPtr = originX + x * cosine + y * sine; + *yPtr = originY - x * sine + y * cosine; +} + +/* * Local Variables: * mode: c * c-basic-offset: 4 diff --git a/generic/tkCanvWind.c b/generic/tkCanvWind.c index a57d428..59e81af 100644 --- a/generic/tkCanvWind.c +++ b/generic/tkCanvWind.c @@ -77,6 +77,8 @@ static void DeleteWinItem(Tk_Canvas canvas, static void DisplayWinItem(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display, Drawable dst, int x, int y, int width, int height); +static void RotateWinItem(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleWinItem(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -130,7 +132,8 @@ Tk_ItemType tkWindowType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateWinItem, /* rotateProc */ + 0, NULL, NULL }; /* @@ -915,6 +918,40 @@ CanvasPsWindow( /* *-------------------------------------------------------------- * + * RotateWinItem -- + * + * This function is called to rotate a window item by a given amount + * about a point. Note that this does *not* rotate the window of the + * item. + * + * Results: + * None. + * + * Side effects: + * The position of the window anchor is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateWinItem( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being rotated. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + WindowItem *winItemPtr = (WindowItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &winItemPtr->x, &winItemPtr->y); + ComputeWindowBbox(canvas, winItemPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleWinItem -- * * This function is invoked to rescale a window item. diff --git a/generic/tkCanvas.c b/generic/tkCanvas.c index 15a2ffb..f8573fc 100644 --- a/generic/tkCanvas.c +++ b/generic/tkCanvas.c @@ -241,6 +241,9 @@ static void CanvasWorldChanged(ClientData instanceData); static int ConfigureCanvas(Tcl_Interp *interp, TkCanvas *canvasPtr, int argc, Tcl_Obj *const *argv, int flags); +static void DefaultRotateImplementation(TkCanvas *canvasPtr, + Tk_Item *itemPtr, double x, double y, + double angleRadians); static void DestroyCanvas(void *memPtr); static int DrawCanvas(Tcl_Interp *interp, ClientData clientData, Tk_PhotoHandle photohandle, int subsample, int zoom); static void DisplayCanvas(ClientData clientData); @@ -560,6 +563,102 @@ ItemTranslate( itemPtr->typePtr->translateProc((Tk_Canvas) canvasPtr, itemPtr, xDelta, yDelta); } + +static inline void +ItemRotate( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double x, + double y, + double angleRadians) +{ + if (itemPtr->typePtr->rotateProc != NULL) { + itemPtr->typePtr->rotateProc((Tk_Canvas) canvasPtr, + itemPtr, x, y, angleRadians); + } else { + DefaultRotateImplementation(canvasPtr, itemPtr, x, y, angleRadians); + } +} + +/* + *-------------------------------------------------------------- + * + * DefaultRotateImplementation -- + * + * The default implementation of the rotation operation, used when items + * do not provide their own version. + * + *-------------------------------------------------------------- + */ + +static void +DefaultRotateImplementation( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double x, + double y, + double angleRadians) +{ + int objc, i, ok = 1; + Tcl_Obj **objv, **newObjv; + double *coordv; + double s = sin(angleRadians); + double c = cos(angleRadians); + Tcl_Interp *interp = canvasPtr->interp; + + /* + * Get the coordinates out of the item. + */ + + if (ItemCoords(canvasPtr, itemPtr, 0, NULL) == TCL_OK && + Tcl_ListObjGetElements(NULL, Tcl_GetObjResult(interp), + &objc, &objv) == TCL_OK) { + coordv = (double *) Tcl_Alloc(sizeof(double) * objc); + for (i=0 ; i<objc ; i++) { + if (Tcl_GetDoubleFromObj(NULL, objv[i], &coordv[i]) != TCL_OK) { + ok = 0; + break; + } + } + if (ok) { + /* + * Apply the rotation. + */ + + for (i=0 ; i<objc ; i+=2) { + double px = coordv[i+0] - x; + double py = coordv[i+1] - y; + double nx = px * c - py * s; + double ny = px * s + py * c; + + coordv[i+0] = nx + x; + coordv[i+1] = ny + y; + } + + /* + * Write the coordinates back into the item. + */ + + newObjv = (Tcl_Obj **) Tcl_Alloc(sizeof(Tcl_Obj *) * objc); + for (i=0 ; i<objc ; i++) { + newObjv[i] = Tcl_NewDoubleObj(coordv[i]); + Tcl_IncrRefCount(newObjv[i]); + } + ItemCoords(canvasPtr, itemPtr, objc, newObjv); + for (i=0 ; i<objc ; i++) { + Tcl_DecrRefCount(newObjv[i]); + } + Tcl_Free((char *) newObjv); + } + Tcl_Free((char *) coordv); + } + + /* + * The interpreter result was (probably) modified above; reset it. + */ + + Tcl_ResetResult(interp); +} /* *-------------------------------------------------------------- @@ -746,25 +845,24 @@ CanvasWidgetCmd( "canvasy", "cget", "configure", "coords", "create", "dchars", "delete", "dtag", "find", "focus", "gettags", "icursor", - "image", - "imove", "index", "insert", "itemcget", - "itemconfigure", + "image", "imove", "index", "insert", + "itemcget", "itemconfigure", "lower", "move", "moveto", "postscript", - "raise", "rchars", "scale", "scan", - "select", "type", "xview", "yview", - NULL + "raise", "rchars", "rotate", "scale", + "scan", "select", "type", "xview", + "yview", NULL }; enum options { CANV_ADDTAG, CANV_BBOX, CANV_BIND, CANV_CANVASX, CANV_CANVASY, CANV_CGET, CANV_CONFIGURE, CANV_COORDS, CANV_CREATE, CANV_DCHARS, CANV_DELETE, CANV_DTAG, CANV_FIND, CANV_FOCUS, CANV_GETTAGS, CANV_ICURSOR, - CANV_IMAGE, - CANV_IMOVE, CANV_INDEX, CANV_INSERT, CANV_ITEMCGET, - CANV_ITEMCONFIGURE, + CANV_IMAGE, CANV_IMOVE, CANV_INDEX, CANV_INSERT, + CANV_ITEMCGET, CANV_ITEMCONFIGURE, CANV_LOWER, CANV_MOVE, CANV_MOVETO, CANV_POSTSCRIPT, - CANV_RAISE, CANV_RCHARS, CANV_SCALE, CANV_SCAN, - CANV_SELECT, CANV_TYPE, CANV_XVIEW, CANV_YVIEW + CANV_RAISE, CANV_RCHARS, CANV_ROTATE, CANV_SCALE, + CANV_SCAN, CANV_SELECT, CANV_TYPE, CANV_XVIEW, + CANV_YVIEW }; if (objc < 2) { @@ -1153,7 +1251,7 @@ CanvasWidgetCmd( int isNew = 0; Tcl_HashEntry *entryPtr; const char *arg; - size_t length; + TkSizeT length; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "type coords ?arg ...?"); @@ -1760,6 +1858,30 @@ CanvasWidgetCmd( } break; } + case CANV_ROTATE: { + double x, y, angle; + Tk_Canvas canvas = (Tk_Canvas) canvasPtr; + + if (objc != 6) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId x y angle"); + result = TCL_ERROR; + goto done; + } + if (Tk_CanvasGetCoordFromObj(interp, canvas, objv[3], &x) != TCL_OK || + Tk_CanvasGetCoordFromObj(interp, canvas, objv[4], &y) != TCL_OK || + Tcl_GetDoubleFromObj(interp, objv[5], &angle) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + angle = angle * 3.1415927 / 180.0; + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + EventuallyRedrawItem(canvasPtr, itemPtr); + ItemRotate(canvasPtr, itemPtr, x, y, angle); + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + } + break; + } case CANV_SCALE: { double xOrigin, yOrigin, xScale, yScale; diff --git a/generic/tkClipboard.c b/generic/tkClipboard.c index 9f6822f..3c3d89b 100644 --- a/generic/tkClipboard.c +++ b/generic/tkClipboard.c @@ -450,7 +450,7 @@ Tk_ClipboardObjCmd( }; enum appendOptions { APPEND_DISPLAYOF, APPEND_FORMAT, APPEND_TYPE }; int subIndex; - size_t length; + TkSizeT length; for (i = 2; i < objc - 1; i++) { string = TkGetStringFromObj(objv[i], &length); diff --git a/generic/tkCmds.c b/generic/tkCmds.c index 391d906..f0abd70 100644 --- a/generic/tkCmds.c +++ b/generic/tkCmds.c @@ -2071,7 +2071,7 @@ TkGetDisplayOf( * present. */ { const char *string; - size_t length; + TkSizeT length; if (objc < 1) { return 0; diff --git a/generic/tkConfig.c b/generic/tkConfig.c index 892692a..7b84207 100644 --- a/generic/tkConfig.c +++ b/generic/tkConfig.c @@ -656,7 +656,7 @@ DoObjConfig( case TK_OPTION_STRING: { char *newStr; const char *value; - size_t length; + TkSizeT length; if (nullOK && ObjectIsEmpty(valuePtr)) { valuePtr = NULL; diff --git a/generic/tkEntry.c b/generic/tkEntry.c index fdd2ff4..9fd8371 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -4385,7 +4385,7 @@ SpinboxInvoke( */ int i, listc; - size_t elemLen, length = entryPtr->numChars; + TkSizeT elemLen, length = entryPtr->numChars; const char *bytes; Tcl_Obj **listv; diff --git a/generic/tkFileFilter.c b/generic/tkFileFilter.c index 6cb188b..561676b 100644 --- a/generic/tkFileFilter.c +++ b/generic/tkFileFilter.c @@ -262,7 +262,7 @@ AddClause( */ for (i=0; i<ostypeCount; i++) { - size_t len; + TkSizeT len; const char *strType = TkGetStringFromObj(ostypeList[i], &len); /* @@ -322,7 +322,7 @@ AddClause( if (globCount > 0 && globList != NULL) { for (i=0; i<globCount; i++) { GlobPattern *globPtr = ckalloc(sizeof(GlobPattern)); - size_t len; + TkSizeT len; const char *str = TkGetStringFromObj(globList[i], &len); len = (len + 1) * sizeof(char); @@ -375,7 +375,7 @@ AddClause( } for (i=0; i<ostypeCount; i++) { Tcl_DString osTypeDS; - size_t len; + TkSizeT len; MacFileType *mfPtr = ckalloc(sizeof(MacFileType)); const char *strType = TkGetStringFromObj(ostypeList[i], &len); char *string; diff --git a/generic/tkFont.c b/generic/tkFont.c index 053524c..cc7861f 100644 --- a/generic/tkFont.c +++ b/generic/tkFont.c @@ -712,7 +712,7 @@ Tk_FontObjCmd( case FONT_MEASURE: { const char *string; Tk_Font tkfont; - size_t length = 0; + TkSizeT length = 0; int skip = 0; if (objc > 4) { @@ -3245,7 +3245,7 @@ Tk_TextLayoutToPostscript( int baseline = chunkPtr->y; Tcl_Obj *psObj = Tcl_NewObj(); int i, j; - size_t len; + TkSizeT len; const char *p, *glyphname; char uindex[5], c, *ps; int ch; @@ -4250,7 +4250,7 @@ TkFontGetFirstTextLayout( } chunkPtr = layoutPtr->chunks; numBytesInChunk = chunkPtr->numBytes; - strncpy(dst, chunkPtr->start, (size_t) numBytesInChunk); + strncpy(dst, chunkPtr->start, numBytesInChunk); *font = layoutPtr->tkfont; return numBytesInChunk; } diff --git a/generic/tkFrame.c b/generic/tkFrame.c index 708faef..d15f9a3 100644 --- a/generic/tkFrame.c +++ b/generic/tkFrame.c @@ -489,7 +489,7 @@ CreateFrame( const char *className, *screenName, *visualName, *colormapName; const char *arg, *useOption; int i, depth; - size_t length; + TkSizeT length; unsigned int mask; Colormap colormap; Visual *visual; @@ -745,7 +745,7 @@ FrameWidgetObjCmd( register Frame *framePtr = clientData; int result = TCL_OK, index; int c, i; - size_t length; + TkSizeT length; Tcl_Obj *objPtr; if (objc < 2) { diff --git a/generic/tkGeometry.c b/generic/tkGeometry.c index eab3850..fbdd530 100644 --- a/generic/tkGeometry.c +++ b/generic/tkGeometry.c @@ -327,23 +327,23 @@ TkSetGeometryMaster( { register TkWindow *winPtr = (TkWindow *) tkwin; - if (winPtr->geometryMaster != NULL && - strcmp(winPtr->geometryMaster, master) == 0) { + if (winPtr->geomMgrName != NULL && + strcmp(winPtr->geomMgrName, master) == 0) { return TCL_OK; } - if (winPtr->geometryMaster != NULL) { + if (winPtr->geomMgrName != NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot use geometry manager %s inside %s which already" " has slaves managed by %s", - master, Tk_PathName(tkwin), winPtr->geometryMaster)); + master, Tk_PathName(tkwin), winPtr->geomMgrName)); Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "FIGHT", NULL); } return TCL_ERROR; } - winPtr->geometryMaster = ckalloc(strlen(master) + 1); - strcpy(winPtr->geometryMaster, master); + winPtr->geomMgrName = ckalloc(strlen(master) + 1); + strcpy(winPtr->geomMgrName, master); return TCL_OK; } @@ -372,14 +372,14 @@ TkFreeGeometryMaster( { register TkWindow *winPtr = (TkWindow *) tkwin; - if (winPtr->geometryMaster != NULL && - strcmp(winPtr->geometryMaster, master) != 0) { + if (winPtr->geomMgrName != NULL && + strcmp(winPtr->geomMgrName, master) != 0) { Tcl_Panic("Trying to free %s from geometry manager %s", - winPtr->geometryMaster, master); + winPtr->geomMgrName, master); } - if (winPtr->geometryMaster != NULL) { - ckfree(winPtr->geometryMaster); - winPtr->geometryMaster = NULL; + if (winPtr->geomMgrName != NULL) { + ckfree(winPtr->geomMgrName); + winPtr->geomMgrName = NULL; } } @@ -425,6 +425,9 @@ Tk_MaintainGeometry( Tk_Window ancestor, parent; TkDisplay *dispPtr = ((TkWindow *) master)->dispPtr; + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)master; + + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)master; if (master == Tk_Parent(slave)) { /* * If the slave is a direct descendant of the master, don't bother @@ -570,6 +573,9 @@ Tk_UnmaintainGeometry( Tk_Window ancestor; TkDisplay *dispPtr = ((TkWindow *) slave)->dispPtr; + ((TkWindow *)slave)->maintainerPtr = NULL; + + ((TkWindow *)slave)->maintainerPtr = NULL; if (master == Tk_Parent(slave)) { /* * If the slave is a direct descendant of the master, diff --git a/generic/tkGrab.c b/generic/tkGrab.c index ff5d083..0efddee 100644 --- a/generic/tkGrab.c +++ b/generic/tkGrab.c @@ -190,7 +190,7 @@ Tk_GrabObjCmd( TkDisplay *dispPtr; const char *arg; int index; - size_t len; + TkSizeT len; static const char *const optionStrings[] = { "current", "release", "set", "status", NULL }; diff --git a/generic/tkGrid.c b/generic/tkGrid.c index 436151a..cedf5f9 100644 --- a/generic/tkGrid.c +++ b/generic/tkGrid.c @@ -2941,6 +2941,7 @@ ConfigureSlaves( Gridder *masterPtr = NULL; Gridder *slavePtr; Tk_Window other, slave, parent, ancestor; + TkWindow *master; int i, j, tmp; int numWindows; int width; @@ -2968,7 +2969,7 @@ ConfigureSlaves( firstChar = 0; for (numWindows=0, i=0; i < objc; i++) { - size_t length; + TkSizeT length; char prevChar = firstChar; string = TkGetStringFromObj(objv[i], &length); @@ -3355,17 +3356,23 @@ ConfigureSlaves( } /* - * Try to make sure our master isn't managed by us. + * Check for management loops. */ - if (masterPtr->masterPtr == slavePtr) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( + for (master = (TkWindow *)masterPtr->tkwin; master != NULL; + master = (TkWindow *)TkGetGeomMaster(master)) { + if (master == (TkWindow *)slave) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't put %s inside %s, would cause management loop", - Tcl_GetString(objv[j]), Tk_PathName(masterPtr->tkwin))); - Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); - Unlink(slavePtr); - return TCL_ERROR; - } + Tcl_GetString(objv[j]), Tk_PathName(masterPtr->tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); + Unlink(slavePtr); + return TCL_ERROR; + } + } + if (masterPtr->tkwin != Tk_Parent(slave)) { + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)masterPtr->tkwin; + } Tk_ManageGeometry(slave, &gridMgrType, slavePtr); diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index da62d1f..cebf8e3 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -757,7 +757,7 @@ StringMatchGIF( Tcl_Interp *interp) /* not used */ { unsigned char *data, header[10]; - size_t got, length; + TkSizeT got, length; MFile handle; data = TkGetByteArrayFromObj(dataObj, &length); @@ -826,7 +826,7 @@ StringReadGIF( int srcX, int srcY) { MFile handle, *hdlPtr = &handle; - size_t length; + TkSizeT length; const char *xferFormat; unsigned char *data = TkGetByteArrayFromObj(dataObj, &length); diff --git a/generic/tkImgListFormat.c b/generic/tkImgListFormat.c index 4636c41..0a4435c 100644 --- a/generic/tkImgListFormat.c +++ b/generic/tkImgListFormat.c @@ -772,7 +772,7 @@ ParseColor( unsigned char *alphaPtr) { const char *specString; - size_t charCount; + TkSizeT charCount; /* * Find out which color format we have diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index a620515..871684d 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -127,7 +127,7 @@ typedef struct { Tcl_Channel channel; /* Channel for from-file reads. */ Tcl_Obj *objDataPtr; unsigned char *strDataBuf; /* Raw source data for from-string reads. */ - size_t strDataLen; /* Length of source data. */ + TkSizeT strDataLen; /* Length of source data. */ unsigned char *base64Data; /* base64 encoded string data. */ unsigned char base64Bits; /* Remaining bits from last base64 read. */ unsigned char base64State; /* Current state of base64 decoder. */ @@ -2098,7 +2098,7 @@ ReadIDAT( */ while (chunkSz && !Tcl_ZlibStreamEof(pngPtr->stream)) { - size_t len1, len2; + TkSizeT len1, len2; /* * Read another block of input into the zlib stream if data remains. @@ -2154,7 +2154,7 @@ ReadIDAT( } TkGetByteArrayFromObj(pngPtr->thisLineObj, &len2); - if (len2 == (size_t)pngPtr->phaseSize) { + if (len2 == (TkSizeT)pngPtr->phaseSize) { if (pngPtr->phase > 7) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "extra data after final scan line of final phase", @@ -2863,7 +2863,7 @@ WriteData( */ if (pngPtr->objDataPtr) { - size_t objSz; + TkSizeT objSz; unsigned char *destPtr; TkGetByteArrayFromObj(pngPtr->objDataPtr, &objSz); @@ -3135,7 +3135,7 @@ WriteIDAT( int rowNum, flush = TCL_ZLIB_NO_FLUSH, result; Tcl_Obj *outputObj; unsigned char *outputBytes; - size_t outputSize; + TkSizeT outputSize; /* * Filter and compress each row one at a time. diff --git a/generic/tkImgPPM.c b/generic/tkImgPPM.c index d6ec63c..ea49fc2 100644 --- a/generic/tkImgPPM.c +++ b/generic/tkImgPPM.c @@ -765,7 +765,7 @@ ReadPPMStringHeader( #define BUFFER_SIZE 1000 char buffer[BUFFER_SIZE], c; int i, numFields, type = 0; - size_t dataSize; + TkSizeT dataSize; unsigned char *dataBuffer; dataBuffer = TkGetByteArrayFromObj(dataPtr, &dataSize); diff --git a/generic/tkImgPhInstance.c b/generic/tkImgPhInstance.c index f67ab36..1360d1e 100644 --- a/generic/tkImgPhInstance.c +++ b/generic/tkImgPhInstance.c @@ -867,8 +867,8 @@ TkImgPhotoInstanceSetSize( if (masterPtr->width == instancePtr->width) { offset = validBox.y * masterPtr->width * 3; memcpy(newError + offset, instancePtr->error + offset, - (size_t) (validBox.height - * masterPtr->width * 3 * sizeof(schar))); + (size_t) validBox.height + * masterPtr->width * 3 * sizeof(schar)); } else if (validBox.width > 0 && validBox.height > 0) { errDestPtr = newError + @@ -1982,8 +1982,8 @@ TkImgResetDither( { if (instancePtr->error) { memset(instancePtr->error, 0, - /*(size_t)*/ (instancePtr->masterPtr->width - * instancePtr->masterPtr->height * 3 * sizeof(schar))); + (size_t) instancePtr->masterPtr->width + * instancePtr->masterPtr->height * 3 * sizeof(schar)); } } diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index c8825a1..d200d0b 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -410,7 +410,7 @@ ImgPhotoCmd( unsigned char *pixelPtr; Tk_PhotoImageBlock block; Tk_PhotoImageFormat *imageFormat; - size_t length; + TkSizeT length; int imageWidth, imageHeight, matched, oldformat = 0; Tcl_Channel chan; Tk_PhotoHandle srcHandle; @@ -1476,7 +1476,7 @@ ParseSubcommandOptions( * TK_PHOTO_COMPOSITE_* constants. */ NULL }; - size_t length; + TkSizeT length; int index, c, bit, currentBit; int values[4], numValues, maxValues, argIndex; const char *option, *expandedOption, *needed; @@ -1758,7 +1758,7 @@ ImgPhotoConfigureMaster( const char *oldFileString, *oldPaletteString; Tcl_Obj *oldData, *data = NULL, *oldFormat, *format = NULL; Tcl_Obj *tempdata, *tempformat; - size_t length; + TkSizeT length; int i, j, result, imageWidth, imageHeight, oldformat; double oldGamma; Tcl_Channel chan; @@ -1846,7 +1846,7 @@ ImgPhotoConfigureMaster( * Force into ByteArray format, which most (all) image handlers will * use anyway. Empty length means ignore the -data option. */ - size_t bytesize; + TkSizeT bytesize; (void) TkGetByteArrayFromObj(data, &bytesize); if (bytesize) { diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c new file mode 100644 index 0000000..f86c45e --- /dev/null +++ b/generic/tkImgSVGnano.c @@ -0,0 +1,698 @@ +/* + * tkImgSVGnano.c + * + * A photo file handler for SVG files. + * + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * Copyright (c) 2018 Christian Gollwitzer auriocus@gmx.de + * Copyright (c) 2018 Rene Zaumseil r.zaumseil@freenet.de + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * This handler is build using the original nanosvg library files from + * https://github.com/memononen/nanosvg and the tcl extension files from + * https://github.com/auriocus/tksvg + * + */ + +#include "tkInt.h" +#define NANOSVG_malloc ckalloc +#define NANOSVG_realloc ckrealloc +#define NANOSVG_free ckfree +#define NANOSVG_SCOPE MODULE_SCOPE +#define NANOSVG_ALL_COLOR_KEYWORDS +#define NANOSVG_IMPLEMENTATION +#include "nanosvg.h" +#define NANOSVGRAST_IMPLEMENTATION +#include "nanosvgrast.h" + +/* Additional parameters to nsvgRasterize() */ + +typedef struct { + double x; + double y; + double scale; +} RastOpts; + +/* + * Per interp cache of last NSVGimage which was matched to + * be immediately rasterized after the match. This helps to + * eliminate double parsing of the SVG file/string. + */ + +typedef struct { + ClientData dataOrChan; + Tcl_DString formatString; + NSVGimage *nsvgImage; + RastOpts ropts; +} NSVGcache; + +static int FileMatchSVG(Tcl_Channel chan, const char *fileName, + Tcl_Obj *format, int *widthPtr, int *heightPtr, + Tcl_Interp *interp); +static int FileReadSVG(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *format, + Tk_PhotoHandle imageHandle, int destX, int destY, + int width, int height, int srcX, int srcY); +static int StringMatchSVG(Tcl_Obj *dataObj, Tcl_Obj *format, + int *widthPtr, int *heightPtr, Tcl_Interp *interp); +static int StringReadSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY); +static NSVGimage * ParseSVGWithOptions(Tcl_Interp *interp, + const char *input, int length, Tcl_Obj *format, + RastOpts *ropts); +static int RasterizeSVG(Tcl_Interp *interp, + Tk_PhotoHandle imageHandle, NSVGimage *nsvgImage, + int destX, int destY, int width, int height, + int srcX, int srcY, RastOpts *ropts); +static NSVGcache * GetCachePtr(Tcl_Interp *interp); +static int CacheSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, NSVGimage *nsvgImage, + RastOpts *ropts); +static NSVGimage * GetCachedSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, RastOpts *ropts); +static void CleanCache(Tcl_Interp *interp); +static void FreeCache(ClientData clientData, Tcl_Interp *interp); + +/* + * The format record for the SVG nano file format: + */ + +Tk_PhotoImageFormat tkImgFmtSVGnano = { + "svg", /* name */ + FileMatchSVG, /* fileMatchProc */ + StringMatchSVG, /* stringMatchProc */ + FileReadSVG, /* fileReadProc */ + StringReadSVG, /* stringReadProc */ + NULL, /* fileWriteProc */ + NULL, /* stringWriteProc */ + NULL +}; + +/* + *---------------------------------------------------------------------- + * + * FileMatchSVG -- + * + * This function is invoked by the photo image type to see if a file + * contains image data in SVG format. + * + * Results: + * The return value is >0 if the file can be successfully parsed, + * and 0 otherwise. + * + * Side effects: + * The file is saved in the internal cache for further use. + * + *---------------------------------------------------------------------- + */ + +static int +FileMatchSVG( + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp) +{ + int length; + Tcl_Obj *dataObj = Tcl_NewObj(); + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage; + + CleanCache(interp); + if (Tcl_ReadChars(chan, dataObj, -1, 0) == -1) { + /* in case of an error reading the file */ + Tcl_DecrRefCount(dataObj); + return 0; + } + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + Tcl_DecrRefCount(dataObj); + if (nsvgImage != NULL) { + *widthPtr = (int) ceil(nsvgImage->width * ropts.scale); + *heightPtr = (int) ceil(nsvgImage->height * ropts.scale); + if ((*widthPtr <= 0) || (*heightPtr <= 0)) { + nsvgDelete(nsvgImage); + return 0; + } + if (!CacheSVG(interp, chan, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FileReadSVG -- + * + * This function is called by the photo image type to read SVG format + * data from a file and write it into a given photo image. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * Side effects: + * The access position in file f is changed, and new data is added to the + * image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +FileReadSVG( + Tcl_Interp *interp, + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, chan, formatObj, &ropts); + + if (nsvgImage == NULL) { + Tcl_Obj *dataObj = Tcl_NewObj(); + + if (Tcl_ReadChars(chan, dataObj, -1, 0) == -1) { + /* in case of an error reading the file */ + Tcl_DecrRefCount(dataObj); + Tcl_SetObjResult(interp, Tcl_NewStringObj("read error", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "READ_ERROR", NULL); + return TCL_ERROR; + } + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); + Tcl_DecrRefCount(dataObj); + if (nsvgImage == NULL) { + return TCL_ERROR; + } + } + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); +} + +/* + *---------------------------------------------------------------------- + * + * StringMatchSVG -- + * + * This function is invoked by the photo image type to see if a string + * contains image data in SVG format. + * + * Results: + * The return value is >0 if the file can be successfully parsed, + * and 0 otherwise. + * + * Side effects: + * The file is saved in the internal cache for further use. + * + *---------------------------------------------------------------------- + */ + +static int +StringMatchSVG( + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage; + + CleanCache(interp); + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + if (nsvgImage != NULL) { + *widthPtr = (int) ceil(nsvgImage->width * ropts.scale); + *heightPtr = (int) ceil(nsvgImage->height * ropts.scale); + if ((*widthPtr <= 0) || (*heightPtr <= 0)) { + nsvgDelete(nsvgImage); + return 0; + } + if (!CacheSVG(interp, dataObj, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * StringReadSVG -- + * + * This function is called by the photo image type to read SVG format + * data from a string and write it into a given photo image. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * Side effects: + * New data is added to the image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +StringReadSVG( + Tcl_Interp *interp, + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, dataObj, formatObj, &ropts); + + if (nsvgImage == NULL) { + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); + } + if (nsvgImage == NULL) { + return TCL_ERROR; + } + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); +} + +/* + *---------------------------------------------------------------------- + * + * ParseSVGWithOptions -- + * + * This function is called to parse the given input string as SVG. + * + * Results: + * Return a newly create NSVGimage on success, and NULL otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static NSVGimage * +ParseSVGWithOptions( + Tcl_Interp *interp, + const char *input, + int length, + Tcl_Obj *formatObj, + RastOpts *ropts) +{ + Tcl_Obj **objv = NULL; + int objc = 0; + double dpi = 96.0; + char unit[3], *p; + char *inputCopy = NULL; + NSVGimage *nsvgImage; + static const char *const fmtOptions[] = { + "-dpi", "-scale", "-unit", NULL + }; + enum fmtOptions { + OPT_DPI, OPT_SCALE, OPT_UNIT + }; + + /* + * The parser destroys the original input string, + * therefore first duplicate. + */ + + inputCopy = attemptckalloc(length+1); + if (inputCopy == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc data buffer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + goto error; + } + memcpy(inputCopy, input, length); + inputCopy[length] = '\0'; + + /* + * Process elements of format specification as a list. + */ + + strcpy(unit, "px"); + ropts->x = ropts->y = 0.0; + ropts->scale = 1.0; + if ((formatObj != NULL) && + Tcl_ListObjGetElements(interp, formatObj, &objc, &objv) != TCL_OK) { + goto error; + } + for (; objc > 0 ; objc--, objv++) { + int optIndex; + + /* + * Ignore the "svg" part of the format specification. + */ + + if (!strcasecmp(Tcl_GetString(objv[0]), "svg")) { + continue; + } + + if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions, + sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) { + goto error; + } + + if (objc < 2) { + ckfree(inputCopy); + inputCopy = NULL; + Tcl_WrongNumArgs(interp, 1, objv, "value"); + goto error; + } + + objc--; + objv++; + + switch ((enum fmtOptions) optIndex) { + case OPT_DPI: + if (Tcl_GetDoubleFromObj(interp, objv[0], &dpi) == TCL_ERROR) { + goto error; + } + if (dpi < 0.0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "-dpi value must be positive", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_DPI", + NULL); + goto error; + } + break; + case OPT_SCALE: + if (Tcl_GetDoubleFromObj(interp, objv[0], &ropts->scale) == + TCL_ERROR) { + goto error; + } + if (ropts->scale <= 0.0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "-scale value must be positive", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", + NULL); + goto error; + } + break; + case OPT_UNIT: + p = Tcl_GetString(objv[0]); + if ((p != NULL) && (p[0])) { + strncpy(unit, p, 3); + unit[2] = '\0'; + } + break; + } + } + + nsvgImage = nsvgParse(inputCopy, unit, (float) dpi); + if (nsvgImage == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot parse SVG image", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "PARSE_ERROR", NULL); + goto error; + } + ckfree(inputCopy); + return nsvgImage; + +error: + if (inputCopy != NULL) { + ckfree(inputCopy); + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * RasterizeSVG -- + * + * This function is called to rasterize the given nsvgImage and + * fill the imageHandle with data. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * + * Side effects: + * On error the given nsvgImage will be deleted. + * + *---------------------------------------------------------------------- + */ + +static int +RasterizeSVG( + Tcl_Interp *interp, + Tk_PhotoHandle imageHandle, + NSVGimage *nsvgImage, + int destX, int destY, + int width, int height, + int srcX, int srcY, + RastOpts *ropts) +{ + int w, h, c; + NSVGrasterizer *rast; + unsigned char *imgData; + Tk_PhotoImageBlock svgblock; + + w = (int) ceil(nsvgImage->width * ropts->scale); + h = (int) ceil(nsvgImage->height * ropts->scale); + rast = nsvgCreateRasterizer(); + if (rast == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot initialize rasterizer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "RASTERIZER_ERROR", + NULL); + goto cleanAST; + } + imgData = attemptckalloc(w * h *4); + if (imgData == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc image buffer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + goto cleanRAST; + } + nsvgRasterize(rast, nsvgImage, (float) ropts->x, (float) ropts->y, + (float) ropts->scale, imgData, w, h, w * 4); + /* transfer the data to a photo block */ + svgblock.pixelPtr = imgData; + svgblock.width = w; + svgblock.height = h; + svgblock.pitch = w * 4; + svgblock.pixelSize = 4; + for (c = 0; c <= 3; c++) { + svgblock.offset[c] = c; + } + if (Tk_PhotoExpand(interp, imageHandle, + destX + width, destY + height) != TCL_OK) { + goto cleanRAST; + } + if (Tk_PhotoPutBlock(interp, imageHandle, &svgblock, destX, destY, + width, height, TK_PHOTO_COMPOSITE_SET) != TCL_OK) { + goto cleanimg; + } + ckfree(imgData); + nsvgDeleteRasterizer(rast); + nsvgDelete(nsvgImage); + return TCL_OK; + +cleanimg: + ckfree(imgData); + +cleanRAST: + nsvgDeleteRasterizer(rast); + +cleanAST: + nsvgDelete(nsvgImage); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * GetCachePtr -- + * + * This function is called to get the per interpreter used + * svg image cache. + * + * Results: + * Return a pointer to the used cache. + * + * Side effects: + * Initialize the cache on the first call. + * + *---------------------------------------------------------------------- + */ + +static NSVGcache * +GetCachePtr( + Tcl_Interp *interp +) { + NSVGcache *cachePtr = Tcl_GetAssocData(interp, "tksvgnano", NULL); + if (cachePtr == NULL) { + cachePtr = ckalloc(sizeof(NSVGcache)); + cachePtr->dataOrChan = NULL; + Tcl_DStringInit(&cachePtr->formatString); + cachePtr->nsvgImage = NULL; + Tcl_SetAssocData(interp, "tksvgnano", FreeCache, cachePtr); + } + return cachePtr; +} + +/* + *---------------------------------------------------------------------- + * + * CacheSVG -- + * + * Add the given svg image informations to the cache for further usage. + * + * Results: + * Return 1 on success, and 0 otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static int +CacheSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + NSVGimage *nsvgImage, + RastOpts *ropts) +{ + int length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); + + if (cachePtr != NULL) { + cachePtr->dataOrChan = dataOrChan; + if (formatObj != NULL) { + data = Tcl_GetStringFromObj(formatObj, &length); + Tcl_DStringAppend(&cachePtr->formatString, data, length); + } + cachePtr->nsvgImage = nsvgImage; + cachePtr->ropts = *ropts; + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * GetCachedSVG -- + * + * Try to get the NSVGimage from the internal cache. + * + * Results: + * Return the found NSVGimage on success, and NULL otherwise. + * + * Side effects: + * Calls the CleanCache() function. + * + *---------------------------------------------------------------------- + */ + +static NSVGimage * +GetCachedSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + RastOpts *ropts) +{ + int length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); + NSVGimage *nsvgImage = NULL; + + if ((cachePtr != NULL) && (cachePtr->nsvgImage != NULL) && + (cachePtr->dataOrChan == dataOrChan)) { + if (formatObj != NULL) { + data = Tcl_GetStringFromObj(formatObj, &length); + if (strcmp(data, Tcl_DStringValue(&cachePtr->formatString)) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; + } + } else if (Tcl_DStringLength(&cachePtr->formatString) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; + } + } + CleanCache(interp); + return nsvgImage; +} + +/* + *---------------------------------------------------------------------- + * + * CleanCache -- + * + * Reset the cache and delete the saved image in it. + * + * Results: + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static void +CleanCache(Tcl_Interp *interp) +{ + NSVGcache *cachePtr = GetCachePtr(interp); + + if (cachePtr != NULL) { + cachePtr->dataOrChan = NULL; + Tcl_DStringSetLength(&cachePtr->formatString, 0); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); + cachePtr->nsvgImage = NULL; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FreeCache -- + * + * This function is called to clean up the internal cache data. + * + * Results: + * + * Side effects: + * Existing image data in the cache and the cache will be deleted. + * + *---------------------------------------------------------------------- + */ + +static void +FreeCache(ClientData clientData, Tcl_Interp *interp) +{ + NSVGcache *cachePtr = clientData; + + Tcl_DStringFree(&cachePtr->formatString); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); + } + ckfree(cachePtr); +} + diff --git a/generic/tkInt.h b/generic/tkInt.h index 767bbbb..e94dcad 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -344,6 +344,9 @@ typedef struct TkDisplay { * by that master. */ int geomInit; +#define TkGetGeomMaster(tkwin) (((TkWindow *)tkwin)->maintainerPtr != NULL ? \ + ((TkWindow *)tkwin)->maintainerPtr : ((TkWindow *)tkwin)->parentPtr) + /* * Information used by tkGet.c only: */ @@ -837,10 +840,14 @@ typedef struct TkWindow { int minReqWidth; /* Minimum requested width. */ int minReqHeight; /* Minimum requested height. */ - char *geometryMaster; #ifdef TK_USE_INPUT_METHODS int ximGeneration; /* Used to invalidate XIC */ #endif /* TK_USE_INPUT_METHODS */ + char *geomMgrName; /* Records the name of the geometry manager. */ + struct TkWindow *maintainerPtr; + /* The geometry master for this window. The + * value is NULL if the window has no master or + * if its master is its parent. */ } TkWindow; /* @@ -979,6 +986,7 @@ MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtDefault; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPNG; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPPM; +MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtSVGnano; MODULE_SCOPE TkMainInfo *tkMainWindowList; MODULE_SCOPE Tk_ImageType tkPhotoImageType; MODULE_SCOPE Tcl_HashTable tkPredefBitmapTable; @@ -1254,11 +1262,16 @@ MODULE_SCOPE int TkInitTkCmd(Tcl_Interp *interp, ClientData clientData); MODULE_SCOPE int TkInitFontchooser(Tcl_Interp *interp, ClientData clientData); +MODULE_SCOPE void TkInitEmbeddedConfigurationInformation( + Tcl_Interp *interp); MODULE_SCOPE void TkpWarpPointer(TkDisplay *dispPtr); MODULE_SCOPE void TkpCancelWarp(TkDisplay *dispPtr); MODULE_SCOPE int TkListCreateFrame(ClientData clientData, Tcl_Interp *interp, Tcl_Obj *listObj, int toplevel, Tcl_Obj *nameObj); +MODULE_SCOPE void TkRotatePoint(double originX, double originY, + double sine, double cosine, double *xPtr, + double *yPtr); #ifdef _WIN32 #define TkParseColor XParseColor @@ -1279,12 +1292,17 @@ MODULE_SCOPE void TkUnixSetXftClipRegion(TkRegion clipRegion); MODULE_SCOPE size_t TkUniCharToUtf(int, char *); #endif +#if TCL_MAJOR_VERSION > 8 #define TkGetStringFromObj(objPtr, lenPtr) \ (((objPtr)->bytes ? 0 : Tcl_GetString(objPtr)), \ *(lenPtr) = (objPtr)->length, (objPtr)->bytes) - MODULE_SCOPE unsigned char *TkGetByteArrayFromObj(Tcl_Obj *objPtr, size_t *lengthPtr); +#else +#define TkGetStringFromObj Tcl_GetStringFromObj +#define TkGetByteArrayFromObj Tcl_GetByteArrayFromObj +#endif + /* * Unsupported commands. diff --git a/generic/tkListbox.c b/generic/tkListbox.c index f722674..64026b1 100644 --- a/generic/tkListbox.c +++ b/generic/tkListbox.c @@ -1103,7 +1103,7 @@ ListboxBboxSubCmd( Tcl_Obj *el, *results[4]; const char *stringRep; int pixelWidth, x, y, result; - size_t stringLen; + TkSizeT stringLen; Tk_FontMetrics fm; /* @@ -1842,7 +1842,7 @@ DisplayListbox( register Tk_Window tkwin = listPtr->tkwin; GC gc; int i, limit, x, y, prevSelected, freeGC; - size_t stringLen; + TkSizeT stringLen; Tk_FontMetrics fm; Tcl_Obj *curElement; Tcl_HashEntry *entry; @@ -2239,7 +2239,7 @@ ListboxComputeGeometry( * window. */ { int width, height, pixelWidth, pixelHeight, i, result; - size_t textLength; + TkSizeT textLength; Tk_FontMetrics fm; Tcl_Obj *element; const char *text; @@ -2327,7 +2327,7 @@ ListboxInsertSubCmd( Tcl_Obj *const objv[]) /* New elements (one per entry). */ { int i, oldMaxWidth, pixelWidth, result; - size_t length; + TkSizeT length; Tcl_Obj *newListObj; const char *stringRep; @@ -2442,7 +2442,7 @@ ListboxDeleteSubCmd( int last) /* Index of last element to delete. */ { int count, i, widthChanged, result, pixelWidth; - size_t length; + TkSizeT length; Tcl_Obj *newListObj, *element; const char *stringRep; Tcl_HashEntry *entry; @@ -3128,7 +3128,7 @@ ListboxFetchSelection( register Listbox *listPtr = clientData; Tcl_DString selection; int count, needNewline, i; - size_t length, stringLen; + TkSizeT length, stringLen; Tcl_Obj *curElement; const char *stringRep; Tcl_HashEntry *entry; @@ -3166,7 +3166,7 @@ ListboxFetchSelection( * Copy the requested portion of the selection to the buffer. */ - if (length <= (size_t)offset) { + if (length <= (TkSizeT)offset) { count = 0; } else { count = length - offset; diff --git a/generic/tkPack.c b/generic/tkPack.c index c1b6345..faa3533 100644 --- a/generic/tkPack.c +++ b/generic/tkPack.c @@ -1182,7 +1182,7 @@ PackAfter( packPtr->flags |= OLD_STYLE; for (index = 0 ; index < optionCount; index++) { Tcl_Obj *curOptPtr = options[index]; - size_t length; + TkSizeT length; const char *curOpt = TkGetStringFromObj(curOptPtr, &length); c = curOpt[0]; @@ -1240,7 +1240,7 @@ PackAfter( packPtr->iPadY = 0; index++; } else if ((c == 'f') && (length > 1) - && (strncmp(curOpt, "frame", (size_t) length) == 0)) { + && (strncmp(curOpt, "frame", length) == 0)) { if (optionCount < (index+2)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "wrong # args: \"frame\"" @@ -1540,6 +1540,7 @@ ConfigureSlaves( { Packer *masterPtr, *slavePtr, *prevPtr, *otherPtr; Tk_Window other, slave, parent, ancestor; + TkWindow *master; int i, j, numWindows, tmp, positionGiven; const char *string; static const char *const optionStrings[] = { @@ -1817,6 +1818,24 @@ ConfigureSlaves( } /* + * Check for management loops. + */ + + for (master = (TkWindow *)masterPtr->tkwin; master != NULL; + master = (TkWindow *)TkGetGeomMaster(master)) { + if (master == (TkWindow *)slave) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't put %s inside %s, would cause management loop", + Tcl_GetString(objv[j]), Tk_PathName(masterPtr->tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); + return TCL_ERROR; + } + } + if (masterPtr->tkwin != Tk_Parent(slave)) { + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)masterPtr->tkwin; + } + + /* * Unpack the slave if it's currently packed, then position it after * prevPtr. */ diff --git a/generic/tkPanedWindow.c b/generic/tkPanedWindow.c index b86df0a..c605bf3 100644 --- a/generic/tkPanedWindow.c +++ b/generic/tkPanedWindow.c @@ -1011,7 +1011,7 @@ ConfigureSlaves( i = sizeof(Slave *) * (pwPtr->numSlaves + numNewSlaves); newSlaves = ckalloc(i); - memset(newSlaves, 0, (size_t) i); + memset(newSlaves, 0, i); if (index == -1) { /* * If none of the existing slaves have to be moved, just copy the old diff --git a/generic/tkPkgConfig.c b/generic/tkPkgConfig.c new file mode 100644 index 0000000..ac0583f --- /dev/null +++ b/generic/tkPkgConfig.c @@ -0,0 +1,166 @@ +/* + * tkPkgConfig.c -- + * + * This file contains the configuration information to embed into the tcl + * binary library. + * + * Copyright (c) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net> + * Copyright (c) 2017 Stuart Cassoff <stwo@users.sourceforge.net> + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +/* Note, the definitions in this module are influenced by the following C + * preprocessor macros: + * + * OSCMa = shortcut for "old style configuration macro activates" + * NSCMdt = shortcut for "new style configuration macro declares that" + * + * - TCL_THREADS OSCMa compilation as threaded. + * - TCL_MEM_DEBUG OSCMa memory debugging. + * + * - TCL_CFG_DO64BIT NSCMdt tk is compiled for a 64bit system. + * - NDEBUG NSCMdt tk is compiled with symbol info off. + * - TCL_CFG_OPTIMIZED NSCMdt tk is compiled with cc optimizations on + * - TCL_CFG_PROFILED NSCMdt tk is compiled with profiling info. + * + * - _WIN32 || __CYGWIN__ The value for the fontsytem key will be + * MAC_OSX_TK chosen based on these macros/defines. + * HAVE_XFT NSCMdt xft font support was requested. + * + * - CFG_RUNTIME_* Paths to various stuff at runtime. + * - CFG_INSTALL_* Paths to various stuff at installation time. + * + * - TCL_CFGVAL_ENCODING string containing the encoding used for the + * configuration values. + */ + +#include "tkInt.h" + + +#ifndef TCL_CFGVAL_ENCODING +#define TCL_CFGVAL_ENCODING "ascii" +#endif + +/* + * Use C preprocessor statements to define the various values for the embedded + * configuration information. + */ + +#ifdef TCL_THREADS +# define CFG_THREADED "1" +#else +# define CFG_THREADED "0" +#endif + +#ifdef TCL_MEM_DEBUG +# define CFG_MEMDEBUG "1" +#else +# define CFG_MEMDEBUG "0" +#endif + +#ifdef TCL_CFG_DO64BIT +# define CFG_64 "1" +#else +# define CFG_64 "0" +#endif + +#ifndef NDEBUG +# define CFG_DEBUG "1" +#else +# define CFG_DEBUG "0" +#endif + +#ifdef TCL_CFG_OPTIMIZED +# define CFG_OPTIMIZED "1" +#else +# define CFG_OPTIMIZED "0" +#endif + +#ifdef TCL_CFG_PROFILED +# define CFG_PROFILED "1" +#else +# define CFG_PROFILED "0" +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +# define CFG_FONTSYSTEM "gdi" +#elif defined(MAC_OSX_TK) +# define CFG_FONTSYSTEM "cocoa" +#elif defined(HAVE_XFT) +# define CFG_FONTSYSTEM "xft" +#else +# define CFG_FONTSYSTEM "x11" +#endif + +static Tcl_Config const cfg[] = { + {"debug", CFG_DEBUG}, + {"threaded", CFG_THREADED}, + {"profiled", CFG_PROFILED}, + {"64bit", CFG_64}, + {"optimized", CFG_OPTIMIZED}, + {"mem_debug", CFG_MEMDEBUG}, + {"fontsystem", CFG_FONTSYSTEM}, + + /* Runtime paths to various stuff */ + +#ifdef CFG_RUNTIME_LIBDIR + {"libdir,runtime", CFG_RUNTIME_LIBDIR}, +#endif +#ifdef CFG_RUNTIME_BINDIR + {"bindir,runtime", CFG_RUNTIME_BINDIR}, +#endif +#ifdef CFG_RUNTIME_SCRDIR + {"scriptdir,runtime", CFG_RUNTIME_SCRDIR}, +#endif +#ifdef CFG_RUNTIME_INCDIR + {"includedir,runtime", CFG_RUNTIME_INCDIR}, +#endif +#ifdef CFG_RUNTIME_DOCDIR + {"docdir,runtime", CFG_RUNTIME_DOCDIR}, +#endif +#ifdef CFG_RUNTIME_DEMODIR + {"demodir,runtime", CFG_RUNTIME_DEMODIR}, +#endif + + /* Installation paths to various stuff */ + +#ifdef CFG_INSTALL_LIBDIR + {"libdir,install", CFG_INSTALL_LIBDIR}, +#endif +#ifdef CFG_INSTALL_BINDIR + {"bindir,install", CFG_INSTALL_BINDIR}, +#endif +#ifdef CFG_INSTALL_SCRDIR + {"scriptdir,install", CFG_INSTALL_SCRDIR}, +#endif +#ifdef CFG_INSTALL_INCDIR + {"includedir,install", CFG_INSTALL_INCDIR}, +#endif +#ifdef CFG_INSTALL_DOCDIR + {"docdir,install", CFG_INSTALL_DOCDIR}, +#endif +#ifdef CFG_INSTALL_DEMODIR + {"demodir,install", CFG_INSTALL_DEMODIR}, +#endif + + /* Last entry, closes the array */ + {NULL, NULL} +}; + +void +TkInitEmbeddedConfigurationInformation( + Tcl_Interp *interp) /* Interpreter the configuration command is + * registered in. */ +{ + Tcl_RegisterConfig(interp, "tk", cfg, TCL_CFGVAL_ENCODING); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/generic/tkPlace.c b/generic/tkPlace.c index 44eac5d..f6be0e5 100644 --- a/generic/tkPlace.c +++ b/generic/tkPlace.c @@ -617,6 +617,7 @@ ConfigureSlave( int mask; Slave *slavePtr; Tk_Window masterWin = (Tk_Window) NULL; + TkWindow *master; if (Tk_TopWinHierarchy(tkwin)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -694,6 +695,25 @@ ConfigureSlave( Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); goto error; } + + /* + * Check for management loops. + */ + + for (master = (TkWindow *)tkwin; master != NULL; + master = (TkWindow *)TkGetGeomMaster(master)) { + if (master == (TkWindow *)slavePtr->tkwin) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't put %s inside %s, would cause management loop", + Tk_PathName(slavePtr->tkwin), Tk_PathName(tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); + goto error; + } + } + if (tkwin != Tk_Parent(slavePtr->tkwin)) { + ((TkWindow *)slavePtr->tkwin)->maintainerPtr = (TkWindow *)tkwin; + } + if ((slavePtr->masterPtr != NULL) && (slavePtr->masterPtr->tkwin == tkwin)) { /* diff --git a/generic/tkRectOval.c b/generic/tkRectOval.c index 58bb99a..507f072 100644 --- a/generic/tkRectOval.c +++ b/generic/tkRectOval.c @@ -148,6 +148,8 @@ static int RectToArea(Tk_Canvas canvas, Tk_Item *itemPtr, double *areaPtr); static double RectToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *pointPtr); +static void RotateRectOval(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleRectOval(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -180,7 +182,8 @@ Tk_ItemType tkRectangleType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateRectOval, /* rotateProc */ + 0, NULL, NULL }; Tk_ItemType tkOvalType = { @@ -204,7 +207,8 @@ Tk_ItemType tkOvalType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateRectOval, /* rotateProc */ + 0, NULL, NULL }; /* @@ -1285,6 +1289,57 @@ OvalToArea( /* *-------------------------------------------------------------- * + * RotateRectOval -- + * + * This function is invoked to rotate a rectangle or oval item's + * coordinates. It works by rotating a computed point in the centre of + * the bounding box, NOT by rotating the corners of the bounding box. + * + * Results: + * None. + * + * Side effects: + * The position of the rectangle or oval is rotated by angleRad about + * (originX, originY), and the bounding box is updated in the generic + * part of the item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateRectOval( + Tk_Canvas canvas, /* Canvas containing rectangle. */ + Tk_Item *itemPtr, /* Rectangle to be scaled. */ + double originX, double originY, + /* Origin about which to rotate rect. */ + double angleRad) /* Amount to scale in X direction. */ +{ + RectOvalItem *rectOvalPtr = (RectOvalItem *) itemPtr; + double newX, newY, oldX, oldY; + + /* + * Compute the centre of the box, then rotate that about the origin. + */ + + newX = oldX = (rectOvalPtr->bbox[0] + rectOvalPtr->bbox[2]) / 2.0; + newY = oldY = (rectOvalPtr->bbox[1] + rectOvalPtr->bbox[3]) / 2.0; + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &newX, &newY); + + /* + * Apply the translation to the box. + */ + + rectOvalPtr->bbox[0] += newX - oldX; + rectOvalPtr->bbox[1] += newY - oldY; + rectOvalPtr->bbox[2] += newX - oldX; + rectOvalPtr->bbox[3] += newY - oldY; + ComputeRectOvalBbox(canvas, rectOvalPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleRectOval -- * * This function is invoked to rescale a rectangle or oval item. diff --git a/generic/tkScrollbar.c b/generic/tkScrollbar.c index 02daafb..5b0f6af 100644 --- a/generic/tkScrollbar.c +++ b/generic/tkScrollbar.c @@ -227,7 +227,7 @@ ScrollbarWidgetObjCmd( { register TkScrollbar *scrollPtr = clientData; int result = TCL_OK, cmdIndex; - size_t length; + TkSizeT length; static const char *const commandNames[] = { "activate", "cget", "configure", "delta", "fraction", "get", "identify", "set", NULL diff --git a/generic/tkSelect.c b/generic/tkSelect.c index 513cf75..85865de 100644 --- a/generic/tkSelect.c +++ b/generic/tkSelect.c @@ -831,7 +831,7 @@ Tk_SelectionObjCmd( const char *targetName = NULL; const char *formatName = NULL; register CommandInfo *cmdInfoPtr; - size_t cmdLength; + TkSizeT cmdLength; static const char *const handleOptionStrings[] = { "-format", "-selection", "-type", NULL }; @@ -1387,7 +1387,7 @@ HandleTclCommand( string = Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &length); count = (length > maxBytes) ? maxBytes : length; - memcpy(buffer, string, (size_t) count); + memcpy(buffer, string, count); buffer[count] = '\0'; /* @@ -1410,7 +1410,7 @@ HandleTclCommand( cmdInfoPtr->charOffset += numChars; length = p - string; if (length > 0) { - strncpy(cmdInfoPtr->buffer, string, (size_t) length); + strncpy(cmdInfoPtr->buffer, string, length); } cmdInfoPtr->buffer[length] = '\0'; } diff --git a/generic/tkText.c b/generic/tkText.c index c748015..acd0ed3 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -864,7 +864,7 @@ TextWidgetObjCmd( for (i = 2; i < objc-2; i++) { int value; - size_t length; + TkSizeT length; const char *option = TkGetStringFromObj(objv[i], &length); char c; @@ -1259,7 +1259,7 @@ TextWidgetObjCmd( Tcl_Obj *objPtr = NULL; int i, found = 0, visible = 0; const char *name; - size_t length; + TkSizeT length; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, @@ -2631,7 +2631,7 @@ InsertChars( int viewUpdate) /* Update the view if set. */ { int lineIndex; - size_t length; + TkSizeT length; TkText *tPtr; int *lineAndByteIndex; int resetViewCount; @@ -3509,7 +3509,7 @@ TextFetchSelection( if ((segPtr->typePtr == &tkTextCharType) && !TkTextIsElided(textPtr, &textPtr->selIndex, NULL)) { memcpy(buffer, segPtr->body.chars + offsetInSeg, - (size_t) chunkSize); + chunkSize); buffer += chunkSize; maxBytes -= chunkSize; count += chunkSize; @@ -4273,7 +4273,7 @@ TextSearchFoundMatch( int matchLength) /* Length also in bytes/chars as per search * type. */ { - size_t numChars; + TkSizeT numChars; int leftToScan; TkTextIndex curIndex, foundIndex; TkTextSegment *segPtr; @@ -4313,7 +4313,7 @@ TextSearchFoundMatch( if (searchSpecPtr->strictLimits && lineNum == searchSpecPtr->stopLine) { if (searchSpecPtr->backwards ^ - ((matchOffset + numChars + 1) > (size_t) searchSpecPtr->stopOffset + 1)) { + ((matchOffset + numChars + 1) > (TkSizeT) searchSpecPtr->stopOffset + 1)) { return 0; } } @@ -4753,7 +4753,7 @@ TextDumpCmd( if (objc == arg) { TkTextIndexForwChars(NULL, &index1, 1, &index2, COUNT_INDICES); } else { - size_t length; + TkSizeT length; const char *str; if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) { @@ -5760,7 +5760,7 @@ SearchCore( */ int firstOffset, lastOffset; - size_t matchOffset, matchLength; + TkSizeT matchOffset, matchLength; int passes; int lineNum = searchSpecPtr->startLine; int code = TCL_OK; @@ -5781,9 +5781,9 @@ SearchCore( #define LOTS_OF_MATCHES 20 int matchNum = LOTS_OF_MATCHES; - size_t smArray[2 * LOTS_OF_MATCHES]; - size_t *storeMatch = smArray; - size_t *storeLength = smArray + LOTS_OF_MATCHES; + TkSizeT smArray[2 * LOTS_OF_MATCHES]; + TkSizeT *storeMatch = smArray; + TkSizeT *storeLength = smArray + LOTS_OF_MATCHES; int lastBackwardsLineMatch = -1; int lastBackwardsMatchOffset = -1; @@ -5968,11 +5968,11 @@ SearchCore( do { int ch; const char *p; - size_t lastFullLine = lastOffset; + TkSizeT lastFullLine = lastOffset; if (firstNewLine == -1) { if (searchSpecPtr->strictLimits - && (firstOffset + matchLength + 1 > (size_t)lastOffset + 1)) { + && (firstOffset + matchLength + 1 > (TkSizeT)lastOffset + 1)) { /* * Not enough characters to match. */ @@ -6090,7 +6090,7 @@ SearchCore( * exact searches. */ - if ((size_t)lastTotal - skipFirst + 1 >= matchLength + 1) { + if ((TkSizeT)lastTotal - skipFirst + 1 >= matchLength + 1) { /* * We now have enough text to match, so we * make a final test and break whatever the @@ -6172,7 +6172,7 @@ SearchCore( } } else { firstOffset = matchLength ? p - startOfLine + matchLength - : p - startOfLine + (size_t)1; + : p - startOfLine + (TkSizeT)1; if (firstOffset >= lastOffset) { /* * Now, we have to be careful not to find @@ -6212,7 +6212,7 @@ SearchCore( do { Tcl_RegExpInfo info; int match; - size_t lastFullLine = lastOffset; + TkSizeT lastFullLine = lastOffset; match = Tcl_RegExpExecObj(interp, regexp, theLine, firstOffset, 1, (firstOffset>0 ? TCL_REG_NOTBOL : 0)); @@ -6230,9 +6230,9 @@ SearchCore( if (!match || ((info.extendStart == info.matches[0].start) - && ((size_t) info.matches[0].end == (size_t) lastOffset - firstOffset))) { + && ((TkSizeT) info.matches[0].end == (TkSizeT) lastOffset - firstOffset))) { int extraLines = 0; - size_t prevFullLine; + TkSizeT prevFullLine; /* * If we find a match that overlaps more than one line, we @@ -6248,7 +6248,7 @@ SearchCore( lastNonOverlap = lastTotal; } - if ((size_t) info.extendStart == (size_t) -1) { + if ((TkSizeT) info.extendStart == TCL_AUTO_LENGTH) { /* * No multi-line match is possible. */ @@ -6345,9 +6345,9 @@ SearchCore( */ if ((match && - firstOffset+(size_t) info.matches[0].end != (size_t) lastTotal && - firstOffset+(size_t) info.matches[0].end + 1 < prevFullLine + 1) - || (size_t) info.extendStart == (size_t) -1) { + firstOffset + (TkSizeT) info.matches[0].end != (TkSizeT) lastTotal && + firstOffset + (TkSizeT) info.matches[0].end + 1 < prevFullLine + 1) + || (TkSizeT) info.extendStart == TCL_AUTO_LENGTH) { break; } @@ -6358,10 +6358,10 @@ SearchCore( * that line. */ - if (match && ((size_t) info.matches[0].start + 1 >= (size_t) lastOffset + 1)) { + if (match && ((TkSizeT) info.matches[0].start + 1 >= (TkSizeT) lastOffset + 1)) { break; } - if (match && ((firstOffset + (size_t) info.matches[0].end) + if (match && ((firstOffset + (TkSizeT) info.matches[0].end) >= prevFullLine)) { if (extraLines > 0) { extraLinesSearched = extraLines - 1; @@ -6415,7 +6415,7 @@ SearchCore( * Possible overlap or enclosure. */ - if ((size_t)thisOffset - lastNonOverlap >= + if ((TkSizeT)thisOffset - lastNonOverlap >= lastBackwardsMatchOffset + matchLength + 1){ /* * Totally encloses previous match, so @@ -6497,11 +6497,11 @@ SearchCore( * previous match. */ - if (matchOffset == (size_t)-1 || + if (matchOffset == TCL_AUTO_LENGTH || ((searchSpecPtr->all || searchSpecPtr->backwards) - && (((size_t)firstOffset + 1< matchOffset + 1) - || ((firstOffset + (size_t) info.matches[0].end - - (size_t) info.matches[0].start) + && (((TkSizeT)firstOffset + 1< matchOffset + 1) + || ((firstOffset + (TkSizeT) info.matches[0].end + - (TkSizeT) info.matches[0].start) > matchOffset + matchLength)))) { matchOffset = firstOffset; @@ -6520,11 +6520,11 @@ SearchCore( * matches on the heap. */ - size_t *newArray = - ckalloc(4 * matchNum * sizeof(size_t)); - memcpy(newArray, storeMatch, matchNum*sizeof(size_t)); + TkSizeT *newArray = + ckalloc(4 * matchNum * sizeof(TkSizeT)); + memcpy(newArray, storeMatch, matchNum*sizeof(TkSizeT)); memcpy(newArray + 2*matchNum, storeLength, - matchNum * sizeof(int)); + matchNum * sizeof(TkSizeT)); if (storeMatch != smArray) { ckfree(storeMatch); } @@ -6665,7 +6665,7 @@ SearchCore( * we are done. */ - if ((lastBackwardsLineMatch == -1) && (matchOffset != (size_t) -1) + if ((lastBackwardsLineMatch == -1) && (matchOffset != TCL_AUTO_LENGTH) && !searchSpecPtr->all) { searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr, lineInfo, theLine, matchOffset, matchLength); @@ -6940,7 +6940,7 @@ TkpTesttextCmd( Tcl_Obj *const objv[]) /* Argument strings. */ { TkText *textPtr; - size_t len; + TkSizeT len; int lineIndex, byteIndex, byteOffset; TkTextIndex index; char buf[64]; diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index 1f44d37..d4f6b83 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -6153,7 +6153,7 @@ TkTextYviewCmd( TextDInfo *dInfoPtr = textPtr->dInfoPtr; int pickPlace, type; int pixels, count; - size_t switchLength; + TkSizeT switchLength; double fraction; TkTextIndex index; @@ -6172,7 +6172,7 @@ TkTextYviewCmd( pickPlace = 0; if (Tcl_GetString(objv[2])[0] == '-') { - register const char *switchStr = + const char *switchStr = TkGetStringFromObj(objv[2], &switchLength); if ((switchLength >= 2) && (strncmp(switchStr, "-pickplace", diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c index 4b773cb..aca08fb 100644 --- a/generic/tkTextMark.c +++ b/generic/tkTextMark.c @@ -126,7 +126,7 @@ TkTextMarkCmd( switch ((enum markOptions) optionIndex) { case MARK_GRAVITY: { char c; - size_t length; + TkSizeT length; const char *str; if (objc < 4 || objc > 5) { diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c index 25e6af1..cb0993b 100644 --- a/generic/tkTextTag.c +++ b/generic/tkTextTag.c @@ -1108,7 +1108,7 @@ FindTag( Tcl_Obj *tagName) /* Name of desired tag. */ { Tcl_HashEntry *hPtr; - size_t len; + TkSizeT len; const char *str; str = TkGetStringFromObj(tagName, &len); diff --git a/generic/tkUtil.c b/generic/tkUtil.c index 6850f47..2950fe0 100644 --- a/generic/tkUtil.c +++ b/generic/tkUtil.c @@ -729,7 +729,7 @@ Tk_GetScrollInfoObj( int *intPtr) /* Filled in with number of pages or lines to * scroll, if any. */ { - size_t length; + TkSizeT length; const char *arg = TkGetStringFromObj(objv[2], &length); #define ArgPfxEq(str) \ @@ -1272,24 +1272,17 @@ size_t TkUniCharToUtf(int ch, char *buf) #endif +#if TCL_MAJOR_VERSION > 8 unsigned char * TkGetByteArrayFromObj( Tcl_Obj *objPtr, size_t *lengthPtr ) { - int length; - - unsigned char *result = Tcl_GetByteArrayFromObj(objPtr, &length); -#if TCL_MAJOR_VERSION > 8 - if (sizeof(TCL_HASH_TYPE) > sizeof(int)) { - /* 64-bit and TIP #494 situation: */ - *lengthPtr = *(TCL_HASH_TYPE *) objPtr->internalRep.twoPtrValue.ptr1; - } else -#endif - /* 32-bit or without TIP #494 */ - *lengthPtr = (size_t) (unsigned) length; + unsigned char *result = Tcl_GetByteArrayFromObj(objPtr, NULL); + *lengthPtr = *(size_t *) objPtr->internalRep.twoPtrValue.ptr1; return result; } +#endif /* TCL_MAJOR_VERSION > 8 */ /* * Local Variables: diff --git a/generic/tkWindow.c b/generic/tkWindow.c index d8c2068..6ad9477 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -340,6 +340,7 @@ CreateTopLevelWindow( Tk_CreatePhotoImageFormat(&tkImgFmtGIF); Tk_CreatePhotoImageFormat(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); + Tk_CreatePhotoImageFormat(&tkImgFmtSVGnano); } if ((parent != NULL) && (screenName != NULL) && (screenName[0] == '\0')) { @@ -665,6 +666,8 @@ TkAllocWindow( winPtr->selHandlerList = NULL; winPtr->geomMgrPtr = NULL; winPtr->geomData = NULL; + winPtr->geomMgrName = NULL; + winPtr->maintainerPtr = NULL; winPtr->reqWidth = winPtr->reqHeight = 1; winPtr->internalBorderLeft = 0; winPtr->wmInfoPtr = NULL; @@ -676,7 +679,6 @@ TkAllocWindow( winPtr->internalBorderBottom = 0; winPtr->minReqWidth = 0; winPtr->minReqHeight = 0; - winPtr->geometryMaster = NULL; return winPtr; } @@ -1461,9 +1463,9 @@ Tk_DestroyWindow( TkOptionDeadWindow(winPtr); TkSelDeadWindow(winPtr); TkGrabDeadWindow(winPtr); - if (winPtr->geometryMaster != NULL) { - ckfree(winPtr->geometryMaster); - winPtr->geometryMaster = NULL; + if (winPtr->geomMgrName != NULL) { + ckfree(winPtr->geomMgrName); + winPtr->geomMgrName = NULL; } if (winPtr->mainPtr != NULL) { if (winPtr->pathName != NULL) { @@ -3065,6 +3067,12 @@ Initialize( } /* + * TIP #59: Make embedded configuration information available. + */ + + TkInitEmbeddedConfigurationInformation(interp); + + /* * Ensure that our obj-types are registered with the Tcl runtime. */ @@ -3202,7 +3210,7 @@ Initialize( */ { - size_t numBytes; + TkSizeT numBytes; const char *bytes = TkGetStringFromObj(nameObj, &numBytes); classObj = Tcl_NewStringObj(bytes, numBytes); diff --git a/generic/ttk/ttkEntry.c b/generic/ttk/ttkEntry.c index 444bc83..b592883 100644 --- a/generic/ttk/ttkEntry.c +++ b/generic/ttk/ttkEntry.c @@ -315,7 +315,7 @@ static char *EntryDisplayString(const char *showChar, int numChars) */ static void EntryUpdateTextLayout(Entry *entryPtr) { - size_t length; + TkSizeT length; char *text; Tk_FreeTextLayout(entryPtr->entry.textLayout); if ((entryPtr->entry.numChars != 0) || (entryPtr->entry.placeholderObj == NULL)) { @@ -1001,7 +1001,7 @@ static int EntryConfigure(Tcl_Interp *interp, void *recordPtr, int mask) Ttk_TraceHandle *vt = 0; if (mask & TEXTVAR_CHANGED) { - if (textVarName && *Tcl_GetString(textVarName)) { + if (textVarName && *Tcl_GetString(textVarName) != '\0') { vt = Ttk_TraceVariable(interp, textVarName,EntryTextVariableTrace,entryPtr); if (!vt) return TCL_ERROR; @@ -1362,7 +1362,7 @@ EntryIndex( int *indexPtr) /* Return value */ { # define EntryWidth(e) (Tk_Width(entryPtr->core.tkwin)) /* Not Right */ - size_t length; + TkSizeT length; const char *string = TkGetStringFromObj(indexObj, &length); if (strncmp(string, "end", length) == 0) { @@ -1403,6 +1403,7 @@ EntryIndex( *indexPtr = Tk_PointToChar(entryPtr->entry.textLayout, x - entryPtr->entry.layoutX, 0); + TtkUpdateScrollInfo(entryPtr->entry.xscrollHandle); if (*indexPtr < entryPtr->entry.xscroll.first) { *indexPtr = entryPtr->entry.xscroll.first; } @@ -1697,7 +1698,7 @@ static int EntryXViewCommand( if (EntryIndex(interp, entryPtr, objv[2], &newFirst) != TCL_OK) { return TCL_ERROR; } - TtkScrollTo(entryPtr->entry.xscrollHandle, newFirst); + TtkScrollTo(entryPtr->entry.xscrollHandle, newFirst, 1); return TCL_OK; } return TtkScrollviewCommand(interp, objc, objv, entryPtr->entry.xscrollHandle); diff --git a/generic/ttk/ttkScroll.c b/generic/ttk/ttkScroll.c index 184f5f2..47db6ac 100644 --- a/generic/ttk/ttkScroll.c +++ b/generic/ttk/ttkScroll.c @@ -181,6 +181,19 @@ void TtkScrollbarUpdateRequired(ScrollHandle h) h->flags |= SCROLL_UPDATE_REQUIRED; } +/* TtkUpdateScrollInfo -- + * Call the layoutProc to update the scroll info first, last, and total. + * Do it only if needed, that is when a redisplay is pending (which + * indicates scroll info are possibly out of date). + */ + +void TtkUpdateScrollInfo(ScrollHandle h) +{ + if (h->corePtr->flags & REDISPLAY_PENDING) { + h->corePtr->widgetSpec->layoutProc(h->corePtr); + } +} + /* TtkScrollviewCommand -- * Widget [xy]view command implementation. * @@ -193,7 +206,10 @@ int TtkScrollviewCommand( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle h) { Scrollable *s = h->scrollPtr; - int newFirst = s->first; + int newFirst; + + TtkUpdateScrollInfo(h); + newFirst = s->first; if (objc == 2) { Tcl_Obj *result[2]; @@ -226,15 +242,19 @@ int TtkScrollviewCommand( } } - TtkScrollTo(h, newFirst); + TtkScrollTo(h, newFirst, 0); return TCL_OK; } -void TtkScrollTo(ScrollHandle h, int newFirst) +void TtkScrollTo(ScrollHandle h, int newFirst, int updateScrollInfo) { Scrollable *s = h->scrollPtr; + if (updateScrollInfo) { + TtkUpdateScrollInfo(h); + } + if (newFirst >= s->total) newFirst = s->total - 1; if (newFirst > s->first && s->last >= s->total) /* don't scroll past end */ diff --git a/generic/ttk/ttkTheme.h b/generic/ttk/ttkTheme.h index f087ce3..e067337 100644 --- a/generic/ttk/ttkTheme.h +++ b/generic/ttk/ttkTheme.h @@ -29,9 +29,13 @@ extern "C" { * +++ Defaults for element option specifications. */ #define DEFAULT_FONT "TkDefaultFont" +#ifdef MAC_OSX_TK +#define DEFAULT_BACKGROUND "systemTextBackgroundColor" +#define DEFAULT_FOREGROUND "systemTextColor" +#else #define DEFAULT_BACKGROUND "#d9d9d9" #define DEFAULT_FOREGROUND "black" - +#endif /*------------------------------------------------------------------------ * +++ Widget states. * Keep in sync with stateNames[] in tkstate.c. diff --git a/generic/ttk/ttkTreeview.c b/generic/ttk/ttkTreeview.c index 0f720d1..f912956 100644 --- a/generic/ttk/ttkTreeview.c +++ b/generic/ttk/ttkTreeview.c @@ -2843,10 +2843,10 @@ static int TreeviewSeeCommand( */ rowNumber = RowNumber(tv, item); if (rowNumber < tv->tree.yscroll.first) { - TtkScrollTo(tv->tree.yscrollHandle, rowNumber); + TtkScrollTo(tv->tree.yscrollHandle, rowNumber, 1); } else if (rowNumber >= tv->tree.yscroll.last) { TtkScrollTo(tv->tree.yscrollHandle, - tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last)); + tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last), 1); } return TCL_OK; diff --git a/generic/ttk/ttkWidget.h b/generic/ttk/ttkWidget.h index 7158a78..e764c3a 100644 --- a/generic/ttk/ttkWidget.h +++ b/generic/ttk/ttkWidget.h @@ -195,7 +195,8 @@ MODULE_SCOPE void TtkFreeScrollHandle(ScrollHandle); MODULE_SCOPE int TtkScrollviewCommand( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle); -MODULE_SCOPE void TtkScrollTo(ScrollHandle, int newFirst); +MODULE_SCOPE void TtkUpdateScrollInfo(ScrollHandle h); +MODULE_SCOPE void TtkScrollTo(ScrollHandle, int newFirst, int updateScrollInfo); MODULE_SCOPE void TtkScrolled(ScrollHandle, int first, int last, int total); MODULE_SCOPE void TtkScrollbarUpdateRequired(ScrollHandle); |