/* Copyright (C) 2001-2015 Peter Selinger. This file is part of Potrace. It is free software and it is covered by the GNU General Public License. See the file COPYING for details. */ /* The SVG backend of Potrace. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "potracelib.h" #include "curve.h" #include "main.h" #include "backend_svg.h" #include "lists.h" #include "auxiliary.h" /* ---------------------------------------------------------------------- */ /* path-drawing auxiliary functions */ /* coordinate quantization */ static inline point_t unit(dpoint_t p) { point_t q; q.x = (long)(floor(p.x*info.unit+.5)); q.y = (long)(floor(p.y*info.unit+.5)); return q; } static point_t cur; static char lastop = 0; static int column = 0; static int newline = 1; static void shiptoken(FILE *fout, char *token) { int c = strlen(token); if (!newline && column+c+1 > 75) { fprintf(fout, "\n"); column = 0; newline = 1; } else if (!newline) { fprintf(fout, " "); column++; } fprintf(fout, "%s", token); column += c; newline = 0; } static void ship(FILE *fout, char *fmt, ...) { va_list args; static char buf[4096]; /* static string limit is okay here because we only use constant format strings - for the same reason, it is okay to use vsprintf instead of vsnprintf below. */ char *p, *q; va_start(args, fmt); vsprintf(buf, fmt, args); buf[4095] = 0; va_end(args); p = buf; while ((q = strchr(p, ' ')) != NULL) { *q = 0; shiptoken(fout, p); p = q+1; } shiptoken(fout, p); } static void svg_moveto(FILE *fout, dpoint_t p) { cur = unit(p); ship(fout, "M%ld %ld", cur.x, cur.y); lastop = 'M'; } static void svg_rmoveto(FILE *fout, dpoint_t p) { point_t q; q = unit(p); ship(fout, "m%ld %ld", q.x-cur.x, q.y-cur.y); cur = q; lastop = 'm'; } static void svg_lineto(FILE *fout, dpoint_t p) { point_t q; q = unit(p); if (lastop != 'l') { ship(fout, "l%ld %ld", q.x-cur.x, q.y-cur.y); } else { ship(fout, "%ld %ld", q.x-cur.x, q.y-cur.y); } cur = q; lastop = 'l'; } static void svg_curveto(FILE *fout, dpoint_t p1, dpoint_t p2, dpoint_t p3) { point_t q1, q2, q3; q1 = unit(p1); q2 = unit(p2); q3 = unit(p3); if (lastop != 'c') { ship(fout, "c%ld %ld %ld %ld %ld %ld", q1.x-cur.x, q1.y-cur.y, q2.x-cur.x, q2.y-cur.y, q3.x-cur.x, q3.y-cur.y); } else { ship(fout, "%ld %ld %ld %ld %ld %ld", q1.x-cur.x, q1.y-cur.y, q2.x-cur.x, q2.y-cur.y, q3.x-cur.x, q3.y-cur.y); } cur = q3; lastop = 'c'; } /* ---------------------------------------------------------------------- */ /* functions for converting a path to an SVG path element */ /* Explicit encoding. If abs is set, move to first coordinate absolutely. */ static int svg_path(FILE *fout, potrace_curve_t *curve, int abs) { int i; dpoint_t *c; int m = curve->n; c = curve->c[m-1]; if (abs) { svg_moveto(fout, c[2]); } else { svg_rmoveto(fout, c[2]); } for (i=0; ic[i]; switch (curve->tag[i]) { case POTRACE_CORNER: svg_lineto(fout, c[1]); svg_lineto(fout, c[2]); break; case POTRACE_CURVETO: svg_curveto(fout, c[0], c[1], c[2]); break; } } newline = 1; shiptoken(fout, "z"); return 0; } /* produce a jaggy path - for debugging. If abs is set, move to first coordinate absolutely. If abs is not set, move to first coordinate relatively, and traverse path in the opposite direction. */ static int svg_jaggy_path(FILE *fout, point_t *pt, int n, int abs) { int i; point_t cur, prev; if (abs) { cur = prev = pt[n-1]; svg_moveto(fout, dpoint(cur)); for (i=0; i=0; i--) { if (pt[i].x != cur.x && pt[i].y != cur.y) { cur = prev; svg_lineto(fout, dpoint(cur)); } prev = pt[i]; } svg_lineto(fout, dpoint(pt[0])); } newline = 1; shiptoken(fout, "z"); return 0; } static void write_paths_opaque(FILE *fout, potrace_path_t *tree) { potrace_path_t *p, *q; for (p=tree; p; p=p->sibling) { if (info.grouping == 2) { fprintf(fout, "\n"); fprintf(fout, "\n"); } column = fprintf(fout, "priv->pt, p->priv->len, 1); } else { svg_path(fout, &p->curve, 1); } fprintf(fout, "\"/>\n"); for (q=p->childlist; q; q=q->sibling) { column = fprintf(fout, "priv->pt, q->priv->len, 1); } else { svg_path(fout, &q->curve, 1); } fprintf(fout, "\"/>\n"); } if (info.grouping == 2) { fprintf(fout, "\n"); } for (q=p->childlist; q; q=q->sibling) { write_paths_opaque(fout, q->childlist); } if (info.grouping == 2) { fprintf(fout, "\n"); } } } static void write_paths_transparent_rec(FILE *fout, potrace_path_t *tree) { potrace_path_t *p, *q; for (p=tree; p; p=p->sibling) { if (info.grouping == 2) { fprintf(fout, "\n"); } if (info.grouping != 0) { column = fprintf(fout, "priv->pt, p->priv->len, 1); } else { svg_path(fout, &p->curve, 1); } for (q=p->childlist; q; q=q->sibling) { if (info.debug == 1) { svg_jaggy_path(fout, q->priv->pt, q->priv->len, 0); } else { svg_path(fout, &q->curve, 0); } } if (info.grouping != 0) { fprintf(fout, "\"/>\n"); } for (q=p->childlist; q; q=q->sibling) { write_paths_transparent_rec(fout, q->childlist); } if (info.grouping == 2) { fprintf(fout, "\n"); } } } static void write_paths_transparent(FILE *fout, potrace_path_t *tree) { if (info.grouping == 0) { column = fprintf(fout, "\n"); } } /* ---------------------------------------------------------------------- */ /* Backend. */ /* public interface for SVG */ int page_svg(FILE *fout, potrace_path_t *plist, imginfo_t *imginfo) { double bboxx = imginfo->trans.bb[0]+imginfo->lmar+imginfo->rmar; double bboxy = imginfo->trans.bb[1]+imginfo->tmar+imginfo->bmar; double origx = imginfo->trans.orig[0] + imginfo->lmar; double origy = bboxy - imginfo->trans.orig[1] - imginfo->bmar; double scalex = imginfo->trans.scalex / info.unit; double scaley = -imginfo->trans.scaley / info.unit; /* header */ fprintf(fout, "\n"); fprintf(fout, "\n"); /* set bounding box and namespace */ fprintf(fout, "\n"); /* metadata: creator */ fprintf(fout, "\n"); fprintf(fout, "Created by "POTRACE" "VERSION", written by Peter Selinger 2001-2015\n"); fprintf(fout, "\n"); /* use a "group" tag to establish coordinate system and style */ fprintf(fout, "\n", info.color); if (info.opaque) { write_paths_opaque(fout, plist); } else { write_paths_transparent(fout, plist); } /* write footer */ fprintf(fout, "\n"); fprintf(fout, "\n"); fflush(fout); return 0; } /* the Gimppath backend is identical, except that it disables --opaque, enables --flat, and the dimensions are pixel-based */ int page_gimp(FILE *fout, potrace_path_t *plist, imginfo_t *imginfo) { info.opaque = 0; info.grouping = 0; return page_svg(fout, plist, imginfo); }