file.c revision 0ea508b1
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		    (size_t) (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 = (XcursorDim) (width > height ? width : height);
44    image->width = (XcursorDim) width;
45    image->height = (XcursorDim) 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_t) 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) + (size_t) 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_t) 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
166    *u = ((XcursorUInt)(bytes[0]) << 0) |
167         ((XcursorUInt)(bytes[1]) << 8) |
168         ((XcursorUInt)(bytes[2]) << 16) |
169         ((XcursorUInt)(bytes[3]) << 24);
170    return XcursorTrue;
171}
172
173static XcursorBool
174_XcursorReadBytes (XcursorFile *file, char *bytes, int length)
175{
176    if (!file || !bytes || (*file->read) (file, (unsigned char *) bytes, length) != length)
177	return XcursorFalse;
178    return XcursorTrue;
179}
180
181static XcursorBool
182_XcursorWriteUInt (XcursorFile *file, XcursorUInt u)
183{
184    unsigned char   bytes[4];
185
186    if (!file)
187        return XcursorFalse;
188
189    bytes[0] = (unsigned char)(u);
190    bytes[1] = (unsigned char)(u >>  8);
191    bytes[2] = (unsigned char)(u >> 16);
192    bytes[3] = (unsigned char)(u >> 24);
193    if ((*file->write) (file, bytes, 4) != 4)
194	return XcursorFalse;
195    return XcursorTrue;
196}
197
198static XcursorBool
199_XcursorWriteBytes (XcursorFile *file, char *bytes, int length)
200{
201    if (!file || !bytes || (*file->write) (file, (unsigned char *) bytes, length) != length)
202	return XcursorFalse;
203    return XcursorTrue;
204}
205
206static void
207_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
208{
209    free (fileHeader);
210}
211
212static XcursorFileHeader *
213_XcursorFileHeaderCreate (XcursorUInt ntoc)
214{
215    XcursorFileHeader	*fileHeader;
216
217    if (ntoc > 0x10000)
218	return NULL;
219    fileHeader = malloc (sizeof (XcursorFileHeader) +
220			 ntoc * sizeof (XcursorFileToc));
221    if (!fileHeader)
222	return NULL;
223    fileHeader->magic = XCURSOR_MAGIC;
224    fileHeader->header = XCURSOR_FILE_HEADER_LEN;
225    fileHeader->version = XCURSOR_FILE_VERSION;
226    fileHeader->ntoc = ntoc;
227    fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
228    return fileHeader;
229}
230
231static XcursorFileHeader *
232_XcursorReadFileHeader (XcursorFile *file)
233{
234    XcursorFileHeader	head, *fileHeader;
235    XcursorUInt		skip;
236    XcursorUInt		n;
237
238    if (!file)
239        return NULL;
240
241    if (!_XcursorReadUInt (file, &head.magic))
242	return NULL;
243    if (head.magic != XCURSOR_MAGIC)
244	return NULL;
245    if (!_XcursorReadUInt (file, &head.header))
246	return NULL;
247    if (!_XcursorReadUInt (file, &head.version))
248	return NULL;
249    if (!_XcursorReadUInt (file, &head.ntoc))
250	return NULL;
251    skip = head.header - XCURSOR_FILE_HEADER_LEN;
252    if (skip)
253	if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
254	    return NULL;
255    fileHeader = _XcursorFileHeaderCreate (head.ntoc);
256    if (!fileHeader)
257	return NULL;
258    fileHeader->magic = head.magic;
259    fileHeader->header = head.header;
260    fileHeader->version = head.version;
261    fileHeader->ntoc = head.ntoc;
262    for (n = 0; n < fileHeader->ntoc; n++)
263    {
264	if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
265	    break;
266	if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
267	    break;
268	if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
269	    break;
270    }
271    if (n != fileHeader->ntoc)
272    {
273	_XcursorFileHeaderDestroy (fileHeader);
274	return NULL;
275    }
276    return fileHeader;
277}
278
279static XcursorUInt
280_XcursorFileHeaderLength (XcursorFileHeader *fileHeader)
281{
282    return (XCURSOR_FILE_HEADER_LEN +
283	    fileHeader->ntoc * XCURSOR_FILE_TOC_LEN);
284}
285
286static XcursorBool
287_XcursorWriteFileHeader (XcursorFile *file, XcursorFileHeader *fileHeader)
288{
289    XcursorUInt toc;
290
291    if (!file || !fileHeader)
292        return XcursorFalse;
293
294    if (!_XcursorWriteUInt (file, fileHeader->magic))
295	return XcursorFalse;
296    if (!_XcursorWriteUInt (file, fileHeader->header))
297	return XcursorFalse;
298    if (!_XcursorWriteUInt (file, fileHeader->version))
299	return XcursorFalse;
300    if (!_XcursorWriteUInt (file, fileHeader->ntoc))
301	return XcursorFalse;
302    for (toc = 0; toc < fileHeader->ntoc; toc++)
303    {
304	if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].type))
305	    return XcursorFalse;
306	if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].subtype))
307	    return XcursorFalse;
308	if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].position))
309	    return XcursorFalse;
310    }
311    return XcursorTrue;
312}
313
314static XcursorBool
315_XcursorSeekToToc (XcursorFile		*file,
316		   XcursorFileHeader	*fileHeader,
317		   int			toc)
318{
319    if (!file || !fileHeader || \
320        (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
321	return XcursorFalse;
322    return XcursorTrue;
323}
324
325static XcursorBool
326_XcursorFileReadChunkHeader (XcursorFile	*file,
327			     XcursorFileHeader	*fileHeader,
328			     int		toc,
329			     XcursorChunkHeader	*chunkHeader)
330{
331    if (!file || !fileHeader || !chunkHeader)
332        return XcursorFalse;
333    if (!_XcursorSeekToToc (file, fileHeader, toc))
334	return XcursorFalse;
335    if (!_XcursorReadUInt (file, &chunkHeader->header))
336	return XcursorFalse;
337    if (!_XcursorReadUInt (file, &chunkHeader->type))
338	return XcursorFalse;
339    if (!_XcursorReadUInt (file, &chunkHeader->subtype))
340	return XcursorFalse;
341    if (!_XcursorReadUInt (file, &chunkHeader->version))
342	return XcursorFalse;
343    /* sanity check */
344    if (chunkHeader->type != fileHeader->tocs[toc].type ||
345	chunkHeader->subtype != fileHeader->tocs[toc].subtype)
346	return XcursorFalse;
347    return XcursorTrue;
348}
349
350static XcursorBool
351_XcursorFileWriteChunkHeader (XcursorFile	    *file,
352			      XcursorFileHeader	    *fileHeader,
353			      int		    toc,
354			      XcursorChunkHeader    *chunkHeader)
355{
356    if (!file || !fileHeader || !chunkHeader)
357        return XcursorFalse;
358    if (!_XcursorSeekToToc (file, fileHeader, toc))
359	return XcursorFalse;
360    if (!_XcursorWriteUInt (file, chunkHeader->header))
361	return XcursorFalse;
362    if (!_XcursorWriteUInt (file, chunkHeader->type))
363	return XcursorFalse;
364    if (!_XcursorWriteUInt (file, chunkHeader->subtype))
365	return XcursorFalse;
366    if (!_XcursorWriteUInt (file, chunkHeader->version))
367	return XcursorFalse;
368    return XcursorTrue;
369}
370
371#define dist(a,b)   ((a) > (b) ? (a) - (b) : (b) - (a))
372
373static XcursorDim
374_XcursorFindBestSize (XcursorFileHeader *fileHeader,
375		      XcursorDim	size,
376		      int		*nsizesp)
377{
378    XcursorUInt	n;
379    int		nsizes = 0;
380    XcursorDim	bestSize = 0;
381    XcursorDim	thisSize;
382
383    if (!fileHeader || !nsizesp)
384        return 0;
385
386    for (n = 0; n < fileHeader->ntoc; n++)
387    {
388	if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
389	    continue;
390	thisSize = fileHeader->tocs[n].subtype;
391	if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
392	{
393	    bestSize = thisSize;
394	    nsizes = 1;
395	}
396	else if (thisSize == bestSize)
397	    nsizes++;
398    }
399    *nsizesp = nsizes;
400    return bestSize;
401}
402
403static int
404_XcursorFindImageToc (XcursorFileHeader	*fileHeader,
405		      XcursorDim	size,
406		      int		count)
407{
408    XcursorUInt		toc;
409    XcursorDim		thisSize;
410
411    if (!fileHeader)
412        return 0;
413
414    for (toc = 0; toc < fileHeader->ntoc; toc++)
415    {
416	if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
417	    continue;
418	thisSize = fileHeader->tocs[toc].subtype;
419	if (thisSize != size)
420	    continue;
421	if (!count)
422	    break;
423	count--;
424    }
425    if (toc == fileHeader->ntoc)
426	return -1;
427    return (int) toc;
428}
429
430static XcursorImage *
431_XcursorReadImage (XcursorFile		*file,
432		   XcursorFileHeader	*fileHeader,
433		   int			toc)
434{
435    XcursorChunkHeader	chunkHeader;
436    XcursorImage	head;
437    XcursorImage	*image;
438    int			n;
439    XcursorPixel	*p;
440
441    if (!file || !fileHeader)
442        return NULL;
443
444    if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
445	return NULL;
446    if (!_XcursorReadUInt (file, &head.width))
447	return NULL;
448    if (!_XcursorReadUInt (file, &head.height))
449	return NULL;
450    if (!_XcursorReadUInt (file, &head.xhot))
451	return NULL;
452    if (!_XcursorReadUInt (file, &head.yhot))
453	return NULL;
454    if (!_XcursorReadUInt (file, &head.delay))
455	return NULL;
456    /* sanity check data */
457    if (head.width > XCURSOR_IMAGE_MAX_SIZE  ||
458	head.height > XCURSOR_IMAGE_MAX_SIZE)
459	return NULL;
460    if (head.width == 0 || head.height == 0)
461	return NULL;
462    if (head.xhot > head.width || head.yhot > head.height)
463	return NULL;
464
465    /* Create the image and initialize it */
466    image = XcursorImageCreate ((int) head.width, (int) head.height);
467    if (image == NULL)
468	return NULL;
469    if (chunkHeader.version < image->version)
470	image->version = chunkHeader.version;
471    image->size = chunkHeader.subtype;
472    image->xhot = head.xhot;
473    image->yhot = head.yhot;
474    image->delay = head.delay;
475    n = (int) (image->width * image->height);
476    p = image->pixels;
477    while (n--)
478    {
479	if (!_XcursorReadUInt (file, p))
480	{
481	    XcursorImageDestroy (image);
482	    return NULL;
483	}
484	p++;
485    }
486    return image;
487}
488
489static XcursorUInt
490_XcursorImageLength (XcursorImage   *image)
491{
492    if (!image)
493        return 0;
494
495    return XCURSOR_IMAGE_HEADER_LEN + (image->width * image->height) * 4;
496}
497
498static XcursorBool
499_XcursorWriteImage (XcursorFile		*file,
500		    XcursorFileHeader	*fileHeader,
501		    int			toc,
502		    XcursorImage	*image)
503{
504    XcursorChunkHeader	chunkHeader;
505    int			n;
506    XcursorPixel	*p;
507
508    if (!file || !fileHeader || !image)
509        return XcursorFalse;
510
511    /* sanity check data */
512    if (image->width > XCURSOR_IMAGE_MAX_SIZE  ||
513	image->height > XCURSOR_IMAGE_MAX_SIZE)
514	return XcursorFalse;
515    if (image->width == 0 || image->height == 0)
516	return XcursorFalse;
517    if (image->xhot > image->width || image->yhot > image->height)
518	return XcursorFalse;
519
520    /* write chunk header */
521    chunkHeader.header = XCURSOR_IMAGE_HEADER_LEN;
522    chunkHeader.type = XCURSOR_IMAGE_TYPE;
523    chunkHeader.subtype = image->size;
524    chunkHeader.version = XCURSOR_IMAGE_VERSION;
525
526    if (!_XcursorFileWriteChunkHeader (file, fileHeader, toc, &chunkHeader))
527	return XcursorFalse;
528
529    /* write extra image header fields */
530    if (!_XcursorWriteUInt (file, image->width))
531	return XcursorFalse;
532    if (!_XcursorWriteUInt (file, image->height))
533	return XcursorFalse;
534    if (!_XcursorWriteUInt (file, image->xhot))
535	return XcursorFalse;
536    if (!_XcursorWriteUInt (file, image->yhot))
537	return XcursorFalse;
538    if (!_XcursorWriteUInt (file, image->delay))
539	return XcursorFalse;
540
541    /* write the image */
542    n = (int) (image->width * image->height);
543    p = image->pixels;
544    while (n--)
545    {
546	if (!_XcursorWriteUInt (file, *p))
547	    return XcursorFalse;
548	p++;
549    }
550    return XcursorTrue;
551}
552
553static XcursorComment *
554_XcursorReadComment (XcursorFile	    *file,
555		     XcursorFileHeader	    *fileHeader,
556		     int		    toc)
557{
558    XcursorChunkHeader	chunkHeader;
559    XcursorUInt		length;
560    XcursorComment	*comment;
561
562    if (!file || !fileHeader)
563        return NULL;
564
565    /* read chunk header */
566    if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
567	return NULL;
568    /* read extra comment header fields */
569    if (!_XcursorReadUInt (file, &length))
570	return NULL;
571    comment = XcursorCommentCreate (chunkHeader.subtype, (int) length);
572    if (!comment)
573	return NULL;
574    if (!_XcursorReadBytes (file, comment->comment, (int) length))
575    {
576	XcursorCommentDestroy (comment);
577	return NULL;
578    }
579    comment->comment[length] = '\0';
580    return comment;
581}
582
583static XcursorUInt
584_XcursorCommentLength (XcursorComment	    *comment)
585{
586    return XCURSOR_COMMENT_HEADER_LEN + (XcursorUInt) strlen (comment->comment);
587}
588
589static XcursorBool
590_XcursorWriteComment (XcursorFile	    *file,
591		      XcursorFileHeader	    *fileHeader,
592		      int		    toc,
593		      XcursorComment	    *comment)
594{
595    XcursorChunkHeader	chunkHeader;
596    XcursorUInt		length;
597
598    if (!file || !fileHeader || !comment || !comment->comment)
599        return XcursorFalse;
600
601    length = (XcursorUInt) strlen (comment->comment);
602
603    /* sanity check data */
604    if (length > XCURSOR_COMMENT_MAX_LEN)
605	return XcursorFalse;
606
607    /* read chunk header */
608    chunkHeader.header = XCURSOR_COMMENT_HEADER_LEN;
609    chunkHeader.type = XCURSOR_COMMENT_TYPE;
610    chunkHeader.subtype = comment->comment_type;
611    chunkHeader.version = XCURSOR_COMMENT_VERSION;
612
613    if (!_XcursorFileWriteChunkHeader (file, fileHeader, toc, &chunkHeader))
614	return XcursorFalse;
615
616    /* write extra comment header fields */
617    if (!_XcursorWriteUInt (file, length))
618	return XcursorFalse;
619
620    if (!_XcursorWriteBytes (file, comment->comment, (int) length))
621	return XcursorFalse;
622    return XcursorTrue;
623}
624
625XcursorImage *
626XcursorXcFileLoadImage (XcursorFile *file, int size)
627{
628    XcursorFileHeader	*fileHeader;
629    XcursorDim		bestSize;
630    int			nsize;
631    int			toc;
632    XcursorImage	*image;
633
634    if (size < 0)
635	return NULL;
636    fileHeader = _XcursorReadFileHeader (file);
637    if (!fileHeader)
638	return NULL;
639    bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
640    if (!bestSize)
641	return NULL;
642    toc = _XcursorFindImageToc (fileHeader, bestSize, 0);
643    if (toc < 0)
644	return NULL;
645    image = _XcursorReadImage (file, fileHeader, toc);
646    _XcursorFileHeaderDestroy (fileHeader);
647    return image;
648}
649
650XcursorImages *
651XcursorXcFileLoadImages (XcursorFile *file, int size)
652{
653    XcursorFileHeader	*fileHeader;
654    XcursorDim		bestSize;
655    int			nsize;
656    XcursorImages	*images;
657    int			n;
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	int 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    XcursorUInt		n;
704    XcursorUInt		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, (int) 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    XcursorUInt		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    {
784	_XcursorFileHeaderDestroy (fileHeader);
785	return 0;
786    }
787    comments = XcursorCommentsCreate (ncomment);
788    if (!comments)
789    {
790	_XcursorFileHeaderDestroy (fileHeader);
791	XcursorImagesDestroy (images);
792	return 0;
793    }
794    for (toc = 0; toc < fileHeader->ntoc; toc++)
795    {
796	switch (fileHeader->tocs[toc].type) {
797	case XCURSOR_COMMENT_TYPE:
798	    comment = _XcursorReadComment (file, fileHeader, (int) toc);
799	    if (comment)
800	    {
801		comments->comments[comments->ncomment] = comment;
802		comments->ncomment++;
803	    }
804	    break;
805	case XCURSOR_IMAGE_TYPE:
806	    image = _XcursorReadImage (file, fileHeader, (int) toc);
807	    if (image)
808	    {
809		images->images[images->nimage] = image;
810		images->nimage++;
811	    }
812	    break;
813	}
814    }
815    _XcursorFileHeaderDestroy (fileHeader);
816    if (images->nimage != nimage || comments->ncomment != ncomment)
817    {
818	XcursorImagesDestroy (images);
819	XcursorCommentsDestroy (comments);
820	images = NULL;
821	comments = NULL;
822	return XcursorFalse;
823    }
824    *imagesp = images;
825    *commentsp = comments;
826    return XcursorTrue;
827}
828
829XcursorBool
830XcursorXcFileSave (XcursorFile		    *file,
831		   const XcursorComments    *comments,
832		   const XcursorImages	    *images)
833{
834    XcursorFileHeader	*fileHeader;
835    XcursorUInt		position;
836    int			n;
837    int			toc;
838
839    if (!file || !comments || !images)
840        return XcursorFalse;
841
842    fileHeader = _XcursorFileHeaderCreate ((XcursorUInt) (comments->ncomment + images->nimage));
843    if (!fileHeader)
844	return XcursorFalse;
845
846    position = _XcursorFileHeaderLength (fileHeader);
847
848    /*
849     * Compute the toc.  Place the images before the comments
850     * as they're more often read
851     */
852
853    toc = 0;
854    for (n = 0; n < images->nimage; n++)
855    {
856	fileHeader->tocs[toc].type = XCURSOR_IMAGE_TYPE;
857	fileHeader->tocs[toc].subtype = images->images[n]->size;
858	fileHeader->tocs[toc].position = position;
859	position += _XcursorImageLength (images->images[n]);
860	toc++;
861    }
862
863    for (n = 0; n < comments->ncomment; n++)
864    {
865	fileHeader->tocs[toc].type = XCURSOR_COMMENT_TYPE;
866	fileHeader->tocs[toc].subtype = comments->comments[n]->comment_type;
867	fileHeader->tocs[toc].position = position;
868	position += _XcursorCommentLength (comments->comments[n]);
869	toc++;
870    }
871
872    /*
873     * Write the header and the toc
874     */
875    if (!_XcursorWriteFileHeader (file, fileHeader))
876	goto bail;
877
878    /*
879     * Write the images
880     */
881    toc = 0;
882    for (n = 0; n < images->nimage; n++)
883    {
884	if (!_XcursorWriteImage (file, fileHeader, toc, images->images[n]))
885	    goto bail;
886	toc++;
887    }
888
889    /*
890     * Write the comments
891     */
892    for (n = 0; n < comments->ncomment; n++)
893    {
894	if (!_XcursorWriteComment (file, fileHeader, toc, comments->comments[n]))
895	    goto bail;
896	toc++;
897    }
898
899    _XcursorFileHeaderDestroy (fileHeader);
900    return XcursorTrue;
901bail:
902    _XcursorFileHeaderDestroy (fileHeader);
903    return XcursorFalse;
904}
905
906static int
907_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
908{
909    FILE    *f = file->closure;
910    return (int) fread (buf, 1, (size_t) len, f);
911}
912
913static int
914_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
915{
916    FILE    *f = file->closure;
917    return (int) fwrite (buf, 1, (size_t) len, f);
918}
919
920static int
921_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
922{
923    FILE    *f = file->closure;
924    return fseek (f, offset, whence);
925}
926
927static void
928_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
929{
930    file->closure = stdfile;
931    file->read = _XcursorStdioFileRead;
932    file->write = _XcursorStdioFileWrite;
933    file->seek = _XcursorStdioFileSeek;
934}
935
936XcursorImage *
937XcursorFileLoadImage (FILE *file, int size)
938{
939    XcursorFile	f;
940
941    if (!file)
942        return NULL;
943
944    _XcursorStdioFileInitialize (file, &f);
945    return XcursorXcFileLoadImage (&f, size);
946}
947
948XcursorImages *
949XcursorFileLoadImages (FILE *file, int size)
950{
951    XcursorFile	f;
952
953    if (!file)
954        return NULL;
955
956    _XcursorStdioFileInitialize (file, &f);
957    return XcursorXcFileLoadImages (&f, size);
958}
959
960XcursorImages *
961XcursorFileLoadAllImages (FILE *file)
962{
963    XcursorFile	f;
964
965    if (!file)
966        return NULL;
967
968    _XcursorStdioFileInitialize (file, &f);
969    return XcursorXcFileLoadAllImages (&f);
970}
971
972XcursorBool
973XcursorFileLoad (FILE		    *file,
974		 XcursorComments    **commentsp,
975		 XcursorImages	    **imagesp)
976{
977    XcursorFile	f;
978
979    if (!file || !commentsp || !imagesp)
980        return XcursorFalse;
981
982    _XcursorStdioFileInitialize (file, &f);
983    return XcursorXcFileLoad (&f, commentsp, imagesp);
984}
985
986XcursorBool
987XcursorFileSaveImages (FILE *file, const XcursorImages *images)
988{
989    XcursorComments *comments;
990    XcursorFile	    f;
991    XcursorBool	    ret;
992
993    if (!file || !images)
994	return 0;
995    if ((comments = XcursorCommentsCreate (0)) == NULL)
996	return 0;
997    _XcursorStdioFileInitialize (file, &f);
998    ret = XcursorXcFileSave (&f, comments, images) && fflush (file) != EOF;
999    XcursorCommentsDestroy (comments);
1000    return ret;
1001}
1002
1003XcursorBool
1004XcursorFileSave (FILE *			file,
1005		 const XcursorComments	*comments,
1006		 const XcursorImages	*images)
1007{
1008    XcursorFile	    f;
1009
1010    if (!file || !comments || !images)
1011        return XcursorFalse;
1012
1013    _XcursorStdioFileInitialize (file, &f);
1014    return XcursorXcFileSave (&f, comments, images) && fflush (file) != EOF;
1015}
1016
1017XcursorImage *
1018XcursorFilenameLoadImage (const char *file, int size)
1019{
1020    FILE	    *f;
1021    XcursorImage    *image;
1022
1023    if (!file || size < 0)
1024        return NULL;
1025
1026    f = fopen (file, "r" FOPEN_CLOEXEC);
1027    if (!f)
1028	return NULL;
1029    image = XcursorFileLoadImage (f, size);
1030    fclose (f);
1031    return image;
1032}
1033
1034XcursorImages *
1035XcursorFilenameLoadImages (const char *file, int size)
1036{
1037    FILE	    *f;
1038    XcursorImages   *images;
1039
1040    if (!file || size < 0)
1041        return NULL;
1042
1043    f = fopen (file, "r" FOPEN_CLOEXEC);
1044    if (!f)
1045	return NULL;
1046    images = XcursorFileLoadImages (f, size);
1047    fclose (f);
1048    return images;
1049}
1050
1051XcursorImages *
1052XcursorFilenameLoadAllImages (const char *file)
1053{
1054    FILE	    *f;
1055    XcursorImages   *images;
1056
1057    if (!file)
1058        return NULL;
1059
1060    f = fopen (file, "r" FOPEN_CLOEXEC);
1061    if (!f)
1062	return NULL;
1063    images = XcursorFileLoadAllImages (f);
1064    fclose (f);
1065    return images;
1066}
1067
1068XcursorBool
1069XcursorFilenameLoad (const char		*file,
1070		     XcursorComments	**commentsp,
1071		     XcursorImages	**imagesp)
1072{
1073    FILE	    *f;
1074    XcursorBool	    ret;
1075
1076    if (!file)
1077        return XcursorFalse;
1078
1079    f = fopen (file, "r" FOPEN_CLOEXEC);
1080    if (!f)
1081	return 0;
1082    ret = XcursorFileLoad (f, commentsp, imagesp);
1083    fclose (f);
1084    return ret;
1085}
1086
1087XcursorBool
1088XcursorFilenameSaveImages (const char *file, const XcursorImages *images)
1089{
1090    FILE	    *f;
1091    XcursorBool	    ret;
1092
1093    if (!file || !images)
1094        return XcursorFalse;
1095
1096    f = fopen (file, "w" FOPEN_CLOEXEC);
1097    if (!f)
1098	return 0;
1099    ret = XcursorFileSaveImages (f, images);
1100    return fclose (f) != EOF && ret;
1101}
1102
1103XcursorBool
1104XcursorFilenameSave (const char		    *file,
1105		     const XcursorComments  *comments,
1106		     const XcursorImages    *images)
1107{
1108    FILE	    *f;
1109    XcursorBool	    ret;
1110
1111    if (!file || !comments || !images)
1112        return XcursorFalse;
1113
1114    f = fopen (file, "w" FOPEN_CLOEXEC);
1115    if (!f)
1116	return 0;
1117    ret = XcursorFileSave (f, comments, images);
1118    return fclose (f) != EOF && ret;
1119}
1120