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 * Copyright (c) 1992 by Brian Boyter
9 *
10 * Any party obtaining a copy of these files is granted, free of charge, a
11 * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
12 * nonexclusive right and license to deal in this software and documentation
13 * files (the "Software"), including without limitation the rights to use,
14 * copy, modify, merge, publish, distribute, sublicense and/or sell copies of
15 * the Software, and to permit persons who receive copies from any such
16 * party to do so, with the only requirement being that the above copyright
17 * and this permission notice remain intact.
18 *
19 */
20
21 /* GS bitmap generation added: 13 Nov 1992, by Michael C. Grant
22 * (mcgrant@rascals.stanford.edu) adapted from Marc Goldburg's
23 * (marcg@rascals.stanford.edu) original idea and code. */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 #include "f_picobj.h"
29
30 #include <errno.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <sys/stat.h>
37 #include <sys/types.h> /* time_t */
38 #include <X11/Intrinsic.h> /* includes X11/Xlib.h, which includes X11/X.h */
39
40 #include "resources.h" /* TMPDIR */
41 #include "object.h"
42 #include "f_readpcx.h" /* read_pcx() */
43 #include "f_util.h" /* file_timestamp() */
44 #include "u_create.h" /* create_picture_entry() */
45 #include "w_file.h" /* check_cancel() */
46 #include "w_msgpanel.h"
47 #include "w_setup.h" /* PIX_PER_INCH, PIX_PER_CM */
48 #include "w_util.h" /* app_flush() */
49
50 extern int read_gif(F_pic *pic, struct xfig_stream *restrict pic_stream);
51 extern int read_eps(F_pic *pic, struct xfig_stream *restrict pic_stream);
52 extern int read_pdf(F_pic *pic, struct xfig_stream *restrict pic_stream);
53 extern int read_ppm(F_pic *pic, struct xfig_stream *restrict pic_stream);
54 #ifdef HAVE_TIFF
55 extern int read_tif(F_pic *pic, struct xfig_stream *restrict pic_stream);
56 #endif
57 extern int read_xbm(F_pic *pic, struct xfig_stream *restrict pic_stream);
58 #ifdef HAVE_JPEG
59 extern int read_jpg(F_pic *pic, struct xfig_stream *restrict pic_stream);
60 #endif
61 #ifdef HAVE_PNG
62 extern int read_png(F_pic *pic, struct xfig_stream *restrict pic_stream);
63 #endif
64 #ifdef USE_XPM
65 extern int read_xpm(F_pic *pic, struct xfig_stream *restrict pic_stream);
66 #endif
67
68
69 static struct _haeders {
70 char *type;
71 char *bytes;
72 int (*readfunc)();
73 } headers[] = {
74 {"GIF", "GIF", read_gif},
75 {"PCX", "\012\005\001", read_pcx},
76 {"EPS", "%!", read_eps},
77 {"PDF", "%PDF", read_pdf},
78 {"PPM", "P3", read_ppm},
79 {"PPM", "P6", read_ppm},
80 #ifdef HAVE_TIFF
81 {"TIFF", "II*\000", read_tif},
82 {"TIFF", "MM\000*", read_tif},
83 #endif
84 {"XBM", "#define", read_xbm},
85 #ifdef HAVE_JPEG
86 {"JPEG", "\377\330\377\340", read_jpg},
87 {"JPEG", "\377\330\377\341", read_jpg},
88 #endif
89 #ifdef HAVE_PNG
90 {"PNG", "\211\120\116\107\015\012\032\012", read_png},
91 #endif
92 #ifdef USE_XPM
93 {"XPM", "/* XPM */", read_xpm},
94 #endif
95 };
96
97
98 static void
init_stream(struct xfig_stream * restrict xf_stream)99 init_stream(struct xfig_stream *restrict xf_stream)
100 {
101 xf_stream->fp = NULL;
102 xf_stream->name = xf_stream->name_buf;
103 xf_stream->name_on_disk = xf_stream->name_on_disk_buf;
104 xf_stream->uncompress = NULL;
105 xf_stream->content = xf_stream->content_buf;
106 *xf_stream->content = '\0';
107 }
108
109 void
free_stream(struct xfig_stream * restrict xf_stream)110 free_stream(struct xfig_stream *restrict xf_stream)
111 {
112 if (xf_stream->content != xf_stream->name_on_disk) {
113 if (*xf_stream->content && unlink(xf_stream->content)) {
114 int err = errno;
115 file_msg("Cannot remove temporary file %s\nError: %s",
116 xf_stream->content, strerror(err));
117 }
118 if (xf_stream->content != xf_stream->content_buf)
119 free(xf_stream->content);
120 }
121 if (xf_stream->name != xf_stream->name_buf)
122 free(xf_stream->name);
123 if (xf_stream->name_on_disk != xf_stream->name_on_disk_buf)
124 free(xf_stream->name_on_disk);
125 }
126
127 /*
128 * Given name, search and return the name of the corresponding file on disk in
129 * found. This may be "name", or "name" with a compression suffix appended,
130 * e.g., "name.gz". If the file must be uncompressed, return the compression
131 * command in a static string pointed to by "uncompress", otherwise let
132 * "uncompress" point to the empty string. If the size of the character buffer
133 * provided in found is too small to accomodate the string, return a pointer to
134 * a malloc()'ed string.
135 * Return 0 on success, or FileInvalid if the file is not found.
136 * Usage:
137 * char found_buf[len];
138 * char *found = found_buf;
139 * file_on_disk(name, &found, sizeof found_buf, &uncompress);
140 * ...
141 * if (found != found_buf)
142 * free(found);
143 */
144 static int
file_on_disk(char * restrict name,char * restrict * found,size_t len,const char * restrict * uncompress)145 file_on_disk(char *restrict name, char *restrict *found, size_t len,
146 const char *restrict *uncompress)
147 {
148 int i;
149 size_t name_len;
150 char *suffix;
151 struct stat status;
152 static const char empty[] = "";
153 static const char *filetypes[][2] = {
154 /* sorted by popularity? */
155 #define FILEONDISK_ADD 5 /* must be max(strlen(filetypes[0][])) + 1 */
156 { ".gz", "gunzip -c" },
157 { ".Z", "gunzip -c" },
158 { ".z", "gunzip -c" },
159 { ".zip", "unzip -p" },
160 { ".bz2", "bunzip2 -c" },
161 { ".bz", "bunzip2 -c" },
162 { ".xz", "unxz -c" }
163 };
164 const int filetypes_len =
165 (int)(sizeof filetypes / sizeof(filetypes[1]));
166
167 name_len = strlen(name);
168 if (name_len >= len &&
169 (*found = malloc(name_len + FILEONDISK_ADD)) == NULL) {
170 file_msg("Out of memory.");
171 return FileInvalid;
172 }
173
174 strcpy(*found, name);
175
176 /*
177 * Possibilities, e.g.,
178 * name name on disk uncompress
179 * img.ppm img.ppm ""
180 * img.ppm img.ppm.gz gunzip -c
181 * img.ppm.gz img.ppm.gz gunzip -c
182 * img.ppm.gz img.ppm ""
183 */
184 if (stat(name, &status)) {
185 /* File not found. Now try, whether a file with one of
186 the known suffices appended exists. */
187 if (len < name_len + FILEONDISK_ADD && (*found =
188 malloc(name_len + FILEONDISK_ADD)) == NULL) {
189 file_msg("Out of memory.");
190 return FileInvalid;
191 }
192 suffix = *found + name_len;
193 for (i = 0; i < filetypes_len; ++i) {
194 strcpy(suffix, filetypes[i][0]);
195 if (!stat(*found, &status)) {
196 *uncompress = filetypes[i][1];
197 break;
198 }
199 }
200
201 /* Not found. Check, whether the file has one of the known
202 compression suffices, but the uncompressed file
203 exists on disk. */
204 *suffix = '\0';
205 if (i == filetypes_len && (suffix = strrchr(name, '.'))) {
206 for (i = 0; i < filetypes_len; ++i) {
207 if (!strcmp(suffix, filetypes[i][0])) {
208 *(*found + (suffix - name)) = '\0';
209 if (!stat(*found, &status)) {
210 *uncompress = empty;
211 break;
212 } else {
213 *found[0] = '\0';
214 i = filetypes_len;
215 }
216 }
217 }
218 }
219
220 if (i == filetypes_len) {
221 /* not found */
222 *found[0] = '\0';
223 return FileInvalid;
224 }
225 } else {
226 /* File exists. Check, whether the name has one of the known
227 compression suffices. */
228 if ((suffix = strrchr(name, '.'))) {
229 for (i = 0; i < filetypes_len; ++i) {
230 if (!strcmp(suffix, filetypes[i][0])) {
231 *uncompress = filetypes[i][1];
232 break;
233 }
234 }
235 } else {
236 i = filetypes_len;
237 }
238
239 if (i == filetypes_len)
240 *uncompress = empty;
241 }
242
243 return 0;
244 }
245
246 /*
247 * Compare the picture information in pic with "file". If the file on disk
248 * is newer than the picture information, set "reread" to true.
249 * If a cached picture bitmap already exists, set "existing" to true.
250 * Return FileInvalid, if "file" is not found, otherwise return 0.
251 */
252 static int
get_picture_status(F_pic * pic,struct _pics * pics,char * file,bool force,bool * reread,bool * existing)253 get_picture_status(F_pic *pic, struct _pics *pics, char *file, bool force,
254 bool *reread, bool *existing)
255 {
256 char found_buf[256];
257 char *found = found_buf;
258 const char *uncompress;
259 time_t mtime;
260
261 /* get the name of the file on disk */
262 if (file_on_disk(pics->file, &found, sizeof found_buf, &uncompress)) {
263 if (found != found_buf)
264 free(found);
265 return FileInvalid;
266 }
267
268 /* check the timestamp */
269 mtime = file_timestamp(found);
270 if (found != found_buf)
271 free(found);
272 if (mtime < 0) {
273 /* oops, doesn't exist? */
274 file_msg("Error %s on %s", strerror(errno), file);
275 return FileInvalid;
276 }
277 if (force || mtime > pics->time_stamp) {
278 if (appres.DEBUG && mtime > pics->time_stamp)
279 fprintf(stderr, "Timestamp changed, reread file %s\n",
280 file);
281 *reread = true;
282 return 0;
283 }
284
285 pic->pic_cache = pics;
286 pics->refcount++;
287
288 if (appres.DEBUG)
289 fprintf(stderr, "Found stored picture %s, count=%d\n", file,
290 pics->refcount);
291
292 /* there is a cached bitmap */
293 if (pics->bitmap != NULL) {
294 *existing = true;
295 *reread = false;
296 put_msg("Reading Picture object file...found cached picture");
297 } else {
298 *existing = false;
299 *reread = true;
300 if (appres.DEBUG)
301 fprintf(stderr, "Re-reading file %s\n", file);
302 }
303 return 0;
304 }
305
306 /*
307 * Check through the pictures repository to see if "file" is already there.
308 * If so, set the pic->pic_cache pointer to that repository entry and set
309 * "existing" to True.
310 * If not, read the file via the relevant reader and add to the repository
311 * and set "existing" to False.
312 * If "force" is true, read the file unconditionally.
313 */
314 void
read_picobj(F_pic * pic,char * file,int color,Boolean force,Boolean * existing)315 read_picobj(F_pic *pic, char *file, int color, Boolean force, Boolean *existing)
316 {
317 FILE *fp;
318 int i;
319 char buf[16];
320 bool reread;
321 struct _pics *pics, *lastpic;
322 struct xfig_stream pic_stream;
323
324 pic->color = color;
325 /* don't touch the flipped flag - caller has already set it */
326 pic->pixmap = (Pixmap)0;
327 pic->hw_ratio = 0.0;
328 pic->pix_rotation = 0;
329 pic->pix_width = 0;
330 pic->pix_height = 0;
331 pic->pix_flipped = 0;
332
333 /* check if user pressed cancel button */
334 if (check_cancel())
335 return;
336
337 put_msg("Reading Picture object file...");
338 app_flush();
339
340 /* look in the repository for this filename */
341 lastpic = pictures;
342 for (pics = pictures; pics; pics = pics->next) {
343 if (strcmp(pics->file, file) == 0) {
344 /* check, whether picture exists, or must be re-read */
345 if (get_picture_status(pic, pics, file, force, &reread,
346 (bool *)existing) ==FileInvalid)
347 return;
348 if (!reread && *existing) {
349 /* must set the h/w ratio here */
350 pic->hw_ratio =(float)pic->pic_cache->bit_size.y
351 / pic->pic_cache->bit_size.x;
352 return;
353 }
354 break;
355 }
356 /* keep pointer to last entry */
357 lastpic = pics;
358 }
359
360 if (pics == NULL) {
361 /* didn't find it in the repository, add it */
362 pics = create_picture_entry();
363 if (lastpic) {
364 /* add to list */
365 lastpic->next = pics;
366 pics->prev = lastpic;
367 } else {
368 /* first one */
369 pictures = pics;
370 }
371 pics->file = strdup(file);
372 pics->refcount = 1;
373 pics->bitmap = NULL;
374 pics->subtype = T_PIC_NONE;
375 pics->numcols = 0;
376 pics->size_x = 0;
377 pics->size_y = 0;
378 pics->bit_size.x = 0;
379 pics->bit_size.y = 0;
380 if (appres.DEBUG)
381 fprintf(stderr, "New picture %s\n", file);
382 }
383 /* put it in the pic */
384 pic->pic_cache = pics;
385 pic->pixmap = (Pixmap)0;
386
387 if (appres.DEBUG)
388 fprintf(stderr, "Reading file %s\n", file);
389
390 init_stream(&pic_stream);
391
392 /* open the file and read a few bytes of the header to see what it is */
393 if ((fp = open_stream(file, &pic_stream)) == NULL) {
394 file_msg("No such picture file: %s", file);
395 free_stream(&pic_stream);
396 return;
397 }
398 /* get the modified time and save it */
399 pics->time_stamp = file_timestamp(pic_stream.name_on_disk);
400
401 /* read some bytes from the file */
402 for (i = 0; i < (int)sizeof buf; ++i) {
403 int c;
404 if ((c = getc(fp)) == EOF)
405 break;
406 buf[i] = (char)c;
407 }
408
409 /* now find which header it is */
410 for (i = 0; i < (int)(sizeof headers / sizeof(headers[0])); ++i)
411 if (!memcmp(buf, headers[i].bytes, strlen(headers[i].bytes)))
412 break;
413
414 /* not found */
415 if (i == (int)(sizeof headers / sizeof(headers[0]))) {
416 file_msg("%s: Unknown image format", file);
417 put_msg("Reading Picture object file...Failed");
418 app_flush();
419 close_stream(&pic_stream);
420 free_stream(&pic_stream);
421 return;
422 }
423
424 /* readfunc() expect an open file stream, positioned not at the
425 start of the stream. The stream remains open after returning. */
426 if (headers[i].readfunc(pic, &pic_stream) != PicSuccess) {
427 file_msg("%s: Bad %s format", file, headers[i].type);
428 } else {
429 put_msg("Reading Picture object file...Done");
430 }
431
432 close_stream(&pic_stream);
433 free_stream(&pic_stream);
434 }
435
436 /*
437 * Return a file stream, either to a pipe or to a regular file.
438 * If xf_stream->uncompress[0] == '\0', it is a regular file, otherwise a pipe.
439 */
440 FILE *
open_stream(char * restrict name,struct xfig_stream * restrict xf_stream)441 open_stream(char *restrict name, struct xfig_stream *restrict xf_stream)
442 {
443 size_t len;
444
445 if (xf_stream->name != name) {
446 /* strcpy (xf_stream->name, name) */
447 len = strlen(name);
448 if (len >= sizeof xf_stream->name_buf) {
449 if ((xf_stream->name = malloc(len + 1)) == NULL) {
450 file_msg("Out of memory.");
451 return NULL;
452 }
453 }
454 memcpy(xf_stream->name, name, len + 1);
455 }
456
457 if (file_on_disk(name, &xf_stream->name_on_disk,
458 sizeof xf_stream->name_on_disk_buf,
459 &xf_stream->uncompress)) {
460
461 free_stream(xf_stream);
462 return NULL;
463 }
464
465 if (*xf_stream->uncompress) {
466 /* a compressed file */
467 char command_buf[256];
468 char *command = command_buf;
469
470 len = strlen(xf_stream->name_on_disk) +
471 strlen(xf_stream->uncompress) + 2;
472 if (len > sizeof command_buf) {
473 if ((command = malloc(len)) == NULL) {
474 file_msg("Out of memory.");
475 return NULL;
476 }
477 }
478 sprintf(command, "%s '%s'",
479 xf_stream->uncompress, xf_stream->name_on_disk);
480 xf_stream->fp = popen(command, "r");
481 if (command != command_buf)
482 free(command);
483 } else {
484 /* uncompressed file */
485 xf_stream->fp = fopen(xf_stream->name_on_disk, "rb");
486 }
487
488 return xf_stream->fp;
489 }
490
491 /*
492 * Close a file stream opened by open_file(). Do not free xf_stream.
493 */
494 int
close_stream(struct xfig_stream * restrict xf_stream)495 close_stream(struct xfig_stream *restrict xf_stream)
496 {
497 if (xf_stream->fp == NULL)
498 return -1;
499
500 if (xf_stream->uncompress[0] == '\0') {
501 /* a regular file */
502 return fclose(xf_stream->fp);
503 } else {
504 /* a pipe */
505 char trash[BUFSIZ];
506 /* for a pipe, must read everything or
507 we'll get a broken pipe message */
508 while (fread(trash, (size_t)1, (size_t)BUFSIZ, xf_stream->fp) ==
509 (size_t)BUFSIZ)
510 ;
511 return pclose(xf_stream->fp);
512 }
513 }
514
515 FILE *
rewind_stream(struct xfig_stream * restrict xf_stream)516 rewind_stream(struct xfig_stream *restrict xf_stream)
517 {
518 if (xf_stream->fp == NULL)
519 return NULL;
520
521 if (xf_stream->uncompress[0] == '\0') {
522 /* a regular file */
523 rewind(xf_stream->fp);
524 return xf_stream->fp;
525 } else {
526 /* a pipe */
527 (void)close_stream(xf_stream);
528 /* if, in the meantime, e.g., the file on disk
529 was uncompressed, change the filetype. */
530 return open_stream(xf_stream->name, xf_stream);
531 }
532 }
533
534 /*
535 * Have xf_stream->content either point to a regular file containing the
536 * uncompressed content of xf_stream->name, or to xf_stream->name_on_disk, if
537 * name_on_disk is not compressed.
538 * Use after a call to open_stream():
539 * struct xfig_stream xf_stream;
540 * open_stream(name, &xf_stream);
541 * close_stream(&xf_stream);
542 * uncompressed_content(&xf_stream)
543 * do_something(xf_stream->content);
544 * free_stream(&xf_stream);
545 */
546 int
uncompressed_content(struct xfig_stream * restrict xf_stream)547 uncompressed_content(struct xfig_stream *restrict xf_stream)
548 {
549 int ret = -1;
550 int fd;
551 int len;
552 char command_buf[256];
553 char *command = command_buf;
554 char *const command_fmt = "%s '%s' 1>&%d";
555 char *const content_fmt = "%s/xfigXXXXXX";
556
557 if (*xf_stream->uncompress == '\0') {
558 xf_stream->content = xf_stream->name_on_disk;
559 return 0;
560 }
561
562 /* uncompress to a temporary file */
563
564 len = snprintf(xf_stream->content, sizeof xf_stream->content_buf,
565 content_fmt, TMPDIR);
566 if (len >= (int)(sizeof xf_stream->content_buf)) {
567 if ((xf_stream->content = malloc(len + 1)) == NULL) {
568 file_msg("Out of memory.");
569 return ret;
570 }
571 len = sprintf(xf_stream->content, content_fmt, TMPDIR);
572 }
573 if (len < 0) {
574 len = errno;
575 file_msg("Unable to write temporary file path to string.");
576 file_msg("Error: %s", strerror(len));
577 return ret;
578 }
579
580 if ((fd = mkstemp(xf_stream->content)) == -1) {
581 fd = errno;
582 file_msg("Could not open temporary file %s, error: %s",
583 xf_stream->content, strerror(fd));
584 return ret;
585 }
586
587 /*
588 * One could already here redirect stdout to the fd of our tmp
589 * file - but then, how to re-open stdout?
590 * close(1);
591 * dup(fd); * takes the lowest integer found, now 1 *
592 * close(fd);
593 */
594 len = snprintf(command, sizeof command_buf, command_fmt,
595 xf_stream->uncompress, xf_stream->name_on_disk, fd);
596 if (len >= (int)(sizeof command_buf)) {
597 if ((command = malloc(len + 1)) == NULL) {
598 file_msg("Out of memory.");
599 close(fd);
600 return ret;
601 }
602 len = sprintf(command, command_fmt, xf_stream->uncompress,
603 xf_stream->name_on_disk, fd);
604 }
605 if (len < 0) {
606 len = errno;
607 file_msg("Unable to write command string.");
608 file_msg("Error: %s", strerror(len));
609 return ret;
610 }
611
612 if (system(command) == 0)
613 ret = 0;
614 else
615 file_msg("Could not uncompress %s, command: %s",
616 xf_stream->name_on_disk, command);
617
618 close(fd);
619 if (command != command_buf)
620 free(command);
621 return ret;
622 }
623
624 /*
625 * Compute the image dimension (size_x, size_y) in Fig-units from the number of
626 * pixels and the resolution in x- and y-direction, pixels_x, pixels_y, and
627 * res_x and res_y, respectively.
628 * The resolution is given in pixels / cm or pixels / inch, depending on
629 * whether 'c' or 'i' is passed to unit. For any other character passed to unit,
630 * a resolution of 72 pixels per inch is assumed.
631 */
632 void
image_size(int * size_x,int * size_y,int pixels_x,int pixels_y,char unit,float res_x,float res_y)633 image_size(int *size_x, int *size_y, int pixels_x, int pixels_y,
634 char unit, float res_x, float res_y)
635 {
636 /* exclude negative and absurdly small values */
637 if (res_x < 1. || res_y < 1.) {
638 res_x = 72.0f;
639 res_y = 72.0f;
640 unit = 'i';
641 }
642
643 if (unit == 'i') { /* pixel / inch */
644 if (appres.INCHES) {
645 *size_x = pixels_x * PIX_PER_INCH / res_x + 0.5;
646 *size_y = pixels_y * PIX_PER_INCH / res_y + 0.5;
647 } else {
648 *size_x = pixels_x * PIX_PER_CM * 2.54 / res_x + 0.5;
649 *size_y = pixels_y * PIX_PER_CM * 2.54 / res_y + 0.5;
650 }
651 } else if (unit == 'c') { /* pixel / cm */
652 if (appres.INCHES) {
653 *size_x = pixels_x * PIX_PER_INCH / (res_x * 2.54) +0.5;
654 *size_y = pixels_y * PIX_PER_INCH / (res_y * 2.54) +0.5;
655 } else {
656 *size_x = pixels_x * PIX_PER_CM / res_x + 0.5;
657 *size_y = pixels_y * PIX_PER_CM / res_y + 0.5;
658 }
659 } else { /* unknown, default to 72 ppi */
660 *size_x = pixels_x * PIC_FACTOR + 0.5;
661 *size_y = pixels_y * PIC_FACTOR * res_x / res_y + 0.5;
662 }
663 }
664