/*
 * MultiGIF  V1.0  28-Jun-96  Andy Wardley
 *
 * (C) 1996 Andy Wardley <abw@peritas.com>.  All rights reserved.
 *
 * gifio.c
 *
 * File input/output for gif read/write functions.  GIF uses little-endian
 * byte ordering.
 *
 */


#include <ctype.h>
#include <stdlib.h>
#include "gifio.h"



/* 
 * BYTE read_byte(FILE *fp)
 *
 * Reads a byte from the file.
 */

BYTE read_byte(FILE *fp)
{
	return (BYTE) (fgetc(fp) & 0xff);
}



/* 
 * void write_byte(FILE *fp)
 *
 * Writes a byte to the file.
 */

void write_byte(BYTE b, FILE *fp)
{
	(void) fputc((int) b, fp);
}



/* 
 * WORD read_word(FILE *fp)
 *
 * Reads a word from the file in little-endian byte order
 */

WORD read_word(FILE *fp)
{
	register WORD w;

	w = (WORD) (fgetc(fp) & 0xff);
	w |= (WORD) (fgetc(fp) & 0xff) << 0x08;

	return w;
}



/* 
 * void write_word(FILE *fp)
 *
 * Writes a word to the file in little-endian byte order
 */

void write_word(WORD w, FILE *fp)
{
	(void) fputc(w  & 0xff, fp);
	(void) fputc(w >> 0x08, fp);
}



/*
 * int read_string(BYTE *buffer, int length, FILE *fp)
 *
 * Reads length characters from the file and places them in the 
 * buffer pointer to by *buffer.  Returns the number of characters
 * read.
 */

int read_string(BYTE *buffer, int length, FILE *fp)
{
	return fread(buffer, sizeof(BYTE), length, fp);
}



/*
 * int write_string(BYTE *buffer, int length, FILE *fp)
 *
 * Writes length bytes from the buffer to the file.  Returns the number of 
 * characters written.
 */

int write_string(BYTE *buffer, int length, FILE *fp)
{
	return fwrite(buffer, sizeof(BYTE), length, fp);
}



/*
 * BYTE *read_data(FILE *fp)
 *
 * Reads an extension block data stream, allocates memory for it and
 * returns a pointer to the memory.  Returns NULL on failure.
 */

BYTE *read_data(FILE *fp)
{
	static BYTE *buffer = NULL;
	BYTE *newbuffer, *ptr;
	WORD blocksize, buffersize = 0;

	/* ensure buffer ptr is NULL on re-entry */
	buffer = NULL;

	/* read each block */
	while ((blocksize = read_byte(fp)) != 0)
	{
		/* realloc is untrustworthy on my machine */
		if ((newbuffer = (BYTE *) malloc(buffersize + blocksize + 1)) == NULL)
		{
			/* free any existing buffer */
			if (buffer != NULL)
				free(buffer);

			return (BYTE *) NULL;
		}

		/* copy old buffer into new buffer */
		if (buffer != NULL)
		{
			(void) memcpy(newbuffer, buffer, buffersize);
			free(buffer);
		}

		/* point buffer at new extended buffer */
		buffer = newbuffer;

		/* point to start of new area (possibly offset into buffer) */
		ptr = buffer + buffersize;
			
		/* update buffersize */
		buffersize += blocksize + 1;

		/* set block size */
		*ptr++ = (char) (blocksize & 0xff);

		/* read data */
		while (blocksize--)
			*ptr++ = read_byte(fp);
	}

	/* terminate */
	*ptr = '\0';

	return buffer;
}



/*
 * int write_data(static BYTE *buffer, FILE *fp)
 *
 * Writes an extension block data stream to the file.  Returns 0 on success, 
 * -1 on failure.
 */

int write_data(BYTE *buffer, FILE *fp)
{
	BYTE *ptr = buffer;
	WORD blocksize;


	/* determine blocksize */
	while ((blocksize = (WORD) *ptr) != 0)
	{
		/* write blocksize and block data itself to file */
		if (fwrite(ptr, 1, (size_t) blocksize + 1, fp) != blocksize + 1)
			return -1;

		/* point past blocksize byte and block data itself */
		ptr += blocksize + 1;
	}

	/* terminate */
	write_byte(blocksize, fp);

	return 0;
}



/*
 * int read_gif_header(gif_header *gh, FILE *fp)
 *
 * Reads a GIF header from the file.  Returns 0 on success, -1 on failure.
 */

