1 /*
2 * FIG : Facility for Interactive Generation of figures
3 * Copyright (c) 1985-1988 by Supoj Sutanthavibul
4 * Parts Copyright (c) 1989-2015 by Brian V. Smith
5 * Parts Copyright (c) 1991 by Paul King
6 * Parts Copyright (c) 2016-2020 by Thomas Loimer
7 *
8 * Any party obtaining a copy of these files is granted, free of charge, a
9 * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
10 * nonexclusive right and license to deal in this software and documentation
11 * files (the "Software"), including without limitation the rights to use,
12 * copy, modify, merge, publish, distribute, sublicense and/or sell copies of
13 * the Software, and to permit persons who receive copies from any such
14 * party to do so, with the only requirement being that the above copyright
15 * and this permission notice remain intact.
16 *
17 */
18
19 /*
20 * Call ghostscript to get the /MediaBox, and convert eps/pdf to bitmaps.
21 * Autor: Thomas Loimer, 2020.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <inttypes.h> /* includes stdint.h */
29 #include <math.h>
30 #include <stdbool.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <X11/X.h> /* TrueColor */
35
36 #ifdef HAVE_GSLIB
37 #include <ghostscript/gdevdsp.h>
38 #include <ghostscript/gserrors.h>
39 #include <ghostscript/iapi.h>
40 #endif
41
42 #include "object.h"
43 #include "resources.h"
44 #include "f_util.h" /* map_to_pattern(), map_to_mono() */
45 #include "w_msgpanel.h" /* file_msg() */
46
47 /*
48 * Exported functions: gs_mediabox(), gs_bitmap().
49 * These are currently only used in f_readeps.c, hence
50 * an extra header file is not provided.
51 */
52
53 #define BITMAP_PPI 160 /* the resolution for rendering bitmaps */
54 #define GS_ERROR (-2)
55
56 /*
57 * Link into the ghostscript library.
58 * If invoked via the library, ghostscript calls three callback functions when
59 * wishing to read from stdin or write to stdout or stderr, respectively.
60 * However, if a device is given, ghostscript writes directly to stdout. This
61 * was found rather late, therefore some functions below are more modular than
62 * necessary.
63 * Here, set up calls to ghostscript and callback functions to read a pdf
64 * file, scanning the output for the /MediaBox specification.
65 *
66 * If linking into the library by dlopen() fails, call the ghostscript
67 * executable via popen("gs..", "r").
68 */
69
70 #ifdef HAVE_GSLIB
71 /*
72 * callback data struct
73 * A pointer to this struct can be passed to ghostscript, which
74 * is then passed to the callback functions.
75 */
76 struct _callback_data {
77 int *bb; /* for stdout_mediabox() callback function */
78 char *errbuf; /* fixed buffer */
79 int errsize;
80 };
81
82 /* callback functions */
83 static int
stdin_void(void * caller_handle,char * buf,int len)84 stdin_void(void *caller_handle, char *buf, int len)
85 {
86 (void)caller_handle;
87 (void)buf;
88
89 return len;
90 }
91
92 static int
stderr_buf(void * caller_handle,const char * str,int len)93 stderr_buf(void *caller_handle, const char *str, int len)
94 {
95 struct _callback_data *data = (struct _callback_data *)caller_handle;
96 static int pos = 0;
97
98 /* buffer full, comparison for == should be sufficient */
99 if (pos >= data->errsize - 1)
100 return len;
101
102 if (pos + len >= data->errsize) /* leave space for a terminating '\0' */
103 len = data->errsize - pos - 1;
104 memcpy(data->errbuf + pos, str, (size_t)len);
105 pos += len;
106 data->errbuf[pos] = '\0';
107
108 return len;
109 }
110
111 static int
stdout_mediabox(void * caller_handle,const char * str,int len)112 stdout_mediabox(void *caller_handle, const char *str, int len)
113 {
114 struct _callback_data *data = (struct _callback_data *)caller_handle;
115 int err;
116 double fbb[4];
117
118 /* This rests on the assumption that ghostscript writes the required
119 information all at once to str. Should use a buffer, instead. */
120
121 /* gs always uses full stops as decimal character. The locale is already
122 set to C in read_pdf() */
123 err = sscanf(str, "[%lf %lf %lf %lf]", fbb, fbb+1, fbb+2, fbb+3);
124 if (err == 4) {
125 data->bb[0] = (int)floor(fbb[0]);
126 data->bb[1] = (int)floor(fbb[1]);
127 data->bb[2] = (int)ceil(fbb[2]);
128 data->bb[3] = (int)ceil(fbb[3]);
129 } else {
130 /* Either the pdf is corrupt, which yields a matching failure,
131 * or a read error occured or EOF is reached.
132 * Ghostscript returns with zero from a corrupt pdf, and writes
133 * error information to stdout.
134 * Use the bounding box to report a, most likely, corrupt pdf.
135 */
136 data->bb[0] = data->bb[1] = data->bb[2] = 0;
137 data->bb[3] = GS_ERROR;
138 }
139
140 return len;
141 }
142
143
144 /*
145 * Link into the ghostscript library with argcnew and argvnew[] for gs >= 9.50,
146 * argcold and argcold[] for gs < 9.50.
147 * Return 0 on success, -1 if the library could not be called, -2 (GS_ERROR)
148 * on a ghostscript error.
149 */
150 static int
gslib(struct _callback_data * data,int (* gs_stdin)(void *,char *,int),int (* gs_stdout)(void *,const char *,int),int (* gs_stderr)(void *,const char *,int),int argcnew,int argcold,char * argvnew[],char * argvold[])151 gslib(struct _callback_data *data, int (*gs_stdin)(void *, char*, int),
152 int (*gs_stdout)(void *, const char*, int),
153 int (*gs_stderr)(void *, const char*, int),
154 int argcnew, int argcold,
155 char *argvnew[], char *argvold[])
156 {
157 int i;
158 int code;
159 const int call_gsexe = -1;
160 int argc;
161 char **argv;
162 struct gsapi_revision_s rev;
163 void *minst = NULL; /* must be initialized to NULL */
164
165
166 /* get gs version */
167 if (gsapi_revision(&rev, (int)(sizeof rev)))
168 return call_gsexe;
169
170 if (rev.revision >= 950) {
171 argc = argcnew;
172 argv = (char **)argvnew;
173 } else {
174 argc = argcold;
175 argv = (char **)argvold;
176 }
177 if (appres.DEBUG) {
178 fprintf(stderr, "Using ghostscript library, revision %ld\n",
179 rev.revision);
180 fputs("Arguments:", stderr);
181 for (i = 0; i < argc; ++i)
182 fprintf(stderr, " %s", argv[i]);
183 fputc('\n', stderr);
184 }
185
186 code = gsapi_new_instance(&minst, (void *)data);
187 if (code < 0) {
188 return call_gsexe;
189 }
190 /* All gsapi_*() functions below return an int, but some
191 probably do not return an useful error code. */
192 gsapi_set_stdio(minst, gs_stdin, gs_stdout, gs_stderr);
193 code = gsapi_set_arg_encoding(minst, GS_ARG_ENCODING_UTF8);
194 if (code == 0)
195 code = gsapi_init_with_args(minst, argc, argv);
196 if (code == 0 || code == gs_error_Quit || code < 0 ||
197 code <= gs_error_Fatal)
198 code = gsapi_exit(minst);
199 gsapi_delete_instance(minst);
200
201 if (code == 0 || code == gs_error_Quit) {
202 return 0;
203 }
204
205 /*
206 * Unfortunately, ghostscript does not report corrupt pdf files, but
207 * still returns zero. Corrupt ps/eps input seems to be reported.
208 */
209 file_msg("Error in ghostscript library, %s.\nOptions:", argv[0]);
210 for (i = 1; i < argc; ++i)
211 file_msg(" %s", argv[i]);
212 if (data->errbuf[0] != '\0')
213 file_msg("Ghostscript error message:\n%s", data->errbuf);
214 return GS_ERROR;
215 }
216 #endif /* HAVE_GSLIB */
217
218 /*
219 * Call ghostscript.
220 * Return an open file stream for reading,
221 * *out = popen({exenew, exeold}, "r");
222 * The user must call pclose(out) after calling gsexe(&out,...).
223 * Use exenew for gs > 9.49, exeold otherwise.
224 * Return 0 for success, -1 on failure to call ghostscript.
225 */
226 static int
gsexe(FILE ** out,bool * isnew,char * exenew,char * exeold)227 gsexe(FILE **out, bool *isnew, char *exenew, char *exeold)
228 {
229 #define old_version 1
230 #define new_version 2
231 #define no_version 0
232 static int version = no_version;
233 const int failure = -1;
234 char *exe;
235 FILE *fp;
236
237
238 if (version == no_version) {
239 int n;
240 int stat;
241 size_t len;
242 double rev;
243 char cmd_buf[128];
244 char *cmd = cmd_buf;
245 const char version_arg[] = " --version";
246
247 if (appres.DEBUG)
248 fprintf(stderr,
249 "Trying to call ghostscript executable %s...\n",
250 appres.ghostscript);
251
252 /* get the ghostscript version */
253 /* allocate the command buffer, if necessary */
254 len = strlen(appres.ghostscript) + sizeof version_arg;
255 if (len > sizeof cmd_buf) {
256 if ((cmd = malloc(len)) == NULL)
257 return failure;
258 }
259
260 /* write the command string */
261 sprintf(cmd, "%s%s", appres.ghostscript, version_arg);
262 fp = popen(cmd, "r");
263 if (cmd != cmd_buf)
264 free(cmd);
265 if (fp == NULL)
266 return failure;
267
268 /* scan for the ghostscript version */
269 /* Currently, gsexe() is only called from gsexe_mediabox(), and
270 * the locale is already set to C in read_pdf. If this changes,
271 * make sure to have here the C or POSIX locale. */
272 n = fscanf(fp, "%lf", &rev);
273 stat = pclose(fp);
274 if (n != 1 || stat != 0)
275 return failure;
276
277 if (rev > 9.49) {
278 exe = exenew;
279 version = new_version;
280 *isnew = true;
281 } else {
282 exe = exeold;
283 version = old_version;
284 *isnew = false;
285 }
286
287 if (appres.DEBUG)
288 fprintf(stderr, "...version %.2f\nCommand line: %s\n",
289 rev, exe);
290 } else { /* version == no_version */
291 if (version == new_version) {
292 exe = exenew;
293 *isnew = true;
294 } else {
295 exe = exeold;
296 *isnew = false;
297 }
298 #undef new_version
299 #undef old_version
300 #undef no_version
301
302 if (appres.DEBUG)
303 fprintf(stderr,
304 "Calling ghostscript.\nCommand line: %s\n",
305 exe);
306 }
307
308 if ((*out = popen(exe, "r")) == NULL)
309 return failure;
310
311 return 0;
312 }
313
314 /*
315 * Call ghostscript to extract the /MediaBox from the pdf given in file.
316 * Command line, for gs >= 9.50,
317 * gs -q -dNODISPLAY --permit-file-read=in.pdf -c \
318 * "(in.pdf) (r) file runpdfbegin 1 pdfgetpage /MediaBox pget pop == quit"
319 * gs < 9.50:
320 * gs -q -dNODISPLAY -dNOSAFER -c \
321 * "(in.pdf) (r) file runpdfbegin 1 pdfgetpage /MediaBox pget pop == quit"
322 * The command line was found, and modified a bit, at
323 *https://stackoverflow.com/questions/2943281/using-ghostscript-to-get-page-size
324 * Beginning with gs 9.50, "-dSAFER" is the default, and permission to access
325 * files must be explicitly given with the --permit-file-{read,write,..}
326 * options. Before gs 9.50, "-dNOSAFER" is the default.
327 *
328 * Return 0 on success, -1 on failure, -2 (GS_ERROR) for a ghostscript-error,
329 * -3 if the path to the ghostscript executable is not given.
330 */
331 static int
gsexe_mediabox(char * file,int * llx,int * lly,int * urx,int * ury)332 gsexe_mediabox(char *file, int *llx, int *lly, int *urx, int *ury)
333 {
334 bool isnew;
335 int n;
336 int stat;
337 size_t len;
338 char *fmt;
339 char exenew_buf[256];
340 char exeold_buf[sizeof exenew_buf];
341 char *exenew;
342 char *exeold;
343 double bb[4] = { 0.0, 0.0, -1.0, -1.0 };
344 FILE *gs_output;
345
346 if (*appres.ghostscript == '\0')
347 return -3;
348
349 exenew = "%s -q -dNODISPLAY \"--permit-file-read=%s\" -c \"(%s) (r) "
350 "file runpdfbegin 1 pdfgetpage /MediaBox pget pop == quit\"";
351 exeold = "%s -q -dNODISPLAY -c \"(%s) (r) "
352 "file runpdfbegin 1 pdfgetpage /MediaBox pget pop == quit\"";
353
354 /* malloc() buffers for the command line, if necessary */
355 fmt = exenew;
356 len = strlen(exenew) + 2*strlen(file) + strlen(appres.ghostscript) - 5;
357 if (len > sizeof exenew_buf) {
358 if ((exenew = malloc(len)) == NULL)
359 return -1;
360 } else {
361 exenew = exenew_buf;
362 }
363 sprintf(exenew, fmt, appres.ghostscript, file, file);
364
365 fmt = exeold;
366 len = strlen(exeold) + strlen(file) + strlen(appres.ghostscript) - 3;
367 if (len > sizeof exeold_buf) {
368 if ((exeold = malloc(len)) == NULL) {
369 if (exenew != exenew_buf)
370 free(exenew);
371 return -1;
372 }
373 } else {
374 exeold = exeold_buf;
375 }
376 sprintf(exeold, fmt, appres.ghostscript, file);
377
378 /* call ghostscript */
379 stat = gsexe(&gs_output, &isnew, exenew, exeold);
380
381 if (exenew != exenew_buf)
382 free(exenew);
383 if (exeold != exeold_buf)
384 free(exeold);
385
386 if (stat != 0) {
387 file_msg("Cannot open pipe with command:\n%s",
388 isnew ? exenew : exeold);
389 return -1;
390 }
391
392 /* scan the output */
393 n = fscanf(gs_output, "[%lf %lf %lf %lf]", bb, bb+1, bb+2, bb+3);
394 stat = pclose(gs_output);
395 if (n != 4 || stat != 0) {
396 if (stat) {
397 file_msg("Error calling ghostscript. Command:\n%s",
398 isnew ? exenew : exeold);
399 return GS_ERROR;
400 } else {
401 return -1;
402 }
403 }
404
405 *llx = (int)floor(bb[0]);
406 *lly = (int)floor(bb[1]);
407 *urx = (int)ceil(bb[2]);
408 *ury = (int)ceil(bb[3]);
409
410 return 0;
411 }
412
413 #ifdef HAVE_GSLIB
414 /*
415 * Return codes: 0..success, -1..within the function, an error occured,
416 * GS_ERROR..the ghostscript interpreter returned an error
417 */
418 static int
gslib_mediabox(char * file,int * llx,int * lly,int * urx,int * ury)419 gslib_mediabox(char *file, int *llx, int *lly, int *urx, int *ury)
420 {
421 int stat;
422 int bb[4] = { 0, 0, -1, -1};
423 char *fmt;
424 size_t len;
425 #define argc 6
426 char errbuf[256] = "";
427 char *argnew[argc];
428 char *argold[argc];
429 char permit_buf[sizeof errbuf];
430 char cmd_buf[sizeof errbuf];
431 struct _callback_data data = {
432 bb, /* bb */
433 errbuf, /* errbuf */
434 sizeof errbuf /* errsize */
435 };
436
437 /* write the argument list and command line */
438 argnew[0] = "libgs";
439 argnew[1] = "-q";
440 argnew[2] = "-dNODISPLAY";
441 argnew[3] = "--permit-file-read=%s"; /* file */
442 argnew[4] = "-c";
443 argnew[5] =
444 "(%s) (r) file runpdfbegin 1 pdfgetpage /MediaBox pget pop == quit";
445
446 argold[0] = argnew[0];
447 argold[1] = argnew[1];
448 argold[2] = argnew[2];
449 argold[3] = "-dNOSAFER";
450 argold[4] = argnew[4];
451 /* argold[5] = argnew[5], assigned below */
452
453 /* write and, if necessary, malloc() argument strings */
454 fmt = argnew[3];
455 len = strlen(file) + strlen(fmt) - 1;
456 if (len > sizeof permit_buf) {
457 if ((argnew[3] = malloc(len)) == NULL)
458 return -1;
459 } else {
460 argnew[3] = permit_buf;
461 }
462 sprintf(argnew[3], fmt, file);
463
464 fmt = argnew[5];
465 len = strlen(file) + strlen(fmt) - 1;
466 if (len > sizeof cmd_buf) {
467 if ((argnew[5] = malloc(len)) == NULL) {
468 if (argnew[3] != permit_buf)
469 free(argnew[3]);
470 return -1;
471 }
472 } else {
473 argnew[5] = cmd_buf;
474 }
475 sprintf(argnew[5], fmt, file);
476 argold[5] = argnew[5];
477
478 /* call into the ghostscript library */
479 stat = gslib(&data, stdin_void, stdout_mediabox, stderr_buf,
480 argc, argc, argnew, argold);
481 if (argnew[3] != permit_buf)
482 free(argnew[3]);
483 if (argnew[5] != cmd_buf)
484 free(argnew[5]);
485 #undef argc
486
487 if (stat == 0) {
488 if (data.bb[1] == 0 && data.bb[3] == GS_ERROR)
489 return GS_ERROR;
490 *llx = data.bb[0];
491 *lly = data.bb[1];
492 *urx = data.bb[2];
493 *ury = data.bb[3];
494 }
495
496 return stat;
497 }
498 #endif /* HAVE_GSLIB */
499
500 /*
501 * Call ghostscript to extract the /MediaBox from the pdf given in file.
502 * Return 0 on success, -1 on failure, GS_ERROR (-2) for a ghostscript error.
503 */
504 int
gs_mediabox(char * file,int * llx,int * lly,int * urx,int * ury)505 gs_mediabox(char *file, int *llx, int *lly, int *urx, int *ury)
506 {
507 int stat;
508
509 #ifdef HAVE_GSLIB
510 stat = gslib_mediabox(file, llx, lly, urx, ury);
511 if (stat == -1)
512 #endif
513 stat = gsexe_mediabox(file, llx, lly, urx, ury);
514 if (stat == GS_ERROR) {
515 file_msg("Could not parse file '%s' with ghostscript.", file);
516 file_msg("If available, error messages are displayed above.");
517 }
518 return stat;
519 }
520
521 /*
522 * Invoke (approximately) the command
523 *
524 * gs [..] -sDEVICE=bitrgb -dRedValues=256 -r<ppi> -g<widht>x<height> \
525 * -o- <file>,
526 *
527 * to obtain a 24bit bitmap of RGB-Values. (It is sufficient to give one out of
528 * -dGreenValues=256, -dBlueValues=256 or -dRedValues=256.)
529 * For monochrome images, invoke
530 *
531 * gs [..] -sDEVICE=bit -r<ppi> -g<widht>x<height> -o- <file>.
532 *
533 * The neural net that reduces the bitmap to a colormapped image of 256 colors
534 * expects BGR triples. Hence, swap the red and blue values.
535 * Return 0 on success, -1 for failure, or GS_ERROR if ghostscript returned with
536 * a non-zero exit code.
537 *
538 */
539 static int
gsexe_bitmap(char * file,F_pic * pic,int llx,int lly,int urx,int ury)540 gsexe_bitmap(char *file, F_pic *pic, int llx, int lly, int urx, int ury)
541 {
542 #define string_of(ppi) #ppi
543 #define rgb_fmt(ppi) "%s -q -dSAFER -sDEVICE=bitrgb -dBlueValues=256 -r" \
544 string_of(ppi) " -g%dx%d -sPageList=1 -o- -c '%d %d translate' -f %s"
545 #define bw_fmt(ppi) "%s -q -dSAFER -sDEVICE=bit -r" string_of(ppi) \
546 " -g%dx%d -sPageList=1 -o- -c '%d %d translate' -f %s"
547 /*
548 * Instead of -llx -lly translate, the commands passed to ghostscript used to
549 * be:
550 * -llx -lly translate
551 * % mark dictionary (otherwise fails for tiger.ps (e.g.):
552 * % many ps files don't 'end' their dictionaries)
553 * countdictstack
554 * mark
555 * /oldshowpage {showpage} bind def
556 * /showpage {} def
557 * /initgraphics {} def <<< this nasty command should never be used!
558 * /initmmatrix {} def <<< this one too
559 * (psfile) run
560 * oldshowpage
561 * % clean up stacks and dicts
562 * cleartomark
563 * countdictstack exch sub { end } repeat
564 * quit
565 */
566 int stat;
567 int w, h;
568 const int failure = -1;
569 size_t len;
570 size_t len_bitmap;
571 char *fmt;
572 char *exe;
573 unsigned char *pos;
574 char exe_buf[256];
575 FILE *gs_output;
576
577 if (*appres.ghostscript == '\0')
578 return failure;
579
580 /* This is the size, to which a pixmap is rendered for display
581 on the canvas, at a magnification of 2.
582 The +1 is sometimes correct, sometimes not */
583 w = (urx - llx) * BITMAP_PPI / 72 + 1;
584 h = (ury - lly) * BITMAP_PPI / 72 + 1;
585 if (tool_cells <= 2 || appres.monochrome) {
586 fmt = bw_fmt(BITMAP_PPI);
587 len_bitmap = (w + 7) / 8 * h;
588 pic->pic_cache->numcols = 0;
589 } else {
590 fmt = rgb_fmt(BITMAP_PPI);
591 if (tool_vclass == TrueColor && image_bpp == 4)
592 len_bitmap = w * h * image_bpp;
593 else
594 len_bitmap = w * h * 3;
595 }
596 pic->pic_cache->bit_size.x = w;
597 pic->pic_cache->bit_size.y = h;
598
599 /* malloc() buffer for the command line, if necessary; The "+ 80" allows
600 four integers of 20 digits each. */
601 len = strlen(fmt) + strlen(file) + strlen(appres.ghostscript) + 80;
602 if (len > sizeof exe_buf) {
603 if ((exe = malloc(len)) == NULL)
604 return failure;
605 } else {
606 exe = exe_buf;
607 }
608
609 /* still check for overflow, because of the integers */
610 if (len <= sizeof exe_buf)
611 len = sizeof exe_buf;
612 stat = snprintf(exe, len, fmt, appres.ghostscript, w, h, -llx, -lly,
613 file);
614 if ((size_t)stat >= len) {
615 if (exe == exe_buf) {
616 if ((exe = malloc((size_t)(stat + 1))) == NULL)
617 return failure;
618 } else {
619 if ((exe = realloc(exe, (size_t)(stat + 1))) == NULL) {
620 free(exe);
621 return failure;
622 }
623 }
624 sprintf(exe, fmt, appres.ghostscript, w, h, -llx, -lly, file);
625 }
626 #undef rgb_fmt
627 #undef bw_fmt
628
629 if (appres.DEBUG)
630 fprintf(stderr, "Calling ghostscript. Command:\n %s\n", exe);
631
632 gs_output = popen(exe, "r");
633 if (gs_output == NULL)
634 file_msg("Cannot open pipe with command:\n%s", exe);
635 if (exe != exe_buf)
636 free(exe);
637 if (gs_output == NULL)
638 return failure;
639
640 if ((pic->pic_cache->bitmap = malloc(len_bitmap)) == NULL) {
641 file_msg("Out of memory.\nCannot create pixmap for %s.", file);
642 return failure;
643 }
644
645 /* write result to pic->pic_cache->bitmap */
646 pos = pic->pic_cache->bitmap;
647 if (tool_cells <= 2 || appres.monochrome) {
648 int c;
649 while ((c = fgetc(gs_output)) != EOF &&
650 (size_t)(pos - pic->pic_cache->bitmap) < len_bitmap) {
651 *(pos++) = (unsigned char)c;
652 }
653 pic->pic_cache->numcols = 0;
654
655 } else if (tool_vclass == TrueColor && image_bpp == 4) {
656 int c[3];
657 while ((c[0] = fgetc(gs_output)) != EOF &&
658 (c[1] = fgetc(gs_output)) != EOF &&
659 (c[2] = fgetc(gs_output)) != EOF &&
660 (size_t)(pos - pic->pic_cache->bitmap) <
661 len_bitmap) {
662 /* this should take care of endian-ness */
663 *(unsigned int *)pos = ((unsigned int)c[0] << 16) +
664 ((unsigned int)c[1] << 8) + (unsigned int)c[2];
665 pos += image_bpp;
666 }
667 pic->pic_cache->numcols = -1; /* no colormap */
668
669 } else {
670 int c[3];
671 /* map_to_palette() expects BGR triples, swap the RGB triples */
672 while ((c[0] = fgetc(gs_output)) != EOF &&
673 (c[1] = fgetc(gs_output)) != EOF &&
674 (c[2] = fgetc(gs_output)) != EOF &&
675 (size_t)(pos - pic->pic_cache->bitmap) <
676 len_bitmap) {
677 *(pos++) = (unsigned char)c[2];
678 *(pos++) = (unsigned char)c[1];
679 *(pos++) = (unsigned char)c[0];
680 }
681 }
682 stat = pclose(gs_output);
683 /* if reading stops just at the last byte in the file, then
684 neither is c[?] == EOF, nor does feof() necessarily return true. */
685 if ((size_t)(pos - pic->pic_cache->bitmap) != len_bitmap) {
686 free(pic->pic_cache->bitmap);
687 pic->pic_cache->bitmap = NULL;
688 file_msg("Error reading pixmap to render %s.", file);
689 return failure;
690 }
691 if (stat) {
692 free(pic->pic_cache->bitmap);
693 pic->pic_cache->bitmap = NULL;
694 return GS_ERROR;
695 }
696
697 if (tool_vclass != TrueColor && tool_cells > 2 && !appres.monochrome) {
698 if (!map_to_palette(pic)) {
699 file_msg("Cannot create colormapped image for %s.",
700 file);
701 return failure;
702 }
703 }
704
705 return 0;
706 }
707
708 #ifdef HAVE_GSLIB
709
710 struct _stdio_data {
711 size_t errsize;
712 size_t errpos;
713 size_t outsize;
714 size_t outpos;
715 char *outbuf;
716 char *errbuf;
717 };
718
719 static int
bitmap_stderr(void * data_handle,const char * str,int len)720 bitmap_stderr(void *data_handle, const char *str, int len)
721 {
722 struct _stdio_data *data = (struct _stdio_data *)data_handle;
723 char *buf = data->errbuf;
724 size_t *pos = &(data->errpos);
725 size_t *size = &(data->errsize);
726
727 /* buffer full, comparison for == should be sufficient */
728 if (*pos >= *size - 1)
729 return len;
730
731 if (*pos + len >= *size) /* leave space for terminating '\0' */
732 len = *size - *pos - 1;
733 memcpy(buf + *pos, str, (size_t)len);
734 *pos += len;
735 buf[*pos] = '\0';
736
737 return len;
738 }
739
740 static int
bitmap_stdout(void * data_handle,const char * str,int len)741 bitmap_stdout(void *data_handle, const char *str, int len)
742 {
743 struct _stdio_data *data = (struct _stdio_data *)data_handle;
744 char *buf = data->outbuf;
745 size_t *pos = &(data->outpos);
746 size_t *size = &(data->outsize);
747
748 /* buffer full, comparison for == should be sufficient */
749 if (*pos >= *size - 1)
750 return len;
751
752 if (*pos + len >= *size) /* leave space for terminating '\0' */
753 len = *size - *pos - 1;
754 memcpy(buf + *pos, str, (size_t)len);
755 *pos += len;
756 buf[*pos] = '\0';
757
758 return len;
759 }
760
761 /* Data provided to the callback functions */
762 struct calldata {
763 int width;
764 int height;
765 int raster;
766 unsigned char *img;
767 };
768
769
770 /*
771 * Callback functions for the ghostscript display device.
772 * The functions are sorted in the order they are called.
773 * See ghostscript/gdevdsp.h.
774 */
775 static int
display_open(void * handle,void * device)776 display_open(void *handle, void *device)
777 {
778 (void) device;
779 (void) handle;
780
781 return 0;
782 }
783
784 static int
display_presize(void * handle,void * device,int width,int height,int raster,unsigned int format)785 display_presize(void *handle, void *device, int width, int height,
786 int raster, unsigned int format)
787 {
788 (void) device;
789 (void) raster;
790 (void) format;
791 struct calldata *data = (struct calldata *)handle;
792
793 if (width == data->width && height == data->height) {
794 return 0;
795 } else {
796 if (appres.DEBUG)
797 fputs("display_presize: Wrong image dimensions.\n",
798 stderr);
799 return -1;
800 }
801 }
802
803 static void *
display_memalloc(void * handle,void * device,unsigned long size)804 display_memalloc(void *handle, void *device, unsigned long size)
805 {
806 (void) device;
807 struct calldata *data = (struct calldata *)handle;
808
809 data->img = malloc((size_t)size);
810
811 if (appres.DEBUG && data->img == NULL)
812 fputs("gslib_bitmap() - display_memalloc(): Out of memory.\n",
813 stderr);
814
815 return (void *)data->img;
816 }
817
818 static int
display_size(void * handle,void * device,int width,int height,int raster,unsigned int format,unsigned char * pimage)819 display_size(void *handle, void *device, int width, int height,
820 int raster, unsigned int format, unsigned char *pimage)
821 {
822 (void) device;
823 (void) raster;
824 (void) format;
825 (void) pimage;
826 struct calldata *data = (struct calldata *)handle;
827
828 if (width == data->width && height == data->height) {
829 data->raster = raster;
830 return 0;
831 } else {
832 if (appres.DEBUG)
833 fputs("display_size: Wrong image dimensions.\n",
834 stderr);
835 return -1;
836 }
837 }
838
839 static int
display_sync(void * handle,void * device)840 display_sync(void *handle, void *device)
841 {
842 (void) handle;
843 (void) device;
844
845 return 0;
846 }
847
848 static int
display_page(void * handle,void * device,int copies,int flush)849 display_page(void *handle, void *device, int copies, int flush)
850 {
851 (void) handle;
852 (void) device;
853 (void) copies;
854 (void) flush;
855
856 /* Other error codes cause messages from ghostscript on stderr. */
857 return gs_error_InterpreterExit;
858 }
859
860 static int
display_preclose(void * handle,void * device)861 display_preclose(void *handle, void *device)
862 {
863 (void) handle;
864 (void) device;
865
866 return 0;
867 }
868
869 static int
display_memfree(void * handle,void * device,void * mem)870 display_memfree(void *handle, void *device, void *mem)
871 {
872 (void) handle;
873 (void) device;
874 (void) mem;
875
876 return 0;
877 }
878
879 static int
display_close(void * handle,void * device)880 display_close(void *handle, void *device)
881 {
882 (void) handle;
883 (void) device;
884
885 return 0;
886 }
887
888 /*
889 * Link into the ghostscript library, writing to the display device.
890 * Return 0 on success, -1 on failure, or GS_ERROR for a ghostscript error.
891 * TODO: With a corrupt pdf, ghostscript will write messages to stdout and
892 * return 0. A stdout callback should be used to catch these messages.
893 * Warning messages seem to be directed to stderr.
894 * TODO: After gs 9.52, ghostscript uses a callout function for registering the
895 * callback functions. Implement that interface.
896 */
897 static int
gslib_bitmap(char * file,F_pic * pic,int llx,int lly,int urx,int ury)898 gslib_bitmap(char *file, F_pic *pic, int llx, int lly, int urx, int ury)
899 {
900 int code;
901 int w, h;
902 int format;
903 int rowstride;
904 const int failure = -1;
905 const int argc = 14;
906 char *arg[14];
907 /* the digits in a hexadezimal number are 2 * sizeof */
908 char arg6[20 + 2 * sizeof(uintptr_t)];
909 /* the number of digits in a decimal number are less
910 * than ((sizeof(int) / 2) * 3 + sizeof(int)) + 2,
911 * see https://stackoverflow.com/questions/43787672
912 */
913 #define DIGITS_IN_INT (((sizeof(int)/ 2) * 3 + sizeof(int)) + 2)
914 char arg7[17 + DIGITS_IN_INT];
915 char arg8[3 + DIGITS_IN_INT];
916 char arg9[4 + 2 * DIGITS_IN_INT];
917 char arg11[12 + 2 * DIGITS_IN_INT];
918 #undef DIGITS_IN_INT
919 void *minst = NULL; /* must be initialized to NULL */
920 char errbuf[BUFSIZ];
921 char outbuf[BUFSIZ];
922 struct calldata handle; /* passed to the callback functions */
923
924 struct _stdio_data stdio_data = {
925 sizeof errbuf, /* errsize */
926 (size_t) 0, /* errpos */
927 sizeof outbuf, /* outsize */
928 (size_t) 0, /* outpos */
929 outbuf,
930 errbuf
931 };
932
933 /* callback structure for "display" device */
934 struct display_callback_s callback_functions = {
935 sizeof(struct display_callback_s),
936 DISPLAY_VERSION_MAJOR,
937 DISPLAY_VERSION_MINOR,
938 display_open,
939 display_preclose,
940 display_close,
941 display_presize,
942 display_size,
943 display_sync,
944 display_page,
945 NULL, /* display_update */
946 display_memalloc,
947 display_memfree,
948 NULL /* display_separation */
949 };
950
951
952 /* This should be the size to which a pixmap is rendered for display
953 on the canvas, at a magnification of 2.
954 The +1 is sometimes correct, sometimes not */
955 w = (urx - llx) * BITMAP_PPI / 72 + 1;
956 h = (ury - lly) * BITMAP_PPI / 72 + 1;
957 handle.width = w;
958 handle.height = h;
959
960 /* Set the format flags for -dDisplayFormat=%d. */
961 /* DISPLAY_ROW_ALIGN_x must be equal or greater than
962 the size of a pointer */
963 format = DISPLAY_TOPFIRST | DISPLAY_ROW_ALIGN_8;
964 if (tool_cells <= 2 || appres.monochrome) {
965 format |= DISPLAY_COLORS_NATIVE | /* DISPLAY_ALPHA_NONE */
966 DISPLAY_DEPTH_1;
967
968 rowstride = (w + 7) / 8;
969 /* size = (size_t)((w + 7) / 8 * h); */
970 pic->pic_cache->numcols = 0;
971 } else {
972 format = DISPLAY_COLORS_RGB | DISPLAY_DEPTH_8 |
973 #ifdef WORDS_BIGENDIAN
974 DISPLAY_BIGENDIAN; /* RGB */
975 #else
976 DISPLAY_LITTLEENDIAN; /* BGR */
977 #endif
978 if (tool_vclass == TrueColor && image_bpp == 4) {
979 #ifdef WORDS_BIGENDIAN
980 format |= DISPLAY_UNUSED_FIRST; /* xRGB */
981 #else
982 format |= DISPLAY_UNUSED_LAST; /* BGRX */
983 #endif
984 rowstride = w * image_bpp;
985 /* size = (size_t)(w * h * image_bpp); */
986 pic->pic_cache->numcols = -1;
987 } else {
988 /* DISPLAY_ALPHA_NONE ... == 0, not necessary */
989 rowstride = w * 3;
990 /* size = (size_t)(w * h * 3); */
991 /* not necessary to set pic->pic_cache->numcols */
992 }
993 }
994
995 /*
996 * Options to ghostscript: It was not possible to use switches from the
997 * family of -dLastPage=1 or -sPageList=1, because these resulted in an
998 * all white pixmap. Therefore, interrupt the interpreter by having the
999 * display_page() callback return gs_error_InterpreterExit. For this
1000 * error code, ghostscript returns 0 and does not write messages to
1001 * stderr.
1002 */
1003 arg[0] = "libgs";
1004 arg[1] = "-q";
1005 arg[2] = "-dSAFER"; /* default for gs >= 9.50; for gs < 9.50,
1006 default is -dNOSAFER */
1007 arg[3] = "-dBATCH";
1008 arg[4] = "-dNOPAUSE";
1009 arg[5] = "-sDEVICE=display";
1010 arg[6] = "-sDisplayHandle=16#%" PRIxPTR;
1011 arg[7] = "-dDisplayFormat=%d";
1012 arg[8] = "-r%d"; /* resolution */
1013 arg[9] = "-g%dx%d"; /* width x height */
1014 arg[10] = "-c";
1015 arg[11] = "%d %d translate";
1016 arg[12] = "-f"; /* terminate list of tokens for the -c switch */
1017 arg[13] = file;
1018
1019 sprintf(arg6, arg[6], (uintptr_t)&handle);
1020 arg[6] = arg6;
1021 sprintf(arg7, arg[7], format);
1022 arg[7] = arg7;
1023 sprintf(arg8, arg[8], BITMAP_PPI);
1024 arg[8] = arg8;
1025 sprintf(arg9, arg[9], w, h);
1026 arg[9] = arg9;
1027 sprintf(arg11, arg[11], -llx, -lly);
1028 arg[11] = arg11;
1029
1030 if (appres.DEBUG) {
1031 int i;
1032 fputs("Using ghostscript library, arguments:\n ", stderr);
1033 for (i = 0; i < argc; ++i) {
1034 fputc(' ', stderr);
1035 fputs(arg[i], stderr);
1036 }
1037 fputc('\n', stderr);
1038 }
1039
1040 /* call ghostscript */
1041 code = gsapi_new_instance(&minst, (void *)&stdio_data);
1042 if (code == 0)
1043 code = gsapi_set_stdio(minst,NULL,bitmap_stdout,bitmap_stderr);
1044 else
1045 return gsexe_bitmap(file, pic, llx, lly, urx, ury);
1046
1047 if (code == 0)
1048 code = gsapi_set_display_callback(minst, &callback_functions);
1049 if (code == 0)
1050 code = gsapi_set_arg_encoding(minst, GS_ARG_ENCODING_UTF8);
1051 if (code == 0)
1052 code = gsapi_init_with_args(minst, argc, arg);
1053 if (code != 0 && code != gs_error_Quit)
1054 gsapi_exit(minst);
1055 else
1056 code = gsapi_exit(minst);
1057
1058 gsapi_delete_instance(minst);
1059
1060 if (stdio_data.outpos > (size_t)0)
1061 file_msg("Message from ghostscript when creating pixmap:\n%s",
1062 stdio_data.outbuf);
1063 if (stdio_data.errpos > (size_t)0)
1064 file_msg("Error message from ghostscript when creating pixmap:\n%s",
1065 stdio_data.errbuf);
1066
1067 if (code != 0 && code != gs_error_Quit) {
1068 free(handle.img);
1069 return GS_ERROR;
1070 }
1071 /* code == 0 || code == gs_error_Quit */
1072 if (rowstride > handle.raster) {
1073 if (appres.DEBUG)
1074 fputs("The pixmap rendered by ghostscript is larger "
1075 "than xfig expected.\n", stderr);
1076 free(handle.img);
1077 return failure;
1078 } else if (rowstride < handle.raster) {
1079 /* Move pixmap data to the alignment expected by xfig. */
1080 int i;
1081 unsigned char *src;
1082 unsigned char *dst;
1083
1084 for (i = 1; i < h; ++i) {
1085 src = handle.img + i * handle.raster;
1086 dst = handle.img + i * rowstride;
1087 for (src = handle.img + i * handle.raster;
1088 src < handle.img + i * handle.raster + rowstride;
1089 ) {
1090 *(dst++) = *(src++);
1091 }
1092 }
1093 handle.img = realloc(handle.img, (size_t)(dst - handle.img));
1094 }
1095
1096 pic->pic_cache->bit_size.x = w;
1097 pic->pic_cache->bit_size.y = h;
1098 pic->pic_cache->bitmap = handle.img;
1099
1100 if (tool_vclass != TrueColor && tool_cells > 2 && !appres.monochrome) {
1101 if (!map_to_palette(pic)) {
1102 file_msg("Cannot create colormapped image for %s.",
1103 file);
1104 return failure;
1105 }
1106 }
1107
1108 return 0;
1109 }
1110 #endif /* HAVE_GSLIB */
1111
1112 /*
1113 * Create a pixmap in pic->pic_cache->bitmap from the ps/eps/pdf file "file"
1114 * having the bounding box llx lly urx ury.
1115 * Return 0 on success, -1 on failure, or GS_ERROR for a ghostscript error.
1116 */
1117 int
gs_bitmap(char * file,F_pic * pic,int llx,int lly,int urx,int ury)1118 gs_bitmap(char *file, F_pic *pic, int llx, int lly, int urx, int ury)
1119 {
1120 int stat;
1121
1122 #ifdef HAVE_GSLIB
1123 stat = gslib_bitmap(file, pic, llx, lly, urx, ury);
1124 #else
1125 stat = gsexe_bitmap(file, pic, llx, lly, urx, ury);
1126 #endif
1127 if (stat == GS_ERROR) {
1128 file_msg("Could not create pixmap from '%s' with ghostscript.",
1129 file);
1130 }
1131 return stat;
1132 }
1133