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  * Some of the following code is extracted from giftopnm.c,
21  * from the netpbm package
22  */
23 
24 /* +-------------------------------------------------------------------+ */
25 /* | Copyright 1990, David Koblas.                                     | */
26 /* |   Permission to use, copy, modify, and distribute this software   | */
27 /* |   and its documentation for any purpose and without fee is hereby | */
28 /* |   granted, provided that the above copyright notice appear in all | */
29 /* |   copies and that both that copyright notice and this permission  | */
30 /* |   notice appear in supporting documentation.  This software is    | */
31 /* |   provided "as is" without express or implied warranty.           | */
32 /* +-------------------------------------------------------------------+ */
33 
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"		/* restrict */
36 #endif
37 
38 #include <X11/Intrinsic.h>	/* Boolean */
39 #include <X11/Xlib.h>		/* True, False */
40 #include <errno.h>
41 #include <stdio.h>
42 #include <stdlib.h>		/* mkstemp */
43 #include <string.h>		/* mkstemp */
44 #include <unistd.h>		/* close, unlink */
45 
46 #include "resources.h"		/* PATH_MAX */
47 #include "object.h"
48 #include "f_picobj.h"
49 #include "f_readpcx.h"
50 #include "w_msgpanel.h"
51 
52 
53 static Boolean	ReadColorMap(FILE *fd, unsigned int number, struct Cmap *cmap);
54 static Boolean	DoGIFextension(FILE *fd, int label);
55 static int	GetDataBlock(FILE *fd, unsigned char *buf);
56 
57 #define LOCALCOLORMAP		0x80
58 #define	ReadOK(file,buffer,len)	\
59 	    (fread((void *)buffer, (size_t)len, (size_t)1, (FILE *)file) != 0)
60 #define BitSet(byte, bit)	(((byte) & (bit)) == (bit))
61 
62 #define LM_to_uint(a,b)		(((b)<<8)|(a))
63 
64 struct {
65 	unsigned int	Width;
66 	unsigned int	Height;
67 	struct	 Cmap	ColorMap[MAX_COLORMAP_SIZE];
68 	unsigned int	BitPixel;
69 	unsigned int	ColorResolution;
70 	unsigned int	Background;
71 	unsigned int	AspectRatio;
72 } GifScreen;
73 
74 struct {
75 	int	transparent;
76 	int	delayTime;
77 	int	inputFlag;
78 	int	disposal;
79 } Gif89 = { -1, -1, -1, 0 };
80 
81 /* return codes:  PicSuccess (1) : success
82 		  FileInvalid (-2) : invalid file
83 */
84 
85 
86 
87 int
read_gif(F_pic * pic,struct xfig_stream * restrict pic_stream)88 read_gif(F_pic *pic, struct xfig_stream *restrict pic_stream)
89 {
90 	char		buf[BUFSIZ];
91 	const char 	pcxname_fmt[] = "%s/xfig-pcx.XXXXXX";
92 	char		pcxname_buf[128];
93 	char		*pcxname = pcxname_buf;
94 	char		*cmd_fmt;
95 	char		*cmd = buf;
96 	FILE		*giftopcx;
97 	struct Cmap	localColorMap[MAX_COLORMAP_SIZE];
98 	int		i, stat, fd;
99 	int		useGlobalColormap;
100 	unsigned int	bitPixel, red, green, blue;
101 	unsigned char	c;
102 	char		version[4];
103 	size_t		size;
104 	struct xfig_stream	pcx;
105 
106 	if (!rewind_stream(pic_stream))
107 		return FileInvalid;
108 
109 	/* command string to convert gif to pcx */
110 	if (!system("{ giftopnm -version && ppmtopcx -version; } 2>/dev/null"))
111 		cmd_fmt = "giftopnm -quiet | ppmtopcx -quiet >'%s'";
112 	else if (!system("convert -version >/dev/null"))
113 		cmd_fmt = "convert - pcx:'%s'";
114 	else if (!system("gm -version >/dev/null"))
115 		cmd_fmt = "gm convert - pcx:'%s'";
116 	else {
117 		file_msg("Cannot read gif files.");
118 		file_msg("To read gif files, install either the netpbm, or the "
119 				"imagemagick, or the graphicsmagick package.");
120 		return FileInvalid;
121 	}
122 
123 	/* first read header to look for any transparent color extension */
124 	if (!ReadOK(pic_stream->fp, buf, 6)) {
125 		return FileInvalid;
126 	}
127 
128 	if (strncmp((char*)buf, "GIF", 3) != 0) {
129 		return FileInvalid;
130 	}
131 
132 	strncpy(version, (char*)(buf + 3), 3);
133 	version[3] = '\0';
134 
135 	if ((strcmp(version, "87a") != 0) && (strcmp(version, "89a") != 0)) {
136 		file_msg("Unknown GIF version %s", version);
137 		return FileInvalid;
138 	}
139 
140 	if (!ReadOK(pic_stream->fp, buf, 7)) {
141 		return FileInvalid;	/* failed to read screen descriptor */
142 	}
143 
144 	GifScreen.Width           = LM_to_uint(buf[0],buf[1]);
145 	GifScreen.Height          = LM_to_uint(buf[2],buf[3]);
146 	GifScreen.BitPixel        = 2<<(buf[4]&0x07);
147 	GifScreen.ColorResolution = (((((int)buf[4])&0x70)>>3)+1);
148 	GifScreen.Background      = (unsigned int) buf[5];
149 	GifScreen.AspectRatio     = (unsigned int) buf[6];
150 
151 	if (BitSet(buf[4], LOCALCOLORMAP)) {	/* Global Colormap */
152 		if (!ReadColorMap(pic_stream->fp, GifScreen.BitPixel,
153 					GifScreen.ColorMap)) {
154 			return FileInvalid;  /* error reading global colormap */
155 		}
156 	}
157 
158 	if (GifScreen.AspectRatio != 0 && GifScreen.AspectRatio != 49) {
159 		if (appres.DEBUG)
160 			fprintf(stderr, "warning - non-square pixels\n");
161 	}
162 
163 	/* assume no transparent color for now */
164 	Gif89.transparent = TRANSP_NONE;
165 
166 	/* read the full header to get any transparency information */
167 	for (;;) {
168 		if (!ReadOK(pic_stream->fp, &c, 1)) {
169 			return FileInvalid;	/* EOF / read error on image data */
170 		}
171 
172 		if (c == ';') {			/* GIF terminator, finish up */
173 			return PicSuccess;	/* all done */
174 		}
175 
176 		if (c == '!') {			/* Extension */
177 			if (!ReadOK(pic_stream->fp, &c, 1))
178 				file_msg("GIF read error on extension function code");
179 			(void)DoGIFextension(pic_stream->fp, c);
180 			continue;
181 		}
182 
183 		if (c != ',') {			/* Not a valid start character*/
184 			continue;
185 		}
186 
187 		if (!ReadOK(pic_stream->fp, buf, 9)) {
188 			/* couldn't read left/top/width/height */
189 			return FileInvalid;
190 		}
191 
192 		useGlobalColormap = !BitSet(buf[8], LOCALCOLORMAP);
193 
194 		bitPixel = 1<<((buf[8]&0x07)+1);
195 
196 		if (!useGlobalColormap) {
197 			if (!ReadColorMap(pic_stream->fp, bitPixel,
198 						localColorMap)) {
199 				file_msg("error reading local GIF colormap" );
200 				return PicSuccess;
201 			}
202 		}
203 		break;			/* image starts here, header is done */
204 	}
205 
206 	/* save transparent indicator */
207 	pic->pic_cache->transp = Gif89.transparent;
208 
209 	/* now call giftopnm and ppmtopcx */
210 
211 	/* make name for temp output file */
212 	size = sizeof pcxname_fmt + strlen(TMPDIR) - 2;
213 	if (size > sizeof pcxname_buf && (pcxname = malloc(size)) == NULL) {
214 		file_msg("Out of memory.");
215 		return FileInvalid;
216 	}
217 	if (sprintf(pcxname, pcxname_fmt, TMPDIR) < 0) {
218 		i = errno;
219 		file_msg("Unable to write temporary file path to string");
220 		file_msg("Error: %s", strerror(i));
221 		if (pcxname != pcxname_buf)
222 			free(pcxname);
223 		return FileInvalid;
224 	}
225 	if ((fd = mkstemp(pcxname)) == -1) {
226 		file_msg("Cannot create temporary file\n");
227 		if (pcxname != pcxname_buf)
228 			free(pcxname);
229 		return FileInvalid;
230 	}
231 	close(fd);
232 
233 	/* make command to convert gif to pcx */
234 	size += strlen(cmd_fmt) - 2;	/* from above, size == sizeof pcxname */
235 	if (size > sizeof buf && (cmd = malloc(size)) == NULL) {
236 		file_msg("Out of memory.");
237 		unlink(pcxname);
238 		if (pcxname != pcxname_buf)
239 			free(pcxname);
240 		return FileInvalid;
241 	}
242 	if (sprintf(cmd, cmd_fmt, pcxname) < 0) {
243 		i = errno;
244 		file_msg("Cannot write command to convert gif to pcx.");
245 		file_msg("Error: %s", strerror(i));
246 		unlink(pcxname);
247 		if (pcxname != pcxname_buf)
248 			free(pcxname);
249 		return FileInvalid;
250 	}
251 
252 	giftopcx = popen(cmd, "w");
253 	if (giftopcx == NULL) {
254 		i = errno;
255 		file_msg("Cannot open pipe to convert gif to pcx\n");
256 		file_msg("Command: %s", cmd);
257 		file_msg("Error: %s", strerror(i));
258 		if (cmd != buf)
259 			free(cmd);
260 		unlink(pcxname);
261 		if (pcxname != pcxname_buf)
262 			free(pcxname);
263 		return FileInvalid;
264 	}
265 
266 	/* write the temporary pcx file */
267 	rewind_stream(pic_stream);
268 	while ((size = fread(buf, 1, sizeof buf, pic_stream->fp)) != 0)
269 		fwrite(buf, size, 1, giftopcx);
270 
271 	if (pclose(giftopcx)) {
272 		i = errno;
273 		file_msg("Cannot convert gif to pcx\n");
274 		file_msg("Command: %s", cmd);
275 		file_msg("Error: %s", strerror(i));
276 		if (cmd != buf)
277 			free(cmd);
278 		unlink(pcxname);
279 		if (pcxname != pcxname_buf)
280 			free(pcxname);
281 		return FileInvalid;
282 	}
283 
284 	if (cmd != buf)
285 		free(cmd);
286 
287 	/*
288 	 * Construct a rudimentary struct xfig_stream that can be passed to
289 	 * read_pcx(). Tell read_pcx() that the FILE pointer is positioned at
290 	 * the start (*name == '\0') and that it is a regular file
291 	 * (*uncompress == '\0').
292 	 * ATTENTION, requires knowledge of fields of struct xfig_stream.
293 	 */
294 	pcx.name_buf[0] = '\0';
295 	pcx.name = pcx.name_buf;
296 	pcx.uncompress = pcx.name_buf;
297 
298 	if ((pcx.fp = fopen(pcxname, "rb")) == NULL) {
299 		file_msg("Cannot open temporary output file\n");
300 		perror("Error");
301 		unlink(pcxname);
302 		if (pcxname != pcxname_buf)
303 			free(pcxname);
304 		return FileInvalid;
305 	}
306 
307 	/* now call read_pcx to read the pcx file */
308 	stat = read_pcx(pic, &pcx);
309 
310 	/* remove temp file */
311 	fclose(pcx.fp);
312 	unlink(pcxname);
313 	if (pcxname != pcxname_buf)
314 		free(pcxname);
315 
316 	pic->pic_cache->subtype = T_PIC_GIF;
317 	/* now match original transparent colortable index with possibly new
318 	   colortable from ppmtopcx */
319 	if (pic->pic_cache->transp != TRANSP_NONE) {
320 	    if (useGlobalColormap) {
321 		red = GifScreen.ColorMap[pic->pic_cache->transp].red;
322 		green = GifScreen.ColorMap[pic->pic_cache->transp].green;
323 		blue = GifScreen.ColorMap[pic->pic_cache->transp].blue;
324 	    } else {
325 		red = localColorMap[pic->pic_cache->transp].red;
326 		green = localColorMap[pic->pic_cache->transp].green;
327 		blue = localColorMap[pic->pic_cache->transp].blue;
328 	    }
329 	    for (i = 0; i < pic->pic_cache->numcols; ++i) {
330 		if (pic->pic_cache->cmap[i].red == red &&
331 		    pic->pic_cache->cmap[i].green == green &&
332 		    pic->pic_cache->cmap[i].blue == blue)
333 			break;
334 	    }
335 	    if (i < pic->pic_cache->numcols)
336 		pic->pic_cache->transp = i;
337 	}
338 
339 	return stat;
340 }
341 
342 static Boolean
ReadColorMap(FILE * fd,unsigned int number,struct Cmap * cmap)343 ReadColorMap(FILE *fd, unsigned int number, struct Cmap *cmap)
344 {
345 	unsigned int	i;
346 	unsigned char	rgb[3];
347 
348 	for (i = 0; i < number; ++i) {
349 	    if (! ReadOK(fd, rgb, sizeof(rgb))) {
350 		file_msg("bad GIF colormap" );
351 		return False;
352 	    }
353 	    cmap[i].red   = rgb[0];
354 	    cmap[i].green = rgb[1];
355 	    cmap[i].blue  = rgb[2];
356 	}
357 	return True;
358 }
359 
360 static Boolean
DoGIFextension(FILE * fd,int label)361 DoGIFextension(FILE *fd, int label)
362 {
363 	static unsigned char buf[256];
364 	char	    *str;
365 
366 	switch (label) {
367 	case 0x01:		/* Plain Text Extension */
368 		str = "Plain Text Extension";
369 		break;
370 	case 0xff:		/* Application Extension */
371 		str = "Application Extension";
372 		break;
373 	case 0xfe:		/* Comment Extension */
374 		str = "Comment Extension";
375 		while (GetDataBlock(fd, buf) != 0) {
376 			; /* GIF comment */
377 		}
378 		return False;
379 	case 0xf9:		/* Graphic Control Extension */
380 		str = "Graphic Control Extension";
381 		(void) GetDataBlock(fd, (unsigned char*) buf);
382 		Gif89.disposal    = (buf[0] >> 2) & 0x7;
383 		Gif89.inputFlag   = (buf[0] >> 1) & 0x1;
384 		Gif89.delayTime   = LM_to_uint(buf[1],buf[2]);
385 		if ((buf[0] & 0x1) != 0)
386 			Gif89.transparent = buf[3];
387 
388 		while (GetDataBlock(fd, buf) != 0)
389 			;
390 		return False;
391 	default:
392 		str = (char *) buf;
393 		sprintf(str, "UNKNOWN (0x%02x)", label);
394 		break;
395 	}
396 
397 	if (appres.DEBUG)
398 		fprintf(stderr,"got a '%s' extension\n", str );
399 
400 	while (GetDataBlock(fd, buf) != 0)
401 		;
402 
403 	return False;
404 }
405 
406 int	ZeroDataBlock = False;
407 
408 static int
GetDataBlock(FILE * fd,unsigned char * buf)409 GetDataBlock(FILE *fd, unsigned char *buf)
410 {
411 	unsigned char	count;
412 
413 	/* error in getting DataBlock size */
414 	if (! ReadOK(fd,&count,1)) {
415 		return -1;
416 	}
417 
418 	ZeroDataBlock = count == 0;
419 
420 	/* error in reading DataBlock */
421 	if ((count != 0) && (! ReadOK(fd, buf, count))) {
422 		return -1;
423 	}
424 
425 	return count;
426 }
427