summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
Diffstat (limited to 'generic')
-rwxr-xr-xgeneric/nanosvg.h3084
-rw-r--r--generic/nanosvgrast.h1467
-rw-r--r--generic/tk.h11
-rw-r--r--generic/tkButton.c2
-rw-r--r--generic/tkCanvArc.c95
-rw-r--r--generic/tkCanvBmap.c38
-rw-r--r--generic/tkCanvImg.c39
-rw-r--r--generic/tkCanvLine.c55
-rw-r--r--generic/tkCanvPoly.c47
-rw-r--r--generic/tkCanvText.c48
-rw-r--r--generic/tkCanvUtil.c38
-rw-r--r--generic/tkCanvWind.c39
-rw-r--r--generic/tkCanvas.c146
-rw-r--r--generic/tkClipboard.c2
-rw-r--r--generic/tkCmds.c2
-rw-r--r--generic/tkConfig.c2
-rw-r--r--generic/tkEntry.c2
-rw-r--r--generic/tkFileFilter.c6
-rw-r--r--generic/tkFont.c6
-rw-r--r--generic/tkFrame.c4
-rw-r--r--generic/tkGeometry.c30
-rw-r--r--generic/tkGrab.c2
-rw-r--r--generic/tkGrid.c25
-rw-r--r--generic/tkImgGIF.c4
-rw-r--r--generic/tkImgListFormat.c2
-rw-r--r--generic/tkImgPNG.c10
-rw-r--r--generic/tkImgPPM.c2
-rw-r--r--generic/tkImgPhInstance.c8
-rw-r--r--generic/tkImgPhoto.c8
-rw-r--r--generic/tkImgSVGnano.c698
-rw-r--r--generic/tkInt.h22
-rw-r--r--generic/tkListbox.c14
-rw-r--r--generic/tkPack.c23
-rw-r--r--generic/tkPanedWindow.c2
-rw-r--r--generic/tkPkgConfig.c166
-rw-r--r--generic/tkPlace.c20
-rw-r--r--generic/tkRectOval.c59
-rw-r--r--generic/tkScrollbar.c2
-rw-r--r--generic/tkSelect.c6
-rw-r--r--generic/tkText.c70
-rw-r--r--generic/tkTextDisp.c4
-rw-r--r--generic/tkTextMark.c2
-rw-r--r--generic/tkTextTag.c2
-rw-r--r--generic/tkUtil.c17
-rw-r--r--generic/tkWindow.c18
-rw-r--r--generic/ttk/ttkEntry.c9
-rw-r--r--generic/ttk/ttkScroll.c26
-rw-r--r--generic/ttk/ttkTheme.h6
-rw-r--r--generic/ttk/ttkTreeview.c4
-rw-r--r--generic/ttk/ttkWidget.h3
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);