Skip to content
OE_svgParser.cpp 18.2 KiB
Newer Older
3dsman's avatar
3dsman committed
/*
 * 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"
3dsman's avatar
3dsman committed
#include <iostream>
#include <math.h>

#ifndef M_PI
 #define M_PI 3.14...
#endif


OE_svgParser::OE_svgParser()
3dsman's avatar
3dsman committed
{
    //OE_svgParser::OE_svg_base();
3dsman's avatar
3dsman committed
    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; }
3dsman's avatar
3dsman committed

float OE_svgParser::vmag(float x, float y) { return sqrtf(x*x + y*y); }
3dsman's avatar
3dsman committed

float OE_svgParser::vecrat(float ux, float uy, float vx, float vy)
3dsman's avatar
3dsman committed
{
	return (ux*vx + uy*vy) / (vmag(ux,uy) * vmag(vx,vy));
}

float OE_svgParser::vecang(float ux, float uy, float vx, float vy)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	*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)
3dsman's avatar
3dsman committed
{
	*dx = x*t[0] + y*t[2];
	*dy = x*t[1] + y*t[3];
}


int OE_svgParser::isspace(char c)
3dsman's avatar
3dsman committed
{
	return strchr(" \t\n\v\f\r", c) != 0;
}

int OE_svgParser::isdigit(char c)
3dsman's avatar
3dsman committed
{
	return strchr("0123456789", c) != 0;
}

int OE_svgParser::isnum(char c)
3dsman's avatar
3dsman committed
{
	return strchr("0123456789+-.eE", c) != 0;
}

const char* OE_svgParser::parseNumber(const char* s, char* it, const int size)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	if (rel) {
		*cpx += args[0];
		*cpy += args[1];
	} else {
		*cpx = args[0];
		*cpy = args[1];
	}
	curve->addPoint(*cpx, *cpy);
3dsman's avatar
3dsman committed
}

void OE_svgParser::pathLineTo(OE_pointcurve* curve, float* cpx, float* cpy, float* args, int rel)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	float  x2, y2, cx1, cy1, cx2, cy2;
3dsman's avatar
3dsman committed

	//x1 = *cpx;
	//y1 = *cpy;
3dsman's avatar
3dsman committed
	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)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	// 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)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
	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)
3dsman's avatar
3dsman committed
{
    OE_svgParser parser;

    // Loading of the SVG file
    TiXmlDocument doc(filename.c_str());
    if ( ! doc.LoadFile() )
3dsman's avatar
3dsman committed
    {
        std::cerr << "error during loading of the following file :" << std::endl;
        std::cerr << "error #" << doc.ErrorId() << " : " << doc.ErrorDesc() << std::endl;

        return nullptr;
3dsman's avatar
3dsman committed
    }
    TiXmlHandle hdl(&doc);

    TiXmlElement *object = hdl.FirstChildElement("svg").Element();
	
    parser.document = new OE_document();
    if (parser.document)
        parser.parseSvg(object);
    return parser.document;
3dsman's avatar
3dsman committed
}

bool OE_svgParser::parseSvg(TiXmlElement *input)
3dsman's avatar
3dsman committed
{
    TiXmlElement* element = 0;
3dsman's avatar
3dsman committed
    if (input)
        for( element = input->FirstChildElement(); element; element = element->NextSiblingElement() )
        {
            const char* val = element->Value();
3dsman's avatar
3dsman committed

            if((strlen(val)==1)&&(strncmp(val,"g",1) == 0))
            {
raoul's avatar
raoul committed
				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] = atof(snb);
						}
						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] = atof(snb);
						}
					}
					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] = atof(snb);
						}
					}
					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);
raoul's avatar
raoul committed
				matrix = oldTransform;
3dsman's avatar
3dsman committed

            if((strlen(val)==4)&&(strncmp(val,"path",4) == 0))
            {
                    parsePath(element);
            }
        }
    return true;
};
3dsman's avatar
3dsman committed

bool OE_svgParser::parsePath(TiXmlElement *input)
{
	
    if (input)
    {
		
		OE_pointcurve* curve;
		
        const char * s = input->Attribute("d");
3dsman's avatar
3dsman committed
        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];
3dsman's avatar
3dsman committed
        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)
3dsman's avatar
3dsman committed
            {

                cpx = 0; cpy = 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(curve, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    // 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);
3dsman's avatar
3dsman committed
                                    cpx2 = cpx; cpy2 = cpy;
                                    break;
                                case 'H':
                                case 'h':
                                    pathHLineTo(curve, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    cpx2 = cpx; cpy2 = cpy;
                                    break;
                                case 'V':
                                case 'v':
                                    pathVLineTo(curve, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    cpx2 = cpx; cpy2 = cpy;
                                    break;
                                case 'C':
                                case 'c':
                                    pathCubicBezTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    break;
                                case 'S':
                                case 's':
                                    pathCubicBezShortTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    break;
                                case 'Q':
                                case 'q':
                                    pathQuadBezTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    break;
                                case 'T':
                                case 't':
                                    pathQuadBezShortTo(curve, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    break;
                                case 'A':
                                case 'a':
                                    pathArcTo(curve, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);
3dsman's avatar
3dsman committed
                                    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

3dsman's avatar
3dsman committed
                        if (cmd == 'M' || cmd == 'm') {
                            // Add the curve to document.
							pushCurve(curve);
							// Start new curve.
							curve = new OE_pointcurve();
3dsman's avatar
3dsman committed
                            nargs = 0;
                        } else if (cmd == 'Z' || cmd == 'z') {
							//z mean the curve is closed so...
                            curve->setClosed(true);
                            if (curve->getNpts() > 3) {
3dsman's avatar
3dsman committed
                                // Move current point to first point
                                curve->getPoint(1,&cpx,&cpy);
3dsman's avatar
3dsman committed
                                cpx2 = cpx; cpy2 = cpy;
								
								// Add the curve to document.
								pushCurve(curve);
3dsman's avatar
3dsman committed
							// Start new curve.
							curve = new OE_pointcurve();
3dsman's avatar
3dsman committed
                            }
                            nargs = 0;
                        }
                    }
                }
				pushCurve(curve);
3dsman's avatar
3dsman committed
                return true;
            }
        }
    }
    return false;
}


bool OE_svgParser::pushCurve(OE_pointcurve * curve)
{
	if (curve->check())
	{
raoul's avatar
raoul committed
		curve->transform(matrix);
		return document->addCurve(curve);
		
	}else{
		delete curve;
		curve = nullptr;
		return false;
3dsman's avatar
3dsman committed
	}