Logo Search packages:      
Sourcecode: inkscape version File versions

svg-path.cpp

#define __SP_SVG_PARSE_C__
/*
   svg-path.c: Parse SVG path element data into bezier path.

   Copyright (C) 2000 Eazel, Inc.
   Copyright (C) 2000 Lauris Kaplinski
   Copyright (C) 2001 Ximian, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Authors:
     Raph Levien <raph@artofcode.com>
     Lauris Kaplinski <lauris@ximian.com>
*/

#include <cstring>
#include <string>
#include <cassert>
#include <glib/gmem.h>
#include <glib/gmessages.h>
#include <glib/gstrfuncs.h>
#include <glib.h> // g_assert()

#include "libnr/n-art-bpath.h"
#include "gnome-canvas-bpath-util.h"
#include "svg/path-string.h"


/* This module parses an SVG path element into an RsvgBpathDef.

   At present, there is no support for <marker> or any other contextual
   information from the SVG file. The API will need to change rather
   significantly to support these.

   Reference: SVG working draft 3 March 2000, section 8.
*/

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif  /*  M_PI  */

/* We are lazy ;-) (Lauris) */
#define rsvg_bpath_def_new gnome_canvas_bpath_def_new
#define rsvg_bpath_def_moveto gnome_canvas_bpath_def_moveto
#define rsvg_bpath_def_lineto gnome_canvas_bpath_def_lineto
#define rsvg_bpath_def_curveto gnome_canvas_bpath_def_curveto
#define rsvg_bpath_def_closepath gnome_canvas_bpath_def_closepath

struct RSVGParsePathCtx {
    GnomeCanvasBpathDef *bpath;
    double cpx, cpy;  /* current point */
    double rpx, rpy;  /* reflection point (for 's' and 't' commands) */
    double spx, spy;  /* beginning of current subpath point */
    char cmd;         /* current command (lowercase) */
    int param;        /* parameter number */
    bool rel;         /* true if relative coords */
    double params[7]; /* parameters that have been parsed */
};

static void rsvg_path_arc_segment(RSVGParsePathCtx *ctx,
              double xc, double yc,
              double th0, double th1,
              double rx, double ry, double x_axis_rotation)
{
    double sin_th, cos_th;
    double a00, a01, a10, a11;
    double x1, y1, x2, y2, x3, y3;
    double t;
    double th_half;

    sin_th = sin (x_axis_rotation * (M_PI / 180.0));
    cos_th = cos (x_axis_rotation * (M_PI / 180.0)); 
    /* inverse transform compared with rsvg_path_arc */
    a00 = cos_th * rx;
    a01 = -sin_th * ry;
    a10 = sin_th * rx;
    a11 = cos_th * ry;

    th_half = 0.5 * (th1 - th0);
    t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half);
    x1 = xc + cos (th0) - t * sin (th0);
    y1 = yc + sin (th0) + t * cos (th0);
    x3 = xc + cos (th1);
    y3 = yc + sin (th1);
    x2 = x3 + t * sin (th1);
    y2 = y3 - t * cos (th1);
    rsvg_bpath_def_curveto(ctx->bpath,
                           a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
                           a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
                           a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
}

/**
 * rsvg_path_arc: Add an RSVG arc to the path context.
 * @ctx: Path context.
 * @rx: Radius in x direction (before rotation).
 * @ry: Radius in y direction (before rotation).
 * @x_axis_rotation: Rotation angle for axes.
 * @large_arc_flag: 0 for arc length <= 180, 1 for arc >= 180.
 * @sweep: 0 for "negative angle", 1 for "positive angle".
 * @x: New x coordinate.
 * @y: New y coordinate.
 *
 **/
