file.c revision 6b7436ae
1/*
2 * Copyright © 2024 Thomas E. Dickey
3 * Copyright © 2002 Keith Packard
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation, and that the name of Keith Packard not be used in
10 * advertising or publicity pertaining to distribution of the software without
11 * specific, written prior permission.  Keith Packard makes no
12 * representations about the suitability of this software for any purpose.  It
13 * is provided "as is" without express or implied warranty.
14 *
15 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21 * PERFORMANCE OF THIS SOFTWARE.
22 */
23
24#include "xcursorint.h"
25#include <stdlib.h>
26#include <string.h>
27
28#ifdef DEBUG_XCURSOR
29#include <stdarg.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32void _XcursorTrace(const char *fmt, ...)
33{
34    FILE *fp = fopen("/tmp/xcursor.log", "a");
35    if (fp != NULL) {
36	unsigned save = umask(0);
37	va_list ap;
38	va_start(ap, fmt);
39	vfprintf(fp, fmt, ap);
40	va_end(ap);
41	fclose(fp);
42	umask(save);
43    }
44}
45
46void *_XcursorReturnAddr(void *addr)
47{
48    _XcursorTrace(T_RETURN(p), addr);
49    return addr;
50}
51
52int _XcursorReturnCode(int code)
53{
54    _XcursorTrace(T_RETURN(d), code);
55    return code;
56}
57
58unsigned long _XcursorReturnLong(unsigned long code)
59{
60    _XcursorTrace(T_RETURN(lu), code);
61    return code;
62}
63
64unsigned _XcursorReturnUint(unsigned code)
65{
66    _XcursorTrace(T_RETURN(u), code);
67    return code;
68}
69
70void _XcursorReturnVoid(void)
71{
72    _XcursorTrace(T_RETURN(s), "");
73    return;
74}
75#endif /* DEBUG_XCURSOR */
76
77XcursorImage *
78XcursorImageCreate (int width, int height)
79{
80    XcursorImage    *image = NULL;
81
82    enterFunc((T_CALLED(XcursorImageCreate) "(%d, %d)\n", width, height));
83
84    if (width < 0 || height < 0) {
85       /* EMPTY */;
86    } else if (width > XCURSOR_IMAGE_MAX_SIZE
87    	    || height > XCURSOR_IMAGE_MAX_SIZE) {
88       /* EMPTY */;
89    } else {
90	image = malloc (sizeof (XcursorImage) +
91			(size_t) (width * height) * sizeof (XcursorPixel));
92	if (image) {
93	    image->version = XCURSOR_IMAGE_VERSION;
94	    image->pixels  = (XcursorPixel *) (image + 1);
95	    image->size	   = (XcursorDim) (width > height ? width : height);
96	    image->width   = (XcursorDim) width;
97	    image->height  = (XcursorDim) height;
98	    image->delay   = 0;
99	}
100    }
101    returnAddr(image);
102}
103
104void
105XcursorImageDestroy (XcursorImage *image)
106{
107    enterFunc((T_CALLED(XcursorImageDestroy ) "(%p)\n", (void*)image));
108
109    free (image);
110
111    returnVoid();
112}
113
114XcursorImages *
115XcursorImagesCreate (int size)
116{
117    XcursorImages   *images;
118
119    enterFunc((T_CALLED(XcursorImagesCreate) "(%d)\n", size));
120
121    images = malloc (sizeof (XcursorImages) +
122		     (size_t) size * sizeof (XcursorImage *));
123    if (images) {
124	images->nimage = 0;
125	images->images = (XcursorImage **) (images + 1);
126	images->name = NULL;
127    }
128    returnAddr(images);
129}
130
131void
132XcursorImagesDestroy (XcursorImages *images)
133{
134    enterFunc((T_CALLED(XcursorImagesDestroy) "(%p)\n", (void*)images));
135
136    if (images) {
137	int	n;
138
139	for (n = 0; n < images->nimage; n++)
140	    XcursorImageDestroy (images->images[n]);
141	if (images->name)
142	    free (images->name);
143	free (images);
144    }
145
146    returnVoid();
147}
148
149void
150XcursorImagesSetName (XcursorImages *images, const char *name)
151{
152    enterFunc((T_CALLED(XcursorImagesSetName) "(%p, \"%s\")\n",
153	      (void*)images,
154	      NonNull(name)));
155
156    if (images && name) {
157	char *new = strdup (name);
158
159	if (new) {
160	    if (images->name)
161		free (images->name);
162	    images->name = new;
163	}
164    }
165
166    returnVoid();
167}
168
169XcursorComment *
170XcursorCommentCreate (XcursorUInt comment_type, int length)
171{
172    XcursorComment  *comment;
173
174    if (length < 0 || length > XCURSOR_COMMENT_MAX_LEN)
175	return NULL;
176
177    comment = malloc (sizeof (XcursorComment) + (size_t) length + 1);
178    if (!comment)
179	return NULL;
180    comment->version = XCURSOR_COMMENT_VERSION;
181    comment->comment_type = comment_type;
182    comment->comment = (char *) (comment + 1);
183    comment->comment[0] = '\0';
184    return comment;
185}
186
187void
188XcursorCommentDestroy (XcursorComment *comment)
189{
190    free (comment);
191}
192
193XcursorComments *
194XcursorCommentsCreate (int size)
195{
196    XcursorComments *comments;
197
198    comments = malloc (sizeof (XcursorComments) +
199		       (size_t) size * sizeof (XcursorComment *));
200    if (!comments)
201	return NULL;
202    comments->ncomment = 0;
203    comments->comments = (XcursorComment **) (comments + 1);
204    return comments;
205}
206
207void
208XcursorCommentsDestroy (XcursorComments *comments)
209{
210    int	n;
211
212    if (!comments)
213	return;
214
215    for (n = 0; n < comments->ncomment; n++)
216	XcursorCommentDestroy (comments->comments[n]);
217    free (comments);
218}
219
220static XcursorBool
221_XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
222{
223    unsigned char   bytes[4];
224
225    if (!file || !u)
226	return XcursorFalse;
227
228    if ((*file->read) (file, bytes, 4) != 4)
229	return XcursorFalse;
230
231    *u = ((XcursorUInt)(bytes[0]) << 0) |
232	 ((XcursorUInt)(bytes[1]) << 8) |
233         ((XcursorUInt)(bytes[2]) << 16) |
234         ((XcursorUInt)(bytes[3]) << 24);
235    return XcursorTrue;
236}
237
238static XcursorBool
239_XcursorReadBytes (XcursorFile *file, char *bytes, int length)
240{
241    if (!file || !bytes || (*file->read) (file, (unsigned char *) bytes, length) != length)
242	return XcursorFalse;
243    return XcursorTrue;
244}
245
246static XcursorBool
247_XcursorWriteUInt (XcursorFile *file, XcursorUInt u)
248{
249    unsigned char   bytes[4];
250
251    if (!file)
252        return XcursorFalse;
253
254    bytes[0] = (unsigned char)(u);
255    bytes[1] = (unsigned char)(u >>  8);
256    bytes[2] = (unsigned char)(u >> 16);
257    bytes[3] = (unsigned char)(u >> 24);
258    if ((*file->write) (file, bytes, 4) != 4)
259	return XcursorFalse;
260    return XcursorTrue;
261}
262
263static XcursorBool
264_XcursorWriteBytes (XcursorFile *file, char *bytes, int length)
265{
266    if (!file || !bytes || (*file->write) (file, (unsigned char *) bytes, length) != length)
267	return XcursorFalse;
268    return XcursorTrue;
269}
270
271static void
272_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
273{
274    free (fileHeader);
275}
276
277static XcursorFileHeader *
278_XcursorFileHeaderCreate (XcursorUInt ntoc)
279{
280    XcursorFileHeader	*fileHeader;
281
282    if (ntoc > 0x10000)
283	return NULL;
284    fileHeader = malloc (sizeof (XcursorFileHeader) +
285			 ntoc * sizeof (XcursorFileToc));
286    if (!fileHeader)
287	return NULL;
288    fileHeader->magic = XCURSOR_MAGIC;
289    fileHeader->header = XCURSOR_FILE_HEADER_LEN;
290    fileHeader->version = XCURSOR_FILE_VERSION;
291    fileHeader->ntoc = ntoc;
292    fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
293    return fileHeader;
294}
295
296static XcursorFileHeader *
297_XcursorReadFileHeader (XcursorFile *file)
298{
299    XcursorFileHeader	head, *fileHeader;
300    XcursorUInt		skip;
301    XcursorUInt		n;
302
303    if (!file)
304        return NULL;
305
306    if (!_XcursorReadUInt (file, &head.magic))
307	return NULL;
308    if (head.magic != XCURSOR_MAGIC)
309	return NULL;
310    if (!_XcursorReadUInt (file, &head.header))
311	return NULL;
312    if (head.header < XCURSOR_FILE_HEADER_LEN)
313	return NULL;
314    if (!_XcursorReadUInt (file, &head.version))
315	return NULL;
316    if (!_XcursorReadUInt (file, &head.ntoc))
317	return NULL;
318    skip = head.header - XCURSOR_FILE_HEADER_LEN;
319    if (skip)
320	if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
321	    return NULL;
322    fileHeader = _XcursorFileHeaderCreate (head.ntoc);
323    if (!fileHeader)
324	return NULL;
325    fileHeader->magic = head.magic;
326    fileHeader->header = head.header;
327    fileHeader->version = head.version;
328    fileHeader->ntoc = head.ntoc;
329    for (n = 0; n < fileHeader->ntoc; n++)
330    {
331	if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
332	    break;
333	if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
334	    break;
335	if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
336	    break;
337    }
338    if (n != fileHeader->ntoc)
339    {
340	_XcursorFileHeaderDestroy (fileHeader);
341	return NULL;
342    }
343    return fileHeader;
344}
345
346static XcursorUInt
347_XcursorFileHeaderLength (XcursorFileHeader *fileHeader)
348{
349    return (XCURSOR_FILE_HEADER_LEN +
350	    fileHeader->ntoc * XCURSOR_FILE_TOC_LEN);
351}
352
353static XcursorBool
354_XcursorWriteFileHeader (XcursorFile *file, XcursorFileHeader *fileHeader)
355{
356    XcursorUInt toc;
357
358    if (!file || !fileHeader)
359        return XcursorFalse;
360
361    if (!_XcursorWriteUInt (file, fileHeader->magic))
362	return XcursorFalse;
363    if (!_XcursorWriteUInt (file, fileHeader->header))
364	return XcursorFalse;
365    if (!_XcursorWriteUInt (file, fileHeader->version))
366	return XcursorFalse;
367    if (!_XcursorWriteUInt (file, fileHeader->ntoc))
368	return XcursorFalse;
369    for (toc = 0; toc < fileHeader->ntoc; toc++)
370    {
371	if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].type))
372	    return XcursorFalse;
373	if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].subtype))
374	    return XcursorFalse;
375	if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].position))
376	    return XcursorFalse;
377    }
378    return XcursorTrue;
379}
380
381static XcursorBool
382_XcursorSeekToToc (XcursorFile		*file,
383		   XcursorFileHeader	*fileHeader,
384		   int			toc)
385{
386    if (!file || !fileHeader || \
387        (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
388	return XcursorFalse;
389    return XcursorTrue;
390}
391
392static XcursorBool
393_XcursorFileReadChunkHeader (XcursorFile	*file,
394			     XcursorFileHeader	*fileHeader,
395			     int		toc,
396			     XcursorChunkHeader	*chunkHeader)
397{
398    if (!file || !fileHeader || !chunkHeader)
399        return XcursorFalse;
400    if (!_XcursorSeekToToc (file, fileHeader, toc))
401	return XcursorFalse;
402    if (!_XcursorReadUInt (file, &chunkHeader->header))
403	return XcursorFalse;
404    if (!_XcursorReadUInt (file, &chunkHeader->type))
405	return XcursorFalse;
406    if (!_XcursorReadUInt (file, &chunkHeader->subtype))
407	return XcursorFalse;
408    if (!_XcursorReadUInt (file, &chunkHeader->version))
409	return XcursorFalse;
410    /* sanity check */
411    if (chunkHeader->type != fileHeader->tocs[toc].type ||
412	chunkHeader->subtype != fileHeader->tocs[toc].subtype)
413	return XcursorFalse;
414    return XcursorTrue;
415}
416
417static XcursorBool
418_XcursorFileWriteChunkHeader (XcursorFile	    *file,
419			      XcursorFileHeader	    *fileHeader,
420			      int		    toc,
421			      XcursorChunkHeader    *chunkHeader)
422{
423    if (!file || !fileHeader || !chunkHeader)
424        return XcursorFalse;
425    if (!_XcursorSeekToToc (file, fileHeader, toc))
426	return XcursorFalse;
427    if (!_XcursorWriteUInt (file, chunkHeader->header))
428	return XcursorFalse;
429    if (!_XcursorWriteUInt (file, chunkHeader->type))
430	return XcursorFalse;
431    if (!_XcursorWriteUInt (file, chunkHeader->subtype))
432	return XcursorFalse;
433    if (!_XcursorWriteUInt (file, chunkHeader->version))
434	return XcursorFalse;
435    return XcursorTrue;
436}
437
438#define dist(a,b)   ((a) > (b) ? (a) - (b) : (b) - (a))
439
440static XcursorDim
441_XcursorFindBestSize (XcursorFileHeader *fileHeader,
442		      XcursorDim	size,
443		      int		*nsizesp)
444{
445    XcursorUInt	n;
446    int		nsizes = 0;
447    XcursorDim	bestSize = 0;
448    XcursorDim	thisSize;
449
450    enterFunc((T_CALLED(_XcursorFindBestSize) "(%p, %u, %p)\n",
451              (void*)fileHeader, size, (void*)nsizesp));
452
453    if (!fileHeader || !nsizesp)
454        returnUint(0);
455
456    for (n = 0; n < fileHeader->ntoc; n++)
457    {
458	if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
459	    continue;
460	thisSize = fileHeader->tocs[n].subtype;
461	if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
462	{
463	    bestSize = thisSize;
464	    nsizes = 1;
465	}
466	else if (thisSize == bestSize)
467	    nsizes++;
468    }
469    *nsizesp = nsizes;
470    returnUint(bestSize);
471}
472
473static int
474_XcursorFindImageToc (XcursorFileHeader	*fileHeader,
475		      XcursorDim	size,
476		      int		count)
477{
478    XcursorUInt		toc;
479    XcursorDim		thisSize;
480
481    enterFunc((T_CALLED(_XcursorFindImageToc) "(%p, %u, %d)\n",
482              (void*)fileHeader, size, count));
483
484    if (!fileHeader)
485        returnCode(0);
486
487    for (toc = 0; toc < fileHeader->ntoc; toc++)
488    {
489	if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
490	    continue;
491	thisSize = fileHeader->tocs[toc].subtype;
492	if (thisSize != size)
493	    continue;
494	if (!count)
495	    break;
496	count--;
497    }
498    if (toc == fileHeader->ntoc)
499	returnCode(-1);
500    returnCode((int) toc);
501}
502
503static XcursorImage *
504_XcursorReadImage (XcursorFile		*file,
505		   XcursorFileHeader	*fileHeader,
506		   int			toc)
507{
508    XcursorChunkHeader	chunkHeader;
509    XcursorImage	head;
510    XcursorImage	*image;
511    int			n;
512    XcursorPixel	*p;
513
514    enterFunc((T_CALLED(_XcursorReadImage) "(%p, %p, %d)\n",
515              (void*)file, (void*)fileHeader, toc));
516
517    if (!file || !fileHeader)
518        returnAddr(NULL);
519
520    if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
521	returnAddr(NULL);
522    if (!_XcursorReadUInt (file, &head.width))
523	returnAddr(NULL);
524    if (!_XcursorReadUInt (file, &head.height))
525	returnAddr(NULL);
526    if (!_XcursorReadUInt (file, &head.xhot))
527	returnAddr(NULL);
528    if (!_XcursorReadUInt (file, &head.yhot))
529	returnAddr(NULL);
530    if (!_XcursorReadUInt (file, &head.delay))
531	returnAddr(NULL);
532    /* sanity check data */
533    if (head.width > XCURSOR_IMAGE_MAX_SIZE  ||
534	head.height > XCURSOR_IMAGE_MAX_SIZE)
535	returnAddr(NULL);
536    if (head.width == 0 || head.height == 0)
537	returnAddr(NULL);
538    if (head.xhot > head.width || head.yhot > head.height)
539	returnAddr(NULL);
540
541    /* Create the image and initialize it */
542    image = XcursorImageCreate ((int) head.width, (int) head.height);
543    if (image == NULL)
544	returnAddr(NULL);
545    if (chunkHeader.version < image->version)
546	image->version = chunkHeader.version;
547    image->size = chunkHeader.subtype;
548    image->xhot = head.xhot;
549    image->yhot = head.yhot;
550    image->delay = head.delay;
551    n = (int) (image->width * image->height);
552    p = image->pixels;
553    while (n--)
554    {
555	if (!_XcursorReadUInt (file, p))
556	{
557	    XcursorImageDestroy (image);
558	    returnAddr(NULL);
559	}
560	p++;
561    }
562    returnAddr(image);
563}
564
565static XcursorUInt
566_XcursorImageLength (XcursorImage   *image)
567{
568    enterFunc((T_CALLED(_XcursorImageLength) "(%p)\n", (void*)image));
569
570    if (!image)
571        returnUint(0);
572
573    returnUint(XCURSOR_IMAGE_HEADER_LEN + (image->width * image->height) * 4);
574}
575
576static XcursorBool
577_XcursorWriteImage (XcursorFile		*file,
578		    XcursorFileHeader	*fileHeader,
579		    int			toc,
580		    XcursorImage	*image)
581{
582    XcursorChunkHeader	chunkHeader;
583    int			n;
584    XcursorPixel	*p;
585
586    enterFunc((T_CALLED(_XcursorWriteImage) "(%p, %p, %d, %p)\n",
587              (void*)file, (void*)fileHeader, toc, (void*)image));
588
589    if (!file || !fileHeader || !image)
590        returnCode(XcursorFalse);
591
592    /* sanity check data */
593    if (image->width > XCURSOR_IMAGE_MAX_SIZE  ||
594	image->height > XCURSOR_IMAGE_MAX_SIZE)
595	returnCode(XcursorFalse);
596    if (image->width == 0 || image->height == 0)
597	returnCode(XcursorFalse);
598    if (image->xhot > image->width || image->yhot > image->height)
599	returnCode(XcursorFalse);
600
601    /* write chunk header */
602    chunkHeader.header = XCURSOR_IMAGE_HEADER_LEN;
603    chunkHeader.type = XCURSOR_IMAGE_TYPE;
604    chunkHeader.subtype = image->size;
605    chunkHeader.version = XCURSOR_IMAGE_VERSION;
606
607    if (!_XcursorFileWriteChunkHeader (file, fileHeader, toc, &chunkHeader))
608	returnCode(XcursorFalse);
609
610    /* write extra image header fields */
611    if (!_XcursorWriteUInt (file, image->width))
612	returnCode(XcursorFalse);
613    if (!_XcursorWriteUInt (file, image->height))
614	returnCode(XcursorFalse);
615    if (!_XcursorWriteUInt (file, image->xhot))
616	returnCode(XcursorFalse);
617    if (!_XcursorWriteUInt (file, image->yhot))
618	returnCode(XcursorFalse);
619    if (!_XcursorWriteUInt (file, image->delay))
620	returnCode(XcursorFalse);
621
622    /* write the image */
623    n = (int) (image->width * image->height);
624    p = image->pixels;
625    while (n--)
626    {
627	if (!_XcursorWriteUInt (file, *p))
628	    returnCode(XcursorFalse);
629	p++;
630    }
631    returnCode(XcursorTrue);
632}
633
634static XcursorComment *
635_XcursorReadComment (XcursorFile	    *file,
636		     XcursorFileHeader	    *fileHeader,
637		     int		    toc)
638{
639    XcursorChunkHeader	chunkHeader;
640    XcursorUInt		length;
641    XcursorComment	*comment;
642
643    enterFunc((T_CALLED(_XcursorReadComment) "(%p, %p, %d)\n",
644              (void*)file, (void*)fileHeader, toc));
645
646    if (!file || !fileHeader)
647        returnAddr(NULL);
648
649    /* read chunk header */
650    if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
651	returnAddr(NULL);
652    /* read extra comment header fields */
653    if (!_XcursorReadUInt (file, &length))
654	returnAddr(NULL);
655    comment = XcursorCommentCreate (chunkHeader.subtype, (int) length);
656    if (!comment)
657	returnAddr(NULL);
658    if (!_XcursorReadBytes (file, comment->comment, (int) length))
659    {
660	XcursorCommentDestroy (comment);
661	returnAddr(NULL);
662    }
663    comment->comment[length] = '\0';
664    returnAddr(comment);
665}
666
667static XcursorUInt
668_XcursorCommentLength (XcursorComment	    *comment)
669{
670    return XCURSOR_COMMENT_HEADER_LEN + (XcursorUInt) strlen (comment->comment);
671}
672
673static XcursorBool
674_XcursorWriteComment (XcursorFile	    *file,
675		      XcursorFileHeader	    *fileHeader,
676		      int		    toc,
677		      XcursorComment	    *comment)
678{
679    XcursorChunkHeader	chunkHeader;
680    XcursorUInt		length;
681
682    if (!file || !fileHeader || !comment || !comment->comment)
683        return XcursorFalse;
684
685    length = (XcursorUInt) strlen (comment->comment);
686
687    /* sanity check data */
688    if (length > XCURSOR_COMMENT_MAX_LEN)
689	return XcursorFalse;
690
691    /* read chunk header */
692    chunkHeader.header = XCURSOR_COMMENT_HEADER_LEN;
693    chunkHeader.type = XCURSOR_COMMENT_TYPE;
694    chunkHeader.subtype = comment->comment_type;
695    chunkHeader.version = XCURSOR_COMMENT_VERSION;
696
697    if (!_XcursorFileWriteChunkHeader (file, fileHeader, toc, &chunkHeader))
698	return XcursorFalse;
699
700    /* write extra comment header fields */
701    if (!_XcursorWriteUInt (file, length))
702	return XcursorFalse;
703
704    if (!_XcursorWriteBytes (file, comment->comment, (int) length))
705	return XcursorFalse;
706    return XcursorTrue;
707}
708
709static XcursorImage *
710_XcursorResizeImage (XcursorImage *src, int size)
711{
712    XcursorDim dest_y, dest_x;
713    double scale = (double) size / src->size;
714    XcursorImage *dest;
715
716    enterFunc((T_CALLED(_XcursorResizeImage) "(%p, %d)\n", (void*)src, size));
717
718    dest = XcursorImageCreate ((int) (src->width * scale),
719			       (int) (src->height * scale));
720    if (!dest)
721	returnAddr(NULL);
722
723    dest->size = (XcursorDim) size;
724    dest->xhot = (XcursorDim) (src->xhot * scale);
725    dest->yhot = (XcursorDim) (src->yhot * scale);
726    dest->delay = src->delay;
727
728    for (dest_y = 0; dest_y < dest->height; dest_y++)
729    {
730	XcursorDim src_y = (XcursorDim) (dest_y / scale);
731	XcursorPixel *src_row = src->pixels + (src_y * src->width);
732	XcursorPixel *dest_row = dest->pixels + (dest_y * dest->width);
733	for (dest_x = 0; dest_x < dest->width; dest_x++)
734	{
735	    XcursorDim src_x = (XcursorDim) (dest_x / scale);
736	    dest_row[dest_x] = src_row[src_x];
737	}
738    }
739
740    returnAddr(dest);
741}
742
743static XcursorImage *
744_XcursorXcFileLoadImage (XcursorFile *file, int size, XcursorBool resize)
745{
746    XcursorFileHeader	*fileHeader;
747    XcursorDim		bestSize;
748    int			nsize;
749    int			toc;
750    XcursorImage	*image;
751
752    enterFunc((T_CALLED(_XcursorXcFileLoadImage) "(%p, %d, %d)\n",
753	      (void*)file, size, resize));
754
755    if (size < 0)
756	returnAddr(NULL);
757    fileHeader = _XcursorReadFileHeader (file);
758    if (!fileHeader)
759	returnAddr(NULL);
760    bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
761    if (!bestSize)
762	returnAddr(NULL);
763    toc = _XcursorFindImageToc (fileHeader, bestSize, 0);
764    if (toc < 0)
765	returnAddr(NULL);
766    image = _XcursorReadImage (file, fileHeader, toc);
767    _XcursorFileHeaderDestroy (fileHeader);
768
769    if (resize && image != NULL && (image->size != (XcursorDim) size))
770    {
771	XcursorImage *resized_image = _XcursorResizeImage (image, size);
772	XcursorImageDestroy (image);
773	image = resized_image;
774    }
775
776    returnAddr(image);
777}
778
779XcursorImage *
780XcursorXcFileLoadImage (XcursorFile *file, int size)
781{
782    enterFunc((T_CALLED(XcursorXcFileLoadImage) "(%p, %d)\n", (void*)file, size));
783
784    returnAddr(_XcursorXcFileLoadImage (file, size, XcursorFalse));
785}
786
787XcursorImages *
788_XcursorXcFileLoadImages (XcursorFile *file, int size, XcursorBool resize)
789{
790    XcursorFileHeader	*fileHeader;
791    XcursorDim		bestSize;
792    int			nsize;
793    XcursorImages	*images;
794    int			n;
795    XcursorImage        *image;
796
797    enterFunc((T_CALLED(_XcursorXcFileLoadImages) "(%p, %d, %d)\n",
798	      (void*)file, size, resize));
799
800    if (!file || size < 0)
801	returnAddr(NULL);
802    fileHeader = _XcursorReadFileHeader (file);
803    if (!fileHeader)
804	returnAddr(NULL);
805    bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
806    if (!bestSize)
807    {
808        _XcursorFileHeaderDestroy (fileHeader);
809	returnAddr(NULL);
810    }
811    images = XcursorImagesCreate (nsize);
812    if (!images)
813    {
814        _XcursorFileHeaderDestroy (fileHeader);
815	returnAddr(NULL);
816    }
817    for (n = 0; n < nsize; n++)
818    {
819	int toc = _XcursorFindImageToc (fileHeader, bestSize, n);
820	if (toc < 0)
821	    break;
822	image = _XcursorReadImage (file, fileHeader, toc);
823	if (!image)
824	    break;
825	if (resize && (image->size != (XcursorDim) size))
826	{
827	    XcursorImage *resized_image = _XcursorResizeImage (image, size);
828	    XcursorImageDestroy (image);
829	    image = resized_image;
830	    if (image == NULL)
831		break;
832	}
833	images->images[images->nimage] = image;
834	images->nimage++;
835    }
836    _XcursorFileHeaderDestroy (fileHeader);
837    if (images != NULL && images->nimage != nsize)
838    {
839	XcursorImagesDestroy (images);
840	images = NULL;
841    }
842    returnAddr(images);
843}
844
845XcursorImages *
846XcursorXcFileLoadImages (XcursorFile *file, int size)
847{
848    enterFunc((T_CALLED(XcursorXcFileLoadImages) "(%p, %d)\n", (void*)file, size));
849
850    returnAddr(_XcursorXcFileLoadImages (file, size, XcursorFalse));
851}
852
853XcursorImages *
854XcursorXcFileLoadAllImages (XcursorFile *file)
855{
856    XcursorFileHeader	*fileHeader;
857    XcursorImage	*image;
858    XcursorImages	*images;
859    int			nimage;
860    XcursorUInt		n;
861    XcursorUInt		toc;
862
863    enterFunc((T_CALLED(XcursorXcFileLoadAllImages) "(%p)\n", (void*)file));
864
865    if (!file)
866        returnAddr(NULL);
867
868    fileHeader = _XcursorReadFileHeader (file);
869    if (!fileHeader)
870	returnAddr(NULL);
871    nimage = 0;
872    for (n = 0; n < fileHeader->ntoc; n++)
873    {
874	switch (fileHeader->tocs[n].type) {
875	case XCURSOR_IMAGE_TYPE:
876	    nimage++;
877	    break;
878	}
879    }
880    images = XcursorImagesCreate (nimage);
881    if (!images)
882    {
883	_XcursorFileHeaderDestroy (fileHeader);
884	returnAddr(NULL);
885    }
886    for (toc = 0; toc < fileHeader->ntoc; toc++)
887    {
888	switch (fileHeader->tocs[toc].type) {
889	case XCURSOR_IMAGE_TYPE:
890	    image = _XcursorReadImage (file, fileHeader, (int) toc);
891	    if (image)
892	    {
893		images->images[images->nimage] = image;
894		images->nimage++;
895	    }
896	    break;
897	}
898    }
899    _XcursorFileHeaderDestroy (fileHeader);
900    if (images->nimage != nimage)
901    {
902	XcursorImagesDestroy (images);
903	images = NULL;
904    }
905    returnAddr(images);
906}
907
908XcursorBool
909XcursorXcFileLoad (XcursorFile	    *file,
910		   XcursorComments  **commentsp,
911		   XcursorImages    **imagesp)
912{
913    XcursorFileHeader	*fileHeader;
914    int			nimage;
915    int			ncomment;
916    XcursorImages	*images;
917    XcursorImage	*image;
918    XcursorComment	*comment;
919    XcursorComments	*comments;
920    XcursorUInt		toc;
921
922    enterFunc((T_CALLED(XcursorXcFileLoad) "(%p, %p, %p)\n",
923	      (void*)file, (void*)commentsp, (void*)imagesp));
924
925    if (!file)
926        returnCode(0);
927    fileHeader = _XcursorReadFileHeader (file);
928    if (!fileHeader)
929	returnCode(0);
930    nimage = 0;
931    ncomment = 0;
932    for (toc = 0; toc < fileHeader->ntoc; toc++)
933    {
934	switch (fileHeader->tocs[toc].type) {
935	case XCURSOR_COMMENT_TYPE:
936	    ncomment++;
937	    break;
938	case XCURSOR_IMAGE_TYPE:
939	    nimage++;
940	    break;
941	}
942    }
943    images = XcursorImagesCreate (nimage);
944    if (!images)
945    {
946	_XcursorFileHeaderDestroy (fileHeader);
947	returnCode(0);
948    }
949    comments = XcursorCommentsCreate (ncomment);
950    if (!comments)
951    {
952	_XcursorFileHeaderDestroy (fileHeader);
953	XcursorImagesDestroy (images);
954	returnCode(0);
955    }
956    for (toc = 0; toc < fileHeader->ntoc; toc++)
957    {
958	switch (fileHeader->tocs[toc].type) {
959	case XCURSOR_COMMENT_TYPE:
960	    comment = _XcursorReadComment (file, fileHeader, (int) toc);
961	    if (comment)
962	    {
963		comments->comments[comments->ncomment] = comment;
964		comments->ncomment++;
965	    }
966	    break;
967	case XCURSOR_IMAGE_TYPE:
968	    image = _XcursorReadImage (file, fileHeader, (int) toc);
969	    if (image)
970	    {
971		images->images[images->nimage] = image;
972		images->nimage++;
973	    }
974	    break;
975	}
976    }
977    _XcursorFileHeaderDestroy (fileHeader);
978    if (images->nimage != nimage || comments->ncomment != ncomment)
979    {
980	XcursorImagesDestroy (images);
981	XcursorCommentsDestroy (comments);
982	images = NULL;
983	comments = NULL;
984	returnCode(XcursorFalse);
985    }
986    *imagesp = images;
987    *commentsp = comments;
988    returnCode(XcursorTrue);
989}
990
991XcursorBool
992XcursorXcFileSave (XcursorFile		    *file,
993		   const XcursorComments    *comments,
994		   const XcursorImages	    *images)
995{
996    XcursorFileHeader	*fileHeader;
997    XcursorUInt		position;
998    XcursorUInt		n;
999    int			toc;
1000    XcursorUInt		ncomment;
1001    XcursorUInt		nimage;
1002
1003    enterFunc((T_CALLED(XcursorXcFileSave) "(%p, %p, %p)\n",
1004	      (void*)file, (const void*)comments, (const void*)images));
1005
1006    if (!file || !comments || !images)
1007	returnCode(XcursorFalse);
1008
1009    /*
1010     * Caller may have tainted the counts.
1011     */
1012    ncomment = (XcursorUInt)(comments->ncomment > 0 ? comments->ncomment : 0);
1013    nimage   = (XcursorUInt)(images->nimage     > 0 ? images->nimage : 0);
1014
1015    fileHeader = _XcursorFileHeaderCreate (ncomment + nimage);
1016    if (!fileHeader)
1017	returnCode(XcursorFalse);
1018
1019    position = _XcursorFileHeaderLength (fileHeader);
1020
1021    /*
1022     * Compute the toc.  Place the images before the comments
1023     * as they're more often read
1024     */
1025
1026    toc = 0;
1027    for (n = 0; n < nimage; n++)
1028    {
1029	fileHeader->tocs[toc].type = XCURSOR_IMAGE_TYPE;
1030	fileHeader->tocs[toc].subtype = images->images[n]->size;
1031	fileHeader->tocs[toc].position = position;
1032	position += _XcursorImageLength (images->images[n]);
1033	toc++;
1034    }
1035
1036    for (n = 0; n < ncomment; n++)
1037    {
1038	fileHeader->tocs[toc].type = XCURSOR_COMMENT_TYPE;
1039	fileHeader->tocs[toc].subtype = comments->comments[n]->comment_type;
1040	fileHeader->tocs[toc].position = position;
1041	position += _XcursorCommentLength (comments->comments[n]);
1042	toc++;
1043    }
1044
1045    /*
1046     * Write the header and the toc
1047     */
1048    if (!_XcursorWriteFileHeader (file, fileHeader))
1049	goto bail;
1050
1051    /*
1052     * Write the images
1053     */
1054    toc = 0;
1055    for (n = 0; n < nimage; n++)
1056    {
1057	if (!_XcursorWriteImage (file, fileHeader, toc, images->images[n]))
1058	    goto bail;
1059	toc++;
1060    }
1061
1062    /*
1063     * Write the comments
1064     */
1065    for (n = 0; n < ncomment; n++)
1066    {
1067	if (!_XcursorWriteComment (file, fileHeader, toc, comments->comments[n]))
1068	    goto bail;
1069	toc++;
1070    }
1071
1072    _XcursorFileHeaderDestroy (fileHeader);
1073    returnCode(XcursorTrue);
1074
1075bail:
1076    _XcursorFileHeaderDestroy (fileHeader);
1077    returnCode(XcursorFalse);
1078}
1079
1080static int
1081_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
1082{
1083    FILE    *f = file->closure;
1084    return (int) fread (buf, 1, (size_t) len, f);
1085}
1086
1087static int
1088_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
1089{
1090    FILE    *f = file->closure;
1091    return (int) fwrite (buf, 1, (size_t) len, f);
1092}
1093
1094static int
1095_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
1096{
1097    FILE    *f = file->closure;
1098    return fseek (f, offset, whence);
1099}
1100
1101static void
1102_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
1103{
1104    file->closure = stdfile;
1105    file->read = _XcursorStdioFileRead;
1106    file->write = _XcursorStdioFileWrite;
1107    file->seek = _XcursorStdioFileSeek;
1108}
1109
1110XcursorImage *
1111_XcursorFileLoadImage (FILE *file, int size, XcursorBool resize)
1112{
1113    XcursorFile	f;
1114
1115    enterFunc((T_CALLED(_XcursorFileLoadImage) "(%p, %d, %d)\n", (void*)file, size, resize));
1116
1117    if (!file)
1118	returnAddr(NULL);
1119
1120    _XcursorStdioFileInitialize (file, &f);
1121    returnAddr(_XcursorXcFileLoadImage (&f, size, resize));
1122}
1123
1124XcursorImages *
1125_XcursorFileLoadImages (FILE *file, int size, XcursorBool resize)
1126{
1127    XcursorFile	f;
1128
1129    enterFunc((T_CALLED(_XcursorFileLoadImages) "(%p, %d, %d)\n", (void*)file, size, resize));
1130
1131    if (!file)
1132	returnAddr(NULL);
1133
1134    _XcursorStdioFileInitialize (file, &f);
1135    returnAddr(_XcursorXcFileLoadImages (&f, size, resize));
1136}
1137
1138XcursorImage *
1139XcursorFileLoadImage (FILE *file, int size)
1140{
1141    XcursorFile	f;
1142
1143    enterFunc((T_CALLED(XcursorFileLoadImage) "(%p, %d)\n", (void*)file, size));
1144
1145    if (!file)
1146	returnAddr(NULL);
1147
1148    _XcursorStdioFileInitialize (file, &f);
1149    returnAddr(XcursorXcFileLoadImage (&f, size));
1150}
1151
1152XcursorImages *
1153XcursorFileLoadImages (FILE *file, int size)
1154{
1155    XcursorFile	f;
1156
1157    enterFunc((T_CALLED(XcursorFileLoadImages) "(%p, %d)\n", (void*)file, size));
1158
1159    if (!file)
1160	returnAddr(NULL);
1161
1162    _XcursorStdioFileInitialize (file, &f);
1163    returnAddr(XcursorXcFileLoadImages (&f, size));
1164}
1165
1166XcursorImages *
1167XcursorFileLoadAllImages (FILE *file)
1168{
1169    XcursorFile	f;
1170
1171    enterFunc((T_CALLED(XcursorFileLoadAllImages) "(%p)\n", (void*)file));
1172
1173    if (!file)
1174	returnAddr(NULL);
1175
1176    _XcursorStdioFileInitialize (file, &f);
1177    returnAddr(XcursorXcFileLoadAllImages (&f));
1178}
1179
1180XcursorBool
1181XcursorFileLoad (FILE		    *file,
1182		 XcursorComments    **commentsp,
1183		 XcursorImages	    **imagesp)
1184{
1185    XcursorFile	f;
1186
1187    enterFunc((T_CALLED(XcursorFileLoad) "(%p, %p, %p)\n",
1188	       (void*)file, (void*)commentsp, (void*)imagesp));
1189
1190    if (!file || !commentsp || !imagesp)
1191	returnCode(XcursorFalse);
1192
1193    _XcursorStdioFileInitialize (file, &f);
1194    returnCode(XcursorXcFileLoad (&f, commentsp, imagesp));
1195}
1196
1197XcursorBool
1198XcursorFileSaveImages (FILE *file, const XcursorImages *images)
1199{
1200    XcursorComments *comments;
1201    XcursorFile	    f;
1202    XcursorBool	    ret;
1203
1204    enterFunc((T_CALLED(XcursorFileSaveImages) "(%p, %p)\n",
1205	       (void*)file, (const void*)images));
1206
1207    if (!file || !images)
1208	returnCode(0);
1209    if ((comments = XcursorCommentsCreate (0)) == NULL)
1210	returnCode(0);
1211    _XcursorStdioFileInitialize (file, &f);
1212    ret = XcursorXcFileSave (&f, comments, images) && fflush (file) != EOF;
1213    XcursorCommentsDestroy (comments);
1214    returnCode(ret);
1215}
1216
1217XcursorBool
1218XcursorFileSave (FILE *			file,
1219		 const XcursorComments	*comments,
1220		 const XcursorImages	*images)
1221{
1222    XcursorFile	    f;
1223
1224    enterFunc((T_CALLED(_XcursorFileSave) "(%p, %p, %p)\n",
1225	       (void*)file, (const void*)comments, (const void*)images));
1226
1227    if (!file || !comments || !images)
1228	returnCode(XcursorFalse);
1229
1230    _XcursorStdioFileInitialize (file, &f);
1231    returnCode(XcursorXcFileSave (&f, comments, images) && fflush (file) != EOF);
1232}
1233
1234XcursorImage *
1235XcursorFilenameLoadImage (const char *file, int size)
1236{
1237    FILE	    *f;
1238    XcursorImage    *image;
1239
1240    enterFunc((T_CALLED(XcursorFilenameLoadImage) "(\"%s\", %d)\n",
1241	      NonNull(file), size));
1242
1243    if (!file || size < 0)
1244	returnAddr(NULL);
1245
1246    f = fopen (file, "r" FOPEN_CLOEXEC);
1247    if (!f)
1248	returnAddr(NULL);
1249    image = XcursorFileLoadImage (f, size);
1250    fclose (f);
1251    returnAddr(image);
1252}
1253
1254XcursorImages *
1255_XcursorFilenameLoadImages (const char *file, int size, XcursorBool resize)
1256{
1257    FILE	    *f;
1258    XcursorImages   *images;
1259
1260    enterFunc((T_CALLED(_XcursorFilenameLoadImages) "(\"%s\", %d, %d)\n",
1261	      NonNull(file), size, resize));
1262
1263    if (!file || size < 0)
1264	returnAddr(NULL);
1265
1266    f = fopen (file, "r" FOPEN_CLOEXEC);
1267    if (!f)
1268	returnAddr(NULL);
1269    images = _XcursorFileLoadImages (f, size, resize);
1270    fclose (f);
1271    returnAddr(images);
1272}
1273
1274XcursorImages *
1275XcursorFilenameLoadImages (const char *file, int size)
1276{
1277    FILE	    *f;
1278    XcursorImages   *images;
1279
1280    enterFunc((T_CALLED(XcursorFilenameLoadImages) "(\"%s\", %d)\n",
1281	      NonNull(file), size));
1282
1283    if (!file || size < 0)
1284	returnAddr(NULL);
1285
1286    f = fopen (file, "r" FOPEN_CLOEXEC);
1287    if (!f)
1288	returnAddr(NULL);
1289    images = XcursorFileLoadImages (f, size);
1290    fclose (f);
1291    returnAddr(images);
1292}
1293
1294XcursorImages *
1295XcursorFilenameLoadAllImages (const char *file)
1296{
1297    FILE	    *f;
1298    XcursorImages   *images;
1299
1300    enterFunc((T_CALLED(XcursorFilenameLoadAllImages) "(\"%s\")\n",
1301	      NonNull(file)));
1302
1303    if (!file)
1304	returnAddr(NULL);
1305
1306    f = fopen (file, "r" FOPEN_CLOEXEC);
1307    if (!f)
1308	returnAddr(NULL);
1309    images = XcursorFileLoadAllImages (f);
1310    fclose (f);
1311    returnAddr(images);
1312}
1313
1314XcursorBool
1315XcursorFilenameLoad (const char		*file,
1316		     XcursorComments	**commentsp,
1317		     XcursorImages	**imagesp)
1318{
1319    FILE	    *f;
1320    XcursorBool	    ret;
1321
1322    enterFunc((T_CALLED() "(\"%s\", %p, %p)\n",
1323	      NonNull(file), (void*)commentsp, (void*)imagesp));
1324
1325    if (!file)
1326	returnCode(XcursorFalse);
1327
1328    f = fopen (file, "r" FOPEN_CLOEXEC);
1329    if (!f)
1330	returnCode(0);
1331    ret = XcursorFileLoad (f, commentsp, imagesp);
1332    fclose (f);
1333    returnCode(ret);
1334}
1335
1336XcursorBool
1337XcursorFilenameSaveImages (const char *file, const XcursorImages *images)
1338{
1339    FILE	    *f;
1340    XcursorBool	    ret;
1341
1342    enterFunc((T_CALLED(XcursorFilenameSaveImages) "(\"%s\", %p)\n",
1343	      NonNull(file), (const void*)images));
1344
1345    if (!file || !images)
1346	returnCode(XcursorFalse);
1347
1348    f = fopen (file, "w" FOPEN_CLOEXEC);
1349    if (!f)
1350	returnCode(0);
1351    ret = XcursorFileSaveImages (f, images);
1352    returnCode(fclose (f) != EOF && ret);
1353}
1354
1355XcursorBool
1356XcursorFilenameSave (const char		    *file,
1357		     const XcursorComments  *comments,
1358		     const XcursorImages    *images)
1359{
1360    FILE	    *f;
1361    XcursorBool	    ret;
1362
1363    enterFunc((T_CALLED(XcursorFilenameSave ) "(\"%s\", %p, %p)\n",
1364    		NonNull(file),
1365		(const void *) comments,
1366		(const void *) images));
1367
1368    if (!file || !comments || !images) {
1369	ret = XcursorFalse;
1370    } else {
1371	f = fopen (file, "w" FOPEN_CLOEXEC);
1372	if (!f) {
1373	    ret = 0;
1374	} else {
1375	    ret = XcursorFileSave (f, comments, images);
1376	    ret = fclose (f) != EOF && ret;
1377	}
1378    }
1379    returnCode(ret);
1380}
1381