Suspended plotter for the skaperfest
Dependencies: mbed HTTPServer EthernetNetIf FatFileSystemCpp
Diff: nanosvg.h
- Revision:
- 0:602ff2b2d41c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nanosvg.h Mon Aug 22 10:24:23 2022 +0000 @@ -0,0 +1,1674 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * Copyright (c) 2016 Renato Grottesi renato.grottesi@gmail.com + * + * 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/) + * + * Limitations: the transform attribute must come before the other attributes. + */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +// Lenght proportional to radius of a cubic bezier handle for 90deg arcs. +#define NSVG_KAPPA90 (0.5522847493f) +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_EPSILON (1e-12) +#define NSVG_MAX_XFORMS 64 + +typedef void (*nsvgMoveToCb)(float, float); +typedef void (*nsvgBeginPathCb)(); +typedef void (*nsvgEndPathCb)(); + +typedef struct NSVGxform +{ + float xform[6]; +} NSVGxform; + +typedef struct NSVGCommonAttrs +{ + float cx; + float cy; + float height; + float r; + float rx; + float ry; + float width; + float x; + float x1; + float x2; + float y; + float y1; + float y2; +} NSVGCommonAttrs; + +typedef struct NSVGparser +{ + NSVGxform xforms[NSVG_MAX_XFORMS]; + int xformCurrent; + float *pts; + int npts; + float sx; + float sy; + int cpts; + char pathFlag; + nsvgMoveToCb moveToCb; + float mtx; // move to last entries + float mty; + nsvgBeginPathCb beginPathCb; + nsvgEndPathCb endPathCb; + float width; + float height; + float scale; + float x0; + float y0; + NSVGCommonAttrs attrs; +} NSVGparser; + +static int nsvg__isspace(char c) +{ + return strchr(" \t\n\v\f\r", c) != 0; +} + +static int nsvg__isdigit(char c) +{ + return strchr("0123456789", c) != 0; +} + +static int nsvg__isnum(char c) +{ + return strchr("0123456789+-.eE", c) != 0; +} + +static 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__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]; +} + +static void nsvg__resetPath(NSVGparser *p) +{ + p->npts = 0; +} + +static void nsvg__addPoint(NSVGparser *p, float x, float y) +{ + if (p->npts + 1 > p->cpts) + { + p->cpts = p->cpts ? p->cpts * 2 : 8; + p->pts = (float *)realloc(p->pts, p->cpts * 2 * sizeof(float)); + if (!p->pts) + return; + } + p->pts[p->npts * 2 + 0] = x; + p->pts[p->npts * 2 + 1] = y; + p->npts++; +} + +static void nsvg__moveTo(NSVGparser *p, float x, float y) +{ + if (p->npts > 0) + { + p->pts[(p->npts - 1) * 2 + 0] = x; + p->pts[(p->npts - 1) * 2 + 1] = y; + } + else + { + nsvg__addPoint(p, x, y); + p->sx = x; + p->sy = 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 NSVGxform *nsvg__getCurrentXForm(NSVGparser *p) +{ + return &p->xforms[p->xformCurrent]; +} + +static void nsvg__pushAttr(NSVGparser *p) +{ + if (p->xformCurrent < NSVG_MAX_XFORMS - 1) + { + p->xformCurrent++; + memcpy(&p->xforms[p->xformCurrent], &p->xforms[p->xformCurrent - 1], sizeof(NSVGxform)); + } +} + +static void nsvg__popAttr(NSVGparser *p) +{ + if (p->xformCurrent > 0) + p->xformCurrent--; +} + +static float nsvg__distPtSeg(float x, float y, float px, float py, float qx, float qy) +{ + float pqx, pqy, dx, dy, d, t; + pqx = qx - px; + pqy = qy - py; + dx = x - px; + dy = y - py; + d = pqx * pqx + pqy * pqy; + t = pqx * dx + pqy * dy; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + dx = px + t * pqx - x; + dy = py + t * pqy - y; + return dx * dx + dy * dy; +} + +static void nsvg__cubicBez(NSVGparser *p, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tol, + int level) +{ + float x12, y12, x23, y23, x34, y34, x123, y123, x234, y234, x1234, y1234; + float d; + + if (level > 12) + 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; + x234 = (x23 + x34) * 0.5f; + y234 = (y23 + y34) * 0.5f; + x1234 = (x123 + x234) * 0.5f; + y1234 = (y123 + y234) * 0.5f; + + d = nsvg__distPtSeg(x1234, y1234, x1, y1, x4, y4); + if (d > tol * tol) + { + nsvg__cubicBez(p, x1, y1, x12, y12, x123, y123, x1234, y1234, tol, level + 1); + nsvg__cubicBez(p, x1234, y1234, x234, y234, x34, y34, x4, y4, tol, level + 1); + } + else + { + if (x4 > 0.0 && x4 < 1.0 && y4 > 0.0 && y4 < 4.0) + p->moveToCb(x4, y4); + p->mtx = x4; + p->mty = y4; + } +} + +static void nsvg__drawPath(NSVGparser *p, char closed) +{ + float pts[8]; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + + NSVGxform *xform = nsvg__getCurrentXForm(p); + int i; + + if (p->npts < 4) + return; + + if (closed) + nsvg__lineTo(p, p->sx, p->sy); + + float orig_x = 0.0; + float orig_y = 0.0; + + int idx = 0; + float norm_f = p->width > p->height ? p->width : p->height; + // Transform path. + for (i = 0; i < p->npts; ++i) + { + nsvg__xformPoint(&pts[(i * 2) % 8], &pts[(i * 2 + 1) % 8], p->pts[i * 2], p->pts[i * 2 + 1], xform->xform); + pts[(i * 2 + 0) % 8] = ((pts[(i * 2 + 0) % 8] / norm_f) * p->scale) + p->x0; + pts[(i * 2 + 1) % 8] = ((pts[(i * 2 + 1) % 8] / norm_f) * p->scale) + p->y0; + /* First point starts the drawing */ + if (i == 0) + { + orig_x = pts[0]; + orig_y = pts[1]; + if (p->mtx != orig_x || p->mty != orig_y) + { + p->endPathCb(); + + if (orig_x > 0.0 && orig_x < 1.0 && orig_y > 0.0 && orig_y < 4.0) + p->moveToCb(orig_x, orig_y); + p->mtx = orig_x; + p->mty = orig_y; + } + p->beginPathCb(); + } + + /* If we have at least 4 points */ + if (i >= 3) + { + /* A bezier every 3 points, linking with one from before */ + if ((i % 3) == 0) + { + nsvg__cubicBez(p, pts[(0 + idx) % 8], pts[(1 + idx) % 8], pts[(2 + idx) % 8], pts[(3 + idx) % 8], pts[(4 + idx) % 8], + pts[(5 + idx) % 8], pts[(6 + idx) % 8], pts[(7 + idx) % 8], 0.00015 * p->scale, 0); + idx = (idx + 8 - 2) % 8; + } + } + } + + if (closed) + { + p->mtx = orig_x; + p->mty = orig_y; + } + + return; +} + +static int nsvg__parseNumber(FILE *fp, char quote, int begin, char *it, const int size) +{ + const int last = size - 1; + int i = 0; + int s = begin; + + // sign + if (s == '-' || s == '+') + { + if (i < last) + it[i++] = s; + s = fgetc(fp); + } + // integer part + while (s != EOF && s != quote && nsvg__isdigit(s)) + { + if (i < last) + it[i++] = s; + s = fgetc(fp); + } + if (s == '.') + { + // decimal point + if (i < last) + it[i++] = s; + s = fgetc(fp); + // fraction part + while (s != EOF && s != quote && nsvg__isdigit(s)) + { + if (i < last) + it[i++] = s; + s = fgetc(fp); + } + } + // exponent + if (s == 'e' || s == 'E') + { + if (i < last) + it[i++] = s; + s = fgetc(fp); + if (s == '-' || s == '+') + { + if (i < last) + it[i++] = s; + s = fgetc(fp); + } + while (s != EOF && s != quote && nsvg__isdigit(s)) + { + if (i < last) + it[i++] = s; + s = fgetc(fp); + } + } + it[i] = '\0'; + return s; +} + +static float nsvg__parseCoordinate(FILE *fp, char quote) +{ + static char attr_value[32]; + int s = fgetc(fp); + // Store value and find the end of it. + int attr_value_len = 0; + while (s != EOF && s != '>' && s != quote) + { + // Only save up to the buffer, but skip to the next quote + if (attr_value_len < (32 - 2)) + { + attr_value[attr_value_len++] = s; + } + s = fgetc(fp); + } + attr_value[attr_value_len++] = '\0'; + + float value = 0.0f; + char units[32] = ""; + sscanf(attr_value, "%f%s", &value, units); + return value; +} + +static int nsvg__parseTransformArgs(FILE *fp, char quote, float *args, int maxNa, int *na) +{ + char it[64]; + int s = fgetc(fp); + + *na = 0; + while (s != EOF && s != quote && s != '(') + s = fgetc(fp); + if (s == EOF || s == quote) + return s; + + while (s != EOF && s != quote && s != ')') + { + if (s == '-' || s == '+' || s == '.' || nsvg__isdigit(s)) + { + if (*na >= maxNa) + return s; + s = nsvg__parseNumber(fp, quote, s, it, 64); + args[(*na)++] = (float)atof(it); + } + else + { + s = fgetc(fp); + } + } + return s; +} + +static int nsvg__parseMatrix(float *xform, FILE *fp, char quote) +{ + float t[6]; + int na = 0; + int s = nsvg__parseTransformArgs(fp, quote, t, 6, &na); + if (na != 6) + return s; + memcpy(xform, t, sizeof(float) * 6); + return s; +} + +static int nsvg__parseTranslate(float *xform, FILE *fp, char quote) +{ + float args[2]; + float t[6]; + int na = 0; + int s = nsvg__parseTransformArgs(fp, quote, args, 2, &na); + if (na == 1) + args[1] = 0.0; + + nsvg__xformSetTranslation(t, args[0], args[1]); + memcpy(xform, t, sizeof(float) * 6); + return s; +} + +static int nsvg__parseScale(float *xform, FILE *fp, char quote) +{ + float args[2]; + int na = 0; + float t[6]; + int s = nsvg__parseTransformArgs(fp, quote, args, 2, &na); + if (na == 1) + args[1] = args[0]; + nsvg__xformSetScale(t, args[0], args[1]); + memcpy(xform, t, sizeof(float) * 6); + return s; +} + +static int nsvg__parseSkewX(float *xform, FILE *fp, char quote) +{ + float args[1]; + int na = 0; + float t[6]; + int s = nsvg__parseTransformArgs(fp, quote, args, 1, &na); + nsvg__xformSetSkewX(t, args[0] / 180.0f * NSVG_PI); + memcpy(xform, t, sizeof(float) * 6); + return s; +} + +static int nsvg__parseSkewY(float *xform, FILE *fp, char quote) +{ + float args[1]; + int na = 0; + float t[6]; + int s = nsvg__parseTransformArgs(fp, quote, args, 1, &na); + nsvg__xformSetSkewY(t, args[0] / 180.0f * NSVG_PI); + memcpy(xform, t, sizeof(float) * 6); + return s; +} + +static int nsvg__parseRotate(float *xform, FILE *fp, char quote) +{ + float args[3]; + int na = 0; + float m[6]; + float t[6]; + int s = nsvg__parseTransformArgs(fp, quote, 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 s; +} + +static int nsvg__parseTransform(float *xform, FILE *fp, char quote) +{ + int s; + float t[6]; + + nsvg__xformIdentity(xform); + while (1) + { + s = fgetc(fp); + + if (s == EOF || s == '>' || s == quote) + break; + + if (s == 'm') + { + if (fgetc(fp) != 'a') + continue; + if (fgetc(fp) != 't') + continue; + if (fgetc(fp) != 'r') + continue; + if (fgetc(fp) != 'i') + continue; + if (fgetc(fp) != 'x') + continue; + s = nsvg__parseMatrix(t, fp, quote); + nsvg__xformPremultiply(xform, t); + } + else if (s == 'r') + { + if (fgetc(fp) != 'o') + continue; + if (fgetc(fp) != 't') + continue; + if (fgetc(fp) != 'a') + continue; + if (fgetc(fp) != 't') + continue; + if (fgetc(fp) != 'e') + continue; + s = nsvg__parseRotate(t, fp, quote); + nsvg__xformPremultiply(xform, t); + } + else if (s == 't') + { + if (fgetc(fp) != 'r') + continue; + if (fgetc(fp) != 'a') + continue; + if (fgetc(fp) != 'n') + continue; + if (fgetc(fp) != 's') + continue; + if (fgetc(fp) != 'l') + continue; + if (fgetc(fp) != 'a') + continue; + if (fgetc(fp) != 't') + continue; + if (fgetc(fp) != 'e') + continue; + s = nsvg__parseTranslate(t, fp, quote); + nsvg__xformPremultiply(xform, t); + } + else if (s == 's') + { + if (fgetc(fp) == 'c') + { + if (fgetc(fp) != 'a') + continue; + if (fgetc(fp) != 'l') + continue; + if (fgetc(fp) != 'e') + continue; + s = nsvg__parseScale(t, fp, quote); + nsvg__xformPremultiply(xform, t); + } + if (fgetc(fp) == 'k') + { + if (fgetc(fp) != 'e') + continue; + if (fgetc(fp) != 'w') + continue; + + if (fgetc(fp) == 'X') + { + s = nsvg__parseSkewX(t, fp, quote); + nsvg__xformPremultiply(xform, t); + } + else if (fgetc(fp) == 'Y') + { + s = nsvg__parseSkewY(t, fp, quote); + nsvg__xformPremultiply(xform, t); + } + else + continue; + } + else + continue; + } + } + + return s; +} + +static int nsvg__getArgsPerElement(char cmd) +{ + switch (cmd) + { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + case 'c': + case 'C': + return 6; + case 'a': + case 'A': + return 7; + } + return 0; +} + +static void nsvg__pathMoveTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) + { + *cpx += args[0]; + *cpy += args[1]; + } + else + { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__moveTo(p, *cpx, *cpy); +} + +static void nsvg__pathLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) + { + *cpx += args[0]; + *cpy += args[1]; + } + else + { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathHLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) + *cpx += args[0]; + else + *cpx = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathVLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + if (rel) + *cpy += args[0]; + else + *cpy = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathCubicBezTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if (rel) + { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } + else + { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathCubicBezShortTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) + { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } + else + { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2 * x1 - *cpx2; + cy1 = 2 * y1 - *cpy2; + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) + { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } + else + { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f / 3.0f * (cx - x1); + cy1 = y1 + 2.0f / 3.0f * (cy - y1); + cx2 = x2 + 2.0f / 3.0f * (cx - x2); + cy2 = y2 + 2.0f / 3.0f * (cy - y2); + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezShortTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) + { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } + else + { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2 * x1 - *cpx2; + cy = 2 * y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f / 3.0f * (cx - x1); + cy1 = y1 + 2.0f / 3.0f * (cy - y1); + cx2 = x2 + 2.0f / 3.0f * (cx - x2); + cy2 = y2 + 2.0f / 3.0f * (cy - y2); + + nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static float nsvg__sqr(float x) +{ + return x * x; +} +static float nsvg__vmag(float x, float y) +{ + return sqrtf(x * x + y * y); +} + +static float nsvg__vecrat(float ux, float uy, float vx, float vy) +{ + return (ux * vx + uy * vy) / (nsvg__vmag(ux, uy) * nsvg__vmag(vx, vy)); +} + +static float nsvg__vecang(float ux, float uy, float vx, float vy) +{ + float r = nsvg__vecrat(ux, uy, vx, vy); + if (r < -1.0f) + r = -1.0f; + if (r > 1.0f) + r = 1.0f; + return ((ux * vy < uy * vx) ? -1.0f : 1.0f) * acosf(r); +} + +static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf(args[0]); // y radius + ry = fabsf(args[1]); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation engle + fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + if (rel) + { // end point + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } + else + { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf(dx * dx + dy * dy); + if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) + { + // The arc degenerates to a line + nsvg__lineTo(p, x2, y2); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf(rotx); + cosrx = cosf(rotx); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr(x1p) / nsvg__sqr(rx) + nsvg__sqr(y1p) / nsvg__sqr(ry); + if (d > 1) + { + d = sqrtf(d); + rx *= d; + ry *= d; + } + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr(rx) * nsvg__sqr(ry) - nsvg__sqr(rx) * nsvg__sqr(y1p) - nsvg__sqr(ry) * nsvg__sqr(x1p); + sb = nsvg__sqr(rx) * nsvg__sqr(y1p) + nsvg__sqr(ry) * nsvg__sqr(x1p); + if (sa < 0.0f) + sa = 0.0f; + if (sb > 0.0f) + s = sqrtf(sa / sb); + if (fa == fs) + s = -s; + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2) / 2.0f + cosrx * cxp - sinrx * cyp; + cy = (y1 + y2) / 2.0f + sinrx * cxp + cosrx * cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle + da = nsvg__vecang(ux, uy, vx, vy); // Delta angle + + // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; + // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if (fa) + { + // Choose large arc + if (da > 0.0f) + da = da - 2 * NSVG_PI; + else + da = 2 * NSVG_PI + da; + } + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; + t[1] = sinrx; + t[2] = -sinrx; + t[3] = cosrx; + t[4] = cx; + t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this + // +1. + ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f); + hda = (da / (float)ndivs) / 2.0f; + kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + if (da < 0.0f) + kappa = -kappa; + + for (i = 0; i <= ndivs; i++) + { + a = a1 + da * (i / (float)ndivs); + dx = cosf(a); + dy = sinf(a); + nsvg__xformPoint(&x, &y, dx * rx, dy * ry, t); // position + nsvg__xformVec(&tanx, &tany, -dy * rx * kappa, dx * ry * kappa, t); // tangent + if (i > 0) + nsvg__cubicBezTo(p, px + ptanx, py + ptany, x - tanx, y - tany, x, y); + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + +static void nsvg__forceLowMemory(NSVGparser *p, char closedFlag) +{ + if (p->npts > 0) + { + nsvg__drawPath(p, closedFlag); + p->pts[0] = p->pts[p->npts * 2 - 2]; + p->pts[1] = p->pts[p->npts * 2 - 1]; + p->npts = 1; + } +} + +static void nsvg__parsePathDescriptors(NSVGparser *p, FILE *fp, char quote) +{ + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + float cpx, cpy, cpx2, cpy2; + char closedFlag; + char item[64]; + + nsvg__resetPath(p); + cpx = 0; + cpy = 0; + cpx2 = 0; + cpy2 = 0; + closedFlag = 0; + nargs = 0; + + int s = fgetc(fp); + + while (s != EOF && s != '>' && s != quote) + { + item[0] = '\0'; + // Skip white spaces and commas + while (s != EOF && s != '>' && s != quote && (nsvg__isspace(s) || s == ',')) + s = fgetc(fp); + if (s == EOF || s == '>' || s == quote) + break; + if (s == '-' || s == '+' || s == '.' || nsvg__isdigit(s)) + { + s = nsvg__parseNumber(fp, quote, s, item, 64); + } + else + { + // Parse command + item[0] = s; + item[1] = '\0'; + s = fgetc(fp); + } + if (!*item) + break; + if (nsvg__isnum(item[0])) + { + if (nargs < 10) + args[nargs++] = (float)atof(item); + if (nargs >= rargs) + { + switch (cmd) + { + case 'm': + case 'M': + nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement(cmd); + cpx2 = cpx; + cpy2 = cpy; + break; + case 'l': + case 'L': + nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + case 'H': + case 'h': + nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + case 'V': + case 'v': + nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + case 'C': + case 'c': + nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); + break; + case 'S': + case 's': + nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); + break; + case 'Q': + case 'q': + nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); + break; + case 'T': + case 't': + nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); + break; + case 'A': + case 'a': + nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); + cpx2 = cpx; + cpy2 = cpy; + break; + default: + if (nargs >= 2) + { + cpx = args[nargs - 2]; + cpy = args[nargs - 1]; + cpx2 = cpx; + cpy2 = cpy; + } + break; + } + nargs = 0; + } + } + else + { + cmd = item[0]; + rargs = nsvg__getArgsPerElement(cmd); + if (cmd == 'M' || cmd == 'm') + { + // Commit path. + if (p->npts > 0) + nsvg__drawPath(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->sx; + cpy = p->sy; + cpx2 = cpx; + cpy2 = cpy; + nsvg__drawPath(p, closedFlag); + } + // Start new subpath. + nsvg__resetPath(p); + nsvg__moveTo(p, cpx, cpy); + closedFlag = 0; + nargs = 0; + } + else + { + nsvg__forceLowMemory(p, closedFlag); + } + } + } + // Commit path. + if (p->npts) + nsvg__drawPath(p, closedFlag); +} + +static void nsvg__parsePolyPoints(NSVGparser *p, FILE *fp, char quote) +{ + float args[2]; + int nargs, npts = 0; + char item[64]; + int s = fgetc(fp); + + nargs = 0; + while (s != EOF && s != '>' && s != quote) + { + item[0] = '\0'; + // Skip white spaces and commas + while (s != EOF && s != '>' && s != quote && (nsvg__isspace(s) || s == ',')) + s = fgetc(fp); + if (s == EOF || s == '>' || s == quote) + break; + if (s == '-' || s == '+' || s == '.' || nsvg__isdigit(s)) + { + s = nsvg__parseNumber(fp, quote, s, item, 64); + } + else + { + // Parse command + item[0] = s; + item[1] = '\0'; + s = fgetc(fp); + } + args[nargs++] = (float)atof(item); + if (nargs >= 2) + { + if (npts == 0) + nsvg__moveTo(p, args[0], args[1]); + else + nsvg__lineTo(p, args[0], args[1]); + nargs = 0; + npts++; + } + } +} + +static void nsvg__parseAttribs(NSVGparser *p, FILE *fp, int *end = NULL) +{ + memset(&p->attrs, 0, sizeof(NSVGCommonAttrs)); + /* Marks not set */ + p->attrs.rx = -1.0; + p->attrs.rx = -1.0; + static char attr_name[32]; + + int s = fgetc(fp); + + // Get attribs + while (!(end && *end) && s != '>' && s != EOF) + { + // Skip white space before the attrib name + while (s != '>' && nsvg__isspace(s)) + s = fgetc(fp); + if (s == '>' || s == EOF) + return; + if (s == '/') + { + if (end) + *end = 1; + return; + } + + int attr_name_len = 0; + // Find end of the attrib name. + while (s != EOF && s != '>' && !nsvg__isspace(s) && s != '=' && attr_name_len < (32 - 2)) + { + attr_name[attr_name_len++] = s; + s = fgetc(fp); + } + attr_name[attr_name_len++] = '\0'; + + // Skip until the beginning of the value. + while (s != EOF && s != '>' && s != '\"' && s != '\'') + s = fgetc(fp); + if (s == '>') + return; + + if (attr_name_len) + { + if (strcmp(attr_name, "d") == 0) + { + nsvg__parsePathDescriptors(p, fp, s); + } + else if (strcmp(attr_name, "points") == 0) + { + nsvg__parsePolyPoints(p, fp, s); + } + else if (strcmp(attr_name, "transform") == 0) + { + NSVGxform *xform = nsvg__getCurrentXForm(p); + if (xform) + { + float xform_floats[6]; + nsvg__parseTransform(xform_floats, fp, s); + nsvg__xformPremultiply(xform->xform, xform_floats); + } + } + else + { + if (strcmp(attr_name, "r") == 0) + p->attrs.r = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "width") == 0) + p->attrs.width = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "height") == 0) + p->attrs.height = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "x") == 0) + p->attrs.x = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "y") == 0) + p->attrs.y = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "rx") == 0) + p->attrs.rx = fabsf(nsvg__parseCoordinate(fp, s)); + else if (strcmp(attr_name, "ry") == 0) + p->attrs.ry = fabsf(nsvg__parseCoordinate(fp, s)); + else if (strcmp(attr_name, "cx") == 0) + p->attrs.cx = fabsf(nsvg__parseCoordinate(fp, s)); + else if (strcmp(attr_name, "cy") == 0) + p->attrs.cy = fabsf(nsvg__parseCoordinate(fp, s)); + else if (strcmp(attr_name, "x1") == 0) + p->attrs.x1 = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "y1") == 0) + p->attrs.y1 = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "x2") == 0) + p->attrs.x2 = nsvg__parseCoordinate(fp, s); + else if (strcmp(attr_name, "y2") == 0) + p->attrs.y2 = nsvg__parseCoordinate(fp, s); + else + { + // Consume the attribute without saving it + char quote = s; + s = fgetc(fp); + while (s != EOF && s != '>' && s != quote) + { + s = fgetc(fp); + } + } + } + } + s = fgetc(fp); + } +} + +static void nsvg__parsePath(NSVGparser *p, FILE *input, int *is_end_tag) +{ + nsvg__parseAttribs(p, input, is_end_tag); +} + +static void nsvg__parseRect(NSVGparser *p, FILE *input) +{ + nsvg__parseAttribs(p, input); + + float x = p->attrs.x; + float y = p->attrs.y; + float w = p->attrs.width; + float h = p->attrs.height; + float rx = p->attrs.rx; + float ry = p->attrs.ry; + + 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__drawPath(p, 1); + } +} + +static void nsvg__parseCircle(NSVGparser *p, FILE *input) +{ + nsvg__parseAttribs(p, input); + + float cx = p->attrs.cx; + float cy = p->attrs.cy; + float r = p->attrs.r; + + 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__drawPath(p, 1); + } +} + +static void nsvg__parseEllipse(NSVGparser *p, FILE *input) +{ + nsvg__parseAttribs(p, input); + + float cx = p->attrs.cx; + float cy = p->attrs.cy; + float rx = p->attrs.rx; + float ry = p->attrs.ry; + + 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__drawPath(p, 1); + } +} + +static void nsvg__parseLine(NSVGparser *p, FILE *input) +{ + nsvg__parseAttribs(p, input); + + float x1 = p->attrs.x1; + float y1 = p->attrs.y1; + float x2 = p->attrs.x2; + float y2 = p->attrs.y2; + + nsvg__resetPath(p); + + nsvg__moveTo(p, x1, y1); + nsvg__lineTo(p, x2, y2); + + nsvg__drawPath(p, 0); +} + +static void nsvg__parsePoly(NSVGparser *p, FILE *input, int closeFlag) +{ + nsvg__resetPath(p); + nsvg__parseAttribs(p, input); + nsvg__drawPath(p, (char)closeFlag); +} + +static void nsvg__parseSVG(NSVGparser *p, FILE *input) +{ + nsvg__parseAttribs(p, input); + p->width = p->attrs.width; + p->height = p->attrs.height; +} + +static void nsvg__parseGroup(NSVGparser *p, FILE *input, int *is_end_tag) +{ + nsvg__parseAttribs(p, input, is_end_tag); +} + +static void nsvg__parseXML(FILE *fp, NSVGparser *p) +{ + int s = fgetc(fp); + while (EOF != s) + { + if (s == '<') + { + s = fgetc(fp); + + // Skip white space after the '<' + while (s != EOF && s != '<' && nsvg__isspace(s)) + s = fgetc(fp); + + // Start of a content or new tag. + int start = 0; + int end = 0; + + // Check if the tag is end tag + if (s == '/') + { + s = fgetc(fp); + end = 1; + } + else + { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if (s == EOF || s == '?' || s == '!') + continue; + + // Get tag name + static const int max_tag_name_len = 16; + char tag_name[max_tag_name_len]; + int tag_name_len = 0; + while (s != EOF && s != '<' && !nsvg__isspace(s) && tag_name_len < (max_tag_name_len - 2)) + { + tag_name[tag_name_len++] = s; + s = fgetc(fp); + } + tag_name[tag_name_len++] = '\0'; + + if (start) + { + const char *known_tags[9] = { "g", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon", "svg" }; + + int found = 0; + for (int i = 0; i < 9; i++) + { + found += strcmp(tag_name, known_tags[i]) == 0; + if (found) + break; + } + if (!found) + continue; + + if (strcmp(tag_name, "g") == 0) + { + nsvg__pushAttr(p); + nsvg__parseGroup(p, fp, &end); + } + else if (strcmp(tag_name, "path") == 0) + { + if (p->pathFlag) // Do not allow nested paths. + continue; + nsvg__pushAttr(p); + nsvg__parsePath(p, fp, &end); + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "rect") == 0) + { + nsvg__pushAttr(p); + nsvg__parseRect(p, fp); + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "circle") == 0) + { + nsvg__pushAttr(p); + nsvg__parseCircle(p, fp); + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "ellipse") == 0) + { + nsvg__pushAttr(p); + nsvg__parseEllipse(p, fp); + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "line") == 0) + { + nsvg__pushAttr(p); + nsvg__parseLine(p, fp); + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "polyline") == 0) + { + nsvg__pushAttr(p); + nsvg__parsePoly(p, fp, 0); + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "polygon") == 0) + { + nsvg__pushAttr(p); + nsvg__parsePoly(p, fp, 1); + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "svg") == 0) + { + nsvg__parseSVG(p, fp); + } + } + + if (end) + { + if (strcmp(tag_name, "g") == 0) + { + nsvg__popAttr(p); + } + else if (strcmp(tag_name, "path") == 0) + { + p->pathFlag = 0; + } + } + } + s = fgetc(fp); + + while (s != EOF && s != '<') + s = fgetc(fp); + } +} + +// Draws SVG from a file +void nsvgDrawFromFile(const char *filename, nsvgMoveToCb moveToCb, nsvgBeginPathCb beginPathCb, nsvgEndPathCb endPathCb, float scale, + float x0, float y0) +{ + FILE *fp = NULL; + + fp = fopen(filename, "rb"); + if (!fp) + return; + + NSVGparser p = {}; + + // Init style + nsvg__xformIdentity(p.xforms[0].xform); + + p.moveToCb = moveToCb; + p.beginPathCb = beginPathCb; + p.endPathCb = endPathCb; + p.scale = scale; + p.x0 = x0; + p.y0 = y0; + + nsvg__parseXML(fp, &p); + + p.endPathCb(); + + free(p.pts); + fclose(fp); +}