static void rsvg_path_arc (RSVGParsePathCtx *ctx,
                           double rx, double ry, double x_axis_rotation,
                           int large_arc_flag, int sweep_flag,
                           double x, double y)
{
    double sin_th, cos_th;
    double a00, a01, a10, a11;
    double x0, y0, x1, y1, xc, yc;
    double d, sfactor, sfactor_sq;
    double th0, th1, th_arc;
    double px, py, pl;
    int i, n_segs;

    sin_th = sin (x_axis_rotation * (M_PI / 180.0));
    cos_th = cos (x_axis_rotation * (M_PI / 180.0));

    /*                                                                            
                                                                                  Correction of out-of-range radii as described in Appendix F.6.6:           

                                                                                  1. Ensure radii are non-zero (Done?).                                      
                                                                                  2. Ensure that radii are positive.                                         
                                                                                  3. Ensure that radii are large enough.                                     
    */                                                                            

    if(rx < 0.0) rx = -rx;                                                        
    if(ry < 0.0) ry = -ry;                                                        

    px = cos_th * (ctx->cpx - x) * 0.5 + sin_th * (ctx->cpy - y) * 0.5;           
    py = cos_th * (ctx->cpy - y) * 0.5 - sin_th * (ctx->cpx - x) * 0.5;           
    pl = (px * px) / (rx * rx) + (py * py) / (ry * ry);                           

    if(pl > 1.0)                                                                  
    {                                                                             
        pl  = sqrt(pl);                                                           
        rx *= pl;                                                                 
        ry *= pl;                                                                 
    }                                                                             

    /* Proceed with computations as described in Appendix F.6.5 */                

    a00 = cos_th / rx;
    a01 = sin_th / rx;
    a10 = -sin_th / ry;
    a11 = cos_th / ry;
    x0 = a00 * ctx->cpx + a01 * ctx->cpy;
    y0 = a10 * ctx->cpx + a11 * ctx->cpy;
    x1 = a00 * x + a01 * y;
    y1 = a10 * x + a11 * y;
    /* (x0, y0) is current point in transformed coordinate space.
       (x1, y1) is new point in transformed coordinate space.

       The arc fits a unit-radius circle in this space.
    */
    d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
    sfactor_sq = 1.0 / d - 0.25;
    if (sfactor_sq < 0) sfactor_sq = 0;
    sfactor = sqrt (sfactor_sq);
    if (sweep_flag == large_arc_flag) sfactor = -sfactor;
    xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
    yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
    /* (xc, yc) is center of the circle. */

    th0 = atan2 (y0 - yc, x0 - xc);
    th1 = atan2 (y1 - yc, x1 - xc);

    th_arc = th1 - th0;
    if (th_arc < 0 && sweep_flag)
        th_arc += 2 * M_PI;
    else if (th_arc > 0 && !sweep_flag)
        th_arc -= 2 * M_PI;

    n_segs = (int) ceil (fabs (th_arc / (M_PI * 0.5 + 0.001)));

    for (i = 0; i < n_segs; i++) {
        rsvg_path_arc_segment(ctx, xc, yc,
                              th0 + i * th_arc / n_segs,
                              th0 + (i + 1) * th_arc / n_segs,
                              rx, ry, x_axis_rotation);
    }

    ctx->cpx = x;
    ctx->cpy = y;
}


/* supply defaults for missing parameters, assuming relative coordinates
   are to be interpreted as x,y */
static void rsvg_parse_path_default_xy(RSVGParsePathCtx *ctx, int n_params)
{
    int i;

    if (ctx->rel) {
        for (i = ctx->param; i < n_params; i++) {
            if (i > 2)
                ctx->params[i] = ctx->params[i - 2];
            else if (i == 1)
                ctx->params[i] = ctx->cpy;
            else if (i == 0)
                /* we shouldn't get here (usually ctx->param > 0 as
                   precondition) */
                ctx->params[i] = ctx->cpx;
        }
    } else {
        for (i = ctx->param; i < n_params; i++) {
            ctx->params[i] = 0.0;
        }
    }
}

