#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 Lauris Kaplinski */ #include #include #include #include #include "libnr/n-art-bpath.h" #include "gnome-canvas-bpath-util.h" #include "stringstream.h" /* This module parses an SVG path element into an RsvgBpathDef. At present, there is no support for 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); for (int i = 0; bpath[i].code != NR_END; i++){ if (i) { os << " "; } switch (bpath [i].code){ case NR_LINETO: os << "L " << bpath[i].x3 << "," << bpath[i].y3; break; case NR_CURVETO: os << "C " << 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) { os << "z "; } closed = ( bpath[i].code == NR_MOVETO ); os << "M " << bpath[i].x3 << "," << bpath[i].y3; break; default: g_assert_not_reached (); } } if (closed) { os << " z "; } // std::string s = os.str(); // gchar *ret = g_strdup(s.c_str()); // delete (s); // return ret; return g_strdup (os.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 :