/* * Copyright (c) 2015 Tricoire Sebastien 3dsman@free.fr * * 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. * */ #include "OE_path.h" #include #include #ifndef M_PI #define M_PI 3.14... #endif OE_path::OE_path() { //OE_path::OE_svg_base(); opacity = 0; // Opacity of the curve. strokeWidth = 0; // Stroke width (scaled). strokeLineJoin = 0; // Stroke join type. strokeLineCap = 0; // Stroke cap type. curves = 0; //next = 0; } OE_path::~OE_path() { OE_curve *curve; while (curves != 0) { curve = curves->getNext(); delete(curves); curves = curve; } } float OE_path::sqr(float x) { return x*x; } float OE_path::vmag(float x, float y) { return sqrtf(x*x + y*y); } float OE_path::vecrat(float ux, float uy, float vx, float vy) { return (ux*vx + uy*vy) / (vmag(ux,uy) * vmag(vx,vy)); } float OE_path::vecang(float ux, float uy, float vx, float vy) { float r = 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); } void OE_path::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]; } void OE_path::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]; } int OE_path::isspace(char c) { return strchr(" \t\n\v\f\r", c) != 0; } int OE_path::isdigit(char c) { return strchr("0123456789", c) != 0; } int OE_path::isnum(char c) { return strchr("0123456789+-.eE", c) != 0; } const char* OE_path::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 && isdigit(*s)) { if (i < last) it[i++] = *s; s++; } if (*s == '.') { // decimal point if (i < last) it[i++] = *s; s++; // fraction part while (*s && 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 && isdigit(*s)) { if (i < last) it[i++] = *s; s++; } } it[i] = '\0'; return s; } void OE_path::pathMoveTo(OE_curve* curve, float* cpx, float* cpy, float* args, int rel) { if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; } curve->addPoint(*cpx, *cpy); } void OE_path::pathLineTo(OE_curve* curve, float* cpx, float* cpy, float* args, int rel) { if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; } curve->lineTo(*cpx, *cpy); } void OE_path::pathHLineTo(OE_curve* curve, float* cpx, float* cpy, float* args, int rel) { if (rel) *cpx += args[0]; else *cpx = args[0]; curve->lineTo(*cpx, *cpy); } void OE_path::pathVLineTo(OE_curve* curve, float* cpx, float* cpy, float* args, int rel) { if (rel) *cpy += args[0]; else *cpy = args[0]; curve->lineTo(*cpx, *cpy); } void OE_path::pathCubicBezTo(OE_curve* curve, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel) { float x2, y2, cx1, cy1, cx2, cy2; //x1 = *cpx; //y1 = *cpy; 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]; } curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx2; *cpy2 = cy2; *cpx = x2; *cpy = y2; } void OE_path::pathCubicBezShortTo(OE_curve* curve, 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; curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx2; *cpy2 = cy2; *cpx = x2; *cpy = y2; } void OE_path::pathQuadBezTo(OE_curve* curve, 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); curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx; *cpy2 = cy; *cpx = x2; *cpy = y2; } void OE_path::pathQuadBezShortTo(OE_curve* curve, 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); curve->cubicBezTo(cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx; *cpy2 = cy; *cpx = x2; *cpy = y2; } void OE_path::pathArcTo(OE_curve* curve, 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 * M_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 curve->lineTo(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 = sqr(x1p)/sqr(rx) + sqr(y1p)/sqr(ry); if (d > 1) { d = sqrtf(d); rx *= d; ry *= d; } // 2) Compute cx', cy' s = 0.0f; sa = sqr(rx)*sqr(ry) - sqr(rx)*sqr(y1p) - sqr(ry)*sqr(x1p); sb = sqr(rx)*sqr(y1p) + sqr(ry)*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 = vecang(1.0f,0.0f, ux,uy); // Initial angle da = 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*M_PI; else da = 2*M_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) / (M_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); xformPoint(&x, &y, dx*rx, dy*ry, t); // position xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent if (i > 0) curve->cubicBezTo(px+ptanx,py+ptany, x-tanx, y-tany, x, y); px = x; py = y; ptanx = tanx; ptany = tany; } *cpx = x2; *cpy = y2; } const char* OE_path::getNextPathItem(const char* s, char* it) { it[0] = '\0'; // Skip white spaces and commas while (*s && (isspace(*s) || *s == ',')) s++; if (!*s) return s; if (*s == '-' || *s == '+' || *s == '.' || isdigit(*s)) { s = parseNumber(s, it, 64); } else { // Parse command it[0] = *s++; it[1] = '\0'; return s; } return s; } int OE_path::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; } bool OE_path::newCurve() { OE_curve* curve = new OE_curve(); if (curve) { if(!curve->setNext(curves)) { delete curve; return false; } curves = curve; curve->controls = false; return true; } return false; } bool OE_path::Parse(TiXmlElement *input) { if (input) { const char * s = input->Attribute("d"); //const char** attr; //const char* s = NULL; char cmd = '\0'; float args[10]; int nargs; int rargs = 0; /*nb arg after a command*/ float cpx, cpy, cpx2, cpy2; // const char* tmp[4]; // char closedFlag; 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); if (newCurve()) { cpx = 0; cpy = 0; //closedFlag = 0; nargs = 0; while (*s) { s = getNextPathItem(s, item); if (!*item) break; if (isnum(item[0])) { if (nargs < 10) args[nargs++] = (float)atof(item); if (nargs >= rargs) { switch (cmd) { case 'm': case 'M': pathMoveTo(curves, &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 = getArgsPerElement(cmd); cpx2 = cpx; cpy2 = cpy; break; case 'l': case 'L': pathLineTo(curves, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'H': case 'h': pathHLineTo(curves, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'V': case 'v': pathVLineTo(curves, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'C': case 'c': pathCubicBezTo(curves, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); break; case 'S': case 's': pathCubicBezShortTo(curves, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); break; case 'Q': case 'q': pathQuadBezTo(curves, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); break; case 'T': case 't': pathQuadBezShortTo(curves, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); break; case 'A': case 'a': pathArcTo(curves, &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 = getArgsPerElement(cmd); //give us the number of arguments if (cmd == 'M' || cmd == 'm') { // Commit path. if (!curves->check()) { OE_curve* curve = curves->next; delete curves; curves = curve; } // Start new subpath. newCurve(); //nsvg__addPath(p, closedFlag); //nsvg__resetPath(p); // closedFlag = 0; nargs = 0; } else if (cmd == 'Z' || cmd == 'z') { curves->setClosed(true); // Commit path. if (curves->getNpts() > 3) { // Move current point to first point curves->getPoint(1,&cpx,&cpy); cpx2 = cpx; cpy2 = cpy; } nargs = 0; } } } return true; } if (!curves->check()) { OE_curve* curve = curves->next; delete curves; curves = curve; } } } return false; } bool OE_path::draw(float dpi) { OE_svg_base::draw(dpi); OE_curve *curve; curve = curves; while (curve != 0) { curve->draw(dpi); curve = curve->getNext(); } return true; }