/* * * Copyright © 2002 Keith Packard * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Keith Packard not be used in * advertising or publicity pertaining to distribution of the software without * specific, written prior permission. Keith Packard makes no * representations about the suitability of this software for any purpose. It * is provided "as is" without express or implied warranty. * * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "xcursorint.h" #include #include XcursorImage * XcursorImageCreate (int width, int height) { XcursorImage *image; image = malloc (sizeof (XcursorImage) + width * height * sizeof (XcursorPixel)); if (!image) return 0; image->version = XCURSOR_IMAGE_VERSION; image->pixels = (XcursorPixel *) (image + 1); image->size = width > height ? width : height; image->width = width; image->height = height; image->delay = 0; return image; } void XcursorImageDestroy (XcursorImage *image) { free (image); } XcursorImages * XcursorImagesCreate (int size) { XcursorImages *images; images = malloc (sizeof (XcursorImages) + size * sizeof (XcursorImage *)); if (!images) return 0; images->nimage = 0; images->images = (XcursorImage **) (images + 1); images->name = 0; return images; } void XcursorImagesDestroy (XcursorImages *images) { int n; for (n = 0; n < images->nimage; n++) XcursorImageDestroy (images->images[n]); if (images->name) free (images->name); free (images); } void XcursorImagesSetName (XcursorImages *images, const char *name) { char *new = malloc (strlen (name) + 1); if (!new) return; strcpy (new, name); if (images->name) free (images->name); images->name = new; } XcursorComment * XcursorCommentCreate (XcursorUInt comment_type, int length) { XcursorComment *comment; if (length > XCURSOR_COMMENT_MAX_LEN) return 0; comment = malloc (sizeof (XcursorComment) + length + 1); if (!comment) return 0; comment->version = XCURSOR_COMMENT_VERSION; comment->comment_type = comment_type; comment->comment = (char *) (comment + 1); comment->comment[0] = '\0'; return comment; } void XcursorCommentDestroy (XcursorComment *comment) { free (comment); } XcursorComments * XcursorCommentsCreate (int size) { XcursorComments *comments; comments = malloc (sizeof (XcursorComments) + size * sizeof (XcursorComment *)); if (!comments) return 0; comments->ncomment = 0; comments->comments = (XcursorComment **) (comments + 1); return comments; } void XcursorCommentsDestroy (XcursorComments *comments) { int n; for (n = 0; n < comments->ncomment; n++) XcursorCommentDestroy (comments->comments[n]); free (comments); } static XcursorBool _XcursorReadUInt (XcursorFile *file, XcursorUInt *u) { unsigned char bytes[4]; if ((*file->read) (file, bytes, 4) != 4) return XcursorFalse; *u = ((bytes[0] << 0) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)); return XcursorTrue; } static XcursorBool _XcursorReadBytes (XcursorFile *file, char *bytes, int length) { if ((*file->read) (file, (unsigned char *) bytes, length) != length) return XcursorFalse; return XcursorTrue; } static XcursorBool _XcursorWriteUInt (XcursorFile *file, XcursorUInt u) { unsigned char bytes[4]; bytes[0] = u; bytes[1] = u >> 8; bytes[2] = u >> 16; bytes[3] = u >> 24; if ((*file->write) (file, bytes, 4) != 4) return XcursorFalse; return XcursorTrue; } static XcursorBool _XcursorWriteBytes (XcursorFile *file, char *bytes, int length) { if ((*file->write) (file, (unsigned char *) bytes, length) != length) return XcursorFalse; return XcursorTrue; } static void _XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader) { free (fileHeader); } static XcursorFileHeader * _XcursorFileHeaderCreate (int ntoc) { XcursorFileHeader *fileHeader; if (ntoc > 0x10000) return 0; fileHeader = malloc (sizeof (XcursorFileHeader) + ntoc * sizeof (XcursorFileToc)); if (!fileHeader) return 0; fileHeader->magic = XCURSOR_MAGIC; fileHeader->header = XCURSOR_FILE_HEADER_LEN; fileHeader->version = XCURSOR_FILE_VERSION; fileHeader->ntoc = ntoc; fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1); return fileHeader; } static XcursorFileHeader * _XcursorReadFileHeader (XcursorFile *file) { XcursorFileHeader head, *fileHeader; XcursorUInt skip; int n; if (!_XcursorReadUInt (file, &head.magic)) return 0; if (head.magic != XCURSOR_MAGIC) return 0; if (!_XcursorReadUInt (file, &head.header)) return 0; if (!_XcursorReadUInt (file, &head.version)) return 0; if (!_XcursorReadUInt (file, &head.ntoc)) return 0; skip = head.header - XCURSOR_FILE_HEADER_LEN; if (skip) if ((*file->seek) (file, skip, SEEK_CUR) == EOF) return 0; fileHeader = _XcursorFileHeaderCreate (head.ntoc); if (!fileHeader) return 0; fileHeader->magic = head.magic; fileHeader->header = head.header; fileHeader->version = head.version; fileHeader->ntoc = head.ntoc; for (n = 0; n < fileHeader->ntoc; n++) { if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type)) break; if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype)) break; if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position)) break; } if (n != fileHeader->ntoc) { _XcursorFileHeaderDestroy (fileHeader); return 0; } return fileHeader; } static XcursorUInt _XcursorFileHeaderLength (XcursorFileHeader *fileHeader) { return (XCURSOR_FILE_HEADER_LEN + fileHeader->ntoc * XCURSOR_FILE_TOC_LEN); } static XcursorBool _XcursorWriteFileHeader (XcursorFile *file, XcursorFileHeader *fileHeader) { int toc; if (!_XcursorWriteUInt (file, fileHeader->magic)) return XcursorFalse; if (!_XcursorWriteUInt (file, fileHeader->header)) return XcursorFalse; if (!_XcursorWriteUInt (file, fileHeader->version)) return XcursorFalse; if (!_XcursorWriteUInt (file, fileHeader->ntoc)) return XcursorFalse; for (toc = 0; toc < fileHeader->ntoc; toc++) { if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].type)) return XcursorFalse; if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].subtype)) return XcursorFalse; if (!_XcursorWriteUInt (file, fileHeader->tocs[toc].position)) return XcursorFalse; } return XcursorTrue; } static XcursorBool _XcursorSeekToToc (XcursorFile *file, XcursorFileHeader *fileHeader, int toc) { if ((*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF) return XcursorFalse; return XcursorTrue; } static XcursorBool _XcursorFileReadChunkHeader (XcursorFile *file, XcursorFileHeader *fileHeader, int toc, XcursorChunkHeader *chunkHeader) { if (!_XcursorSeekToToc (file, fileHeader, toc)) return XcursorFalse; if (!_XcursorReadUInt (file, &chunkHeader->header)) return XcursorFalse; if (!_XcursorReadUInt (file, &chunkHeader->type)) return XcursorFalse; if (!_XcursorReadUInt (file, &chunkHeader->subtype)) return XcursorFalse; if (!_XcursorReadUInt (file, &chunkHeader->version)) return XcursorFalse; /* sanity check */ if (chunkHeader->type != fileHeader->tocs[toc].type || chunkHeader->subtype != fileHeader->tocs[toc].subtype) return XcursorFalse; return XcursorTrue; } static XcursorBool _XcursorFileWriteChunkHeader (XcursorFile *file, XcursorFileHeader *fileHeader, int toc, XcursorChunkHeader *chunkHeader) { if (!_XcursorSeekToToc (file, fileHeader, toc)) return XcursorFalse; if (!_XcursorWriteUInt (file, chunkHeader->header)) return XcursorFalse; if (!_XcursorWriteUInt (file, chunkHeader->type)) return XcursorFalse; if (!_XcursorWriteUInt (file, chunkHeader->subtype)) return XcursorFalse; if (!_XcursorWriteUInt (file, chunkHeader->version)) return XcursorFalse; return XcursorTrue; } #define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a)) static XcursorDim _XcursorFindBestSize (XcursorFileHeader *fileHeader, XcursorDim size, int *nsizesp) { int n; int nsizes = 0; XcursorDim bestSize = 0; XcursorDim thisSize; for (n = 0; n < fileHeader->ntoc; n++) { if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE) continue; thisSize = fileHeader->tocs[n].subtype; if (!bestSize || dist (thisSize, size) < dist (bestSize, size)) { bestSize = thisSize; nsizes = 1; } else if (thisSize == bestSize) nsizes++; } *nsizesp = nsizes; return bestSize; } static int _XcursorFindImageToc (XcursorFileHeader *fileHeader, XcursorDim size, int count) { int toc; XcursorDim thisSize; for (toc = 0; toc < fileHeader->ntoc; toc++) { if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE) continue; thisSize = fileHeader->tocs[toc].subtype; if (thisSize != size) continue; if (!count) break; count--; } if (toc == fileHeader->ntoc) return -1; return toc; } static XcursorImage * _XcursorReadImage (XcursorFile *file, XcursorFileHeader *fileHeader, int toc) { XcursorChunkHeader chunkHeader; XcursorImage head; XcursorImage *image; int n; XcursorPixel *p; if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader)) return 0; if (!_XcursorReadUInt (file, &head.width)) return 0; if (!_XcursorReadUInt (file, &head.height)) return 0; if (!_XcursorReadUInt (file, &head.xhot)) return 0; if (!_XcursorReadUInt (file, &head.yhot)) return 0; if (!_XcursorReadUInt (file, &head.delay)) return 0; /* sanity check data */ if (head.width >= 0x10000 || head.height > 0x10000) return 0; if (head.width == 0 || head.height == 0) return 0; if (head.xhot > head.width || head.yhot > head.height) return 0; /* Create the image and initialize it */ image = XcursorImageCreate (head.width, head.height); if (chunkHeader.version < image->version) image->version = chunkHeader.version; image->size = chunkHeader.subtype; image->xhot = head.xhot; image->yhot = head.yhot; image->delay = head.delay; n = image->width * image->height; p = image->pixels; while (n--) { if (!_XcursorReadUInt (file, p)) { XcursorImageDestroy (image); return 0; } p++; } return image; } static XcursorUInt _XcursorImageLength (XcursorImage *image) { return XCURSOR_IMAGE_HEADER_LEN + (image->width * image->height) * 4; } static XcursorBool _XcursorWriteImage (XcursorFile *file, XcursorFileHeader *fileHeader, int toc, XcursorImage *image) { XcursorChunkHeader chunkHeader; int n; XcursorPixel *p; /* sanity check data */ if (image->width > XCURSOR_IMAGE_MAX_SIZE || image->height > XCURSOR_IMAGE_MAX_SIZE) return XcursorFalse; if (image->width == 0 || image->height == 0) return XcursorFalse; if (image->xhot > image->width || image->yhot > image->height) return XcursorFalse; /* write chunk header */ chunkHeader.header = XCURSOR_IMAGE_HEADER_LEN; chunkHeader.type = XCURSOR_IMAGE_TYPE; chunkHeader.subtype = image->size; chunkHeader.version = XCURSOR_IMAGE_VERSION; if (!_XcursorFileWriteChunkHeader (file, fileHeader, toc, &chunkHeader)) return XcursorFalse; /* write extra image header fields */ if (!_XcursorWriteUInt (file, image->width)) return XcursorFalse; if (!_XcursorWriteUInt (file, image->height)) return XcursorFalse; if (!_XcursorWriteUInt (file, image->xhot)) return XcursorFalse; if (!_XcursorWriteUInt (file, image->yhot)) return XcursorFalse; if (!_XcursorWriteUInt (file, image->delay)) return XcursorFalse; /* write the image */ n = image->width * image->height; p = image->pixels; while (n--) { if (!_XcursorWriteUInt (file, *p)) return XcursorFalse; p++; } return XcursorTrue; } static XcursorComment * _XcursorReadComment (XcursorFile *file, XcursorFileHeader *fileHeader, int toc) { XcursorChunkHeader chunkHeader; XcursorUInt length; XcursorComment *comment; /* read chunk header */ if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader)) return 0; /* read extra comment header fields */ if (!_XcursorReadUInt (file, &length)) return 0; comment = XcursorCommentCreate (chunkHeader.subtype, length); if (!comment) return 0; if (!_XcursorReadBytes (file, comment->comment, length)) { XcursorCommentDestroy (comment); return 0; } comment->comment[length] = '\0'; return comment; } static XcursorUInt _XcursorCommentLength (XcursorComment *comment) { return XCURSOR_COMMENT_HEADER_LEN + strlen (comment->comment); } static XcursorBool _XcursorWriteComment (XcursorFile *file, XcursorFileHeader *fileHeader, int toc, XcursorComment *comment) { XcursorChunkHeader chunkHeader; XcursorUInt length; length = strlen (comment->comment); /* sanity check data */ if (length > XCURSOR_COMMENT_MAX_LEN) return XcursorFalse; /* read chunk header */ chunkHeader.header = XCURSOR_COMMENT_HEADER_LEN; chunkHeader.type = XCURSOR_COMMENT_TYPE; chunkHeader.subtype = comment->comment_type; chunkHeader.version = XCURSOR_COMMENT_VERSION; if (!_XcursorFileWriteChunkHeader (file, fileHeader, toc, &chunkHeader)) return XcursorFalse; /* write extra comment header fields */ if (!_XcursorWriteUInt (file, length)) return XcursorFalse; if (!_XcursorWriteBytes (file, comment->comment, length)) return XcursorFalse; return XcursorTrue; } XcursorImage * XcursorXcFileLoadImage (XcursorFile *file, int size) { XcursorFileHeader *fileHeader; XcursorDim bestSize; int nsize; int toc; XcursorImage *image; if (size < 0) return 0; fileHeader = _XcursorReadFileHeader (file); if (!fileHeader) return 0; bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize); if (!bestSize) return 0; toc = _XcursorFindImageToc (fileHeader, bestSize, 0); if (toc < 0) return 0; image = _XcursorReadImage (file, fileHeader, toc); _XcursorFileHeaderDestroy (fileHeader); return image; } XcursorImages * XcursorXcFileLoadImages (XcursorFile *file, int size) { XcursorFileHeader *fileHeader; XcursorDim bestSize; int nsize; XcursorImages *images; int n; int toc; if (size < 0) return 0; fileHeader = _XcursorReadFileHeader (file); if (!fileHeader) return 0; bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize); if (!bestSize) return 0; images = XcursorImagesCreate (nsize); if (!images) return 0; for (n = 0; n < nsize; n++) { toc = _XcursorFindImageToc (fileHeader, bestSize, n); if (toc < 0) break; images->images[images->nimage] = _XcursorReadImage (file, fileHeader, toc); if (!images->images[images->nimage]) break; images->nimage++; } _XcursorFileHeaderDestroy (fileHeader); if (images->nimage != nsize) { XcursorImagesDestroy (images); images = 0; } return images; } XcursorImages * XcursorXcFileLoadAllImages (XcursorFile *file) { XcursorFileHeader *fileHeader; XcursorImage *image; XcursorImages *images; int nimage; int n; int toc; fileHeader = _XcursorReadFileHeader (file); if (!fileHeader) return 0; nimage = 0; for (n = 0; n < fileHeader->ntoc; n++) { switch (fileHeader->tocs[n].type) { case XCURSOR_IMAGE_TYPE: nimage++; break; } } images = XcursorImagesCreate (nimage); if (!images) return 0; for (toc = 0; toc < fileHeader->ntoc; toc++) { switch (fileHeader->tocs[toc].type) { case XCURSOR_IMAGE_TYPE: image = _XcursorReadImage (file, fileHeader, toc); if (image) { images->images[images->nimage] = image; images->nimage++; } break; } } _XcursorFileHeaderDestroy (fileHeader); if (images->nimage != nimage) { XcursorImagesDestroy (images); images = 0; } return images; } XcursorBool XcursorXcFileLoad (XcursorFile *file, XcursorComments **commentsp, XcursorImages **imagesp) { XcursorFileHeader *fileHeader; int nimage; int ncomment; XcursorImages *images; XcursorImage *image; XcursorComment *comment; XcursorComments *comments; int toc; fileHeader = _XcursorReadFileHeader (file); if (!fileHeader) return 0; nimage = 0; ncomment = 0; for (toc = 0; toc < fileHeader->ntoc; toc++) { switch (fileHeader->tocs[toc].type) { case XCURSOR_COMMENT_TYPE: ncomment++; break; case XCURSOR_IMAGE_TYPE: nimage++; break; } } images = XcursorImagesCreate (nimage); if (!images) return 0; comments = XcursorCommentsCreate (ncomment); if (!comments) { XcursorImagesDestroy (images); return 0; } for (toc = 0; toc < fileHeader->ntoc; toc++) { switch (fileHeader->tocs[toc].type) { case XCURSOR_COMMENT_TYPE: comment = _XcursorReadComment (file, fileHeader, toc); if (comment) { comments->comments[comments->ncomment] = comment; comments->ncomment++; } break; case XCURSOR_IMAGE_TYPE: image = _XcursorReadImage (file, fileHeader, toc); if (image) { images->images[images->nimage] = image; images->nimage++; } break; } } _XcursorFileHeaderDestroy (fileHeader); if (images->nimage != nimage || comments->ncomment != ncomment) { XcursorImagesDestroy (images); XcursorCommentsDestroy (comments); images = 0; comments = 0; return XcursorFalse; } *imagesp = images; *commentsp = comments; return XcursorTrue; } XcursorBool XcursorXcFileSave (XcursorFile *file, const XcursorComments *comments, const XcursorImages *images) { XcursorFileHeader *fileHeader; XcursorUInt position; int n; int toc; fileHeader = _XcursorFileHeaderCreate (comments->ncomment + images->nimage); if (!fileHeader) return XcursorFalse; position = _XcursorFileHeaderLength (fileHeader); /* * Compute the toc. Place the images before the comments * as they're more often read */ toc = 0; for (n = 0; n < images->nimage; n++) { fileHeader->tocs[toc].type = XCURSOR_IMAGE_TYPE; fileHeader->tocs[toc].subtype = images->images[n]->size; fileHeader->tocs[toc].position = position; position += _XcursorImageLength (images->images[n]); toc++; } for (n = 0; n < comments->ncomment; n++) { fileHeader->tocs[toc].type = XCURSOR_COMMENT_TYPE; fileHeader->tocs[toc].subtype = comments->comments[n]->comment_type; fileHeader->tocs[toc].position = position; position += _XcursorCommentLength (comments->comments[n]); toc++; } /* * Write the header and the toc */ if (!_XcursorWriteFileHeader (file, fileHeader)) goto bail; /* * Write the images */ toc = 0; for (n = 0; n < images->nimage; n++) { if (!_XcursorWriteImage (file, fileHeader, toc, images->images[n])) goto bail; toc++; } /* * Write the comments */ for (n = 0; n < comments->ncomment; n++) { if (!_XcursorWriteComment (file, fileHeader, toc, comments->comments[n])) goto bail; toc++; } _XcursorFileHeaderDestroy (fileHeader); return XcursorTrue; bail: _XcursorFileHeaderDestroy (fileHeader); return XcursorFalse; } static int _XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len) { FILE *f = file->closure; return fread (buf, 1, len, f); } static int _XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len) { FILE *f = file->closure; return fwrite (buf, 1, len, f); } static int _XcursorStdioFileSeek (XcursorFile *file, long offset, int whence) { FILE *f = file->closure; return fseek (f, offset, whence); } static void _XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file) { file->closure = stdfile; file->read = _XcursorStdioFileRead; file->write = _XcursorStdioFileWrite; file->seek = _XcursorStdioFileSeek; } XcursorImage * XcursorFileLoadImage (FILE *file, int size) { XcursorFile f; _XcursorStdioFileInitialize (file, &f); return XcursorXcFileLoadImage (&f, size); } XcursorImages * XcursorFileLoadImages (FILE *file, int size) { XcursorFile f; _XcursorStdioFileInitialize (file, &f); return XcursorXcFileLoadImages (&f, size); } XcursorImages * XcursorFileLoadAllImages (FILE *file) { XcursorFile f; _XcursorStdioFileInitialize (file, &f); return XcursorXcFileLoadAllImages (&f); } XcursorBool XcursorFileLoad (FILE *file, XcursorComments **commentsp, XcursorImages **imagesp) { XcursorFile f; _XcursorStdioFileInitialize (file, &f); return XcursorXcFileLoad (&f, commentsp, imagesp); } XcursorBool XcursorFileSaveImages (FILE *file, const XcursorImages *images) { XcursorComments *comments = XcursorCommentsCreate (0); XcursorFile f; XcursorBool ret; if (!comments) return 0; _XcursorStdioFileInitialize (file, &f); ret = XcursorXcFileSave (&f, comments, images) && fflush (file) != EOF; XcursorCommentsDestroy (comments); return ret; } XcursorBool XcursorFileSave (FILE * file, const XcursorComments *comments, const XcursorImages *images) { XcursorFile f; _XcursorStdioFileInitialize (file, &f); return XcursorXcFileSave (&f, comments, images) && fflush (file) != EOF; } XcursorImage * XcursorFilenameLoadImage (const char *file, int size) { FILE *f; XcursorImage *image; f = fopen (file, "r"); if (!f) return 0; image = XcursorFileLoadImage (f, size); fclose (f); return image; } XcursorImages * XcursorFilenameLoadImages (const char *file, int size) { FILE *f; XcursorImages *images; f = fopen (file, "r"); if (!f) return 0; images = XcursorFileLoadImages (f, size); fclose (f); return images; } XcursorImages * XcursorFilenameLoadAllImages (const char *file) { FILE *f; XcursorImages *images; f = fopen (file, "r"); if (!f) return 0; images = XcursorFileLoadAllImages (f); fclose (f); return images; } XcursorBool XcursorFilenameLoad (const char *file, XcursorComments **commentsp, XcursorImages **imagesp) { FILE *f; XcursorBool ret; f = fopen (file, "r"); if (!f) return 0; ret = XcursorFileLoad (f, commentsp, imagesp); fclose (f); return ret; } XcursorBool XcursorFilenameSaveImages (const char *file, const XcursorImages *images) { FILE *f; XcursorBool ret; f = fopen (file, "w"); if (!f) return 0; ret = XcursorFileSaveImages (f, images); return fclose (f) != EOF && ret; } XcursorBool XcursorFilenameSave (const char *file, const XcursorComments *comments, const XcursorImages *images) { FILE *f; XcursorBool ret; f = fopen (file, "w"); if (!f) return 0; ret = XcursorFileSave (f, comments, images); return fclose (f) != EOF && ret; }