static void rsvg_parse_path_do_cmd(RSVGParsePathCtx *ctx, bool final)
{
    double x1, y1, x2, y2, x3, y3;

#ifdef VERBOSE
    int i;

    g_print ("parse_path %c:", ctx->cmd);
    for (i = 0; i < ctx->param; i++) {
        g_print(" %f", ctx->params[i]);
    }
    g_print (final ? ".\n" : "\n");
#endif

    switch (ctx->cmd) {
    case 'm':
        /* moveto */
        if (ctx->param == 2 || final)
        {
            rsvg_parse_path_default_xy (ctx, 2);
#ifdef VERBOSE
            g_print ("'m' moveto %g,%g\n",
                     ctx->params[0], ctx->params[1]);
#endif
            rsvg_bpath_def_moveto (ctx->bpath,
                                   ctx->params[0], ctx->params[1]);
            ctx->cpx = ctx->rpx = ctx->spx = ctx->params[0];
            ctx->cpy = ctx->rpy = ctx->spy = ctx->params[1];
            ctx->param = 0;
            ctx->cmd = 'l';
        }
        break;
    case 'l':
        /* lineto */
        if (ctx->param == 2 || final)
        {
            rsvg_parse_path_default_xy (ctx, 2);
#ifdef VERBOSE
            g_print ("'l' lineto %g,%g\n",
                     ctx->params[0], ctx->params[1]);
#endif
            rsvg_bpath_def_lineto (ctx->bpath,
                                   ctx->params[0], ctx->params[1]);
            ctx->cpx = ctx->rpx = ctx->params[0];
            ctx->cpy = ctx->rpy = ctx->params[1];
            ctx->param = 0;
        }
        break;
    case 'c':
        /* curveto */
        if (ctx->param == 6 || final )
        {
            rsvg_parse_path_default_xy (ctx, 6);
            x1 = ctx->params[0];
            y1 = ctx->params[1];
            x2 = ctx->params[2];
            y2 = ctx->params[3];
            x3 = ctx->params[4];
            y3 = ctx->params[5];
#ifdef VERBOSE
            g_print ("'c' curveto %g,%g %g,%g, %g,%g\n",
                     x1, y1, x2, y2, x3, y3);
#endif
            rsvg_bpath_def_curveto (ctx->bpath,
                                    x1, y1, x2, y2, x3, y3);
            ctx->rpx = x2;
            ctx->rpy = y2;
            ctx->cpx = x3;
            ctx->cpy = y3;
            ctx->param = 0;
        }
        break;
    case 's':
        /* smooth curveto */
        if (ctx->param == 4 || final)
        {
            rsvg_parse_path_default_xy (ctx, 4);
            x1 = 2 * ctx->cpx - ctx->rpx;
            y1 = 2 * ctx->cpy - ctx->rpy;
            x2 = ctx->params[0];
            y2 = ctx->params[1];
            x3 = ctx->params[2];
            y3 = ctx->params[3];
#ifdef VERBOSE
            g_print ("'s' curveto %g,%g %g,%g, %g,%g\n",
                     x1, y1, x2, y2, x3, y3);
#endif
            rsvg_bpath_def_curveto (ctx->bpath,
                                    x1, y1, x2, y2, x3, y3);
            ctx->rpx = x2;
            ctx->rpy = y2;
            ctx->cpx = x3;
            ctx->cpy = y3;
            ctx->param = 0;
        }
        break;
    case 'h':
        /* horizontal lineto */
        if (ctx->param == 1) {
#ifdef VERBOSE
            g_print ("'h' lineto %g,%g\n",
                     ctx->params[0], ctx->cpy);
#endif
            rsvg_bpath_def_lineto (ctx->bpath,
                                   ctx->params[0], ctx->cpy);
            ctx->cpx = ctx->rpx = ctx->params[0];
            ctx->param = 0;
        }
        break;
    case 'v':
        /* vertical lineto */
        if (ctx->param == 1) {
#ifdef VERBOSE
            g_print ("'v' lineto %g,%g\n",
                     ctx->cpx, ctx->params[0]);
#endif
            rsvg_bpath_def_lineto (ctx->bpath,
                                   ctx->cpx, ctx->params[0]);
            ctx->cpy = ctx->rpy = ctx->params[0];
            ctx->param = 0;
        }
        break;
    case 'q':
        /* quadratic bezier curveto */

        /* non-normative reference:
           http://www.icce.rug.nl/erikjan/bluefuzz/beziers/beziers/beziers.html
        */
        if (ctx->param == 4 || final)
        {
            rsvg_parse_path_default_xy (ctx, 4);
            /* raise quadratic bezier to cubic */
            x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
            y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
            x3 = ctx->params[2];
            y3 = ctx->params[3];
            x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
            y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
#ifdef VERBOSE
            g_print("'q' curveto %g,%g %g,%g, %g,%g\n",
                    x1, y1, x2, y2, x3, y3);
#endif
            rsvg_bpath_def_curveto(ctx->bpath,
                                   x1, y1, x2, y2, x3, y3);
            ctx->rpx = ctx->params[0];
            ctx->rpy = ctx->params[1];
            ctx->cpx = x3;
            ctx->cpy = y3;
            ctx->param = 0;
        }
        break;
    case 't':
        /* Truetype quadratic bezier curveto */
        if (ctx->param == 2 || final) {
            double xc, yc; /* quadratic control point */

            xc = 2 * ctx->cpx - ctx->rpx;
            yc = 2 * ctx->cpy - ctx->rpy;
            /* generate a quadratic bezier with control point = xc, yc */
            x1 = (ctx->cpx + 2 * xc) * (1.0 / 3.0);
            y1 = (ctx->cpy + 2 * yc) * (1.0 / 3.0);
            x3 = ctx->params[0];
            y3 = ctx->params[1];
            x2 = (x3 + 2 * xc) * (1.0 / 3.0);
            y2 = (y3 + 2 * yc) * (1.0 / 3.0);
#ifdef VERBOSE
            g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
                     x1, y1, x2, y2, x3, y3);
#endif
            rsvg_bpath_def_curveto (ctx->bpath,
                                    x1, y1, x2, y2, x3, y3);
            ctx->rpx = xc;
            ctx->rpy = yc;
            ctx->cpx = x3;
            ctx->cpy = y3;
            ctx->param = 0;
        } else if (final) {
            if (ctx->param > 2) {
                rsvg_parse_path_default_xy(ctx, 4);
                /* raise quadratic bezier to cubic */
                x1 = (ctx->cpx + 2 * ctx->params[0]) * (1.0 / 3.0);
                y1 = (ctx->cpy + 2 * ctx->params[1]) * (1.0 / 3.0);
                x3 = ctx->params[2];
                y3 = ctx->params[3];
                x2 = (x3 + 2 * ctx->params[0]) * (1.0 / 3.0);
                y2 = (y3 + 2 * ctx->params[1]) * (1.0 / 3.0);
#ifdef VERBOSE
                g_print ("'t' curveto %g,%g %g,%g, %g,%g\n",
                         x1, y1, x2, y2, x3, y3);
#endif
                rsvg_bpath_def_curveto (ctx->bpath,
                                        x1, y1, x2, y2, x3, y3);
                ctx->rpx = x2;
                ctx->rpy = y2;
                ctx->cpx = x3;
                ctx->cpy = y3;
            } else {
                rsvg_parse_path_default_xy(ctx, 2);
#ifdef VERBOSE
                g_print ("'t' lineto %g,%g\n",
                         ctx->params[0], ctx->params[1]);
#endif
                rsvg_bpath_def_lineto(ctx->bpath,
                                      ctx->params[0], ctx->params[1]);
                ctx->cpx = ctx->rpx = ctx->params[0];
                ctx->cpy = ctx->rpy = ctx->params[1];
            }
            ctx->param = 0;
        }
        break;
    case 'a':
        if (ctx->param == 7 || final)
        {
            rsvg_path_arc(ctx,
                          ctx->params[0], ctx->params[1], ctx->params[2],
                          (int) ctx->params[3], (int) ctx->params[4],
                          ctx->params[5], ctx->params[6]);
            ctx->param = 0;
        }
        break;
    default:
        ctx->param = 0;
    }
}

