Suspended plotter for the skaperfest
Dependencies: mbed HTTPServer EthernetNetIf FatFileSystemCpp
nanosvg.h
- Committer:
- rengro01
- Date:
- 2022-08-22
- Revision:
- 0:602ff2b2d41c
File content as of revision 0:602ff2b2d41c:
/* * 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); }