Skip to content
OE_path.cpp 16.8 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_path.h"
#include <iostream>
#include <math.h>

#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);
3dsman's avatar
3dsman committed
}

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;
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_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;
        }
        //curve->next = curves;
        curves = curve;
        curve->controls = true;
3dsman's avatar
3dsman committed
        return true;
    }
    return false;
}

bool OE_path::Parse(TiXmlElement *input)
{
    if (input)
    {
3dsman's avatar
3dsman committed
        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->getNpts() > 0)
                            {
                                // Start new subpath.
                                newCurve();
                            }
                                //nsvg__addPath(p, closedFlag);

                            //nsvg__resetPath(p);
                           // closedFlag = 0;
                            nargs = 0;
                        } else if (cmd == 'Z' || cmd == 'z') {

                            curves->setClosed(true);
                            //closedFlag = 1;
                            // Commit path.
                            if (curves->getNpts() > 0) {
                                // Move current point to first point
                                curves->getPoint(1,&cpx,&cpy);
                                //cpx = curves->pts[0];
                                //cpy = curves->pts[1];
                                cpx2 = cpx; cpy2 = cpy;
                                //nsvg__addPath(p, closedFlag);
                            }

                            //closedFlag = 0;
                            nargs = 0;
                        }
                    }
                    /*
                    if (curves->getNpts()==0)
                    {
                    OE_curve* curve = curves->next;
                    delete curves;
                    curves = curve;
                    }*/
                }
                // Commit path.
                /*
                else
                {
                    OE_curve* curve = curves->next;
                    delete curves;
                    curves = curve;
                }*/
                    //nsvg__addPath(p, closedFlag);
                //float xMin, yMin, xMax, yMax;
                //curve->getBound(&xMin, &yMin, &xMax, &yMax);
                return true;
            }
        }
    }
	//nsvg__addShape(p);

    //OE_svg_base::Parse(input);
    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;
}