static void rsvg_parse_path_data(RSVGParsePathCtx *ctx, const char *data)
{
    int i = 0;
    double val = 0;
    char c = 0;
    bool in_num = false;
    bool in_frac = false;
    bool in_exp = false;
    bool exp_wait_sign = false;
    int sign = 0;
    int exp = 0;
    int exp_sign = 0;
    double frac = 0.0;

    /* fixme: Do better error processing: e.g. at least stop parsing as soon as we find an error.
     * At some point we'll need to do all of
     * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing.
     */
    for (i = 0; ; i++)
    {
        c = data[i];
        if (c >= '0' && c <= '9')
        {
            /* digit */
            if (in_num)
            {
                if (in_exp)
                {
                    exp = (exp * 10) + c - '0';
                    exp_wait_sign = false;
                }
                else if (in_frac)
                    val += (frac *= 0.1) * (c - '0');
                else
                    val = (val * 10) + c - '0';
            }
            else
            {
                in_num = true;
                assert(!in_frac && !in_exp);
                exp = 0;
                exp_sign = 1;
                exp_wait_sign = false;
                val = c - '0';
                sign = 1;
            }
        }
        else if (c == '.' && !(in_frac || in_exp))
        {
            if (!in_num)
            {
                in_num = true;
                assert(!in_exp);
                exp = 0;
                exp_sign = 1;
                exp_wait_sign = false;
                val = 0;
                sign = 1;
            }
            in_frac = true;
            frac = 1;
        }
        else if ((c == 'E' || c == 'e') && in_num)
        {
            /* fixme: Should we add `&& !in_exp' to the above condition?
             * It looks like the current code will parse `1e3e4' (as 1e4). */
            in_exp = true;
            exp_wait_sign = true;
            exp = 0;
            exp_sign = 1;
        }
        else if ((c == '+' || c == '-') && in_exp)
        {
            exp_sign = c == '+' ? 1 : -1;
        }
        else if (in_num)
        {
            /* end of number */

            val *= sign * pow (10, exp_sign * exp);
            if (ctx->rel)
            {
                /* Handle relative coordinates. This switch statement attempts
                   to determine _what_ the coords are relative to. This is
                   underspecified in the 12 Apr working draft. */
                switch (ctx->cmd)
                {
                case 'l':
                case 'm':
                case 'c':
                case 's':
                case 'q':
                case 't':
                    if ( ctx->param & 1 ) {
                        val += ctx->cpy; /* odd param, y */
                    } else {
                        val += ctx->cpx; /* even param, x */
                    }
                break;
                case 'a':
                    /* rule: sixth and seventh are x and y, rest are not
                       relative */
                    if (ctx->param == 5)
                        val += ctx->cpx;
                    else if (ctx->param == 6)
                        val += ctx->cpy;
                    break;
                case 'h':
                    /* rule: x-relative */
                    val += ctx->cpx;
                    break;
                case 'v':
                    /* rule: y-relative */
                    val += ctx->cpy;
                    break;
                }
            }
            ctx->params[ctx->param++] = val;
            rsvg_parse_path_do_cmd (ctx, false);
            if (c=='.') {
                in_num = true;
                val = 0;
                in_frac = true;
                in_exp = false;
                frac = 1;
            }
            else {
                in_num = false;
                in_frac = false;
                in_exp = false;
            }
        }

        if (c == '\0')
            break;
        else if ((c == '+' || c == '-') && !exp_wait_sign)
        {
            sign = c == '+' ? 1 : -1;;
            val = 0;
            in_num = true;
            in_frac = false;
            in_exp = false;
            exp = 0;
            exp_sign = 1;
            exp_wait_sign = false;
        }
        else if (c == 'z' || c == 'Z')
        {
            if (ctx->param)
                rsvg_parse_path_do_cmd (ctx, true);
            rsvg_bpath_def_closepath (ctx->bpath);

            ctx->cmd = 'm';
            ctx->params[0] = ctx->cpx = ctx->rpx = ctx->spx;
            ctx->params[1] = ctx->cpy = ctx->rpy = ctx->spy;
            ctx->param = 2;
        }
        else if (c >= 'A' && c <= 'Z' && c != 'E')
        {
            if (ctx->param)
                rsvg_parse_path_do_cmd (ctx, true);
            ctx->cmd = c + 'a' - 'A';
            ctx->rel = false;
        }
        else if (c >= 'a' && c <= 'z' && c != 'e')
        {
            if (ctx->param)
                rsvg_parse_path_do_cmd (ctx, true);
            ctx->cmd = c;
            ctx->rel = true;
        }
        /* else c _should_ be whitespace or , */
    }
}


