xref: /potrace-1.14/src/main.c (revision b3fce824)
1 /* Copyright (C) 2001-2017 Peter Selinger.
2    This file is part of Potrace. It is free software and it is covered
3    by the GNU General Public License. See the file COPYING for details. */
4 
5 #define _XOPEN_SOURCE 500
6 
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <errno.h>
14 #include <string.h>
15 #include <strings.h>
16 #include <getopt.h>
17 #include <math.h>
18 
19 #include "main.h"
20 #include "potracelib.h"
21 #include "backend_pdf.h"
22 #include "backend_eps.h"
23 #include "backend_pgm.h"
24 #include "backend_svg.h"
25 #include "backend_xfig.h"
26 #include "backend_dxf.h"
27 #include "backend_geojson.h"
28 #include "potracelib.h"
29 #include "bitmap_io.h"
30 #include "bitmap.h"
31 #include "platform.h"
32 #include "auxiliary.h"
33 #include "progress_bar.h"
34 #include "trans.h"
35 
36 #ifndef M_PI
37 #define M_PI 3.14159265358979323846
38 #endif
39 
40 #define UNDEF ((double)(1e30))   /* a value to represent "undefined" */
41 
42 struct info_s info;
43 
44 /* ---------------------------------------------------------------------- */
45 /* some data structures for option processing */
46 
47 struct pageformat_s {
48   const char *name;
49   int w, h;
50 };
51 typedef struct pageformat_s pageformat_t;
52 
53 /* dimensions of the various page formats, in postscript points */
54 static pageformat_t pageformat[] = {
55   { "a4",        595,  842 },
56   { "a3",        842, 1191 },
57   { "a5",        421,  595 },
58   { "b5",        516,  729 },
59   { "letter",    612,  792 },
60   { "legal",     612, 1008 },
61   { "tabloid",   792, 1224 },
62   { "statement", 396,  612 },
63   { "executive", 540,  720 },
64   { "folio",     612,  936 },
65   { "quarto",    610,  780 },
66   { "10x14",     720, 1008 },
67   { NULL, 0, 0 },
68 };
69 
70 struct turnpolicy_s {
71   const char *name;
72   int n;
73 };
74 typedef struct turnpolicy_s turnpolicy_t;
75 
76 /* names of turn policies */
77 static turnpolicy_t turnpolicy[] = {
78   {"black",    POTRACE_TURNPOLICY_BLACK},
79   {"white",    POTRACE_TURNPOLICY_WHITE},
80   {"left",     POTRACE_TURNPOLICY_LEFT},
81   {"right",    POTRACE_TURNPOLICY_RIGHT},
82   {"minority", POTRACE_TURNPOLICY_MINORITY},
83   {"majority", POTRACE_TURNPOLICY_MAJORITY},
84   {"random",   POTRACE_TURNPOLICY_RANDOM},
85   {NULL, 0},
86 };
87 
88 /* backends and their characteristics */
89 struct backend_s {
90   const char *name;       /* name of this backend */
91   const char *ext;        /* file extension */
92   int fixed;        /* fixed page size backend? */
93   int pixel;        /* pixel-based backend? */
94   int multi;        /* multi-page backend? */
95   int (*init_f)(FILE *fout);                 /* initialization function */
96   int (*page_f)(FILE *fout, potrace_path_t *plist, imginfo_t *imginfo);
97                                              /* per-bitmap function */
98   int (*term_f)(FILE *fout);                 /* finalization function */
99   int opticurve;    /* opticurve capable (true Bezier curves?) */
100 };
101 typedef struct backend_s backend_t;
102 
103 static backend_t backend[] = {
104   { "eps",        ".eps", 0, 0, 0,   NULL,     page_eps,     NULL,     1 },
105   { "postscript", ".ps",  1, 0, 1,   init_ps,  page_ps,      term_ps,  1 },
106   { "ps",         ".ps",  1, 0, 1,   init_ps,  page_ps,      term_ps,  1 },
107   { "pdf",        ".pdf", 0, 0, 1,   init_pdf, page_pdf,     term_pdf, 1 },
108   { "pdfpage",    ".pdf", 1, 0, 1,   init_pdf, page_pdfpage, term_pdf, 1 },
109   { "svg",        ".svg", 0, 0, 0,   NULL,     page_svg,     NULL,     1 },
110   { "dxf",        ".dxf", 0, 1, 0,   NULL,     page_dxf,     NULL,     1 },
111   { "geojson",    ".json",0, 1, 0,   NULL,     page_geojson, NULL,     1 },
112   { "pgm",        ".pgm", 0, 1, 1,   NULL,     page_pgm,     NULL,     1 },
113   { "gimppath",   ".svg", 0, 1, 0,   NULL,     page_gimp,    NULL,     1 },
114   { "xfig",       ".fig", 1, 0, 0,   NULL,     page_xfig,    NULL,     0 },
115   { NULL, NULL, 0, 0, 0, NULL, NULL, NULL, 0 },
116 };
117 
118 /* look up a backend by name. If found, return 0 and set *bp. If not
119    found leave *bp unchanged and return 1, or 2 on ambiguous
120    prefix. */
backend_lookup(const char * name,backend_t ** bp)121 static int backend_lookup(const char *name, backend_t **bp) {
122   int i;
123   int m=0;  /* prefix matches */
124   backend_t *b = NULL;
125 
126   for (i=0; backend[i].name; i++) {
127     if (strcasecmp(backend[i].name, name)==0) {
128       *bp = &backend[i];
129       return 0;
130     } else if (strncasecmp(backend[i].name, name, strlen(name))==0) {
131       m++;
132       b = &backend[i];
133     }
134   }
135   /* if there was no exact match, and exactly one prefix match, use that */
136   if (m==1) {
137     *bp = b;
138     return 0;
139   } else if (m) {
140     return 2;
141   } else {
142     return 1;
143   }
144 }
145 
146 /* list all available backends by name, in a comma separated list.
147    Assume the cursor starts in column j, and break lines at length
148    linelen. Do not output any trailing punctuation. Return the column
149    the cursor is in. */
backend_list(FILE * fout,int j,int linelen)150 static int backend_list(FILE *fout, int j, int linelen) {
151   int i;
152 
153   for (i=0; backend[i].name; i++) {
154     if (j + (int)strlen(backend[i].name) > linelen) {
155       fprintf(fout, "\n");
156       j = 0;
157     }
158     j += fprintf(fout, "%s", backend[i].name);
159     if (backend[i+1].name) {
160       j += fprintf(fout, ", ");
161     }
162   }
163   return j;
164 }
165 
166 /* ---------------------------------------------------------------------- */
167 /* some info functions */
168 
license(FILE * f)169 static void license(FILE *f) {
170   fprintf(f,
171   "This program is free software; you can redistribute it and/or modify\n"
172   "it under the terms of the GNU General Public License as published by\n"
173   "the Free Software Foundation; either version 2 of the License, or\n"
174   "(at your option) any later version.\n"
175   "\n"
176   "This program is distributed in the hope that it will be useful,\n"
177   "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
178   "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
179   "GNU General Public License for more details.\n"
180   "\n"
181   "You should have received a copy of the GNU General Public License\n"
182   "along with this program; if not, write to the Free Software Foundation\n"
183   "Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n"
184 	  );
185 }
186 
show_defaults(FILE * f)187 static void show_defaults(FILE *f) {
188   fprintf(f, "Default unit: " DEFAULT_DIM_NAME "\n");
189   fprintf(f, "Default page size: " DEFAULT_PAPERFORMAT "\n");
190 }
191 
usage(FILE * f)192 static void usage(FILE *f) {
193   int j;
194 
195   fprintf(f, "Usage: " POTRACE " [options] [filename...]\n");
196   fprintf(f, "General options:\n");
197   fprintf(f, " -h, --help                 - print this help message and exit\n");
198   fprintf(f, " -v, --version              - print version info and exit\n");
199   fprintf(f, " -l, --license              - print license info and exit\n");
200   fprintf(f, "File selection:\n");
201   fprintf(f, " <filename>                 - an input file\n");
202   fprintf(f, " -o, --output <filename>    - write all output to this file\n");
203   fprintf(f, " --                         - end of options; 0 or more input filenames follow\n");
204   fprintf(f, "Backend selection:\n");
205   fprintf(f, " -b, --backend <name>       - select backend by name\n");
206   fprintf(f, " -e, --eps                  - EPS backend (encapsulated PostScript) (default)\n");
207   fprintf(f, " -p, --postscript           - PostScript backend\n");
208   fprintf(f, " -s, --svg                  - SVG backend (scalable vector graphics)\n");
209   fprintf(f, " -g, --pgm                  - PGM backend (portable greymap)\n");
210   fprintf(f, " -b pdf                     - PDF backend (portable document format)\n");
211   fprintf(f, " -b pdfpage                 - fixed page-size PDF backend\n");
212   fprintf(f, " -b dxf                     - DXF backend (drawing interchange format)\n");
213   fprintf(f, " -b geojson                 - GeoJSON backend\n");
214   fprintf(f, " -b gimppath                - Gimppath backend (GNU Gimp)\n");
215   fprintf(f, " -b xfig                    - XFig backend\n");
216   fprintf(f, "Algorithm options:\n");
217   fprintf(f, " -z, --turnpolicy <policy>  - how to resolve ambiguities in path decomposition\n");
218   fprintf(f, " -t, --turdsize <n>         - suppress speckles of up to this size (default 2)\n");
219   fprintf(f, " -a, --alphamax <n>         - corner threshold parameter (default 1)\n");
220   fprintf(f, " -n, --longcurve            - turn off curve optimization\n");
221   fprintf(f, " -O, --opttolerance <n>     - curve optimization tolerance (default 0.2)\n");
222   fprintf(f, " -u, --unit <n>             - quantize output to 1/unit pixels (default 10)\n");
223   fprintf(f, " -d, --debug <n>            - produce debugging output of type n (n=1,2,3)\n");
224   fprintf(f, "Scaling and placement options:\n");
225   fprintf(f, " -P, --pagesize <format>    - page size (default is " DEFAULT_PAPERFORMAT ")\n");
226   fprintf(f, " -W, --width <dim>          - width of output image\n");
227   fprintf(f, " -H, --height <dim>         - height of output image\n");
228   fprintf(f, " -r, --resolution <n>[x<n>] - resolution (in dpi) (dimension-based backends)\n");
229   fprintf(f, " -x, --scale <n>[x<n>]      - scaling factor (pixel-based backends)\n");
230   fprintf(f, " -S, --stretch <n>          - yresolution/xresolution\n");
231   fprintf(f, " -A, --rotate <angle>       - rotate counterclockwise by angle\n");
232   fprintf(f, " -M, --margin <dim>         - margin\n");
233   fprintf(f, " -L, --leftmargin <dim>     - left margin\n");
234   fprintf(f, " -R, --rightmargin <dim>    - right margin\n");
235   fprintf(f, " -T, --topmargin <dim>      - top margin\n");
236   fprintf(f, " -B, --bottommargin <dim>   - bottom margin\n");
237   fprintf(f, " --tight                    - remove whitespace around the input image\n");
238   fprintf(f, "Color options, supported by some backends:\n");
239   fprintf(f, " -C, --color #rrggbb        - set foreground color (default black)\n");
240   fprintf(f, " --fillcolor #rrggbb        - set fill color (default transparent)\n");
241   fprintf(f, " --opaque                   - make white shapes opaque\n");
242   fprintf(f, "SVG options:\n");
243   fprintf(f, " --group                    - group related paths together\n");
244   fprintf(f, " --flat                     - whole image as a single path\n");
245   fprintf(f, "Postscript/EPS/PDF options:\n");
246   fprintf(f, " -c, --cleartext            - do not compress the output\n");
247   fprintf(f, " -2, --level2               - use postscript level 2 compression (default)\n");
248 #ifdef HAVE_ZLIB
249   fprintf(f, " -3, --level3               - use postscript level 3 compression\n");
250 #endif
251   fprintf(f, " -q, --longcoding           - do not optimize for file size\n");
252   fprintf(f, "PGM options:\n");
253   fprintf(f, " -G, --gamma <n>            - gamma value for anti-aliasing (default 2.2)\n");
254   fprintf(f, "Frontend options:\n");
255   fprintf(f, " -k, --blacklevel <n>       - black/white cutoff in input file (default 0.5)\n");
256   fprintf(f, " -i, --invert               - invert bitmap\n");
257   fprintf(f, "Progress bar options:\n");
258   fprintf(f, " --progress                 - show progress bar\n");
259   fprintf(f, " --tty <mode>               - progress bar rendering: vt100 or dumb\n");
260   fprintf(f, "\n");
261   fprintf(f, "Dimensions can have optional units, e.g. 6.5in, 15cm, 100pt.\n");
262   fprintf(f, "Default is " DEFAULT_DIM_NAME " (or pixels for pgm, dxf, and gimppath backends).\n");
263   fprintf(f, "Possible input file formats are: pnm (pbm, pgm, ppm), bmp.\n");
264   j = fprintf(f, "Backends are: ");
265   backend_list(f, j, 78);
266   fprintf(f, ".\n");
267 }
268 
269 /* ---------------------------------------------------------------------- */
270 /* auxiliary functions for parameter parsing */
271 
272 /* parse a dimension of the kind "1.5in", "7cm", etc. Return result in
273    postscript points (=1/72 in). If endptr!=NULL, store pointer to
274    next character in *endptr in the manner of strtod(3). */
parse_dimension(char * s,char ** endptr)275 static dim_t parse_dimension(char *s, char **endptr) {
276   char *p;
277   dim_t res;
278 
279   res.x = strtod(s, &p);
280   res.d = 0;
281   if (p!=s) {
282     if (!strncasecmp(p, "in", 2)) {
283       res.d = DIM_IN;
284       p += 2;
285     } else if (!strncasecmp(p, "cm", 2)) {
286       res.d = DIM_CM;
287       p += 2;
288     } else if (!strncasecmp(p, "mm", 2)) {
289       res.d = DIM_MM;
290       p += 2;
291     } else if (!strncasecmp(p, "pt", 2)) {
292       res.d = DIM_PT;
293       p += 2;
294     }
295   }
296   if (endptr!=NULL) {
297     *endptr = p;
298   }
299   return res;
300 }
301 
302 /* parse a pair of dimensions, such as "8.5x11in", "30mmx4cm" */
parse_dimensions(char * s,char ** endptr,dim_t * dxp,dim_t * dyp)303 static void parse_dimensions(char *s, char **endptr, dim_t *dxp, dim_t *dyp) {
304   char *p, *q;
305   dim_t dx, dy;
306 
307   dx = parse_dimension(s, &p);
308   if (p==s) {
309     goto fail;
310   }
311   if (*p != 'x') {
312     goto fail;
313   }
314   p++;
315   dy = parse_dimension(p, &q);
316   if (q==p) {
317     goto fail;
318   }
319   if (dx.d && !dy.d) {
320     dy.d = dx.d;
321   } else if (!dx.d && dy.d) {
322     dx.d = dy.d;
323   }
324   *dxp = dx;
325   *dyp = dy;
326   if (endptr != NULL) {
327     *endptr = q;
328   }
329   return;
330 
331  fail:
332   dx.x = dx.d = dy.x = dy.d = 0;
333   *dxp = dx;
334   *dyp = dy;
335   if (endptr != NULL) {
336     *endptr = s;
337   }
338   return;
339 }
340 
double_of_dim(dim_t d,double def)341 static inline double double_of_dim(dim_t d, double def) {
342   if (d.d) {
343     return d.x * d.d;
344   } else {
345     return d.x * def;
346   }
347 }
348 
parse_color(char * s)349 static int parse_color(char *s) {
350   int i, d;
351   int col = 0;
352 
353   if (s[0] != '#' || strlen(s) != 7) {
354     return -1;
355   }
356   for (i=0; i<6; i++) {
357     d = s[6-i];
358     if (d >= '0' && d <= '9') {
359       col |= (d-'0') << (4*i);
360     } else if (d >= 'a' && d <= 'f') {
361       col |= (d-'a'+10) << (4*i);
362     } else if (d >= 'A' && d <= 'F') {
363       col |= (d-'A'+10) << (4*i);
364     } else {
365       return -1;
366     }
367   }
368   return col;
369 }
370 
371 /* ---------------------------------------------------------------------- */
372 /* option processing */
373 
374 /* codes for options that don't have short form */
375 enum {
376   OPT_TIGHT = 300,
377   OPT_FILLCOLOR,
378   OPT_OPAQUE,
379   OPT_GROUP,
380   OPT_FLAT,
381   OPT_PROGRESS,
382   OPT_TTY
383 };
384 
385 static struct option longopts[] = {
386   {"help",          0, 0, 'h'},
387   {"version",       0, 0, 'v'},
388   {"show-defaults", 0, 0, 'V'}, /* undocumented option for compatibility */
389   {"license",       0, 0, 'l'},
390   {"width",         1, 0, 'W'},
391   {"height",        1, 0, 'H'},
392   {"resolution",    1, 0, 'r'},
393   {"scale",         1, 0, 'x'},
394   {"stretch",       1, 0, 'S'},
395   {"margin",        1, 0, 'M'},
396   {"leftmargin",    1, 0, 'L'},
397   {"rightmargin",   1, 0, 'R'},
398   {"topmargin",     1, 0, 'T'},
399   {"bottommargin",  1, 0, 'B'},
400   {"tight",         0, 0, OPT_TIGHT},
401   {"rotate",        1, 0, 'A'},
402   {"pagesize",      1, 0, 'P'},
403   {"turdsize",      1, 0, 't'},
404   {"unit",          1, 0, 'u'},
405   {"cleartext",     0, 0, 'c'},
406   {"level2",        0, 0, '2'},
407   {"level3",        0, 0, '3'},
408   {"eps",           0, 0, 'e'},
409   {"postscript",    0, 0, 'p'},
410   {"svg",           0, 0, 's'},
411   {"pgm",           0, 0, 'g'},
412   {"backend",       1, 0, 'b'},
413   {"debug",         1, 0, 'd'},
414   {"color",         1, 0, 'C'},
415   {"fillcolor",     1, 0, OPT_FILLCOLOR},
416   {"turnpolicy",    1, 0, 'z'},
417   {"gamma",         1, 0, 'G'},
418   {"longcurve",     0, 0, 'n'},
419   {"longcoding",    0, 0, 'q'},
420   {"alphamax",      1, 0, 'a'},
421   {"opttolerance",  1, 0, 'O'},
422   {"output",        1, 0, 'o'},
423   {"blacklevel",    1, 0, 'k'},
424   {"invert",        0, 0, 'i'},
425   {"opaque",        0, 0, OPT_OPAQUE},
426   {"group",         0, 0, OPT_GROUP},
427   {"flat",          0, 0, OPT_FLAT},
428   {"progress",      0, 0, OPT_PROGRESS},
429   {"tty",           1, 0, OPT_TTY},
430 
431   {0, 0, 0, 0}
432 };
433 
434 static const char *shortopts = "hvVlW:H:r:x:S:M:L:R:T:B:A:P:t:u:c23epsgb:d:C:z:G:nqa:O:o:k:i";
435 
dopts(int ac,char * av[])436 static void dopts(int ac, char *av[]) {
437   int c;
438   char *p;
439   int i, j, r;
440   dim_t dim, dimx, dimy;
441   int matches, bestmatch;
442 
443   /* defaults */
444   backend_lookup("eps", &info.backend);
445   info.debug = 0;
446   info.width_d.x = UNDEF;
447   info.height_d.x = UNDEF;
448   info.rx = UNDEF;
449   info.ry = UNDEF;
450   info.sx = UNDEF;
451   info.sy = UNDEF;
452   info.stretch = 1;
453   info.lmar_d.x = UNDEF;
454   info.rmar_d.x = UNDEF;
455   info.tmar_d.x = UNDEF;
456   info.bmar_d.x = UNDEF;
457   info.angle = 0;
458   info.paperwidth = DEFAULT_PAPERWIDTH;
459   info.paperheight = DEFAULT_PAPERHEIGHT;
460   info.tight = 0;
461   info.unit = 10;
462   info.compress = 1;
463   info.pslevel = 2;
464   info.color = 0x000000;
465   info.gamma = 2.2;
466   info.param = potrace_param_default();
467   if (!info.param) {
468     fprintf(stderr, "" POTRACE ": %s\n", strerror(errno));
469     exit(2);
470   }
471   info.longcoding = 0;
472   info.outfile = NULL;
473   info.blacklevel = 0.5;
474   info.invert = 0;
475   info.opaque = 0;
476   info.grouping = 1;
477   info.fillcolor = 0xffffff;
478   info.progress = 0;
479   info.progress_bar = DEFAULT_PROGRESS_BAR;
480 
481   while ((c = getopt_long(ac, av, shortopts, longopts, NULL)) != -1) {
482     switch (c) {
483     case 'h':
484       fprintf(stdout, "" POTRACE " " VERSION ". Transforms bitmaps into vector graphics.\n\n");
485       usage(stdout);
486       exit(0);
487       break;
488     case 'v':
489     case 'V':
490       fprintf(stdout, "" POTRACE " " VERSION ". Copyright (C) 2001-2017 Peter Selinger.\n");
491       fprintf(stdout, "Library version: %s\n", potrace_version());
492       show_defaults(stdout);
493       exit(0);
494       break;
495     case 'l':
496       fprintf(stdout, "" POTRACE " " VERSION ". Copyright (C) 2001-2017 Peter Selinger.\n\n");
497       license(stdout);
498       exit(0);
499       break;
500     case 'W':
501       info.width_d = parse_dimension(optarg, &p);
502       if (*p) {
503 	fprintf(stderr, "" POTRACE ": invalid dimension -- %s\n", optarg);
504 	exit(1);
505       }
506       break;
507     case 'H':
508       info.height_d = parse_dimension(optarg, &p);
509       if (*p) {
510 	fprintf(stderr, "" POTRACE ": invalid dimension -- %s\n", optarg);
511 	exit(1);
512       }
513       break;
514     case 'r':
515       parse_dimensions(optarg, &p, &dimx, &dimy);
516       if (*p == 0 && dimx.d == 0 && dimy.d == 0 && dimx.x != 0.0 && dimy.x != 0.0) {
517 	info.rx = dimx.x;
518 	info.ry = dimy.x;
519 	break;
520       }
521       dim = parse_dimension(optarg, &p);
522       if (*p == 0 && dim.d == 0 && dim.x != 0.0) {
523 	info.rx = info.ry = dim.x;
524 	break;
525       }
526       fprintf(stderr, "" POTRACE ": invalid resolution -- %s\n", optarg);
527       exit(1);
528       break;
529     case 'x':
530       parse_dimensions(optarg, &p, &dimx, &dimy);
531       if (*p == 0 && dimx.d == 0 && dimy.d == 0) {
532 	info.sx = dimx.x;
533 	info.sy = dimy.x;
534 	break;
535       }
536       dim = parse_dimension(optarg, &p);
537       if (*p == 0 && dim.d == 0) {
538 	info.sx = info.sy = dim.x;
539 	break;
540       }
541       fprintf(stderr, "" POTRACE ": invalid scaling factor -- %s\n", optarg);
542       exit(1);
543       break;
544     case 'S':
545       info.stretch = atof(optarg);
546       break;
547     case 'M':
548       info.lmar_d = parse_dimension(optarg, &p);
549       if (*p) {
550 	fprintf(stderr, "" POTRACE ": invalid dimension -- %s\n", optarg);
551 	exit(1);
552       }
553       info.rmar_d = info.tmar_d = info.bmar_d = info.lmar_d;
554       break;
555     case 'L':
556       info.lmar_d = parse_dimension(optarg, &p);
557       if (*p) {
558 	fprintf(stderr, "" POTRACE ": invalid dimension -- %s\n", optarg);
559 	exit(1);
560       }
561       break;
562     case 'R':
563       info.rmar_d = parse_dimension(optarg, &p);
564       if (*p) {
565 	fprintf(stderr, "" POTRACE ": invalid dimension -- %s\n", optarg);
566 	exit(1);
567       }
568       break;
569     case 'T':
570       info.tmar_d = parse_dimension(optarg, &p);
571       if (*p) {
572 	fprintf(stderr, "" POTRACE ": invalid dimension -- %s\n", optarg);
573 	exit(1);
574       }
575       break;
576     case 'B':
577       info.bmar_d = parse_dimension(optarg, &p);
578       if (*p) {
579 	fprintf(stderr, "" POTRACE ": invalid dimension -- %s\n", optarg);
580 	exit(1);
581       }
582       break;
583     case OPT_TIGHT:
584       info.tight = 1;
585       break;
586     case 'A':
587       info.angle = strtod(optarg, &p);
588       if (*p) {
589 	fprintf(stderr, "" POTRACE ": invalid angle -- %s\n", optarg);
590 	exit(1);
591       }
592       break;
593     case 'P':
594       matches = 0;
595       bestmatch = 0;
596       for (i=0; pageformat[i].name!=NULL; i++) {
597         if (strcasecmp(pageformat[i].name, optarg)==0) {
598           matches = 1;
599           bestmatch = i;
600           break;
601 	} else if (strncasecmp(pageformat[i].name, optarg, strlen(optarg))==0) {
602 	  /* don't allow partial match on "10x14" */
603 	  if (optarg[0] != '1') {
604 	    matches++;
605 	    bestmatch = i;
606 	  }
607 	}
608       }
609       if (matches == 1) {
610 	info.paperwidth = pageformat[bestmatch].w;
611 	info.paperheight = pageformat[bestmatch].h;
612 	break;
613       }
614       parse_dimensions(optarg, &p, &dimx, &dimy);
615       if (*p == 0) {
616 	info.paperwidth = (int)rint(double_of_dim(dimx, DEFAULT_DIM));
617 	info.paperheight = (int)rint(double_of_dim(dimy, DEFAULT_DIM));
618 	break;
619       }
620       if (matches == 0) {
621 	fprintf(stderr, "" POTRACE ": unrecognized page format -- %s\n", optarg);
622       } else {
623 	fprintf(stderr, "" POTRACE ": ambiguous page format -- %s\n", optarg);
624       }
625       j = fprintf(stderr, "Use one of: ");
626       for (i=0; pageformat[i].name!=NULL; i++) {
627 	if (j + strlen(pageformat[i].name) > 75) {
628 	  fprintf(stderr, "\n");
629 	  j = 0;
630 	}
631 	j += fprintf(stderr, "%s, ", pageformat[i].name);
632       }
633       fprintf(stderr, "or specify <dim>x<dim>.\n");
634       exit(1);
635       break;
636     case 't':
637       info.param->turdsize = atoi(optarg);
638       break;
639     case 'u':
640       info.unit = strtod(optarg, &p);
641       if (*p) {
642         fprintf(stderr, "" POTRACE ": invalid unit -- %s\n", optarg);
643         exit(1);
644       }
645       break;
646     case 'c':
647       info.pslevel = 2;
648       info.compress = 0;
649       break;
650     case '2':
651       info.pslevel = 2;
652       info.compress = 1;
653       break;
654     case '3':
655 #ifdef HAVE_ZLIB
656       info.pslevel = 3;
657       info.compress = 1;
658 #else
659       fprintf(stderr, "" POTRACE ": option -3 not supported, using -2 instead.\n");
660       fflush(stderr);
661       info.pslevel = 2;
662       info.compress = 1;
663 #endif
664       break;
665     case 'e':
666       backend_lookup("eps", &info.backend);
667       break;
668     case 'p':
669       backend_lookup("postscript", &info.backend);
670       break;
671     case 's':
672       backend_lookup("svg", &info.backend);
673       break;
674     case 'g':
675       backend_lookup("pgm", &info.backend);
676       break;
677     case 'b':
678       r = backend_lookup(optarg, &info.backend);
679       if (r==1 || r==2) {
680 	if (r==1) {
681 	  fprintf(stderr, "" POTRACE ": unrecognized backend -- %s\n", optarg);
682 	} else {
683 	  fprintf(stderr, "" POTRACE ": ambiguous backend -- %s\n", optarg);
684 	}
685 	j = fprintf(stderr, "Use one of: ");
686 	backend_list(stderr, j, 70);
687 	fprintf(stderr, ".\n");
688 	exit(1);
689       }
690       break;
691     case 'd':
692       info.debug = atoi(optarg);
693       break;
694     case 'C':
695       info.color = parse_color(optarg);
696       if (info.color == -1) {
697 	fprintf(stderr, "" POTRACE ": invalid color -- %s\n", optarg);
698 	exit(1);
699       }
700       break;
701     case OPT_FILLCOLOR:
702       info.fillcolor = parse_color(optarg);
703       if (info.fillcolor == -1) {
704 	fprintf(stderr, "" POTRACE ": invalid color -- %s\n", optarg);
705 	exit(1);
706       }
707       info.opaque = 1;
708       break;
709     case 'z':
710       matches = 0;
711       bestmatch = 0;
712       for (i=0; turnpolicy[i].name!=NULL; i++) {
713         if (strcasecmp(turnpolicy[i].name, optarg)==0) {
714 	  matches = 1;
715 	  bestmatch = i;
716           break;
717 	} else if (strncasecmp(turnpolicy[i].name, optarg, strlen(optarg))==0) {
718 	  matches++;
719 	  bestmatch = i;
720 	}
721       }
722       if (matches == 1) {
723 	info.param->turnpolicy = turnpolicy[bestmatch].n;
724 	break;
725       }
726       if (matches == 0) {
727 	fprintf(stderr, "" POTRACE ": unrecognized turnpolicy -- %s\n", optarg);
728       } else {
729 	fprintf(stderr, "" POTRACE ": ambiguous turnpolicy -- %s\n", optarg);
730       }
731       j = fprintf(stderr, "Use one of: ");
732       for (i=0; turnpolicy[i].name!=NULL; i++) {
733 	if (j + strlen(turnpolicy[i].name) > 75) {
734 	  fprintf(stderr, "\n");
735 	  j = 0;
736 	}
737 	j += fprintf(stderr, "%s%s", turnpolicy[i].name, turnpolicy[i+1].name ? ", " : "");
738       }
739       fprintf(stderr, ".\n");
740       exit(1);
741       break;
742     case 'G':
743       info.gamma = atof(optarg);
744       break;
745     case 'n':
746       info.param->opticurve = 0;
747       break;
748     case 'q':
749       info.longcoding = 1;
750       break;
751     case 'a':
752       info.param->alphamax = strtod(optarg, &p);
753       if (*p) {
754 	fprintf(stderr, "" POTRACE ": invalid alphamax -- %s\n", optarg);
755 	exit(1);
756       }
757       break;
758     case 'O':
759       info.param->opttolerance = strtod(optarg, &p);
760       if (*p) {
761 	fprintf(stderr, "" POTRACE ": invalid opttolerance -- %s\n", optarg);
762 	exit(1);
763       }
764       break;
765     case 'o':
766       free(info.outfile);
767       info.outfile = strdup(optarg);
768       if (!info.outfile) {
769 	fprintf(stderr, "" POTRACE ": %s\n", strerror(errno));
770         exit(2);
771       }
772       break;
773     case 'k':
774       info.blacklevel = strtod(optarg, &p);
775       if (*p) {
776 	fprintf(stderr, "" POTRACE ": invalid blacklevel -- %s\n", optarg);
777 	exit(1);
778       }
779       break;
780     case 'i':
781       info.invert = 1;
782       break;
783     case OPT_OPAQUE:
784       info.opaque = 1;
785       break;
786     case OPT_GROUP:
787       info.grouping = 2;
788       break;
789     case OPT_FLAT:
790       info.grouping = 0;
791       break;
792     case OPT_PROGRESS:
793       info.progress = 1;
794       break;
795     case OPT_TTY:
796       if (strcmp(optarg, "dumb") == 0) {
797 	info.progress_bar = progress_bar_simplified;
798       } else if (strcmp(optarg, "vt100") == 0) {
799 	info.progress_bar = progress_bar_vt100;
800       } else {
801 	fprintf(stderr, "" POTRACE ": invalid tty mode -- %s. Try --help for more info\n", optarg);
802 	exit(1);
803       }
804       break;
805     case '?':
806       fprintf(stderr, "Try --help for more info\n");
807       exit(1);
808       break;
809     default:
810       fprintf(stderr, "" POTRACE ": Unimplemented option -- %c\n", c);
811       exit(1);
812     }
813   }
814   info.infiles = &av[optind];
815   info.infilecount = ac-optind;
816   info.some_infiles = info.infilecount ? 1 : 0;
817 
818   /* if "--" was used, even an empty list of filenames is considered
819      "some" filenames. */
820   if (strcmp(av[optind-1], "--") == 0) {
821     info.some_infiles = 1;
822   }
823 }
824 
825 /* ---------------------------------------------------------------------- */
826 /* calculations with bitmap dimensions, positioning etc */
827 
828 /* determine the dimensions of the output based on command line and
829    image dimensions, and optionally, based on the actual image outline. */
calc_dimensions(imginfo_t * imginfo,potrace_path_t * plist)830 static void calc_dimensions(imginfo_t *imginfo, potrace_path_t *plist) {
831   double dim_def;
832   double maxwidth, maxheight, sc;
833   int default_scaling = 0;
834 
835   /* we take care of a special case: if one of the image dimensions is
836      0, we change it to 1. Such an image is empty anyway, so there
837      will be 0 paths in it. Changing the dimensions avoids division by
838      0 error in calculating scaling factors, bounding boxes and
839      such. This doesn't quite do the right thing in all cases, but it
840      is better than causing overflow errors or "nan" output in
841      backends.  Human users don't tend to process images of size 0
842      anyway; they might occur in some pipelines. */
843   if (imginfo->pixwidth == 0) {
844     imginfo->pixwidth = 1;
845   }
846   if (imginfo->pixheight == 0) {
847     imginfo->pixheight = 1;
848   }
849 
850   /* set the default dimension for width, height, margins */
851   if (info.backend->pixel) {
852     dim_def = DIM_PT;
853   } else {
854     dim_def = DEFAULT_DIM;
855   }
856 
857   /* apply default dimension to width, height, margins */
858   imginfo->width = info.width_d.x == UNDEF ? UNDEF : double_of_dim(info.width_d, dim_def);
859   imginfo->height = info.height_d.x == UNDEF ? UNDEF : double_of_dim(info.height_d, dim_def);
860   imginfo->lmar = info.lmar_d.x == UNDEF ? UNDEF : double_of_dim(info.lmar_d, dim_def);
861   imginfo->rmar = info.rmar_d.x == UNDEF ? UNDEF : double_of_dim(info.rmar_d, dim_def);
862   imginfo->tmar = info.tmar_d.x == UNDEF ? UNDEF : double_of_dim(info.tmar_d, dim_def);
863   imginfo->bmar = info.bmar_d.x == UNDEF ? UNDEF : double_of_dim(info.bmar_d, dim_def);
864 
865   /* start with a standard rectangle */
866   trans_from_rect(&imginfo->trans, imginfo->pixwidth, imginfo->pixheight);
867 
868   /* if info.tight is set, tighten the bounding box */
869   if (info.tight) {
870     trans_tighten(&imginfo->trans, plist);
871   }
872 
873   /* sx/rx is just an alternate way to specify width; sy/ry is just an
874      alternate way to specify height. */
875   if (info.backend->pixel) {
876     if (imginfo->width == UNDEF && info.sx != UNDEF) {
877       imginfo->width = imginfo->trans.bb[0] * info.sx;
878     }
879     if (imginfo->height == UNDEF && info.sy != UNDEF) {
880       imginfo->height = imginfo->trans.bb[1] * info.sy;
881     }
882   } else {
883     if (imginfo->width == UNDEF && info.rx != UNDEF) {
884       imginfo->width = imginfo->trans.bb[0] / info.rx * 72;
885     }
886     if (imginfo->height == UNDEF && info.ry != UNDEF) {
887       imginfo->height = imginfo->trans.bb[1] / info.ry * 72;
888     }
889   }
890 
891   /* if one of width/height is specified, use stretch to determine the
892      other */
893   if (imginfo->width == UNDEF && imginfo->height != UNDEF) {
894     imginfo->width = imginfo->height / imginfo->trans.bb[1] * imginfo->trans.bb[0] / info.stretch;
895   } else if (imginfo->width != UNDEF && imginfo->height == UNDEF) {
896     imginfo->height = imginfo->width / imginfo->trans.bb[0] * imginfo->trans.bb[1] * info.stretch;
897   }
898 
899   /* if width and height are still variable, tenatively use the
900      default scaling factor of 72dpi (for dimension-based backends) or
901      1 (for pixel-based backends). For fixed-size backends, this will
902      be adjusted later to fit the page. */
903   if (imginfo->width == UNDEF && imginfo->height == UNDEF) {
904     imginfo->width = imginfo->trans.bb[0];
905     imginfo->height = imginfo->trans.bb[1] * info.stretch;
906     default_scaling = 1;
907   }
908 
909   /* apply scaling */
910   trans_scale_to_size(&imginfo->trans, imginfo->width, imginfo->height);
911 
912   /* apply rotation, and tighten the bounding box again, if necessary */
913   if (info.angle != 0.0) {
914     trans_rotate(&imginfo->trans, info.angle);
915     if (info.tight) {
916       trans_tighten(&imginfo->trans, plist);
917     }
918   }
919 
920   /* for fixed-size backends, if default scaling was in effect,
921      further adjust the scaling to be the "best fit" for the given
922      page size and margins. */
923   if (default_scaling && info.backend->fixed) {
924 
925     /* try to squeeze it between margins */
926     maxwidth = UNDEF;
927     maxheight = UNDEF;
928 
929     if (imginfo->lmar != UNDEF && imginfo->rmar != UNDEF) {
930       maxwidth = info.paperwidth - imginfo->lmar - imginfo->rmar;
931     }
932     if (imginfo->bmar != UNDEF && imginfo->tmar != UNDEF) {
933       maxheight = info.paperheight - imginfo->bmar - imginfo->tmar;
934     }
935     if (maxwidth == UNDEF && maxheight == UNDEF) {
936       maxwidth = max(info.paperwidth - 144, info.paperwidth * 0.75);
937       maxheight = max(info.paperheight - 144, info.paperheight * 0.75);
938     }
939 
940     if (maxwidth == UNDEF) {
941       sc = maxheight / imginfo->trans.bb[1];
942     } else if (maxheight == UNDEF) {
943       sc = maxwidth / imginfo->trans.bb[0];
944     } else {
945       sc = min(maxwidth / imginfo->trans.bb[0], maxheight / imginfo->trans.bb[1]);
946     }
947 
948     /* re-scale coordinate system */
949     imginfo->width *= sc;
950     imginfo->height *= sc;
951     trans_rescale(&imginfo->trans, sc);
952   }
953 
954   /* adjust margins */
955   if (info.backend->fixed) {
956     if (imginfo->lmar == UNDEF && imginfo->rmar == UNDEF) {
957       imginfo->lmar = (info.paperwidth-imginfo->trans.bb[0])/2;
958     } else if (imginfo->lmar == UNDEF) {
959       imginfo->lmar = (info.paperwidth-imginfo->trans.bb[0]-imginfo->rmar);
960     } else if (imginfo->lmar != UNDEF && imginfo->rmar != UNDEF) {
961       imginfo->lmar += (info.paperwidth-imginfo->trans.bb[0]-imginfo->lmar-imginfo->rmar)/2;
962     }
963     if (imginfo->bmar == UNDEF && imginfo->tmar == UNDEF) {
964       imginfo->bmar = (info.paperheight-imginfo->trans.bb[1])/2;
965     } else if (imginfo->bmar == UNDEF) {
966       imginfo->bmar = (info.paperheight-imginfo->trans.bb[1]-imginfo->tmar);
967     } else if (imginfo->bmar != UNDEF && imginfo->tmar != UNDEF) {
968       imginfo->bmar += (info.paperheight-imginfo->trans.bb[1]-imginfo->bmar-imginfo->tmar)/2;
969     }
970   } else {
971     if (imginfo->lmar == UNDEF) {
972       imginfo->lmar = 0;
973     }
974     if (imginfo->rmar == UNDEF) {
975       imginfo->rmar = 0;
976     }
977     if (imginfo->bmar == UNDEF) {
978       imginfo->bmar = 0;
979     }
980     if (imginfo->tmar == UNDEF) {
981       imginfo->tmar = 0;
982     }
983   }
984 }
985 
986 /* ---------------------------------------------------------------------- */
987 /* auxiliary functions for file handling */
988 
989 /* open a file for reading. Return stdin if filename is NULL or "-" */
my_fopen_read(const char * filename)990 static FILE *my_fopen_read(const char *filename) {
991   if (filename == NULL || strcmp(filename, "-") == 0) {
992     return stdin;
993   }
994   return fopen(filename, "rb");
995 }
996 
997 /* open a file for writing. Return stdout if filename is NULL or "-" */
my_fopen_write(const char * filename)998 static FILE *my_fopen_write(const char *filename) {
999   if (filename == NULL || strcmp(filename, "-") == 0) {
1000     return stdout;
1001   }
1002   return fopen(filename, "wb");
1003 }
1004 
1005 /* close a file, but do nothing is filename is NULL or "-" */
my_fclose(FILE * f,const char * filename)1006 static void my_fclose(FILE *f, const char *filename) {
1007   if (filename == NULL || strcmp(filename, "-") == 0) {
1008     return;
1009   }
1010   fclose(f);
1011 }
1012 
1013 /* make output filename from input filename. Return an allocated value. */
make_outfilename(const char * infile,const char * ext)1014 static char *make_outfilename(const char *infile, const char *ext) {
1015   char *outfile;
1016   char *p;
1017 
1018   if (strcmp(infile, "-") == 0) {
1019     return strdup("-");
1020   }
1021 
1022   outfile = (char *) malloc(strlen(infile)+strlen(ext)+5);
1023   if (!outfile) {
1024     return NULL;
1025   }
1026   strcpy(outfile, infile);
1027   p = strrchr(outfile, '.');
1028   if (p) {
1029     *p = 0;
1030   }
1031   strcat(outfile, ext);
1032 
1033   /* check that input and output filenames are different */
1034   if (strcmp(infile, outfile) == 0) {
1035     strcpy(outfile, infile);
1036     strcat(outfile, "-out");
1037   }
1038 
1039   return outfile;
1040 }
1041 
1042 /* ---------------------------------------------------------------------- */
1043 /* Process one infile */
1044 
1045 /* Process one or more bitmaps from fin, and write the results to fout
1046    using the page_f function of the appropriate backend. */
1047 
process_file(backend_t * b,const char * infile,const char * outfile,FILE * fin,FILE * fout)1048 static void process_file(backend_t *b, const char *infile, const char *outfile, FILE *fin, FILE *fout) {
1049   int r;
1050   potrace_bitmap_t *bm = NULL;
1051   imginfo_t imginfo;
1052   int eof_flag = 0;  /* to indicate premature eof */
1053   int count;         /* number of bitmaps successfully processed, this file */
1054   potrace_state_t *st;
1055 
1056   for (count=0; ; count++) {
1057     /* read a bitmap */
1058     r = bm_read(fin, info.blacklevel, &bm);
1059     switch (r) {
1060     case -1:  /* system error */
1061       fprintf(stderr, "" POTRACE ": %s: %s\n", infile, strerror(errno));
1062       exit(2);
1063     case -2:  /* corrupt file format */
1064       fprintf(stderr, "" POTRACE ": %s: file format error: %s\n", infile, bm_read_error);
1065       exit(2);
1066     case -3:  /* empty file */
1067       if (count>0) {  /* end of file */
1068 	return;
1069       }
1070       fprintf(stderr, "" POTRACE ": %s: empty file\n", infile);
1071       exit(2);
1072     case -4:  /* wrong magic */
1073       if (count>0) {
1074 	fprintf(stderr, "" POTRACE ": %s: warning: junk at end of file\n", infile);
1075 	return;
1076       }
1077       fprintf(stderr, "" POTRACE ": %s: file format not recognized\n", infile);
1078       fprintf(stderr, "Possible input file formats are: pnm (pbm, pgm, ppm), bmp.\n");
1079       exit(2);
1080     case 1:  /* unexpected end of file */
1081       fprintf(stderr, "" POTRACE ": warning: %s: premature end of file\n", infile);
1082       eof_flag = 1;
1083       break;
1084     }
1085 
1086     /* prepare progress bar, if requested */
1087     if (info.progress) {
1088       r = info.progress_bar->init(&info.param->progress, infile, count);
1089       if (r) {
1090 	fprintf(stderr, "" POTRACE ": %s\n", strerror(errno));
1091 	exit(2);
1092       }
1093     } else {
1094       info.param->progress.callback = NULL;
1095     }
1096 
1097     if (info.invert) {
1098       bm_invert(bm);
1099     }
1100 
1101     /* process the image */
1102     st = potrace_trace(info.param, bm);
1103     if (!st || st->status != POTRACE_STATUS_OK) {
1104       fprintf(stderr, "" POTRACE ": %s: %s\n", infile, strerror(errno));
1105       exit(2);
1106     }
1107 
1108     /* calculate image dimensions */
1109     imginfo.pixwidth = bm->w;
1110     imginfo.pixheight = bm->h;
1111     bm_free(bm);
1112 
1113     calc_dimensions(&imginfo, st->plist);
1114 
1115     r = b->page_f(fout, st->plist, &imginfo);
1116     if (r) {
1117       fprintf(stderr, "" POTRACE ": %s: %s\n", outfile, strerror(errno));
1118       exit(2);
1119     }
1120 
1121     potrace_state_free(st);
1122 
1123     if (info.progress) {
1124       info.progress_bar->term(&info.param->progress);
1125     }
1126 
1127     if (eof_flag || !b->multi) {
1128       return;
1129     }
1130   }
1131   /* not reached */
1132 }
1133 
1134 /* ---------------------------------------------------------------------- */
1135 /* main: handle file i/o */
1136 
1137 #define TRY(x) if (x) goto try_error
1138 
main(int ac,char * av[])1139 int main(int ac, char *av[]) {
1140   backend_t *b;  /* backend info */
1141   FILE *fin, *fout;
1142   int i;
1143   char *outfile;
1144 
1145   /* platform-specific initializations, e.g., set file i/o to binary */
1146   platform_init();
1147 
1148   /* process options */
1149   dopts(ac, av);
1150 
1151   b = info.backend;
1152   if (b==NULL) {
1153     fprintf(stderr, "" POTRACE ": internal error: selected backend not found\n");
1154     exit(1);
1155   }
1156 
1157   /* fix some parameters */
1158   /* if backend cannot handle opticurve, disable it */
1159   if (b->opticurve == 0) {
1160     info.param->opticurve = 0;
1161   }
1162 
1163   /* there are several ways to call us:
1164      potrace                     -- stdin to stdout
1165      potrace -o outfile          -- stdin to outfile
1166      potrace file...             -- encode each file and generate outfile names
1167      potrace -o outfile file...  -- concatenate files and write to outfile
1168 
1169      The latter form is only allowed one file for single-page
1170      backends.  For multi-page backends, each file must contain 0 or
1171      more complete bitmaps.
1172   */
1173 
1174   if (!info.some_infiles) {                 /* read from stdin */
1175 
1176     fout = my_fopen_write(info.outfile);
1177     if (!fout) {
1178       fprintf(stderr, "" POTRACE ": %s: %s\n", info.outfile ? info.outfile : "stdout", strerror(errno));
1179       exit(2);
1180     }
1181     if (b->init_f) {
1182       TRY(b->init_f(fout));
1183     }
1184     process_file(b, "stdin", info.outfile ? info.outfile : "stdout", stdin, fout);
1185     if (b->term_f) {
1186       TRY(b->term_f(fout));
1187     }
1188     my_fclose(fout, info.outfile);
1189     free(info.outfile);
1190     potrace_param_free(info.param);
1191     return 0;
1192 
1193   } else if (!info.outfile) {                /* infiles -> multiple outfiles */
1194 
1195     for (i=0; i<info.infilecount; i++) {
1196       outfile = make_outfilename(info.infiles[i], b->ext);
1197       if (!outfile) {
1198 	fprintf(stderr, "" POTRACE ": %s\n", strerror(errno));
1199 	exit(2);
1200       }
1201       fin = my_fopen_read(info.infiles[i]);
1202       if (!fin) {
1203 	fprintf(stderr, "" POTRACE ": %s: %s\n", info.infiles[i], strerror(errno));
1204 	exit(2);
1205       }
1206       fout = my_fopen_write(outfile);
1207       if (!fout) {
1208 	fprintf(stderr, "" POTRACE ": %s: %s\n", outfile, strerror(errno));
1209 	exit(2);
1210       }
1211       if (b->init_f) {
1212 	TRY(b->init_f(fout));
1213       }
1214       process_file(b, info.infiles[i], outfile, fin, fout);
1215       if (b->term_f) {
1216 	TRY(b->term_f(fout));
1217       }
1218       my_fclose(fin, info.infiles[i]);
1219       my_fclose(fout, outfile);
1220       free(outfile);
1221     }
1222     potrace_param_free(info.param);
1223     return 0;
1224 
1225   } else {                                   /* infiles to single outfile */
1226 
1227     if (!b->multi && info.infilecount >= 2) {
1228       fprintf(stderr, "" POTRACE ": cannot use multiple input files with -o in %s mode\n", b->name);
1229       exit(1);
1230     }
1231     if (info.infilecount == 0) {
1232       fprintf(stderr, "" POTRACE ": cannot use empty list of input files with -o\n");
1233       exit(1);
1234     }
1235 
1236     fout = my_fopen_write(info.outfile);
1237     if (!fout) {
1238       fprintf(stderr, "" POTRACE ": %s: %s\n", info.outfile, strerror(errno));
1239       exit(2);
1240     }
1241     if (b->init_f) {
1242       TRY(b->init_f(fout));
1243     }
1244     for (i=0; i<info.infilecount; i++) {
1245       fin = my_fopen_read(info.infiles[i]);
1246       if (!fin) {
1247 	fprintf(stderr, "" POTRACE ": %s: %s\n", info.infiles[i], strerror(errno));
1248 	exit(2);
1249       }
1250       process_file(b, info.infiles[i], info.outfile, fin, fout);
1251       my_fclose(fin, info.infiles[i]);
1252     }
1253     if (b->term_f) {
1254       TRY(b->term_f(fout));
1255     }
1256     my_fclose(fout, info.outfile);
1257     free(info.outfile);
1258     potrace_param_free(info.param);
1259     return 0;
1260 
1261   }
1262 
1263   /* not reached */
1264 
1265  try_error:
1266   fprintf(stderr, "" POTRACE ": %s\n", strerror(errno));
1267   exit(2);
1268 }
1269