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