File indexing completed on 2024-12-22 04:04:08

0001 /* Copyright (C) 2001-2019 Peter Selinger.
0002    This file is part of Potrace. It is free software and it is covered
0003    by the GNU General Public License. See the file COPYING for details. */
0004 
0005 
0006 /* The Postscript backend of Potrace. This can produce "ps" or "eps"
0007    output, and different kinds of graphical debugging
0008    output. Postscript compression is optionally supplied via the
0009    functions in flate.c. */
0010 
0011 #ifdef HAVE_CONFIG_H
0012 #include <config.h>
0013 #endif
0014 
0015 #include <stdio.h>
0016 #include <stdarg.h>
0017 #include <string.h>
0018 #include <math.h>
0019 #include <stdlib.h>
0020 
0021 #include "potracelib.h"
0022 #include "curve.h"
0023 #include "main.h"
0024 #include "backend_eps.h"
0025 #include "flate.h"
0026 #include "lists.h"
0027 #include "auxiliary.h"
0028 
0029 #define SAFE_CALLOC(var, n, typ) \
0030   if ((var = (typ *)calloc(n, sizeof(typ))) == NULL) goto calloc_error 
0031 
0032 typedef int color_t;
0033 
0034 #define black  0x000000
0035 #define red    0xff0000
0036 #define green  0x008000
0037 #define blue   0x0000ff
0038 
0039 #define TRY(x) if (x) goto try_error
0040 
0041 /* ---------------------------------------------------------------------- */
0042 /* functions for interfacing with compression backend */
0043 
0044 /* xship: callback function that must be initialized before calling
0045    any other functions of the "ship" family. xship_file must be
0046    initialized too. */
0047 
0048 /* print the token to f, but filtered through a compression
0049    filter in case filter!=0 */
0050 static int (*xship)(FILE *f, int filter, const char *s, int len);
0051 static FILE *xship_file;
0052 
0053 /* ship postscript code, filtered */
0054 static int ship(const char *fmt, ...) {
0055   va_list args;
0056   static char buf[4096]; /* static string limit is okay here because
0057                 we only use constant format strings - for
0058                 the same reason, it is okay to use
0059                 vsprintf instead of vsnprintf below. */
0060   va_start(args, fmt);
0061   vsprintf(buf, fmt, args);
0062   buf[4095] = 0;
0063   va_end(args);
0064 
0065   xship(xship_file, 1, buf, strlen(buf));
0066   return 0;
0067 }  
0068 
0069 /* ship a postscript comment, unfiltered */
0070 static int shipcom(const char *fmt, ...) {
0071   static char buf[4096];
0072   va_list args;
0073 
0074   va_start(args, fmt);
0075   vsprintf(buf, fmt, args);
0076   buf[4095] = 0;
0077   va_end(args);
0078 
0079   xship(xship_file, 0, buf, strlen(buf));
0080   return 0;
0081 }
0082 
0083 /* set all callback functions */
0084 static void eps_callbacks(FILE *fout) {
0085   if (info.compress && info.pslevel==2) {
0086     xship = lzw_xship;
0087   } else if (info.compress && info.pslevel==3) {
0088     xship = flate_xship;
0089   } else {
0090     xship = dummy_xship;
0091   }
0092   xship_file = fout;
0093 }  
0094 
0095 /* ---------------------------------------------------------------------- */
0096 /* postscript path-drawing auxiliary functions */
0097 
0098 /* coordinate quantization */
0099 static inline point_t unit(dpoint_t p) {
0100   point_t q;
0101 
0102   q.x = (long)(floor(p.x*info.unit+.5));
0103   q.y = (long)(floor(p.y*info.unit+.5));
0104   return q;
0105 }
0106 
0107 /* current point */
0108 static point_t cur;
0109 
0110 static void eps_coords(dpoint_t p) {
0111   cur = unit(p);
0112   ship("%ld %ld ", cur.x, cur.y);
0113 }
0114 
0115 static void eps_rcoords(dpoint_t p) {
0116   point_t q;
0117 
0118   q = unit(p);
0119   ship("%ld %ld ", q.x-cur.x, q.y-cur.y);
0120   cur = q;
0121 }
0122 
0123 static void eps_moveto(dpoint_t p) {
0124   eps_coords(p);
0125   ship("moveto\n");
0126 }
0127 
0128 /* move to point + offset */
0129 static void eps_moveto_offs(dpoint_t p, double xoffs, double yoffs) {
0130   /* note: structs are passed by value, so the following assignment
0131      does not modify the original struct in the caller */
0132   p.x += xoffs;
0133   p.y += yoffs;
0134   eps_coords(p);
0135   ship("moveto\n");
0136 }
0137 
0138 static void eps_lineto(dpoint_t p) {
0139   eps_rcoords(p);
0140   ship("rlineto\n");
0141 }
0142 
0143 static void eps_curveto(dpoint_t p1, dpoint_t p2, dpoint_t p3) {
0144   point_t q1, q2, q3;
0145 
0146   q1 = unit(p1);
0147   q2 = unit(p2);
0148   q3 = unit(p3);
0149 
0150   ship("%ld %ld %ld %ld %ld %ld rcurveto\n", q1.x-cur.x, q1.y-cur.y, q2.x-cur.x, q2.y-cur.y, q3.x-cur.x, q3.y-cur.y);
0151   
0152   cur = q3;
0153 }
0154 
0155 /* this procedure returns a statically allocated string */
0156 static const char *eps_colorstring(const color_t col) {
0157   double r, g, b;
0158   static char buf[100];
0159 
0160   r = (col & 0xff0000) >> 16;
0161   g = (col & 0x00ff00) >> 8;
0162   b = (col & 0x0000ff) >> 0;
0163 
0164   if (r==0 && g==0 && b==0) {
0165     return "0 setgray";
0166   } else if (r==255 && g==255 && b==255) {
0167     return "1 setgray";
0168   } else if (r == g && g == b) {
0169     sprintf(buf, "%.3f setgray", r/255.0);
0170     return buf;
0171   } else {
0172     sprintf(buf, "%.3f %.3f %.3f setrgbcolor", r/255.0, g/255.0, b/255.0);
0173     return buf;
0174   }
0175 }
0176 
0177 static color_t eps_color = -1;
0178 static double eps_width = -1;
0179 
0180 static void eps_setcolor(const color_t col) {
0181   if (col == eps_color) {
0182     return;
0183   }
0184   eps_color = col;
0185 
0186   ship("%s\n", eps_colorstring(col));
0187 }
0188 
0189 static void eps_linewidth(double w) {
0190   if (w == eps_width) {
0191     return;
0192   }
0193   eps_width = w;
0194   ship("%f setlinewidth\n", w * info.unit);
0195 }
0196 
0197 /* ---------------------------------------------------------------------- */
0198 /* functions for converting a path to postscript code */
0199 
0200 /* ---------------------------------------------------------------------- */
0201 /* ASCII encoding */
0202 
0203 /* explicit encoding, does not use special macros */
0204 static int eps_path_long(privcurve_t *curve) {
0205   int i;
0206   dpoint_t *c;
0207   int m = curve->n;
0208 
0209   c = curve->c[m-1];
0210   eps_moveto(c[2]);
0211 
0212   for (i=0; i<m; i++) {
0213     c = curve->c[i];
0214     switch (curve->tag[i]) {
0215     case POTRACE_CORNER:
0216       eps_lineto(c[1]);
0217       eps_lineto(c[2]);
0218       break;
0219     case POTRACE_CURVETO:
0220       eps_curveto(c[0], c[1], c[2]);
0221       break;
0222     }
0223   }
0224   return 0;
0225 }
0226 
0227 /* size-optimized encoding relies on special macros */
0228 static int eps_path_short(privcurve_t *curve) {
0229   int i, i1;
0230   long int *bq = NULL;  /* bq[m] */
0231   long int *aq = NULL;  /* aq[m] */
0232   point_t *v = NULL;    /* v[m] */
0233   dpoint_t *q = NULL;   /* q[m] */
0234   double M;
0235   int m = curve->n;
0236 
0237   SAFE_CALLOC(bq, m, long int);
0238   SAFE_CALLOC(aq, m, long int);
0239   SAFE_CALLOC(v, m, point_t);
0240   SAFE_CALLOC(q, m, dpoint_t);
0241 
0242   /* quantize vertices */
0243   for (i=0; i<m; i++) {
0244     v[i] = unit(curve->vertex[i]);
0245   }
0246 
0247   /* quantize beta */
0248   for (i=0; i<m; i++) {
0249     i1 = mod(i+1,m);
0250     M = max(10, max(abs(v[i1].x-v[i].x), abs(v[i1].y-v[i].y)));
0251     bq[i] = (int)(M * curve->beta[i] + 0.5);
0252     if (curve->beta[i] != 0.5) {
0253       q[i1] = interval(bq[i]/M, dpoint(v[i]), dpoint(v[i1]));
0254     } else {
0255       q[i1] = interval(0.5, dpoint(v[i]), dpoint(v[i1]));
0256     }
0257   }
0258 
0259   /* quantize alpha */
0260   for (i=0; i<m; i++) {
0261     i1 = mod(i+1,m);
0262     M = max(10, max(max(abs(q[i].x-v[i].x), abs(q[i].y-v[i].y)),
0263             max(abs(v[i].x-q[i1].x), abs(v[i].y-q[i1].y))));
0264     if (curve->tag[i] == POTRACE_CURVETO) {
0265       aq[i] = (int)(M * curve->alpha[i] + 0.5);
0266       if (aq[i] > M) {
0267     aq[i]--;
0268       }
0269     }
0270   }
0271 
0272   /* generate output */
0273   ship("%ld %ld ", v[m-1].x, v[m-1].y);
0274   ship("%ld %ld ", v[0].x - v[m-1].x, v[0].y - v[m-1].y);
0275   if (curve->beta[m-1] == 0.5) {
0276     ship("i\n");
0277   } else {
0278     ship("%ld I\n", bq[m-1]);
0279   }
0280   for (i=0; i<m; i++) {
0281     if (i<m-1) {
0282       ship("%ld %ld ", v[i+1].x - v[i].x, v[i+1].y - v[i].y);
0283       if (curve->beta[i] != 0.5) {
0284     ship("%ld ", bq[i]);
0285       }
0286     }
0287     if (curve->tag[i] == POTRACE_CURVETO) {
0288       ship(curve->beta[i] == 0.5 ? "%ld c\n" : "%ld C\n", aq[i]);
0289     } else {
0290       ship(curve->beta[i] == 0.5 ? "v\n" : "V\n");
0291     }
0292   }  
0293 
0294   free(bq);
0295   free(aq);
0296   free(v);
0297   free(q);
0298   return 0;
0299 
0300  calloc_error:
0301   free(bq);
0302   free(aq);
0303   free(v);
0304   free(q);
0305   return 1;
0306 }
0307 
0308 static int eps_path(privcurve_t *curve) {
0309   if (info.longcoding==0 && curve->alphacurve) {
0310     return eps_path_short(curve);
0311   } else {
0312     return eps_path_long(curve);
0313   }
0314 }
0315 
0316 /* ---------------------------------------------------------------------- */
0317 /* functions for rendering various internal data structures, used to
0318    generate debugging output */
0319 
0320 /* output jaggie curve in grey */
0321 static void eps_jaggy(potrace_path_t *plist) {
0322   potrace_path_t *p;
0323   int i;
0324 
0325   ship(".9 setgray\n");
0326   list_forall (p, plist) {
0327     point_t *pt = p->priv->pt;
0328     point_t cur, prev;
0329 
0330     if (p->sign == '+') {
0331       cur = prev = pt[p->priv->len-1];
0332       eps_moveto(dpoint(cur));
0333       for (i=0; i<p->priv->len; i++) {
0334     if (pt[i].x != cur.x && pt[i].y != cur.y) {
0335       cur = prev;
0336       eps_lineto(dpoint(cur));
0337     }
0338     prev = pt[i];
0339       }
0340       eps_lineto(dpoint(pt[p->priv->len-1]));
0341     } else {
0342       cur = prev = pt[0];
0343       eps_moveto(dpoint(cur));
0344       for (i=p->priv->len-1; i>=0; i--) {
0345     if (pt[i].x != cur.x && pt[i].y != cur.y) {
0346           cur = prev;
0347           eps_lineto(dpoint(cur));
0348         }
0349         prev = pt[i];
0350       }
0351       eps_lineto(dpoint(pt[0]));
0352     }
0353     if (p->next == NULL || p->next->sign == '+') {
0354       ship("fill\n");
0355     }
0356   }
0357 }
0358 
0359 /* output polygon */
0360 static void eps_polygon(privcurve_t *curve, const color_t col) {
0361   int i;
0362   int m = curve->n;
0363 
0364   eps_linewidth(.02);
0365   eps_setcolor(col);
0366   eps_moveto(curve->vertex[m-1]);
0367   for (i=0; i<m; i++) {
0368     eps_lineto(curve->vertex[i]);
0369   }
0370   ship("stroke\n");
0371 }
0372 
0373 /* output lines L and parameter alpha */
0374 static void eps_L(privcurve_t *curve, const color_t col) {
0375   int i, i1;
0376   double gamma;
0377   dpoint_t p1, p4, p1l, p4l;
0378   int m = curve->n;
0379  
0380   for (i=0; i<m; i++) {
0381     i1 = mod(i+1, m);
0382     gamma = curve->alpha0[i1] * 0.75;
0383     
0384     p1 = curve->c[i][2];
0385     p4 = curve->c[i1][2];
0386     p1l = interval(gamma, p1, curve->vertex[i1]);
0387     p4l = interval(gamma, p4, curve->vertex[i1]);
0388     eps_linewidth(.02);
0389     eps_setcolor(col);
0390     eps_moveto(p1l);
0391     eps_lineto(p4l);
0392     ship("stroke\n");
0393     eps_moveto_offs(curve->vertex[i1], -.4, -.4);
0394     ship("times (%.2f) show\n", curve->alpha0[i1]);
0395   }
0396 }
0397 
0398 /* ---------------------------------------------------------------------- */
0399 /* postscript macros */
0400 
0401 /* special macros for size-optimized rendering of Bezier curves */
0402 static const char *optimacros =
0403   "/D{bind def}def\n"
0404   "/R{roll}D\n"
0405   "/K{copy}D\n"
0406   "/P{pop}D\n"
0407   "/p{3 2 R add 3 1 R add exch}D\n"
0408   "/t{dup 4 3 R mul 3 1 R mul}D\n"
0409   "/a{dup 1 sub neg 4 1 R t 5 2 R t p}D\n"
0410   "/m{2 K le{exch}if P}D\n"
0411   "/n{abs exch abs m}D\n"
0412   "/d{-1 t p n}D\n"
0413   "/s{[4 2 R] cvx def}D\n"
0414   "/g{7 K P 4 K P P d 5 1 R d 10 m m div 5 K 12 8 R 5 4 R a 9 4 R 3 2 R a 6 4 R curveto}D\n"
0415   "/e{4 2 R lineto lineto P P}D\n"
0416   "/q{3 K P n 10 m div}D\n"
0417   "/f{x y 7 4 R 5 1 R 4 K p /y s 7 2 R 2 K 9 7 R 7 6 R t p 2 K /x s}D\n"
0418   "/C{4 1 R q f 7 6 R g}D\n"
0419   "/V{q f e}D\n"
0420   "/c{3 1 R .5 f 7 6 R g}D\n"
0421   "/v{.5 f e}D\n"
0422   "/j{5 K P p /y s 3 K t 7 5 R p /x s x moveto P}D\n"
0423   "/i{.5 j}D\n"
0424   "/I{dup 6 1 R q j 3 2 R}D\n"
0425   "/z{closepath}D\n"
0426   "/b{%s z fill}D\n"
0427   "/w{%s z fill}D\n";
0428 
0429 /* special macros for debug output */
0430 static const char *debugmacros =
0431   "/unit { %f } def\n"
0432   "/box { newpath 0 0 moveto 0 1 lineto 1 1 lineto 1 0 lineto closepath } def\n"
0433   "/circ { newpath 0 0 1 0 360 arc closepath } def\n"
0434   "/dot { gsave .15 mul dup scale circ fill grestore } def\n"
0435   "/sq { gsave unit unit scale -.5 -.5 translate box .02 setlinewidth stroke grestore } def\n"
0436   "/sq1 { gsave translate sq unit .6 mul dot grestore } def\n"
0437   "/dot2 { gsave translate unit dot grestore } def\n"
0438   "/usq { gsave unit unit scale -.5 -.5 rmoveto 0 1 rlineto 1 0 rlineto 0 -1 rlineto closepath .02 setlinewidth stroke grestore } def\n"
0439   "/dot1 { gsave translate unit .3 mul dup scale circ fill grestore } def\n"
0440   "/times { /Times-Roman findfont unit .3 mul scalefont setfont } def\n"
0441   "/times1 { /Times-Roman findfont unit 10 mul scalefont setfont 0 0 0 setrgbcolor } def\n"
0442   "/times2 { /Times-Roman findfont unit 2 mul scalefont setfont 0 0 0 setrgbcolor } def\n";
0443 
0444 /* ---------------------------------------------------------------------- */
0445 /* Backends for various types of output. */
0446 
0447 /* Normal output: black on transparent */
0448 static int render0(potrace_path_t *plist) {
0449   potrace_path_t *p;
0450 
0451   if (info.longcoding) {
0452     eps_setcolor(info.color);
0453     list_forall (p, plist) {
0454       TRY(eps_path(p->priv->fcurve));
0455       ship("closepath\n");
0456       if (p->next == NULL || p->next->sign == '+') {
0457     ship("fill\n");
0458       }
0459     }
0460   } else {
0461     list_forall (p, plist) {
0462       TRY(eps_path(p->priv->fcurve));
0463       if (p->next == NULL || p->next->sign == '+') {
0464     ship("b\n");
0465       } else {
0466     ship("z\n");
0467       }
0468     }
0469   }
0470   return 0;
0471 
0472  try_error:
0473   return 1;
0474 }
0475 
0476 /* Opaque output: alternating black and white */
0477 static int render0_opaque(potrace_path_t *plist) {
0478   potrace_path_t *p;
0479   
0480   if (info.longcoding) {
0481     list_forall (p, plist) {
0482       TRY(eps_path(p->priv->fcurve));
0483       ship("closepath\n");
0484       eps_setcolor(p->sign=='+' ? info.color : info.fillcolor);
0485       ship("fill\n");
0486     }
0487   } else {
0488     list_forall (p, plist) {
0489       TRY(eps_path(p->priv->fcurve));
0490       ship(p->sign=='+' ? "b\n" : "w\n");
0491     }
0492   }
0493   return 0;
0494 
0495  try_error:
0496   return 1;
0497 }
0498 
0499 /* Debug output type 1 (show optimal polygon) */
0500 static int render1(potrace_path_t *plist) {
0501   potrace_path_t *p;
0502   int i;
0503 
0504   eps_jaggy(plist);
0505 
0506   list_forall (p, plist) {
0507 
0508     point_t *pt = p->priv->pt;
0509     int n = p->priv->len;
0510     int m = p->priv->m;
0511     int *po = p->priv->po;
0512 
0513     eps_linewidth(.02);
0514     eps_setcolor(black);
0515     /* output jaggie curve in boxed style */
0516     for (i=1; i<n; i++) {
0517       eps_moveto(dpoint(pt[i-1]));
0518       eps_lineto(dpoint(pt[i]));
0519       ship("stroke\n");
0520       eps_coords(dpoint(pt[i]));
0521       ship("sq1\n");
0522     }
0523     eps_moveto(dpoint(pt[n-1]));
0524     eps_lineto(dpoint(pt[0]));
0525     ship("stroke\n");
0526     eps_coords(dpoint(pt[0]));
0527     ship("sq1\n");
0528 
0529     /* output the uncorrected polygon */
0530     eps_linewidth(.1);
0531     eps_setcolor(blue);
0532     eps_moveto(dpoint(pt[po[0]]));
0533     for (i=1; i<m; i++) {
0534       eps_lineto(dpoint(pt[po[i]]));
0535     }
0536     eps_lineto(dpoint(pt[po[0]]));
0537     ship("stroke\n");
0538     for (i=0; i<m; i++) {
0539       eps_coords(dpoint(pt[po[i]]));
0540       ship("dot2\n");
0541     }
0542   }
0543   return 0;
0544 }
0545 
0546 /* Debug output type 2 (show corrected polygon and edge detection) */
0547 static int render2(potrace_path_t *plist) {
0548   potrace_path_t *p;
0549   int i;
0550 
0551   /* output original bitmap in grey */
0552   eps_jaggy(plist);
0553   
0554   list_forall (p, plist) {
0555     /* output polygon with corrected edges, lines L, and parameter alpha */
0556     eps_polygon(&p->priv->curve, black);
0557     eps_L(&p->priv->curve, black);
0558     
0559     /* output the vertex unit squares */
0560     for (i=0; i<p->priv->curve.n; i++) {
0561       eps_moveto(p->priv->curve.vertex[i]);
0562       ship("usq\n");
0563     }
0564 
0565     /* output the path */
0566     eps_linewidth(.1);
0567     eps_setcolor(blue);
0568     TRY(eps_path(&p->priv->curve));
0569     ship("closepath\n");
0570     ship("stroke\n");
0571 
0572     if (info.param->opticurve && info.debug == 3) {
0573 
0574       /* output opticurve */
0575       eps_linewidth(.05);
0576       eps_setcolor(red);
0577       TRY(eps_path(&p->priv->ocurve));
0578       ship("closepath\n");
0579       ship("stroke\n");
0580       
0581       /* output dots */
0582       for (i=0; i<p->priv->ocurve.n; i++) {
0583     eps_coords(p->priv->ocurve.c[i][2]);
0584     ship("dot1\n");
0585       }
0586     }
0587   }
0588   return 0;
0589 
0590  try_error:
0591   return 1;
0592 }
0593 
0594 /* Free-style debug output */
0595 static int render_debug(potrace_path_t *plist) {
0596   potrace_path_t *p;
0597   int count;
0598   int i;
0599 
0600   /* output original bitmap in grey */
0601   eps_jaggy(plist);
0602 
0603   count = -1;
0604   list_forall (p, plist) {
0605     count++;
0606 
0607     /* output path numbers */
0608     eps_moveto_offs(p->priv->curve.vertex[0], 0, 5);
0609     ship("times1 (%d) show\n", count);
0610 
0611     /* output polygon with corrected edges, lines L, and parameter alpha */
0612     eps_polygon(&p->priv->curve, black);
0613     eps_L(&p->priv->curve, black);
0614 
0615     /* output the vertex unit squares */
0616     for (i=0; i<p->priv->curve.n; i++) {
0617       eps_moveto(p->priv->curve.vertex[i]);
0618       ship("usq\n");
0619     }
0620 
0621     /* output the vertex numbers */
0622     for (i=0; i<p->priv->curve.n; i++) {
0623       eps_moveto_offs(p->priv->curve.vertex[i], +1, +1);
0624       ship("times2 (%d) show\n", i);
0625     }
0626     
0627     /* output the path */
0628     eps_linewidth(.1);
0629     eps_setcolor(blue);
0630     TRY(eps_path(&p->priv->curve));
0631     ship("closepath\n");
0632     ship("stroke\n");
0633     
0634     if (info.param->opticurve) {
0635 
0636       /* output the opti-verteces polygon */
0637       eps_polygon(&p->priv->ocurve, green);
0638       
0639       /* output opticurve */
0640       eps_linewidth(.05);
0641       eps_setcolor(red);
0642       TRY(eps_path(&p->priv->ocurve));
0643       ship("closepath\n");
0644       ship("stroke\n");
0645       
0646       /* output dots */
0647       for (i=0; i<p->priv->ocurve.n; i++) {
0648     eps_coords(p->priv->ocurve.c[i][2]);
0649     ship("dot1\n");
0650       }
0651 
0652       /* output beta parameters */
0653       for (i=0; i<p->priv->ocurve.n; i++) {
0654     eps_moveto_offs(p->priv->ocurve.c[i][2], +.4, -.4);
0655     ship("times (%.2f) show\n", p->priv->ocurve.beta[i]);
0656       }
0657     }
0658   }
0659   return 0;
0660 
0661  try_error:
0662   return 1;
0663 }
0664 
0665 /* select the appropriate rendering function from above */
0666 static int eps_render(potrace_path_t *plist) {
0667   int r;
0668   
0669   switch (info.debug) {
0670   case 0:
0671     if (info.opaque) {
0672       r = render0_opaque(plist);
0673     } else {
0674       r = render0(plist);
0675     }
0676     break;
0677   case 1:
0678     r = render1(plist);
0679     break;
0680   case 2: case 3:
0681     r = render2(plist);
0682     break;
0683   default:
0684     r = render_debug(plist);
0685     break;
0686   }
0687   return r;
0688 }  
0689 
0690 /* ---------------------------------------------------------------------- */
0691 /* EPS header and footer */
0692 
0693 static int eps_init(imginfo_t *imginfo) {
0694   double origx = imginfo->trans.orig[0] + imginfo->lmar;
0695   double origy = imginfo->trans.orig[1] + imginfo->bmar;
0696   double scalex = imginfo->trans.scalex / info.unit;
0697   double scaley = imginfo->trans.scaley / info.unit;
0698   char *c0, *c1;
0699 
0700   shipcom("%%!PS-Adobe-3.0 EPSF-3.0\n");
0701   shipcom("%%%%Creator: " POTRACE " " VERSION ", written by Peter Selinger 2001-2019\n");
0702   shipcom("%%%%LanguageLevel: %d\n", info.pslevel);
0703   shipcom("%%%%BoundingBox: 0 0 %.0f %.0f\n", 
0704       ceil(imginfo->trans.bb[0]+imginfo->lmar+imginfo->rmar),
0705       ceil(imginfo->trans.bb[1]+imginfo->tmar+imginfo->bmar));
0706   shipcom("%%%%HiResBoundingBox: 0 0 %f %f\n", 
0707       imginfo->trans.bb[0]+imginfo->lmar+imginfo->rmar,
0708       imginfo->trans.bb[1]+imginfo->tmar+imginfo->bmar);
0709   shipcom("%%%%Pages: 1\n");
0710   shipcom("%%%%EndComments\n");
0711   
0712   shipcom("%%%%Page: 1 1\n");
0713   ship("save\n");
0714   if (!info.longcoding) {
0715     c0 = strdup(eps_colorstring(info.color));
0716     c1 = strdup(eps_colorstring(info.fillcolor));
0717     if (!c0 || !c1) {
0718       free(c0);
0719       free(c1);
0720       return 1;
0721     }
0722     ship(optimacros, c0, c1);
0723     free(c0);
0724     free(c1);
0725   }
0726   if (info.debug) {
0727     ship(debugmacros, info.unit);
0728   }
0729   if (origx != 0 || origy != 0) {
0730     ship("%f %f translate\n", origx, origy);
0731   }
0732   if (info.angle != 0) {
0733     ship("%.2f rotate\n", info.angle);
0734   }
0735   ship("%f %f scale\n", scalex, scaley);
0736 
0737   return 0;
0738 }
0739 
0740 static void eps_term(void) {
0741   ship("restore\n");
0742   shipcom("%%%%EOF\n");
0743 }
0744 
0745 /* public interface for EPS */
0746 int page_eps(FILE *fout, potrace_path_t *plist, imginfo_t *imginfo) {
0747   eps_callbacks(fout);
0748 
0749   TRY(eps_init(imginfo));
0750   TRY(eps_render(plist));
0751   eps_term();
0752   return 0;
0753 
0754  try_error:
0755   return 1;
0756 }
0757 
0758 /* ---------------------------------------------------------------------- */
0759 /* PostScript header and footer */
0760 
0761 static int eps_pagenumber;
0762 
0763 int init_ps(FILE *fout) {
0764   char *c0, *c1;
0765 
0766   /* set callback functions for shipping routines */
0767   eps_callbacks(fout);
0768 
0769   shipcom("%%!PS-Adobe-3.0\n");
0770   shipcom("%%%%Creator: " POTRACE " " VERSION ", written by Peter Selinger 2001-2019\n");
0771   shipcom("%%%%LanguageLevel: %d\n", info.pslevel);
0772   shipcom("%%%%BoundingBox: 0 0 %d %d\n", info.paperwidth, info.paperheight);
0773   shipcom("%%%%Pages: (atend)\n");
0774   shipcom("%%%%EndComments\n");
0775   if (!info.longcoding || info.debug) {
0776     shipcom("%%%%BeginSetup\n");
0777     if (!info.longcoding) {
0778       c0 = strdup(eps_colorstring(info.color));
0779       c1 = strdup(eps_colorstring(info.fillcolor));
0780       if (!c0 || !c1) {
0781     free(c0);
0782     free(c1);
0783     return 1;
0784       }
0785       ship(optimacros, c0, c1);
0786       free(c0);
0787       free(c1);
0788     }
0789     if (info.debug) {
0790       ship(debugmacros, info.unit);
0791     }
0792     shipcom("%%%%EndSetup\n");
0793   }
0794   eps_pagenumber = 0;
0795   fflush(fout);
0796   return 0;
0797 }
0798 
0799 int term_ps(FILE *fout) {
0800   eps_callbacks(fout);
0801 
0802   shipcom("%%%%Trailer\n");
0803   shipcom("%%%%Pages: %d\n", eps_pagenumber);
0804   shipcom("%%%%EOF\n");
0805   fflush(fout);
0806 
0807   return 0;
0808 }
0809 
0810 static void eps_pageinit_ps(imginfo_t *imginfo) {
0811   double origx = imginfo->trans.orig[0] + imginfo->lmar;
0812   double origy = imginfo->trans.orig[1] + imginfo->bmar;
0813   double scalex = imginfo->trans.scalex / info.unit;
0814   double scaley = imginfo->trans.scaley / info.unit;
0815 
0816   eps_pagenumber++;
0817   eps_color = -1;
0818   eps_width = -1;
0819 
0820   shipcom("%%%%Page: %d %d\n", eps_pagenumber, eps_pagenumber);
0821   ship("save\n");
0822   if (origx != 0 || origy != 0) {
0823     ship("%f %f translate\n", origx, origy);
0824   }
0825   if (info.angle != 0) {
0826     ship("%.2f rotate\n", info.angle);
0827   }
0828   ship("%f %f scale\n", scalex, scaley);
0829 }
0830 
0831 static void eps_pageterm_ps(void) {
0832   ship("restore\n");
0833   ship("showpage\n");
0834 }
0835 
0836 int page_ps(FILE *fout, potrace_path_t *plist, imginfo_t *imginfo) {
0837   int r;
0838 
0839   eps_callbacks(fout);
0840 
0841   eps_pageinit_ps(imginfo);
0842 
0843   r = eps_render(plist);
0844   if (r) {
0845     return r;
0846   }
0847 
0848   eps_pageterm_ps();
0849 
0850   shipcom("");
0851 
0852   fflush(fout);
0853 
0854   return 0;
0855 }