NArtBpath *sp_svg_read_path(gchar const *str)
{
    RSVGParsePathCtx ctx;
    NArtBpath *bpath;

    ctx.bpath = gnome_canvas_bpath_def_new ();
    ctx.cpx = 0.0;
    ctx.cpy = 0.0;
    ctx.cmd = 0;
    ctx.param = 0;

    rsvg_parse_path_data (&ctx, str);

    if (ctx.param && ctx.cmd != 'm') {
        rsvg_parse_path_do_cmd (&ctx, TRUE);
    }

    gnome_canvas_bpath_def_art_finish (ctx.bpath);

    bpath = g_new (NArtBpath, ctx.bpath->n_bpath);
    memcpy (bpath, ctx.bpath->bpath, ctx.bpath->n_bpath * sizeof (NArtBpath));
    g_assert ((bpath + ctx.bpath->n_bpath - 1)->code == NR_END);
    gnome_canvas_bpath_def_unref (ctx.bpath);

    return bpath;
}

gchar *sp_svg_write_path(NArtBpath const *bpath)
{
    Inkscape::SVGOStringStream os;
    bool closed=false;
    
    g_return_val_if_fail (bpath != NULL, NULL);

    Inkscape::SVG::PathString str;

    for (int i = 0; bpath[i].code != NR_END; i++){
        switch (bpath [i].code){
        case NR_LINETO:
            str.lineTo(bpath[i].x3, bpath[i].y3);
            break;

        case NR_CURVETO:
            str.curveTo(bpath[i].x1, bpath[i].y1,
                        bpath[i].x2, bpath[i].y2,
                        bpath[i].x3, bpath[i].y3);
            break;

        case NR_MOVETO_OPEN:
        case NR_MOVETO:
            if (closed) {
                str.closePath();
            }
            closed = ( bpath[i].code == NR_MOVETO );
            str.moveTo(bpath[i].x3, bpath[i].y3);
            break;

        default:
            g_assert_not_reached ();
        }
    }
    if (closed) {
        str.closePath();
    }

    return g_strdup(str.c_str());
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :

Generated by  Doxygen 1.6.0   Back to index