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