1 /*----------------------------------------------------------------------------
2                                  pstopnm
3 ------------------------------------------------------------------------------
4   Use Ghostscript to convert a Postscript file into a PBM, PGM, or PNM
5   file.
6 
7   Implementation note: This program feeds the input file to Ghostcript
8   directly (with possible statements preceding it), and uses
9   Ghostscript's PNM output device drivers.  As an alternative,
10   Ghostscript also comes with the Postscript program pstoppm.ps which
11   we could run and it would read the input file and produce PNM
12   output.  It isn't clear to me what pstoppm.ps adds to what you get
13   from just feeding your input directly to Ghostscript as the main program.
14 
15 -----------------------------------------------------------------------------*/
16 
17 #define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */
18 #define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
19     /* Make sure fdopen() is in stdio.h and strdup() is in string.h */
20 
21 #include <assert.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <signal.h>
27 #include <sys/wait.h>
28 #include <sys/stat.h>
29 
30 #include "pm_c_util.h"
31 #include "mallocvar.h"
32 #include "pnm.h"
33 #include "shhopt.h"
34 #include "nstring.h"
35 
36 static bool verbose;
37 
38 enum Orientation {PORTRAIT, LANDSCAPE, UNSPECIFIED};
39 struct Box {
40     /* Description of a rectangle within an image; all coordinates
41        measured in points (1/72") with lower left corner of page being the
42        origin.  Negative values are OK.
43     */
44     bool isDefined;
45 
46     /* Nothing below is meaningful unless 'isDefined' is true */
47     int llx;  /* lower left X coord */
48     int lly;  /* lower left Y coord */
49     int urx;  /* upper right X coord */
50     int ury;  /* upper right Y coord */
51 };
52 
53 
54 
55 static void
assertValidBox(struct Box const box)56 assertValidBox(struct Box const box) {
57 
58     if (box.isDefined) {
59         assert(box.urx >= box.llx);
60         assert(box.ury >= box.lly);
61     }
62 }
63 
64 
65 
66 struct Dimensions {
67 /*----------------------------------------------------------------------------
68   Horizontal and vertical dimensions of something, both in pixels and
69   spatial distance (points).
70 
71   Sizes are in pixels.  Resolutions are in dots per inch (pixels per inch);
72 -----------------------------------------------------------------------------*/
73     unsigned int xsize;
74     unsigned int ysize;
75     unsigned int xres;
76     unsigned int yres;
77 };
78 
79 struct CmdlineInfo {
80     /* All the information the user supplied in the command line,
81        in a form easy for the program to use.
82     */
83     const char * inputFileName;  /* Names of input files */
84     unsigned int forceplain;
85     struct Box extractBox;
86     unsigned int nocrop;
87     unsigned int formatType;
88     unsigned int verbose;
89     float xborder;
90     unsigned int xmax;
91     unsigned int xsize;  /* zero means unspecified */
92     float yborder;
93     unsigned int ymax;
94     unsigned int ysize;  /* zero means unspecified */
95     unsigned int dpi;    /* zero means unspecified */
96     enum Orientation orientation;
97     unsigned int stdoutSpec;
98     unsigned int textalphabits;
99 };
100 
101 
102 static void
parseCommandLine(int argc,char ** argv,struct CmdlineInfo * const cmdlineP)103 parseCommandLine(int argc, char ** argv,
104                  struct CmdlineInfo * const cmdlineP) {
105 /*----------------------------------------------------------------------------
106    Note that the file spec array we return is stored in the storage that
107    was passed to us as the argv array.
108 -----------------------------------------------------------------------------*/
109     optEntry * option_def;
110         /* Instructions to pm_optParseOptions3 on how to parse our options.
111          */
112     optStruct3 opt;
113 
114     unsigned int option_def_index;
115 
116     unsigned int pbmOpt, pgmOpt, ppmOpt;
117     unsigned int portraitOpt, landscapeOpt;
118     float llx, lly, urx, ury;
119     unsigned int llxSpec, llySpec, urxSpec, urySpec;
120     unsigned int xmaxSpec, ymaxSpec, xsizeSpec, ysizeSpec, dpiSpec;
121     unsigned int textalphabitsSpec;
122 
123     MALLOCARRAY_NOFAIL(option_def, 100);
124 
125     option_def_index = 0;   /* incremented by OPTENTRY */
126     OPTENT3(0, "forceplain", OPT_FLAG,  NULL, &cmdlineP->forceplain,     0);
127     OPTENT3(0, "llx",        OPT_FLOAT, &llx, &llxSpec,                  0);
128     OPTENT3(0, "lly",        OPT_FLOAT, &lly, &llySpec,                  0);
129     OPTENT3(0, "urx",        OPT_FLOAT, &urx, &urxSpec,                  0);
130     OPTENT3(0, "ury",        OPT_FLOAT, &ury, &urySpec,                  0);
131     OPTENT3(0, "nocrop",     OPT_FLAG,  NULL, &cmdlineP->nocrop,         0);
132     OPTENT3(0, "pbm",        OPT_FLAG,  NULL, &pbmOpt ,                  0);
133     OPTENT3(0, "pgm",        OPT_FLAG,  NULL, &pgmOpt,                   0);
134     OPTENT3(0, "ppm",        OPT_FLAG,  NULL, &ppmOpt,                   0);
135     OPTENT3(0, "verbose",    OPT_FLAG,  NULL, &cmdlineP->verbose,        0);
136     OPTENT3(0, "xborder",    OPT_FLOAT, &cmdlineP->xborder, NULL,        0);
137     OPTENT3(0, "xmax",       OPT_UINT,  &cmdlineP->xmax, &xmaxSpec,      0);
138     OPTENT3(0, "xsize",      OPT_UINT,  &cmdlineP->xsize, &xsizeSpec,    0);
139     OPTENT3(0, "yborder",    OPT_FLOAT, &cmdlineP->yborder, NULL,        0);
140     OPTENT3(0, "ymax",       OPT_UINT,  &cmdlineP->ymax, &ymaxSpec,      0);
141     OPTENT3(0, "ysize",      OPT_UINT,  &cmdlineP->ysize, &ysizeSpec,    0);
142     OPTENT3(0, "dpi",        OPT_UINT,  &cmdlineP->dpi, &dpiSpec,        0);
143     OPTENT3(0, "portrait",   OPT_FLAG,  NULL, &portraitOpt,              0);
144     OPTENT3(0, "landscape",  OPT_FLAG,  NULL, &landscapeOpt,             0);
145     OPTENT3(0, "stdout",     OPT_FLAG,  NULL, &cmdlineP->stdoutSpec,     0);
146     OPTENT3(0, "textalphabits", OPT_UINT,
147             &cmdlineP->textalphabits,  &textalphabitsSpec, 0);
148 
149     /* Set the defaults */
150     cmdlineP->xborder = cmdlineP->yborder = 0.1;
151 
152     opt.opt_table = option_def;
153     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
154     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
155 
156     pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
157         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
158 
159     if (xmaxSpec) {
160         if (cmdlineP->xmax == 0)
161             pm_error("zero is not a valid value for -xmax");
162     } else
163         cmdlineP->xmax = 612;
164 
165     if (ymaxSpec) {
166         if (cmdlineP->ymax == 0)
167             pm_error("zero is not a valid value for -ymax");
168     } else
169         cmdlineP->ymax = 792;
170 
171     if (xsizeSpec) {
172         if (cmdlineP->xsize == 0)
173             pm_error("zero is not a valid value for -xsize");
174     } else
175         cmdlineP->xsize = 0;
176 
177     if (ysizeSpec) {
178         if (cmdlineP->ysize == 0)
179             pm_error("zero is not a valid value for -ysize");
180     } else
181         cmdlineP->ysize = 0;
182 
183     if (portraitOpt && !landscapeOpt)
184         cmdlineP->orientation = PORTRAIT;
185     else if (!portraitOpt && landscapeOpt)
186         cmdlineP->orientation = LANDSCAPE;
187     else if (!portraitOpt && !landscapeOpt)
188         cmdlineP->orientation = UNSPECIFIED;
189     else
190         pm_error("Cannot specify both -portrait and -landscape options");
191 
192     if (pbmOpt)
193         cmdlineP->formatType = PBM_TYPE;
194     else if (pgmOpt)
195         cmdlineP->formatType = PGM_TYPE;
196     else if (ppmOpt)
197         cmdlineP->formatType = PPM_TYPE;
198     else
199         cmdlineP->formatType = PPM_TYPE;
200 
201     /* If any one of the 4 bounding box coordinates is given on the
202        command line, we default any of the 4 that aren't.
203     */
204     if (llxSpec || llySpec || urxSpec || urySpec) {
205         cmdlineP->extractBox.isDefined = true;
206 
207         if (!llxSpec) cmdlineP->extractBox.llx = 72;
208         else cmdlineP->extractBox.llx = llx * 72;
209         if (!llySpec) cmdlineP->extractBox.lly = 72;
210         else cmdlineP->extractBox.lly = lly * 72;
211         if (!urxSpec) cmdlineP->extractBox.urx = 540;
212         else cmdlineP->extractBox.urx = urx * 72;
213         if (!urySpec) cmdlineP->extractBox.ury = 720;
214         else cmdlineP->extractBox.ury = ury * 72;
215     } else {
216         cmdlineP->extractBox.isDefined = false;
217     }
218 
219     if (dpiSpec) {
220         if (cmdlineP->dpi == 0)
221             pm_error("Zero is not a valid value for -dpi");
222     } else
223         cmdlineP->dpi = 0;
224 
225     if (dpiSpec && xsizeSpec + ysizeSpec + xmaxSpec + ymaxSpec > 0)
226         pm_error("You may not specify both size options and -dpi");
227 
228     if (textalphabitsSpec) {
229         if (cmdlineP->textalphabits != 1 && cmdlineP->textalphabits != 2
230             && cmdlineP->textalphabits != 4) {
231             /* Pstopnm won't take this value, and we don't want to inflict
232                a Pstopnm failure error message on the user.
233             */
234             pm_error("Valid values for -textalphabits are 1, 2, and 4.  "
235                      "You specified %u", cmdlineP->textalphabits );
236         }
237     } else
238         cmdlineP->textalphabits = 4;
239 
240     if (argc-1 == 0)
241         cmdlineP->inputFileName = "-";  /* stdin */
242     else if (argc-1 == 1)
243         cmdlineP->inputFileName = argv[1];
244     else
245         pm_error("Too many arguments (%d).  "
246                  "Only need one: the Postscript file name", argc-1);
247 
248     free(option_def);
249 }
250 
251 
252 
253 static void
addPsToFileName(char const origFileName[],const char ** const newFileNameP)254 addPsToFileName(char          const origFileName[],
255                 const char ** const newFileNameP) {
256 /*----------------------------------------------------------------------------
257    If origFileName[] does not name an existing file, but the same
258    name with ".ps" added to the end does, return the name with the .ps
259    attached.  Otherwise, just return origFileName[].
260 
261    Return the name in newly malloc'ed storage, pointed to by
262    *newFileNameP.
263 -----------------------------------------------------------------------------*/
264     struct stat statbuf;
265     int statRc;
266 
267     statRc = lstat(origFileName, &statbuf);
268 
269     if (statRc == 0)
270         *newFileNameP = strdup(origFileName);
271     else {
272         const char * fileNamePlusPs;
273 
274         pm_asprintf(&fileNamePlusPs, "%s.ps", origFileName);
275 
276         statRc = lstat(fileNamePlusPs, &statbuf);
277         if (statRc == 0)
278             *newFileNameP = strdup(fileNamePlusPs);
279         else
280             *newFileNameP = strdup(origFileName);
281         pm_strfree(fileNamePlusPs);
282     }
283     if (verbose)
284         pm_message("Input file is %s", *newFileNameP);
285 }
286 
287 
288 
289 static unsigned int
resolution(unsigned int const dotCt,unsigned int const pointCt)290 resolution(unsigned int const dotCt,
291            unsigned int const pointCt) {
292 /*----------------------------------------------------------------------------
293    The resolution in dots per inch when 'dotCt' dots print 'pointCt' points
294    long.
295 
296    When this would round to zero, we return 1 dot per inch instead so it
297    doesn't play havoc with arithmetic - it's never going to happen unless
298    something is broken anyway.
299 -----------------------------------------------------------------------------*/
300     return MAX(1, (unsigned int)((float)dotCt * 72 / pointCt + 0.5));
301 }
302 
303 
304 
305 static void
computeSizeResFromSizeSpec(unsigned int const requestedXsize,unsigned int const requestedYsize,unsigned int const imageWidth,unsigned int const imageHeight,struct Dimensions * const imageDimP)306 computeSizeResFromSizeSpec(unsigned int        const requestedXsize,
307                            unsigned int        const requestedYsize,
308                            unsigned int        const imageWidth,
309                            unsigned int        const imageHeight,
310                            struct Dimensions * const imageDimP) {
311 /*----------------------------------------------------------------------------
312    Compute output image size and assumed Postscript input resolution, assuming
313    user requested a specific size for at least one of the dimensions and the
314    input is 'imageWidth' x 'imageHeight' points.
315 
316    'requestedXsize' is what the user requested for output image width in
317    pixels, or zero if he made no request.  'requestedYsize' is analogous
318    for the height.
319 -----------------------------------------------------------------------------*/
320     assert(requestedXsize || requestedYsize);
321 
322     assert(imageWidth > 0);
323 
324     if (requestedXsize) {
325         imageDimP->xsize = requestedXsize;
326         imageDimP->xres = resolution(requestedXsize, imageWidth);
327         if (!requestedYsize) {
328             imageDimP->yres = imageDimP->xres;
329             imageDimP->ysize = (unsigned int)
330                 (imageHeight * (float)imageDimP->yres/72 + 0.5);
331         }
332     }
333 
334     assert(imageHeight > 0);
335 
336     if (requestedYsize) {
337         imageDimP->ysize = requestedYsize;
338         imageDimP->yres = resolution(requestedYsize, imageHeight);
339         if (!requestedXsize) {
340             imageDimP->xres = imageDimP->yres;
341             imageDimP->xsize = (unsigned int)
342                 (imageWidth * (float)imageDimP->xres/72 + 0.5);
343         }
344     }
345 }
346 
347 
348 
349 static void
computeSizeResBlind(unsigned int const xmax,unsigned int const ymax,unsigned int const imageWidth,unsigned int const imageHeight,bool const nocrop,struct Dimensions * const imageDimP)350 computeSizeResBlind(unsigned int        const xmax,
351                     unsigned int        const ymax,
352                     unsigned int        const imageWidth,
353                     unsigned int        const imageHeight,
354                     bool                const nocrop,
355                     struct Dimensions * const imageDimP) {
356 
357     if (imageWidth == 0 || imageHeight == 0) {
358         imageDimP->xres = imageDimP->yres = 72;
359     } else {
360         imageDimP->xres = imageDimP->yres =
361             MIN(resolution(xmax, imageWidth),
362                 resolution(ymax, imageHeight));
363     }
364 
365     if (nocrop) {
366         imageDimP->xsize = xmax;
367         imageDimP->ysize = ymax;
368     } else {
369         imageDimP->xsize = (unsigned int)
370             (imageWidth * (float)imageDimP->xres / 72 + 0.5);
371         imageDimP->ysize = (unsigned int)
372             (imageHeight * (float)imageDimP->yres / 72 + 0.5);
373     }
374 }
375 
376 
377 
378 static void
computeSizeRes(struct CmdlineInfo const cmdline,struct Box const borderedBox,struct Dimensions * const imageDimP)379 computeSizeRes(struct CmdlineInfo  const cmdline,
380                struct Box          const borderedBox,
381                struct Dimensions * const imageDimP) {
382 /*----------------------------------------------------------------------------
383   Figure out how big the output image should be and what output device
384   resolution Ghostscript should assume (return as *imageDimP).
385 
386   A resolution number is the number of pixels per inch that the
387   printer prints.  Since we're emulating a printed page with a PNM
388   image, and a PNM image has no spatial dimension (you can't say how
389   many inches wide a PNM image is), it's kind of confusing.
390 
391   If the user doesn't select a resolution, we choose the resolution
392   that causes the image to be a certain number of pixels, knowing how
393   big (in inches) Ghostscript wants the printed picture to be.  For
394   example, the part of the Postscript image we are going to print is 2
395   inches wide.  We want the PNM image to be 1000 pixels wide.  So we
396   tell Ghostscript that our horizontal output device resolution is 500
397   pixels per inch.
398 
399   X and Y in all returned values are with respect to the image, not the
400   page.  Note that the image might be placed sideways on the page, so that
401   page X and Y would be reversed from image X and Y.
402 -----------------------------------------------------------------------------*/
403     /* The horizontal and vertical sizes of the input image, in points
404        (1/72 inch)
405     */
406     unsigned int const sx = borderedBox.urx - borderedBox.llx;
407     unsigned int const sy = borderedBox.ury - borderedBox.lly;
408 
409     assertValidBox(borderedBox); assert(borderedBox.isDefined);
410 
411     if (cmdline.dpi) {
412         /* User gave resolution; we figure out output image size */
413         imageDimP->xres = imageDimP->yres = cmdline.dpi;
414         imageDimP->xsize = ROUNDU(cmdline.dpi * sx / 72.0);
415         imageDimP->ysize = ROUNDU(cmdline.dpi * sy / 72.0);
416     } else  if (cmdline.xsize || cmdline.ysize) {
417         if (sx == 0 || sy == 0)
418             pm_error("Input image is zero size; we cannot satisfy your "
419                      "produce your requested output dimensions");
420 
421         computeSizeResFromSizeSpec(cmdline.xsize, cmdline.ysize, sx, sy,
422                                    imageDimP);
423     } else
424         computeSizeResBlind(cmdline.xmax, cmdline.ymax, sx, sy, cmdline.nocrop,
425                             imageDimP);
426 
427     if (cmdline.verbose) {
428         pm_message("output is %u pixels wide X %u pixels high",
429                    imageDimP->xsize, imageDimP->ysize);
430         pm_message("output device resolution is %u dpi horiz, %u dpi vert",
431                    imageDimP->xres, imageDimP->yres);
432     }
433 }
434 
435 
436 
437 enum PostscriptLanguage {COMMON_POSTSCRIPT, ENCAPSULATED_POSTSCRIPT};
438 
439 static enum PostscriptLanguage
languageDeclaration(char const inputFileName[])440 languageDeclaration(char const inputFileName[]) {
441 /*----------------------------------------------------------------------------
442   Return the Postscript language in which the file declares it is written.
443   (Except that if the file is on Standard Input or doesn't validly declare
444   a language, just say it is Common Postscript).
445 -----------------------------------------------------------------------------*/
446     enum PostscriptLanguage language;
447 
448     if (streq(inputFileName, "-"))
449         /* Can't read stdin, because we need it to remain positioned for the
450            Ghostscript interpreter to read it.
451         */
452         language = COMMON_POSTSCRIPT;
453     else {
454         FILE *infile;
455         char line[80];
456 
457         infile = pm_openr(inputFileName);
458 
459         if (fgets(line, sizeof(line), infile) == NULL)
460             language = COMMON_POSTSCRIPT;
461         else {
462             const char epsHeader[] = " EPSF-";
463 
464             if (strstr(line, epsHeader))
465                 language = ENCAPSULATED_POSTSCRIPT;
466             else
467                 language = COMMON_POSTSCRIPT;
468         }
469         fclose(infile);
470     }
471     if (verbose)
472         pm_message("language is %s",
473                    language == ENCAPSULATED_POSTSCRIPT ?
474                    "encapsulated postscript" :
475                    "not encapsulated postscript");
476     return language;
477 }
478 
479 
480 
481 static struct Box
boundingBoxFmPostscriptFile(FILE * const ifP)482 boundingBoxFmPostscriptFile(FILE * const ifP) {
483 
484     struct Box retval;
485     bool eof;
486 
487     for (retval.isDefined = false, eof = false; !retval.isDefined && !eof; ) {
488         char line[200];
489         char * fgetsRc;
490 
491         fgetsRc = fgets(line, sizeof(line), ifP);
492 
493         if (fgetsRc == NULL)
494             eof = true;
495         else {
496             int rc;
497             int llx, lly, urx, ury;
498 
499             rc = sscanf(line, "%%%%BoundingBox: %d %d %d %d",
500                         &llx, &lly, &urx, &ury);
501             if (rc == 4) {
502                 /* We found a BoundingBox statement */
503 
504                 if (llx > urx)
505                     pm_error("%%%%BoundingBox statement in input file has "
506                              "lower left corner to the right of the "
507                              "upper right corner");
508                 if (lly > ury)
509                     pm_error("%%%%BoundingBox statement in input file has "
510                              "lower left corner above the "
511                              "upper right corner");
512 
513                 retval.llx = llx; retval.lly = lly;
514                 retval.urx = urx; retval.ury = ury;
515                 retval.isDefined = true;
516             }
517         }
518     }
519     fclose(ifP);
520 
521     return retval;
522 }
523 
524 
525 
526 static struct Box
computeBoxToExtract(struct Box const cmdlineExtractBox,char const inputFileName[])527 computeBoxToExtract(struct Box const cmdlineExtractBox,
528                     char       const inputFileName[]) {
529 
530     struct Box retval;
531 
532     if (cmdlineExtractBox.isDefined)
533         /* User told us what box to extract, so that's what we'll do */
534         retval = cmdlineExtractBox;
535     else {
536         /* Try to get the bounding box from the DSC %%BoundingBox
537            statement (A Postscript comment) in the input.
538         */
539         struct Box psBb;  /* Box described by %%BoundingBox stmt in input */
540 
541         if (streq(inputFileName, "-"))
542             /* Can't read stdin, because we need it to remain
543                positioned for the Ghostscript interpreter to read it.
544             */
545             psBb.isDefined = false;
546         else {
547             FILE * ifP;
548 
549             ifP = pm_openr(inputFileName);
550 
551             psBb = boundingBoxFmPostscriptFile(ifP);
552 
553             if (!psBb.isDefined)
554                 pm_message("Warning: no %%%%BoundingBox statement "
555                            "in the input or command line.  "
556                            "Will use defaults");
557         }
558         if (psBb.isDefined) {
559             if (verbose)
560                 pm_message("Using %%%%BoundingBox statement from input.");
561             retval = psBb;
562         } else {
563             /* Use the center of an 8.5" x 11" page with 1" border all around*/
564             retval.isDefined = true;
565             retval.llx = 72;
566             retval.lly = 72;
567             retval.urx = 540;
568             retval.ury = 720;
569         }
570     }
571 
572     assert(retval.isDefined);
573 
574     if (verbose)
575         pm_message("Extracting the box ((%d,%d),(%d,%d))",
576                    retval.llx, retval.lly, retval.urx, retval.ury);
577     return retval;
578 }
579 
580 
581 
582 static enum Orientation
computeOrientation(struct CmdlineInfo const cmdline,struct Box const extractBox)583 computeOrientation(struct CmdlineInfo const cmdline,
584                    struct Box         const extractBox) {
585 /*----------------------------------------------------------------------------
586    The proper orientation of the image on the page, given the user's
587    parameters 'cmdline' and the image dimensions 'extractBox'.
588 -----------------------------------------------------------------------------*/
589     /* We're putting an _image_ on a _page_.  Either one can have portrait or
590        landscape aspect ratio.  In our return value, orientation just means
591        whether the image is rotated on the page: Portrait means it isn't
592        Landscape means it is.  The result can be confusing: Consider an image
593        which is a landscape, wider than it is tall, being printed on a page
594        which is also wider than it is tall.  The orientation we would return
595        for that case is Portrait.
596 
597        The decision is simple: if the user didn't request a particular
598        orientation, we return the value that makes the image orientation match
599        the page orientation.  If both possibilities match equally (because the
600        image or the page is square), we return Portrait.
601     */
602 
603     enum Orientation retval;
604 
605     if (cmdline.orientation != UNSPECIFIED)
606         retval = cmdline.orientation;
607     else {
608         /* Dimensions of image to print, in points */
609         unsigned int const imageWidPt = extractBox.urx - extractBox.llx;
610         unsigned int const imageHgtPt = extractBox.ury - extractBox.lly;
611 
612         /* Dimensions of image to print, in pixels (possibly of assumed
613            resolution)
614         */
615         unsigned int imageWidXel;
616         unsigned int imageHgtXel;
617 
618         /* We have to deal with the awkward case that the printed pixels are
619            not square.  We match up the aspect ratio of the image in _pixels_
620            and the aspect ratio of the page in _pixels_.  But only the ratio
621            matters; we don't care what the absolute size of the pixels is.
622            And that's good, because if the user didn't specify xsize/ysize, we
623            don't know the absolute size in pixels.  In that case, fortunately,
624            the pixels are guaranteed to be square so we can just pretend it is
625            one point per pixel and get the right result.
626         */
627 
628         if (cmdline.xsize && cmdline.ysize) {
629             imageWidXel = cmdline.xsize;
630             imageHgtXel = cmdline.ysize;
631         } else {
632             /* Pixels are square, so it doesn't matter what the resolution
633                is; just call it one pixel per point.
634             */
635             imageWidXel = imageWidPt;
636             imageHgtXel = imageHgtPt;
637         }
638 
639         if (imageHgtXel >= imageWidXel && cmdline.ymax >= cmdline.xmax) {
640             /* Both image and page are higher than wide, so no rotation */
641             retval = PORTRAIT;
642         } else if (imageHgtXel < imageWidXel &&
643                    cmdline.ymax < cmdline.xmax) {
644             /* Both image and page are wider than high, so no rotation */
645             retval = PORTRAIT;
646         } else {
647             /* Image and pixel have opposite aspect ratios, so rotate
648                for best fit.
649             */
650             retval = LANDSCAPE;
651         }
652     }
653     return retval;
654 }
655 
656 
657 
658 static struct Box
addBorders(struct Box const inputBox,float const xborderScale,float const yborderScale)659 addBorders(struct Box const inputBox,
660            float      const xborderScale,
661            float      const yborderScale) {
662 /*----------------------------------------------------------------------------
663    Return a box which is 'inputBox' plus some borders.
664 
665    Add left and right borders that are the fraction 'xborderScale' of the
666    width of the input box; likewise for top and bottom borders with
667    'yborderScale'.
668 -----------------------------------------------------------------------------*/
669     unsigned int const leftRightBorderSize =
670         ROUNDU((inputBox.urx - inputBox.llx) * xborderScale);
671     unsigned int const topBottomBorderSize =
672         ROUNDU((inputBox.ury - inputBox.lly) * yborderScale);
673 
674     struct Box retval;
675 
676     assertValidBox(inputBox); assert(inputBox.isDefined);
677 
678     retval.llx = inputBox.llx - (int)leftRightBorderSize;
679     retval.lly = inputBox.lly - (int)topBottomBorderSize;
680     retval.urx = inputBox.urx + (int)leftRightBorderSize;
681     retval.ury = inputBox.ury + (int)topBottomBorderSize;
682     retval.isDefined = true;
683 
684     if (verbose)
685         pm_message("With borders, extracted box is ((%d,%d),(%d,%d))",
686                    retval.llx, retval.lly, retval.urx, retval.ury);
687 
688     return retval;
689 }
690 
691 
692 
693 static void
writePstrans(struct Box const box,struct Dimensions const d,enum Orientation const orientation,FILE * const pipeToGsP)694 writePstrans(struct Box        const box,
695              struct Dimensions const d,
696              enum Orientation  const orientation,
697              FILE *            const pipeToGsP) {
698 
699     int const xsize = d.xsize;
700     int const ysize = d.ysize;
701     int const xres  = d.xres;
702     int const yres  = d.yres;
703 
704     const char * pstrans;
705 
706     assert(xres > 0); assert(yres > 0);
707 
708     switch (orientation) {
709     case PORTRAIT: {
710         int llx, lly;
711         llx = box.llx - (xsize * 72 / xres - (box.urx - box.llx)) / 2;
712         lly = box.lly - (ysize * 72 / yres - (box.ury - box.lly)) / 2;
713         pm_asprintf(&pstrans, "%d neg %d neg translate", llx, lly);
714     } break;
715     case LANDSCAPE: {
716         int llx, ury;
717         llx = box.llx - (xsize * 72 / xres - (box.urx - box.llx)) / 2;
718         ury = box.ury + (ysize * 72 / yres - (box.ury - box.lly)) / 2;
719         pm_asprintf(&pstrans, "90 rotate %d neg %d neg translate", llx, ury);
720     } break;
721     case UNSPECIFIED:
722         assert(false);
723     }
724 
725     if (pstrans == pm_strsol)
726         pm_error("Unable to allocate memory for pstrans");
727 
728     if (verbose)
729         pm_message("Postscript prefix command: '%s'", pstrans);
730 
731     fprintf(pipeToGsP, "%s\n", pstrans);
732 
733     pm_strfree(pstrans);
734 }
735 
736 
737 
738 static const char *
computeOutfileArg(struct CmdlineInfo const cmdline)739 computeOutfileArg(struct CmdlineInfo const cmdline) {
740 /*----------------------------------------------------------------------------
741    Determine the value for the "OutputFile" variable to pass to Ghostscript,
742    which is what tells Ghostscript where to put its output.  This is either
743    a pattern such as "foo%03d.ppm" or "-" to indicate Standard Output.
744 
745    We go with "-" if, according to 'cmdline', the user asked for
746    Standard Output or is giving his input on Standard Input.  Otherwise,
747    we go with the pattern, based on the name of the input file and output
748    format type the user requested.
749 -----------------------------------------------------------------------------*/
750     const char * retval;  /* malloc'ed */
751 
752     if (cmdline.stdoutSpec)
753         retval = strdup("-");
754     else if (streq(cmdline.inputFileName, "-"))
755         retval = strdup("-");
756     else {
757         char * basename;
758         const char * suffix;
759 
760         basename  = strdup(cmdline.inputFileName);
761         if (strlen(basename) > 3 &&
762             streq(basename+strlen(basename)-3, ".ps"))
763             /* The input file name ends in ".ps".  Chop it off. */
764             basename[strlen(basename)-3] = '\0';
765 
766         switch (cmdline.formatType) {
767         case PBM_TYPE: suffix = "pbm"; break;
768         case PGM_TYPE: suffix = "pgm"; break;
769         case PPM_TYPE: suffix = "ppm"; break;
770         default: pm_error("Internal error: invalid value for formatType: %d",
771                           cmdline.formatType);
772         }
773         pm_asprintf(&retval, "%s%%03d.%s", basename, suffix);
774 
775         pm_strfree(basename);
776     }
777     return(retval);
778 }
779 
780 
781 
782 static const char *
computeGsDevice(int const formatType,bool const forceplain)783 computeGsDevice(int  const formatType,
784                 bool const forceplain) {
785 
786     const char * basetype;
787     const char * retval;
788 
789     switch (formatType) {
790     case PBM_TYPE: basetype = "pbm"; break;
791     case PGM_TYPE: basetype = "pgm"; break;
792     case PPM_TYPE: basetype = "ppm"; break;
793     default: pm_error("Internal error: invalid value formatType");
794     }
795     if (forceplain)
796         retval = strdup(basetype);
797     else
798         pm_asprintf(&retval, "%sraw", basetype);
799 
800     if (retval == NULL)
801         pm_error("Unable to allocate memory for gs device");
802 
803     return(retval);
804 }
805 
806 
807 
808 static void
findGhostscriptProg(const char ** const retvalP)809 findGhostscriptProg(const char ** const retvalP) {
810 
811     *retvalP = NULL;  /* initial assumption */
812     if (getenv("GHOSTSCRIPT"))
813         *retvalP = strdup(getenv("GHOSTSCRIPT"));
814     if (*retvalP == NULL) {
815         if (getenv("PATH") != NULL) {
816             char * pathwork;  /* malloc'ed */
817             const char * candidate;
818 
819             pathwork = strdup(getenv("PATH"));
820 
821             candidate = strtok(pathwork, ":");
822 
823             *retvalP = NULL;
824             while (!*retvalP && candidate) {
825                 struct stat statbuf;
826                 const char * filename;
827                 int rc;
828 
829                 pm_asprintf(&filename, "%s/gs", candidate);
830                 rc = stat(filename, &statbuf);
831                 if (rc == 0) {
832                     if (S_ISREG(statbuf.st_mode))
833                         *retvalP = strdup(filename);
834                 } else if (errno != ENOENT)
835                     pm_error("Error looking for Ghostscript program.  "
836                              "stat(\"%s\") returns errno %d (%s)",
837                              filename, errno, strerror(errno));
838                 pm_strfree(filename);
839 
840                 candidate = strtok(NULL, ":");
841             }
842             free(pathwork);
843         }
844     }
845     if (*retvalP == NULL)
846         *retvalP = strdup("/usr/bin/gs");
847 }
848 
849 
850 
851 static void
execGhostscript(int const inputPipeFd,char const ghostscriptDevice[],char const outfileArg[],struct Dimensions const pageDim,unsigned int const textalphabits)852 execGhostscript(int               const inputPipeFd,
853                 char              const ghostscriptDevice[],
854                 char              const outfileArg[],
855                 struct Dimensions const pageDim,
856                 unsigned int      const textalphabits) {
857 /*----------------------------------------------------------------------------
858    Exec the Ghostscript program and have it execute the Postscript program
859    that it receives on 'inputPipeFd', then exit.
860 
861    'pageDim' describes the print area.  X and Y in 'pageDim' are with respect
862    to the page, independent of whether the program we receive on 'inputPipeFd'
863    puts an image in there sideways.
864 -----------------------------------------------------------------------------*/
865     const char * arg0;
866     const char * ghostscriptProg;
867     const char * deviceopt;
868     const char * outfileopt;
869     const char * gopt;
870     const char * ropt;
871     const char * textalphabitsopt;
872 
873     findGhostscriptProg(&ghostscriptProg);
874 
875     /* Put the input pipe on Standard Input */
876     dup2(inputPipeFd, STDIN_FILENO);
877     close(inputPipeFd);
878 
879     pm_asprintf(&arg0, "gs");
880     pm_asprintf(&deviceopt, "-sDEVICE=%s", ghostscriptDevice);
881     pm_asprintf(&outfileopt, "-sOutputFile=%s", outfileArg);
882     pm_asprintf(&gopt, "-g%dx%d", pageDim.xsize, pageDim.ysize);
883     pm_asprintf(&ropt, "-r%dx%d", pageDim.xres, pageDim.yres);
884     pm_asprintf(&textalphabitsopt, "-dTextAlphaBits=%u", textalphabits);
885 
886     /* -dSAFER causes Postscript to disable %pipe and file operations,
887        which are almost certainly not needed here.  This prevents our
888        Postscript program from doing crazy unexpected things, possibly
889        as a result of a malicious booby trapping of our Postscript file.
890     */
891 
892     if (verbose) {
893         pm_message("execing '%s' with args '%s' (arg 0), "
894                    "'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'",
895                    ghostscriptProg, arg0,
896                    deviceopt, outfileopt, gopt, ropt, textalphabitsopt,
897                    "-q", "-dNOPAUSE",
898                    "-dSAFER", "-");
899     }
900 
901     execl(ghostscriptProg, arg0, deviceopt, outfileopt, gopt, ropt,
902 	  textalphabitsopt, "-q", "-dNOPAUSE", "-dSAFER", "-", NULL);
903 
904     pm_error("execl() of Ghostscript ('%s') failed, errno=%d (%s)",
905              ghostscriptProg, errno, strerror(errno));
906 }
907 
908 
909 
910 static void
copyFileStream(FILE * const ifP,FILE * const ofP)911 copyFileStream(FILE * const ifP,
912                FILE * const ofP) {
913 
914     bool eof;
915 
916     for (eof = false; !eof; ) {
917         char buffer[4096];
918         size_t readCt;
919 
920         readCt = fread(buffer, 1, sizeof(buffer), ifP);
921 
922         if (readCt == 0)
923             eof = true;
924         else
925             fwrite(buffer, 1, readCt, ofP);
926     }
927 }
928 
929 
930 
931 static void
feedPsToGhostScript(const char * const inputFileName,struct Box const borderedBox,struct Dimensions const imageDim,enum Orientation const orientation,int const pipeToGhostscriptFd,enum PostscriptLanguage const language)932 feedPsToGhostScript(const char *            const inputFileName,
933                     struct Box              const borderedBox,
934                     struct Dimensions       const imageDim,
935                     enum Orientation        const orientation,
936                     int                     const pipeToGhostscriptFd,
937                     enum PostscriptLanguage const language) {
938 /*----------------------------------------------------------------------------
939    Send a Postscript program to the Ghostscript process running on the
940    other end of the pipe 'pipeToGhostscriptFd'.  That program is mostly
941    the contents of file 'inputFileName' (special value "-" means Standard
942    Input), but we may add a little to it.
943 
944    The image has dimensions 'imageDim' and is oriented on the page according
945    to 'orientation' ('imageDim' X and Y are with respect to the image itself,
946    without regard to how it is oriented on the page).
947 -----------------------------------------------------------------------------*/
948     FILE * pipeToGsP;  /* Pipe to Ghostscript's standard input */
949     FILE * ifP;
950 
951     pipeToGsP = fdopen(pipeToGhostscriptFd, "w");
952     if (pipeToGsP == NULL)
953         pm_error("Unable to open stream on pipe to Ghostscript process.");
954 
955     ifP = pm_openr(inputFileName);
956     /*
957       In encapsulated Postscript, we the encapsulator are supposed to
958       handle showing the page (which we do by passing a showpage
959       statement to Ghostscript).  Any showpage statement in the
960       input must be defined to have no effect.
961 
962       See "Enscapsulated PostScript Format File Specification",
963       v. 3.0, 1 May 1992, in particular Example 2, p. 21.  I found
964       it at
965       http://partners.adobe.com/asn/developer/pdfs/tn/5002.EPSF_Spec.pdf
966       The example given is a much fancier solution than we need
967       here, I think, so I boiled it down a bit.  JM
968     */
969     if (language == ENCAPSULATED_POSTSCRIPT) {
970         const char * const defShowpageCmd =
971             "/b4_Inc_state save def /showpage { } def";
972         if (verbose)
973             pm_message("Defining showpage with '%s'", defShowpageCmd);
974 
975         fprintf(pipeToGsP, "\n%s\n", defShowpageCmd);
976     }
977 
978     writePstrans(borderedBox, imageDim, orientation, pipeToGsP);
979 
980     /* If our child dies, it closes the pipe and when we next write to it,
981        we get a SIGPIPE.  We must survive that signal in order to report
982        on the fate of the child.  So we ignore SIGPIPE:
983     */
984     signal(SIGPIPE, SIG_IGN);
985 
986     copyFileStream(ifP, pipeToGsP);
987 
988     pm_close(ifP);
989 
990     if (language == ENCAPSULATED_POSTSCRIPT) {
991         const char * const restoreShowpageCmd =
992             "b4_Inc_state restore showpage";
993 
994         if (verbose)
995             pm_message("Restoring showpage with '%s'", restoreShowpageCmd);
996 
997         fprintf(pipeToGsP, "\n%s\n", restoreShowpageCmd);
998     }
999     fclose(pipeToGsP);
1000 }
1001 
1002 
1003 
1004 static struct Dimensions
pageDimFromImageDim(struct Dimensions const imageDim,enum Orientation const orientation)1005 pageDimFromImageDim(struct Dimensions const imageDim,
1006                     enum Orientation  const orientation) {
1007 /*----------------------------------------------------------------------------
1008    The dimensions of the page of an image whose dimensions are
1009    'imageDim', if we place it on the page with orientation 'orientation'.
1010 
1011    (I.e. swap and X and Y if landscape orientation).
1012 
1013    'orientation' must not be UNSPECIFIED.
1014 -----------------------------------------------------------------------------*/
1015     struct Dimensions retval;
1016 
1017     switch (orientation) {
1018     case PORTRAIT:
1019         retval = imageDim;
1020         break;
1021     case LANDSCAPE:
1022         retval.xsize = imageDim.ysize;
1023         retval.ysize = imageDim.xsize;
1024         retval.xres  = imageDim.yres;
1025         retval.yres  = imageDim.xres;
1026         break;
1027     case UNSPECIFIED:
1028         assert(false);
1029         break;
1030     }
1031 
1032     return retval;
1033 }
1034 
1035 
1036 
1037 static void
executeGhostscript(char const inputFileName[],struct Box const borderedBox,struct Dimensions const imageDim,enum Orientation const orientation,char const ghostscriptDevice[],char const outfileArg[],unsigned int const textalphabits,enum PostscriptLanguage const language)1038 executeGhostscript(char                    const inputFileName[],
1039                    struct Box              const borderedBox,
1040                    struct Dimensions       const imageDim,
1041                    enum Orientation        const orientation,
1042                    char                    const ghostscriptDevice[],
1043                    char                    const outfileArg[],
1044                    unsigned int            const textalphabits,
1045                    enum PostscriptLanguage const language) {
1046 
1047     int rc;
1048     int pipefd[2];
1049 
1050     if (strlen(outfileArg) > 80)
1051         pm_error("output file spec too long.");
1052 
1053     rc = pm_pipe(pipefd);
1054     if (rc < 0)
1055         pm_error("Unable to create pipe to talk to Ghostscript process.  "
1056                  "errno = %d (%s)", errno, strerror(errno));
1057 
1058     rc = fork();
1059     if (rc < 0)
1060         pm_error("Unable to fork a Ghostscript process.  errno=%d (%s)",
1061                  errno, strerror(errno));
1062     else if (rc == 0) {
1063         /* Child process */
1064         close(pipefd[1]);
1065         execGhostscript(pipefd[0], ghostscriptDevice, outfileArg,
1066                         pageDimFromImageDim(imageDim, orientation),
1067                         textalphabits);
1068     } else {
1069         /* parent process */
1070         pid_t const ghostscriptPid = rc;
1071         int const pipeToGhostscriptFd = pipefd[1];
1072 
1073         int gsTermStatus;  /* termination status of Ghostscript process */
1074         pid_t rc;
1075 
1076         close(pipefd[0]);
1077 
1078         feedPsToGhostScript(inputFileName, borderedBox,
1079                             imageDim, orientation,
1080                             pipeToGhostscriptFd, language);
1081 
1082         rc = waitpid(ghostscriptPid, &gsTermStatus, 0);
1083         if (rc < 0)
1084             pm_error("Wait for Ghostscript process to terminated failed.  "
1085                      "errno = %d (%s)", errno, strerror(errno));
1086 
1087         if (gsTermStatus != 0) {
1088             if (WIFEXITED(gsTermStatus))
1089                 pm_error("Ghostscript failed.  Exit code=%d\n",
1090                          WEXITSTATUS(gsTermStatus));
1091             else if (WIFSIGNALED(gsTermStatus))
1092                 pm_error("Ghostscript process died because of a signal %d.",
1093                          WTERMSIG(gsTermStatus));
1094             else
1095                 pm_error("Ghostscript process died with exit code %d",
1096                          gsTermStatus);
1097         }
1098     }
1099 }
1100 
1101 
1102 
1103 int
main(int argc,char ** argv)1104 main(int argc, char ** argv) {
1105 
1106     struct CmdlineInfo cmdline;
1107     const char * inputFileName;  /* malloc'ed */
1108         /* The file specification of our Postscript input file */
1109     struct Dimensions imageDim;
1110         /* Size and resolution of the input image */
1111     struct Box extractBox;
1112         /* coordinates of the box within the input we are to extract; i.e.
1113            that will become the output.
1114            */
1115     struct Box borderedBox;
1116         /* Same as above, but expanded to include borders */
1117 
1118     enum PostscriptLanguage language;
1119     enum Orientation orientation;
1120     const char * ghostscriptDevice;
1121     const char * outfileArg;
1122 
1123     pnm_init(&argc, argv);
1124 
1125     parseCommandLine(argc, argv, &cmdline);
1126 
1127     verbose = cmdline.verbose;
1128 
1129     addPsToFileName(cmdline.inputFileName, &inputFileName);
1130 
1131     extractBox = computeBoxToExtract(cmdline.extractBox, inputFileName);
1132 
1133     language = languageDeclaration(inputFileName);
1134 
1135     orientation = computeOrientation(cmdline, extractBox);
1136 
1137     borderedBox = addBorders(extractBox, cmdline.xborder, cmdline.yborder);
1138 
1139     assertValidBox(borderedBox); assert(borderedBox.isDefined);
1140 
1141     computeSizeRes(cmdline, borderedBox, &imageDim);
1142 
1143     assert(imageDim.xres > 0); assert(imageDim.yres > 0);
1144 
1145     outfileArg = computeOutfileArg(cmdline);
1146 
1147     ghostscriptDevice =
1148         computeGsDevice(cmdline.formatType, cmdline.forceplain);
1149 
1150     pm_message("Writing %s format", ghostscriptDevice);
1151 
1152     executeGhostscript(inputFileName, borderedBox, imageDim, orientation,
1153                        ghostscriptDevice, outfileArg, cmdline.textalphabits,
1154                        language);
1155 
1156     pm_strfree(ghostscriptDevice);
1157     pm_strfree(outfileArg);
1158 
1159     return 0;
1160 }
1161 
1162 
1163