/* * 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_svgParser.h" #include "QString" // For locale free str to float conversion #include #include #ifndef M_PI #define M_PI 3.14159 #endif OE_svgParser::OE_svgParser() { //OE_svgParser::OE_svg_base(); opacity = 0; // Opacity of the curve. strokeWidth = 0; // Stroke width (scaled). strokeLineJoin = 0; // Stroke join type. strokeLineCap = 0; // Stroke cap type. } float OE_svgParser::sqr(float x) { return x*x; } float OE_svgParser::vmag(float x, float y) { return sqrtf(x*x + y*y); } float OE_svgParser::vecrat(float ux, float uy, float vx, float vy) { return (ux*vx + uy*vy) / (vmag(ux,uy) * vmag(vx,vy)); } float OE_svgParser::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_svgParser::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_svgParser::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_svgParser::isspace(char c) { return strchr(" \t\n\v\f\r", c) != 0; } int OE_svgParser::isdigit(char c) { return strchr("0123456789", c) != 0; } int OE_svgParser::isnum(char c) { return strchr("0123456789+-.eE", c) != 0; } const char* OE_svgParser::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_svgParser::pathMoveTo(OE_pointcurve* 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_svgParser::pathLineTo(OE_pointcurve* 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_svgParser::pathHLineTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel) { if (rel) { *cpx += args[0]; } else { *cpx = args[0]; } curve->lineTo(*cpx, *cpy); } void OE_svgParser::pathVLineTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel) { if (rel) { *cpy += args[0]; } else { *cpy = args[0]; } curve->lineTo(*cpx, *cpy); } void OE_svgParser::pathCubicBezTo(OE_pointcurve* 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_svgParser::pathCubicBezShortTo(OE_pointcurve* 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_svgParser::pathQuadBezTo(OE_pointcurve* 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_svgParser::pathQuadBezShortTo(OE_pointcurve* 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_svgParser::pathArcTo(OE_pointcurve* 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_svgParser::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_svgParser::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; } OE_document* OE_svgParser::fromFile(const std::string filename, const char* /*units*/, float /*dpi*/) { OE_svgParser parser; // Loading of the SVG file TiXmlDocument doc(filename.c_str()); if (! doc.LoadFile()) { std::cerr << "error during loading of the following file :" << std::endl; std::cerr << "error #" << doc.ErrorId() << " : " << doc.ErrorDesc() << std::endl; return nullptr; } TiXmlHandle hdl(&doc); TiXmlElement *object = hdl.FirstChildElement("svg").Element(); parser.document = new OE_document(); if (parser.document) { parser.parseSvg(object); } return parser.document; } bool OE_svgParser::parseSvg(TiXmlElement *input) { TiXmlElement* element = 0; if (input) { for( element = input->FirstChildElement(); element; element = element->NextSiblingElement() ) { const char* val = element->Value(); if((strlen(val)==1)&&(strncmp(val,"g",1) == 0)) { const char* transform = element->Attribute("transform"); OE_Matrix newTransform; OE_Matrix oldTransform(matrix); if (transform) { char snb[64]; float f[6] = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; if (!strncmp(transform, "matrix", 6)) { const char* matrix = transform+strlen("matrix("); for (int i=0; i<6; i++) { snb[0] = 0; matrix = parseNumber(matrix, snb, sizeof(snb))+1; f[i] = QString(snb).toFloat(); } newTransform = OE_Matrix(f); } else if (!strncmp(transform, "translate", 9)) { const char* translate = transform+strlen("translate("); for (int i=4; i<6; i++) { snb[0] = 0; translate = parseNumber(translate, snb, sizeof(snb))+1; f[i] = QString(snb).toFloat(); } } else if (!strncmp(transform, "scale", 5)) { const char* scale = transform+strlen("scale("); for (int i=0; i<6; i+=3) { snb[0] = 0; scale = parseNumber(scale, snb, sizeof(snb))+1; f[i] = QString(snb).toFloat(); } } else if (!strncmp(transform, "rotate", 6)) { // https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform printf("Unmanaged transform \"%s\"\n", transform); } else if (!strncmp(transform, "skewX", 5)) { printf("Unmanaged transform \"%s\"\n", transform); } else if (!strncmp(transform, "skewX", 5)) { printf("Unmanaged transform \"%s\"\n", transform); } else { printf("Unknown transform \"%s\"\n", transform); } } matrix = matrix * newTransform; parseSvg(element); matrix = oldTransform; } if((strlen(val)==4)&&(strncmp(val,"path",4) == 0)) { parsePath(element); } } } return true; } bool OE_svgParser::parsePath(TiXmlElement *input) { if (input) { OE_pointcurve* curve; const char * s = input->Attribute("d"); char cmd = '\0'; float args[10]; int nargs; int rargs = 0; /*nb arg after a command*/ float cpx, cpy, cpx2, cpy2; char item[64]; /* //const char** attr; //const char* s = NULL; // const char* tmp[4]; 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) { curve = new OE_pointcurve(); if (curve) { cpx = 0; cpy = 0; nargs = 0; while (*s) { s = getNextPathItem(s, item); if (!*item) break; if (isnum(item[0])) { if (nargs < 10) { args[nargs++] = QString(item).toFloat(); } if (nargs >= rargs) { switch (cmd) { case 'm': case 'M': pathMoveTo(curve, &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(curve, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'H': case 'h': pathHLineTo(curve, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'V': case 'v': pathVLineTo(curve, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'C': case 'c': pathCubicBezTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); break; case 'S': case 's': pathCubicBezShortTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); break; case 'Q': case 'q': pathQuadBezTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); break; case 'T': case 't': pathQuadBezShortTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); break; case 'A': case 'a': pathArcTo(curve, &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') { // Add the curve to document. pushCurve(curve); // Start new curve. curve = new OE_pointcurve(); nargs = 0; } else if (cmd == 'Z' || cmd == 'z') { //z mean the curve is closed so... curve->setClosed(true); if (curve->getNpts() > 3) { // Move current point to first point curve->getPoint(1,&cpx,&cpy); cpx2 = cpx; cpy2 = cpy; // Add the curve to document. pushCurve(curve); // Start new curve. curve = new OE_pointcurve(); } nargs = 0; } } } pushCurve(curve); return true; } } } return false; } bool OE_svgParser::pushCurve(OE_pointcurve * curve) { if (curve->check()) { curve->transform(matrix); return document->addCurve(curve); } else { delete curve; curve = nullptr; return false; } }