int read_gif_header(gif_header *gh, FILE *fp)
{
	/* read signature and version number */
	(void) read_string(gh->signature, sizeof(gh->signature), fp);
	(void) read_string(gh->version, sizeof(gh->version), fp);

	/* read screen size */
	gh->screen_width = read_word(fp);
	gh->screen_height = read_word(fp);

	/* rest of header */
	gh->packed = read_byte(fp);
	gh->background = read_byte(fp);
	gh->aspect_ratio = read_byte(fp);

	/* check for file error */
	if (ferror(fp))
		return -1;

	/* check GIF signature and version */
	if (strncmp((const char *) gh->signature, "GIF", 3) != 0)
		return -1;
	if (!isdigit(gh->version[0]) || !isdigit(gh->version[1])
			|| !isalpha(gh->version[2]))
		return -1;

	return 0;
}



/*
 * int write_gif_header(gif_header *gh, FILE *fp)
 *
 * Writes a GIF header to the file.  Returns 0 on success, -1 on failure.
 */

int write_gif_header(gif_header *gh, FILE *fp)
{
	/* write signature and version number */
	(void) write_string(gh->signature, sizeof(gh->signature), fp);
	(void) write_string(gh->version, sizeof(gh->version), fp);

	/* write screen size */
	write_word(gh->screen_width, fp);
	write_word(gh->screen_height, fp);

	/* rest of header */
	write_byte(gh->packed, fp);
	write_byte(gh->background, fp);
	write_byte(gh->aspect_ratio, fp);

	/* check for file error */
	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int read_gif_colour_table(gif_colour *gct, int size, FILE *fp)
 *
 * Reads a GIF colour table from the file.  The number of elements is 
 * specified by size.  Returns 0 on success or -1 on failure.
 */

int read_gif_colour_table(gif_colour *gct, int size, FILE *fp)
{
	register int col;

	/* read a block of colours */
	for (col = 0; col < size; col++)
	{
		gct[col].red = read_byte(fp);
		gct[col].green = read_byte(fp);
		gct[col].blue = read_byte(fp);
	}

	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int write_gif_colour_table(gif_colour *gct, int size, FILE *fp)
 *
 * Writes a GIF colour table to the file.  The number of elements is 
 * specified by size.  Returns 0 on success or -1 on failure.
 */

int write_gif_colour_table(gif_colour *gct, int size, FILE *fp)
{
	register int col;

	/* write a block of colours */
	for (col = 0; col < size; col++)
	{
		write_byte(gct[col].red, fp);
		write_byte(gct[col].green, fp);
		write_byte(gct[col].blue, fp);
	}

	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int read_gif_img_desc(gif_img_desc *gid, FILE *fp)
 *
 * Reads a GIF image description block.  Returns 0 on success or -1
 * on failure.
 */

int read_gif_img_desc(gif_img_desc *gid, FILE *fp)
{
	/* note that the seperator has already been read */

	/* read image dimensions */
	gid->left = read_word(fp);
	gid->top = read_word(fp);
	gid->width = read_word(fp);
	gid->height = read_word(fp);

	/* read packed flags */
	gid->packed = read_byte(fp);
	
	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int write_gif_img_desc(gif_img_desc *gid, FILE *fp)
 *
 * Writes a GIF image description block.  Returns 0 on success or -1
 * on failure.
 */

int write_gif_img_desc(gif_img_desc *gid, FILE *fp)
{
	/* write image description seperator */
	write_byte(GIF_IMAGE_DESC, fp);	

	/* write image dimensions */
	write_word(gid->left, fp);
	write_word(gid->top, fp);
	write_word(gid->width, fp);
	write_word(gid->height, fp);

	/* write packed flags */
	write_byte(gid->packed, fp);
	
	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int read_gif_graphic_control(gif_graphic_control *ggc, FILE *fp)
 *
 * Reads a GIF graphic control block.  Returns 0 on success or -1
 * on failure.
 */

int read_gif_graphic_control(gif_graphic_control *ggc, FILE *fp)
{
	/* note that the introducer and label have already been read */

	ggc->blocksize = read_byte(fp);
	ggc->packed = read_byte(fp);
	ggc->delay = read_word(fp);
	ggc->transparent = read_byte(fp);
	ggc->terminator = read_byte(fp);

	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int write_gif_graphic_control(gif_graphic_control *ggc, FILE *fp)
 *
 * Writes a GIF graphic control block.  Returns 0 on success or -1
 * on failure.
 */

int write_gif_graphic_control(gif_graphic_control *ggc, FILE *fp)
{
	/* write seperator and label */
	write_byte(GIF_EXTENSION, fp);
	write_byte(GIF_GRAPHIC_EXT, fp);

	write_byte(ggc->blocksize, fp);
	write_byte(ggc->packed, fp);
	write_word(ggc->delay, fp);
	write_byte(ggc->transparent, fp);
	write_byte(ggc->terminator, fp);

	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int read_gif_plain_text(gif_plain_text *gpt, FILE *fp)
 *
 * Reads a GIF plain text block.  Returns 0 on success or -1
 * on failure.
 */

int read_gif_plain_text(gif_plain_text *gpt, FILE *fp)
{
	/* note that the introducer and label have already been read */
	
	/* read blocksize */
	gpt->blocksize = read_byte(fp);

	/* read grid size */
	gpt->text_grid_left = read_word(fp);
	gpt->text_grid_top = read_word(fp);
	gpt->text_grid_width = read_word(fp);
	gpt->text_grid_height = read_word(fp);

	/* read cell size */
	gpt->cell_width = read_byte(fp);
	gpt->cell_height = read_byte(fp);

	/* read text colours */
	gpt->text_fg_colour = read_byte(fp);
	gpt->text_bg_colour = read_byte(fp);

	/* read plain text data */
	gpt->plain_text_data = read_data(fp);

	/* set terminator to 0 (terminator was read by read_data) */
	gpt->terminator = 0;

	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int write_gif_plain_text(gif_plain_text *gpt, FILE *fp)
 *
 * Writes a GIF plain text block.  Returns 0 on success or -1
 * on failure.
 */

int write_gif_plain_text(gif_plain_text *gpt, FILE *fp)
{
	/* write seperator and label */
	write_byte(GIF_EXTENSION, fp);
	write_byte(GIF_TEXT_EXT, fp);
	
	/* write blocksize */
	write_byte(gpt->blocksize, fp);

	/* write grid size */
	write_word(gpt->text_grid_left, fp);
	write_word(gpt->text_grid_top, fp);
	write_word(gpt->text_grid_width, fp);
	write_word(gpt->text_grid_height, fp);

	/* write cell size */
	write_byte(gpt->cell_width, fp);
	write_byte(gpt->cell_height, fp);

	/* write text colours */
	write_byte(gpt->text_fg_colour, fp);
	write_byte(gpt->text_bg_colour, fp);

	/* write plain text data */
	return write_data(gpt->plain_text_data, fp);
}



/*
 * int read_gif_app_ext(gif_app_ext *gae, FILE *fp)
 *
 * Reads a GIF application extension block.  Returns 0 on success or -1
 * on failure.
 */

int read_gif_app_ext(gif_app_ext *gae, FILE *fp)
{
	/* note that the introducer and label have already been read */
	
	/* read blocksize */
	gae->blocksize = read_byte(fp);

	/* read identifier and authcode */
	(void) read_string(gae->identifier, sizeof(gae->identifier), fp);
	(void) read_string(gae->authcode, sizeof(gae->authcode), fp);

	/* read application data */
	gae->app_data = read_data(fp);

	/* set terminator to 0 (terminator was read by read_data) */
	gae->terminator = 0;

	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int write_gif_app_ext(gif_app_ext *gae, FILE *fp)
 *
 * Writes a GIF application extension block.  Returns 0 on success or -1
 * on failure.
 */

int write_gif_app_ext(gif_app_ext *gae, FILE *fp)
{
	/* write seperator and label */
	write_byte(GIF_EXTENSION, fp);
	write_byte(GIF_APP_EXT, fp);
	
	/* write blocksize */
	write_byte(gae->blocksize, fp);

	/* write identifier and authcode */
	(void) write_string(gae->identifier, sizeof(gae->identifier), fp);
	(void) write_string(gae->authcode, sizeof(gae->authcode), fp);

	/* write application data */
	return write_data(gae->app_data, fp);
}



/*
 * int read_gif_comment(gif_comment *gc, FILE *fp)
 *
 * Reads a GIF comment block.  Returns 0 on success or -1
 * on failure.
 */

int read_gif_comment(gif_comment *gc, FILE *fp)
{
	/* note that the introducer and label have already been read */
	
	/* read comment data */
	gc->comment_data = read_data(fp);

	/* set terminator to 0 (terminator was read by read_data) */
	gc->terminator = 0;

	if (ferror(fp))
		return -1;

	return 0;
}



/*
 * int write_gif_comment(gif_comment *gc, FILE *fp)
 *
 * Writes a GIF comment block.  Returns 0 on success or -1
 * on failure.
 */

int write_gif_comment(gif_comment *gc, FILE *fp)
{
	/* write seperator and label */
	write_byte(GIF_EXTENSION, fp);
	write_byte(GIF_COMMENT_EXT, fp);
	
	/* write comment data */
	return write_data(gc->comment_data, fp);
}

