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